View Javadoc
1   /**
2    * This file Copyright (c) 2017-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.framework.ioc;
35  
36  import info.magnolia.i18nsystem.util.AnnotationUtils;
37  import info.magnolia.objectfactory.Components;
38  import info.magnolia.objectfactory.annotation.LazySingleton;
39  
40  import java.lang.annotation.Annotation;
41  import java.util.ArrayList;
42  import java.util.HashMap;
43  import java.util.List;
44  import java.util.Map;
45  
46  import javax.inject.Singleton;
47  
48  import com.google.inject.AbstractModule;
49  import com.google.inject.Binding;
50  import com.google.inject.Module;
51  import com.google.inject.spi.Element;
52  import com.google.inject.spi.Elements;
53  
54  /**
55   * Detects the bindings which involve types annotated with {@link Singleton} and
56   * {@link info.magnolia.objectfactory.annotation.LazySingleton} and will suppress their impact by driving the bindings
57   * to more narrow UI-related scopes.
58   * <p>
59   * E.g. we have a class Foo implemented by FooImpl and FooImpl is marked with @Singleton
60   * annotation. With old ways in Magnolia @Singleton might mean that Foo is an app/sub-app/...
61   * component depending on how foo interface is configured in module descriptor. However,
62   * in the realm where there's just one Injector for the whole UI singleton means singleton
63   * for the whole web-app. Since the singleton-ness is enforced with annotation it is not easy
64   * to suppress it. However, there is a way.
65   * </p>
66   * <p>
67   * Guice allows sort of chaining the bindings, meaning that if Foo is bound to FooImpl and FooImpl
68   * in turn is bound to FooOtherImpl, the FooOtherImpl will be used when injecting Foo.
69   * Similar trick can be done with the scopes. Scope specified with module API overrides the
70   * scope coming from annotation. Using this capability we will drop the misleading singleton
71   * scope by explicitly mapping impl types (and providers) to the according UI scopes.
72   * </p>
73   */
74  class EliminateRedundantSingletonAnnotations extends AbstractModule {
75  
76      private final Module sourceModule;
77  
78      EliminateRedundantSingletonAnnotations(Module module) {
79          this.sourceModule = module;
80      }
81  
82      @Override
83      protected void configure() {
84  
85          // First - collect all the linked bindings (interface <-> impl) and provider bindings
86          // which have the implementations marked with @Singleton
87          final Map<Class, Binding> faultySingletons = new HashMap<>();
88          final List<Element> sourceElements = Elements.getElements(sourceModule);
89  
90          // Store elements (bindings) that will have to be re-applied as is
91          final List<Element> elementsToWrite = new ArrayList<>(sourceElements);
92  
93          // Inspect linked key bindings and provider key bindings, search for the @Singleton
94          // @LazySingleton annotated types. Aggregate such bindings
95          GuiceSpi.getBindings(sourceElements).forEach(binding ->
96                  GuiceSpi.<Class>inspect(binding)
97                  .onLinkedBinding(linkedKeyBinding -> linkedKeyBinding.getLinkedKey().getTypeLiteral().getRawType())
98                  .onProviderKeyBinding(providerKeyBinding -> providerKeyBinding.getProviderKey().getTypeLiteral().getRawType())
99                  .visit()
100                 .filter(this::isAnnotatedSingleton)
101                 .ifPresent(type -> faultySingletons.put(type, binding)));
102         
103         // Suppress the @Singleton and @LazySingleton effect
104         faultySingletons.forEach((type, binding) -> {
105             final UiContextAnnotation uiContextAnnotation = UiAnnotations.cast(binding.getKey().getAnnotation());
106 
107             final Class<? extends Annotation> relatedScope = uiContextAnnotation.getRelatedScopeAnnotation(isEagerSingleton(type));
108             GuiceSpi.<Void>inspect(binding)
109 
110                     .consumeProviderKeyBinding(providerKeyBinding -> {
111                         // For singleton provider bound types: explicitly bind the provider to the
112                         // appropriate UI scope
113                         bind(providerKeyBinding.getProviderKey()).in(relatedScope);
114                         // Then bind the key to such provider in the same scope
115                         bind(providerKeyBinding.getKey()).toProvider(providerKeyBinding.getProviderKey()).in(relatedScope);
116                     })
117 
118                     .consumeLinkedBinding(linkedKeyBinding -> {
119                         // For type <-> impl bindings, use #newInstance-flavored provider to
120                         // mascarade the singleton scope
121                         // TODO - probably ConstructorBinding would be more appropriate here?
122                         bind(linkedKeyBinding.getKey())
123                                 .toProvider(() -> Components.newInstance(linkedKeyBinding.getLinkedKey().getTypeLiteral().getRawType()))
124                                 .in(relatedScope);
125                     })
126 
127                     .visit();
128         });
129 
130         elementsToWrite.removeAll(faultySingletons.values());
131         elementsToWrite.forEach(element -> element.applyTo(binder()));
132     }
133 
134     private boolean isEagerSingleton(Class type) {
135         return AnnotationUtils.hasAnnotationOn(type, com.google.inject.Singleton.class) ||
136                 AnnotationUtils.hasAnnotationOn(type, Singleton.class);
137     }
138 
139     private boolean isAnnotatedSingleton(Class targetType) {
140         return AnnotationUtils.hasAnnotationOn(targetType, com.google.inject.Singleton.class) ||
141                AnnotationUtils.hasAnnotationOn(targetType, Singleton.class) ||
142                AnnotationUtils.hasAnnotationOn(targetType, LazySingleton.class);
143     }
144 }