001    /*
002     * Copyright (C) 2003-2009 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    /**
042     * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
043     * @version $Revision$
044     */
045    public class NodeMetaClass extends MetaClassImpl {
046    
047      public static void setup() {
048    
049      }
050    
051      public NodeMetaClass(MetaClassRegistry registry, Class<? extends Node> theClass) throws IntrospectionException {
052        super(registry, theClass);
053      }
054    
055      @Override
056      public Object invokeMethod(Object object, String name, Object[] args) {
057        try {
058          return _invokeMethod(object, name, args);
059        }
060        catch (RepositoryException e) {
061          // Do that better
062          throw new Error(e);
063        }
064      }
065    
066      private Object _invokeMethod(Object object, String name, Object[] args) throws RepositoryException {
067        Node node = (Node)object;
068    
069        //
070        if (args != null) {
071          if (args.length == 0) {
072            if ("iterator".equals(name)) {
073              return node.getNodes();
074            }
075          }
076          else if (args.length == 1) {
077            Object arg = args[0];
078    
079            // This is the trick we need to use because the javax.jcr.Node interface
080            // has a getProperty(String name) method that is shadowed by the GroovyObject
081            // method with the same signature.
082            if (arg instanceof String && "getProperty".equals(name)) {
083              String propertyName = (String)arg;
084              return JCRUtils.getProperty(node, propertyName);
085            }
086            else if (arg instanceof Closure) {
087              Closure closure = (Closure)arg;
088              if ("eachProperty".equals(name)) {
089                PropertyIterator properties = node.getProperties();
090                while (properties.hasNext()) {
091                  Property n = properties.nextProperty();
092                  closure.call(new Object[]{n});
093                }
094                return null;
095              }/* else if ("eachWithIndex".equals(name)) {
096                  NodeIterator nodes = node.getNodes();
097                  int index = 0;
098                  while (nodes.hasNext()) {
099                    Node n = nodes.nextNode();
100                    closure.call(new Object[]{n,index++});
101                  }
102                  return null;
103                }*/
104            }
105            else if ("getAt".equals(name)) {
106              if (arg instanceof Integer) {
107                NodeIterator it = node.getNodes();
108                long size = it.getSize();
109                long index = (Integer)arg;
110    
111                // Bounds detection
112                if (index < 0) {
113                  if (index < -size) throw new ArrayIndexOutOfBoundsException((int)index);
114                  index = size + index;
115                }
116                else if (index >= size) throw new ArrayIndexOutOfBoundsException((int)index);
117    
118                //
119                it.skip(index);
120                return it.next();
121              }
122            }
123          }
124        }
125    
126        // We let groovy handle the call
127        MetaMethod validMethod = super.getMetaMethod(name, args);
128        if (validMethod != null) {
129          return validMethod.invoke(node, args);
130        }
131    
132        //
133        throw new MissingMethodException(name, Node.class, args);
134      }
135    
136      @Override
137      public Object getProperty(Object object, String property) {
138        try {
139          return _getProperty(object, property);
140        }
141        catch (RepositoryException e) {
142          throw new Error(e);
143        }
144      }
145    
146      private Object _getProperty(Object object, String propertyName) throws RepositoryException {
147        Node node = (Node)object;
148    
149        // Access defined properties
150        MetaProperty metaProperty = super.getMetaProperty(propertyName);
151        if (metaProperty != null) {
152          return metaProperty.getProperty(node);
153        }
154    
155        // First we try to access a property
156        try {
157          Property property = node.getProperty(propertyName);
158          PropertyType type = PropertyType.fromValue(property.getType());
159          return type.get(property);
160        }
161        catch (PathNotFoundException e) {
162        }
163    
164        // If we don't find it as a property we try it as a child node
165        try {
166          return node.getNode(propertyName);
167        }
168        catch (PathNotFoundException e) {
169        }
170    
171        //
172        return null;
173      }
174    
175      @Override
176      public void setProperty(Object object, String property, Object newValue) {
177        try {
178          _setProperty(object, property, newValue);
179        }
180        catch (Exception e) {
181          throw new Error(e);
182        }
183      }
184    
185      private void _setProperty(Object object, String propertyName, Object propertyValue) throws RepositoryException {
186        Node node = (Node)object;
187        if (propertyValue == null) {
188          node.setProperty(propertyName, (Value)null);
189        } else {
190    
191          // Get property type
192          PropertyType type;
193          try {
194            Property property = node.getProperty(propertyName);
195            type = PropertyType.fromValue(property.getType());
196          } catch (PathNotFoundException e) {
197            type = PropertyType.fromCanonicalType(propertyValue.getClass());
198          }
199    
200          // Update the property and get the updated property
201          Property property;
202          if (type != null) {
203            property = type.set(node, propertyName, propertyValue);
204          } else {
205            property = null;
206          }
207    
208          //
209          if (property == null && propertyValue instanceof String) {
210            if (propertyValue instanceof String) {
211              // This is likely a conversion from String that should be handled natively by JCR itself
212              node.setProperty(propertyName, (String)propertyValue);
213            } else {
214              throw new MissingPropertyException("Property " + propertyName + " does not have a correct type " + propertyValue.getClass().getName());
215            }
216          }
217        }
218      }
219    }