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.lang.reflect.Field;
17 import java.lang.reflect.Method;
18 import java.lang.reflect.Modifier;
19
20 import javax.annotation.Resource;
21 import javax.naming.InitialContext;
22 import javax.naming.NameNotFoundException;
23 import javax.naming.NamingException;
24
25 import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
26 import org.eclipse.jetty.plus.annotation.Injection;
27 import org.eclipse.jetty.plus.annotation.InjectionCollection;
28 import org.eclipse.jetty.util.log.Log;
29 import org.eclipse.jetty.webapp.MetaData;
30 import org.eclipse.jetty.webapp.WebAppContext;
31
32 public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationHandler
33 {
34 protected WebAppContext _context;
35
36
37 public ResourceAnnotationHandler (WebAppContext wac)
38 {
39 super(true);
40 _context = wac;
41 }
42
43
44 /**
45 * Class level Resource annotations declare a name in the
46 * environment that will be looked up at runtime. They do
47 * not specify an injection.
48 */
49 public void doHandle(Class clazz)
50 {
51 if (Util.isServletType(clazz))
52 {
53 handleClass(clazz);
54
55 Method[] methods = clazz.getDeclaredMethods();
56 for (int i=0; i<methods.length; i++)
57 handleMethod(clazz, methods[i]);
58 Field[] fields = clazz.getDeclaredFields();
59 //For each field, get all of it's annotations
60 for (int i=0; i<fields.length; i++)
61 handleField(clazz, fields[i]);
62 }
63 }
64
65 public void handleClass (Class clazz)
66 {
67 Resource resource = (Resource)clazz.getAnnotation(Resource.class);
68 if (resource != null)
69 {
70 String name = resource.name();
71 String mappedName = resource.mappedName();
72 Resource.AuthenticationType auth = resource.authenticationType();
73 Class type = resource.type();
74 boolean shareable = resource.shareable();
75
76 if (name==null || name.trim().equals(""))
77 throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
78
79 try
80 {
81 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name,mappedName))
82 if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name,mappedName))
83 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
84 }
85 catch (NamingException e)
86 {
87 Log.warn(e);
88 }
89 }
90 }
91
92 public void handleField(Class clazz, Field field)
93 {
94 Resource resource = (Resource)field.getAnnotation(Resource.class);
95 if (resource != null)
96 {
97 //JavaEE Spec 5.2.3: Field cannot be static
98 if (Modifier.isStatic(field.getModifiers()))
99 {
100 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be static");
101 return;
102 }
103
104 //JavaEE Spec 5.2.3: Field cannot be final
105 if (Modifier.isFinal(field.getModifiers()))
106 {
107 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be final");
108 return;
109 }
110
111 //work out default name
112 String name = clazz.getCanonicalName()+"/"+field.getName();
113
114 //allow @Resource name= to override the field name
115 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
116 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
117 //get the type of the Field
118 Class type = field.getType();
119
120 //Servlet Spec 3.0 p. 76
121 //If a descriptor has specified at least 1 injection target for this
122 //resource, then it overrides this annotation
123 MetaData metaData = _context.getMetaData();
124 if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
125 {
126 //at least 1 injection was specified for this resource by a descriptor, so
127 //it overrides this annotation
128 return;
129 }
130
131 //No injections for this resource in any descriptors, so we can add it
132 //Does the injection already exist?
133 InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
134 if (injections == null)
135 {
136 injections = new InjectionCollection();
137 _context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
138 }
139 Injection injection = injections.getInjection(name, clazz, field);
140 if (injection == null)
141 {
142 //No injection has been specified, add it
143 try
144 {
145 boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
146 if (!bound)
147 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
148 if (!bound)
149 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
150 if (!bound)
151 {
152 //see if there is an env-entry value been bound
153 try
154 {
155 InitialContext ic = new InitialContext();
156 String nameInEnvironment = (mappedName!=null?mappedName:name);
157 ic.lookup("java:comp/env/"+nameInEnvironment);
158 bound = true;
159 }
160 catch (NameNotFoundException e)
161 {
162 bound = false;
163 }
164 }
165 //Check there is a JNDI entry for this annotation
166 if (bound)
167 {
168 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
169 // Make the Injection for it if the binding succeeded
170 injection = new Injection();
171 injection.setTarget(clazz, field, type);
172 injection.setJndiName(name);
173 injection.setMappingName(mappedName);
174 injections.add(injection);
175
176 //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
177 metaData.setOrigin("resource-ref."+name+".injection");
178 }
179 else if (!Util.isEnvEntryType(type))
180 {
181 //if this is an env-entry type resource and there is no value bound for it, it isn't
182 //an error, it just means that perhaps the code will use a default value instead
183 // JavaEE Spec. sec 5.4.1.3
184
185 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
186 }
187 }
188 catch (NamingException e)
189 {
190 //if this is an env-entry type resource and there is no value bound for it, it isn't
191 //an error, it just means that perhaps the code will use a default value instead
192 // JavaEE Spec. sec 5.4.1.3
193 if (!Util.isEnvEntryType(type))
194 throw new IllegalStateException(e);
195 }
196 }
197 }
198 }
199
200
201 /**
202 * Process a Resource annotation on a Method.
203 *
204 * This will generate a JNDI entry, and an Injection to be
205 * processed when an instance of the class is created.
206 */
207 public void handleMethod(Class clazz, Method method)
208 {
209
210 Resource resource = (Resource)method.getAnnotation(Resource.class);
211 if (resource != null)
212 {
213 /*
214 * Commons Annotations Spec 2.3
215 * " The Resource annotation is used to declare a reference to a resource.
216 * It can be specified on a class, methods or on fields. When the
217 * annotation is applied on a field or method, the container will
218 * inject an instance of the requested resource into the application
219 * when the application is initialized... Even though this annotation
220 * is not marked Inherited, if used all superclasses MUST be examined
221 * to discover all uses of this annotation. All such annotation instances
222 * specify resources that are needed by the application. Note that this
223 * annotation may appear on private fields and methods of the superclasses.
224 * Injection of the declared resources needs to happen in these cases as
225 * well, even if a method with such an annotation is overridden by a subclass."
226 *
227 * Which IMHO, put more succinctly means "If you find a @Resource on any method
228 * or field, inject it!".
229 */
230 //JavaEE Spec 5.2.3: Method cannot be static
231 if (Modifier.isStatic(method.getModifiers()))
232 {
233 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": cannot be static");
234 return;
235 }
236
237 // Check it is a valid javabean: must be void return type, the name must start with "set" and it must have
238 // only 1 parameter
239 if (!method.getName().startsWith("set"))
240 {
241 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, does not start with 'set'");
242 return;
243 }
244
245 if (method.getParameterTypes().length != 1)
246 {
247 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not single argument to method");
248 return;
249 }
250
251 if (Void.TYPE != method.getReturnType())
252 {
253 Log.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not void");
254 return;
255 }
256
257
258 //default name is the javabean property name
259 String name = method.getName().substring(3);
260 name = name.substring(0,1).toLowerCase()+name.substring(1);
261 name = clazz.getCanonicalName()+"/"+name;
262
263 name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
264 String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
265 Class paramType = method.getParameterTypes()[0];
266
267 Class resourceType = resource.type();
268
269 //Servlet Spec 3.0 p. 76
270 //If a descriptor has specified at least 1 injection target for this
271 //resource, then it overrides this annotation
272 MetaData metaData = _context.getMetaData();
273 if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
274 {
275 //at least 1 injection was specified for this resource by a descriptor, so
276 //it overrides this annotation
277 return;
278 }
279
280 //check if an injection has already been setup for this target by web.xml
281 InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
282 if (injections == null)
283 {
284 injections = new InjectionCollection();
285 _context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
286 }
287 Injection injection = injections.getInjection(name, clazz, method, paramType);
288 if (injection == null)
289 {
290 try
291 {
292 //try binding name to environment
293 //try the webapp's environment first
294 boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
295
296 //try the server's environment
297 if (!bound)
298 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
299
300 //try the jvm's environment
301 if (!bound)
302 bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
303
304 //TODO if it is an env-entry from web.xml it can be injected, in which case there will be no
305 //NamingEntry, just a value bound in java:comp/env
306 if (!bound)
307 {
308 try
309 {
310 InitialContext ic = new InitialContext();
311 String nameInEnvironment = (mappedName!=null?mappedName:name);
312 ic.lookup("java:comp/env/"+nameInEnvironment);
313 bound = true;
314 }
315 catch (NameNotFoundException e)
316 {
317 bound = false;
318 }
319 }
320
321 if (bound)
322 {
323 Log.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
324 // Make the Injection for it
325 injection = new Injection();
326 injection.setTarget(clazz, method,paramType,resourceType);
327 injection.setJndiName(name);
328 injection.setMappingName(mappedName);
329 injections.add(injection);
330 //TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
331 metaData.setOrigin("resource-ref."+name+".injection");
332 }
333 else if (!Util.isEnvEntryType(paramType))
334 {
335
336 //if this is an env-entry type resource and there is no value bound for it, it isn't
337 //an error, it just means that perhaps the code will use a default value instead
338 // JavaEE Spec. sec 5.4.1.3
339 throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
340 }
341 }
342 catch (NamingException e)
343 {
344 //if this is an env-entry type resource and there is no value bound for it, it isn't
345 //an error, it just means that perhaps the code will use a default value instead
346 // JavaEE Spec. sec 5.4.1.3
347 if (!Util.isEnvEntryType(paramType))
348 throw new IllegalStateException(e);
349 }
350 }
351
352 }
353 }
354 }