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