View Javadoc

1   package org.apache.turbine.services.intake;
2   
3   /*
4    * Copyright 2001-2005 The Apache Software Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License")
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
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             // Files are webapp.root relative
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             // Get the timestamp of the youngest file to be compared with
174             // a serialized file. If it is younger than the serialized file,
175             // then we have to parse the XML anyway.
176             timeStamp =
177                     (xmlFile.lastModified() > timeStamp) ? xmlFile.lastModified() : timeStamp;
178         }
179 
180         Map serializedMap = loadSerialized(serialDataPath, timeStamp);
181 
182         if (serializedMap != null)
183         {
184             // Use the serialized data as XML groups. Don't parse.
185             appDataElements = serializedMap;
186             log.debug("Using the serialized map");
187         }
188         else
189         {
190             // Parse all the given XML files
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                         // Add the fully qualified group name. Do _not_ check for
252                         // the existence of the key if the unqualified registration succeeded
253                         // (because then it was added by the registerGroup above).
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             // This name already exists.
308             return false;
309         }
310 
311         boolean keyExists = groupNameMap.keySet().contains(group.getKey());
312 
313         if (checkKey && keyExists)
314         {
315             // The key for this package is already registered for another group
316             return false;
317         }
318 
319         groupNames.put(groupName, appData);
320 
321         groupKeyMap.put(groupName, group.getKey());
322 
323         if (!keyExists)
324         {
325             // This key does not exist. Add it to the hash.
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                 // Maybe an old file from intake. Ignore it and try to delete
392                 log.info("serialized object is not an intake map, ignoring");
393                 in.close();
394                 in = null;
395                 serialDataFile.delete(); // Try to delete the file lying around
396             }
397         }
398         catch (Exception e)
399         {
400             log.error("Serialized File could not be read.", e);
401 
402             // We got a corrupt file for some reason.
403             // Null out serialData to be sure
404             serialData = null;
405         }
406         finally
407         {
408             // Could be null if we opened a file, didn't find it to be a
409             // Map object and then nuked it away.
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             // write the appData file out
467             out = new FileOutputStream(serialDataPath);
468             ObjectOutputStream pout = new ObjectOutputStream(out);
469             pout.writeObject(appDataElements);
470             pout.flush();
471 
472             // read the file back in. for some reason on OSX 10.1
473             // this is necessary.
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); // Java sucks.
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                 // we have already completed the reflection on the getter, so
690                 // save it so we do not have to repeat
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                             // Do nothing
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); // Java sucks some more.
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                 // we have already completed the reflection on the setter, so
775                 // save it so we do not have to repeat
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                             // Do nothing
793                         }
794                     }
795                 }
796             }
797         }
798         return getter;
799     }
800 }