blob: fa35c4c4c039de76a4c8264ae6e9150b879b8b23 [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.annotations;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import javax.annotation.Resource;
import javax.naming.InitialContext;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import org.eclipse.jetty.annotations.AnnotationIntrospector.AbstractIntrospectableAnnotationHandler;
import org.eclipse.jetty.plus.annotation.Injection;
import org.eclipse.jetty.plus.annotation.InjectionCollection;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.webapp.MetaData;
import org.eclipse.jetty.webapp.WebAppContext;
public class ResourceAnnotationHandler extends AbstractIntrospectableAnnotationHandler
{
private static final Logger LOG = Log.getLogger(ResourceAnnotationHandler.class);
protected WebAppContext _context;
public ResourceAnnotationHandler (WebAppContext wac)
{
super(true);
_context = wac;
}
/**
* Class level Resource annotations declare a name in the
* environment that will be looked up at runtime. They do
* not specify an injection.
*/
public void doHandle(Class<?> clazz)
{
if (Util.supportsResourceInjection(clazz))
{
handleClass(clazz);
Method[] methods = clazz.getDeclaredMethods();
for (int i=0; i<methods.length; i++)
handleMethod(clazz, methods[i]);
Field[] fields = clazz.getDeclaredFields();
//For each field, get all of it's annotations
for (int i=0; i<fields.length; i++)
handleField(clazz, fields[i]);
}
}
public void handleClass (Class<?> clazz)
{
Resource resource = (Resource)clazz.getAnnotation(Resource.class);
if (resource != null)
{
String name = resource.name();
String mappedName = resource.mappedName();
if (name==null || name.trim().equals(""))
throw new IllegalStateException ("Class level Resource annotations must contain a name (Common Annotations Spec Section 2.3)");
try
{
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name,mappedName))
if (!org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name,mappedName))
throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
}
catch (NamingException e)
{
LOG.warn(e);
}
}
}
public void handleField(Class<?> clazz, Field field)
{
Resource resource = (Resource)field.getAnnotation(Resource.class);
if (resource != null)
{
//JavaEE Spec 5.2.3: Field cannot be static
if (Modifier.isStatic(field.getModifiers()))
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be static");
return;
}
//JavaEE Spec 5.2.3: Field cannot be final
if (Modifier.isFinal(field.getModifiers()))
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+field.getName()+": cannot be final");
return;
}
//work out default name
String name = clazz.getCanonicalName()+"/"+field.getName();
//allow @Resource name= to override the field name
name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
//get the type of the Field
Class<?> type = field.getType();
//Servlet Spec 3.0 p. 76
//If a descriptor has specified at least 1 injection target for this
//resource, then it overrides this annotation
MetaData metaData = _context.getMetaData();
if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
{
//at least 1 injection was specified for this resource by a descriptor, so
//it overrides this annotation
return;
}
//No injections for this resource in any descriptors, so we can add it
//Does the injection already exist?
InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
if (injections == null)
{
injections = new InjectionCollection();
_context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
}
Injection injection = injections.getInjection(name, clazz, field);
if (injection == null)
{
//No injection has been specified, add it
try
{
boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
if (!bound)
bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
if (!bound)
bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
if (!bound)
{
//see if there is an env-entry value been bound
try
{
InitialContext ic = new InitialContext();
String nameInEnvironment = (mappedName!=null?mappedName:name);
ic.lookup("java:comp/env/"+nameInEnvironment);
bound = true;
}
catch (NameNotFoundException e)
{
bound = false;
}
}
//Check there is a JNDI entry for this annotation
if (bound)
{
LOG.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
// Make the Injection for it if the binding succeeded
injection = new Injection();
injection.setTarget(clazz, field, type);
injection.setJndiName(name);
injection.setMappingName(mappedName);
injections.add(injection);
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(type))
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
}
}
catch (NamingException e)
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
if (!Util.isEnvEntryType(type))
throw new IllegalStateException(e);
}
}
}
}
/**
* Process a Resource annotation on a Method.
*
* This will generate a JNDI entry, and an Injection to be
* processed when an instance of the class is created.
*/
public void handleMethod(Class<?> clazz, Method method)
{
Resource resource = (Resource)method.getAnnotation(Resource.class);
if (resource != null)
{
/*
* Commons Annotations Spec 2.3
* " The Resource annotation is used to declare a reference to a resource.
* It can be specified on a class, methods or on fields. When the
* annotation is applied on a field or method, the container will
* inject an instance of the requested resource into the application
* when the application is initialized... Even though this annotation
* is not marked Inherited, if used all superclasses MUST be examined
* to discover all uses of this annotation. All such annotation instances
* specify resources that are needed by the application. Note that this
* annotation may appear on private fields and methods of the superclasses.
* Injection of the declared resources needs to happen in these cases as
* well, even if a method with such an annotation is overridden by a subclass."
*
* Which IMHO, put more succinctly means "If you find a @Resource on any method
* or field, inject it!".
*/
//JavaEE Spec 5.2.3: Method cannot be static
if (Modifier.isStatic(method.getModifiers()))
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": cannot be static");
return;
}
// Check it is a valid javabean: must be void return type, the name must start with "set" and it must have
// only 1 parameter
if (!method.getName().startsWith("set"))
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, does not start with 'set'");
return;
}
if (method.getParameterTypes().length != 1)
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not single argument to method");
return;
}
if (Void.TYPE != method.getReturnType())
{
LOG.warn("Skipping Resource annotation on "+clazz.getName()+"."+method.getName()+": invalid java bean, not void");
return;
}
//default name is the javabean property name
String name = method.getName().substring(3);
name = name.substring(0,1).toLowerCase(Locale.ENGLISH)+name.substring(1);
name = clazz.getCanonicalName()+"/"+name;
name = (resource.name()!=null && !resource.name().trim().equals("")? resource.name(): name);
String mappedName = (resource.mappedName()!=null && !resource.mappedName().trim().equals("")?resource.mappedName():null);
Class<?> paramType = method.getParameterTypes()[0];
Class<?> resourceType = resource.type();
//Servlet Spec 3.0 p. 76
//If a descriptor has specified at least 1 injection target for this
//resource, then it overrides this annotation
MetaData metaData = _context.getMetaData();
if (metaData.getOriginDescriptor("resource-ref."+name+".injection") != null)
{
//at least 1 injection was specified for this resource by a descriptor, so
//it overrides this annotation
return;
}
//check if an injection has already been setup for this target by web.xml
InjectionCollection injections = (InjectionCollection)_context.getAttribute(InjectionCollection.INJECTION_COLLECTION);
if (injections == null)
{
injections = new InjectionCollection();
_context.setAttribute(InjectionCollection.INJECTION_COLLECTION, injections);
}
Injection injection = injections.getInjection(name, clazz, method, paramType);
if (injection == null)
{
try
{
//try binding name to environment
//try the webapp's environment first
boolean bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context, name, mappedName);
//try the server's environment
if (!bound)
bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(_context.getServer(), name, mappedName);
//try the jvm's environment
if (!bound)
bound = org.eclipse.jetty.plus.jndi.NamingEntryUtil.bindToENC(null, name, mappedName);
//TODO if it is an env-entry from web.xml it can be injected, in which case there will be no
//NamingEntry, just a value bound in java:comp/env
if (!bound)
{
try
{
InitialContext ic = new InitialContext();
String nameInEnvironment = (mappedName!=null?mappedName:name);
ic.lookup("java:comp/env/"+nameInEnvironment);
bound = true;
}
catch (NameNotFoundException e)
{
bound = false;
}
}
if (bound)
{
LOG.debug("Bound "+(mappedName==null?name:mappedName) + " as "+ name);
// Make the Injection for it
injection = new Injection();
injection.setTarget(clazz, method,paramType,resourceType);
injection.setJndiName(name);
injection.setMappingName(mappedName);
injections.add(injection);
//TODO - an @Resource is equivalent to a resource-ref, resource-env-ref, message-destination
metaData.setOrigin("resource-ref."+name+".injection",resource,clazz);
}
else if (!Util.isEnvEntryType(paramType))
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
throw new IllegalStateException("No resource at "+(mappedName==null?name:mappedName));
}
}
catch (NamingException e)
{
//if this is an env-entry type resource and there is no value bound for it, it isn't
//an error, it just means that perhaps the code will use a default value instead
// JavaEE Spec. sec 5.4.1.3
if (!Util.isEnvEntryType(paramType))
throw new IllegalStateException(e);
}
}
}
}
}