View Javadoc
1   /**
2    * This file Copyright (c) 2014-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.jcr;
35  
36  import info.magnolia.cms.util.ExceptionUtil;
37  import info.magnolia.config.NamedDefinition;
38  import info.magnolia.config.registry.DefinitionMetadata;
39  import info.magnolia.config.registry.DefinitionMetadataBuilder;
40  import info.magnolia.config.registry.DefinitionProvider;
41  import info.magnolia.config.registry.DefinitionProviderBuilder;
42  import info.magnolia.config.registry.DefinitionRawView;
43  import info.magnolia.config.registry.Registry;
44  import info.magnolia.config.source.ConfigurationSource;
45  import info.magnolia.config.source.ConfigurationSourceType;
46  import info.magnolia.config.source.ConfigurationSourceTypes;
47  import info.magnolia.jcr.node2bean.Node2BeanException;
48  import info.magnolia.jcr.node2bean.Node2BeanProcessor;
49  import info.magnolia.jcr.node2bean.TransformationState;
50  import info.magnolia.jcr.node2bean.TypeDescriptor;
51  import info.magnolia.jcr.node2bean.TypeMapping;
52  import info.magnolia.jcr.node2bean.impl.Node2BeanTransformerImpl;
53  import info.magnolia.jcr.node2bean.impl.PreConfiguredBeanUtils;
54  import info.magnolia.jcr.predicate.NodeTypePredicate;
55  import info.magnolia.jcr.util.NodeTypes;
56  import info.magnolia.jcr.util.NodeUtil;
57  import info.magnolia.jcr.util.NodeVisitor;
58  import info.magnolia.module.ModuleRegistry;
59  import info.magnolia.objectfactory.ComponentProvider;
60  import info.magnolia.objectfactory.Components;
61  
62  import java.io.IOException;
63  import java.util.ArrayList;
64  import java.util.HashMap;
65  import java.util.HashSet;
66  import java.util.Iterator;
67  import java.util.LinkedList;
68  import java.util.List;
69  import java.util.Map;
70  import java.util.Set;
71  
72  import javax.jcr.Node;
73  import javax.jcr.RepositoryException;
74  
75  import org.apache.commons.lang3.StringUtils;
76  import org.apache.jackrabbit.commons.predicate.Predicate;
77  import org.slf4j.Logger;
78  import org.slf4j.LoggerFactory;
79  
80  /**
81   * Configuration source for JCR configuration.
82   *
83   * @param <T> type the source will provide
84   */
85  public class JcrConfigurationSource<T> implements ConfigurationSource {
86  
87      private static final Logger log = LoggerFactory.getLogger(JcrConfigurationSource.class);
88  
89      private final Registry<T> registry;
90      private final String pathInModule;
91      private final Predicate nodeFilter;
92      private final Node2BeanProcessor node2BeanProcessor;
93      private Set<DefinitionMetadata> registeredIds = new HashSet<>();
94      private final RegistryBasedObservingManager observingManager;
95  
96      public JcrConfigurationSource(Registry<T> registry, String pathInModule, Predicate nodeFilter,
97                                    Node2BeanProcessor node2BeanProcessor, ModuleRegistry moduleRegistry) throws IOException {
98          this.registry = registry;
99          this.pathInModule = pathInModule;
100         this.nodeFilter = nodeFilter;
101         this.node2BeanProcessor = node2BeanProcessor;
102 
103         this.observingManager = new RegistryBasedObservingManager(pathInModule, moduleRegistry, this);
104     }
105 
106     @Override
107     public ConfigurationSourceType type() {
108         return ConfigurationSourceTypes.jcr;
109     }
110 
111     @Override
112     public void start() {
113         this.observingManager.start();
114     }
115 
116     protected void reload(List<Node> nodes) throws RepositoryException {
117         final List<DefinitionProvider<T>> providers = new ArrayList<>();
118 
119         for (Node node : nodes) {
120             collectProvidersFor(providers, node);
121         }
122 
123         this.registeredIds = registry.unregisterAndRegister(registeredIds, providers);
124     }
125 
126     protected void collectProvidersFor(final List<DefinitionProvider<T>> providers, Node node) throws RepositoryException {
127         NodeUtil.visit(node, new NodeVisitor() {
128             @Override
129             public void visit(Node parent) throws RepositoryException {
130                 for (Node configNode : NodeUtil.getNodes(parent, NodeTypes.ContentNode.NAME)) {
131                     if (checkNode(configNode)) {
132                         DefinitionProvider<T> provider = newProvider(configNode);
133                         if (provider != null) {
134                             providers.add(provider);
135                         }
136                     }
137                 }
138             }
139         }, new NodeTypePredicate(NodeTypes.Content.NAME, false));
140     }
141 
142     /**
143      * Returns true if the given node should be used to load a definition from it.
144      */
145     protected boolean checkNode(Node node) {
146         return nodeFilter.evaluate(node);
147     }
148 
149     protected DefinitionProvider<T> newProvider(Node configNode) throws RepositoryException {
150         String nodePath = configNode.getPath();
151 
152         final DefinitionProviderBuilder<T> builder = DefinitionProviderBuilder.newBuilder();
153         final DefinitionMetadataBuilder metadataBuilder = createMetadata(configNode);
154         builder.metadata(metadataBuilder);
155 
156         try {
157             // Transform into the actual bean
158             MapPopulatingN2BTransformer transformer = new MapPopulatingN2BTransformer();
159             @SuppressWarnings("unchecked")
160             final T bean = (T) node2BeanProcessor.toBean(configNode, true, transformer, Components.getComponentProvider());
161             builder.definition(bean);
162 
163             // Override metadata name if needed
164             if (bean instanceof NamedDefinition) {
165                 final String definitionName = ((NamedDefinition) bean).getName();
166                 if (definitionName != null) {
167                     // Definition's name has priority
168                     metadataBuilder.name(definitionName);
169                 }
170             }
171 
172             // TODO raw view should be attached to builder before attempting any bean transformation
173             builder.rawView(transformer.getRawView().getSubRawView());
174 
175         } catch (Throwable t) {
176             // Here we're catching any exception, from node2BeanTransformer down to underlying potential errors (e.g. NoClassDefFoundError)
177             builder.addErrorMessage(ExceptionUtil.exceptionToWords(t));
178             log.warn("Problem while registering {} from {}: {}", getRegistry().type(), nodePath, ExceptionUtil.exceptionToWords(t), t);
179         }
180 
181         DefinitionProvider<T> provider = builder.build();
182         return provider;
183     }
184 
185     protected DefinitionMetadataBuilder createMetadata(Node configNode) throws RepositoryException {
186         final String fallbackDefinitionName = configNode.getName();
187 
188         final String path = configNode.getPath();
189         final String[] pathElements = path.split("/");
190         final String moduleName = pathElements[2];
191         final String relPath = StringUtils.removeStart(path, "/modules/" + moduleName + "/" + pathInModule + "/");
192 
193         return registry.newMetadataBuilder()
194                 .type(getRegistry().type())
195                 .name(fallbackDefinitionName)
196                 .module(moduleName)
197                 .location(path)
198                 .relativeLocation(relPath);
199     }
200 
201     protected Registry<T> getRegistry() {
202         return registry;
203     }
204 
205     /**
206      * Simple implementation of {@link DefinitionRawView}.
207      */
208     private static class DefinitionRawViewImpl implements DefinitionRawView {
209 
210         private List<Property> properties;
211 
212         public DefinitionRawViewImpl(List<Property> properties) {
213             this.properties = properties;
214         }
215 
216         @Override
217         public List<Property> properties() {
218             return properties;
219         }
220     }
221 
222     /**
223      * Populates a {@link DefinitionRawView} along with normal N2B transformation.
224      *
225      * @see #newProvider(javax.jcr.Node)
226      */
227     private class MapPopulatingN2BTransformer extends Node2BeanTransformerImpl {
228 
229         private Map<String, DefinitionRawView.Property> populatedProperties = new HashMap<>();
230 
231         private DefinitionRawView.Property rawView;
232 
233         public MapPopulatingN2BTransformer() {
234             super(new PreConfiguredBeanUtils());
235         }
236 
237         @Override
238         protected TypeDescriptor onResolveType(TypeMapping mapping, TransformationState state, TypeDescriptor resolvedType, ComponentProvider componentProvider) {
239             if (resolvedType == null && state.getLevel() == 1) {
240                 return mapping.getTypeDescriptor(getRegistry().type().baseClass());
241             }
242             return resolvedType;
243         }
244 
245         @Override
246         public void initBean(TransformationState state, Map values) throws Node2BeanException {
247             // Invoked almost at the end of N2B transformation
248             super.initBean(state, values);
249             // Create a new level of resulting map
250             final List<DefinitionRawView.Property> properties = new LinkedList<>();
251             try {
252                 final Iterator<Map.Entry<String, Object>> it = values.entrySet().iterator();
253 
254                 while (it.hasNext()) {
255                     final Map.Entry<String, Object> entry = it.next();
256                     // Replace the concrete objects with their tracked map representations
257                     if (this.populatedProperties.containsKey(entry.getKey())) {
258                         properties.add(this.populatedProperties.remove(entry.getKey()));
259                     } else {
260                         properties.add(DefinitionRawView.Property.simple(entry.getKey(), String.valueOf(entry.getValue())));
261                     }
262                 }
263 
264                 // Append erased class property
265                 if (state.getCurrentNode().hasProperty("class")) {
266                     properties.add(DefinitionRawView.Property.simple("class", state.getCurrentNode().getProperty("class").getString()));
267                 }
268 
269                 String name = state.getCurrentNode().getName();
270                 if (state.getCurrentType().isCollection() || state.getCurrentType().isMap() || state.getCurrentType().isArray()) {
271                     rawView = DefinitionRawView.Property.collection(name, properties);
272                 } else {
273                     rawView = DefinitionRawView.Property.subBean(name, new DefinitionRawViewImpl(properties));
274                 }
275                 // Track current object map representation
276                 this.populatedProperties.put(name, rawView);
277 
278             } catch (RepositoryException e) {
279 
280             }
281         }
282 
283         // Use this method to get the resulting map after transform
284         public DefinitionRawView.Property getRawView() {
285             return rawView;
286         }
287     }
288 }