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