blob: eb0a0949cee5bad02aa16ce842da5f580c7c12ab [file] [log] [blame]
package org.apache.turbine.services.intake;
/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2001-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" and
* "Apache Turbine" must not be used to endorse or promote products
* derived from this software without prior written permission. For
* written permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* "Apache Turbine", nor may "Apache" appear in their name, without
* prior written permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import javax.servlet.ServletConfig;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.StackKeyedObjectPool;
import org.apache.turbine.Turbine;
import org.apache.turbine.services.InitializationException;
import org.apache.turbine.services.TurbineBaseService;
import org.apache.turbine.services.intake.model.Group;
import org.apache.turbine.services.intake.transform.XmlToAppData;
import org.apache.turbine.services.intake.xmlmodel.AppData;
import org.apache.turbine.services.intake.xmlmodel.XmlGroup;
/**
* This service provides access to input processing objects based
* on an XML specification.
*
* @deprecated Use the Fulcrum Intake component instead.
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @author <a href="mailto:quintonm@bellsouth.net">Quinton McCombs</a>
* @version $Id$
*/
public class TurbineIntakeService
extends TurbineBaseService
implements IntakeService
{
/** Map of groupNames -> appData elements */
private Map groupNames;
/** The cache of group names. */
private Map groupNameMap;
/** The cache of group keys. */
private Map groupKeyMap;
/** The cache of property getters. */
private Map getterMap;
/** The cache of property setters. */
private Map setterMap;
/** AppData -> keyed Pools Map */
private Map keyedPools;
/** Used for logging */
private static Log log = LogFactory.getLog(TurbineIntakeService.class);
/**
* Constructor. All Components need a public no argument constructor
* to be a legal Component.
*/
public TurbineIntakeService()
{
}
/**
* Called the first time the Service is used.
*
* @throws InitializationException Something went wrong in the init
* stage
*/
public void init()
throws InitializationException
{
Vector defaultXmlPathes = new Vector();
defaultXmlPathes.add(XML_PATH_DEFAULT);
List xmlPathes = getConfiguration()
.getList(XML_PATH, defaultXmlPathes);
Map appDataElements = null;
String serialDataPath = getConfiguration()
.getString(SERIAL_XML, SERIAL_XML_DEFAULT);
if (!serialDataPath.equalsIgnoreCase("none"))
{
serialDataPath = Turbine.getRealPath(serialDataPath);
}
else
{
serialDataPath = null;
}
log.debug("Path for serializing: " + serialDataPath);
groupNames = new HashMap();
groupKeyMap = new HashMap();
groupNameMap = new HashMap();
getterMap = new HashMap();
setterMap = new HashMap();
keyedPools = new HashMap();
if (xmlPathes == null)
{
String LOAD_ERROR = "No pathes for XML files were specified. " +
"Check that the property exists in " +
"TurbineResources.props and were loaded.";
log.error(LOAD_ERROR);
throw new InitializationException(LOAD_ERROR);
}
Set xmlFiles = new HashSet();
long timeStamp = 0;
for (Iterator it = xmlPathes.iterator(); it.hasNext();)
{
// Files are webapp.root relative
String xmlPath = Turbine.getRealPath((String) it.next());
File xmlFile = new File(xmlPath);
log.debug("Path for XML File: " + xmlFile);
if (!xmlFile.canRead())
{
String READ_ERR = "Could not read input file " + xmlPath;
log.error(READ_ERR);
throw new InitializationException(READ_ERR);
}
xmlFiles.add(xmlPath);
log.debug("Added " + xmlPath + " as File to parse");
// Get the timestamp of the youngest file to be compared with
// a serialized file. If it is younger than the serialized file,
// then we have to parse the XML anyway.
timeStamp =
(xmlFile.lastModified() > timeStamp) ? xmlFile.lastModified() : timeStamp;
}
Map serializedMap = loadSerialized(serialDataPath, timeStamp);
if (serializedMap != null)
{
// Use the serialized data as XML groups. Don't parse.
appDataElements = serializedMap;
log.debug("Using the serialized map");
}
else
{
// Parse all the given XML files
appDataElements = new HashMap();
for (Iterator it = xmlFiles.iterator(); it.hasNext();)
{
String xmlPath = (String) it.next();
AppData appData = null;
log.debug("Now parsing: " + xmlPath);
try
{
XmlToAppData xmlApp = new XmlToAppData();
appData = xmlApp.parseFile(xmlPath);
}
catch (Exception e)
{
log.error("Could not parse XML file " + xmlPath, e);
throw new InitializationException("Could not parse XML file " +
xmlPath, e);
}
appDataElements.put(appData, xmlPath);
log.debug("Saving appData for " + xmlPath);
}
saveSerialized(serialDataPath, appDataElements);
}
try
{
for (Iterator it = appDataElements.keySet().iterator(); it.hasNext();)
{
AppData appData = (AppData) it.next();
int maxPooledGroups = 0;
List glist = appData.getGroups();
String groupPrefix = appData.getGroupPrefix();
for (int i = glist.size() - 1; i >= 0; i--)
{
XmlGroup g = (XmlGroup) glist.get(i);
String groupName = g.getName();
boolean registerUnqualified = registerGroup(groupName, g, appData, true);
if (!registerUnqualified)
{
log.info("Ignored redefinition of Group " + groupName
+ " or Key " + g.getKey()
+ " from " + appDataElements.get(appData));
}
if (groupPrefix != null)
{
StringBuffer qualifiedName = new StringBuffer();
qualifiedName.append(groupPrefix)
.append(':')
.append(groupName);
// Add the fully qualified group name. Do _not_ check for
// the existence of the key if the unqualified registration succeeded
// (because then it was added by the registerGroup above).
if (!registerGroup(qualifiedName.toString(), g, appData, !registerUnqualified))
{
log.error("Could not register fully qualified name " + qualifiedName
+ ", maybe two XML files have the same prefix. Ignoring it.");
}
}
maxPooledGroups =
Math.max(maxPooledGroups,
Integer.parseInt(g.getPoolCapacity()));
}
KeyedPoolableObjectFactory factory =
new Group.GroupFactory(appData);
keyedPools.put(appData, new StackKeyedObjectPool(factory, maxPooledGroups));
}
setInit(true);
}
catch (Exception e)
{
throw new InitializationException(
"TurbineIntakeService failed to initialize", e);
}
}
/**
* Called the first time the Service is used.
*
* @param config A ServletConfig.
* @deprecated use init() instead.
*/
public void init(ServletConfig config)
throws InitializationException
{
init();
}
/**
* Registers a given group name in the system
*
* @param groupName The name to register the group under
* @param group The XML Group to register in
* @param appData The app Data object where the group can be found
* @param checkKey Whether to check if the key also exists.
*
* @return true if successful, false if not
*/
private boolean registerGroup(String groupName, XmlGroup group, AppData appData, boolean checkKey)
{
if (groupNames.keySet().contains(groupName))
{
// This name already exists.
return false;
}
boolean keyExists = groupNameMap.keySet().contains(group.getKey());
if (checkKey && keyExists)
{
// The key for this package is already registered for another group
return false;
}
groupNames.put(groupName, appData);
groupKeyMap.put(groupName, group.getKey());
if (!keyExists)
{
// This key does not exist. Add it to the hash.
groupNameMap.put(group.getKey(), groupName);
}
List classNames = group.getMapToObjects();
for (Iterator iter2 = classNames.iterator(); iter2.hasNext();)
{
String className = (String) iter2.next();
if (!getterMap.containsKey(className))
{
getterMap.put(className, new HashMap());
setterMap.put(className, new HashMap());
}
}
return true;
}
/**
* Tries to load a serialized Intake Group file. This
* can reduce the startup time of Turbine.
*
* @param serialDataPath The path of the File to load.
*
* @return A map with appData objects loaded from the file
* or null if the map could not be loaded.
*/
private Map loadSerialized(String serialDataPath, long timeStamp)
{
log.debug("Entered loadSerialized("
+ serialDataPath + ", "
+ timeStamp + ")");
if (serialDataPath == null)
{
return null;
}
File serialDataFile = new File(serialDataPath);
if (!serialDataFile.exists())
{
log.info("No serialized file found, parsing XML");
return null;
}
if (serialDataFile.lastModified() <= timeStamp)
{
log.info("serialized file too old, parsing XML");
return null;
}
InputStream in = null;
Map serialData = null;
try
{
in = new FileInputStream(serialDataFile);
ObjectInputStream p = new ObjectInputStream(in);
Object o = p.readObject();
if (o instanceof Map)
{
serialData = (Map) o;
}
else
{
// Maybe an old file from intake. Ignore it and try to delete
log.info("serialized object is not an intake map, ignoring");
in.close();
in = null;
serialDataFile.delete(); // Try to delete the file lying around
}
}
catch (Exception e)
{
log.error("Serialized File could not be read.", e);
// We got a corrupt file for some reason.
// Null out serialData to be sure
serialData = null;
}
finally
{
// Could be null if we opened a file, didn't find it to be a
// Map object and then nuked it away.
try
{
if (in != null)
{
in.close();
}
}
catch (Exception e)
{
log.error("Exception while closing file", e);
}
}
log.info("Loaded serialized map object, ignoring XML");
return serialData;
}
/**
* Writes a parsed XML map with all the appData groups into a
* file. This will speed up loading time when you restart the
* Intake Service because it will only unserialize this file instead
* of reloading all of the XML files
*
* @param serialDataPath The path of the file to write to
* @param appDataElements A Map containing all of the XML parsed appdata elements
*/
private void saveSerialized(String serialDataPath, Map appDataElements)
{
log.debug("Entered saveSerialized("
+ serialDataPath + ", appDataElements)");
if (serialDataPath == null)
{
return;
}
File serialData = new File(serialDataPath);
try
{
serialData.createNewFile();
serialData.delete();
}
catch (Exception e)
{
log.info("Could not create serialized file " + serialDataPath
+ ", not serializing the XML data");
return;
}
OutputStream out = null;
InputStream in = null;
try
{
// write the appData file out
out = new FileOutputStream(serialDataPath);
ObjectOutputStream pout = new ObjectOutputStream(out);
pout.writeObject(appDataElements);
pout.flush();
// read the file back in. for some reason on OSX 10.1
// this is necessary.
in = new FileInputStream(serialDataPath);
ObjectInputStream pin = new ObjectInputStream(in);
Map dummy = (Map) pin.readObject();
log.debug("Serializing successful");
}
catch (Exception e)
{
log.info("Could not write serialized file to " + serialDataPath
+ ", not serializing the XML data");
}
finally
{
try
{
if (out != null)
{
out.close();
}
if (in != null)
{
in.close();
}
}
catch (Exception e)
{
log.error("Exception while closing file", e);
}
}
}
/**
* Gets an instance of a named group either from the pool
* or by calling the Factory Service if the pool is empty.
*
* @param groupName the name of the group.
* @return a Group instance.
* @throws IntakeException if recycling fails.
*/
public Group getGroup(String groupName)
throws IntakeException
{
Group group = null;
AppData appData = (AppData) groupNames.get(groupName);
if (groupName == null)
{
throw new IntakeException(
"Intake TurbineIntakeService.getGroup(groupName) is null");
}
if (appData == null)
{
throw new IntakeException(
"Intake TurbineIntakeService.getGroup(groupName): No XML definition for Group "
+ groupName + " found");
}
try
{
group = (Group) ((KeyedObjectPool) keyedPools.get(appData)).borrowObject(groupName);
}
catch (Exception e)
{
throw new IntakeException("Could not get group " + groupName, e);
}
return group;
}
/**
* Puts a Group back to the pool.
*
* @param instance the object instance to recycle.
*
* @throws IntakeException The passed group name does not exist.
*/
public void releaseGroup(Group instance)
throws IntakeException
{
if (instance != null)
{
String groupName = instance.getIntakeGroupName();
AppData appData = (AppData) groupNames.get(groupName);
if (appData == null)
{
throw new IntakeException(
"Intake TurbineIntakeService.releaseGroup(groupName): "
+ "No XML definition for Group " + groupName + " found");
}
try
{
((KeyedObjectPool) keyedPools.get(appData)).returnObject(groupName, instance);
}
catch (Exception e)
{
new IntakeException("Could not get group " + groupName, e);
}
}
}
/**
* Gets the current size of the pool for a group.
*
* @param groupName the name of the group.
*
* @throws IntakeException The passed group name does not exist.
*/
public int getSize(String groupName)
throws IntakeException
{
AppData appData = (AppData) groupNames.get(groupName);
if (appData == null)
{
throw new IntakeException(
"Intake TurbineIntakeService.Size(groupName): No XML definition for Group "
+ groupName + " found");
}
KeyedObjectPool kop = (KeyedObjectPool) keyedPools.get(groupName);
return kop.getNumActive(groupName)
+ kop.getNumIdle(groupName);
}
/**
* Names of all the defined groups.
*
* @return array of names.
*/
public String[] getGroupNames()
{
return (String[]) groupNames.keySet().toArray(new String[0]);
}
/**
* Gets the key (usually a short identifier) for a group.
*
* @param groupName the name of the group.
* @return the the key.
*/
public String getGroupKey(String groupName)
{
return (String) groupKeyMap.get(groupName);
}
/**
* Gets the group name given its key.
*
* @param groupKey the key.
* @return groupName the name of the group.
*/
public String getGroupName(String groupKey)
{
return (String) groupNameMap.get(groupKey);
}
/**
* Gets the Method that can be used to set a property.
*
* @param className the name of the object.
* @param propName the name of the property.
* @return the setter.
* @throws ClassNotFoundException
* @throws IntrospectionException
*/
public Method getFieldSetter(String className, String propName)
throws ClassNotFoundException, IntrospectionException
{
Map settersForClassName = (Map) setterMap.get(className);
if (settersForClassName == null)
{
throw new IntrospectionException("No setter Map for " + className + " available!");
}
Method setter = (Method) settersForClassName.get(propName);
if (setter == null)
{
PropertyDescriptor pd =
new PropertyDescriptor(propName,
Class.forName(className));
synchronized (setterMap)
{
setter = pd.getWriteMethod();
settersForClassName.put(propName, setter);
if (setter == null)
{
log.error("Intake: setter for '" + propName
+ "' in class '" + className
+ "' could not be found.");
}
}
// we have already completed the reflection on the getter, so
// save it so we do not have to repeat
synchronized (getterMap)
{
Map gettersForClassName = (Map) getterMap.get(className);
if (gettersForClassName != null)
{
try
{
Method getter = pd.getReadMethod();
if (getter != null)
{
gettersForClassName.put(propName, getter);
}
}
catch (Exception e)
{
// Do nothing
}
}
}
}
return setter;
}
/**
* Gets the Method that can be used to get a property value.
*
* @param className the name of the object.
* @param propName the name of the property.
* @return the getter.
* @throws ClassNotFoundException
* @throws IntrospectionException
*/
public Method getFieldGetter(String className, String propName)
throws ClassNotFoundException, IntrospectionException
{
Map gettersForClassName = (Map) getterMap.get(className);
if (gettersForClassName == null)
{
throw new IntrospectionException("No getter Map for " + className + " available!");
}
Method getter = (Method) gettersForClassName.get(propName);
if (getter == null)
{
PropertyDescriptor pd = null;
synchronized (getterMap)
{
pd = new PropertyDescriptor(propName,
Class.forName(className));
getter = pd.getReadMethod();
gettersForClassName.put(propName, getter);
if (getter == null)
{
log.error("Intake: getter for '" + propName
+ "' in class '" + className
+ "' could not be found.");
}
}
// we have already completed the reflection on the setter, so
// save it so we do not have to repeat
synchronized (setterMap)
{
Map settersForClassName = (Map) getterMap.get(className);
if (settersForClassName != null)
{
try
{
Method setter = pd.getWriteMethod();
if (setter != null)
{
settersForClassName.put(propName, setter);
}
}
catch (Exception e)
{
// Do nothing
}
}
}
}
return getter;
}
}