View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht;
21  
22  import com.google.common.base.Function;
23  import com.google.common.base.Throwables;
24  import com.google.common.collect.ImmutableSet;
25  import com.google.common.collect.ImmutableSetMultimap;
26  import com.google.common.collect.Maps;
27  import com.google.common.collect.SetMultimap;
28  import org.apache.commons.lang3.reflect.MethodUtils;
29  import org.grouplens.grapht.graph.DAGEdge;
30  import org.grouplens.grapht.graph.DAGNode;
31  import org.grouplens.grapht.reflect.Desire;
32  import org.slf4j.Logger;
33  import org.slf4j.LoggerFactory;
34  
35  import javax.annotation.Nullable;
36  import javax.annotation.PreDestroy;
37  import java.lang.reflect.Method;
38  import java.util.*;
39  
40  /**
41   * Container for dependency-injected components.  A container is the scope of memoization, so
42   * components with a cache policy of {@link CachePolicy#MEMOIZE} will share an instance so long
43   * as they are instantiated by the same instantiator.
44   *
45   * @since 0.9
46   * @author <a href="http://www.grouplens.org">GroupLens Research</a>
47   */
48  public class InjectionContainer {
49      private static final Logger logger = LoggerFactory.getLogger(InjectionContainer.class);
50  
51      private final CachePolicy defaultCachePolicy;
52      private final Map<DAGNode<Component, Dependency>, Instantiator> providerCache;
53      private final LifecycleManager manager;
54  
55      /**
56       * Create a new instantiator with a default policy of {@code MEMOIZE}.
57       * @return The instantiator.
58       */
59      public static InjectionContainer create() {
60          return create(CachePolicy.MEMOIZE);
61      }
62  
63      /**
64       * Create a new instantiator without a lifecycle manager.
65       * @param dft The default cache policy.
66       * @return The instantiator.
67       */
68      public static InjectionContainer create(CachePolicy dft) {
69          return new InjectionContainer(dft, null);
70      }
71  
72      /**
73       * Create a new instantiator.
74       * @param dft The default cache policy.
75       * @param mgr The lifecycle manager.
76       * @return The instantiator.
77       */
78      public static InjectionContainer create(CachePolicy dft, LifecycleManager mgr) {
79          return new InjectionContainer(dft, mgr);
80      }
81  
82      private InjectionContainer(CachePolicy dft, LifecycleManager mgr) {
83          defaultCachePolicy = dft;
84          providerCache = new WeakHashMap<DAGNode<Component, Dependency>, Instantiator>();
85          manager = mgr;
86      }
87  
88      /**
89       * Get a provider that, when invoked, will return an instance of the component represented
90       * by a graph.
91       *
92       *
93       * @param node The graph.
94       * @return A provider to instantiate {@code graph}.
95       * @see #makeInstantiator(DAGNode, SetMultimap)
96       */
97      public Instantiator makeInstantiator(DAGNode<Component, Dependency> node) {
98          return makeInstantiator(node, ImmutableSetMultimap.<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>>of());
99      }
100 
101     /**
102      * Get a provider that, when invoked, will return an instance of the component represented
103      * by a graph with back edges.  The provider will implement the cache policy, so cached nodes
104      * will return a memoized provider.
105      *
106      * @param node The graph.
107      * @param backEdges A multimap of back edges for cyclic dependencies.
108      * @return A provider to instantiate {@code graph}.
109      */
110     public Instantiator makeInstantiator(DAGNode<Component, Dependency> node,
111                                          SetMultimap<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>> backEdges) {
112         Instantiator cached;
113         synchronized (providerCache) {
114             cached = providerCache.get(node);
115         }
116         if (cached == null) {
117             logger.debug("Node has not been memoized, instantiating: {}", node.getLabel());
118 
119             Map<Desire, Instantiator> depMap = makeDependencyMap(node, backEdges);
120 
121             Instantiator raw = node.getLabel().getSatisfaction().makeInstantiator(depMap, manager);
122 
123             CachePolicy policy = node.getLabel().getCachePolicy();
124             if (policy.equals(CachePolicy.NO_PREFERENCE)) {
125                 policy = defaultCachePolicy;
126             }
127             if (policy.equals(CachePolicy.MEMOIZE)) {
128                 // enforce memoization on providers for MEMOIZE policy
129                 cached = Instantiators.memoize(raw);
130             } else {
131                 // Satisfaction.makeInstantiator() returns providers that are expected
132                 // to create new instances with each invocation
133                 assert policy.equals(CachePolicy.NEW_INSTANCE);
134                 cached = raw;
135             }
136             synchronized (providerCache) {
137                 if (!providerCache.containsKey(node)) {
138                     providerCache.put(node, cached);
139                 } else {
140                     logger.debug("two threads built instantiator for {}, discarding 2nd build", node);
141                     cached = providerCache.get(node);
142                 }
143             }
144         }
145         return cached;
146     }
147 
148     private Map<Desire, Instantiator> makeDependencyMap(DAGNode<Component, Dependency> node, SetMultimap<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>> backEdges) {
149         Set<DAGEdge<Component,Dependency>> edges = node.getOutgoingEdges();
150         if (backEdges.containsKey(node)) {
151             ImmutableSet.Builder<DAGEdge<Component,Dependency>> bld = ImmutableSet.builder();
152             edges = bld.addAll(edges)
153                        .addAll(backEdges.get(node))
154                        .build();
155         }
156 
157         ImmutableSet.Builder<Desire> desires = ImmutableSet.builder();
158         for (DAGEdge<Component,Dependency> edge: edges) {
159             desires.add(edge.getLabel().getInitialDesire());
160         }
161         return Maps.asMap(desires.build(), new DepLookup(edges, backEdges));
162     }
163 
164     /**
165      * Get the lifecycle manager for this container.
166      * @return The lifecycle manager for the container.
167      */
168     @Nullable
169     public LifecycleManager getLifecycleManager() {
170         return manager;
171     }
172 
173     /**
174      * Function to look up a desire in a set of dependency edges.
175      */
176     private class DepLookup implements Function<Desire,Instantiator> {
177         private final Set<DAGEdge<Component, Dependency>> edges;
178         private final SetMultimap<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>> backEdges;
179 
180         /**
181          * Construct a depenency lookup funciton.
182          * @param edges The set of edges to consult.
183          * @param backEdges The back edge map (to pass to {@link #makeInstantiator(DAGNode,SetMultimap)}).
184          */
185         public DepLookup(Set<DAGEdge<Component,Dependency>> edges,
186                          SetMultimap<DAGNode<Component, Dependency>, DAGEdge<Component, Dependency>> backEdges) {
187             this.edges = edges;
188             this.backEdges = backEdges;
189         }
190 
191         @Nullable
192         @Override
193         public Instantiator apply(@Nullable Desire input) {
194             for (DAGEdge<Component,Dependency> edge: edges) {
195                 if (edge.getLabel().getInitialDesire().equals(input)) {
196                     return makeInstantiator(edge.getTail(), backEdges);
197                 }
198             }
199             return null;
200         }
201     }
202 }