View Javadoc
1   /**
2    * This file Copyright (c) 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.i18nsystem.util.AnnotationUtils;
40  import info.magnolia.objectfactory.CandidateParameterResolver;
41  import info.magnolia.objectfactory.ComponentProvider;
42  import info.magnolia.objectfactory.ParameterResolver;
43  import info.magnolia.objectfactory.guice.GuiceComponentProvider;
44  import info.magnolia.ui.UIComponent;
45  import info.magnolia.ui.api.ioc.ViewScoped;
46  import info.magnolia.ui.datasource.DatasourceDefinition;
47  import info.magnolia.ui.datasource.WithDatasource;
48  import info.magnolia.util.Util;
49  
50  import java.io.Serializable;
51  import java.util.Arrays;
52  import java.util.HashMap;
53  import java.util.Map;
54  import java.util.Optional;
55  import java.util.function.Function;
56  import java.util.function.Supplier;
57  import java.util.stream.Stream;
58  
59  import org.slf4j.Logger;
60  import org.slf4j.LoggerFactory;
61  
62  import com.google.common.base.Suppliers;
63  import com.google.inject.Key;
64  import com.vaadin.server.VaadinServlet;
65  
66  /**
67   * Specialised component provider supplied with additional {@link ParameterResolver parameter resolvers}.
68   * <ul>
69   * <li>{@link UiComponentContextParameterResolver}</li>
70   * </ul>
71   */
72  public class UiComponentProvider implements ComponentProvider, Serializable {
73  
74      private static final Logger log = LoggerFactory.getLogger(UiComponentProvider.class);
75  
76      private final transient GuiceComponentProvider parentComponentProvider;
77      private final CurrentUiContextReference currentUiContextReference = CurrentUiContextReference.get();
78      private final UiContextReference viewContextReference;
79      private final Supplier<DatasourceDefinition> datasourceDefinition;
80  
81      public UiComponentProvider(UiContextReference viewContextReference, GuiceComponentProvider parentComponentProvider) {
82          this.parentComponentProvider = parentComponentProvider;
83          this.viewContextReference = viewContextReference;
84          this.datasourceDefinition = Suppliers.memoize(() -> this.getComponent(DatasourceDefinition.class));
85      }
86  
87      public UiComponentProvider(UiContextReference viewContextReference) {
88          this(viewContextReference, (GuiceComponentProvider) VaadinServlet.getCurrent().getServletContext().getAttribute("componentProvider"));
89      }
90  
91      public UiContextReference getUiContextReference() {
92          return viewContextReference;
93      }
94  
95      public UiComponentProvider inChildContext(String name, Object definition) {
96          Builder builder = builder(name, viewContextReference).withInstances(definition);
97          if (definition instanceof WithDatasource) {
98              Map<Class, Object> instances = new HashMap<>();
99              Optional.ofNullable(((WithDatasource) definition).getDatasource())
100                     .ifPresent(datasourceDefinition -> instances.put(DatasourceDefinition.class, datasourceDefinition));
101             builder = builder.withInstances(instances);
102         }
103 
104         return builder.build();
105     }
106 
107     public UiComponentProvider inChildContext(NamedDefinition definition) {
108         return inChildContext(definition.getName(), definition);
109     }
110 
111     @Override
112     public <T> T getComponent(Class<T> type) {
113         return provideInCurrentScope(() -> getParent().getComponent(type));
114     }
115 
116     @Override
117     public <T> Class<? extends T> getImplementation(Class<T> type) {
118         return getParent().getImplementation(getClosestMappedType(type));
119     }
120 
121     @Override
122     public <T> T getSingleton(Class<T> type) {
123         return getParent().getSingleton(type);
124     }
125 
126     @Override
127     public <T> T newInstance(Class<T> type, Object... parameters) {
128         return newInstanceWithParameterResolvers(type, new CandidateParameterResolver(parameters));
129     }
130 
131     @Override
132     public <T> T newInstanceWithParameterResolvers(Class<T> type, ParameterResolver... parameters) {
133         ParameterResolver[] combinedParameters = new ParameterResolver[parameters.length + 1];
134         combinedParameters[parameters.length] = new UiComponentContextParameterResolver(viewContextReference);
135         System.arraycopy(parameters, 0, combinedParameters, 0, parameters.length);
136 
137         T instance = provideInCurrentScope(() -> getParent().newInstanceWithParameterResolvers(getClosestMappedType(type), combinedParameters));
138 
139         if (instance instanceof UIComponent) {
140             BeanStore beanStore = SessionStore.access().getBeanStore(getUiContextReference());
141             if (beanStore.hasInstanceOf(UIComponent.class)) {
142                 log.warn("Attempted to cache a UIComponent ({}) instance in bean store containing another UIComponent ({})",
143                         type.getSimpleName(), beanStore.get(UIComponent.class).getClass().getSimpleName());
144             }
145 
146             beanStore.put(type, instance);
147         }
148 
149         // Cache instance if marked as ViewScoped
150         if (AnnotationUtils.hasAnnotationOn(type, ViewScoped.class)) {
151             //noinspection unchecked
152             SessionStore.access().getBeanStore(getUiContextReference()).put(type, instance);
153         }
154 
155         return instance;
156     }
157 
158     @Override
159     public GuiceComponentProvider getParent() {
160         return this.parentComponentProvider;
161     }
162 
163     protected Stream<UiContextReference> getReachableUiContextReferences() {
164         Stream<UiContextReference> datasourceReference = Optional.ofNullable(datasourceDefinition.get())
165                 .map(DatasourceDefinition::getName)
166                 .map(UiContextReference::ofDatasourceComponent)
167                 .map(Stream::of)
168                 .orElse(Stream.empty());
169 
170         return Stream.concat(currentUiContextReference.getAvailableContextReferences().stream(), datasourceReference);
171     }
172 
173     private <T> T provideInCurrentScope(Supplier<T> provider) {
174         final UiContextReference previousUiContextReference = currentUiContextReference.getUiContextReference();
175             currentUiContextReference.setUiContextReference(viewContextReference);
176         try {
177             return provider.get();
178         } finally {
179             currentUiContextReference.setUiContextReference(previousUiContextReference);
180         }
181     }
182 
183     private <T> Key<T> getClosestMappedType(Class<T> type) {
184         return getReachableUiContextReferences()
185                 .map(key -> Key.get(type, key.getAnnotation()))
186                 // first check if there is a matching annotated type mapping
187                 .filter(key -> getParent().getTypeMappings().containsKey(key))
188                 .findFirst()
189                 // otherwise just fetch the un-annotated type mapping
190                 .orElseGet(() -> Key.get(type));
191     }
192 
193     public static Builder builder(String name, UiContextReference parentReference) {
194         String id = name;
195         UiContextReference viewContextReference;
196         do {
197             viewContextReference = UiContextReference.ofView(id, parentReference);
198             id = Util.createUniqueName(id);
199         } while (SessionStore.access().getBeanStore(viewContextReference) != null);
200 
201         return new Builder().withUiContextReference(viewContextReference);
202     }
203 
204     public static class Builder {
205 
206         private Builder() {
207         }
208 
209         private UiContextReference uiContextReference;
210         private Map<Class, Object> instances = new HashMap<>();
211 
212         public Builder withUiContextReference(UiContextReference uiContextReference) {
213             this.uiContextReference = uiContextReference;
214             return this;
215         }
216 
217         public Builder withInstances(Object... instances) {
218             this.instances.putAll(Arrays.stream(instances)
219                     .collect(toMap(Object::getClass, Function.identity(), (first, second) -> second)));
220             return this;
221         }
222 
223         public Builder withInstances(Map<Class, Object> instances) {
224             this.instances.putAll(instances);
225             return this;
226         }
227 
228         public UiComponentProvider build() {
229             BeanStore beanStore = SessionStore.access().createBeanStore(uiContextReference);
230             instances.forEach(beanStore::put);
231 
232             UiContextBoundComponentProviderhtml#UiContextBoundComponentProvider">UiContextBoundComponentProvider uiComponentProvider = new UiContextBoundComponentProvider(uiContextReference);
233             beanStore.put(UiContextBoundComponentProvider.class, uiComponentProvider);
234             beanStore.put(ComponentProvider.class, uiComponentProvider);
235 
236             return uiComponentProvider;
237         }
238     }
239 }