http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java new file mode 100644 index 0000000..1879933 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/ContainerImpl.java @@ -0,0 +1,603 @@ +/** + * Copyright (C) 2006 Google Inc. + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.io.Serializable; +import java.lang.annotation.Annotation; +import java.lang.reflect.*; +import java.security.AccessControlException; +import java.util.*; +import java.util.Map.Entry; + +/** + * Default {@link Container} implementation. + * + * @author [email protected] (Bob Lee) + * @see ContainerBuilder + */ +class ContainerImpl implements Container { + + final Map<Key<?>, InternalFactory<?>> factories; + final Map<Class<?>, Set<String>> factoryNamesByType; + + ContainerImpl(Map<Key<?>, InternalFactory<?>> factories) { + this.factories = factories; + Map<Class<?>, Set<String>> map = new HashMap<>(); + for (Key<?> key : factories.keySet()) { + Set<String> names = map.get(key.getType()); + if (names == null) { + names = new HashSet<>(); + map.put(key.getType(), names); + } + names.add(key.getName()); + } + + for (Entry<Class<?>, Set<String>> entry : map.entrySet()) { + entry.setValue(Collections.unmodifiableSet(entry.getValue())); + } + + this.factoryNamesByType = Collections.unmodifiableMap(map); + } + + @SuppressWarnings("unchecked") + <T> InternalFactory<? extends T> getFactory(Key<T> key) { + return (InternalFactory<T>) factories.get(key); + } + + /** + * Field and method injectors. + */ + final Map<Class<?>, List<Injector>> injectors = + new ReferenceCache<Class<?>, List<Injector>>() { + @Override + protected List<Injector> create(Class<?> key) { + List<Injector> 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<Injector> 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<Class<?>> staticInjections) { + final List<Injector> injectors = new ArrayList<>(); + + for (Class<?> clazz : staticInjections) { + addInjectorsForFields(clazz.getDeclaredFields(), true, injectors); + addInjectorsForMethods(clazz.getDeclaredMethods(), true, injectors); + } + + callInContext(new ContextualCallable<Void>() { + public Void call(InternalContext context) { + for (Injector injector : injectors) { + injector.inject(context, null); + } + return null; + } + }); + } + + void addInjectorsForMethods(Method[] methods, boolean statics, List<Injector> injectors) { + addInjectorsForMembers(Arrays.asList(methods), statics, injectors, + new InjectorFactory<Method>() { + public Injector create(ContainerImpl container, Method method, + String name) throws MissingDependencyException { + return new MethodInjector(container, method, name); + } + }); + } + + void addInjectorsForFields(Field[] fields, boolean statics, List<Injector> injectors) { + addInjectorsForMembers(Arrays.asList(fields), statics, injectors, + new InjectorFactory<Field>() { + public Injector create(ContainerImpl container, Field field, + String name) throws MissingDependencyException { + return new FieldInjector(container, field, name); + } + }); + } + + <M extends Member & AnnotatedElement> void addInjectorsForMembers( + List<M> members, boolean statics, List<Injector> injectors, InjectorFactory<M> 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<M extends Member & AnnotatedElement> { + + 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; + if (!field.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) { + sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + } + field.setAccessible(true); + } catch (AccessControlException e) { + throw new DependencyException("Security manager in use, could not access field: " + + field.getDeclaringClass().getName() + "(" + field.getName() + ")", e); + } + } + + 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 + */ + <M extends AccessibleObject & Member> ParameterInjector<?>[] + getParametersInjectors(M member, Annotation[][] annotations, Class[] parameterTypes, String defaultName) throws MissingDependencyException { + List<ParameterInjector<?>> parameterInjectors = new ArrayList<>(); + + Iterator<Annotation[]> 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); + } + + <T> ParameterInjector<T> createParameterInjector(Key<T> key, Member member) throws MissingDependencyException { + InternalFactory<? extends T> factory = getFactory(key); + if (factory == null) { + throw new MissingDependencyException("No mapping found for dependency " + key + " in " + member + "."); + } + + ExternalContext<T> externalContext = ExternalContext.newInstance(member, key, this); + return new ParameterInjector<T>(externalContext, factory); + } + + @SuppressWarnings("unchecked") + private ParameterInjector<?>[] toArray(List<ParameterInjector<?>> 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; + if (!method.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) { + sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + } + method.setAccessible(true); + } catch (AccessControlException e) { + throw new DependencyException("Security manager in use, could not access method: " + + name + "(" + method.getName() + ")", e); + } + } + + 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<Class<?>, ConstructorInjector> constructors = + new ReferenceCache<Class<?>, ConstructorInjector>() { + @Override + @SuppressWarnings("unchecked") + protected ConstructorInjector<?> create(Class<?> implementation) { + return new ConstructorInjector(ContainerImpl.this, implementation); + } + }; + + static class ConstructorInjector<T> { + + final Class<T> implementation; + final List<Injector> injectors; + final Constructor<T> constructor; + final ParameterInjector<?>[] parameterInjectors; + + ConstructorInjector(ContainerImpl container, Class<T> implementation) { + this.implementation = implementation; + + constructor = findConstructorIn(implementation); + if (!constructor.isAccessible()) { + SecurityManager sm = System.getSecurityManager(); + try { + if (sm != null) { + sm.checkPermission(new ReflectPermission("suppressAccessChecks")); + } + constructor.setAccessible(true); + } catch (AccessControlException e) { + throw new DependencyException("Security manager in use, could not access constructor: " + + implementation.getName() + "(" + constructor.getName() + ")", e); + } + } + + MissingDependencyException exception = null; + Inject inject = null; + ParameterInjector<?>[] parameters = null; + + try { + inject = constructor.getAnnotation(Inject.class); + parameters = constructParameterInjector(inject, container, constructor); + } catch (MissingDependencyException e) { + exception = e; + } + parameterInjectors = parameters; + + if (exception != null) { + if (inject != null && inject.required()) { + throw new DependencyException(exception); + } + } + injectors = container.injectors.get(implementation); + } + + ParameterInjector<?>[] constructParameterInjector( + Inject inject, ContainerImpl container, Constructor<T> constructor) throws MissingDependencyException { + return constructor.getParameterTypes().length == 0 + ? null // default constructor. + : container.getParametersInjectors( + constructor, + constructor.getParameterAnnotations(), + constructor.getParameterTypes(), + inject.value() + ); + } + + @SuppressWarnings("unchecked") + private Constructor<T> findConstructorIn(Class<T> implementation) { + Constructor<T> found = null; + Constructor<T>[] declaredConstructors = (Constructor<T>[]) implementation.getDeclaredConstructors(); + for (Constructor<T> 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<? super T> expectedType) { + ConstructionContext<T> 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 | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } finally { + constructionContext.removeCurrentReference(); + } + } + } + + static class ParameterInjector<T> { + + final ExternalContext<T> externalContext; + final InternalFactory<? extends T> factory; + + public ParameterInjector(ExternalContext<T> externalContext, InternalFactory<? extends T> 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<Injector> injectors = this.injectors.get(o.getClass()); + for (Injector injector : injectors) { + injector.inject(context, o); + } + } + + <T> T inject(Class<T> implementation, InternalContext context) { + try { + ConstructorInjector<T> constructor = getConstructor(implementation); + return implementation.cast(constructor.construct(context, implementation)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + <T> T getInstance(Class<T> type, String name, InternalContext context) { + ExternalContext<?> previous = context.getExternalContext(); + Key<T> 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> T getInstance(Class<T> type, InternalContext context) { + return getInstance(type, DEFAULT_NAME, context); + } + + public void inject(final Object o) { + callInContext(new ContextualCallable<Void>() { + public Void call(InternalContext context) { + inject(o, context); + return null; + } + }); + } + + public <T> T inject(final Class<T> implementation) { + return callInContext(new ContextualCallable<T>() { + public T call(InternalContext context) { + return inject(implementation, context); + } + }); + } + + public <T> T getInstance(final Class<T> type, final String name) { + return callInContext(new ContextualCallable<T>() { + public T call(InternalContext context) { + return getInstance(type, name, context); + } + }); + } + + public <T> T getInstance(final Class<T> type) { + return callInContext(new ContextualCallable<T>() { + public T call(InternalContext context) { + return getInstance(type, context); + } + }); + } + + public Set<String> getInstanceNames(final Class<?> type) { + Set<String> names = factoryNamesByType.get(type); + if (names == null) { + names = Collections.emptySet(); + } + return names; + } + + ThreadLocal<Object[]> localContext = new ThreadLocal<Object[]>() { + @Override + protected Object[] initialValue() { + return new Object[1]; + } + }; + + /** + * Looks up thread local context. Creates (and removes) a new context if necessary. + */ + <T> T callInContext(ContextualCallable<T> callable) { + Object[] reference = localContext.get(); + if (reference[0] == null) { + reference[0] = new InternalContext(this); + try { + return callable.call((InternalContext) reference[0]); + } finally { + // Only remove the context if this call created it. + reference[0] = null; + // WW-3768: ThreadLocal was not removed + localContext.remove(); + } + } else { + // Someone else will clean up this context. + return callable.call((InternalContext) reference[0]); + } + } + + interface ContextualCallable<T> { + T call(InternalContext context); + } + + /** + * Gets a constructor function for a given implementation class. + */ + @SuppressWarnings("unchecked") + <T> ConstructorInjector<T> getConstructor(Class<T> implementation) { + return constructors.get(implementation); + } + + final ThreadLocal<Object> 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); + } + } +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Context.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Context.java b/core/src/main/java/com/opensymphony/xwork2/inject/Context.java new file mode 100644 index 0000000..233b5e4 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Context.java @@ -0,0 +1,57 @@ +/** + * 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 java.lang.reflect.Member; + +/** + * Context of the current injection. + * + * @author [email protected] (Bob Lee) + */ +public interface Context { + + /** + * Gets the {@link Container}. + */ + Container getContainer(); + + /** + * Gets the current scope strategy. See {@link + * Container#setScopeStrategy(Scope.Strategy)}. + * + * @throws IllegalStateException if no strategy has been set + */ + Scope.Strategy getScopeStrategy(); + + /** + * Gets the field, method or constructor which is being injected. Returns + * {@code null} if the object currently being constructed is pre-loaded as + * a singleton or requested from {@link Container#getInstance(Class)}. + */ + Member getMember(); + + /** + * Gets the type of the field or parameter which is being injected. + */ + Class<?> getType(); + + /** + * Gets the name of the injection specified by {@link Inject#value()}. + */ + String getName(); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java b/core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java new file mode 100644 index 0000000..f92896d --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/DependencyException.java @@ -0,0 +1,37 @@ +/** + * 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; + +/** + * Thrown when a dependency is misconfigured. + * + * @author [email protected] (Bob Lee) + */ +public class DependencyException extends RuntimeException { + + public DependencyException(String message) { + super(message); + } + + public DependencyException(String message, Throwable cause) { + super(message, cause); + } + + public DependencyException(Throwable cause) { + super(cause); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java b/core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java new file mode 100644 index 0000000..0c054e8 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/ExternalContext.java @@ -0,0 +1,73 @@ +/** + * Copyright (C) 2006 Google Inc. + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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 java.lang.reflect.Member; +import java.util.LinkedHashMap; + +/** + * An immutable snapshot of the current context which is safe to + * expose to client code. + * + * @author [email protected] (Bob Lee) + */ +class ExternalContext<T> implements Context { + + final Member member; + final Key<T> key; + final ContainerImpl container; + + public ExternalContext(Member member, Key<T> key, ContainerImpl container) { + this.member = member; + this.key = key; + this.container = container; + } + + public Class<T> getType() { + return key.getType(); + } + + public Scope.Strategy getScopeStrategy() { + return (Scope.Strategy) container.localScopeStrategy.get(); + } + + public Container getContainer() { + return container; + } + + public Member getMember() { + return member; + } + + public String getName() { + return key.getName(); + } + + @Override + public String toString() { + return "Context" + new LinkedHashMap<String, Object>() {{ + put("member", member); + put("type", getType()); + put("name", getName()); + put("container", container); + }}.toString(); + } + + static <T> ExternalContext<T> newInstance(Member member, Key<T> key, ContainerImpl container) { + return new ExternalContext<T>(member, key, container); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Factory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Factory.java b/core/src/main/java/com/opensymphony/xwork2/inject/Factory.java new file mode 100644 index 0000000..1117506 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Factory.java @@ -0,0 +1,34 @@ +/** + * 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; + +/** + * A custom factory. Creates objects which will be injected. + * + * @author [email protected] (Bob Lee) + */ +public interface Factory<T> { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @return instance to be injected + * @throws Exception if unable to create object + */ + T create(Context context) throws Exception; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Inject.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Inject.java b/core/src/main/java/com/opensymphony/xwork2/inject/Inject.java new file mode 100644 index 0000000..610d2bb --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Inject.java @@ -0,0 +1,46 @@ +/** + * 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 static com.opensymphony.xwork2.inject.Container.DEFAULT_NAME; + +import static java.lang.annotation.ElementType.*; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * <p>Annotates members and parameters which should have their value[s] + * injected. + * + * @author [email protected] (Bob Lee) + */ +@Target({METHOD, CONSTRUCTOR, FIELD, PARAMETER}) +@Retention(RUNTIME) +public @interface Inject { + + /** + * Dependency name. Defaults to {@link Container#DEFAULT_NAME}. + */ + String value() default DEFAULT_NAME; + + /** + * Whether or not injection is required. Applicable only to methods and + * fields (not constructors or parameters). + */ + boolean required() default true; +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java b/core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java new file mode 100644 index 0000000..7014480 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/InternalContext.java @@ -0,0 +1,77 @@ +/** + * 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 java.util.HashMap; +import java.util.Map; + +/** + * Internal context. Used to coordinate injections and support circular + * dependencies. + * + * @author [email protected] (Bob Lee) + */ +class InternalContext { + + final ContainerImpl container; + final Map<Object, ConstructionContext<?>> constructionContexts = new HashMap<Object, ConstructionContext<?>>(); + Scope.Strategy scopeStrategy; + ExternalContext<?> externalContext; + + InternalContext(ContainerImpl container) { + this.container = container; + } + + public Container getContainer() { + return container; + } + + ContainerImpl getContainerImpl() { + return container; + } + + Scope.Strategy getScopeStrategy() { + if (scopeStrategy == null) { + scopeStrategy = (Scope.Strategy) container.localScopeStrategy.get(); + + if (scopeStrategy == null) { + throw new IllegalStateException("Scope strategy not set. Please call Container.setScopeStrategy()."); + } + } + + return scopeStrategy; + } + + @SuppressWarnings("unchecked") + <T> ConstructionContext<T> getConstructionContext(Object key) { + ConstructionContext<T> constructionContext = (ConstructionContext<T>) constructionContexts.get(key); + if (constructionContext == null) { + constructionContext = new ConstructionContext<T>(); + constructionContexts.put(key, constructionContext); + } + return constructionContext; + } + + @SuppressWarnings("unchecked") + <T> ExternalContext<T> getExternalContext() { + return (ExternalContext<T>) externalContext; + } + + void setExternalContext(ExternalContext<?> externalContext) { + this.externalContext = externalContext; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java b/core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java new file mode 100644 index 0000000..49fcf27 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/InternalFactory.java @@ -0,0 +1,35 @@ +/** + * 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 java.io.Serializable; + +/** + * Creates objects which will be injected. + * + * @author [email protected] (Bob Lee) + */ +interface InternalFactory<T> extends Serializable { + + /** + * Creates an object to be injected. + * + * @param context of this injection + * @return instance to be injected + */ + T create(InternalContext context); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Key.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Key.java b/core/src/main/java/com/opensymphony/xwork2/inject/Key.java new file mode 100644 index 0000000..ad9e0ba --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Key.java @@ -0,0 +1,77 @@ +/** + * Copyright (C) 2006 Google Inc. + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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; + +/** + * Dependency mapping key. Uniquely identified by the required type and name. + * + * @author [email protected] (Bob Lee) + */ +class Key<T> { + + final Class<T> type; + final String name; + final int hashCode; + + private Key(Class<T> type, String name) { + if (type == null) { + throw new NullPointerException("Type is null."); + } + if (name == null) { + throw new NullPointerException("Name is null."); + } + + this.type = type; + this.name = name; + + hashCode = type.hashCode() * 31 + name.hashCode(); + } + + Class<T> getType() { + return type; + } + + String getName() { + return name; + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Key)) { + return false; + } + if (o == this) { + return true; + } + Key other = (Key) o; + return name.equals(other.name) && type.equals(other.type); + } + + @Override + public String toString() { + return "[type=" + type.getName() + ", name='" + name + "']"; + } + + static <T> Key<T> newInstance(Class<T> type, String name) { + return new Key<T>(type, name); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Scope.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Scope.java b/core/src/main/java/com/opensymphony/xwork2/inject/Scope.java new file mode 100644 index 0000000..62443f7 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Scope.java @@ -0,0 +1,214 @@ +/** + * Copyright (C) 2006 Google Inc. + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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 java.util.concurrent.Callable; + +/** + * Scope of an injected objects. + * + * @author crazybob + */ +public enum Scope { + + /** + * One instance per injection. + */ + DEFAULT { + @Override + <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name, + InternalFactory<? extends T> factory) { + return factory; + } + }, + + /** + * One instance per container. + */ + SINGLETON { + @Override + <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name, final InternalFactory<? extends T> factory) { + return new InternalFactory<T>() { + T instance; + + public T create(InternalContext context) { + synchronized (context.getContainer()) { + if (instance == null) { + instance = factory.create(context); + } + return instance; + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per thread. + * <p/> + * <p><b>Note:</b> if a thread local object strongly references its {@link + * Container}, neither the {@code Container} nor the object will be + * eligible for garbage collection, i.e. memory leak. + */ + THREAD { + @Override + <T> InternalFactory<? extends T> scopeFactory(Class<T> type, String name, final InternalFactory<? extends T> factory) { + return new InternalFactory<T>() { + final ThreadLocal<T> threadLocal = new ThreadLocal<T>(); + + public T create(final InternalContext context) { + T t = threadLocal.get(); + if (t == null) { + t = factory.create(context); + threadLocal.set(t); + } + return t; + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per request. + */ + REQUEST { + @Override + <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name, final InternalFactory<? extends T> factory) { + return new InternalFactory<T>() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInRequest( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per session. + */ + SESSION { + @Override + <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name, final InternalFactory<? extends T> factory) { + return new InternalFactory<T>() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInSession( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }, + + /** + * One instance per wizard. + */ + WIZARD { + @Override + <T> InternalFactory<? extends T> scopeFactory(final Class<T> type, final String name, final InternalFactory<? extends T> factory) { + return new InternalFactory<T>() { + public T create(InternalContext context) { + Strategy strategy = context.getScopeStrategy(); + try { + return strategy.findInWizard( + type, name, toCallable(context, factory)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String toString() { + return factory.toString(); + } + }; + } + }; + + <T> Callable<? extends T> toCallable(final InternalContext context, + final InternalFactory<? extends T> factory) { + return new Callable<T>() { + public T call() throws Exception { + return factory.create(context); + } + }; + } + + /** + * Wraps factory with scoping logic. + */ + abstract <T> InternalFactory<? extends T> scopeFactory( + Class<T> type, String name, InternalFactory<? extends T> factory); + + /** + * Pluggable scoping strategy. Enables users to provide custom + * implementations of request, session, and wizard scopes. Implement and + * pass to {@link + * Container#setScopeStrategy(com.opensymphony.xwork2.inject.Scope.Strategy)}. + */ + public interface Strategy { + + /** + * Finds an object for the given type and name in the request scope. + * Creates a new object if necessary using the given factory. + */ + <T> T findInRequest(Class<T> type, String name, + Callable<? extends T> factory) throws Exception; + + /** + * Finds an object for the given type and name in the session scope. + * Creates a new object if necessary using the given factory. + */ + <T> T findInSession(Class<T> type, String name, + Callable<? extends T> factory) throws Exception; + + /** + * Finds an object for the given type and name in the wizard scope. + * Creates a new object if necessary using the given factory. + */ + <T> T findInWizard(Class<T> type, String name, + Callable<? extends T> factory) throws Exception; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java b/core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java new file mode 100644 index 0000000..31a9447 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/Scoped.java @@ -0,0 +1,37 @@ +/** + * 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 java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import java.lang.annotation.Target; + +/** + * Annotates a scoped implementation class. + * + * @author crazybob + */ +@Target(ElementType.TYPE) +@Retention(RUNTIME) +public @interface Scoped { + + /** + * Scope. + */ + Scope value(); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/package-info.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/package-info.java b/core/src/main/java/com/opensymphony/xwork2/inject/package-info.java new file mode 100644 index 0000000..6e26e24 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/package-info.java @@ -0,0 +1,30 @@ +/** + * 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. + */ + +/** + * <i>Guice</i> (pronounced "juice"). A lightweight dependency injection + * container. Features include: + * + * <ul> + * <li>constructor, method, and field injection</li> + * <li>static method and field injection</li> + * <li>circular reference support (including constructors if you depend upon + * interfaces)</li> + * <li>high performance</li> + * <li>externalize what needs to be and no more</li> + * </ul> + */ +package com.opensymphony.xwork2.inject; http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java new file mode 100644 index 0000000..869b96b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizablePhantomReference.java @@ -0,0 +1,35 @@ +/** + * 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.util; + +import java.lang.ref.PhantomReference; + +/** + * Phantom reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a + * background thread invokes after the garbage collector reclaims the + * referent. This is a simpler alternative to using a {@link + * java.lang.ref.ReferenceQueue}. + * + * @author [email protected] (Bob Lee) + */ +public abstract class FinalizablePhantomReference<T> + extends PhantomReference<T> implements FinalizableReference { + + protected FinalizablePhantomReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java new file mode 100644 index 0000000..16653e2 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReference.java @@ -0,0 +1,32 @@ +/** + * 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.util; + +/** + * Package-private interface implemented by references that have code to run + * after garbage collection of their referents. + * + * @author [email protected] (Bob Lee) + */ +interface FinalizableReference { + + /** + * Invoked on a background thread after the referent has been garbage + * collected. + */ + void finalizeReferent(); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java new file mode 100644 index 0000000..72121ef --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableReferenceQueue.java @@ -0,0 +1,77 @@ +/** + * 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.util; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Starts a background thread that cleans up after reclaimed referents. + * + * @author Bob Lee ([email protected]) + */ +class FinalizableReferenceQueue extends ReferenceQueue<Object> { + + private static final Logger logger = + Logger.getLogger(FinalizableReferenceQueue.class.getName()); + + private FinalizableReferenceQueue() {} + + void cleanUp(Reference reference) { + try { + ((FinalizableReference) reference).finalizeReferent(); + } catch (Throwable t) { + deliverBadNews(t); + } + } + + void deliverBadNews(Throwable t) { + logger.log(Level.SEVERE, "Error cleaning up after reference.", t); + } + + void start() { + Thread thread = new Thread("FinalizableReferenceQueue") { + @Override + public void run() { + while (true) { + try { + cleanUp(remove()); + } catch (InterruptedException e) { /* ignore */ } + } + } + }; + thread.setDaemon(true); + thread.start(); + } + + static ReferenceQueue<Object> instance = createAndStart(); + + static FinalizableReferenceQueue createAndStart() { + FinalizableReferenceQueue queue = new FinalizableReferenceQueue(); + queue.start(); + return queue; + } + + /** + * Gets instance. + */ + public static ReferenceQueue<Object> getInstance() { + return instance; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java new file mode 100644 index 0000000..eeb1c20 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableSoftReference.java @@ -0,0 +1,34 @@ +/** + * 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.util; + +import java.lang.ref.SoftReference; + +/** + * Soft reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author [email protected] (Bob Lee) + */ +public abstract class FinalizableSoftReference<T> extends SoftReference<T> + implements FinalizableReference { + + protected FinalizableSoftReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java new file mode 100644 index 0000000..7c3a67f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/FinalizableWeakReference.java @@ -0,0 +1,34 @@ +/** + * 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.util; + +import java.lang.ref.WeakReference; + +/** + * Weak reference with a {@link com.opensymphony.xwork2.inject.util.FinalizableReference#finalizeReferent() finalizeReferent()} method which a background + * thread invokes after the garbage collector reclaims the referent. This is a + * simpler alternative to using a {@link java.lang.ref.ReferenceQueue}. + * + * @author [email protected] (Bob Lee) + */ +public abstract class FinalizableWeakReference<T> extends WeakReference<T> + implements FinalizableReference { + + protected FinalizableWeakReference(T referent) { + super(referent, FinalizableReferenceQueue.getInstance()); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java new file mode 100644 index 0000000..fd21237 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/Function.java @@ -0,0 +1,44 @@ +/** + * 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.util; + +/** + * A Function provides a transformation on an object and returns the resulting + * object. For example, a {@code StringToIntegerFunction} may implement + * <code>Function<String,Integer></code> and transform integers in String + * format to Integer format. + * + * <p>The transformation on the source object does not necessarily result in + * an object of a different type. For example, a + * {@code FarenheitToCelciusFunction} may implement + * <code>Function<Float,Float></code>. + * + * <p>Implementors of Function which may cause side effects upon evaluation are + * strongly encouraged to state this fact clearly in their API documentation. + */ +public interface Function<F,T> { + + /** + * Applies the function to an object of type {@code F}, resulting in an object + * of type {@code T}. Note that types {@code F} and {@code T} may or may not + * be the same. + * + * @param from The source object. + * @return The resulting object. + */ + T apply(F from); +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java new file mode 100644 index 0000000..9ebf545 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/inject/util/ReferenceCache.java @@ -0,0 +1,177 @@ +/** + * Copyright (C) 2006 Google Inc. + * <p/> + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.util; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.util.concurrent.*; + +import static com.opensymphony.xwork2.inject.util.ReferenceType.STRONG; + +/** + * Extends {@link ReferenceMap} to support lazy loading values by overriding + * {@link #create(Object)}. + * + * @author [email protected] (Bob Lee) + */ +public abstract class ReferenceCache<K, V> extends ReferenceMap<K, V> { + + private static final long serialVersionUID = 0; + + transient ConcurrentMap<Object, Future<V>> futures = new ConcurrentHashMap<>(); + transient ThreadLocal<Future<V>> localFuture = new ThreadLocal<>(); + + public ReferenceCache(ReferenceType keyReferenceType, ReferenceType valueReferenceType) { + super(keyReferenceType, valueReferenceType); + } + + /** + * Equivalent to {@code new ReferenceCache(STRONG, STRONG)}. + */ + public ReferenceCache() { + super(STRONG, STRONG); + } + + /** + * Override to lazy load values. Use as an alternative to {@link + * #put(Object, Object)}. Invoked by getter if value isn't already cached. + * Must not return {@code null}. This method will not be called again until + * the garbage collector reclaims the returned value. + */ + protected abstract V create(K key); + + V internalCreate(K key) { + try { + FutureTask<V> futureTask = new FutureTask<>(new CallableCreate(key)); + + // use a reference so we get the same equality semantics. + Object keyReference = referenceKey(key); + Future<V> future = futures.putIfAbsent(keyReference, futureTask); + if (future == null) { + // winning thread. + try { + if (localFuture.get() != null) { + throw new IllegalStateException("Nested creations within the same cache are not allowed."); + } + localFuture.set(futureTask); + futureTask.run(); + V value = futureTask.get(); + putStrategy().execute(this, keyReference, referenceValue(keyReference, value)); + return value; + } finally { + localFuture.remove(); + futures.remove(keyReference); + } + } else { + // wait for winning thread. + return future.get(); + } + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) { + throw (RuntimeException) cause; + } else if (cause instanceof Error) { + throw (Error) cause; + } + throw new RuntimeException(cause); + } + } + + /** + * {@inheritDoc} + * <p/> + * If this map does not contain an entry for the given key and {@link + * #create(Object)} has been overridden, this method will create a new + * value, put it in the map, and return it. + * + * @throws NullPointerException if {@link #create(Object)} returns null. + * @throws java.util.concurrent.CancellationException if the creation is + * cancelled. See {@link #cancel()}. + */ + @SuppressWarnings("unchecked") + @Override + public V get(final Object key) { + V value = super.get(key); + return (value == null) ? internalCreate((K) key) : value; + } + + /** + * Cancels the current {@link #create(Object)}. Throws {@link + * java.util.concurrent.CancellationException} to all clients currently + * blocked on {@link #get(Object)}. + */ + protected void cancel() { + Future<V> future = localFuture.get(); + if (future == null) { + throw new IllegalStateException("Not in create()."); + } + future.cancel(false); + } + + class CallableCreate implements Callable<V> { + + K key; + + public CallableCreate(K key) { + this.key = key; + } + + public V call() { + // try one more time (a previous future could have come and gone.) + V value = internalGet(key); + if (value != null) { + return value; + } + + // create value. + value = create(key); + if (value == null) { + throw new NullPointerException("create(K) returned null for: " + key); + } + return value; + } + } + + /** + * Returns a {@code ReferenceCache} delegating to the specified {@code + * function}. The specified function must not return {@code null}. + */ + public static <K, V> ReferenceCache<K, V> of( + ReferenceType keyReferenceType, + ReferenceType valueReferenceType, + final Function<? super K, ? extends V> function) { + ensureNotNull(function); + return new ReferenceCache<K, V>(keyReferenceType, valueReferenceType) { + @Override + protected V create(K key) { + return function.apply(key); + } + + private static final long serialVersionUID = 0; + }; + } + + private void readObject(ObjectInputStream in) throws IOException, + ClassNotFoundException { + in.defaultReadObject(); + this.futures = new ConcurrentHashMap<>(); + this.localFuture = new ThreadLocal<>(); + } + +}
