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 = new RegistryBasedObservingManager(pathInModule, moduleRegistry, this);
109     }
110 
111     /**
112      * @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.
113      */
114     @Deprecated
115     public JcrConfigurationSource(Registry<T> registry, String pathInModule, Predicate nodeFilter, Node2BeanProcessor node2BeanProcessor, ModuleRegistry moduleRegistry) throws IOException {
116         this(registry, pathInModule, nodeFilter, moduleRegistry, Components.getComponent(Map2BeanTransformer.class));
117     }
118 
119     @Override
120     public ConfigurationSourceType type() {
121         return ConfigurationSourceTypes.jcr;
122     }
123 
124     @Override
125     public void start() {
126         this.observingManager.start();
127     }
128 
129     protected void reload(List<Node> nodes) throws RepositoryException {
130         final List<DefinitionProvider<T>> providers = new ArrayList<>();
131 
132         for (Node node : nodes) {
133             collectProvidersFor(providers, node);
134         }
135 
136         this.registeredIds = registry.unregisterAndRegister(registeredIds, providers);
137     }
138 
139     protected void collectProvidersFor(final List<DefinitionProvider<T>> providers, Node node) throws RepositoryException {
140         NodeUtil.visit(node, new NodeVisitor() {
141             @Override
142             public void visit(Node parent) throws RepositoryException {
143                 for (Node configNode : NodeUtil.getNodes(parent, NodeTypes.ContentNode.NAME)) {
144                     if (checkNode(configNode)) {
145                         DefinitionProvider<T> provider = newProvider(configNode);
146                         if (provider != null) {
147                             providers.add(provider);
148                         }
149                     }
150                 }
151             }
152         }, new NodeTypePredicate(NodeTypes.Content.NAME, false));
153     }
154 
155     /**
156      * Returns true if the given node should be used to load a definition from it.
157      */
158     protected boolean checkNode(Node node) {
159         return nodeFilter.evaluate(node);
160     }
161 
162     protected DefinitionProvider<T> newProvider(Node configNode) throws RepositoryException {
163         final DefinitionProviderBuilder<T> builder = registry.newDefinitionProviderBuilder();
164         final DefinitionMetadataBuilder metadataBuilder = createMetadata(configNode);
165 
166         final Node2MapTransformer transformer = new Node2MapTransformer();
167 
168         final Map<String, Object> configMap = transformer.toMap(new ExtendingNodeWrapper(configNode));
169         final TransformationResult<T> transformationResult;
170 
171         builder.rawView(new DefinitionRawViewMapWrapper(configMap));
172         // Override metadata name if needed
173         if (NamedDefinition.class.isAssignableFrom(getRegistry().type().baseClass())) {
174             final Object nameValue = configMap.get(NAME_PROPERTY);
175             if (nameValue instanceof String) {
176                 metadataBuilder.name((String) nameValue);
177             } else if (nameValue == null) {
178                 configMap.put(NAME_PROPERTY, metadataBuilder.getName());
179             }
180         }
181 
182         transformationResult = map2BeanTransformer.transform(configMap, getRegistry().type().baseClass());
183 
184         builder.metadata(metadataBuilder);
185 
186         final DefinitionProvider<T> definitionProvider = builder.buildFromTransformationResult(transformationResult);
187 
188         DefinitionProviderProblemLogger
189                 .withLoggingContext(log, true)
190                 .logProblems(definitionProvider);
191 
192         return definitionProvider;
193     }
194 
195     protected DefinitionMetadataBuilder createMetadata(Node configNode) throws RepositoryException {
196         final String fallbackDefinitionName = configNode.getName();
197 
198         final String path = configNode.getPath();
199         final String[] pathElements = path.split("/");
200         final String moduleName = pathElements[2];
201         final String relPath = StringUtils.removeStart(path, "/modules/" + moduleName + "/" + pathInModule + "/");
202 
203         return registry.newMetadataBuilder()
204                 .type(getRegistry().type())
205                 .name(fallbackDefinitionName)
206                 .module(moduleName)
207                 .location(path)
208                 .configurationSourceType(ConfigurationSourceTypes.jcr)
209                 .relativeLocation(relPath);
210     }
211 
212     protected Registry<T> getRegistry() {
213         return registry;
214     }
215 
216     /**
217      * {@link Map2BeanTransformer} extension capable of transformation of JCR node to a mere map.
218      */
219     final static class Node2MapTransformer extends Node2BeanTransformerImpl {
220 
221         Node2MapTransformer() {
222             super(new PreConfiguredBeanUtils());
223         }
224 
225         Map<String, Object> toMap(Node node) throws RepositoryException {
226 
227             Map<String, Object> map = new LinkedHashMap<>();
228             PropertyIterator it = getProperties(node);
229             while (it.hasNext()) {
230                 Property p = it.nextProperty();
231                 Object val = p.isMultiple() ? Arrays.stream(p.getValues()).map(PropertyUtil::getValueObject).collect(Collectors.toList()) : PropertyUtil.getValueObject(p.getValue());
232                 if (val != null) {
233                     map.put(p.getName(), val);
234                 }
235             }
236             final NodeIterator children = getChildren(node);
237 
238             while (children.hasNext()) {
239                 Node childNode = (Node) children.next();
240                 // in case the the class can not get resolved we can use now
241                 // the parent bean to resolve the class
242 
243                 Object childBean = toMap(childNode);
244 
245                 // can be null if forceCreation is true
246                 if (childBean != null) {
247                     String name = childNode.getName();
248                     try {
249                         if (childNode.getIndex() > 1) {
250                             name += childNode.getIndex();
251                         }
252                     } catch (RepositoryException e) {
253                         log.error("can't read index of the node [{}]", childNode, e);
254                     }
255                     map.put(name, childBean);
256                 }
257             }
258 
259             return map;
260         }
261     }
262 }