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