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.source.jcr;
35  
36  import info.magnolia.config.NamedDefinition;
37  import info.magnolia.config.registry.DefinitionMetadata;
38  import info.magnolia.config.registry.DefinitionMetadataBuilder;
39  import info.magnolia.config.registry.DefinitionProvider;
40  import info.magnolia.config.registry.DefinitionProviderBuilder;
41  import info.magnolia.config.registry.DefinitionProviderProblemLogger;
42  import info.magnolia.config.registry.Registry;
43  import info.magnolia.config.source.ConfigurationSource;
44  import info.magnolia.config.source.ConfigurationSourceType;
45  import info.magnolia.config.source.ConfigurationSourceTypes;
46  import info.magnolia.config.source.raw.DefinitionRawViewMapWrapper;
47  import info.magnolia.jcr.node2bean.Node2BeanProcessor;
48  import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl;
49  import info.magnolia.jcr.node2bean.impl.PreConfiguredBeanUtils;
50  import info.magnolia.jcr.predicate.NodeTypePredicate;
51  import info.magnolia.jcr.util.NodeTypes;
52  import info.magnolia.jcr.util.NodeUtil;
53  import info.magnolia.jcr.util.NodeVisitor;
54  import info.magnolia.jcr.util.PropertyUtil;
55  import info.magnolia.jcr.wrapper.ExtendingNodeWrapper;
56  import info.magnolia.map2bean.Map2BeanTransformer;
57  import info.magnolia.module.ModuleRegistry;
58  import info.magnolia.objectfactory.Components;
59  import info.magnolia.transformer.TransformationResult;
60  
61  import java.io.IOException;
62  import java.util.ArrayList;
63  import java.util.Arrays;
64  import java.util.HashSet;
65  import java.util.LinkedHashMap;
66  import java.util.List;
67  import java.util.Map;
68  import java.util.Set;
69  import java.util.stream.Collectors;
70  
71  import javax.inject.Inject;
72  import javax.jcr.Node;
73  import javax.jcr.NodeIterator;
74  import javax.jcr.Property;
75  import javax.jcr.PropertyIterator;
76  import javax.jcr.RepositoryException;
77  
78  import org.apache.commons.lang3.StringUtils;
79  import org.apache.jackrabbit.commons.predicate.Predicate;
80  import org.slf4j.Logger;
81  import org.slf4j.LoggerFactory;
82  
83  /**
84   * Configuration source for JCR configuration.
85   *
86   * @param <T> type the source will provide
87   */
88  public class JcrConfigurationSource<T> implements ConfigurationSource {
89  
90      private static final Logger log = LoggerFactory.getLogger(JcrConfigurationSource.class);
91      private static final String NAME_PROPERTY = "name";
92  
93      private final Registry<T> registry;
94      private final String pathInModule;
95      private final Predicate nodeFilter;
96      private final Map2BeanTransformer map2BeanTransformer;
97      private Set<DefinitionMetadata> registeredIds = new HashSet<>();
98      private final RegistryBasedObservingManager observingManager;
99  
100     @Inject
101     public JcrConfigurationSource(Registry<T> registry, String pathInModule, Predicate nodeFilter,
102                                   ModuleRegistry moduleRegistry, Map2BeanTransformer map2BeanTransformer) {
103         this.registry = registry;
104         this.pathInModule = pathInModule;
105         this.nodeFilter = nodeFilter;
106         this.map2BeanTransformer = map2BeanTransformer;
107 
108         this.observingManager = getObservingManager(pathInModule, moduleRegistry);
109     }
110 
111     protected RegistryBasedObservingManager getObservingManager(String pathInModule, ModuleRegistry moduleRegistry) {
112         return new RegistryBasedObservingManager(pathInModule, moduleRegistry, this);
113     }
114 
115     /**
116      * @deprecated since 6.0. Use {@link #JcrConfigurationSource(info.magnolia.config.registry.Registry, String, org.apache.jackrabbit.commons.predicate.Predicate, info.magnolia.module.ModuleRegistry, info.magnolia.map2bean.Map2BeanTransformer)} instead.
117      */
118     @Deprecated
119     public JcrConfigurationSource(Registry<T> registry, String pathInModule, Predicate nodeFilter, Node2BeanProcessor node2BeanProcessor, ModuleRegistry moduleRegistry) throws IOException {
120         this(registry, pathInModule, nodeFilter, moduleRegistry, Components.getComponent(Map2BeanTransformer.class));
121     }
122 
123     @Override
124     public ConfigurationSourceType type() {
125         return ConfigurationSourceTypes.jcr;
126     }
127 
128     @Override
129     public void start() {
130         this.observingManager.start();
131     }
132 
133     protected void reload(List<Node> nodes) throws RepositoryException {
134         final List<DefinitionProvider<T>> providers = new ArrayList<>();
135 
136         for (Node node : nodes) {
137             collectProvidersFor(providers, node);
138         }
139 
140         this.registeredIds = registry.unregisterAndRegister(registeredIds, providers);
141     }
142 
143     protected void collectProvidersFor(final List<DefinitionProvider<T>> providers, Node node) throws RepositoryException {
144         NodeUtil.visit(node, new NodeVisitor() {
145             @Override
146             public void visit(Node parent) throws RepositoryException {
147                 for (Node configNode : NodeUtil.getNodes(parent, NodeTypes.ContentNode.NAME)) {
148                     if (checkNode(configNode)) {
149                         DefinitionProvider<T> provider = newProvider(configNode);
150                         if (provider != null) {
151                             providers.add(provider);
152                         }
153                     }
154                 }
155             }
156         }, new NodeTypePredicate(NodeTypes.Content.NAME, false));
157     }
158 
159     /**
160      * Returns true if the given node should be used to load a definition from it.
161      */
162     protected boolean checkNode(Node node) {
163         return nodeFilter.evaluate(node);
164     }
165 
166     protected DefinitionProvider<T> newProvider(Node configNode) throws RepositoryException {
167         final DefinitionProviderBuilder<T> builder = registry.newDefinitionProviderBuilder();
168         final DefinitionMetadataBuilder metadataBuilder = createMetadata(configNode);
169         builder.metadata(metadataBuilder);
170 
171         final Node2MapTransformer transformer = new Node2MapTransformer();
172 
173         final Map<String, Object> configMap = transformer.toMap(new ExtendingNodeWrapper(configNode));
174         final TransformationResult<T> transformationResult;
175 
176         Class definitionType = metadataBuilder.getType().baseClass();
177 
178         builder.rawView(new DefinitionRawViewMapWrapper(configMap));
179         // Override metadata name if needed
180         if (NamedDefinition.class.isAssignableFrom(definitionType)) {
181             final Object nameValue = configMap.get(NAME_PROPERTY);
182             if (nameValue instanceof String) {
183                 metadataBuilder.name((String) nameValue);
184             } else if (nameValue == null) {
185                 configMap.put(NAME_PROPERTY, metadataBuilder.getName());
186             }
187         }
188 
189         transformationResult = map2BeanTransformer.transform(configMap, definitionType);
190 
191         builder.metadata(metadataBuilder);
192 
193         final DefinitionProvider<T> definitionProvider = builder.buildFromTransformationResult(transformationResult);
194 
195         DefinitionProviderProblemLogger
196                 .withLoggingContext(log, true)
197                 .logProblems(definitionProvider);
198 
199         return definitionProvider;
200     }
201 
202     protected DefinitionMetadataBuilder createMetadata(Node configNode) throws RepositoryException {
203         final String fallbackDefinitionName = configNode.getName();
204 
205         final String path = configNode.getPath();
206         final String[] pathElements = path.split("/");
207         final String moduleName = pathElements[2];
208         final String relPath = StringUtils.removeStart(path, "/modules/" + moduleName + "/" + pathInModule + "/");
209 
210         return registry.newMetadataBuilder()
211                 .type(getRegistry().type())
212                 .name(fallbackDefinitionName)
213                 .module(moduleName)
214                 .location(path)
215                 .configurationSourceType(ConfigurationSourceTypes.jcr)
216                 .relativeLocation(relPath);
217     }
218 
219     protected Registry<T> getRegistry() {
220         return registry;
221     }
222 
223     /**
224      * {@link Map2BeanTransformer} extension capable of transformation of JCR node to a mere map.
225      */
226     final static class Node2MapTransformer extends Node2BeanTransformerImpl {
227 
228         Node2MapTransformer() {
229             super(new PreConfiguredBeanUtils());
230         }
231 
232         Map<String, Object> toMap(Node node) throws RepositoryException {
233 
234             Map<String, Object> map = new LinkedHashMap<>();
235             PropertyIterator it = getProperties(node);
236             while (it.hasNext()) {
237                 Property p = it.nextProperty();
238                 Object val = p.isMultiple() ? Arrays.stream(p.getValues()).map(PropertyUtil::getValueObject).collect(Collectors.toList()) : PropertyUtil.getValueObject(p.getValue());
239                 if (val != null) {
240                     map.put(p.getName(), val);
241                 }
242             }
243             final NodeIterator children = getChildren(node);
244 
245             while (children.hasNext()) {
246                 Node childNode = (Node) children.next();
247                 // in case the the class can not get resolved we can use now
248                 // the parent bean to resolve the class
249 
250                 Object childBean = toMap(childNode);
251 
252                 // can be null if forceCreation is true
253                 if (childBean != null) {
254                     String name = childNode.getName();
255                     try {
256                         if (childNode.getIndex() > 1) {
257                             name += childNode.getIndex();
258                         }
259                     } catch (RepositoryException e) {
260                         log.error("can't read index of the node [{}]", childNode, e);
261                     }
262                     map.put(name, childBean);
263                 }
264             }
265 
266             return map;
267         }
268     }
269 }