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