]> git.argeo.org Git - lgpl/argeo-commons.git/blob - server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jcr/BeanNodeMapper.java
d05af407f33e3996e696c871bf0d146746868588
[lgpl/argeo-commons.git] / server / runtime / org.argeo.server.jackrabbit / src / main / java / org / argeo / jcr / BeanNodeMapper.java
1 package org.argeo.jcr;
2
3 import java.beans.PropertyDescriptor;
4 import java.io.InputStream;
5 import java.util.ArrayList;
6 import java.util.Calendar;
7 import java.util.Date;
8 import java.util.GregorianCalendar;
9 import java.util.HashMap;
10 import java.util.List;
11 import java.util.Map;
12 import java.util.StringTokenizer;
13
14 import javax.jcr.ItemNotFoundException;
15 import javax.jcr.Node;
16 import javax.jcr.NodeIterator;
17 import javax.jcr.Property;
18 import javax.jcr.PropertyIterator;
19 import javax.jcr.PropertyType;
20 import javax.jcr.RepositoryException;
21 import javax.jcr.Session;
22 import javax.jcr.Value;
23 import javax.jcr.ValueFactory;
24
25 import org.apache.commons.logging.Log;
26 import org.apache.commons.logging.LogFactory;
27 import org.argeo.ArgeoException;
28 import org.springframework.beans.BeanWrapper;
29 import org.springframework.beans.BeanWrapperImpl;
30
31 public class BeanNodeMapper {
32 private final static Log log = LogFactory.getLog(BeanNodeMapper.class);
33
34 private final static String NODE_VALUE = "value";
35
36 // private String keyNode = "bean:key";
37 private String uuidProperty = "uuid";
38 private String classProperty = "class";
39
40 private Boolean versioning = false;
41 private Boolean strictUuidReference = false;
42 private String primaryNodeType = null;
43
44 private ClassLoader classLoader = getClass().getClassLoader();
45
46 public String storagePath(Object obj) {
47 String clss = obj.getClass().getName();
48 StringBuffer buf = new StringBuffer("/objects/");
49 StringTokenizer st = new StringTokenizer(clss, ".");
50 while (st.hasMoreTokens()) {
51 buf.append(st.nextToken()).append('/');
52 }
53 buf.append(obj.toString());
54 return buf.toString();
55 }
56
57 public Node save(Session session, Object obj) {
58 return save(session, storagePath(obj), obj);
59 }
60
61 public void update(Node node, Object obj) {
62 try {
63 beanToNode(createBeanWrapper(obj), node);
64 } catch (RepositoryException e) {
65 throw new ArgeoException("Cannot update node " + node + " with "
66 + obj, e);
67 }
68 }
69
70 public Node save(Session session, String path, Object obj) {
71 try {
72 BeanWrapper beanWrapper = createBeanWrapper(obj);
73 final Node node;
74 String parentPath = JcrUtils.parentPath(path);
75 // find or create parent node
76 Node parentNode;
77 if (session.itemExists(path))
78 parentNode = (Node) session.getItem(parentPath);
79 else
80 parentNode = JcrUtils.mkdirs(session, parentPath, null,
81 versioning);
82 // create node
83 if (primaryNodeType != null)
84 node = parentNode.addNode(JcrUtils.lastPathElement(path),
85 primaryNodeType);
86 else
87 node = parentNode.addNode(JcrUtils.lastPathElement(path));
88
89 beanToNode(beanWrapper, node);
90 return node;
91 } catch (ArgeoException e) {
92 throw e;
93 } catch (Exception e) {
94 throw new ArgeoException("Cannot save or update " + obj + " under "
95 + path, e);
96 }
97 }
98
99 @SuppressWarnings("unchecked")
100 public Object nodeToBean(Node node) throws RepositoryException {
101
102 String clssName = node.getProperty(classProperty).getValue()
103 .getString();
104
105 if (log.isTraceEnabled())
106 log.debug("Map node " + node.getPath() + " to bean " + clssName);
107
108 BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName));
109
110 // process properties
111 PropertyIterator propIt = node.getProperties();
112 props: while (propIt.hasNext()) {
113 Property prop = propIt.nextProperty();
114 if (!beanWrapper.isWritableProperty(prop.getName()))
115 continue props;
116
117 PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop
118 .getName());
119 Class propClass = pd.getPropertyType();
120
121 // list
122 if (propClass != null && List.class.isAssignableFrom(propClass)) {
123 List<Object> lst = new ArrayList<Object>();
124 Class<?> valuesClass = classFromProperty(prop);
125 if (valuesClass != null)
126 for (Value value : prop.getValues()) {
127 lst.add(asObject(value, valuesClass));
128 }
129 continue props;
130 }
131
132 Object value = asObject(prop.getValue(), pd.getPropertyType());
133 if (value != null)
134 beanWrapper.setPropertyValue(prop.getName(), value);
135 }
136
137 // process children nodes
138 NodeIterator nodeIt = node.getNodes();
139 nodes: while (nodeIt.hasNext()) {
140 Node childNode = nodeIt.nextNode();
141 String name = childNode.getName();
142 if (!beanWrapper.isWritableProperty(name))
143 continue nodes;
144
145 PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name);
146 Class propClass = pd.getPropertyType();
147
148 if (propClass != null && List.class.isAssignableFrom(propClass)) {
149 String lstClass = childNode.getProperty(classProperty)
150 .getString();
151 List<Object> lst;
152 try {
153 lst = (List<Object>) loadClass(lstClass).newInstance();
154 } catch (Exception e) {
155 lst = new ArrayList<Object>();
156 }
157
158 NodeIterator valuesIt = childNode.getNodes();
159 while (valuesIt.hasNext()) {
160 Node lstValueNode = valuesIt.nextNode();
161 Object lstValue = nodeToBean(lstValueNode);
162 lst.add(lstValue);
163 }
164
165 beanWrapper.setPropertyValue(name, lst);
166 continue nodes;
167 }
168
169 if (propClass != null && Map.class.isAssignableFrom(propClass)) {
170 String mapClass = childNode.getProperty(classProperty)
171 .getString();
172 Map<Object, Object> map;
173 try {
174 map = (Map<Object, Object>) loadClass(mapClass)
175 .newInstance();
176 } catch (Exception e) {
177 map = new HashMap<Object, Object>();
178 }
179
180 // properties
181 PropertyIterator keysPropIt = childNode.getProperties();
182 keyProps: while (keysPropIt.hasNext()) {
183 Property keyProp = keysPropIt.nextProperty();
184 // FIXME: use property editor
185 String key = keyProp.getName();
186 if (classProperty.equals(key))
187 continue keyProps;
188
189 Class keyPropClass = classFromProperty(keyProp);
190 if (keyPropClass != null) {
191 Object mapValue = asObject(keyProp.getValue(),
192 keyPropClass);
193 map.put(key, mapValue);
194 }
195 }
196
197 // node
198 NodeIterator keysIt = childNode.getNodes();
199 while (keysIt.hasNext()) {
200 Node mapValueNode = keysIt.nextNode();
201 // FIXME: use property editor
202 Object key = mapValueNode.getName();
203
204 Object mapValue = nodeToBean(mapValueNode);
205
206 map.put(key, mapValue);
207 }
208 beanWrapper.setPropertyValue(name, map);
209 continue nodes;
210 }
211
212 // default
213 Object value = nodeToBean(childNode);
214 beanWrapper.setPropertyValue(name, value);
215
216 }
217 return beanWrapper.getWrappedInstance();
218 }
219
220 protected void beanToNode(BeanWrapper beanWrapper, Node node)
221 throws RepositoryException {
222 if (log.isTraceEnabled())
223 log.debug("Map bean to node " + node.getPath());
224
225 properties: for (PropertyDescriptor pd : beanWrapper
226 .getPropertyDescriptors()) {
227 String name = pd.getName();
228
229 if (!beanWrapper.isReadableProperty(name))
230 continue properties;// skip
231
232 Object value = beanWrapper.getPropertyValue(name);
233 if (value == null) {
234 // remove values when updating
235 if (node.hasProperty(name))
236 node.setProperty(name, (Value) null);
237 if (node.hasNode(name))
238 node.getNode(name).remove();
239
240 continue properties;
241 }
242
243 // if (uuidProperty != null && uuidProperty.equals(name)) {
244 // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
245 // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
246 // continue properties;
247 // }
248
249 if ("class".equals(name)) {
250 if (classProperty != null) {
251 node.setProperty(classProperty, ((Class<?>) value)
252 .getName());
253 // TODO: store a class hierarchy?
254 }
255 continue properties;
256 }
257
258 if (value instanceof Class<?>) {
259 node.setProperty(name, ((Class<?>) value).getName());
260 continue properties;
261 }
262
263 Value val = asValue(node.getSession(), value);
264 if (val != null) {
265 node.setProperty(name, val);
266 continue properties;
267 }
268
269 if (value instanceof List<?>) {
270 List<?> lst = (List<?>) value;
271 addList(node, name, lst);
272 continue properties;
273 }
274
275 if (value instanceof Map<?, ?>) {
276 Map<?, ?> map = (Map<?, ?>) value;
277 addMap(node, name, map);
278 continue properties;
279 }
280
281 BeanWrapper child = createBeanWrapper(value);
282 // TODO: delegate to another mapper
283
284 // TODO: deal with references
285 // Node childNode = findChildReference(session, child);
286 // if (childNode != null) {
287 // node.setProperty(name, childNode);
288 // continue properties;
289 // }
290
291 // default case (recursive)
292 if (node.hasNode(name)) {// update
293 // TODO: optimize
294 node.getNode(name).remove();
295 }
296 Node childNode = node.addNode(name);
297 beanToNode(child, childNode);
298 }
299 }
300
301 protected void addList(Node node, String name, List<?> lst)
302 throws RepositoryException {
303 if (node.hasNode(name)) {// update
304 // TODO: optimize
305 node.getNode(name).remove();
306 }
307
308 Node listNode = node.addNode(name);
309 listNode.setProperty(classProperty, lst.getClass().getName());
310 Value[] values = new Value[lst.size()];
311 boolean atLeastOneSet = false;
312 for (int i = 0; i < lst.size(); i++) {
313 Object lstValue = lst.get(i);
314 values[i] = asValue(node.getSession(), lstValue);
315 if (values[i] != null) {
316 atLeastOneSet = true;
317 } else {
318 Node childNode = findChildReference(node.getSession(),
319 createBeanWrapper(lstValue));
320 if (childNode != null) {
321 values[i] = node.getSession().getValueFactory()
322 .createValue(childNode);
323 atLeastOneSet = true;
324 }
325 }
326 }
327
328 // will be either properties or nodes, not both
329 if (!atLeastOneSet && lst.size() != 0) {
330 for (Object lstValue : lst) {
331 Node childNode = listNode.addNode(NODE_VALUE);
332 beanToNode(createBeanWrapper(lstValue), childNode);
333 }
334 } else {
335 listNode.setProperty(name, values);
336 }
337 }
338
339 protected void addMap(Node node, String name, Map<?, ?> map)
340 throws RepositoryException {
341 if (node.hasNode(name)) {// update
342 // TODO: optimize
343 node.getNode(name).remove();
344 }
345
346 Node mapNode = node.addNode(name);
347 mapNode.setProperty(classProperty, map.getClass().getName());
348 for (Object key : map.keySet()) {
349 Object mapValue = map.get(key);
350 // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
351 // null);
352 String keyStr;
353 // if (pe == null) {
354 if (key instanceof CharSequence)
355 keyStr = key.toString();
356 else
357 throw new ArgeoException(
358 "Cannot find property editor for class "
359 + key.getClass());
360 // } else {
361 // pe.setValue(key);
362 // keyStr = pe.getAsText();
363 // }
364 // TODO: check string format
365
366 Value mapVal = asValue(node.getSession(), mapValue);
367 if (mapVal != null)
368 mapNode.setProperty(keyStr, mapVal);
369 else {
370 Node entryNode = mapNode.addNode(keyStr);
371 beanToNode(createBeanWrapper(mapValue), entryNode);
372 }
373
374 }
375
376 }
377
378 protected BeanWrapper createBeanWrapper(Object obj) {
379 return new BeanWrapperImpl(obj);
380 }
381
382 protected BeanWrapper createBeanWrapper(Class<?> clss) {
383 return new BeanWrapperImpl(clss);
384 }
385
386 /** Returns null if value cannot be found */
387 protected Value asValue(Session session, Object value)
388 throws RepositoryException {
389 ValueFactory valueFactory = session.getValueFactory();
390 if (value instanceof Integer)
391 return valueFactory.createValue((Integer) value);
392 else if (value instanceof Long)
393 return valueFactory.createValue((Long) value);
394 else if (value instanceof Float)
395 return valueFactory.createValue((Float) value);
396 else if (value instanceof Double)
397 return valueFactory.createValue((Double) value);
398 else if (value instanceof Boolean)
399 return valueFactory.createValue((Boolean) value);
400 else if (value instanceof Calendar)
401 return valueFactory.createValue((Calendar) value);
402 else if (value instanceof Date) {
403 Calendar cal = new GregorianCalendar();
404 cal.setTime((Date) value);
405 return valueFactory.createValue(cal);
406 } else if (value instanceof CharSequence)
407 return valueFactory.createValue(value.toString());
408 else if (value instanceof InputStream)
409 return valueFactory.createValue((InputStream) value);
410 else
411 return null;
412 }
413
414 protected Class<?> classFromProperty(Property property)
415 throws RepositoryException {
416 switch (property.getType()) {
417 case PropertyType.LONG:
418 return Long.class;
419 case PropertyType.DOUBLE:
420 return Double.class;
421 case PropertyType.STRING:
422 return String.class;
423 case PropertyType.BOOLEAN:
424 return Boolean.class;
425 case PropertyType.DATE:
426 return Calendar.class;
427 case PropertyType.NAME:
428 return null;
429 default:
430 throw new ArgeoException("Cannot find class for property "
431 + property + ", type="
432 + PropertyType.nameFromValue(property.getType()));
433 }
434 }
435
436 protected Object asObject(Value value, Class<?> propClass)
437 throws RepositoryException {
438 if (propClass.equals(Integer.class))
439 return (int) value.getLong();
440 else if (propClass.equals(Long.class))
441 return value.getLong();
442 else if (propClass.equals(Float.class))
443 return (float) value.getDouble();
444 else if (propClass.equals(Double.class))
445 return value.getDouble();
446 else if (propClass.equals(Boolean.class))
447 return value.getBoolean();
448 else if (CharSequence.class.isAssignableFrom(propClass))
449 return value.getString();
450 else if (InputStream.class.isAssignableFrom(propClass))
451 return value.getStream();
452 else if (Calendar.class.isAssignableFrom(propClass))
453 return value.getDate();
454 else if (Date.class.isAssignableFrom(propClass))
455 return value.getDate().getTime();
456 else
457 return null;
458 }
459
460 protected Node findChildReference(Session session, BeanWrapper child)
461 throws RepositoryException {
462 if (child.isReadableProperty(uuidProperty)) {
463 String childUuid = child.getPropertyValue(uuidProperty).toString();
464 try {
465 return session.getNodeByUUID(childUuid);
466 } catch (ItemNotFoundException e) {
467 if (strictUuidReference)
468 throw new ArgeoException("No node found with uuid "
469 + childUuid, e);
470 }
471 }
472 return null;
473 }
474
475 protected Class<?> loadClass(String name) {
476 //log.debug("Class loader: " + classLoader);
477 try {
478 return classLoader.loadClass(name);
479 } catch (ClassNotFoundException e) {
480 throw new ArgeoException("Cannot load class " + name, e);
481 }
482 }
483
484 protected String propertyName(String name) {
485 return name;
486 }
487
488 public void setVersioning(Boolean versioning) {
489 this.versioning = versioning;
490 }
491
492 public void setUuidProperty(String uuidProperty) {
493 this.uuidProperty = uuidProperty;
494 }
495
496 public void setClassProperty(String classProperty) {
497 this.classProperty = classProperty;
498 }
499
500 public void setStrictUuidReference(Boolean strictUuidReference) {
501 this.strictUuidReference = strictUuidReference;
502 }
503
504 public void setPrimaryNodeType(String primaryNodeType) {
505 this.primaryNodeType = primaryNodeType;
506 }
507
508 public void setClassLoader(ClassLoader classLoader) {
509 this.classLoader = classLoader;
510 }
511
512 }