1   // ========================================================================
2   // $Id: AbstractConfiguration.java 3029 2008-06-19 08:48:11Z janb $
3   // Copyright 2006 Mort Bay Consulting Pty. Ltd.
4   // ------------------------------------------------------------------------
5   // Licensed under the Apache License, Version 2.0 (the "License");
6   // you may not use this file except in compliance with the License.
7   // You may obtain a copy of the License at 
8   // http://www.apache.org/licenses/LICENSE-2.0
9   // Unless required by applicable law or agreed to in writing, software
10  // distributed under the License is distributed on an "AS IS" BASIS,
11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  // See the License for the specific language governing permissions and
13  // limitations under the License.
14  // ========================================================================
15  
16  package org.mortbay.jetty.plus.webapp;
17  
18  
19  import java.util.EventListener;
20  import java.util.Iterator;
21  
22  import javax.servlet.UnavailableException;
23  
24  import org.mortbay.jetty.plus.annotation.Injection;
25  import org.mortbay.jetty.plus.annotation.InjectionCollection;
26  import org.mortbay.jetty.plus.annotation.LifeCycleCallback;
27  import org.mortbay.jetty.plus.annotation.LifeCycleCallbackCollection;
28  import org.mortbay.jetty.plus.annotation.PostConstructCallback;
29  import org.mortbay.jetty.plus.annotation.PreDestroyCallback;
30  import org.mortbay.jetty.plus.annotation.RunAsCollection;
31  import org.mortbay.jetty.plus.servlet.ServletHandler;
32  import org.mortbay.jetty.security.SecurityHandler;
33  import org.mortbay.jetty.servlet.ServletHolder;
34  import org.mortbay.jetty.webapp.WebAppContext;
35  import org.mortbay.jetty.webapp.WebXmlConfiguration;
36  import org.mortbay.log.Log;
37  import org.mortbay.util.TypeUtil;
38  import org.mortbay.xml.XmlParser;
39  
40  
41  
42  /**
43   * Configuration
44   *
45   *
46   */
47  public abstract class AbstractConfiguration extends WebXmlConfiguration
48  {
49      protected LifeCycleCallbackCollection _callbacks = new LifeCycleCallbackCollection();
50      protected InjectionCollection _injections = new InjectionCollection();
51      protected RunAsCollection _runAsCollection = new RunAsCollection();
52      
53      public abstract void bindEnvEntry (String name, Object value) throws Exception;
54      
55      public abstract void bindResourceRef (String name, Class type) throws Exception;
56      
57      public abstract void bindResourceEnvRef (String name, Class type) throws Exception;
58      
59      public abstract void bindUserTransaction () throws Exception;
60      
61      public abstract void bindMessageDestinationRef (String name, Class type)  throws Exception;
62      
63      
64      /**
65       * @throws ClassNotFoundException
66       */
67      public AbstractConfiguration() throws ClassNotFoundException
68      {
69          super();
70      }
71  
72      
73      public void setWebAppContext (WebAppContext context)
74      {
75          super.setWebAppContext(context);
76          
77          //set up our special ServletHandler to remember injections and lifecycle callbacks
78          ServletHandler servletHandler = new ServletHandler();
79          SecurityHandler securityHandler = getWebAppContext().getSecurityHandler();        
80          org.mortbay.jetty.servlet.ServletHandler existingHandler = getWebAppContext().getServletHandler();       
81          servletHandler.setFilterMappings(existingHandler.getFilterMappings());
82          servletHandler.setFilters(existingHandler.getFilters());
83          servletHandler.setServlets(existingHandler.getServlets());
84          servletHandler.setServletMappings(existingHandler.getServletMappings());
85          getWebAppContext().setServletHandler(servletHandler);
86          securityHandler.setHandler(servletHandler);       
87      }
88      
89      public void configureDefaults ()
90      throws Exception
91      {
92          super.configureDefaults();
93      }
94     
95      public void configureWebApp ()
96      throws Exception
97      {
98          super.configureWebApp();
99          bindUserTransaction();
100     }
101     
102     public void deconfigureWebApp()
103     throws Exception
104     {
105         //call any preDestroy methods on the listeners
106         callPreDestroyCallbacks();
107         
108         super.deconfigureWebApp();
109     }
110     
111     public void configure(String webXml)
112     throws Exception
113     {
114         //parse web.xml
115         super.configure(webXml);
116         
117         //parse classes for annotations, if necessary
118         if (!_metaDataComplete)
119         {
120             if (Log.isDebugEnabled()) Log.debug("Processing annotations");
121             parseAnnotations();
122         }
123         //do any injects on the listeners that were created and then
124         //also callback any postConstruct lifecycle methods
125         injectAndCallPostConstructCallbacks();
126     }
127 
128     
129   
130     
131     
132     protected void initialize(XmlParser.Node config) 
133     throws ClassNotFoundException,UnavailableException
134     {
135         super.initialize(config);
136         
137         //configure injections and callbacks to be called by the FilterHolder and ServletHolder
138         //when they lazily instantiate the Filter/Servlet.
139         ((ServletHandler)getWebAppContext().getServletHandler()).setInjections(_injections);
140         ((ServletHandler)getWebAppContext().getServletHandler()).setCallbacks(_callbacks);
141     }
142     
143     
144     protected void initWebXmlElement(String element,XmlParser.Node node) throws Exception
145     {
146         if ("env-entry".equals(element))
147         {
148             initEnvEntry (node);
149         }
150         else if ("resource-ref".equals(element))
151         {
152             //resource-ref entries are ONLY for connection factories
153             //the resource-ref says how the app will reference the jndi lookup relative
154             //to java:comp/env, but it is up to the deployer to map this reference to
155             //a real resource in the environment. At the moment, we insist that the
156             //jetty.xml file name of the resource has to be exactly the same as the
157             //name in web.xml deployment descriptor, but it shouldn't have to be
158             initResourceRef(node);
159         }
160         else if ("resource-env-ref".equals(element))
161         {
162             //resource-env-ref elements are a non-connection factory type of resource
163             //the app looks them up relative to java:comp/env
164             //again, need a way for deployer to link up app naming to real naming.
165             //Again, we insist now that the name of the resource in jetty.xml is
166             //the same as web.xml
167             initResourceEnvRef(node);      
168         }
169         else if ("message-destination-ref".equals(element))
170         {
171             initMessageDestinationRef(node);
172         }
173         else if ("post-construct".equals(element))
174         {
175             //post-construct is the name of a class and method to call after all
176             //resources have been setup but before the class is put into use
177             initPostConstruct(node);
178         }
179         else if ("pre-destroy".equals(element))
180         {
181             //pre-destroy is the name of a class and method to call just as
182             //the instance is being destroyed
183             initPreDestroy(node);
184         }
185         else
186         {
187             super.initWebXmlElement(element, node);
188         }
189  
190     }
191     
192     /**
193      * JavaEE 5.4.1.3 
194      * 
195      * 
196      * @param node
197      * @throws Exception
198      */
199     protected void initEnvEntry (XmlParser.Node node)
200     throws Exception
201     {
202         String name=node.getString("env-entry-name",false,true);
203         String type = node.getString("env-entry-type",false,true);
204         String valueStr = node.getString("env-entry-value",false,true);
205         
206         //if there's no value there's no point in making a jndi entry
207         //nor processing injection entries
208         if (valueStr==null || valueStr.equals(""))
209         {
210             Log.warn("No value for env-entry-name "+name);
211             return;
212         }
213       
214         //the javaee_5.xsd says that the env-entry-type is optional
215         //if there is an <injection> element, because you can get
216         //type from the element, but what to do if there is more
217         //than one <injection> element, do you just pick the type
218         //of the first one?
219         
220         //check for <injection> elements
221         initInjection (node, name, TypeUtil.fromName(type));
222        
223         //bind the entry into jndi
224         Object value = TypeUtil.valueOf(type,valueStr);
225         bindEnvEntry(name, value);
226         
227     }
228     
229     
230     /**
231      * Common Annotations Spec section 2.3:
232      *  resource-ref is for:
233      *    - javax.sql.DataSource
234      *    - javax.jms.ConnectionFactory
235      *    - javax.jms.QueueConnectionFactory
236      *    - javax.jms.TopicConnectionFactory
237      *    - javax.mail.Session
238      *    - java.net.URL
239      *    - javax.resource.cci.ConnectionFactory
240      *    - org.omg.CORBA_2_3.ORB
241      *    - any other connection factory defined by a resource adapter
242      * @param node
243      * @throws Exception
244      */
245     protected void initResourceRef (XmlParser.Node node)
246     throws Exception
247     {
248         String jndiName = node.getString("res-ref-name",false,true);
249         String type = node.getString("res-type", false, true);
250         String auth = node.getString("res-auth", false, true);
251         String shared = node.getString("res-sharing-scope", false, true);
252 
253         //check for <injection> elements
254         Class typeClass = TypeUtil.fromName(type);
255         if (typeClass==null)
256             typeClass = getWebAppContext().loadClass(type);
257         initInjection (node, jndiName, typeClass);
258         
259         bindResourceRef(jndiName, typeClass);
260     }
261     
262     
263     /**
264      * Common Annotations Spec section 2.3:
265      *   resource-env-ref is for:
266      *     - javax.transaction.UserTransaction
267      *     - javax.resource.cci.InteractionSpec
268      *     - anything else that is not a connection factory
269      * @param node
270      * @throws Exception
271      */
272     protected void initResourceEnvRef (XmlParser.Node node)
273     throws Exception
274     {
275         String jndiName = node.getString("resource-env-ref-name",false,true);
276         String type = node.getString("resource-env-ref-type", false, true);
277 
278         //check for <injection> elements
279         
280         //JavaEE Spec sec 5.7.1.3 says the resource-env-ref-type
281         //is mandatory, but the schema says it is optional!
282         Class typeClass = TypeUtil.fromName(type);
283         if (typeClass==null)
284             typeClass = getWebAppContext().loadClass(type);
285         initInjection (node, jndiName, typeClass);
286         
287         bindResourceEnvRef(jndiName, typeClass);
288     }
289     
290     
291     /**
292      * Common Annotations Spec section 2.3:
293      *   message-destination-ref is for:
294      *     - javax.jms.Queue
295      *     - javax.jms.Topic
296      * @param node
297      * @throws Exception
298      */
299     protected void initMessageDestinationRef (XmlParser.Node node)
300     throws Exception
301     {
302         String jndiName = node.getString("message-destination-ref-name",false,true);
303         String type = node.getString("message-destination-type",false,true);
304         String usage = node.getString("message-destination-usage",false,true);
305         
306         Class typeClass = TypeUtil.fromName(type);
307         if (typeClass==null)
308             typeClass = getWebAppContext().loadClass(type);
309         initInjection(node, jndiName, typeClass);
310         
311         bindMessageDestinationRef(jndiName, typeClass);
312     }
313     
314     
315     
316     /**
317      * Process &lt;post-construct&gt;
318      * @param node
319      */
320     protected void initPostConstruct(XmlParser.Node node)
321     {
322         String className = node.getString("lifecycle-callback-class", false, true);
323         String methodName = node.getString("lifecycle-callback-method", false, true);
324         
325         if (className==null || className.equals(""))
326         {
327             Log.warn("No lifecycle-callback-class specified");
328             return;
329         }
330         if (methodName==null || methodName.equals(""))
331         {
332             Log.warn("No lifecycle-callback-method specified for class "+className);
333             return;
334         }
335         
336         try
337         {
338             Class clazz = getWebAppContext().loadClass(className);
339             LifeCycleCallback callback = new PostConstructCallback();
340             callback.setTarget(clazz, methodName);
341             _callbacks.add(callback);
342         }
343         catch (ClassNotFoundException e)
344         {
345             Log.warn("Couldn't load post-construct target class "+className);
346         }
347     }
348     
349     
350     /**
351      * Process &lt;pre-destroy&gt;
352      * @param node
353      */
354     protected void initPreDestroy(XmlParser.Node node)
355     {
356         String className = node.getString("lifecycle-callback-class", false, true);
357         String methodName = node.getString("lifecycle-callback-method", false, true);
358         if (className==null || className.equals(""))
359         {
360             Log.warn("No lifecycle-callback-class specified for pre-destroy");
361             return;
362         }
363         if (methodName==null || methodName.equals(""))
364         {
365             Log.warn("No lifecycle-callback-method specified for pre-destroy class "+className);
366             return;
367         } 
368         
369         try
370         {
371             Class clazz = getWebAppContext().loadClass(className);
372             LifeCycleCallback callback = new PreDestroyCallback();
373             callback.setTarget(clazz, methodName);
374             _callbacks.add(callback);
375         }
376         catch (ClassNotFoundException e)
377         {
378             Log.warn("Couldn't load pre-destory target class "+className);
379         }
380     }
381     
382     
383     /**
384      * Iterate over the &lt;injection-target&gt; entries for a node
385      * 
386      * @param node
387      * @param jndiName
388      * @param valueClass
389      * @return the type of the injectable
390      */
391     protected void initInjection (XmlParser.Node node, String jndiName, Class valueClass)
392     {
393         Iterator  itor = node.iterator("injection-target");
394         
395         while(itor.hasNext())
396         {
397             XmlParser.Node injectionNode = (XmlParser.Node)itor.next(); 
398             String targetClassName = injectionNode.getString("injection-target-class", false, true);
399             String targetName = injectionNode.getString("injection-target-name", false, true);
400             if ((targetClassName==null) || targetClassName.equals(""))
401             {
402                 Log.warn("No classname found in injection-target");
403                 continue;
404             }
405             if ((targetName==null) || targetName.equals(""))
406             {
407                 Log.warn("No field or method name in injection-target");
408                 continue;
409             }
410 
411             // comments in the javaee_5.xsd file specify that the targetName is looked
412             // for first as a java bean property, then if that fails, as a field
413             try
414             {
415                 Class clazz = getWebAppContext().loadClass(targetClassName);
416                 Injection injection = new Injection();
417                 injection.setTargetClass(clazz);
418                 injection.setJndiName(jndiName);
419                 injection.setTarget(clazz, targetName, valueClass);
420                  _injections.add(injection);
421             }
422             catch (ClassNotFoundException e)
423             {
424                 Log.warn("Couldn't load injection target class "+targetClassName);
425             }
426         }
427     }
428     
429     
430     /**
431      * Parse all classes that are mentioned in web.xml (servlets, filters, listeners)
432      * for annotations.
433      * 
434      * 
435      * 
436      * @throws Exception
437      */
438     protected abstract void parseAnnotations () throws Exception;
439    
440     
441     
442     protected void injectAndCallPostConstructCallbacks()
443     throws Exception
444     {
445         //look thru the servlets to apply any runAs annotations
446         //NOTE: that any run-as in web.xml will already have been applied
447         ServletHolder[] holders = getWebAppContext().getServletHandler().getServlets();
448         for (int i=0;holders!=null && i<holders.length;i++)
449         {
450             _runAsCollection.setRunAs(holders[i]);
451         }
452         
453         
454         EventListener[] listeners = getWebAppContext().getEventListeners();
455         for (int i=0;i<listeners.length;i++)
456         {
457             _injections.inject(listeners[i]);
458             _callbacks.callPostConstructCallback(listeners[i]);
459         }
460     }
461     
462     
463     protected void callPreDestroyCallbacks ()
464     throws Exception
465     {
466         EventListener[] listeners = getWebAppContext().getEventListeners();
467         for (int i=0;i<listeners.length;i++)
468         {
469             _callbacks.callPreDestroyCallback(listeners[i]);
470         }
471     }
472    
473 }