View Javadoc
1   /**
2    * This file Copyright (c) 2014-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.config.registry;
35  
36  import static info.magnolia.config.registry.decoration.DefinitionDecorators.*;
37  import static java.util.stream.Collectors.toList;
38  
39  import info.magnolia.config.registry.decoration.CachingDefinitionDecorator;
40  import info.magnolia.config.registry.decoration.DefinitionDecorator;
41  import info.magnolia.config.registry.validator.DefinitionValidator;
42  import info.magnolia.config.source.stub.ConfigurationSourceTypeStub;
43  import info.magnolia.module.ModuleRegistry;
44  
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.HashSet;
48  import java.util.List;
49  import java.util.Optional;
50  import java.util.Set;
51  
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  /**
56   * Abstract {@link info.magnolia.config.registry.Registry} base class holding a map of all objects of the defined type.
57   *
58   * @param <T> the type of the contained objects
59   */
60  public abstract class AbstractRegistry<T> implements Registry<T> {
61  
62      private static final Logger log = LoggerFactory.getLogger(AbstractRegistry.class);
63  
64      private final RegistryMap<T> registryMap = new RegistryMap<>();
65  
66      private final Set<DefinitionDecorator<T>> definitionDecorators = new HashSet<>();
67  
68      private final Set<DefinitionValidator<T>> validators = new HashSet<>();
69  
70      private final ModuleRegistry moduleRegistry;
71  
72      public AbstractRegistry(ModuleRegistry moduleRegistry) {
73          this.moduleRegistry = moduleRegistry;
74      }
75  
76      @Override
77      public void start() {
78          throw new IllegalStateException("not implemented yet");
79      }
80  
81      protected RegistryMap<T> getRegistryMap() {
82          return registryMap;
83      }
84  
85      @Override
86      public void register(DefinitionProvider<T> provider) {
87          getRegistryMap().put(onRegister(provider));
88      }
89  
90      @Override
91      public Set<DefinitionMetadata> unregisterAndRegister(Collection<DefinitionMetadata> registeredIds, Collection<DefinitionProvider<T>> providers) {
92          List<DefinitionProvider<T>> wrappedProviders = providers.stream()
93                  .map(this::onRegister)
94                  .collect(toList());
95  
96          for (DefinitionMetadata definitionMetadata : registeredIds) {
97              if (providers.stream().noneMatch(provider -> provider.getMetadata().equals(definitionMetadata))) { //unregistering without replacement
98                  for (DefinitionDecorator<T> definitionDecorator : definitionDecorators) {
99                      if (definitionDecorator.appliesTo(getRegistryMap().get(definitionMetadata))) { //there is a decorator for the definition which will be unregistered
100                         wrappedProviders.add(new DecoratedDefinitionProviderStub<>(this, definitionDecorator));
101                         break;
102                     }
103                 }
104             }
105         }
106         return getRegistryMap().removeAndPutAll(registeredIds, wrappedProviders);
107     }
108 
109     protected DefinitionProvider<T> onRegister(DefinitionProvider<T> provider) {
110         return provider;
111     }
112 
113     @Override
114     public DefinitionProvider<T> getProvider(final DefinitionMetadata id) {
115         DefinitionProvider<T> provider = getRegistryMap().get(id);
116         if (provider == null) {
117             throw new NoSuchDefinitionException(getReferenceId(id));
118         }
119         provider =  provider.isValid() ? getDecoratedDefinitionProvider(provider) : provider;
120         return validate(provider);
121     }
122 
123     @Override
124     public DefinitionProvider<T> getProvider(final String referenceId) {
125         DefinitionProvider<T> provider = getRegistryMap().getByStringKey(referenceId);
126         if (provider == null) {
127             throw new NoSuchDefinitionException(referenceId);
128         }
129         provider = provider.isValid() ? getDecoratedDefinitionProvider(provider) : provider;
130         return validate(provider);
131     }
132 
133     protected final DefinitionProvider<T> getDecoratedDefinitionProvider(final DefinitionProvider<T> provider) {
134         // Find applicable definition decorators and sort them according to module hierarchy
135         List<DefinitionDecorator<T>> decorators = definitionDecorators.stream()
136                 .filter(appliesTo(provider))
137                 .sorted(moduleDependencyBasedComparator(moduleRegistry))
138                 .collect(toList());
139 
140         // Apply decorators (decoration result may be cached within {@link CachingDefinitionDecorator} => this cycle is
141         // not necessarily expensive).
142         DefinitionProvider<T> decoratedProvider = provider;
143         for (final DefinitionDecorator<T> definitionDecorator : decorators) {
144             final DefinitionMetadata definitionProviderMetadata = provider.getMetadata();
145             log.debug("Decorating [{}] definition with the reference id [{}] with [{}]", definitionProviderMetadata.getType().name(), definitionProviderMetadata.getReferenceId(), definitionDecorator.toString());
146             decoratedProvider = definitionDecorator.decorate(decoratedProvider);
147         }
148         return decoratedProvider;
149     }
150 
151     @Override
152     public Collection<DefinitionProvider<T>> getAllProviders() {
153         return getAllMetadata().stream()
154                 .map(this::getProvider)
155                 .collect(toList());
156     }
157 
158     @Override
159     public Collection<DefinitionMetadata> getAllMetadata() {
160         return getRegistryMap().keySet();
161     }
162 
163     @Override
164     public Collection<T> getAllDefinitions() {
165         return getAllProviders().stream()
166                 .filter(DefinitionProvider::isValid)
167                 .map(DefinitionProvider::get)
168                 .collect(toList());
169     }
170 
171     @Override
172     @Deprecated
173     public DefinitionQuery<T> query() {
174         return DefinitionQuery.build(this);
175     }
176 
177     @Override
178     public void addDecorator(DefinitionDecorator<T> definitionDecorator) {
179         if (definitionDecorator.metadata().getDecoratedDefinitionReference() != null && getAllProviders().stream().noneMatch(definitionDecorator::appliesTo)) {
180             register(new DecoratedDefinitionProviderStub<>(this, definitionDecorator));
181         }
182 
183         final CachingDefinitionDecorator<T> cachingDecorator = new CachingDefinitionDecorator<>(definitionDecorator);
184 
185         // Remove an equal decorator if it is present first (mind that equality is verified via #equals(...) method)
186         definitionDecorators.remove(cachingDecorator);
187 
188         // Register a new decorator
189         definitionDecorators.add(cachingDecorator);
190     }
191 
192     @Override
193     public void removeDecorator(DefinitionDecorator<T> definitionDecorator) {
194         // TODO improve this as part of MAGNOLIA-6627 resolution
195         final DefinitionDecorator<T> toRemove = definitionDecorator instanceof CachingDefinitionDecorator ? definitionDecorator : new CachingDefinitionDecorator<>(definitionDecorator);
196         definitionDecorators.remove(toRemove);
197 
198         // remove decoration target stub (if exists):
199         final Optional<DefinitionProvider<T>> decoratedStub = getAllProviders().stream().filter(provider -> definitionDecorator.appliesTo(provider) && ConfigurationSourceTypeStub.stub == provider.getMetadata().getConfigurationSourceType()).findAny();
200         if (decoratedStub.isPresent() && definitionDecorators.stream().noneMatch(decorator -> decorator.appliesTo(decoratedStub.get()))) {
201             getRegistryMap().remove(decoratedStub.get().getMetadata());
202         }
203     }
204 
205     @Override
206     public String getReferenceId(DefinitionReference definitionReference) {
207         /**
208          * This is a hack which exploits {@link DefinitionMetadataBuilder} capabilities to resolve reference id
209          * for the module entry. We instantiate a new {@link DefinitionMetadataBuilder} and obtain a ref id from it
210          * based on the sufficient information stored in {@link definitionReference}.
211          */
212         return newMetadataBuilder().
213                 relativeLocation(definitionReference.getRelativeLocation()).
214                 name(definitionReference.getName()).
215                 module(definitionReference.getModule()).
216                 buildReferenceId();
217     }
218 
219     Set<DefinitionDecorator<T>> getDefinitionDecorators() {
220         return definitionDecorators;
221     }
222 
223     public void addValidator(DefinitionValidator<T> validator) {
224         validators.add(validator);
225     }
226 
227     protected DefinitionProvider<T> validate(DefinitionProvider<T> provider) {
228         return new DefinitionProviderWrapper<T>(provider) {
229             @Override
230             public Collection<Problem> getProblems() {
231                 final Collection<Problem> problems = new ArrayList<>(super.getProblems());
232                 validators.forEach(validator -> problems.addAll(validator.validate(provider)));
233                 return problems;
234             }
235         };
236     }
237 }