View Javadoc
1   /**
2    * This file Copyright (c) 2016-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 static java.util.stream.Collectors.toMap;
37  
38  import info.magnolia.config.NamedDefinition;
39  import info.magnolia.objectfactory.CandidateParameterResolver;
40  import info.magnolia.objectfactory.ComponentProvider;
41  import info.magnolia.objectfactory.ParameterResolver;
42  import info.magnolia.objectfactory.guice.GuiceComponentProvider;
43  import info.magnolia.ui.datasource.WithDatasource;
44  import info.magnolia.ui.framework.datasource.definition.DatasourceDefinition;
45  import info.magnolia.util.Util;
46  
47  import java.io.Serializable;
48  import java.util.Arrays;
49  import java.util.HashMap;
50  import java.util.Map;
51  import java.util.Optional;
52  import java.util.function.Function;
53  import java.util.function.Supplier;
54  import java.util.stream.Stream;
55  
56  import com.google.inject.Inject;
57  import com.google.inject.Key;
58  import com.vaadin.server.VaadinServlet;
59  
60  /**
61   * Lightweight {@link ComponentProvider} implementation for UI components which conducts provisioning
62   * by delegating it to the only UI Guice instance via a {@link GuiceComponentProvider} associated with it.
63   * <p>
64   * The only significant function of such {@link ComponentProvider component provider} is that it
65   * is bound to a certain UI context via {@link UiContextReference} which is pushed into {@link CurrentUiContextReference}
66   * before any Guice invocation. By doing this Guice knows (when uncertain) what flavour of components to inject.
67   * </p>
68   *
69   * @see UiAnnotations
70   * @see UiContextApplyingProvider
71   */
72  public class UiContextBoundComponentProvider implements ComponentProvider, Serializable {
73  
74      private final transient GuiceComponentProvider parentComponentProvider;
75  
76      private final CurrentUiContextReference currentUiContextReference;
77  
78      private final UiContextReference uiContextReference;
79  
80      @Inject
81      public UiContextBoundComponentProvider(UiContextReference uiContextReference) {
82          this(uiContextReference, (GuiceComponentProvider) VaadinServlet.getCurrent().getServletContext().getAttribute("componentProvider"), CurrentUiContextReference.get());
83      }
84  
85      public UiContextBoundComponentProvider(UiContextReference uiContextReference, GuiceComponentProvider parentComponentProvider) {
86          this(uiContextReference, parentComponentProvider, CurrentUiContextReference.get());
87      }
88  
89      public UiContextBoundComponentProvider(UiContextReference uiContextReference, GuiceComponentProvider parentComponentProvider, CurrentUiContextReference currentUiContextReference) {
90          this.uiContextReference = uiContextReference;
91          this.parentComponentProvider = parentComponentProvider;
92          this.currentUiContextReference = currentUiContextReference;
93      }
94  
95      public UiContextReference getUiContextReference() {
96          return this.uiContextReference;
97      }
98  
99      @Override
100     public <T> Class<? extends T> getImplementation(Class<T> type) {
101         return getParent().getImplementation(getClosestMappedType(type));
102     }
103 
104     private <T> Key<T> getClosestMappedType(Class<T> type) {
105         return getReachableUiContextReferences()
106                 .map(key -> Key.get(type, key.getAnnotation()))
107                 // first check if there is a matching annotated type mapping
108                 .filter(key -> getParent().getTypeMappings().containsKey(key))
109                 .findFirst()
110                 // otherwise just fetch the un-annotated type mapping
111                 .orElseGet(() -> Key.get(type));
112     }
113 
114     protected Stream<UiContextReference> getReachableUiContextReferences() {
115         return this.currentUiContextReference.getAvailableContextReferences().stream();
116     }
117 
118     @Override
119     public <T> T getSingleton(Class<T> type) {
120         return getParent().getSingleton(type);
121     }
122 
123     @Override
124     public <T> T getComponent(Class<T> type) {
125         return provideInCurrentScope(() -> getParent().getComponent(type));
126     }
127 
128     @Override
129     public <T> T newInstance(Class<T> type, Object... parameters) {
130         return newInstanceWithParameterResolvers(type, new CandidateParameterResolver(parameters));
131     }
132 
133     @Override
134     public <T> T newInstanceWithParameterResolvers(Class<T> type, ParameterResolver... parameters) {
135         return provideInCurrentScope(() -> getParent().newInstanceWithParameterResolvers(getClosestMappedType(type), parameters));
136     }
137 
138     @Override
139     public GuiceComponentProvider getParent() {
140         return this.parentComponentProvider;
141     }
142 
143     public UiContextBoundComponentProvider inChildContext(String name, Object definition) {
144         Builder builder = builder(name, uiContextReference).withInstances(definition);
145         if (definition instanceof WithDatasource) {
146             Map<Class, Object> instances = new HashMap<>();
147             Optional.ofNullable(((WithDatasource) definition).getDatasource())
148                     .ifPresent(datasourceDefinition -> instances.put(DatasourceDefinition.class, datasourceDefinition));
149             builder = builder.withInstances(instances);
150         }
151 
152         return builder.build();
153     }
154 
155     public UiContextBoundComponentProvider inChildContext(NamedDefinition definition) {
156         return inChildContext(definition.getName(), definition);
157     }
158 
159     private <T> T provideInCurrentScope(Supplier<T> provider) {
160         final UiContextReference previousUiContextReference = currentUiContextReference.getUiContextReference();
161         currentUiContextReference.setUiContextReference(this.uiContextReference);
162         try {
163             return provider.get();
164         } finally {
165             currentUiContextReference.setUiContextReference(previousUiContextReference);
166         }
167     }
168 
169     public static Builder builder(String name, UiContextReference parentReference) {
170         String id = name;
171         UiContextReference viewContextReference;
172         do {
173             viewContextReference = UiContextReference.ofView(id, parentReference);
174             id = Util.createUniqueName(id);
175         } while (SessionStore.access().getBeanStore(viewContextReference) != null);
176 
177         return new Builder().withUiContextReference(viewContextReference);
178     }
179 
180     public static class Builder {
181 
182         private Builder() {
183         }
184 
185         private UiContextReference uiContextReference;
186         private Map<Class, Object> instances = new HashMap<>();
187 
188         public Builder withUiContextReference(UiContextReference uiContextReference) {
189             this.uiContextReference = uiContextReference;
190             return this;
191         }
192 
193         public Builder withInstances(Object... instances) {
194             this.instances.putAll(Arrays.stream(instances)
195                     .collect(toMap(Object::getClass, Function.identity(), (first, second) -> second)));
196             return this;
197         }
198 
199         public Builder withInstances(Map<Class, Object> instances) {
200             this.instances.putAll(instances);
201             return this;
202         }
203 
204         public UiContextBoundComponentProvider build() {
205             BeanStore beanStore = SessionStore.access().createBeanStore(uiContextReference);
206             instances.forEach(beanStore::put);
207 
208             ViewComponentProviderml#ViewComponentProvider">ViewComponentProvider viewComponentProvider = new ViewComponentProvider(uiContextReference);
209             beanStore.put(UiContextBoundComponentProvider.class, viewComponentProvider);
210             beanStore.put(ComponentProvider.class, viewComponentProvider);
211 
212             return viewComponentProvider;
213         }
214     }
215 }