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