Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.inject » [javadoc | source]
    1   /**
    2    * Copyright (C) 2006 Google Inc.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    * http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package com.opensymphony.xwork2.inject;
   18   
   19   import com.opensymphony.xwork2.inject.util.ReferenceCache;
   20   
   21   import java.lang.annotation.Annotation;
   22   import java.lang.reflect.AccessibleObject;
   23   import java.lang.reflect.AnnotatedElement;
   24   import java.lang.reflect.Constructor;
   25   import java.lang.reflect.Field;
   26   import java.lang.reflect.InvocationTargetException;
   27   import java.lang.reflect.Member;
   28   import java.lang.reflect.Method;
   29   import java.lang.reflect.Modifier;
   30   import java.util.ArrayList;
   31   import java.util.Arrays;
   32   import java.util.Collections;
   33   import java.util.HashMap;
   34   import java.util.HashSet;
   35   import java.util.Iterator;
   36   import java.util.List;
   37   import java.util.Map;
   38   import java.util.Set;
   39   import java.util.Map.Entry;
   40   import java.io.Serializable;
   41   
   42   /**
   43    * Default {@link Container} implementation.
   44    *
   45    * @see ContainerBuilder
   46    * @author crazybob@google.com (Bob Lee)
   47    */
   48   class ContainerImpl implements Container {
   49   
   50     final Map<Key<?>, InternalFactory<?>> factories;
   51     final Map<Class<?>,Set<String>> factoryNamesByType;
   52   
   53     ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) {
   54       this.factories = factories;
   55       Map<Class<?>,Set<String>> map = new HashMap<Class<?>,Set<String>>();
   56       for (Key<?> key : factories.keySet()) {
   57         Set<String> names = map.get(key.getType());
   58         if (names == null) {
   59           names = new HashSet<String>();
   60           map.put(key.getType(), names);
   61         }
   62         names.add(key.getName());
   63       }
   64       
   65       for (Entry<Class<?>,Set<String>> entry : map.entrySet()) {
   66         entry.setValue(Collections.unmodifiableSet(entry.getValue()));
   67       }
   68       
   69       this.factoryNamesByType = Collections.unmodifiableMap(map);
   70     }
   71   
   72     @SuppressWarnings("unchecked")
   73     <T> InternalFactory<? extends T> getFactory(Key<T> key) {
   74       return (InternalFactory<T>) factories.get(key);
   75     }
   76   
   77     /**
   78      * Field and method injectors.
   79      */
   80     final Map<Class<?>, List<Injector>> injectors =
   81         new ReferenceCache<Class<?>, List<Injector>>() {
   82           protected List<Injector> create(Class<?> key) {
   83             List<Injector> injectors = new ArrayList<Injector>();
   84             addInjectors(key, injectors);
   85             return injectors;
   86           }
   87         };
   88   
   89     /**
   90      * Recursively adds injectors for fields and methods from the given class to
   91      * the given list. Injects parent classes before sub classes.
   92      */
   93     void addInjectors(Class clazz, List<Injector> injectors) {
   94       if (clazz == Object.class) {
   95         return;
   96       }
   97   
   98       // Add injectors for superclass first.
   99       addInjectors(clazz.getSuperclass(), injectors);
  100   
  101       // TODO (crazybob): Filter out overridden members.
  102       addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
  103       addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
  104     }
  105   
  106     void injectStatics(List<Class<?>> staticInjections) {
  107       final List<Injector> injectors = new ArrayList<Injector>();
  108   
  109       for (Class<?> clazz : staticInjections) {
  110         addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
  111         addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
  112       }
  113   
  114       callInContext(new ContextualCallable<Void>() {
  115         public Void call(InternalContext context) {
  116           for (Injector injector : injectors) {
  117             injector.inject(context, null);
  118           }
  119           return null;
  120         }
  121       });
  122     }
  123   
  124     void addInjectorsForMethods(Method[] methods, boolean statics,
  125         List<Injector> injectors) {
  126       addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
  127           new InjectorFactory<Method>() {
  128             public Injector create(ContainerImpl container, Method method,
  129                 String name) throws MissingDependencyException {
  130               return new MethodInjector(container, method, name);
  131             }
  132           });
  133     }
  134   
  135     void addInjectorsForFields(Field[] fields, boolean statics,
  136         List<Injector> injectors) {
  137       addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
  138           new InjectorFactory<Field>() {
  139             public Injector create(ContainerImpl container, Field field,
  140                 String name) throws MissingDependencyException {
  141               return new FieldInjector(container, field, name);
  142             }
  143           });
  144     }
  145   
  146     <M extends Member & AnnotatedElement> void addInjectorsForMembers(
  147         List<M> members, boolean statics, List<Injector> injectors,
  148         InjectorFactory<M> injectorFactory) {
  149       for (M member : members) {
  150         if (isStatic(member) == statics) {
  151           Inject inject = member.getAnnotation(Inject.class);
  152           if (inject != null) {
  153             try {
  154               injectors.add(injectorFactory.create(this, member, inject.value()));
  155             } catch (MissingDependencyException e) {
  156               if (inject.required()) {
  157                 throw new DependencyException(e);
  158               }
  159             }
  160           }
  161         }
  162       }
  163     }
  164   
  165     interface InjectorFactory<M extends Member & AnnotatedElement> {
  166       Injector create(ContainerImpl container, M member, String name)
  167           throws MissingDependencyException;
  168     }
  169   
  170     private boolean isStatic(Member member) {
  171       return Modifier.isStatic(member.getModifiers());
  172     }
  173   
  174     static class FieldInjector implements Injector {
  175   
  176       final Field field;
  177       final InternalFactory<?> factory;
  178       final ExternalContext<?> externalContext;
  179   
  180       public FieldInjector(ContainerImpl container, Field field, String name)
  181           throws MissingDependencyException {
  182         this.field = field;
  183         field.setAccessible(true);
  184   
  185         Key<?> key = Key.newInstance(field.getType(), name);
  186         factory = container.getFactory(key);
  187         if (factory == null) {
  188           throw new MissingDependencyException(
  189               "No mapping found for dependency " + key + " in " + field + ".");
  190         }
  191   
  192         this.externalContext = ExternalContext.newInstance(field, key, container);
  193       }
  194   
  195       public void inject(InternalContext context, Object o) {
  196         ExternalContext<?> previous = context.getExternalContext();
  197         context.setExternalContext(externalContext);
  198         try {
  199           field.set(o, factory.create(context));
  200         } catch (IllegalAccessException e) {
  201           throw new AssertionError(e);
  202         } finally {
  203           context.setExternalContext(previous);
  204         }
  205       }
  206     }
  207   
  208     /**
  209      * Gets parameter injectors.
  210      *
  211      * @param member to which the parameters belong
  212      * @param annotations on the parameters
  213      * @param parameterTypes parameter types
  214      * @return injections
  215      */
  216     <M extends AccessibleObject & Member> ParameterInjector<?>[]
  217         getParametersInjectors(M member,
  218         Annotation[][] annotations, Class[] parameterTypes, String defaultName)
  219         throws MissingDependencyException {
  220       List<ParameterInjector<?>> parameterInjectors =
  221           new ArrayList<ParameterInjector<?>>();
  222   
  223       Iterator<Annotation[]> annotationsIterator =
  224           Arrays.asList(annotations).iterator();
  225       for (Class<?> parameterType : parameterTypes) {
  226         Inject annotation = findInject(annotationsIterator.next());
  227         String name = annotation == null ? defaultName : annotation.value();
  228         Key<?> key = Key.newInstance(parameterType, name);
  229         parameterInjectors.add(createParameterInjector(key, member));
  230       }
  231   
  232       return toArray(parameterInjectors);
  233     }
  234   
  235     <T> ParameterInjector<T> createParameterInjector(
  236         Key<T> key, Member member) throws MissingDependencyException {
  237       InternalFactory<? extends T> factory = getFactory(key);
  238       if (factory == null) {
  239         throw new MissingDependencyException(
  240             "No mapping found for dependency " + key + " in " + member + ".");
  241       }
  242   
  243       ExternalContext<T> externalContext =
  244           ExternalContext.newInstance(member, key, this);
  245       return new ParameterInjector<T>(externalContext, factory);
  246     }
  247   
  248     @SuppressWarnings("unchecked")
  249     private ParameterInjector<?>[] toArray(
  250         List<ParameterInjector<?>> parameterInjections) {
  251       return parameterInjections.toArray(
  252           new ParameterInjector[parameterInjections.size()]);
  253     }
  254   
  255     /**
  256      * Finds the {@link Inject} annotation in an array of annotations.
  257      */
  258     Inject findInject(Annotation[] annotations) {
  259       for (Annotation annotation : annotations) {
  260         if (annotation.annotationType() == Inject.class) {
  261           return Inject.class.cast(annotation);
  262         }
  263       }
  264       return null;
  265     }
  266   
  267     static class MethodInjector implements Injector {
  268   
  269       final Method method;
  270       final ParameterInjector<?>[] parameterInjectors;
  271   
  272       public MethodInjector(ContainerImpl container, Method method, String name)
  273           throws MissingDependencyException {
  274         this.method = method;
  275         method.setAccessible(true);
  276   
  277         Class<?>[] parameterTypes = method.getParameterTypes();
  278         if (parameterTypes.length == 0) {
  279           throw new DependencyException(
  280               method + " has no parameters to inject.");
  281         }
  282         parameterInjectors = container.getParametersInjectors(
  283             method, method.getParameterAnnotations(), parameterTypes, name);
  284       }
  285   
  286       public void inject(InternalContext context, Object o) {
  287         try {
  288           method.invoke(o, getParameters(method, context, parameterInjectors));
  289         } catch (Exception e) {
  290           throw new RuntimeException(e);
  291         }
  292       }
  293     }
  294   
  295     Map<Class<?>, ConstructorInjector> constructors =
  296         new ReferenceCache<Class<?>, ConstructorInjector>() {
  297           @SuppressWarnings("unchecked")
  298           protected ConstructorInjector<?> create(Class<?> implementation) {
  299             return new ConstructorInjector(ContainerImpl.this, implementation);
  300           }
  301         };
  302   
  303     static class ConstructorInjector<T> {
  304   
  305       final Class<T> implementation;
  306       final List<Injector> injectors;
  307       final Constructor<T> constructor;
  308       final ParameterInjector<?>[] parameterInjectors;
  309   
  310       ConstructorInjector(ContainerImpl container, Class<T> implementation) {
  311         this.implementation = implementation;
  312   
  313         constructor = findConstructorIn(implementation);
  314         constructor.setAccessible(true);
  315   
  316         MissingDependencyException exception = null;
  317         Inject inject = null;
  318         ParameterInjector<?>[] parameters = null;
  319           
  320         try {
  321           inject = constructor.getAnnotation(Inject.class);
  322           parameters = constructParameterInjector(inject, container, constructor);
  323         } catch (MissingDependencyException e) {
  324           exception = e;
  325         }
  326         parameterInjectors = parameters;
  327   
  328         if ( exception != null) {
  329           if ( inject != null && inject.required()) {
  330             throw new DependencyException(exception);
  331           }
  332         }
  333         injectors = container.injectors.get(implementation);
  334       }
  335   
  336       ParameterInjector<?>[] constructParameterInjector(
  337       Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException{
  338       return constructor.getParameterTypes().length == 0
  339         ? null // default constructor.
  340         : container.getParametersInjectors(
  341           constructor,
  342           constructor.getParameterAnnotations(),
  343           constructor.getParameterTypes(),
  344           inject.value()
  345         );
  346       }
  347   
  348       @SuppressWarnings("unchecked")
  349       private Constructor<T> findConstructorIn(Class<T> implementation) {
  350         Constructor<T> found = null;
  351         Constructor<T>[] declaredConstructors = (Constructor<T>[]) implementation
  352                       .getDeclaredConstructors();
  353         for(Constructor<T> constructor :  declaredConstructors) {
  354           if (constructor.getAnnotation(Inject.class) != null) {
  355             if (found != null) {
  356               throw new DependencyException("More than one constructor annotated"
  357                 + " with @Inject found in " + implementation + ".");
  358             }
  359             found = constructor;
  360           }
  361         }
  362         if (found != null) {
  363           return found;
  364         }
  365   
  366         // If no annotated constructor is found, look for a no-arg constructor
  367         // instead.
  368         try {
  369           return implementation.getDeclaredConstructor();
  370         } catch (NoSuchMethodException e) {
  371           throw new DependencyException("Could not find a suitable constructor"
  372               + " in " + implementation.getName() + ".");
  373         }
  374       }
  375   
  376       /**
  377        * Construct an instance. Returns {@code Object} instead of {@code T}
  378        * because it may return a proxy.
  379        */
  380       Object construct(InternalContext context, Class<? super T> expectedType) {
  381         ConstructionContext<T> constructionContext =
  382             context.getConstructionContext(this);
  383   
  384         // We have a circular reference between constructors. Return a proxy.
  385         if (constructionContext.isConstructing()) {
  386           // TODO (crazybob): if we can't proxy this object, can we proxy the
  387           // other object?
  388           return constructionContext.createProxy(expectedType);
  389         }
  390   
  391         // If we're re-entering this factory while injecting fields or methods,
  392         // return the same instance. This prevents infinite loops.
  393         T t = constructionContext.getCurrentReference();
  394         if (t != null) {
  395           return t;
  396         }
  397   
  398         try {
  399           // First time through...
  400           constructionContext.startConstruction();
  401           try {
  402             Object[] parameters =
  403                 getParameters(constructor, context, parameterInjectors);
  404             t = constructor.newInstance(parameters);
  405             constructionContext.setProxyDelegates(t);
  406           } finally {
  407             constructionContext.finishConstruction();
  408           }
  409   
  410           // Store reference. If an injector re-enters this factory, they'll
  411           // get the same reference.
  412           constructionContext.setCurrentReference(t);
  413   
  414           // Inject fields and methods.
  415           for (Injector injector : injectors) {
  416             injector.inject(context, t);
  417           }
  418   
  419           return t;
  420         } catch (InstantiationException e) {
  421           throw new RuntimeException(e);
  422         } catch (IllegalAccessException e) {
  423           throw new RuntimeException(e);
  424         } catch (InvocationTargetException e) {
  425           throw new RuntimeException(e);
  426         } finally {
  427           constructionContext.removeCurrentReference();
  428         }
  429       }
  430     }
  431   
  432     static class ParameterInjector<T> {
  433   
  434       final ExternalContext<T> externalContext;
  435       final InternalFactory<? extends T> factory;
  436   
  437       public ParameterInjector(ExternalContext<T> externalContext,
  438           InternalFactory<? extends T> factory) {
  439         this.externalContext = externalContext;
  440         this.factory = factory;
  441       }
  442   
  443       T inject(Member member, InternalContext context) {
  444         ExternalContext<?> previous = context.getExternalContext();
  445         context.setExternalContext(externalContext);
  446         try {
  447           return factory.create(context);
  448         } finally {
  449           context.setExternalContext(previous);
  450         }
  451       }
  452     }
  453   
  454     private static Object[] getParameters(Member member, InternalContext context,
  455         ParameterInjector[] parameterInjectors) {
  456       if (parameterInjectors == null) {
  457         return null;
  458       }
  459   
  460       Object[] parameters = new Object[parameterInjectors.length];
  461       for (int i = 0; i < parameters.length; i++) {
  462         parameters[i] = parameterInjectors[i].inject(member, context);
  463       }
  464       return parameters;
  465     }
  466   
  467     void inject(Object o, InternalContext context) {
  468       List<Injector> injectors = this.injectors.get(o.getClass());
  469       for (Injector injector : injectors) {
  470         injector.inject(context, o);
  471       }
  472     }
  473   
  474     <T> T inject(Class<T> implementation, InternalContext context) {
  475       try {
  476         ConstructorInjector<T> constructor = getConstructor(implementation);
  477         return implementation.cast(
  478             constructor.construct(context, implementation));
  479       } catch (Exception e) {
  480         throw new RuntimeException(e);
  481       }
  482     }
  483   
  484     @SuppressWarnings("unchecked")
  485     <T> T getInstance(Class<T> type, String name, InternalContext context) {
  486       ExternalContext<?> previous = context.getExternalContext();
  487       Key<T> key = Key.newInstance(type, name);
  488       context.setExternalContext(ExternalContext.newInstance(null, key, this));
  489       try {
  490         InternalFactory o = getFactory(key);
  491         if (o != null) {
  492             return getFactory(key).create(context);
  493         } else {
  494             return null;
  495         }
  496       } finally {
  497         context.setExternalContext(previous);
  498       }
  499     }
  500   
  501     <T> T getInstance(Class<T> type, InternalContext context) {
  502       return getInstance(type, DEFAULT_NAME, context);
  503     }
  504   
  505     public void inject(final Object o) {
  506       callInContext(new ContextualCallable<Void>() {
  507         public Void call(InternalContext context) {
  508           inject(o, context);
  509           return null;
  510         }
  511       });
  512     }
  513   
  514     public <T> T inject(final Class<T> implementation) {
  515       return callInContext(new ContextualCallable<T>() {
  516         public T call(InternalContext context) {
  517           return inject(implementation, context);
  518         }
  519       });
  520     }
  521   
  522     public <T> T getInstance(final Class<T> type, final String name) {
  523       return callInContext(new ContextualCallable<T>() {
  524         public T call(InternalContext context) {
  525           return getInstance(type, name, context);
  526         }
  527       });
  528     }
  529   
  530     public <T> T getInstance(final Class<T> type) {
  531       return callInContext(new ContextualCallable<T>() {
  532         public T call(InternalContext context) {
  533           return getInstance(type, context);
  534         }
  535       });
  536     }
  537     
  538     public Set<String> getInstanceNames(final Class<?> type) {
  539       return factoryNamesByType.get(type);
  540     }
  541   
  542     ThreadLocal<Object[]> localContext =
  543         new ThreadLocal<Object[]>() {
  544           protected InternalContext[] initialValue() {
  545             return new InternalContext[1];
  546           }
  547         };
  548   
  549     /**
  550      * Looks up thread local context. Creates (and removes) a new context if
  551      * necessary.
  552      */
  553     <T> T callInContext(ContextualCallable<T> callable) {
  554       InternalContext[] reference = (InternalContext[])localContext.get();
  555       if (reference[0] == null) {
  556         reference[0] = new InternalContext(this);
  557         try {
  558           return callable.call(reference[0]);
  559         } finally {
  560           // Only remove the context if this call created it.
  561           reference[0] = null;
  562         }
  563       } else {
  564         // Someone else will clean up this context.
  565         return callable.call(reference[0]);
  566       }
  567     }
  568   
  569     interface ContextualCallable<T> {
  570       T call(InternalContext context);
  571     }
  572   
  573     /**
  574      * Gets a constructor function for a given implementation class.
  575      */
  576     @SuppressWarnings("unchecked")
  577     <T> ConstructorInjector<T> getConstructor(Class<T> implementation) {
  578       return constructors.get(implementation);
  579     }
  580   
  581     final ThreadLocal<Object> localScopeStrategy =
  582         new ThreadLocal<Object>();
  583   
  584     public void setScopeStrategy(Scope.Strategy scopeStrategy) {
  585       this.localScopeStrategy.set(scopeStrategy);
  586     }
  587   
  588     public void removeScopeStrategy() {
  589       this.localScopeStrategy.remove();
  590     }
  591   
  592     /**
  593      * Injects a field or method in a given object.
  594      */
  595     interface Injector extends Serializable {
  596       void inject(InternalContext context, Object o);
  597     }
  598   
  599     static class MissingDependencyException extends Exception {
  600   
  601       MissingDependencyException(String message) {
  602         super(message);
  603       }
  604     }
  605   }

Save This Page
Home » xwork-2.1.1-src » com.opensymphony.xwork2.inject » [javadoc | source]