// ========================================================================
// $Id: AbstractConfiguration.java 5533 2009-09-17 03:44:34Z janb $
// Copyright 2006 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================
package org.mortbay.jetty.plus.webapp;
import java.util.EventListener;
import java.util.Iterator;
import javax.servlet.UnavailableException;
import org.mortbay.jetty.plus.annotation.Injection;
import org.mortbay.jetty.plus.annotation.InjectionCollection;
import org.mortbay.jetty.plus.annotation.LifeCycleCallback;
import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
import org.mortbay.jetty.plus.annotation.PostConstructCallback;
import org.mortbay.jetty.plus.annotation.PreDestroyCallback;
import org.mortbay.jetty.plus.annotation.RunAsCollection;
import org.mortbay.jetty.plus.servlet.ServletHandler;
import org.mortbay.jetty.security.SecurityHandler;
import org.mortbay.jetty.servlet.FilterHolder;
import org.mortbay.jetty.servlet.FilterMapping;
import org.mortbay.jetty.servlet.ServletHolder;
import org.mortbay.jetty.servlet.ServletMapping;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.jetty.webapp.WebXmlConfiguration;
import org.mortbay.log.Log;
import org.mortbay.util.TypeUtil;
import org.mortbay.xml.XmlParser;
/**
* Configuration
*
*
*/
public abstract class AbstractConfiguration extends WebXmlConfiguration
{
protected LifeCycleCallbackCollection _callbacks = new LifeCycleCallbackCollection();
protected InjectionCollection _injections = new InjectionCollection();
protected RunAsCollection _runAsCollection = new RunAsCollection();
private boolean _metadataComplete = true;
public abstract void bindEnvEntry (String name, Object value) throws Exception;
public abstract void bindResourceRef (String name, Class type) throws Exception;
public abstract void bindResourceEnvRef (String name, Class type) throws Exception;
public abstract void bindUserTransaction () throws Exception;
public abstract void bindMessageDestinationRef (String name, Class type) throws Exception;
public void setWebAppContext (WebAppContext context)
{
super.setWebAppContext(context);
//set up our special ServletHandler to remember injections and lifecycle callbacks
ServletHandler servletHandler = new ServletHandler();
SecurityHandler securityHandler = getWebAppContext().getSecurityHandler();
org.mortbay.jetty.servlet.ServletHandler existingHandler = getWebAppContext().getServletHandler();
servletHandler.setFilters(existingHandler.getFilters());
servletHandler.setFilterMappings(existingHandler.getFilterMappings());
servletHandler.setServlets(existingHandler.getServlets());
servletHandler.setServletMappings(existingHandler.getServletMappings());
getWebAppContext().setServletHandler(servletHandler);
securityHandler.setHandler(servletHandler);
}
public void configureDefaults ()
throws Exception
{
super.configureDefaults();
}
public void configureWebApp ()
throws Exception
{
super.configureWebApp();
bindUserTransaction();
}
public void deconfigureWebApp()
throws Exception
{
//call any preDestroy methods on the listeners
callPreDestroyCallbacks();
super.deconfigureWebApp();
}
public void configure(String webXml)
throws Exception
{
//parse web.xml
super.configure(webXml);
//parse classes for annotations, if necessary
if (!_metadataComplete)
{
if (Log.isDebugEnabled()) Log.debug("Processing annotations");
parseAnnotations();
}
//do any injects on the listeners that were created and then
//also callback any postConstruct lifecycle methods
injectAndCallPostConstructCallbacks();
}
protected void initialize(XmlParser.Node config)
throws ClassNotFoundException,UnavailableException
{
super.initialize(config);
//configure injections and callbacks to be called by the FilterHolder and ServletHolder
//when they lazily instantiate the Filter/Servlet.
((ServletHandler)getWebAppContext().getServletHandler()).setInjections(_injections);
((ServletHandler)getWebAppContext().getServletHandler()).setCallbacks(_callbacks);
//find out if we need to process annotations
// servlet 2.5 web.xml && metadata-complete==false
if (_version == 25)
_metadataComplete = Boolean.valueOf((String)config.getAttribute("metadata-complete", "false")).booleanValue();
}
protected void initWebXmlElement(String element,XmlParser.Node node) throws Exception
{
if ("env-entry".equals(element))
{
initEnvEntry (node);
}
else if ("resource-ref".equals(element))
{
//resource-ref entries are ONLY for connection factories
//the resource-ref says how the app will reference the jndi lookup relative
//to java:comp/env, but it is up to the deployer to map this reference to
//a real resource in the environment. At the moment, we insist that the
//jetty.xml file name of the resource has to be exactly the same as the
//name in web.xml deployment descriptor, but it shouldn't have to be
initResourceRef(node);
}
else if ("resource-env-ref".equals(element))
{
//resource-env-ref elements are a non-connection factory type of resource
//the app looks them up relative to java:comp/env
//again, need a way for deployer to link up app naming to real naming.
//Again, we insist now that the name of the resource in jetty.xml is
//the same as web.xml
initResourceEnvRef(node);
}
else if ("message-destination-ref".equals(element))
{
initMessageDestinationRef(node);
}
else if ("post-construct".equals(element))
{
//post-construct is the name of a class and method to call after all
//resources have been setup but before the class is put into use
initPostConstruct(node);
}
else if ("pre-destroy".equals(element))
{
//pre-destroy is the name of a class and method to call just as
//the instance is being destroyed
initPreDestroy(node);
}
else
{
super.initWebXmlElement(element, node);
}
}
/**
* JavaEE 5.4.1.3
*
*
* @param node
* @throws Exception
*/
protected void initEnvEntry (XmlParser.Node node)
throws Exception
{
String name=node.getString("env-entry-name",false,true);
String type = node.getString("env-entry-type",false,true);
String valueStr = node.getString("env-entry-value",false,true);
//if there's no value there's no point in making a jndi entry
//nor processing injection entries
if (valueStr==null || valueStr.equals(""))
{
Log.warn("No value for env-entry-name "+name);
return;
}
//the javaee_5.xsd says that the env-entry-type is optional
//if there is an <injection> element, because you can get
//type from the element, but what to do if there is more
//than one <injection> element, do you just pick the type
//of the first one?
//check for <injection> elements
initInjection (node, name, TypeUtil.fromName(type));
//bind the entry into jndi
Object value = TypeUtil.valueOf(type,valueStr);
bindEnvEntry(name, value);
}
/**
* Common Annotations Spec section 2.3:
* resource-ref is for:
* - javax.sql.DataSource
* - javax.jms.ConnectionFactory
* - javax.jms.QueueConnectionFactory
* - javax.jms.TopicConnectionFactory
* - javax.mail.Session
* - java.net.URL
* - javax.resource.cci.ConnectionFactory
* - org.omg.CORBA_2_3.ORB
* - any other connection factory defined by a resource adapter
* @param node
* @throws Exception
*/
protected void initResourceRef (XmlParser.Node node)
throws Exception
{
String jndiName = node.getString("res-ref-name",false,true);
String type = node.getString("res-type", false, true);
String auth = node.getString("res-auth", false, true);
String shared = node.getString("res-sharing-scope", false, true);
//check for <injection> elements
Class typeClass = TypeUtil.fromName(type);
if (typeClass==null)
typeClass = getWebAppContext().loadClass(type);
initInjection (node, jndiName, typeClass);
bindResourceRef(jndiName, typeClass);
}
/**
* Common Annotations Spec section 2.3:
* resource-env-ref is for:
* - javax.transaction.UserTransaction
* - javax.resource.cci.InteractionSpec
* - anything else that is not a connection factory
* @param node
* @throws Exception
*/
protected void initResourceEnvRef (XmlParser.Node node)
throws Exception
{
String jndiName = node.getString("resource-env-ref-name",false,true);
String type = node.getString("resource-env-ref-type", false, true);
//check for <injection> elements
//JavaEE Spec sec 5.7.1.3 says the resource-env-ref-type
//is mandatory, but the schema says it is optional!
Class typeClass = TypeUtil.fromName(type);
if (typeClass==null)
typeClass = getWebAppContext().loadClass(type);
initInjection (node, jndiName, typeClass);
bindResourceEnvRef(jndiName, typeClass);
}
/**
* Common Annotations Spec section 2.3:
* message-destination-ref is for:
* - javax.jms.Queue
* - javax.jms.Topic
* @param node
* @throws Exception
*/
protected void initMessageDestinationRef (XmlParser.Node node)
throws Exception
{
String jndiName = node.getString("message-destination-ref-name",false,true);
String type = node.getString("message-destination-type",false,true);
String usage = node.getString("message-destination-usage",false,true);
Class typeClass = TypeUtil.fromName(type);
if (typeClass==null)
typeClass = getWebAppContext().loadClass(type);
initInjection(node, jndiName, typeClass);
bindMessageDestinationRef(jndiName, typeClass);
}
/**
* Process <post-construct>
* @param node
*/
protected void initPostConstruct(XmlParser.Node node)
{
String className = node.getString("lifecycle-callback-class", false, true);
String methodName = node.getString("lifecycle-callback-method", false, true);
if (className==null || className.equals(""))
{
Log.warn("No lifecycle-callback-class specified");
return;
}
if (methodName==null || methodName.equals(""))
{
Log.warn("No lifecycle-callback-method specified for class "+className);
return;
}
try
{
Class clazz = getWebAppContext().loadClass(className);
LifeCycleCallback callback = new PostConstructCallback();
callback.setTarget(clazz, methodName);
_callbacks.add(callback);
}
catch (ClassNotFoundException e)
{
Log.warn("Couldn't load post-construct target class "+className);
}
}
/**
* Process <pre-destroy>
* @param node
*/
protected void initPreDestroy(XmlParser.Node node)
{
String className = node.getString("lifecycle-callback-class", false, true);
String methodName = node.getString("lifecycle-callback-method", false, true);
if (className==null || className.equals(""))
{
Log.warn("No lifecycle-callback-class specified for pre-destroy");
return;
}
if (methodName==null || methodName.equals(""))
{
Log.warn("No lifecycle-callback-method specified for pre-destroy class "+className);
return;
}
try
{
Class clazz = getWebAppContext().loadClass(className);
LifeCycleCallback callback = new PreDestroyCallback();
callback.setTarget(clazz, methodName);
_callbacks.add(callback);
}
catch (ClassNotFoundException e)
{
Log.warn("Couldn't load pre-destory target class "+className);
}
}
/**
* Iterate over the <injection-target> entries for a node
*
* @param node
* @param jndiName
* @param valueClass
* @return the type of the injectable
*/
protected void initInjection (XmlParser.Node node, String jndiName, Class valueClass)
{
Iterator itor = node.iterator("injection-target");
while(itor.hasNext())
{
XmlParser.Node injectionNode = (XmlParser.Node)itor.next();
String targetClassName = injectionNode.getString("injection-target-class", false, true);
String targetName = injectionNode.getString("injection-target-name", false, true);
if ((targetClassName==null) || targetClassName.equals(""))
{
Log.warn("No classname found in injection-target");
continue;
}
if ((targetName==null) || targetName.equals(""))
{
Log.warn("No field or method name in injection-target");
continue;
}
// comments in the javaee_5.xsd file specify that the targetName is looked
// for first as a java bean property, then if that fails, as a field
try
{
Class clazz = getWebAppContext().loadClass(targetClassName);
Injection injection = new Injection();
injection.setTargetClass(clazz);
injection.setJndiName(jndiName);
injection.setTarget(clazz, targetName, valueClass);
_injections.add(injection);
}
catch (ClassNotFoundException e)
{
Log.warn("Couldn't load injection target class "+targetClassName);
}
}
}
/**
* Parse all classes that are mentioned in web.xml (servlets, filters, listeners)
* for annotations.
*
*
*
* @throws Exception
*/
protected abstract void parseAnnotations () throws Exception;
protected void injectAndCallPostConstructCallbacks()
throws Exception
{
//look thru the servlets to apply any runAs annotations
//NOTE: that any run-as in web.xml will already have been applied
ServletHolder[] holders = getWebAppContext().getServletHandler().getServlets();
for (int i=0;holders!=null && i<holders.length;i++)
{
_runAsCollection.setRunAs(holders[i]);
}
EventListener[] listeners = getWebAppContext().getEventListeners();
for (int i=0;listeners!=null && i<listeners.length;i++)
{
_injections.inject(listeners[i]);
_callbacks.callPostConstructCallback(listeners[i]);
}
}
protected void callPreDestroyCallbacks ()
throws Exception
{
EventListener[] listeners = getWebAppContext().getEventListeners();
for (int i=0; listeners!=null && i<listeners.length;i++)
{
_callbacks.callPreDestroyCallback(listeners[i]);
}
}
}