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