View Javadoc

1   /**
2    * This file Copyright (c) 2003-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.module.groovy.support.nodes;
35  
36  import groovy.lang.DelegatingMetaClass;
37  import groovy.lang.GroovySystem;
38  import groovy.lang.MetaClass;
39  import groovy.xml.QName;
40  import info.magnolia.cms.core.Content;
41  import info.magnolia.cms.core.ItemType;
42  import info.magnolia.cms.core.NodeData;
43  import info.magnolia.cms.security.AccessDeniedException;
44  import info.magnolia.cms.util.ContentWrapper;
45  
46  import java.math.BigDecimal;
47  import java.util.ArrayList;
48  import java.util.Collection;
49  import java.util.Iterator;
50  import java.util.List;
51  
52  import javax.jcr.PathNotFoundException;
53  import javax.jcr.PropertyType;
54  import javax.jcr.RepositoryException;
55  import javax.jcr.Value;
56  import javax.jcr.ValueFormatException;
57  
58  import org.apache.commons.collections.IteratorUtils;
59  import org.apache.commons.lang.StringUtils;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  
64  /**
65   * A special <em>groovish</em> implementation of Magnolia's {@link ContentWrapper}.
66   * This makes it possible, for example, navigating the nodes in a Magnolia repository
67   * with a <code>. (dot)</code> notation and access their properties with <code>.</code> or <code>.@</code>
68   * notation much like it happens when using the result of parsing an xml with groovy's
69   * {@link XmlSlurper}. For example, here is how, in a groovy
70   * script, one could navigate to and print the node data named <em>abstract</em>:
71   * <pre>
72   * hm = ctx.getHierarchyManager('website')
73   * node = hm.getContent(hm, '/demo-project')
74   * println node.about.history.abstract
75   * </pre>
76   * The above example can also be made more groovy-like by using the <code>@</code> notation to access the attribute
77   * <pre>
78   * node.about.history.@abstract
79   * </pre>
80   * As <code>node</code> is also a {@link Content}, you can call any of its
81   * methods. E.g.
82   * <pre>
83   * println node.metaData.template
84   * println node.about.children.title
85   * println node.about.parent <strong>(this can return null)</strong>
86   * </pre>
87   * <strong>IMPORTANT:</strong> The <code>.children</code> shortcut, unlikely {@link Content#getChildren()}, <strong>always returns {@link ItemType#CONTENT}s AND {@link ItemType#CONTENTNODE}s </strong>.
88   * If you only need either type, then call directly one of the {@link Content#getChildren()} methods on the parent node.
89   *<br/>
90   *<br/>
91  
92   * If you want to look at the <i>attributes</i> or <i>data</i> for a certain node you can simply call
93   * <pre>
94   * println node.about.nodeData
95   * </pre>
96   *
97   * It is also possible to assign values to node data or create new ones. E.g.
98   * <pre>
99   * node.boo = 3.14d
100  * node.coo = true
101  * node.foo = 'some text'
102  * node.doo = 100
103  * </pre>
104  *
105  * will assign the values on the right hand side to the node data on left hand side. Should those not exist, they will be automatically created.
106  * Furthermore, the correct type will be detected based on the value assigned (i.e. Boolean, String, Long or Double).
107  * <br/>
108  * <br/>
109  * <strong>IMPORTANT:</strong> All the above assignments will be in-memory only unless explicitly persisted via a call to <code>save()</code> on a parent node.
110  *
111  * @author fgrilli
112  * @version $Id$
113  */
114 public class MgnlGroovyNode extends ContentWrapper {
115 
116     static {
117         // wrap the standard MetaClass with the delegate
118         setMetaClass(GroovySystem.getMetaClassRegistry().getMetaClass(MgnlGroovyNode.class), MgnlGroovyNode.class);
119     }
120 
121     protected static final Logger log = LoggerFactory.getLogger(MgnlGroovyNode.class);
122 
123     private String name;
124 
125     public MgnlGroovyNode(Content content) {
126         super(content);
127         this.name = content.getName();
128     }
129 
130     protected static void setMetaClass(final MetaClass metaClass, Class nodeClass) {
131         final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {
132 
133             public Object getAttribute(final Object object, final String attribute) {
134                 MgnlGroovyNode n = (MgnlGroovyNode) object;
135                 return n.get("@" + attribute);
136             }
137 
138             public void setAttribute(final Object object, final String attribute, final Object newValue) {
139                 MgnlGroovyNode n = (MgnlGroovyNode) object;
140                 try {
141                     n.setNodeData(attribute, newValue);
142                 }
143                 catch (AccessDeniedException e) {
144                     log.error(e.getMessage());
145                 }
146                 catch (PathNotFoundException e) {
147                     log.error(e.getMessage());
148                 }
149                 catch (RepositoryException e) {
150                     log.error(e.getMessage());
151                 }
152             }
153 
154             public Object getProperty(Object object, String property) {
155                 if (object instanceof MgnlGroovyNode) {
156                     MgnlGroovyNode n = (MgnlGroovyNode) object;
157                     return n.get(property);
158                 }
159                 return super.getProperty(object, property);
160             }
161 
162             public void setProperty(Object object, String property, Object newValue) {
163                 String attribute = null;
164                 if (property.startsWith("@")) {
165                     attribute = property.substring(1);
166                 } else {
167                     attribute = property;
168                 }
169                 MgnlGroovyNode n = (MgnlGroovyNode) object;
170                 try {
171                     //else you get java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
172                     if(newValue instanceof Integer){
173                         newValue = ((Integer) newValue).longValue();
174                     }
175                     n.setNodeData(attribute, newValue);
176                 }
177                 catch (AccessDeniedException e) {
178                     log.error(e.getMessage());
179                 }
180                 catch (PathNotFoundException e) {
181                     log.error(e.getMessage());
182                 }
183                 catch (RepositoryException e) {
184                     log.error(e.getMessage());
185                 }
186             }
187         };
188         GroovySystem.getMetaClassRegistry().setMetaClass(nodeClass, newMetaClass);
189     }
190 
191     public Iterator<Content> iterator(){
192         return getChainedChildrenIterator();
193     }
194 
195     public String name() {
196         return name;
197     }
198 
199     /**
200      * Provides lookup of elements by non-namespaced name.
201      * @param key the name (or shortcut key) of the node(s) of interest
202      * @return the nodes which match key
203      */
204     public Object get(String key) {
205         if (key != null && key.charAt(0) == '@') {
206             String attributeName = key.substring(1);
207             return getNodeDataValue(attributeName);
208         }
209         if ("parent".equals(key)) {
210             try {
211                 return getParent();
212             }
213             catch (RepositoryException e) {
214                 return null;
215             }
216         }
217         if ("children".equals(key)) {
218             Collection<Content> retVal = getChildren();
219             retVal.addAll(getChildren(ItemType.CONTENTNODE));
220             return retVal;
221         }
222         if("metaData".equals(key)){
223             return getMetaData();
224         }
225         if("nodeData".equals(key)){
226             return getNodeDataCollection();
227         }
228         Content retVal = getByName(key);
229         if(retVal != null){
230             return retVal;
231         }
232         //last attempt: lets assume the client requested for an attribute without the @ notation
233         return getNodeDataValue(key);
234     }
235 
236     @Override
237     protected Content wrap(Content node) {
238         return new MgnlGroovyNode(node);
239     }
240 
241     /**
242      * Returns  the value for the given attribute (nodeData).
243      * The Java types returned vary according to the underlying {@link PropertyType}.
244      * <ul>
245      * <li> {@link PropertyType#STRING} returns a {@link String}
246      * <li> {@link PropertyType#DATE} returns a {@link Calendar}
247      * <li> {@link PropertyType#DOUBLE} returns a {@link BigDecimal}
248      * <li> {@link PropertyType#LONG} returns a {@link BigDecimal}
249      * <li> {@link PropertyType#BINARY} returns the {@link BinaryNodeData} value.
250      * <li> All other property types will be returned as {@link String}(s)
251      * </ul>
252      */
253     protected Object getNodeDataValue(String attributeName) {
254         Collection<NodeData> data = getNodeDataCollection(attributeName);
255         Iterator<NodeData> it = data.iterator();
256         if(!it.hasNext()) {
257             return null;
258         }
259         NodeData nodeData = it.next();
260         Value propertyValue = nodeData.getValue();
261         Object value = null;
262         if (propertyValue != null) {
263             try {
264                 switch (propertyValue.getType()) {
265                     case PropertyType.STRING :
266                         value = propertyValue.getString();
267                         break;
268                     case PropertyType.BINARY :
269                         value = propertyValue;
270                         break;
271                     case PropertyType.DATE :
272                         value = propertyValue.getDate();
273                         break;
274                     case PropertyType.DOUBLE:
275                         value = BigDecimal.valueOf(propertyValue.getDouble());
276                         break;
277                     case  PropertyType.LONG:
278                         value = BigDecimal.valueOf(propertyValue.getLong());
279                         break;
280                     default :
281                         value = propertyValue.getString();
282                 }
283             }
284             catch (ValueFormatException e) {
285                 log.warn(e.getMessage());
286             }
287             catch (IllegalStateException e) {
288                 log.warn(e.getMessage());
289             }
290             catch (RepositoryException e) {
291                 log.warn(e.getMessage());
292             }
293         }
294         return value;
295     }
296 
297     /**
298      * Provides lookup of elements by QName.
299      * @param name the QName of interest
300      * @return the nodes matching name
301      * @deprecated This method is wrong and luckily unused by this module. Use {@link MgnlGroovyNode#getByName(String)} instead.
302      */
303     public List<Content> getAt(QName name) {
304         List<Content> answer = new ArrayList<Content>();
305         for (Iterator<Content> iter = getChainedChildrenIterator(); iter.hasNext();) {
306                 Content childNode = iter.next();
307                 Object childNodeName = childNode.getName();
308                 if (name.matches(childNodeName)) {
309                     answer.add(childNode);
310                 }
311         }
312         return answer;
313     }
314 
315     /**
316      * Provides lookup of elements by name. Also handles {@link QName}s
317      * @param name the name of interest
318      * @return the nodes matching name
319      */
320     protected Content getByName(String name) {
321         for (Iterator<Content> iter = getChainedChildrenIterator(); iter.hasNext();) {
322             Content childNode = iter.next();
323             Object childNodeName = childNode.getName();
324             if (childNodeName instanceof QName) {
325                 QName qn = (QName) childNodeName;
326                 if (qn.matches(name)) {
327                     return childNode;
328                 }
329             }
330             else if (name.equals(childNodeName)) {
331                 return childNode;
332             }
333         }
334         return null;
335     }
336 
337     public String toString() {
338         StringBuilder retVal = new StringBuilder();
339         Iterator<Content> iterator = getChainedChildrenIterator();
340         retVal.append("(+) " + name + "\n");
341         while (iterator.hasNext()) {
342             Content c = iterator.next();
343             if (hasMoreChildren(c)) {
344                 retVal.append("\t(+) ");
345             } else {
346                 retVal.append("\t(-) ");
347             }
348             retVal.append(c.getName() + "\n");
349         }
350 
351         if (!getNodeDataCollection().isEmpty()) {
352             for (NodeData nodeData : getNodeDataCollection()) {
353                 retVal.append("\t\t* " + nodeData.getName() + ": ["
354                         + StringUtils.abbreviate(nodeData.getString(), 80)
355                         + "]\n");
356             }
357 
358         }
359 
360         return retVal.toString();
361     }
362 
363     @SuppressWarnings("unchecked")
364     private Iterator<Content> getChainedChildrenIterator() {
365         return IteratorUtils.chainedIterator(getChildren().iterator(), getChildren(ItemType.CONTENTNODE).iterator());
366     }
367 
368     private boolean hasMoreChildren(Content c){
369         return c.hasChildren() || c.hasChildren(ItemType.CONTENTNODE.getSystemName());
370     }
371 
372     @Override
373     public int hashCode() {
374         final int prime = 31;
375         int result = 1;
376         result = prime * result + ((name == null) ? 0 : name.hashCode());
377         return result;
378     }
379 
380     @Override
381     public boolean equals(Object obj) {
382         if (this == obj)
383             return true;
384         if (obj == null)
385             return false;
386         if (getClass() != obj.getClass())
387             return false;
388         MgnlGroovyNode other = (MgnlGroovyNode) obj;
389         if (name == null) {
390             if (other.name != null)
391                 return false;
392         } else if (!getHandle().equals(other.getHandle()))
393             return false;
394         return true;
395     }
396 
397     /*TODO
398      * public void print(PrintWriter out) {
399        new MgnlGroovyNodePrinter(out).print(this);
400     }
401     */
402 
403 
404 }