1 package org.apache.turbine.services.intake;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.beans.IntrospectionException;
23 import java.beans.PropertyDescriptor;
24
25 import java.io.File;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.InputStream;
29 import java.io.ObjectInputStream;
30 import java.io.ObjectOutputStream;
31 import java.io.OutputStream;
32
33 import java.lang.reflect.Method;
34
35 import java.util.HashMap;
36 import java.util.HashSet;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Set;
41 import java.util.Vector;
42
43 import javax.servlet.ServletConfig;
44
45 import org.apache.commons.lang.StringUtils;
46
47 import org.apache.commons.logging.Log;
48 import org.apache.commons.logging.LogFactory;
49
50 import org.apache.commons.pool.KeyedObjectPool;
51 import org.apache.commons.pool.KeyedPoolableObjectFactory;
52 import org.apache.commons.pool.impl.StackKeyedObjectPool;
53
54 import org.apache.turbine.Turbine;
55 import org.apache.turbine.services.InitializationException;
56 import org.apache.turbine.services.TurbineBaseService;
57 import org.apache.turbine.services.intake.model.Group;
58 import org.apache.turbine.services.intake.transform.XmlToAppData;
59 import org.apache.turbine.services.intake.xmlmodel.AppData;
60 import org.apache.turbine.services.intake.xmlmodel.XmlGroup;
61
62 /***
63 * This service provides access to input processing objects based
64 * on an XML specification.
65 *
66 * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
67 * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
68 * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
69 * @version $Id: TurbineIntakeService.java 534527 2007-05-02 16:10:59Z tv $
70 */
71 public class TurbineIntakeService
72 extends TurbineBaseService
73 implements IntakeService
74 {
75 /*** Map of groupNames -> appData elements */
76 private Map groupNames;
77
78 /*** The cache of group names. */
79 private Map groupNameMap;
80
81 /*** The cache of group keys. */
82 private Map groupKeyMap;
83
84 /*** The cache of property getters. */
85 private Map getterMap;
86
87 /*** The cache of property setters. */
88 private Map setterMap;
89
90 /*** AppData -> keyed Pools Map */
91 private Map keyedPools;
92
93 /*** Used for logging */
94 private static Log log = LogFactory.getLog(TurbineIntakeService.class);
95
96 /***
97 * Constructor. All Components need a public no argument constructor
98 * to be a legal Component.
99 */
100 public TurbineIntakeService()
101 {
102 }
103
104 /***
105 * Called the first time the Service is used.
106 *
107 * @throws InitializationException Something went wrong in the init
108 * stage
109 */
110 public void init()
111 throws InitializationException
112 {
113 Vector defaultXmlPathes = new Vector();
114 defaultXmlPathes.add(XML_PATH_DEFAULT);
115
116 List xmlPathes = getConfiguration()
117 .getList(XML_PATH, defaultXmlPathes);
118
119 Map appDataElements = null;
120
121 String serialDataPath = getConfiguration()
122 .getString(SERIAL_XML, SERIAL_XML_DEFAULT);
123
124 if (!serialDataPath.equalsIgnoreCase("none"))
125 {
126 serialDataPath = Turbine.getRealPath(serialDataPath);
127 }
128 else
129 {
130 serialDataPath = null;
131 }
132
133 log.debug("Path for serializing: " + serialDataPath);
134
135 groupNames = new HashMap();
136 groupKeyMap = new HashMap();
137 groupNameMap = new HashMap();
138 getterMap = new HashMap();
139 setterMap = new HashMap();
140 keyedPools = new HashMap();
141
142 if (xmlPathes == null)
143 {
144 String LOAD_ERROR = "No pathes for XML files were specified. " +
145 "Check that the property exists in " +
146 "TurbineResources.props and were loaded.";
147
148 log.error(LOAD_ERROR);
149 throw new InitializationException(LOAD_ERROR);
150 }
151
152 Set xmlFiles = new HashSet();
153
154 long timeStamp = 0;
155
156 for (Iterator it = xmlPathes.iterator(); it.hasNext();)
157 {
158
159 String xmlPath = Turbine.getRealPath((String) it.next());
160 File xmlFile = new File(xmlPath);
161
162 log.debug("Path for XML File: " + xmlFile);
163
164 if (!xmlFile.canRead())
165 {
166 String READ_ERR = "Could not read input file " + xmlPath;
167
168 log.error(READ_ERR);
169 throw new InitializationException(READ_ERR);
170 }
171
172 xmlFiles.add(xmlPath);
173
174 log.debug("Added " + xmlPath + " as File to parse");
175
176
177
178
179 timeStamp =
180 (xmlFile.lastModified() > timeStamp) ? xmlFile.lastModified() : timeStamp;
181 }
182
183 Map serializedMap = loadSerialized(serialDataPath, timeStamp);
184
185 if (serializedMap != null)
186 {
187
188 appDataElements = serializedMap;
189 log.debug("Using the serialized map");
190 }
191 else
192 {
193
194 appDataElements = new HashMap();
195
196 for (Iterator it = xmlFiles.iterator(); it.hasNext();)
197 {
198 String xmlPath = (String) it.next();
199 AppData appData = null;
200
201 log.debug("Now parsing: " + xmlPath);
202 try
203 {
204 XmlToAppData xmlApp = new XmlToAppData();
205 appData = xmlApp.parseFile(xmlPath);
206 }
207 catch (Exception e)
208 {
209 log.error("Could not parse XML file " + xmlPath, e);
210
211 throw new InitializationException("Could not parse XML file " +
212 xmlPath, e);
213 }
214
215 appDataElements.put(appData, xmlPath);
216 log.debug("Saving appData for " + xmlPath);
217 }
218
219 saveSerialized(serialDataPath, appDataElements);
220 }
221
222 try
223 {
224 for (Iterator it = appDataElements.keySet().iterator(); it.hasNext();)
225 {
226 AppData appData = (AppData) it.next();
227
228 int maxPooledGroups = 0;
229 List glist = appData.getGroups();
230
231 String groupPrefix = appData.getGroupPrefix();
232
233 for (int i = glist.size() - 1; i >= 0; i--)
234 {
235 XmlGroup g = (XmlGroup) glist.get(i);
236 String groupName = g.getName();
237
238 boolean registerUnqualified = registerGroup(groupName, g, appData, true);
239
240 if (!registerUnqualified)
241 {
242 log.info("Ignored redefinition of Group " + groupName
243 + " or Key " + g.getKey()
244 + " from " + appDataElements.get(appData));
245 }
246
247 if (groupPrefix != null)
248 {
249 StringBuffer qualifiedName = new StringBuffer();
250 qualifiedName.append(groupPrefix)
251 .append(':')
252 .append(groupName);
253
254
255
256
257 if (!registerGroup(qualifiedName.toString(), g, appData, !registerUnqualified))
258 {
259 log.error("Could not register fully qualified name " + qualifiedName
260 + ", maybe two XML files have the same prefix. Ignoring it.");
261 }
262 }
263
264 maxPooledGroups =
265 Math.max(maxPooledGroups,
266 Integer.parseInt(g.getPoolCapacity()));
267
268 }
269
270 KeyedPoolableObjectFactory factory =
271 new Group.GroupFactory(appData);
272 keyedPools.put(appData, new StackKeyedObjectPool(factory, maxPooledGroups));
273 }
274
275 setInit(true);
276 }
277 catch (Exception e)
278 {
279 throw new InitializationException(
280 "TurbineIntakeService failed to initialize", e);
281 }
282 }
283
284 /***
285 * Called the first time the Service is used.
286 *
287 * @param config A ServletConfig.
288 * @deprecated use init() instead.
289 */
290 public void init(ServletConfig config)
291 throws InitializationException
292 {
293 init();
294 }
295
296 /***
297 * Registers a given group name in the system
298 *
299 * @param groupName The name to register the group under
300 * @param group The XML Group to register in
301 * @param appData The app Data object where the group can be found
302 * @param checkKey Whether to check if the key also exists.
303 *
304 * @return true if successful, false if not
305 */
306 private boolean registerGroup(String groupName, XmlGroup group, AppData appData, boolean checkKey)
307 {
308 if (groupNames.keySet().contains(groupName))
309 {
310
311 return false;
312 }
313
314 boolean keyExists = groupNameMap.keySet().contains(group.getKey());
315
316 if (checkKey && keyExists)
317 {
318
319 return false;
320 }
321
322 groupNames.put(groupName, appData);
323
324 groupKeyMap.put(groupName, group.getKey());
325
326 if (!keyExists)
327 {
328
329 groupNameMap.put(group.getKey(), groupName);
330 }
331
332 List classNames = group.getMapToObjects();
333 for (Iterator iter2 = classNames.iterator(); iter2.hasNext();)
334 {
335 String className = (String) iter2.next();
336 if (!getterMap.containsKey(className))
337 {
338 getterMap.put(className, new HashMap());
339 setterMap.put(className, new HashMap());
340 }
341 }
342 return true;
343 }
344
345 /***
346 * Tries to load a serialized Intake Group file. This
347 * can reduce the startup time of Turbine.
348 *
349 * @param serialDataPath The path of the File to load.
350 *
351 * @return A map with appData objects loaded from the file
352 * or null if the map could not be loaded.
353 */
354 private Map loadSerialized(String serialDataPath, long timeStamp)
355 {
356 log.debug("Entered loadSerialized("
357 + serialDataPath + ", "
358 + timeStamp + ")");
359
360 if (serialDataPath == null)
361 {
362 return null;
363 }
364
365 File serialDataFile = new File(serialDataPath);
366
367 if (!serialDataFile.exists())
368 {
369 log.info("No serialized file found, parsing XML");
370 return null;
371 }
372
373 if (serialDataFile.lastModified() <= timeStamp)
374 {
375 log.info("serialized file too old, parsing XML");
376 return null;
377 }
378
379 InputStream in = null;
380 Map serialData = null;
381
382 try
383 {
384 in = new FileInputStream(serialDataFile);
385 ObjectInputStream p = new ObjectInputStream(in);
386 Object o = p.readObject();
387
388 if (o instanceof Map)
389 {
390 serialData = (Map) o;
391 }
392 else
393 {
394
395 log.info("serialized object is not an intake map, ignoring");
396 in.close();
397 in = null;
398 serialDataFile.delete();
399 }
400 }
401 catch (Exception e)
402 {
403 log.error("Serialized File could not be read.", e);
404
405
406
407 serialData = null;
408 }
409 finally
410 {
411
412
413 try
414 {
415 if (in != null)
416 {
417 in.close();
418 }
419 }
420 catch (Exception e)
421 {
422 log.error("Exception while closing file", e);
423 }
424 }
425
426 log.info("Loaded serialized map object, ignoring XML");
427 return serialData;
428 }
429
430 /***
431 * Writes a parsed XML map with all the appData groups into a
432 * file. This will speed up loading time when you restart the
433 * Intake Service because it will only unserialize this file instead
434 * of reloading all of the XML files
435 *
436 * @param serialDataPath The path of the file to write to
437 * @param appDataElements A Map containing all of the XML parsed appdata elements
438 */
439 private void saveSerialized(String serialDataPath, Map appDataElements)
440 {
441
442 log.debug("Entered saveSerialized("
443 + serialDataPath + ", appDataElements)");
444
445 if (serialDataPath == null)
446 {
447 return;
448 }
449
450 File serialData = new File(serialDataPath);
451
452 try
453 {
454 serialData.createNewFile();
455 serialData.delete();
456 }
457 catch (Exception e)
458 {
459 log.info("Could not create serialized file " + serialDataPath
460 + ", not serializing the XML data");
461 return;
462 }
463
464 OutputStream out = null;
465 InputStream in = null;
466
467 try
468 {
469
470 out = new FileOutputStream(serialDataPath);
471 ObjectOutputStream pout = new ObjectOutputStream(out);
472 pout.writeObject(appDataElements);
473 pout.flush();
474
475
476
477 in = new FileInputStream(serialDataPath);
478 ObjectInputStream pin = new ObjectInputStream(in);
479 Map dummy = (Map) pin.readObject();
480
481 log.debug("Serializing successful");
482 }
483 catch (Exception e)
484 {
485 log.info("Could not write serialized file to " + serialDataPath
486 + ", not serializing the XML data");
487 }
488 finally
489 {
490 try
491 {
492 if (out != null)
493 {
494 out.close();
495 }
496 if (in != null)
497 {
498 in.close();
499 }
500 }
501 catch (Exception e)
502 {
503 log.error("Exception while closing file", e);
504 }
505 }
506 }
507
508 /***
509 * Gets an instance of a named group either from the pool
510 * or by calling the Factory Service if the pool is empty.
511 *
512 * @param groupName the name of the group.
513 * @return a Group instance.
514 * @throws IntakeException if recycling fails.
515 */
516 public Group getGroup(String groupName)
517 throws IntakeException
518 {
519 Group group = null;
520
521 AppData appData = (AppData) groupNames.get(groupName);
522
523 if (groupName == null)
524 {
525 throw new IntakeException(
526 "Intake TurbineIntakeService.getGroup(groupName) is null");
527 }
528 if (appData == null)
529 {
530 throw new IntakeException(
531 "Intake TurbineIntakeService.getGroup(groupName): No XML definition for Group "
532 + groupName + " found");
533 }
534 try
535 {
536 group = (Group) ((KeyedObjectPool) keyedPools.get(appData)).borrowObject(groupName);
537 }
538 catch (Exception e)
539 {
540 throw new IntakeException("Could not get group " + groupName, e);
541 }
542 return group;
543 }
544
545 /***
546 * Puts a Group back to the pool.
547 *
548 * @param instance the object instance to recycle.
549 *
550 * @throws IntakeException The passed group name does not exist.
551 */
552 public void releaseGroup(Group instance)
553 throws IntakeException
554 {
555 if (instance != null)
556 {
557 String groupName = instance.getIntakeGroupName();
558 AppData appData = (AppData) groupNames.get(groupName);
559
560 if (appData == null)
561 {
562 throw new IntakeException(
563 "Intake TurbineIntakeService.releaseGroup(groupName): "
564 + "No XML definition for Group " + groupName + " found");
565 }
566
567 try
568 {
569 ((KeyedObjectPool) keyedPools.get(appData)).returnObject(groupName, instance);
570 }
571 catch (Exception e)
572 {
573 new IntakeException("Could not get group " + groupName, e);
574 }
575 }
576 }
577
578 /***
579 * Gets the current size of the pool for a group.
580 *
581 * @param groupName the name of the group.
582 *
583 * @throws IntakeException The passed group name does not exist.
584 */
585 public int getSize(String groupName)
586 throws IntakeException
587 {
588 AppData appData = (AppData) groupNames.get(groupName);
589 if (appData == null)
590 {
591 throw new IntakeException(
592 "Intake TurbineIntakeService.Size(groupName): No XML definition for Group "
593 + groupName + " found");
594 }
595
596 KeyedObjectPool kop = (KeyedObjectPool) keyedPools.get(groupName);
597
598 return kop.getNumActive(groupName)
599 + kop.getNumIdle(groupName);
600 }
601
602 /***
603 * Names of all the defined groups.
604 *
605 * @return array of names.
606 */
607 public String[] getGroupNames()
608 {
609 return (String[]) groupNames.keySet().toArray(new String[0]);
610 }
611
612 /***
613 * Gets the key (usually a short identifier) for a group.
614 *
615 * @param groupName the name of the group.
616 * @return the the key.
617 */
618 public String getGroupKey(String groupName)
619 {
620 return (String) groupKeyMap.get(groupName);
621 }
622
623 /***
624 * Gets the group name given its key.
625 *
626 * @param groupKey the key.
627 * @return groupName the name of the group.
628 */
629 public String getGroupName(String groupKey)
630 {
631 return (String) groupNameMap.get(groupKey);
632 }
633
634 /***
635 * Gets the Method that can be used to set a property.
636 *
637 * @param className the name of the object.
638 * @param propName the name of the property.
639 * @return the setter.
640 * @throws ClassNotFoundException
641 * @throws IntrospectionException
642 */
643 public Method getFieldSetter(String className, String propName)
644 throws ClassNotFoundException, IntrospectionException
645 {
646 Map settersForClassName = (Map) setterMap.get(className);
647
648 if (settersForClassName == null)
649 {
650 throw new IntrospectionException("No setter Map for " + className + " available!");
651 }
652
653 Method setter = (Method) settersForClassName.get(propName);
654
655 if (setter == null)
656 {
657 PropertyDescriptor pd = null;
658
659 synchronized (setterMap)
660 {
661 try
662 {
663 pd = new PropertyDescriptor(propName,
664 Class.forName(className));
665 }
666 catch (IntrospectionException ie)
667 {
668 if (log.isWarnEnabled())
669 {
670 log.warn("Trying to find only a setter for " + propName);
671 }
672
673 pd = new PropertyDescriptor(propName,
674 Class.forName(className),
675 "set" + StringUtils.capitalize(propName),
676 null);
677 }
678
679 setter = pd.getWriteMethod();
680 settersForClassName.put(propName, setter);
681
682 if (setter == null)
683 {
684 log.error("Intake: setter for '" + propName
685 + "' in class '" + className
686 + "' could not be found.");
687 }
688 }
689
690 if (pd.getReadMethod() != null)
691 {
692
693
694 synchronized (getterMap)
695 {
696 Map gettersForClassName = (Map) getterMap.get(className);
697
698 if (gettersForClassName != null)
699 {
700 try
701 {
702 Method getter = pd.getReadMethod();
703 if (getter != null)
704 {
705 gettersForClassName.put(propName, getter);
706 }
707 }
708 catch (Exception e)
709 {
710
711 }
712 }
713 }
714 }
715 }
716 return setter;
717 }
718
719 /***
720 * Gets the Method that can be used to get a property value.
721 *
722 * @param className the name of the object.
723 * @param propName the name of the property.
724 * @return the getter.
725 * @throws ClassNotFoundException
726 * @throws IntrospectionException
727 */
728 public Method getFieldGetter(String className, String propName)
729 throws ClassNotFoundException, IntrospectionException
730 {
731 Map gettersForClassName = (Map) getterMap.get(className);
732
733 if (gettersForClassName == null)
734 {
735 throw new IntrospectionException("No getter Map for " + className + " available!");
736 }
737
738 Method getter = (Method) gettersForClassName.get(propName);
739
740 if (getter == null)
741 {
742 PropertyDescriptor pd = null;
743
744 synchronized (getterMap)
745 {
746 try
747 {
748 pd = new PropertyDescriptor(propName,
749 Class.forName(className));
750 }
751 catch (IntrospectionException ie)
752 {
753 if (log.isWarnEnabled())
754 {
755 log.warn("Trying to find only a getter for " + propName);
756 }
757
758 pd = new PropertyDescriptor(propName,
759 Class.forName(className),
760 "get" + StringUtils.capitalize(propName),
761 null);
762 }
763
764 getter = pd.getReadMethod();
765 gettersForClassName.put(propName, getter);
766
767 if (getter == null)
768 {
769 log.error("Intake: getter for '" + propName
770 + "' in class '" + className
771 + "' could not be found.");
772 }
773 }
774
775 if (pd.getWriteMethod() != null)
776 {
777
778
779 synchronized (setterMap)
780 {
781 Map settersForClassName = (Map) getterMap.get(className);
782
783 if (settersForClassName != null)
784 {
785 try
786 {
787 Method setter = pd.getWriteMethod();
788 if (setter != null)
789 {
790 settersForClassName.put(propName, setter);
791 }
792 }
793 catch (Exception e)
794 {
795
796 }
797 }
798 }
799 }
800 }
801 return getter;
802 }
803 }