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