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