View Javadoc
1   /**
2    * This file Copyright (c) 2015-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.source.yaml.decoration;
35  
36  import static info.magnolia.config.converters.RawDefinitionViewToMapConverter.rawViewToMap;
37  import static info.magnolia.config.maputil.ToMap.toMap;
38  import static info.magnolia.config.registry.DefinitionProvider.Problem.DefaultTypes.DECORATION;
39  
40  import info.magnolia.cms.util.ExceptionUtil;
41  import info.magnolia.config.maputil.ConfigurationMapOverlay;
42  import info.magnolia.config.registry.DefinitionProvider;
43  import info.magnolia.config.registry.DefinitionProviderBuilder;
44  import info.magnolia.config.registry.DefinitionProviderProblemLogger;
45  import info.magnolia.config.registry.DefinitionRawView;
46  import info.magnolia.config.registry.DefinitionReferenceIdResolver;
47  import info.magnolia.config.registry.Registry;
48  import info.magnolia.config.registry.decoration.DefinitionDecorator;
49  import info.magnolia.config.source.DefinitionProviderWrapperWithProxyFallback;
50  import info.magnolia.config.source.raw.DefinitionRawViewMapWrapper;
51  import info.magnolia.config.source.yaml.YamlReader;
52  import info.magnolia.config.source.yaml.construct.IncludeFileYamlWithModificationPossibility;
53  import info.magnolia.config.source.yaml.construct.OverrideSource;
54  import info.magnolia.init.MagnoliaConfigurationProperties;
55  import info.magnolia.map2bean.Map2BeanTransformer;
56  import info.magnolia.objectfactory.Components;
57  import info.magnolia.resourceloader.Resource;
58  import info.magnolia.transformer.TransformationResult;
59  
60  import java.util.HashMap;
61  import java.util.Map;
62  import java.util.Optional;
63  
64  import org.apache.commons.collections4.MapUtils;
65  import org.apache.commons.lang3.exception.ExceptionUtils;
66  import org.slf4j.Logger;
67  import org.slf4j.LoggerFactory;
68  
69  import com.google.common.collect.ImmutableList;
70  
71  import lombok.SneakyThrows;
72  
73  /**
74   * {@link info.magnolia.config.registry.decoration.DefinitionDecorator Definition decorator} implementation which uses a YAML
75   * file as a decoration data source. Decorates a single {@link DefinitionProvider} with a certain reference id.
76   *
77   * @param <T> definition type corresponding to the decorated definition provider
78   */
79  public class YamlDefinitionDecorator<T> extends AbstractFileDefinitionDecorator<T> {
80  
81      private static final Logger log = LoggerFactory.getLogger(YamlDefinitionDecorator.class);
82  
83      private final YamlReader yamlReader;
84  
85      private final Resource yamlFile;
86  
87      private final MagnoliaConfigurationProperties magnoliaConfigurationProperties;
88  
89      private final YamlDefinitionDecoratorMetadata metadata;
90  
91      private final DefinitionReferenceIdResolver referenceIdResolver;
92  
93      private final Map2BeanTransformer map2BeanTransformer;
94  
95      /**
96       * @deprecated since 5.5 - use {@link #YamlDefinitionDecorator(YamlDefinitionDecoratorMetadata, DefinitionReferenceIdResolver, Resource, Map2BeanTransformer, MagnoliaConfigurationProperties)} instead.
97       */
98      @Deprecated
99      public YamlDefinitionDecorator(
100             YamlDefinitionDecoratorMetadata metadata,
101             DefinitionReferenceIdResolver referenceIdResolver,
102             Resource decoratorYamlFile,
103             Map2BeanTransformer map2BeanTransformer) {
104         this(metadata, referenceIdResolver, decoratorYamlFile, map2BeanTransformer, Components.getComponent(MagnoliaConfigurationProperties.class));
105     }
106 
107     public YamlDefinitionDecorator(
108             YamlDefinitionDecoratorMetadata metadata,
109             DefinitionReferenceIdResolver referenceIdResolver,
110             Resource decoratorYamlFile,
111             Map2BeanTransformer map2BeanTransformer,
112             MagnoliaConfigurationProperties magnoliaConfigurationProperties) {
113         this.yamlFile = decoratorYamlFile;
114         this.magnoliaConfigurationProperties = magnoliaConfigurationProperties;
115         this.yamlReader = new YamlReader();
116         this.metadata = metadata;
117         this.referenceIdResolver = referenceIdResolver;
118         this.map2BeanTransformer = map2BeanTransformer;
119     }
120 
121     @Override
122     public Resource getDecoratorFile() {
123         return yamlFile;
124     }
125 
126     @Override
127     public YamlDefinitionDecoratorMetadata metadata() {
128         return metadata;
129     }
130 
131     @Override
132     public boolean appliesTo(DefinitionProvider<T> definitionProvider) {
133         return definitionProvider.getMetadata().getReferenceId().equals(referenceIdResolver.getReferenceId(metadata.getDecoratedDefinitionReference()));
134     }
135 
136     @Override
137     public DefinitionProvider<T> decorate(final DefinitionProvider<T> definitionProvider) {
138         final DefinitionProviderBuilder<T> definitionProviderBuilder = referenceIdResolver instanceof Registry ? ((Registry) referenceIdResolver).newDefinitionProviderBuilder() : DefinitionProviderBuilder.newBuilder();
139 
140         this.yamlReader.registerCustomConstruct(OverrideSource.TAG, new OverrideSource(definitionProviderBuilder::addProblem));
141         this.yamlReader.registerCustomMultiConstruct(IncludeFileYamlWithModificationPossibility.TAG_PREFIX, new IncludeFileYamlWithModificationPossibility(yamlReader, this.yamlFile.getOrigin(), definitionProviderBuilder::addProblem));
142 
143         DefinitionRawView raw = definitionProvider.getRaw();
144         Map<String, Object> decoratedMapRepresentation = new HashMap<>();
145         Map<String, Object> definitionData = new HashMap<>();
146         try {
147             // `rawViewToMap` has side-effects
148             // if the definition raw-view is already a map; get it straight
149             definitionData = raw instanceof DefinitionRawViewMapWrapper ? ((DefinitionRawViewMapWrapper) raw).getRawMap() : rawViewToMap(raw);
150         } catch (Exception e) {
151             definitionProviderBuilder.addProblem(
152                     DefinitionProvider.Problem
153                             .severe()
154                             .withType(DECORATION)
155                             .withTitle(String.format("Failed to parse raw definition from [%s] during decorating with [%s]", definitionProvider.getMetadata().getName(), yamlFile.getPath()))
156                             .withRelatedException(e)
157                             .withDetails(ExceptionUtil.exceptionToWords(
158                                     Optional
159                                             .ofNullable(ExceptionUtils.getRootCause(e))
160                                             .orElse(e)))
161                             .build());
162         }
163         try {
164             if (MapUtils.isNotEmpty(definitionData)) {
165                 final Map<String, Object> decoratorData = parseYamlFile();
166 
167                 if (decoratorData.isEmpty()) {
168                     definitionProviderBuilder
169                             .addProblem(
170                                     DefinitionProvider.Problem
171                                             .major()
172                                             .withType(DECORATION)
173                                             .withTitle(String.format("Decoration data from [%s] is empty", yamlFile.getPath()))
174                                             .build());
175                 }
176 
177                 decoratedMapRepresentation = ConfigurationMapOverlay
178                         .of(definitionData)
179                         .by(decoratorData)
180                         .at(metadata.getDecoratedPath())
181                         .overlay();
182             }
183         } catch (Exception e) {
184             decoratedMapRepresentation = new HashMap<>();
185             definitionProviderBuilder.addProblem(
186                     DefinitionProvider.Problem
187                             .major()
188                             .withType(DECORATION)
189                             .withTitle(String.format("Failed to parse decoration data from [%s]", yamlFile.getPath()))
190                             .withRelatedException(e)
191                             .withDetails(ExceptionUtil.exceptionToWords(
192                                     Optional
193                                             .ofNullable(ExceptionUtils.getRootCause(e))
194                                             .orElse(e)))
195                             .build());
196         }
197 
198         //noinspection unchecked
199         final TransformationResult<T> transformationResult =
200                 map2BeanTransformer
201                         .transform(decoratedMapRepresentation, definitionProvider.getMetadata().getType().baseClass());
202 
203         final DefinitionProvider<T> decoratedDefinitionProvider = definitionProviderBuilder
204                 .metadata(definitionProvider.getMetadata())
205                 .rawView(new DefinitionRawViewMapWrapper(decoratedMapRepresentation))
206                 .decorators(ImmutableList
207                         .<DefinitionDecorator<T>>builder()
208                         .addAll(definitionProvider.getDecorators())
209                         .add(YamlDefinitionDecorator.this)
210                         .build())
211                 .buildFromTransformationResult(transformationResult);
212 
213         log.info("Applied {} to definition provider [{}]", this, definitionProvider.getMetadata());
214 
215         DefinitionProviderProblemLogger.withLoggingContext(log, magnoliaConfigurationProperties.getBooleanProperty("magnolia.develop")).logProblems(decoratedDefinitionProvider);
216 
217         return new DefinitionProviderWrapperWithProxyFallback<>(decoratedDefinitionProvider, definitionProvider.get());
218     }
219 
220     @SneakyThrows
221     // we would like to bubble the exception but stay backwards compatible with the previous versions, hence the sneaky throws...
222     protected final Map<String, Object> parseYamlFile() {
223         return toMap(yamlReader.read(yamlFile));
224     }
225 
226     @Override
227     public String toString() {
228         return String.format("YAML file based decorator from [%s]", yamlFile.getPath());
229     }
230 }