View Javadoc
1   /**
2    * This file Copyright (c) 2014-2016 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.config.registry;
35  
36  import static com.google.common.collect.FluentIterable.from;
37  import static info.magnolia.config.registry.decoration.DefinitionDecorators.appliesTo;
38  import static info.magnolia.config.registry.decoration.DefinitionDecorators.moduleDependencyBasedComparator;
39  
40  import info.magnolia.config.NamedDefinition;
41  import info.magnolia.config.registry.decoration.CachingDefinitionDecorator;
42  import info.magnolia.config.registry.decoration.DefinitionDecorator;
43  import info.magnolia.module.ModuleRegistry;
44  import info.magnolia.objectfactory.Components;
45  
46  import java.lang.reflect.Method;
47  import java.util.Collection;
48  import java.util.List;
49  import java.util.Set;
50  
51  import javax.annotation.Nullable;
52  
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  import com.google.common.base.Function;
57  import com.google.common.base.Predicate;
58  import com.google.common.base.Predicates;
59  import com.google.common.collect.Collections2;
60  import com.google.common.collect.Lists;
61  import com.google.common.collect.Sets;
62  import com.thoughtworks.proxy.factory.CglibProxyFactory;
63  import com.thoughtworks.proxy.toys.decorate.Decorating;
64  import com.thoughtworks.proxy.toys.decorate.Decorator;
65  
66  /**
67   * Abstract {@link info.magnolia.config.registry.Registry} base class holding a map of all objects of the defined type.
68   *
69   * @param <T> the type of the contained objects
70   */
71  public abstract class AbstractRegistry<T> implements Registry<T> {
72  
73      private static final Logger log = LoggerFactory.getLogger(AbstractRegistry.class);
74  
75      private final RegistryMap<T> registryMap = new RegistryMap<>();
76  
77      private final CglibProxyFactory proxyFactory;
78  
79      private final Set<DefinitionDecorator<T>> definitionDecorators = Sets.newHashSet();
80  
81      private final ModuleRegistry moduleRegistry;
82  
83      /**
84       * @deprecated since 5.4.6, use {@link #AbstractRegistry(info.magnolia.module.ModuleRegistry}.
85       */
86      @Deprecated
87      public AbstractRegistry() {
88          this(Components.getComponent(ModuleRegistry.class));
89      }
90  
91      public AbstractRegistry(ModuleRegistry moduleRegistry) {
92          this.moduleRegistry = moduleRegistry;
93          this.proxyFactory = new CglibProxyFactory(false);
94      }
95  
96      @Override
97      public void start() {
98          throw new IllegalStateException("not implemented yet");
99      }
100 
101     protected RegistryMap<T> getRegistryMap() {
102         return registryMap;
103     }
104 
105     @Override
106     public void register(DefinitionProvider<T> provider) {
107         getRegistryMap().put(onRegister(provider));
108     }
109 
110     @Override
111     public Set<DefinitionMetadata> unregisterAndRegister(Collection<DefinitionMetadata> registeredIds, Collection<DefinitionProvider<T>> providers) {
112         final Collection<DefinitionProvider<T>> wrappedProviders = Collections2.transform(providers, new Function<DefinitionProvider<T>, DefinitionProvider<T>>() {
113             @Override
114             public DefinitionProvider<T> apply(DefinitionProvider<T> input) {
115                 return onRegister(input);
116             }
117         });
118         return getRegistryMap().removeAndPutAll(registeredIds, wrappedProviders);
119     }
120 
121     /**
122      * This default implementation wraps the given provider when the definition is a {@link NamedDefinition}; in such cases,
123      * the definition is proxied and its getName() method is intercepted and redirected to {@link DefinitionMetadata#getName()}.
124      * Overload or override this if the provider for this registry needs to "post-process" the provider/beans being registered.
125      * Don't forget to check {@link DefinitionProvider#isValid()} before calling {@link DefinitionProvider#get()}
126      * @see info.magnolia.config.registry.DefinitionProviderWrapper
127      */
128     protected DefinitionProvider<T> onRegister(DefinitionProvider<T> provider) {
129         if (provider.isValid()) {
130             final T def = provider.get();
131             final T decoratedDef;
132             if (def instanceof NamedDefinition) {
133                 decoratedDef = Decorating.proxy(def).visiting(new GetNameFromMetadata<>(provider)).build(proxyFactory);
134             } else {
135                 decoratedDef = def;
136             }
137 
138             return new SwappedDefinitionProviderWrapper<>(provider, decoratedDef);
139         } else {
140             return provider;
141         }
142     }
143 
144     @Override
145     public DefinitionProvider<T> getProvider(final DefinitionMetadata id) {
146         final DefinitionProvider<T> provider = getRegistryMap().get(id);
147         if (provider == null) {
148             throw new NoSuchDefinitionException(getReferenceId(id));
149         }
150         return getDecoratedDefinitionProvider(provider);
151     }
152 
153     @Override
154     public DefinitionProvider<T> getProvider(final String referenceId) {
155         final DefinitionProvider<T> provider = getRegistryMap().getByStringKey(referenceId);
156         if (provider == null) {
157             throw new NoSuchDefinitionException(referenceId);
158         }
159         return getDecoratedDefinitionProvider(provider);
160     }
161 
162     protected final DefinitionProvider<T> getDecoratedDefinitionProvider(final DefinitionProvider<T> provider) {
163         // Find applicable definition decorators and sort them according to module hierarchy
164         final List<DefinitionDecorator<T>> decorators =
165                 from(this.definitionDecorators).
166                 filter(appliesTo(provider)).
167                 toSortedList(moduleDependencyBasedComparator(moduleRegistry));
168 
169         // Apply decorators (decoration result may be cached within {@link CachingDefinitionDecorator} => this cycle is
170         // not necessarily expensive).
171         DefinitionProvider<T> decoratedProvider = provider;
172         for (final DefinitionDecorator<T> definitionDecorator : decorators) {
173             final DefinitionMetadata definitionProviderMetadata = provider.getMetadata();
174             log.debug("Decorating [{}] definition with the reference id [{}] with [{}]", definitionProviderMetadata.getType().name(), definitionProviderMetadata.getReferenceId(), definitionDecorator.toString());
175             decoratedProvider = definitionDecorator.decorate(decoratedProvider);
176         }
177 
178         return decoratedProvider;
179     }
180 
181     @Override
182     public Collection<DefinitionProvider<T>> getAllProviders() {
183         return Collections2.transform(getAllMetadata(), new Function<DefinitionMetadata, DefinitionProvider<T>>() {
184             @Nullable
185             @Override
186             public DefinitionProvider<T> apply(@Nullable DefinitionMetadata input) {
187                 return getProvider(input);
188             }
189         });
190     }
191 
192     @Override
193     public Collection<DefinitionMetadata> getAllMetadata() {
194         return getRegistryMap().keySet();
195     }
196 
197     @Override
198     public Collection<T> getAllDefinitions() {
199         final Collection<DefinitionProvider<T>> validProviders = Collections2.filter(getAllProviders(), VALID);
200         return Lists.newArrayList(Collections2.transform(validProviders, new DefinitionProviderGet<T>()));
201     }
202 
203     private final Predicate<DefinitionProvider<T>> VALID = new ValidDefinitionProvider<>();
204     private final Predicate<DefinitionProvider<T>> ALL = Predicates.alwaysTrue();
205     private final Function<DefinitionProvider<T>, T> GET = new DefinitionProviderGet<>();
206 
207     private static class ValidDefinitionProvider<T> implements Predicate<DefinitionProvider<T>> {
208         @Override
209         public boolean apply(DefinitionProvider<T> input) {
210             return input.isValid();
211         }
212     }
213 
214     private static class DefinitionProviderGet<T> implements Function<DefinitionProvider<T>, T> {
215         @Override
216         public T apply(DefinitionProvider<T> input) {
217             return input.get();
218         }
219     }
220 
221     @Override
222     public DefinitionQuery<T> query() {
223         return new DefinitionQueryImpl<>(this);
224     }
225 
226     @Override
227     public void addDecorator(DefinitionDecorator<T> definitionDecorator) {
228         final CachingDefinitionDecorator<T> cachingDecorator = new CachingDefinitionDecorator<>(definitionDecorator);
229 
230         // Remove an equal decorator if it is present first (mind that equality is verified via #equals(...) method)
231         definitionDecorators.remove(cachingDecorator);
232 
233         // Register a new decorator
234         definitionDecorators.add(cachingDecorator);
235     }
236 
237     @Override
238     public void removeDecorator(DefinitionDecorator<T> definitionDecorator) {
239         // TODO improve this as part of MAGNOLIA-6627 resolution
240         final DefinitionDecorator<T> toRemove = definitionDecorator instanceof CachingDefinitionDecorator ? definitionDecorator : new CachingDefinitionDecorator<>(definitionDecorator);
241         definitionDecorators.remove(toRemove);
242     }
243 
244     @Override
245     public String getReferenceId(DefinitionReference definitionReference) {
246         /**
247          * This is a hack which exploits {@link DefinitionMetadataBuilder} capabilities to resolve reference id
248          * for the module entry. We instantiate a new {@link DefinitionMetadataBuilder} and obtain a ref id from it
249          * based on the sufficient information stored in {@link definitionReference}.
250          */
251         return newMetadataBuilder().
252                 relativeLocation(definitionReference.getRelativeLocation()).
253                 name(definitionReference.getName()).
254                 module(definitionReference.getModule()).
255                 buildReferenceId();
256     }
257 
258     private static class DefinitionQueryImpl<T> extends DefinitionQuery<T> {
259         private final Registry registry;
260 
261         public DefinitionQueryImpl(Registry registry) {
262             this.registry = registry;
263         }
264 
265         @Override
266         public Collection<DefinitionProvider<T>> findMultiple() {
267             return findMultipleOn(registry);
268         }
269 
270         protected Collection<DefinitionProvider<T>> findMultipleOn(final Registry<T> registry) {
271             final Collection<DefinitionMetadata> allMetadata = registry.getAllMetadata();
272             final Collection<DefinitionMetadata> matchingMetas = Collections2.filter(allMetadata, new QueryPredicate<T>(this));
273             // TODO #transform wraps the original collection instead of copying. Make sure this is OK.
274             return Collections2.transform(matchingMetas, new Function<DefinitionMetadata, DefinitionProvider<T>>() {
275                 @Override
276                 public DefinitionProvider<T> apply(DefinitionMetadata md) {
277                     return registry.getProvider(md);
278                 }
279             });
280         }
281     }
282 
283     private static class QueryPredicate<T> implements Predicate<DefinitionMetadata> {
284         private final DefinitionQuery query;
285 
286         public QueryPredicate(DefinitionQuery query) {
287             this.query = query;
288         }
289 
290         @Override
291         public boolean apply(DefinitionMetadata input) {
292             boolean match = true;
293             match &= match(query.getName(), input.getName());
294             match &= match(query.getModule(), input.getModule());
295             return match;
296         }
297 
298         private boolean match(String queryParam, String actual) {
299             if (queryParam == null) {
300                 // then we ignore this param, consider it a match
301                 return true;
302             }
303             return queryParam.equals(actual);
304         }
305     }
306 
307     private static class GetNameFromMetadata<T> extends Decorator<T> {
308         private final DefinitionProvider<T> definitionProvider;
309 
310         public GetNameFromMetadata(DefinitionProvider<T> definitionProvider) {
311             this.definitionProvider = definitionProvider;
312         }
313 
314         @Override
315         public Object decorateResult(T proxy, Method method, Object[] args, Object result) {
316             if (result == null) {
317                 // TODO we kinda hard-code the NamedInterface method here . . .
318                 if ("getName".equals(method.getName())) {
319                     return definitionProvider.getMetadata().getName();
320                 }
321             }
322             return result;
323         }
324     }
325 
326     private static class SwappedDefinitionProviderWrapper<T> extends DefinitionProviderWrapper<T> {
327         private final T decoratedDef;
328 
329         public SwappedDefinitionProviderWrapper(DefinitionProvider<T> provider, T decoratedDef) {
330             super(provider);
331             this.decoratedDef = decoratedDef;
332         }
333 
334         @Override
335         public T get() {
336             return decoratedDef;
337         }
338     }
339 }