View Javadoc
1   /**
2    * This file Copyright (c) 2013-2015 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.security.AccessDeniedException;
41  import info.magnolia.jcr.RuntimeRepositoryException;
42  import info.magnolia.jcr.iterator.DelegatingNodeIterator;
43  import info.magnolia.jcr.util.MetaDataUtil;
44  import info.magnolia.jcr.util.NodeTypes;
45  import info.magnolia.jcr.util.NodeUtil;
46  import info.magnolia.jcr.util.PropertyUtil;
47  import info.magnolia.jcr.wrapper.DelegateNodeWrapper;
48  
49  import java.math.BigDecimal;
50  import java.util.ArrayList;
51  import java.util.List;
52  
53  import javax.jcr.Node;
54  import javax.jcr.NodeIterator;
55  import javax.jcr.PathNotFoundException;
56  import javax.jcr.Property;
57  import javax.jcr.PropertyIterator;
58  import javax.jcr.PropertyType;
59  import javax.jcr.RepositoryException;
60  import javax.jcr.Value;
61  import javax.jcr.ValueFormatException;
62  
63  import org.apache.commons.lang.StringUtils;
64  import org.apache.jackrabbit.commons.iterator.NodeIteratorAdapter;
65  import org.slf4j.Logger;
66  import org.slf4j.LoggerFactory;
67  
68  /**
69   * Implementation of wrapped Node object used in Groovy console context.
70   * This makes it possible, for example, navigating the nodes in a Magnolia repository
71   * 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}.
72   * For example, here is how, in a groovy script, one could navigate to and print the node data named <em>abstract</em>:
73   * 
74   * <pre>
75   * session = ctx.getJCRSession('website')
76   * node = session.getNode('/demo-project')
77   * println node.about.history.abstract
78   * </pre>
79   * 
80   * The above example can also be made more groovy-like by using the <code>@</code> notation to access the property
81   * 
82   * <pre>
83   * node.about.history.@abstract
84   * </pre>
85   * 
86   * As <code>node</code> is also a {@link Node}, you can call any of its
87   * methods. E.g.
88   * 
89   * <pre>
90   * println node.metaData.template
91   * println node.about.title
92   * println node.about.parent <strong>(this can return null)</strong>
93   * </pre>
94   * 
95   * <br/>
96   * If you want to iterate over <i>properties</i> for a certain node you can do for instance
97   * 
98   * <pre>
99   * node.about.properties.each {println it.name}
100  * </pre>
101  * 
102  * It is also possible to assign values to properties or create new ones. E.g.
103  * 
104  * <pre>
105  * node.foo = 3.14d
106  * node.bar = true
107  * node.baz = 'some text'
108  * node.qux = 100
109  * </pre>
110  * 
111  * 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.
112  * Furthermore, the correct type will be detected based on the value assigned (i.e. Boolean, String, Calendar, Long or Double). <br/>
113  * <br/>
114  * <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.
115  */
116 public class MgnlGroovyJCRNode extends DelegateNodeWrapper {
117 
118     static {
119         // wrap the standard MetaClass with the delegate
120         setMetaClass(GroovySystem.getMetaClassRegistry().getMetaClass(MgnlGroovyJCRNode.class), MgnlGroovyJCRNode.class);
121     }
122 
123     protected static final Logger log = LoggerFactory.getLogger(MgnlGroovyJCRNode.class);
124 
125     private String name;
126 
127     public MgnlGroovyJCRNode(Node node) {
128         super(node);
129         try {
130             this.name = "/".equals(node.getPath()) ? "/root" : getName();
131         } catch (RepositoryException e) {
132             throw new RuntimeRepositoryException(e);
133         }
134     }
135 
136     protected static void setMetaClass(final MetaClass metaClass, Class nodeClass) {
137         final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {
138 
139             @Override
140             public Object getAttribute(final Object object, final String attribute) {
141                 MgnlGroovyJCRNode n = (MgnlGroovyJCRNode) object;
142                 try {
143                     return n.get("@" + attribute);
144                 } catch (ValueFormatException e) {
145                     log.error(e.getMessage());
146                 } catch (RepositoryException e) {
147                     log.error(e.getMessage());
148                 }
149                 return null;
150             }
151 
152             @Override
153             public void setAttribute(final Object object, final String attribute, final Object newValue) {
154                 MgnlGroovyJCRNode n = (MgnlGroovyJCRNode) object;
155                 try {
156                     PropertyUtil.setProperty(n, attribute, newValue);
157                 }
158                 catch (AccessDeniedException e) {
159                     log.error(e.getMessage());
160                 }
161                 catch (PathNotFoundException e) {
162                     log.error(e.getMessage());
163                 }
164                 catch (RepositoryException e) {
165                     log.error(e.getMessage());
166                 }
167             }
168 
169             @Override
170             public Object getProperty(Object object, String property) {
171                 if (object instanceof MgnlGroovyJCRNode) {
172                     MgnlGroovyJCRNode n = (MgnlGroovyJCRNode) object;
173                     try {
174                         return n.get(property);
175                     } catch (ValueFormatException e) {
176                         log.error(e.getMessage());
177                     } catch (RepositoryException e) {
178                         log.error(e.getMessage());
179                     }
180                     return null;
181                 }
182                 return super.getProperty(object, property);
183             }
184 
185             @Override
186             public void setProperty(Object object, String property, Object newValue) {
187                 String attribute = null;
188                 if (property.startsWith("@")) {
189                     attribute = property.substring(1);
190                 } else {
191                     attribute = property;
192                 }
193                 MgnlGroovyJCRNode n = (MgnlGroovyJCRNode) object;
194                 try {
195                     // else you get java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.Long
196                     if (newValue instanceof Integer) {
197                         newValue = ((Integer) newValue).longValue();
198                     }
199                     PropertyUtil.setProperty(n, attribute, newValue);
200                 }
201                 catch (AccessDeniedException e) {
202                     log.error(e.getMessage());
203                 }
204                 catch (PathNotFoundException e) {
205                     log.error(e.getMessage());
206                 }
207                 catch (RepositoryException e) {
208                     log.error(e.getMessage());
209                 }
210             }
211         };
212 
213         GroovySystem.getMetaClassRegistry().setMetaClass(nodeClass, newMetaClass);
214     }
215 
216     public String name() {
217         return name;
218     }
219 
220     /**
221      * Provides lookup of elements by non-namespaced name.
222      * 
223      * @param key the name (or shortcut key) of the node(s) of interest
224      * @return the nodes which match key
225      * @throws RepositoryException
226      * @throws ValueFormatException
227      */
228     public Object get(String key) throws ValueFormatException, RepositoryException {
229         if (key != null && key.charAt(0) == '@') {
230             String attributeName = key.substring(1);
231             Property property = PropertyUtil.getPropertyOrNull(getWrappedNode(), attributeName);
232             return getPropertyValue(property);
233         }
234         try {
235             if ("parent".equals(key)) {
236                 return getParent();
237             }
238             if ("children".equals(key) || "nodes".equals(key)) {
239                 Iterable<Node> iterable = NodeUtil.getNodes(getWrappedNode());
240                 return new MgnlGroovyWrappingNodeIterator(new NodeIteratorAdapter(iterable.iterator()));
241             }
242             if ("metaData".equals(key)) {
243                 return MetaDataUtil.getMetaData(getWrappedNode());
244             }
245             if ("properties".equals(key)) {
246                 return getProperties();
247             }
248             if ("name".equals(key)) {
249                 return name;
250             }
251             return new MgnlGroovyJCRNode(getNode(key));
252 
253         } catch (RepositoryException e) {
254             log.info(e.getMessage());
255         }
256 
257         // last attempt: lets assume the client requested for an attribute without the @ notation
258         Property property = PropertyUtil.getPropertyOrNull(getWrappedNode(), key);
259         return getPropertyValue(property);
260     }
261 
262     /**
263      * Returns the value for the given property.
264      * The Java types returned vary according to the underlying {@link PropertyType}.
265      * <ul>
266      * <li> {@link PropertyType#STRING} returns a {@link String}
267      * <li> {@link PropertyType#DATE} returns a {@link java.util.Calendar}
268      * <li> {@link PropertyType#DOUBLE} returns a {@link BigDecimal}
269      * <li> {@link PropertyType#LONG} returns a {@link BigDecimal}
270      * <li> {@link PropertyType#BINARY} returns the {@link java.io.InputStream} value.
271      * <li>All other property types will be returned as {@link String}(s)
272      * </ul>
273      */
274     protected Object getPropertyValue(Property property) throws RepositoryException, ValueFormatException {
275         if (property == null) {
276             return "unknown property";
277         }
278         if (property.isMultiple()) {
279             List<Object> values = new ArrayList<Object>();
280             for (Value value : property.getValues()) {
281                 values.add(getPropertyValueInternal(value));
282             }
283             return values;
284         } else {
285             return getPropertyValueInternal(property.getValue());
286         }
287 
288     }
289 
290     public String print() {
291         StringBuilder retVal = new StringBuilder();
292         try {
293             Iterable<Node> nodes = NodeUtil.getNodes(getWrappedNode());
294             retVal.append("(+) " + name + "\n");
295             for (Node node : nodes) {
296                 if (!showNode(node)) {
297                     continue;
298                 }
299                 if ((NodeUtil.getNodes(node).iterator().hasNext())) {
300                     retVal.append("\t(+) ");
301                 } else {
302                     retVal.append("\t(-) ");
303                 }
304                 retVal.append(node.getName() + "\n");
305             }
306 
307             if (getProperties().getSize() > 0) {
308                 PropertyIterator it = super.getProperties();
309                 while (it.hasNext()) {
310                     Property property = it.nextProperty();
311                     if (!showProperty(property)) {
312                         continue;
313                     }
314                     String name = property.getName();
315 
316                     retVal.append("\t\t* " + name + ": ["
317                             + StringUtils.abbreviate(getPropertyValue(property).toString(), 80)
318                             + "]\n");
319 
320                 }
321 
322             }
323         } catch (ValueFormatException e) {
324             e.printStackTrace();
325         } catch (RepositoryException e) {
326             e.printStackTrace();
327         }
328 
329         return retVal.toString();
330     }
331 
332     @Override
333     public int hashCode() {
334         final int prime = 31;
335         int result = 1;
336         result = prime * result + ((name == null) ? 0 : name.hashCode());
337         return result;
338     }
339 
340     @Override
341     public boolean equals(Object obj) {
342         if (this == obj)
343             return true;
344         if (obj == null)
345             return false;
346         if (getClass() != obj.getClass())
347             return false;
348         MgnlGroovyJCRNode other = (MgnlGroovyJCRNode) obj;
349         if (name == null) {
350             if (other.name != null)
351                 return false;
352         } else
353             try {
354                 if (!getIdentifier().equals(other.getIdentifier()))
355                     return false;
356             } catch (RepositoryException e) {
357                 return false;
358             }
359         return true;
360     }
361 
362     /**
363      * MgnlGroovyWrappingNodeIterator.
364      */
365     protected class MgnlGroovyWrappingNodeIterator extends DelegatingNodeIterator {
366         public MgnlGroovyWrappingNodeIterator(NodeIterator iterator) {
367             super(iterator);
368         }
369 
370         @Override
371         public Node nextNode() {
372             return new MgnlGroovyJCRNode(super.nextNode());
373         }
374 
375         @Override
376         public Object next() {
377             return new MgnlGroovyJCRNode((Node) super.next());
378         }
379     }
380 
381     protected boolean showNode(Node node) {
382         try {
383             if (NodeTypes.MetaData.NAME.equals(node.getPrimaryNodeType().getName())
384                     || node.getName().startsWith("jcr:")
385                     || node.getName().startsWith("rep:")) {
386                 return false;
387             }
388         } catch (RepositoryException e) {
389             log.error(e.getMessage());
390             return false;
391         }
392         return true;
393     }
394 
395     protected boolean showProperty(Property property) {
396         try {
397             if (property.getName().startsWith("jcr:")) {
398                 return false;
399             }
400         } catch (RepositoryException e) {
401             log.error(e.getMessage());
402             return false;
403         }
404         return true;
405     }
406 
407     private Object getPropertyValueInternal(Value property) throws RepositoryException {
408         switch (property.getType()) {
409         case PropertyType.STRING:
410             return property.getString();
411         case PropertyType.BOOLEAN:
412             return property.getBoolean();
413         case PropertyType.BINARY:
414             return property.getBinary().getStream();
415         case PropertyType.DATE:
416             return property.getDate();
417         case PropertyType.DECIMAL:
418             return property.getDecimal();
419         case PropertyType.DOUBLE:
420             return BigDecimal.valueOf(property.getDouble());
421         case PropertyType.LONG:
422             return BigDecimal.valueOf(property.getLong());
423         default:
424             return property.getString();
425         }
426     }
427 
428 }