View Javadoc
1   /**
2    * This file Copyright (c) 2013-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.rest.registry;
35  
36  import static info.magnolia.rest.registry.EndpointDefinitionRegistryEventType.*;
37  
38  import info.magnolia.config.registry.AbstractRegistry;
39  import info.magnolia.config.registry.DefinitionMetadata;
40  import info.magnolia.config.registry.DefinitionMetadataBuilder;
41  import info.magnolia.config.registry.DefinitionProvider;
42  import info.magnolia.config.registry.DefinitionProvider.Problem;
43  import info.magnolia.config.registry.DefinitionType;
44  import info.magnolia.config.registry.decoration.DefinitionDecorator;
45  import info.magnolia.event.EventBus;
46  import info.magnolia.event.SystemEventBus;
47  import info.magnolia.module.ModuleRegistry;
48  import info.magnolia.objectfactory.Components;
49  import info.magnolia.registry.RegistrationException;
50  import info.magnolia.rest.DynamicPath;
51  import info.magnolia.rest.EndpointDefinition;
52  
53  import java.util.Collection;
54  import java.util.List;
55  import java.util.Optional;
56  import java.util.Set;
57  
58  import javax.inject.Inject;
59  import javax.inject.Named;
60  import javax.inject.Singleton;
61  
62  import org.slf4j.Logger;
63  import org.slf4j.LoggerFactory;
64  
65  /**
66   * Registry for {@linkplain EndpointDefinition REST endpoint definitions}.
67   */
68  @Singleton
69  public class EndpointDefinitionRegistry extends AbstractRegistry<EndpointDefinition> {
70  
71      private static final Logger log = LoggerFactory.getLogger(EndpointDefinitionRegistry.class);
72  
73      static final DefinitionType TYPE = new DefinitionType() {
74          @Override
75          public String name() {
76              return "restEndpoint";
77          }
78  
79          @Override
80          public Class baseClass() {
81              return EndpointDefinition.class;
82          }
83      };
84  
85      private EventBus systemEventBus;
86  
87      @Inject
88      public EndpointDefinitionRegistry(ModuleRegistry moduleRegistry, @Named(SystemEventBus.NAME) EventBus systemEventBus) {
89          super(moduleRegistry);
90          this.systemEventBus = systemEventBus;
91      }
92  
93      /**
94       * @deprecated since 2.0 use {@link #EndpointDefinitionRegistry(ModuleRegistry, EventBus)} instead.
95       */
96      @Deprecated
97      public EndpointDefinitionRegistry(@Named(SystemEventBus.NAME) EventBus systemEventBus) {
98          this(Components.getComponent(ModuleRegistry.class), systemEventBus);
99      }
100 
101     @Override
102     public DefinitionType type() {
103         return TYPE;
104     }
105 
106     @Override
107     public DefinitionMetadataBuilder newMetadataBuilder() {
108         return new RelativePathMetadataBuilder();
109     }
110 
111     @Override
112     public void register(DefinitionProvider<EndpointDefinition> provider) {
113         DefinitionMetadata registeredMetadata = getRegistryMap().put(onRegister(provider));
114         if (registeredMetadata != null) {
115             DefinitionProvider<EndpointDefinition> decoratedProvider = getDecoratedDefinitionProvider(provider);
116             systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(REGISTERED, decoratedProvider));
117         }
118     }
119 
120     @Override
121     protected DefinitionProvider<EndpointDefinition> onRegister(DefinitionProvider<EndpointDefinition> provider) {
122 
123         DefinitionProvider<EndpointDefinition> definitionProvider = super.onRegister(provider);
124 
125         try {
126             final EndpointDefinition endpointDefinition = provider.get();
127             DefinitionProvider<EndpointDefinition> conflictDefinition = getAllProviders().stream()
128                     .filter(p -> !p.getMetadata().getReferenceId().equals(provider.getMetadata().getReferenceId()))
129                     .filter(p -> endpointDefinition.getImplementationClass().getAnnotation(DynamicPath.class) == null)
130                     .filter(p -> endpointDefinition.getImplementationClass().equals(p.get().getImplementationClass()))
131                     .findFirst().orElse(null);
132 
133             if (conflictDefinition != null) {
134                 final String message = String.format("Endpoint definitions [%s:%s] and [%s:%s] both declare implementationClass [%s]. REST module V1 does not support endpoints with identical implementationClass.",
135                         provider.getMetadata().getName(),
136                         provider.getMetadata().getReferenceId(),
137                         conflictDefinition.getMetadata().getName(),
138                         conflictDefinition.getMetadata().getReferenceId(),
139                         endpointDefinition.getImplementationClass());
140                 Problem problem = Problem.major()
141                         .withDetails(message)
142                         .withTitle("Conflict endpoint definitions.")
143                         .withType(Problem.DefaultTypes.SEMANTIC)
144                         .withLocation(provider.getMetadata().getLocation())
145                         .build();
146                 provider.getProblems().add(problem);
147             }
148         } catch (Exception e) {
149             log.error("Error happens when checking endpoint duplications.", e);
150         }
151         return definitionProvider;
152     }
153 
154     @Override
155     public Set<DefinitionMetadata> unregisterAndRegister(Collection<DefinitionMetadata> toRemoveIds, Collection<DefinitionProvider<EndpointDefinition>> definitionProviders) {
156         Set<DefinitionMetadata> registeredIds = super.unregisterAndRegister(toRemoveIds, definitionProviders);
157 
158         toRemoveIds.stream()
159                 .filter(metadata -> !registeredIds.contains(metadata))
160                 .map(DefinitionMetadata::getReferenceId)
161                 .forEach(referenceId -> systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(UNREGISTERED, referenceId)));
162 
163         definitionProviders.stream()
164                 .filter(provider -> toRemoveIds.contains(provider.getMetadata()))
165                 .filter(provider -> registeredIds.contains(provider.getMetadata()))
166                 .forEach(provider -> systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(REREGISTERED, provider)));
167 
168         definitionProviders.stream()
169                 .filter(provider -> !toRemoveIds.contains(provider.getMetadata()))
170                 .filter(provider -> registeredIds.contains(provider.getMetadata()))
171                 .forEach(provider -> systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(REGISTERED, provider)));
172 
173         return registeredIds;
174     }
175 
176     @Override
177     public void addDecorator(DefinitionDecorator<EndpointDefinition> decorator) {
178         super.addDecorator(decorator);
179         Optional<DefinitionProvider<EndpointDefinition>> targetProvider = getAllProviders().stream()
180                 .filter(decorator::appliesTo)
181                 .findFirst();
182         if (targetProvider.isPresent()) {
183             systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(REREGISTERED, targetProvider.get()));
184         } else {
185             //provided decorator cannot be applied to any provider.
186             super.removeDecorator(decorator);
187         }
188     }
189 
190     @Override
191     public void removeDecorator(DefinitionDecorator<EndpointDefinition> decorator) {
192         Optional<DefinitionProvider<EndpointDefinition>> targetProvider = getAllProviders().stream()
193                 .filter(decorator::appliesTo)
194                 .findFirst();
195         super.removeDecorator(decorator);
196         targetProvider.ifPresent(provider -> systemEventBus.fireEvent(new EndpointDefinitionRegistryEvent(REREGISTERED, provider)));
197     }
198 
199     /**
200      * @deprecated since 2.0 use {@link #getProvider(String)} instead.
201      */
202     @Deprecated
203     public EndpointDefinition getEndpointDefinition(String name) throws RegistrationException {
204         final EndpointDefinition endpointDefinition;
205         try {
206             endpointDefinition = getProvider(name).get();
207         } catch (NoSuchDefinitionException | InvalidDefinitionException e) {
208             throw new RegistrationException(e.getMessage(), e);
209         }
210         return endpointDefinition;
211     }
212 
213     /**
214      * @deprecated since 2.0 use {@link #unregisterAndRegister(Collection, Collection)} instead.
215      */
216     @Deprecated
217     public Set<String> unregisterAndRegister(Set<String> registeredNames, List<EndpointDefinitionProvider> providers) {
218         return null;
219     }
220 
221     /**
222      * @deprecated since 2.0 use standard registry APIs instead ({@link #getProvider(String)} and {@link DefinitionProvider#isValid()}).
223      */
224     @Deprecated
225     public boolean isEndpointDefinitionRegistered(String name) {
226         try {
227             return getProvider(name).isValid();
228         } catch (NoSuchDefinitionException e) {
229             return false;
230         }
231     }
232 
233     /**
234      * @deprecated since 2.0 use {@link #getAllDefinitions()} instead.
235      */
236     @Deprecated
237     public Collection<EndpointDefinition> getAllEndpointDefinitions() {
238         return getAllDefinitions();
239     }
240 
241     /**
242      * This DefinitionMetadataBuilder generates referenceId from the relative-location only.
243      * <p>This way, resource paths are dynamically configured/deduced from config path;
244      * and the {@link info.magnolia.rest.RestDispatcherServlet RestDispatcherServlet} is responsible for the routing.
245      */
246     public static class RelativePathMetadataBuilder extends DefinitionMetadataBuilder {
247         @Override
248         protected String buildReferenceId() {
249             return getRelativeLocation();
250         }
251     }
252 }