001: // ========================================================================
002: // $Id: AbstractConfiguration.java 1448 2006-12-29 20:46:57Z janb $
003: // Copyright 2006 Mort Bay Consulting Pty. Ltd.
004: // ------------------------------------------------------------------------
005: // Licensed under the Apache License, Version 2.0 (the "License");
006: // you may not use this file except in compliance with the License.
007: // You may obtain a copy of the License at
008: // http://www.apache.org/licenses/LICENSE-2.0
009: // Unless required by applicable law or agreed to in writing, software
010: // distributed under the License is distributed on an "AS IS" BASIS,
011: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: // See the License for the specific language governing permissions and
013: // limitations under the License.
014: // ========================================================================
015:
016: package org.mortbay.jetty.plus.webapp;
017:
018: import java.lang.reflect.Field;
019: import java.lang.reflect.Method;
020: import java.util.EventListener;
021: import java.util.Iterator;
022:
023: import javax.servlet.UnavailableException;
024:
025: import org.mortbay.jetty.plus.annotation.Injection;
026: import org.mortbay.jetty.plus.annotation.InjectionCollection;
027: import org.mortbay.jetty.plus.annotation.LifeCycleCallback;
028: import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
029: import org.mortbay.jetty.plus.annotation.PostConstructCallback;
030: import org.mortbay.jetty.plus.annotation.PreDestroyCallback;
031: import org.mortbay.jetty.plus.servlet.ServletHandler;
032: import org.mortbay.jetty.security.SecurityHandler;
033: import org.mortbay.jetty.webapp.WebXmlConfiguration;
034: import org.mortbay.log.Log;
035: import org.mortbay.util.TypeUtil;
036: import org.mortbay.xml.XmlParser;
037:
038: /**
039: * Configuration
040: *
041: *
042: */
043: public abstract class AbstractConfiguration extends WebXmlConfiguration {
044: private LifeCycleCallbackCollection _callbacks = new LifeCycleCallbackCollection();
045: private InjectionCollection _injections = new InjectionCollection();
046: private boolean _metadataComplete = true;
047:
048: public abstract void bindEnvEntry(String name, Object value)
049: throws Exception;
050:
051: public abstract void bindResourceRef(String name, Class type)
052: throws Exception;
053:
054: public abstract void bindResourceEnvRef(String name, Class type)
055: throws Exception;
056:
057: public abstract void bindUserTransaction() throws Exception;
058:
059: public abstract void bindMessageDestinationRef(String name,
060: Class type) throws Exception;
061:
062: public void configureDefaults() throws Exception {
063: super .configureDefaults();
064: }
065:
066: public void configureWebApp() throws Exception {
067: super .configureWebApp();
068: bindUserTransaction();
069: }
070:
071: public void deconfigureWebApp() throws Exception {
072: //call any preDestroy methods on the listeners
073: callPreDestroyCallbacks();
074:
075: super .deconfigureWebApp();
076: }
077:
078: public void configure(String webXml) throws Exception {
079: //parse web.xml
080: super .configure(webXml);
081:
082: //parse classes for annotations, if necessary
083: if (!_metadataComplete) {
084: if (Log.isDebugEnabled())
085: Log.debug("Processing annotations");
086: parseAnnotations();
087: }
088: //do any injects on the listeners that were created and then
089: //also callback any postConstruct lifecycle methods
090: injectAndCallPostConstructCallbacks();
091: }
092:
093: protected void initialize(XmlParser.Node config)
094: throws ClassNotFoundException, UnavailableException {
095: //set up our special ServletHandler to remember injections and lifecycle callbacks
096: ServletHandler servletHandler = new ServletHandler();
097: SecurityHandler securityHandler = getWebAppContext()
098: .getSecurityHandler();
099:
100: org.mortbay.jetty.servlet.ServletHandler existingHandler = getWebAppContext()
101: .getServletHandler();
102:
103: servletHandler.setFilterMappings(existingHandler
104: .getFilterMappings());
105: servletHandler.setFilters(existingHandler.getFilters());
106: servletHandler.setServlets(existingHandler.getServlets());
107: servletHandler.setServletMappings(existingHandler
108: .getServletMappings());
109: getWebAppContext().setServletHandler(servletHandler);
110: securityHandler.setHandler(servletHandler);
111:
112: super .initialize(config);
113:
114: //configure injections and callbacks to be called by the FilterHolder and ServletHolder
115: //when they lazily instantiate the Filter/Servlet.
116: ((ServletHandler) getWebAppContext().getServletHandler())
117: .setInjections(_injections);
118: ((ServletHandler) getWebAppContext().getServletHandler())
119: .setCallbacks(_callbacks);
120:
121: //find out if we need to process annotations
122: // servlet 2.5 web.xml && metadata-complete==false
123: if (_version == 25)
124: _metadataComplete = Boolean.parseBoolean(config
125: .getAttribute("metadata-complete", "false"));
126: }
127:
128: protected void initWebXmlElement(String element, XmlParser.Node node)
129: throws Exception {
130: if ("env-entry".equals(element)) {
131: initEnvEntry(node);
132: } else if ("resource-ref".equals(element)) {
133: //resource-ref entries are ONLY for connection factories
134: //the resource-ref says how the app will reference the jndi lookup relative
135: //to java:comp/env, but it is up to the deployer to map this reference to
136: //a real resource in the environment. At the moment, we insist that the
137: //jetty.xml file name of the resource has to be exactly the same as the
138: //name in web.xml deployment descriptor, but it shouldn't have to be
139: initResourceRef(node);
140: } else if ("resource-env-ref".equals(element)) {
141: //resource-env-ref elements are a non-connection factory type of resource
142: //the app looks them up relative to java:comp/env
143: //again, need a way for deployer to link up app naming to real naming.
144: //Again, we insist now that the name of the resource in jetty.xml is
145: //the same as web.xml
146: initResourceEnvRef(node);
147: } else if ("post-construct".equals(element)) {
148: //post-construct is the name of a class and method to call after all
149: //resources have been setup but before the class is put into use
150: initPostConstruct(node);
151: } else if ("pre-destroy".equals(element)) {
152: //pre-destroy is the name of a class and method to call just as
153: //the instance is being destroyed
154: initPreDestroy(node);
155: } else {
156: super .initWebXmlElement(element, node);
157: }
158:
159: }
160:
161: /**
162: * JavaEE 5.4.1.3
163: *
164: *
165: * @param node
166: * @throws Exception
167: */
168: protected void initEnvEntry(XmlParser.Node node) throws Exception {
169: String name = node.getString("env-entry-name", false, true);
170: String type = node.getString("env-entry-type", false, true);
171: String valueStr = node
172: .getString("env-entry-value", false, true);
173:
174: //if there's no value there's no point in making a jndi entry
175: //nor processing injection entries
176: if (valueStr == null || valueStr.equals("")) {
177: Log.warn("No value for env-entry-name " + name);
178: return;
179: }
180:
181: //the javaee_5.xsd says that the env-entry-type is optional
182: //if there is an <injection> element, because you can get
183: //type from the element, but what to do if there is more
184: //than one <injection> element, do you just pick the type
185: //of the first one?
186:
187: //check for <injection> elements
188: initInjection(node, name, TypeUtil.fromName(type));
189:
190: //bind the entry into jndi
191: Object value = TypeUtil.valueOf(type, valueStr);
192: bindEnvEntry(name, value);
193:
194: }
195:
196: /**
197: * Common Annotations Spec section 2.3:
198: * resource-ref is for:
199: * - javax.sql.DataSource
200: * - javax.jms.ConnectionFactory
201: * - javax.jms.QueueConnectionFactory
202: * - javax.jms.TopicConnectionFactory
203: * - javax.mail.Session
204: * - java.net.URL
205: * - javax.resource.cci.ConnectionFactory
206: * - org.omg.CORBA_2_3.ORB
207: * - any other connection factory defined by a resource adapter
208: * @param node
209: * @throws Exception
210: */
211: protected void initResourceRef(XmlParser.Node node)
212: throws Exception {
213: String jndiName = node.getString("res-ref-name", false, true);
214: String type = node.getString("res-type", false, true);
215: String auth = node.getString("res-auth", false, true);
216: String shared = node
217: .getString("res-sharing-scope", false, true);
218:
219: //check for <injection> elements
220: Class typeClass = TypeUtil.fromName(type);
221: if (typeClass == null)
222: typeClass = getWebAppContext().loadClass(type);
223: initInjection(node, jndiName, typeClass);
224:
225: bindResourceRef(jndiName, typeClass);
226: }
227:
228: /**
229: * Common Annotations Spec section 2.3:
230: * resource-env-ref is for:
231: * - javax.transaction.UserTransaction
232: * - javax.resource.cci.InteractionSpec
233: * - anything else that is not a connection factory
234: * @param node
235: * @throws Exception
236: */
237: protected void initResourceEnvRef(XmlParser.Node node)
238: throws Exception {
239: String jndiName = node.getString("resource-env-ref-name",
240: false, true);
241: String type = node.getString("resource-env-ref-type", false,
242: true);
243:
244: //check for <injection> elements
245:
246: //JavaEE Spec sec 5.7.1.3 says the resource-env-ref-type
247: //is mandatory, but the schema says it is optional!
248: Class typeClass = TypeUtil.fromName(type);
249: if (typeClass == null)
250: typeClass = getWebAppContext().loadClass(type);
251: initInjection(node, jndiName, typeClass);
252:
253: bindResourceEnvRef(jndiName, typeClass);
254: }
255:
256: /**
257: * Common Annotations Spec section 2.3:
258: * message-destination-ref is for:
259: * - javax.jms.Queue
260: * - javax.jms.Topic
261: * @param node
262: * @throws Exception
263: */
264: protected void initMessageDestinationRef(XmlParser.Node node)
265: throws Exception {
266: String jndiName = node.getString(
267: "message-destination-ref-name", false, true);
268: String type = node.getString("message-destination-type", false,
269: true);
270: String usage = node.getString("message-destination-usage",
271: false, true);
272:
273: Class typeClass = TypeUtil.fromName(type);
274: if (typeClass == null)
275: typeClass = getWebAppContext().loadClass(type);
276: initInjection(node, jndiName, typeClass);
277:
278: bindMessageDestinationRef(jndiName, typeClass);
279: }
280:
281: /**
282: * Process <post-construct>
283: * @param node
284: */
285: protected void initPostConstruct(XmlParser.Node node) {
286: String className = node.getString("lifecycle-callback-class",
287: false, true);
288: String methodName = node.getString("lifecycle-callback-method",
289: false, true);
290:
291: if (className == null || className.equals("")) {
292: Log.warn("No lifecycle-callback-class specified");
293: return;
294: }
295: if (methodName == null || methodName.equals("")) {
296: Log
297: .warn("No lifecycle-callback-method specified for class "
298: + className);
299: return;
300: }
301:
302: try {
303: Class clazz = getWebAppContext().loadClass(className);
304: LifeCycleCallback callback = new PostConstructCallback();
305: callback.setTarget(clazz, methodName);
306: _callbacks.add(callback);
307: } catch (ClassNotFoundException e) {
308: Log.warn("Couldn't load post-construct target class "
309: + className);
310: }
311: }
312:
313: /**
314: * Process <pre-destroy>
315: * @param node
316: */
317: protected void initPreDestroy(XmlParser.Node node) {
318: String className = node.getString("lifecycle-callback-class",
319: false, true);
320: String methodName = node.getString("lifecycle-callback-method",
321: false, true);
322: if (className == null || className.equals("")) {
323: Log
324: .warn("No lifecycle-callback-class specified for pre-destroy");
325: return;
326: }
327: if (methodName == null || methodName.equals("")) {
328: Log
329: .warn("No lifecycle-callback-method specified for pre-destroy class "
330: + className);
331: return;
332: }
333:
334: try {
335: Class clazz = getWebAppContext().loadClass(className);
336: LifeCycleCallback callback = new PreDestroyCallback();
337: callback.setTarget(clazz, methodName);
338: _callbacks.add(callback);
339: } catch (ClassNotFoundException e) {
340: Log.warn("Couldn't load pre-destory target class "
341: + className);
342: }
343: }
344:
345: /**
346: * Iterate over the <injection-target> entries for a node
347: *
348: * @param node
349: * @param jndiName
350: * @param valueClass
351: * @return the type of the injectable
352: */
353: protected void initInjection(XmlParser.Node node, String jndiName,
354: Class valueClass) {
355: Iterator itor = node.iterator("injection-target");
356:
357: while (itor.hasNext()) {
358: XmlParser.Node injectionNode = (XmlParser.Node) itor.next();
359: String targetClassName = injectionNode.getString(
360: "injection-target-class", false, true);
361: String targetName = injectionNode.getString(
362: "injection-target-name", false, true);
363: if ((targetClassName == null) || targetClassName.equals("")) {
364: Log.warn("No classname found in injection-target");
365: continue;
366: }
367: if ((targetName == null) || targetName.equals("")) {
368: Log.warn("No field or method name in injection-target");
369: continue;
370: }
371:
372: // comments in the javaee_5.xsd file specify that the targetName is looked
373: // for first as a java bean property, then if that fails, as a field
374: try {
375: Class clazz = getWebAppContext().loadClass(
376: targetClassName);
377: Injection injection = new Injection();
378: injection.setClassName(targetClassName);
379: injection.setJndiName(jndiName);
380: injection.setTarget(clazz, targetName, valueClass);
381: _injections.add(injection);
382: } catch (ClassNotFoundException e) {
383: Log.warn("Couldn't load injection target class "
384: + targetClassName);
385: }
386: }
387: }
388:
389: /**
390: * Parse all classes that are mentioned in web.xml (servlets, filters, listeners)
391: * for annotations.
392: *
393: * TODO
394: *
395: * @throws Exception
396: */
397: protected void parseAnnotations() throws Exception {
398: Log
399: .info("Class annotation processing is not currently implemented");
400: }
401:
402: protected void injectAndCallPostConstructCallbacks()
403: throws Exception {
404: EventListener[] listeners = getWebAppContext()
405: .getEventListeners();
406: for (int i = 0; i < listeners.length; i++) {
407: _injections.inject(listeners[i]);
408: _callbacks.callPostConstructCallback(listeners[i]);
409: }
410: }
411:
412: protected void callPreDestroyCallbacks() {
413: EventListener[] listeners = getWebAppContext()
414: .getEventListeners();
415: for (int i = 0; i < listeners.length; i++) {
416: _callbacks.callPreDestroyCallback(listeners[i]);
417: }
418: }
419:
420: }
|