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 }