001: package org.apache.turbine.services.intake;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.beans.IntrospectionException;
023: import java.beans.PropertyDescriptor;
024:
025: import java.io.File;
026: import java.io.FileInputStream;
027: import java.io.FileOutputStream;
028: import java.io.InputStream;
029: import java.io.ObjectInputStream;
030: import java.io.ObjectOutputStream;
031: import java.io.OutputStream;
032:
033: import java.lang.reflect.Method;
034:
035: import java.util.HashMap;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Map;
040: import java.util.Set;
041: import java.util.Vector;
042:
043: import javax.servlet.ServletConfig;
044:
045: import org.apache.commons.lang.StringUtils;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049:
050: import org.apache.commons.pool.KeyedObjectPool;
051: import org.apache.commons.pool.KeyedPoolableObjectFactory;
052: import org.apache.commons.pool.impl.StackKeyedObjectPool;
053:
054: import org.apache.turbine.Turbine;
055: import org.apache.turbine.services.InitializationException;
056: import org.apache.turbine.services.TurbineBaseService;
057: import org.apache.turbine.services.intake.model.Group;
058: import org.apache.turbine.services.intake.transform.XmlToAppData;
059: import org.apache.turbine.services.intake.xmlmodel.AppData;
060: import org.apache.turbine.services.intake.xmlmodel.XmlGroup;
061:
062: /**
063: * This service provides access to input processing objects based
064: * on an XML specification.
065: *
066: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
067: * @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
068: * @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
069: * @version $Id: TurbineIntakeService.java 534527 2007-05-02 16:10:59Z tv $
070: */
071: public class TurbineIntakeService extends TurbineBaseService implements
072: IntakeService {
073: /** Map of groupNames -> appData elements */
074: private Map groupNames;
075:
076: /** The cache of group names. */
077: private Map groupNameMap;
078:
079: /** The cache of group keys. */
080: private Map groupKeyMap;
081:
082: /** The cache of property getters. */
083: private Map getterMap;
084:
085: /** The cache of property setters. */
086: private Map setterMap;
087:
088: /** AppData -> keyed Pools Map */
089: private Map keyedPools;
090:
091: /** Used for logging */
092: private static Log log = LogFactory
093: .getLog(TurbineIntakeService.class);
094:
095: /**
096: * Constructor. All Components need a public no argument constructor
097: * to be a legal Component.
098: */
099: public TurbineIntakeService() {
100: }
101:
102: /**
103: * Called the first time the Service is used.
104: *
105: * @throws InitializationException Something went wrong in the init
106: * stage
107: */
108: public void init() throws InitializationException {
109: Vector defaultXmlPathes = new Vector();
110: defaultXmlPathes.add(XML_PATH_DEFAULT);
111:
112: List xmlPathes = getConfiguration().getList(XML_PATH,
113: defaultXmlPathes);
114:
115: Map appDataElements = null;
116:
117: String serialDataPath = getConfiguration().getString(
118: SERIAL_XML, SERIAL_XML_DEFAULT);
119:
120: if (!serialDataPath.equalsIgnoreCase("none")) {
121: serialDataPath = Turbine.getRealPath(serialDataPath);
122: } else {
123: serialDataPath = null;
124: }
125:
126: log.debug("Path for serializing: " + serialDataPath);
127:
128: groupNames = new HashMap();
129: groupKeyMap = new HashMap();
130: groupNameMap = new HashMap();
131: getterMap = new HashMap();
132: setterMap = new HashMap();
133: keyedPools = new HashMap();
134:
135: if (xmlPathes == null) {
136: String LOAD_ERROR = "No pathes for XML files were specified. "
137: + "Check that the property exists in "
138: + "TurbineResources.props and were loaded.";
139:
140: log.error(LOAD_ERROR);
141: throw new InitializationException(LOAD_ERROR);
142: }
143:
144: Set xmlFiles = new HashSet();
145:
146: long timeStamp = 0;
147:
148: for (Iterator it = xmlPathes.iterator(); it.hasNext();) {
149: // Files are webapp.root relative
150: String xmlPath = Turbine.getRealPath((String) it.next());
151: File xmlFile = new File(xmlPath);
152:
153: log.debug("Path for XML File: " + xmlFile);
154:
155: if (!xmlFile.canRead()) {
156: String READ_ERR = "Could not read input file "
157: + xmlPath;
158:
159: log.error(READ_ERR);
160: throw new InitializationException(READ_ERR);
161: }
162:
163: xmlFiles.add(xmlPath);
164:
165: log.debug("Added " + xmlPath + " as File to parse");
166:
167: // Get the timestamp of the youngest file to be compared with
168: // a serialized file. If it is younger than the serialized file,
169: // then we have to parse the XML anyway.
170: timeStamp = (xmlFile.lastModified() > timeStamp) ? xmlFile
171: .lastModified() : timeStamp;
172: }
173:
174: Map serializedMap = loadSerialized(serialDataPath, timeStamp);
175:
176: if (serializedMap != null) {
177: // Use the serialized data as XML groups. Don't parse.
178: appDataElements = serializedMap;
179: log.debug("Using the serialized map");
180: } else {
181: // Parse all the given XML files
182: appDataElements = new HashMap();
183:
184: for (Iterator it = xmlFiles.iterator(); it.hasNext();) {
185: String xmlPath = (String) it.next();
186: AppData appData = null;
187:
188: log.debug("Now parsing: " + xmlPath);
189: try {
190: XmlToAppData xmlApp = new XmlToAppData();
191: appData = xmlApp.parseFile(xmlPath);
192: } catch (Exception e) {
193: log.error("Could not parse XML file " + xmlPath, e);
194:
195: throw new InitializationException(
196: "Could not parse XML file " + xmlPath, e);
197: }
198:
199: appDataElements.put(appData, xmlPath);
200: log.debug("Saving appData for " + xmlPath);
201: }
202:
203: saveSerialized(serialDataPath, appDataElements);
204: }
205:
206: try {
207: for (Iterator it = appDataElements.keySet().iterator(); it
208: .hasNext();) {
209: AppData appData = (AppData) it.next();
210:
211: int maxPooledGroups = 0;
212: List glist = appData.getGroups();
213:
214: String groupPrefix = appData.getGroupPrefix();
215:
216: for (int i = glist.size() - 1; i >= 0; i--) {
217: XmlGroup g = (XmlGroup) glist.get(i);
218: String groupName = g.getName();
219:
220: boolean registerUnqualified = registerGroup(
221: groupName, g, appData, true);
222:
223: if (!registerUnqualified) {
224: log.info("Ignored redefinition of Group "
225: + groupName + " or Key " + g.getKey()
226: + " from "
227: + appDataElements.get(appData));
228: }
229:
230: if (groupPrefix != null) {
231: StringBuffer qualifiedName = new StringBuffer();
232: qualifiedName.append(groupPrefix).append(':')
233: .append(groupName);
234:
235: // Add the fully qualified group name. Do _not_ check for
236: // the existence of the key if the unqualified registration succeeded
237: // (because then it was added by the registerGroup above).
238: if (!registerGroup(qualifiedName.toString(), g,
239: appData, !registerUnqualified)) {
240: log
241: .error("Could not register fully qualified name "
242: + qualifiedName
243: + ", maybe two XML files have the same prefix. Ignoring it.");
244: }
245: }
246:
247: maxPooledGroups = Math.max(maxPooledGroups, Integer
248: .parseInt(g.getPoolCapacity()));
249:
250: }
251:
252: KeyedPoolableObjectFactory factory = new Group.GroupFactory(
253: appData);
254: keyedPools.put(appData, new StackKeyedObjectPool(
255: factory, maxPooledGroups));
256: }
257:
258: setInit(true);
259: } catch (Exception e) {
260: throw new InitializationException(
261: "TurbineIntakeService failed to initialize", e);
262: }
263: }
264:
265: /**
266: * Called the first time the Service is used.
267: *
268: * @param config A ServletConfig.
269: * @deprecated use init() instead.
270: */
271: public void init(ServletConfig config)
272: throws InitializationException {
273: init();
274: }
275:
276: /**
277: * Registers a given group name in the system
278: *
279: * @param groupName The name to register the group under
280: * @param group The XML Group to register in
281: * @param appData The app Data object where the group can be found
282: * @param checkKey Whether to check if the key also exists.
283: *
284: * @return true if successful, false if not
285: */
286: private boolean registerGroup(String groupName, XmlGroup group,
287: AppData appData, boolean checkKey) {
288: if (groupNames.keySet().contains(groupName)) {
289: // This name already exists.
290: return false;
291: }
292:
293: boolean keyExists = groupNameMap.keySet().contains(
294: group.getKey());
295:
296: if (checkKey && keyExists) {
297: // The key for this package is already registered for another group
298: return false;
299: }
300:
301: groupNames.put(groupName, appData);
302:
303: groupKeyMap.put(groupName, group.getKey());
304:
305: if (!keyExists) {
306: // This key does not exist. Add it to the hash.
307: groupNameMap.put(group.getKey(), groupName);
308: }
309:
310: List classNames = group.getMapToObjects();
311: for (Iterator iter2 = classNames.iterator(); iter2.hasNext();) {
312: String className = (String) iter2.next();
313: if (!getterMap.containsKey(className)) {
314: getterMap.put(className, new HashMap());
315: setterMap.put(className, new HashMap());
316: }
317: }
318: return true;
319: }
320:
321: /**
322: * Tries to load a serialized Intake Group file. This
323: * can reduce the startup time of Turbine.
324: *
325: * @param serialDataPath The path of the File to load.
326: *
327: * @return A map with appData objects loaded from the file
328: * or null if the map could not be loaded.
329: */
330: private Map loadSerialized(String serialDataPath, long timeStamp) {
331: log.debug("Entered loadSerialized(" + serialDataPath + ", "
332: + timeStamp + ")");
333:
334: if (serialDataPath == null) {
335: return null;
336: }
337:
338: File serialDataFile = new File(serialDataPath);
339:
340: if (!serialDataFile.exists()) {
341: log.info("No serialized file found, parsing XML");
342: return null;
343: }
344:
345: if (serialDataFile.lastModified() <= timeStamp) {
346: log.info("serialized file too old, parsing XML");
347: return null;
348: }
349:
350: InputStream in = null;
351: Map serialData = null;
352:
353: try {
354: in = new FileInputStream(serialDataFile);
355: ObjectInputStream p = new ObjectInputStream(in);
356: Object o = p.readObject();
357:
358: if (o instanceof Map) {
359: serialData = (Map) o;
360: } else {
361: // Maybe an old file from intake. Ignore it and try to delete
362: log
363: .info("serialized object is not an intake map, ignoring");
364: in.close();
365: in = null;
366: serialDataFile.delete(); // Try to delete the file lying around
367: }
368: } catch (Exception e) {
369: log.error("Serialized File could not be read.", e);
370:
371: // We got a corrupt file for some reason.
372: // Null out serialData to be sure
373: serialData = null;
374: } finally {
375: // Could be null if we opened a file, didn't find it to be a
376: // Map object and then nuked it away.
377: try {
378: if (in != null) {
379: in.close();
380: }
381: } catch (Exception e) {
382: log.error("Exception while closing file", e);
383: }
384: }
385:
386: log.info("Loaded serialized map object, ignoring XML");
387: return serialData;
388: }
389:
390: /**
391: * Writes a parsed XML map with all the appData groups into a
392: * file. This will speed up loading time when you restart the
393: * Intake Service because it will only unserialize this file instead
394: * of reloading all of the XML files
395: *
396: * @param serialDataPath The path of the file to write to
397: * @param appDataElements A Map containing all of the XML parsed appdata elements
398: */
399: private void saveSerialized(String serialDataPath,
400: Map appDataElements) {
401:
402: log.debug("Entered saveSerialized(" + serialDataPath
403: + ", appDataElements)");
404:
405: if (serialDataPath == null) {
406: return;
407: }
408:
409: File serialData = new File(serialDataPath);
410:
411: try {
412: serialData.createNewFile();
413: serialData.delete();
414: } catch (Exception e) {
415: log
416: .info("Could not create serialized file "
417: + serialDataPath
418: + ", not serializing the XML data");
419: return;
420: }
421:
422: OutputStream out = null;
423: InputStream in = null;
424:
425: try {
426: // write the appData file out
427: out = new FileOutputStream(serialDataPath);
428: ObjectOutputStream pout = new ObjectOutputStream(out);
429: pout.writeObject(appDataElements);
430: pout.flush();
431:
432: // read the file back in. for some reason on OSX 10.1
433: // this is necessary.
434: in = new FileInputStream(serialDataPath);
435: ObjectInputStream pin = new ObjectInputStream(in);
436: Map dummy = (Map) pin.readObject();
437:
438: log.debug("Serializing successful");
439: } catch (Exception e) {
440: log
441: .info("Could not write serialized file to "
442: + serialDataPath
443: + ", not serializing the XML data");
444: } finally {
445: try {
446: if (out != null) {
447: out.close();
448: }
449: if (in != null) {
450: in.close();
451: }
452: } catch (Exception e) {
453: log.error("Exception while closing file", e);
454: }
455: }
456: }
457:
458: /**
459: * Gets an instance of a named group either from the pool
460: * or by calling the Factory Service if the pool is empty.
461: *
462: * @param groupName the name of the group.
463: * @return a Group instance.
464: * @throws IntakeException if recycling fails.
465: */
466: public Group getGroup(String groupName) throws IntakeException {
467: Group group = null;
468:
469: AppData appData = (AppData) groupNames.get(groupName);
470:
471: if (groupName == null) {
472: throw new IntakeException(
473: "Intake TurbineIntakeService.getGroup(groupName) is null");
474: }
475: if (appData == null) {
476: throw new IntakeException(
477: "Intake TurbineIntakeService.getGroup(groupName): No XML definition for Group "
478: + groupName + " found");
479: }
480: try {
481: group = (Group) ((KeyedObjectPool) keyedPools.get(appData))
482: .borrowObject(groupName);
483: } catch (Exception e) {
484: throw new IntakeException("Could not get group "
485: + groupName, e);
486: }
487: return group;
488: }
489:
490: /**
491: * Puts a Group back to the pool.
492: *
493: * @param instance the object instance to recycle.
494: *
495: * @throws IntakeException The passed group name does not exist.
496: */
497: public void releaseGroup(Group instance) throws IntakeException {
498: if (instance != null) {
499: String groupName = instance.getIntakeGroupName();
500: AppData appData = (AppData) groupNames.get(groupName);
501:
502: if (appData == null) {
503: throw new IntakeException(
504: "Intake TurbineIntakeService.releaseGroup(groupName): "
505: + "No XML definition for Group "
506: + groupName + " found");
507: }
508:
509: try {
510: ((KeyedObjectPool) keyedPools.get(appData))
511: .returnObject(groupName, instance);
512: } catch (Exception e) {
513: new IntakeException("Could not get group " + groupName,
514: e);
515: }
516: }
517: }
518:
519: /**
520: * Gets the current size of the pool for a group.
521: *
522: * @param groupName the name of the group.
523: *
524: * @throws IntakeException The passed group name does not exist.
525: */
526: public int getSize(String groupName) throws IntakeException {
527: AppData appData = (AppData) groupNames.get(groupName);
528: if (appData == null) {
529: throw new IntakeException(
530: "Intake TurbineIntakeService.Size(groupName): No XML definition for Group "
531: + groupName + " found");
532: }
533:
534: KeyedObjectPool kop = (KeyedObjectPool) keyedPools
535: .get(groupName);
536:
537: return kop.getNumActive(groupName) + kop.getNumIdle(groupName);
538: }
539:
540: /**
541: * Names of all the defined groups.
542: *
543: * @return array of names.
544: */
545: public String[] getGroupNames() {
546: return (String[]) groupNames.keySet().toArray(new String[0]);
547: }
548:
549: /**
550: * Gets the key (usually a short identifier) for a group.
551: *
552: * @param groupName the name of the group.
553: * @return the the key.
554: */
555: public String getGroupKey(String groupName) {
556: return (String) groupKeyMap.get(groupName);
557: }
558:
559: /**
560: * Gets the group name given its key.
561: *
562: * @param groupKey the key.
563: * @return groupName the name of the group.
564: */
565: public String getGroupName(String groupKey) {
566: return (String) groupNameMap.get(groupKey);
567: }
568:
569: /**
570: * Gets the Method that can be used to set a property.
571: *
572: * @param className the name of the object.
573: * @param propName the name of the property.
574: * @return the setter.
575: * @throws ClassNotFoundException
576: * @throws IntrospectionException
577: */
578: public Method getFieldSetter(String className, String propName)
579: throws ClassNotFoundException, IntrospectionException {
580: Map settersForClassName = (Map) setterMap.get(className);
581:
582: if (settersForClassName == null) {
583: throw new IntrospectionException("No setter Map for "
584: + className + " available!");
585: }
586:
587: Method setter = (Method) settersForClassName.get(propName);
588:
589: if (setter == null) {
590: PropertyDescriptor pd = null;
591:
592: synchronized (setterMap) {
593: try {
594: pd = new PropertyDescriptor(propName, Class
595: .forName(className));
596: } catch (IntrospectionException ie) {
597: if (log.isWarnEnabled()) {
598: log.warn("Trying to find only a setter for "
599: + propName);
600: }
601:
602: pd = new PropertyDescriptor(propName, Class
603: .forName(className), "set"
604: + StringUtils.capitalize(propName), null); // Java sucks.
605: }
606:
607: setter = pd.getWriteMethod();
608: settersForClassName.put(propName, setter);
609:
610: if (setter == null) {
611: log.error("Intake: setter for '" + propName
612: + "' in class '" + className
613: + "' could not be found.");
614: }
615: }
616:
617: if (pd.getReadMethod() != null) {
618: // we have already completed the reflection on the getter, so
619: // save it so we do not have to repeat
620: synchronized (getterMap) {
621: Map gettersForClassName = (Map) getterMap
622: .get(className);
623:
624: if (gettersForClassName != null) {
625: try {
626: Method getter = pd.getReadMethod();
627: if (getter != null) {
628: gettersForClassName.put(propName,
629: getter);
630: }
631: } catch (Exception e) {
632: // Do nothing
633: }
634: }
635: }
636: }
637: }
638: return setter;
639: }
640:
641: /**
642: * Gets the Method that can be used to get a property value.
643: *
644: * @param className the name of the object.
645: * @param propName the name of the property.
646: * @return the getter.
647: * @throws ClassNotFoundException
648: * @throws IntrospectionException
649: */
650: public Method getFieldGetter(String className, String propName)
651: throws ClassNotFoundException, IntrospectionException {
652: Map gettersForClassName = (Map) getterMap.get(className);
653:
654: if (gettersForClassName == null) {
655: throw new IntrospectionException("No getter Map for "
656: + className + " available!");
657: }
658:
659: Method getter = (Method) gettersForClassName.get(propName);
660:
661: if (getter == null) {
662: PropertyDescriptor pd = null;
663:
664: synchronized (getterMap) {
665: try {
666: pd = new PropertyDescriptor(propName, Class
667: .forName(className));
668: } catch (IntrospectionException ie) {
669: if (log.isWarnEnabled()) {
670: log.warn("Trying to find only a getter for "
671: + propName);
672: }
673:
674: pd = new PropertyDescriptor(propName, Class
675: .forName(className), "get"
676: + StringUtils.capitalize(propName), null); // Java sucks some more.
677: }
678:
679: getter = pd.getReadMethod();
680: gettersForClassName.put(propName, getter);
681:
682: if (getter == null) {
683: log.error("Intake: getter for '" + propName
684: + "' in class '" + className
685: + "' could not be found.");
686: }
687: }
688:
689: if (pd.getWriteMethod() != null) {
690: // we have already completed the reflection on the setter, so
691: // save it so we do not have to repeat
692: synchronized (setterMap) {
693: Map settersForClassName = (Map) getterMap
694: .get(className);
695:
696: if (settersForClassName != null) {
697: try {
698: Method setter = pd.getWriteMethod();
699: if (setter != null) {
700: settersForClassName.put(propName,
701: setter);
702: }
703: } catch (Exception e) {
704: // Do nothing
705: }
706: }
707: }
708: }
709: }
710: return getter;
711: }
712: }
|