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