View Javadoc

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