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