View Javadoc
1   /**
2    * This file Copyright (c) 2017 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.rest.delivery.jcr.decorator;
35  
36  import info.magnolia.jcr.decoration.ContentDecoratorNodeWrapper;
37  import info.magnolia.jcr.util.NodeUtil;
38  import info.magnolia.objectfactory.ComponentProvider;
39  import info.magnolia.objectfactory.Components;
40  import info.magnolia.rest.delivery.jcr.filter.FilteringContentDecoratorBuilder;
41  import info.magnolia.rest.reference.ReferenceDefinition;
42  import info.magnolia.rest.reference.ReferenceResolver;
43  import info.magnolia.rest.reference.ReferenceResolverDefinition;
44  
45  import java.util.ArrayList;
46  import java.util.Collection;
47  import java.util.HashMap;
48  import java.util.Iterator;
49  import java.util.List;
50  import java.util.Map;
51  import java.util.Optional;
52  import java.util.stream.Collectors;
53  
54  import javax.jcr.Item;
55  import javax.jcr.Node;
56  import javax.jcr.NodeIterator;
57  import javax.jcr.Property;
58  import javax.jcr.PropertyIterator;
59  import javax.jcr.RepositoryException;
60  
61  import org.apache.jackrabbit.commons.ItemNameMatcher;
62  import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
63  import org.apache.jackrabbit.commons.iterator.PropertyIteratorAdapter;
64  import org.apache.jackrabbit.spi.commons.iterator.Iterators;
65  
66  import com.google.common.collect.Lists;
67  
68  /**
69   * A Node wrapper implementation which knows about expanding reference properties.
70   */
71  public class ReferenceExpandWrapper extends ContentDecoratorNodeWrapper {
72  
73      private final Map<String, Item> resolvedItems = new HashMap<>();
74      private final FilteringContentDecoratorBuilder expandDecorators;
75      private final ComponentProvider componentProvider = Components.getComponentProvider();
76  
77      public ReferenceExpandWrapper(Node wrapped, List<ReferenceDefinition> references, FilteringContentDecoratorBuilder expandDecorators) {
78          super(wrapped, new ReferenceExpandDecorator(references, expandDecorators));
79          this.expandDecorators = expandDecorators;
80          try {
81              PropertyIterator properties = wrapped.getProperties();
82              while (properties.hasNext()) {
83                  Property property = properties.nextProperty();
84  
85                  Optional<Node> resolvedItem = resolveProperty(property, references);
86  
87                  if (resolvedItem.isPresent()) {
88                      resolvedItems.put(property.getName(), resolvedItem.get());
89                  }
90              }
91          } catch (RepositoryException e) {
92              throw new RuntimeException("Can't wrap node [" + wrapped + "]", e);
93          }
94      }
95  
96      private Optional<Node> resolveProperty(Property property, List<ReferenceDefinition> references) throws RepositoryException {
97          for (ReferenceDefinition definition : references) {
98              boolean matchNodeType = definition.getNodeType() == null || definition.getNodeType() != null && NodeUtil.isNodeType(wrapped, definition.getNodeType());
99              boolean matchItemName = definition.getPropertyName() == null || property.getName().matches(definition.getPropertyName());
100             if (matchNodeType && matchItemName) {
101                 Optional<Node> itemOptional = resolveReference(property, definition);
102                 if (itemOptional.isPresent()) {
103                     Node resolvedNode = itemOptional.get();
104                     return Optional.of(new AdditionNodeWrapper(resolvedNode, property.getName()));
105                 }
106             }
107         }
108 
109         return Optional.empty();
110     }
111 
112     private Optional<Node> resolveReference(Property property, ReferenceDefinition definition) throws RepositoryException {
113         ReferenceResolverDefinition resolverDefinition = definition.getReferenceResolver();
114         if (resolverDefinition != null && resolverDefinition.getImplementationClass() != null) {
115             ReferenceResolver referenceResolver = componentProvider.newInstance(resolverDefinition.getImplementationClass(), resolverDefinition);
116             Optional resolved = referenceResolver.resolve(property);
117             if (resolved.isPresent() && resolved.get() instanceof Node) {
118                 return resolved;
119             }
120         }
121 
122         return Optional.empty();
123     }
124 
125     @Override
126     public boolean hasProperties() throws RepositoryException {
127         boolean hasProperties = resolvedItems.values().stream()
128                 .anyMatch(item -> !item.isNode());
129         return hasProperties || super.hasProperties();
130     }
131 
132     @Override
133     public boolean hasProperty(String relPath) throws RepositoryException {
134         boolean hasProperties = resolvedItems.entrySet().stream()
135                 .anyMatch(entry -> !entry.getValue().isNode() && entry.getKey().equals(relPath));
136         return hasProperties || super.hasProperty(relPath);
137     }
138 
139     @Override
140     public PropertyIterator getProperties() throws RepositoryException {
141         return getProperties("*");
142     }
143 
144     @Override
145     public PropertyIterator getProperties(String namePattern) throws RepositoryException {
146         return getProperties(new String[] {namePattern});
147     }
148 
149     @Override
150     public PropertyIterator getProperties(String[] nameGlobs) throws RepositoryException {
151         Iterator properties = Iterators.filterIterator(Iterators.properties(super.getProperties(nameGlobs)), property -> {
152             try {
153                 return !resolvedItems.containsKey(property.getName());
154             } catch (RepositoryException e) {
155                 return false;
156             }
157         });
158 
159         List<Property> merged = Lists.newArrayList(properties);
160         List<Property> resolvedProperties = resolvedItems.entrySet().stream()
161                 .filter(entry -> !entry.getValue().isNode() && ItemNameMatcher.matches(entry.getKey(), nameGlobs))
162                 .map(entry -> (Property) entry.getValue())
163                 .collect(Collectors.toList());
164         merged.addAll(resolvedProperties);
165 
166         return new PropertyIteratorAdapter(merged);
167     }
168 
169     @Override
170     public boolean hasNodes() throws RepositoryException {
171         boolean hasNodes = resolvedItems.values().stream()
172                 .anyMatch(Item::isNode);
173         return hasNodes || super.hasNodes();
174     }
175 
176     @Override
177     public boolean hasNode(String relPath) throws RepositoryException {
178         boolean hasNodes = resolvedItems.entrySet().stream()
179                 .anyMatch(entry ->entry.getValue().isNode() && entry.getKey().equals(relPath) );
180         return hasNodes || super.hasNode(relPath);
181     }
182 
183     @Override
184     public Node getNode(String relPath) throws RepositoryException {
185         Optional<Item> itemOptional = resolvedItems.entrySet().stream()
186                 .filter(entry -> entry.getValue().isNode() && entry.getKey().equals(relPath))
187                 .findFirst()
188                 .map(Map.Entry::getValue);
189 
190         if (itemOptional.isPresent()) {
191             return (Node) itemOptional.get();
192         }
193 
194         return super.getNode(relPath);
195     }
196 
197     @Override
198     public NodeIterator getNodes() throws RepositoryException {
199         return getNodes("*");
200     }
201 
202     @Override
203     public NodeIterator getNodes(String namePattern) throws RepositoryException {
204         return getNodes(new String[] {namePattern});
205     }
206 
207     @Override
208     public NodeIterator getNodes(String[] nameGlobs) throws RepositoryException {
209         Collection<Node> children = NodeUtil.getCollectionFromNodeIterator(getWrappedNode().getNodes(nameGlobs));
210 
211         List<Node> merged = new ArrayList<>(children);
212         List<Node> resolvedNodes = resolvedItems.entrySet().stream()
213                 .filter(entry -> ItemNameMatcher.matches(entry.getKey(), nameGlobs) && entry.getValue().isNode())
214                 .map(entry -> (Node) entry.getValue())
215                 .collect(Collectors.toList());
216         merged.addAll(resolvedNodes);
217 
218         return wrapNodeIterator(new NodeIteratorAdapter(merged));
219     }
220 
221     @Override
222     protected NodeIterator wrapNodeIterator(NodeIterator nodeIterator) {
223         if (expandDecorators != null) {
224             return expandDecorators.wrapNodeIterator(nodeIterator);
225         }
226 
227         return nodeIterator;
228     }
229 
230     @Override
231     protected Node wrapNode(Node node) {
232         if (expandDecorators != null) {
233             return expandDecorators.wrapNode(node);
234         }
235 
236         return node;
237     }
238 }