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