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