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

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