All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.opensymphony.xwork2.inject.ContainerImpl Maven / Gradle / Ivy

Go to download

XWork is an command-pattern framework that is used to power WebWork as well as other applications. XWork provides an Inversion of Control container, a powerful expression language, data type conversion, validation, and pluggable configuration.

There is a newer version: 2.1.3
Show newest version
/**
 * Copyright (C) 2006 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.opensymphony.xwork2.inject;

import com.opensymphony.xwork2.inject.util.ReferenceCache;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import java.io.Serializable;

/**
 * Default {@link Container} implementation.
 *
 * @see ContainerBuilder
 * @author [email protected] (Bob Lee)
 */
class ContainerImpl implements Container {

  final Map, InternalFactory> factories;
  final Map,Set> factoryNamesByType;

  ContainerImpl(Map, InternalFactory> factories) {
    this.factories = factories;
    Map,Set> map = new HashMap,Set>();
    for (Key key : factories.keySet()) {
      Set names = map.get(key.getType());
      if (names == null) {
        names = new HashSet();
        map.put(key.getType(), names);
      }
      names.add(key.getName());
    }
    
    for (Entry,Set> entry : map.entrySet()) {
      entry.setValue(Collections.unmodifiableSet(entry.getValue()));
    }
    
    this.factoryNamesByType = Collections.unmodifiableMap(map);
  }

  @SuppressWarnings("unchecked")
   InternalFactory getFactory(Key key) {
    return (InternalFactory) factories.get(key);
  }

  /**
   * Field and method injectors.
   */
  final Map, List> injectors =
      new ReferenceCache, List>() {
        protected List create(Class key) {
          List injectors = new ArrayList();
          addInjectors(key, injectors);
          return injectors;
        }
      };

  /**
   * Recursively adds injectors for fields and methods from the given class to
   * the given list. Injects parent classes before sub classes.
   */
  void addInjectors(Class clazz, List injectors) {
    if (clazz == Object.class) {
      return;
    }

    // Add injectors for superclass first.
    addInjectors(clazz.getSuperclass(), injectors);

    // TODO (crazybob): Filter out overridden members.
    addInjectorsForFields(clazz.getDeclaredFields(), false, injectors);
    addInjectorsForMethods(clazz.getDeclaredMethods(), false, injectors);
  }

  void injectStatics(List> staticInjections) {
    final List injectors = new ArrayList();

    for (Class clazz : staticInjections) {
      addInjectorsForFields(clazz.getDeclaredFields(), true, injectors);
      addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors);
    }

    callInContext(new ContextualCallable() {
      public Void call(InternalContext context) {
        for (Injector injector : injectors) {
          injector.inject(context, null);
        }
        return null;
      }
    });
  }

  void addInjectorsForMethods(Method[] methods, boolean statics,
      List injectors) {
    addInjectorsForMembers(Arrays.asList(methods), statics, injectors,
        new InjectorFactory() {
          public Injector create(ContainerImpl container, Method method,
              String name) throws MissingDependencyException {
            return new MethodInjector(container, method, name);
          }
        });
  }

  void addInjectorsForFields(Field[] fields, boolean statics,
      List injectors) {
    addInjectorsForMembers(Arrays.asList(fields), statics, injectors,
        new InjectorFactory() {
          public Injector create(ContainerImpl container, Field field,
              String name) throws MissingDependencyException {
            return new FieldInjector(container, field, name);
          }
        });
  }

   void addInjectorsForMembers(
      List members, boolean statics, List injectors,
      InjectorFactory injectorFactory) {
    for (M member : members) {
      if (isStatic(member) == statics) {
        Inject inject = member.getAnnotation(Inject.class);
        if (inject != null) {
          try {
            injectors.add(injectorFactory.create(this, member, inject.value()));
          } catch (MissingDependencyException e) {
            if (inject.required()) {
              throw new DependencyException(e);
            }
          }
        }
      }
    }
  }

  interface InjectorFactory {
    Injector create(ContainerImpl container, M member, String name)
        throws MissingDependencyException;
  }

  private boolean isStatic(Member member) {
    return Modifier.isStatic(member.getModifiers());
  }

  static class FieldInjector implements Injector {

    final Field field;
    final InternalFactory factory;
    final ExternalContext externalContext;

    public FieldInjector(ContainerImpl container, Field field, String name)
        throws MissingDependencyException {
      this.field = field;
      field.setAccessible(true);

      Key key = Key.newInstance(field.getType(), name);
      factory = container.getFactory(key);
      if (factory == null) {
        throw new MissingDependencyException(
            "No mapping found for dependency " + key + " in " + field + ".");
      }

      this.externalContext = ExternalContext.newInstance(field, key, container);
    }

    public void inject(InternalContext context, Object o) {
      ExternalContext previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
        field.set(o, factory.create(context));
      } catch (IllegalAccessException e) {
        throw new AssertionError(e);
      } finally {
        context.setExternalContext(previous);
      }
    }
  }

  /**
   * Gets parameter injectors.
   *
   * @param member to which the parameters belong
   * @param annotations on the parameters
   * @param parameterTypes parameter types
   * @return injections
   */
   ParameterInjector[]
      getParametersInjectors(M member,
      Annotation[][] annotations, Class[] parameterTypes, String defaultName)
      throws MissingDependencyException {
    List> parameterInjectors =
        new ArrayList>();

    Iterator annotationsIterator =
        Arrays.asList(annotations).iterator();
    for (Class parameterType : parameterTypes) {
      Inject annotation = findInject(annotationsIterator.next());
      String name = annotation == null ? defaultName : annotation.value();
      Key key = Key.newInstance(parameterType, name);
      parameterInjectors.add(createParameterInjector(key, member));
    }

    return toArray(parameterInjectors);
  }

   ParameterInjector createParameterInjector(
      Key key, Member member) throws MissingDependencyException {
    InternalFactory factory = getFactory(key);
    if (factory == null) {
      throw new MissingDependencyException(
          "No mapping found for dependency " + key + " in " + member + ".");
    }

    ExternalContext externalContext =
        ExternalContext.newInstance(member, key, this);
    return new ParameterInjector(externalContext, factory);
  }

  @SuppressWarnings("unchecked")
  private ParameterInjector[] toArray(
      List> parameterInjections) {
    return parameterInjections.toArray(
        new ParameterInjector[parameterInjections.size()]);
  }

  /**
   * Finds the {@link Inject} annotation in an array of annotations.
   */
  Inject findInject(Annotation[] annotations) {
    for (Annotation annotation : annotations) {
      if (annotation.annotationType() == Inject.class) {
        return Inject.class.cast(annotation);
      }
    }
    return null;
  }

  static class MethodInjector implements Injector {

    final Method method;
    final ParameterInjector[] parameterInjectors;

    public MethodInjector(ContainerImpl container, Method method, String name)
        throws MissingDependencyException {
      this.method = method;
      method.setAccessible(true);

      Class[] parameterTypes = method.getParameterTypes();
      if (parameterTypes.length == 0) {
        throw new DependencyException(
            method + " has no parameters to inject.");
      }
      parameterInjectors = container.getParametersInjectors(
          method, method.getParameterAnnotations(), parameterTypes, name);
    }

    public void inject(InternalContext context, Object o) {
      try {
        method.invoke(o, getParameters(method, context, parameterInjectors));
      } catch (Exception e) {
        throw new RuntimeException(e);
      }
    }
  }

  Map, ConstructorInjector> constructors =
      new ReferenceCache, ConstructorInjector>() {
        @SuppressWarnings("unchecked")
        protected ConstructorInjector create(Class implementation) {
          return new ConstructorInjector(ContainerImpl.this, implementation);
        }
      };

  static class ConstructorInjector {

    final Class implementation;
    final List injectors;
    final Constructor constructor;
    final ParameterInjector[] parameterInjectors;

    ConstructorInjector(ContainerImpl container, Class implementation) {
      this.implementation = implementation;

      constructor = findConstructorIn(implementation);
      constructor.setAccessible(true);

      try {
        Inject inject = constructor.getAnnotation(Inject.class);
        parameterInjectors = inject == null
            ? null // default constructor.
            : container.getParametersInjectors(
                constructor,
                constructor.getParameterAnnotations(),
                constructor.getParameterTypes(),
                inject.value()
              );
      } catch (MissingDependencyException e) {
        throw new DependencyException(e);
      }
      injectors = container.injectors.get(implementation);
    }

    @SuppressWarnings("unchecked")
    private Constructor findConstructorIn(Class implementation) {
      Constructor found = null;
      Constructor[] declaredConstructors = (Constructor[]) implementation
                    .getDeclaredConstructors();
      for(Constructor constructor :  declaredConstructors) {
        if (constructor.getAnnotation(Inject.class) != null) {
          if (found != null) {
            throw new DependencyException("More than one constructor annotated"
              + " with @Inject found in " + implementation + ".");
          }
          found = constructor;
        }
      }
      if (found != null) {
        return found;
      }

      // If no annotated constructor is found, look for a no-arg constructor
      // instead.
      try {
        return implementation.getDeclaredConstructor();
      } catch (NoSuchMethodException e) {
        throw new DependencyException("Could not find a suitable constructor"
            + " in " + implementation.getName() + ".");
      }
    }

    /**
     * Construct an instance. Returns {@code Object} instead of {@code T}
     * because it may return a proxy.
     */
    Object construct(InternalContext context, Class expectedType) {
      ConstructionContext constructionContext =
          context.getConstructionContext(this);

      // We have a circular reference between constructors. Return a proxy.
      if (constructionContext.isConstructing()) {
        // TODO (crazybob): if we can't proxy this object, can we proxy the
        // other object?
        return constructionContext.createProxy(expectedType);
      }

      // If we're re-entering this factory while injecting fields or methods,
      // return the same instance. This prevents infinite loops.
      T t = constructionContext.getCurrentReference();
      if (t != null) {
        return t;
      }

      try {
        // First time through...
        constructionContext.startConstruction();
        try {
          Object[] parameters =
              getParameters(constructor, context, parameterInjectors);
          t = constructor.newInstance(parameters);
          constructionContext.setProxyDelegates(t);
        } finally {
          constructionContext.finishConstruction();
        }

        // Store reference. If an injector re-enters this factory, they'll
        // get the same reference.
        constructionContext.setCurrentReference(t);

        // Inject fields and methods.
        for (Injector injector : injectors) {
          injector.inject(context, t);
        }

        return t;
      } catch (InstantiationException e) {
        throw new RuntimeException(e);
      } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
      } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
      } finally {
        constructionContext.removeCurrentReference();
      }
    }
  }

  static class ParameterInjector {

    final ExternalContext externalContext;
    final InternalFactory factory;

    public ParameterInjector(ExternalContext externalContext,
        InternalFactory factory) {
      this.externalContext = externalContext;
      this.factory = factory;
    }

    T inject(Member member, InternalContext context) {
      ExternalContext previous = context.getExternalContext();
      context.setExternalContext(externalContext);
      try {
        return factory.create(context);
      } finally {
        context.setExternalContext(previous);
      }
    }
  }

  private static Object[] getParameters(Member member, InternalContext context,
      ParameterInjector[] parameterInjectors) {
    if (parameterInjectors == null) {
      return null;
    }

    Object[] parameters = new Object[parameterInjectors.length];
    for (int i = 0; i < parameters.length; i++) {
      parameters[i] = parameterInjectors[i].inject(member, context);
    }
    return parameters;
  }

  void inject(Object o, InternalContext context) {
    List injectors = this.injectors.get(o.getClass());
    for (Injector injector : injectors) {
      injector.inject(context, o);
    }
  }

   T inject(Class implementation, InternalContext context) {
    try {
      ConstructorInjector constructor = getConstructor(implementation);
      return implementation.cast(
          constructor.construct(context, implementation));
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  @SuppressWarnings("unchecked")
   T getInstance(Class type, String name, InternalContext context) {
    ExternalContext previous = context.getExternalContext();
    Key key = Key.newInstance(type, name);
    context.setExternalContext(ExternalContext.newInstance(null, key, this));
    try {
      InternalFactory o = getFactory(key);
      if (o != null) {
          return getFactory(key).create(context);
      } else {
          return null;
      }
    } finally {
      context.setExternalContext(previous);
    }
  }

   T getInstance(Class type, InternalContext context) {
    return getInstance(type, DEFAULT_NAME, context);
  }

  public void inject(final Object o) {
    callInContext(new ContextualCallable() {
      public Void call(InternalContext context) {
        inject(o, context);
        return null;
      }
    });
  }

  public  T inject(final Class implementation) {
    return callInContext(new ContextualCallable() {
      public T call(InternalContext context) {
        return inject(implementation, context);
      }
    });
  }

  public  T getInstance(final Class type, final String name) {
    return callInContext(new ContextualCallable() {
      public T call(InternalContext context) {
        return getInstance(type, name, context);
      }
    });
  }

  public  T getInstance(final Class type) {
    return callInContext(new ContextualCallable() {
      public T call(InternalContext context) {
        return getInstance(type, context);
      }
    });
  }
  
  public Set getInstanceNames(final Class type) {
    return factoryNamesByType.get(type);
  }

  ThreadLocal localContext =
      new ThreadLocal() {
        protected InternalContext[] initialValue() {
          return new InternalContext[1];
        }
      };

  /**
   * Looks up thread local context. Creates (and removes) a new context if
   * necessary.
   */
   T callInContext(ContextualCallable callable) {
    InternalContext[] reference = (InternalContext[]) localContext.get();
    if (reference[0] == null) {
      reference[0] = new InternalContext(this);
      try {
        return callable.call(reference[0]);
      } finally {
        // Only remove the context if this call created it.
        reference[0] = null;
      }
    } else {
      // Someone else will clean up this context.
      return callable.call(reference[0]);
    }
  }

  interface ContextualCallable {
    T call(InternalContext context);
  }

  /**
   * Gets a constructor function for a given implementation class.
   */
  @SuppressWarnings("unchecked")
   ConstructorInjector getConstructor(Class implementation) {
    return constructors.get(implementation);
  }

  final ThreadLocal localScopeStrategy =
      new ThreadLocal();

  public void setScopeStrategy(Scope.Strategy scopeStrategy) {
    this.localScopeStrategy.set(scopeStrategy);
  }

  public void removeScopeStrategy() {
    this.localScopeStrategy.remove();
  }

  /**
   * Injects a field or method in a given object.
   */
  interface Injector extends Serializable {
    void inject(InternalContext context, Object o);
  }

  static class MissingDependencyException extends Exception {

    MissingDependencyException(String message) {
      super(message);
    }
  }
}