View Javadoc

1   /**
2    * This file Copyright (c) 2010-2011 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.cms.util;
35  
36  import java.util.Collection;
37  import java.util.Comparator;
38  import java.util.LinkedHashMap;
39  import java.util.SortedMap;
40  import java.util.TreeMap;
41  
42  import javax.jcr.RepositoryException;
43  import org.apache.commons.lang.StringUtils;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  import info.magnolia.cms.core.AbstractContent;
48  import info.magnolia.cms.core.Content;
49  import info.magnolia.cms.core.DefaultNodeData;
50  import info.magnolia.cms.core.NodeData;
51  
52  
53  /**
54   * This wrapper allows extending other nodes (mainly useful to extend configurations). A content node can define
55   * a nodeData with the name 'extends'. Its value is either an absolute or relative path. The merge is then performed as follows:
56   *
57   * <ul>
58   * <li>nodeDatas are merged and values are overwritten
59   * <li>sub content nodes are merged, the original order is guaranteed, new nodes are added at the
60   * end of the list
61   * </ul>
62   *
63   * The mechanism supports multiple inheritances as such:
64   * <ul>
65   * <li>the node the current node inherits from can again extend a node
66   * <li>nodes laying deeper in the hierarchy can extend an other node
67   * </ul>
68   *
69   * @author pbaerfuss
70   * @version $Id$
71   * @see InheritanceContentWrapper a class supporting content inheritance.
72   */
73  public class ExtendingContentWrapper extends ContentWrapper {
74  
75      private static final String EXTENDING_NODE_DATA = "extends";
76      private static final String EXTENDING_NODE_DATA_OVERRIDE = "override";
77  
78      private boolean extending;
79  
80      private Content extendedContent;
81      
82      private static final Logger log = LoggerFactory.getLogger(ExtendingContentWrapper.class);
83      
84      //This is just for test purposes. Use ExtendingContentWrapper(Content wrappedContent) as was here before.
85      ExtendingContentWrapper(Content wrappedContent, boolean failOnError) {
86          super(wrappedContent);
87          try {
88              extending = getWrappedContent().hasNodeData(EXTENDING_NODE_DATA);
89              if (extending) {
90                  NodeData extendingNodeData = getWrappedContent().getNodeData(EXTENDING_NODE_DATA);
91  
92                  // check if override is not forced
93                  String extendedNode = extendingNodeData.getString();
94  
95                  Content parent = extendingNodeData.getParent();
96                  boolean absolute = true;
97                  
98                  if(!extendedNode.isEmpty()){
99                      if(!extendedNode.startsWith("/")){ 
100                         if(parent.hasContent(extendedNode)){
101                             extendedNode = parent.getContent(extendedNode).getHandle();
102                         }else{
103                             absolute = false;
104                         }
105                     }
106                 }
107                 
108                 if (StringUtils.isBlank(extendedNode)) {
109                     // there is nothing to do, extending node is not defined ... probably caught in middle of config
110                     extending = false;
111                 } else if (absolute && !getWrappedContent().getHierarchyManager().isExist(extendedNode)){
112                     String message = "Can't find referenced node for value: " + wrappedContent;
113                     log.error(message);
114                     if (failOnError){
115                         throw new RuntimeException(message);
116                     }
117                     extending = false;
118                 } else if (!absolute && !parent.hasContent(extendedNode)){
119                     String message = "Can't find referenced node for value: " + wrappedContent;
120                     log.error(message);
121                     if (failOnError){
122                         throw new RuntimeException(message);
123                     }
124                     extending = false;
125                 } else if (EXTENDING_NODE_DATA_OVERRIDE.equals(extendedNode)) {
126                     extending = false;
127                 } else {
128                     // support multiple inheritance
129                     extendedContent = wrapIfNeeded(extendingNodeData.getReferencedContent());
130                 }
131             }
132         }
133         catch (RepositoryException e) {
134             throw new RuntimeException("Can't wrap node [" + wrappedContent + "]", e);
135         }
136     }
137 
138     public ExtendingContentWrapper(Content wrappedContent) {
139         this(wrappedContent, false);
140     }
141 
142     /**
143      * Does not support the extends nodedata but chains the two nodes directly. Each node is
144      * wrapped internally to ensure that each of them support the extends nodedata for themselves.
145      */
146     protected ExtendingContentWrapper(Content wrappedContent, Content extendedContent) {
147         super(wrapIfNeeded(wrappedContent));
148         extending = true;
149         try {
150             if (getWrappedContent().hasNodeData(EXTENDING_NODE_DATA)) {
151                 NodeData extendingNodeData = getWrappedContent().getNodeData(EXTENDING_NODE_DATA);
152 
153                 // check if override is not forced
154                 extending = !EXTENDING_NODE_DATA_OVERRIDE.equals(extendingNodeData.getString());
155             }
156         }
157         catch (RepositoryException e) {
158             throw new RuntimeException("Can't determine extends point for node [" + wrappedContent + "]", e);
159         }
160         // might extend further more
161         this.extendedContent = wrapIfNeeded(extendedContent);
162     }
163 
164     private static Content wrapIfNeeded(Content content) {
165         if (content instanceof ExtendingContentWrapper) {
166             return content;
167         }
168         return new ExtendingContentWrapper(content);
169     }
170 
171     public boolean isExtending() {
172         return this.extending;
173     }
174 
175     @Override
176     public Content getWrappedContent() {
177         Content wrapped = super.getWrappedContent();
178         if (wrapped instanceof ExtendingContentWrapper) {
179             ExtendingContentWrapper wrappedECW = (ExtendingContentWrapper) wrapped;
180             if (!wrappedECW.extending) {
181                 // wrapped but not extending ==> should not be wrapped in the first place but decision is made too late - in init<> of the ECW
182                 return ((ExtendingContentWrapper) wrapped).getWrappedContent();
183             }
184         }
185         return super.getWrappedContent();
186     }
187     @Override
188     public boolean hasContent(String name) throws RepositoryException {
189         if (getWrappedContent().hasContent(name)) {
190             return true;
191         }
192         else if (extending && extendedContent.hasContent(name)) {
193             return true;
194         }
195         return false;
196     }
197 
198     @Override
199     public Content getContent(String name) throws RepositoryException {
200         Content content;
201         if (getWrappedContent().hasContent(name)) {
202             content = getWrappedContent().getContent(name);
203         }
204         else if (extending && extendedContent.hasContent(name)) {
205             content = extendedContent.getContent(name);
206         }
207         else {
208             // this will throw a PathNotFoundException
209             content = getWrappedContent().getContent(name);
210         }
211         return wrap(content);
212     }
213 
214     @Override
215     public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) {
216         Collection<Content> directChildren = ((AbstractContent)getWrappedContent()).getChildren(filter, namePattern, orderCriteria);
217         if (extending) {
218             Collection<Content> inheritedChildren = ((AbstractContent)extendedContent).getChildren(filter, namePattern, orderCriteria);
219             // keep order, add new elements at the end of the collection
220             LinkedHashMap<String, Content> merged = new LinkedHashMap<String, Content>();
221             for (Content content : inheritedChildren) {
222                 merged.put(content.getName(), content);
223             }
224             for (Content content : directChildren) {
225                 merged.put(content.getName(), content);
226             }
227             return wrapContentNodes(merged.values());
228         }
229         else {
230             return wrapContentNodes(directChildren);
231         }
232     }
233 
234     @Override
235     public NodeData getNodeData(String name) {
236         if (EXTENDING_NODE_DATA.equals(name)) {
237             return new DefaultNodeData(extendedContent, name) {
238                 @Override
239                 public boolean isExist() {
240                     return false;
241                 }
242 
243                 @Override
244                 public void save() throws RepositoryException {
245                     // do nothing
246                 }
247             };
248         }
249         try {
250             if (getWrappedContent().hasNodeData(name)) {
251                 return wrap(getWrappedContent().getNodeData(name));
252             }
253             else if (extending && extendedContent.hasNodeData(name)) {
254                 return wrap(extendedContent.getNodeData(name));
255             }
256             else {
257                 return wrap(getWrappedContent().getNodeData(name));
258             }
259         }
260         catch (RepositoryException e) {
261             throw new RuntimeException("Can't read nodedata from extended node [" + extendedContent + "]", e);
262         }
263     }
264 
265     @Override
266     public boolean hasNodeData(String name) throws RepositoryException {
267         if (EXTENDING_NODE_DATA.equals(name)) {
268             return false;
269         }
270         return super.hasNodeData(name);
271     }
272 
273     @Override
274     public Collection<NodeData> getNodeDataCollection() {
275         final Content wrapped = getWrappedContent();
276         Collection<NodeData> directChildren = wrapped.getNodeDataCollection();
277         try {
278             if (wrapped.hasNodeData(EXTENDING_NODE_DATA)) {
279                 for (NodeData child : directChildren) {
280                     if (EXTENDING_NODE_DATA.equals(child.getName())) {
281                         directChildren.remove(child);
282                         break;
283                     }
284                 }
285             }
286         } catch (RepositoryException e) {
287             throw new RuntimeException("Can't read nodedata collection from node [" + wrapped.getHandle() + "]", e);
288         }
289         if (extending) {
290             Collection<NodeData> inheritedChildren = extendedContent.getNodeDataCollection();
291             // sort by name
292             SortedMap<String, NodeData> merged = new TreeMap<String, NodeData>();
293             for (NodeData nodeData : inheritedChildren) {
294                 merged.put(nodeData.getName(), nodeData);
295             }
296             for (NodeData nodeData : directChildren) {
297                 merged.put(nodeData.getName(), nodeData);
298             }
299             return wrapNodeDatas(merged.values());
300         }
301         else {
302             return wrapNodeDatas(directChildren);
303         }
304     }
305 
306     @Override
307     protected Content wrap(Content node) {
308         // get the same subnode of the extended content
309         try {
310             if (extending && extendedContent.hasContent(node.getName())) {
311                 // FIXME we have to calculate the relative path
312                 Content extendedSubContent = extendedContent.getContent(node.getName());
313                 return new ExtendingContentWrapper(node, extendedSubContent);
314             }
315         }
316         catch (RepositoryException e) {
317             throw new RuntimeException("Can't wrap " + node, e);
318         }
319         return wrapIfNeeded(node);
320     }
321 
322 }