View Javadoc

1   /**
2    * This file Copyright (c) 2010-2010 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  
44  import info.magnolia.cms.core.AbstractContent;
45  import info.magnolia.cms.core.Content;
46  import info.magnolia.cms.core.NodeData;
47  
48  
49  /**
50   * This wrapper allows extending other nodes (mainly useful to extend configurations). A content node can define
51   * a nodedata with the name 'extends'. Its value is either an absolute or relative path. The merge is then performed as follows:
52   *
53   * <ul>
54   * <li>node datas are merged and values are overwritten
55   * <li>sub content nodes are merged, the original order is guaranteed, new nodes are added at the
56   * end of the list
57   * </ul>
58   *
59   * The mechanism supports multiple inheritances as such:
60   * <ul>
61   * <li>the node the current node inherits from can again extend a node
62   * <li>nodes laying deeper in the hierarchy can extend an other node
63   * </ul>
64   *
65   * @author pbaerfuss
66   * @version $Id$
67   * @see InheritanceContentWrapper a class supporting content inheritance.
68   */
69  public class ExtendingContentWrapper extends ContentWrapper {
70  
71      private static final String EXTENDING_NODE_DATA = "extends";
72  
73      private boolean extending;
74  
75      private Content extendedContent;
76  
77      public ExtendingContentWrapper(Content wrappedContent) {
78          super(wrappedContent);
79          try {
80              extending = getWrappedContent().hasNodeData(EXTENDING_NODE_DATA);
81              if (extending) {
82                  // support multiple inheritance
83                  extendedContent = wrapIfNeeded(getWrappedContent().getNodeData(EXTENDING_NODE_DATA).getReferencedContent());
84              }
85          }
86          catch (RepositoryException e) {
87              throw new RuntimeException("Can't wrap node [" + wrappedContent + "]", e);
88          }
89      }
90  
91      /**
92       * Does not support the extends nodedata but chains the two nodes directly. Each node is
93       * wrapped internally to ensure that each of them support the extends nodedata for themselves.
94       */
95      protected ExtendingContentWrapper(Content wrappedContent, Content extendedContent) {
96          super(wrapIfNeeded(wrappedContent));
97          this.extending = true;
98          // might extend further more
99          this.extendedContent = wrapIfNeeded(extendedContent);
100     }
101 
102     private static Content wrapIfNeeded(Content content) {
103         if (content instanceof ExtendingContentWrapper) {
104             return content;
105         }
106         return new ExtendingContentWrapper(content);
107     }
108 
109     public boolean isExtending() {
110         return this.extending;
111     }
112 
113     @Override
114     public boolean hasContent(String name) throws RepositoryException {
115         if (getWrappedContent().hasContent(name)) {
116             return true;
117         }
118         else if (extending && extendedContent.hasContent(name)) {
119             return true;
120         }
121         return false;
122     }
123 
124     @Override
125     public Content getContent(String name) throws RepositoryException {
126         Content content;
127         if (getWrappedContent().hasContent(name)) {
128             content = getWrappedContent().getContent(name);
129         }
130         else if (extending && extendedContent.hasContent(name)) {
131             content = extendedContent.getContent(name);
132         }
133         else {
134             // this will throw a PathNotFoundException
135             content = getWrappedContent().getContent(name);
136         }
137         return wrap(content);
138     }
139 
140     @Override
141     public Collection<Content> getChildren(ContentFilter filter, String namePattern, Comparator<Content> orderCriteria) {
142         Collection<Content> directChildren = ((AbstractContent)getWrappedContent()).getChildren(filter, namePattern, orderCriteria);
143         if (extending) {
144             Collection<Content> inheritedChildren = ((AbstractContent)extendedContent).getChildren(filter, namePattern, orderCriteria);
145             // keep order, add new elements at the end of the collection
146             LinkedHashMap<String, Content> merged = new LinkedHashMap<String, Content>();
147             for (Content content : inheritedChildren) {
148                 merged.put(content.getName(), content);
149             }
150             for (Content content : directChildren) {
151                 merged.put(content.getName(), content);
152             }
153             return wrapContentNodes(merged.values());
154         }
155         else {
156             return wrapContentNodes(directChildren);
157         }
158     }
159 
160     @Override
161     public NodeData getNodeData(String name) {
162         try {
163             if (getWrappedContent().hasNodeData(name)) {
164                 return wrap(getWrappedContent().getNodeData(name));
165             }
166             else if (extending && extendedContent.hasNodeData(name)) {
167                 return wrap(extendedContent.getNodeData(name));
168             }
169             else {
170                 return wrap(getWrappedContent().getNodeData(name));
171             }
172         }
173         catch (RepositoryException e) {
174             throw new RuntimeException("Can't read nodedata from extended node [" + extendedContent + "]", e);
175         }
176     }
177 
178     @Override
179     public Collection<NodeData> getNodeDataCollection() {
180         Collection<NodeData> directChildren = getWrappedContent().getNodeDataCollection();
181         if (extending) {
182             Collection<NodeData> inheritedChildren = extendedContent.getNodeDataCollection();
183             // sort by name
184             SortedMap<String, NodeData> merged = new TreeMap<String, NodeData>();
185             for (NodeData nodeData : inheritedChildren) {
186                 merged.put(nodeData.getName(), nodeData);
187             }
188             for (NodeData nodeData : directChildren) {
189                 merged.put(nodeData.getName(), nodeData);
190             }
191             return wrapNodeDatas(merged.values());
192         }
193         else {
194             return wrapNodeDatas(directChildren);
195         }
196     }
197 
198     protected Content wrap(Content node) {
199         // get the same subnode of the extended content
200         try {
201             if (extending && extendedContent.hasContent(node.getName())) {
202                 // FIXME we have to calculate the relative path
203                 Content extendedSubContent = extendedContent.getContent(node.getName());
204                 return new ExtendingContentWrapper(node, extendedSubContent);
205             }
206         }
207         catch (RepositoryException e) {
208             throw new RuntimeException("Can't wrap " + node, e);
209         }
210         return new ExtendingContentWrapper(node);
211     }
212 
213 }