View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.annotations;
15  
16  import java.util.List;
17  
18  import javax.naming.InitialContext;
19  import javax.naming.NameNotFoundException;
20  import javax.naming.NamingException;
21  
22  import org.eclipse.jetty.annotations.AnnotationParser.AnnotationHandler;
23  import org.eclipse.jetty.annotations.AnnotationParser.Value;
24  import org.eclipse.jetty.plus.annotation.Injection;
25  import org.eclipse.jetty.plus.annotation.InjectionCollection;
26  import org.eclipse.jetty.util.log.Log;
27  import org.eclipse.jetty.webapp.WebAppContext;
28  
29  public class ResourceAnnotationHandler implements AnnotationHandler
30  {
31      protected WebAppContext _wac;
32      protected InjectionCollection _injections;
33  
34      public ResourceAnnotationHandler (WebAppContext wac)
35      {
36          _wac = wac;
37          _injections = (InjectionCollection)_wac.getAttribute(InjectionCollection.INJECTION_COLLECTION);
38      }
39  
40  
41      /**
42       *  Class level Resource annotations declare a name in the
43       *  environment that will be looked up at runtime. They do
44       *  not specify an injection.
45       */
46      public void handleClass(String className, int version, int access, String signature, String superName, String[] interfaces, String annotation,
47                              List<Value> values)
48      {
49          String name = null;
50          String mappedName = null;
51          String resourceType = null;
52          if (values != null)
53          {
54              for (Value v : values)
55              {
56                  if ("name".equals(v.getName()))
57                      name = (String)v.getValue();
58                  else if ("mappedName".equals(v.getName()))
59                      mappedName = (String)v.getValue();
60                  else if ("type".equals(v.getName()))
61                      resourceType = (String)v.getValue();
62              }
63  
64              if (name==null || name.trim().equals(""))
65                  throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
66  
67              try
68              {
69                  if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name,mappedName))
70                      if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.getServer(), name,mappedName))
71                          throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
72              }
73              catch (NamingException e)
74              {
75                  Log.warn(e);
76              }
77          }
78      }
79  
80      public void handleField(String className, String fieldName, int access, String fieldType, String signature, Object value, String annotation,
81                              List<Value> values)
82      {
83          try
84          {
85              //JavaEE Spec 5.2.3: Field cannot be static
86              if ((access & org.objectweb.asm.Opcodes.ACC_STATIC) > 0)
87              {
88                  Log.warn("Skipping Resource annotation on "+className+"."+fieldName+": cannot be static");
89                  return;
90              }
91  
92              //JavaEE Spec 5.2.3: Field cannot be final
93              if ((access & org.objectweb.asm.Opcodes.ACC_FINAL) > 0)
94              {
95                  Log.warn("Skipping Resource annotation on "+className+"."+fieldName+": cannot be final");
96                  return;
97              }
98  
99              //work out default name
100             String name = className+"/"+fieldName;
101             String mappedName = null;
102             org.objectweb.asm.Type resourceType = null;
103             if (values != null)
104             {
105                 for (Value val :values)
106                 {
107                     //allow @Resource name= to override the field name
108                     if (val.getName().equals("name") && !"".equals((String)val.getValue()))
109                         name = (String)(val.getValue());
110                     //get the mappedName if there is one
111                     else if (val.getName().equals("mappedName") && !"".equals((String)val.getValue()))
112                         mappedName = (String)val.getValue();
113                     //save @Resource type, so we can check it is compatible with field type later
114                     else if (val.getName().equals("type"))
115                     {
116                        resourceType = (org.objectweb.asm.Type)(val.getValue());
117                     }
118                 }
119             }
120           
121             //check if an injection has already been setup for this target by web.xml
122             Injection webXmlInjection = _injections.getInjection(name, className, fieldName);
123             if (webXmlInjection == null)
124             {
125                 try
126                 {
127                     boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name, mappedName);
128                     if (!bound)
129                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.getServer(), name, mappedName);
130                     if (!bound)
131                         bound =  org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName); 
132                     if (!bound)
133                     {
134                         //see if there is an env-entry value been bound from web.xml
135                         try
136                         {
137                             InitialContext ic = new InitialContext();
138                             String nameInEnvironment = (mappedName!=null?mappedName:name);
139                             ic.lookup("java:comp/env/"+nameInEnvironment);                               
140                             bound = true;
141                         }
142                         catch (NameNotFoundException e)
143                         {
144                             bound = false;
145                         }
146                     }
147                     //Check there is a JNDI entry for this annotation 
148                     if (bound)
149                     { 
150                         Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
151                         //   Make the Injection for it if the binding succeeded
152                         Injection injection = new Injection();
153                         injection.setTarget(className, fieldName, Util.asCanonicalName(resourceType));
154                         injection.setJndiName(name);
155                         injection.setMappingName(mappedName);
156                         _injections.add(injection); 
157                     }  
158                     else if (!Util.isEnvEntryType(fieldType))
159                     {
160                         System.err.println(fieldType);
161                         
162                         //if this is an env-entry type resource and there is no value bound for it, it isn't
163                         //an error, it just means that perhaps the code will use a default value instead
164                         // JavaEE Spec. sec 5.4.1.3
165 
166                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
167                     }
168                 }
169                 catch (NamingException e)
170                 {
171                     //if this is an env-entry type resource and there is no value bound for it, it isn't
172                     //an error, it just means that perhaps the code will use a default value instead
173                     // JavaEE Spec. sec 5.4.1.3
174                     if (!Util.isEnvEntryType(fieldType))
175                         throw new IllegalStateException(e);
176                 }
177             }
178         }
179         catch (Exception e)
180         {
181             Log.warn(e);
182         }
183     }
184 
185     /**
186      * Process a Resource annotation on a Method.
187      * 
188      * This will generate a JNDI entry, and an Injection to be
189      * processed when an instance of the class is created.
190      */
191     public void handleMethod(String className, String methodName, int access, String desc, String signature, String[] exceptions, String annotation,
192                              List<Value> values)
193     {
194         try
195         {
196           
197             /*
198              * Commons Annotations Spec 2.3
199              * " The Resource annotation is used to declare a reference to a resource.
200              *   It can be specified on a class, methods or on fields. When the 
201              *   annotation is applied on a field or method, the container will 
202              *   inject an instance of the requested resource into the application 
203              *   when the application is initialized... Even though this annotation 
204              *   is not marked Inherited, if used all superclasses MUST be examined 
205              *   to discover all uses of this annotation. All such annotation instances 
206              *   specify resources that are needed by the application. Note that this 
207              *   annotation may appear on private fields and methods of the superclasses. 
208              *   Injection of the declared resources needs to happen in these cases as 
209              *   well, even if a method with such an annotation is overridden by a subclass."
210              *  
211              *  Which IMHO, put more succinctly means "If you find a @Resource on any method
212              *  or field, inject it!".
213              */
214             //JavaEE Spec 5.2.3: Method cannot be static
215             if ((access & org.objectweb.asm.Opcodes.ACC_STATIC) > 0)
216             {
217                 Log.warn("Skipping Resource annotation on "+className+"."+methodName+": cannot be static");
218                 return;
219             }
220 
221             // Check it is a valid javabean: must be void return type, the name must start with "set" and it must have
222             // only 1 parameter
223             if (!methodName.startsWith("set"))
224             {
225                 Log.warn("Skipping Resource annotation on "+className+"."+methodName+": invalid java bean, does not start with 'set'");
226                 return;
227             }
228             org.objectweb.asm.Type[] args = org.objectweb.asm.Type.getArgumentTypes(desc);
229             if (args == null || args.length != 1)
230             {
231                 Log.warn("Skipping Resource annotation on "+className+"."+methodName+": invalid java bean, not single argument to method");
232                 return; 
233             }
234             org.objectweb.asm.Type retVal = org.objectweb.asm.Type.getReturnType(desc);
235             if (!org.objectweb.asm.Type.VOID_TYPE.equals(retVal))
236             {
237                 Log.warn("Skipping Resource annotation on "+className+"."+methodName+": invalid java bean, not void");
238                 return; 
239             }
240         
241 
242             //default name is the javabean property name
243             String name = methodName.substring(3);
244             name = name.substring(0,1).toLowerCase()+name.substring(1);
245             name = className+"/"+name;
246             String mappedName = null;
247             org.objectweb.asm.Type resourceType = null;
248             if (values != null)
249             {
250                 for (Value v : values)
251                 {
252                     //allow default name to be overridden
253                     if ("name".equals(v.getName()))
254                         name = (String)(v.getValue());
255                     //get the mappedName if there is one
256                     else if ("mappedName".equals(v.getName()) && !"".equals((String)(v.getValue())))
257                         mappedName = (String)(v.getValue());
258                     else if ("type".equals(v.getName()))
259                     {
260                         resourceType = (org.objectweb.asm.Type)(v.getValue());
261                     }
262                     //TODO: authentication and shareable
263                 }
264             }
265 
266             //check if an injection has already been setup for this target by web.xml
267             Injection webXmlInjection = _injections.getInjection(name, className, methodName, Util.asCanonicalName(args[0]));
268             if (webXmlInjection == null)
269             {
270                 try
271                 {
272                     //try binding name to environment
273                     //try the webapp's environment first
274                     boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac, name, mappedName);
275 
276                     //try the server's environment
277                     if (!bound)
278                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_wac.getServer(), name, mappedName);
279 
280                     //try the jvm's environment
281                     if (!bound)
282                         bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
283 
284                     //TODO if it is an env-entry from web.xml it can be injected, in which case there will be no
285                     //NamingEntry, just a value bound in java:comp/env
286                     if (!bound)
287                     {
288                         try
289                         {
290                             InitialContext ic = new InitialContext();
291                             String nameInEnvironment = (mappedName!=null?mappedName:name);
292                             ic.lookup("java:comp/env/"+nameInEnvironment);                               
293                             bound = true;
294                         }
295                         catch (NameNotFoundException e)
296                         {
297                             bound = false;
298                         }
299                     }
300 
301                     if (bound)
302                     {
303                         Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
304                         //   Make the Injection for it
305                         Injection injection = new Injection();
306                         injection.setTarget(className, methodName, Util.asCanonicalName(args[0]), Util.asCanonicalName(resourceType));
307                         injection.setJndiName(name);
308                         injection.setMappingName(mappedName);
309                         _injections.add(injection);
310                     } 
311                     else if (!Util.isEnvEntryType(args[0].getDescriptor()))
312                     {
313 
314                         //if this is an env-entry type resource and there is no value bound for it, it isn't
315                         //an error, it just means that perhaps the code will use a default value instead
316                         // JavaEE Spec. sec 5.4.1.3   
317                         throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
318                     }
319                 }
320                 catch (NamingException e)
321                 {  
322                     //if this is an env-entry type resource and there is no value bound for it, it isn't
323                     //an error, it just means that perhaps the code will use a default value instead
324                     // JavaEE Spec. sec 5.4.1.3
325                     if (!Util.isEnvEntryType(args[0].getDescriptor()))
326                         throw new IllegalStateException(e);
327                 }
328             }
329         }
330         catch (Exception e)
331         {
332             Log.warn(e);
333         }
334     }
335 }