View Javadoc

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