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     */
019    package org.crsh.jcr.groovy;
020    
021    import groovy.lang.Closure;
022    import groovy.lang.GroovySystem;
023    import groovy.lang.MetaClassImpl;
024    import groovy.lang.MetaClassRegistry;
025    import groovy.lang.MetaMethod;
026    import groovy.lang.MetaProperty;
027    import groovy.lang.MissingMethodException;
028    import groovy.lang.MissingPropertyException;
029    import org.crsh.jcr.JCRUtils;
030    import org.crsh.jcr.PropertyType;
031    
032    import javax.jcr.Node;
033    import javax.jcr.NodeIterator;
034    import javax.jcr.PathNotFoundException;
035    import javax.jcr.Property;
036    import javax.jcr.PropertyIterator;
037    import javax.jcr.RepositoryException;
038    import javax.jcr.Value;
039    import java.beans.IntrospectionException;
040    
041    public 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    }