001/*
002 * Copyright (C) 2012 eXo Platform SAS.
003 *
004 * This is free software; you can redistribute it and/or modify it
005 * under the terms of the GNU Lesser General Public License as
006 * published by the Free Software Foundation; either version 2.1 of
007 * the License, or (at your option) any later version.
008 *
009 * This software is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * You should have received a copy of the GNU Lesser General Public
015 * License along with this software; if not, write to the Free
016 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018 */
019package org.crsh.jcr.groovy;
020
021import groovy.lang.Closure;
022import groovy.lang.GroovySystem;
023import groovy.lang.MetaClassImpl;
024import groovy.lang.MetaClassRegistry;
025import groovy.lang.MetaMethod;
026import groovy.lang.MetaProperty;
027import groovy.lang.MissingMethodException;
028import groovy.lang.MissingPropertyException;
029import org.crsh.jcr.JCRUtils;
030import org.crsh.jcr.PropertyType;
031
032import javax.jcr.Node;
033import javax.jcr.NodeIterator;
034import javax.jcr.PathNotFoundException;
035import javax.jcr.Property;
036import javax.jcr.PropertyIterator;
037import javax.jcr.RepositoryException;
038import javax.jcr.Value;
039import java.beans.IntrospectionException;
040
041public class NodeMetaClass extends MetaClassImpl {
042
043  public static void setup() {
044
045  }
046
047  public NodeMetaClass(MetaClassRegistry registry, Class<? extends Node> theClass) throws IntrospectionException {
048    super(registry, theClass);
049  }
050
051  @Override
052  public Object invokeMethod(Object object, String name, Object[] args) {
053    try {
054      return _invokeMethod(object, name, args);
055    }
056    catch (RepositoryException e) {
057      // Do that better
058      throw new Error(e);
059    }
060  }
061
062  private Object _invokeMethod(Object object, String name, Object[] args) throws RepositoryException {
063    Node node = (Node)object;
064
065    //
066    if (args != null) {
067      if (args.length == 0) {
068        if ("iterator".equals(name)) {
069          return node.getNodes();
070        }
071      }
072      else if (args.length == 1) {
073        Object arg = args[0];
074
075        // This is the trick we need to use because the javax.jcr.Node interface
076        // has a getProperty(String name) method that is shadowed by the GroovyObject
077        // method with the same signature.
078        if (arg instanceof String && "getProperty".equals(name)) {
079          String propertyName = (String)arg;
080          return JCRUtils.getProperty(node, propertyName);
081        }
082        else if (arg instanceof Closure) {
083          Closure closure = (Closure)arg;
084          if ("eachProperty".equals(name)) {
085            PropertyIterator properties = node.getProperties();
086            while (properties.hasNext()) {
087              Property n = properties.nextProperty();
088              closure.call(new Object[]{n});
089            }
090            return null;
091          }/* else if ("eachWithIndex".equals(name)) {
092              NodeIterator nodes = node.getNodes();
093              int index = 0;
094              while (nodes.hasNext()) {
095                Node n = nodes.nextNode();
096                closure.call(new Object[]{n,index++});
097              }
098              return null;
099            }*/
100        }
101        else if ("getAt".equals(name)) {
102          if (arg instanceof Integer) {
103            NodeIterator it = node.getNodes();
104            long size = it.getSize();
105            long index = (Integer)arg;
106
107            // Bounds detection
108            if (index < 0) {
109              if (index < -size) throw new ArrayIndexOutOfBoundsException((int)index);
110              index = size + index;
111            }
112            else if (index >= size) throw new ArrayIndexOutOfBoundsException((int)index);
113
114            //
115            it.skip(index);
116            return it.next();
117          }
118        }
119      }
120    }
121
122    // We let groovy handle the call
123    MetaMethod validMethod = super.getMetaMethod(name, args);
124    if (validMethod != null) {
125      return validMethod.invoke(node, args);
126    }
127
128    //
129    throw new MissingMethodException(name, Node.class, args);
130  }
131
132  @Override
133  public Object getProperty(Object object, String property) {
134    try {
135      return _getProperty(object, property);
136    }
137    catch (RepositoryException e) {
138      throw new Error(e);
139    }
140  }
141
142  private Object _getProperty(Object object, String propertyName) throws RepositoryException {
143    Node node = (Node)object;
144
145    // Access defined properties
146    MetaProperty metaProperty = super.getMetaProperty(propertyName);
147    if (metaProperty != null) {
148      return metaProperty.getProperty(node);
149    }
150
151    // First we try to access a property
152    try {
153      Property property = node.getProperty(propertyName);
154      PropertyType type = PropertyType.fromValue(property.getType());
155      return type.get(property);
156    }
157    catch (PathNotFoundException e) {
158    }
159
160    // If we don't find it as a property we try it as a child node
161    try {
162      return node.getNode(propertyName);
163    }
164    catch (PathNotFoundException e) {
165    }
166
167    //
168    return null;
169  }
170
171  @Override
172  public void setProperty(Object object, String property, Object newValue) {
173    try {
174      _setProperty(object, property, newValue);
175    }
176    catch (Exception e) {
177      throw new Error(e);
178    }
179  }
180
181  private void _setProperty(Object object, String propertyName, Object propertyValue) throws RepositoryException {
182    Node node = (Node)object;
183    if (propertyValue == null) {
184      node.setProperty(propertyName, (Value)null);
185    } else {
186
187      // Get property type
188      PropertyType type;
189      try {
190        Property property = node.getProperty(propertyName);
191        type = PropertyType.fromValue(property.getType());
192      } catch (PathNotFoundException e) {
193        type = PropertyType.fromCanonicalType(propertyValue.getClass());
194      }
195
196      // Update the property and get the updated property
197      Property property;
198      if (type != null) {
199        property = type.set(node, propertyName, propertyValue);
200      } else {
201        property = null;
202      }
203
204      //
205      if (property == null && propertyValue instanceof String) {
206        if (propertyValue instanceof String) {
207          // This is likely a conversion from String that should be handled natively by JCR itself
208          node.setProperty(propertyName, (String)propertyValue);
209        } else {
210          throw new MissingPropertyException("Property " + propertyName + " does not have a correct type " + propertyValue.getClass().getName());
211        }
212      }
213    }
214  }
215}