3 import java
.beans
.PropertyDescriptor
;
4 import java
.io
.InputStream
;
5 import java
.util
.ArrayList
;
6 import java
.util
.Calendar
;
8 import java
.util
.GregorianCalendar
;
9 import java
.util
.HashMap
;
10 import java
.util
.List
;
12 import java
.util
.StringTokenizer
;
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
;
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
;
31 public class BeanNodeMapper
implements NodeMapper
, NodeMapperProvider
{
32 private final static Log log
= LogFactory
.getLog(BeanNodeMapper
.class);
34 private final static String NODE_VALUE
= "value";
36 // private String keyNode = "bean:key";
37 private String uuidProperty
= "uuid";
38 private String classProperty
= "class";
40 private Boolean versioning
= false;
41 private Boolean strictUuidReference
= false;
42 private String primaryNodeType
= null;
44 private ClassLoader classLoader
= getClass().getClassLoader();
46 private NodeMapperProvider nodeMapperProvider
;
48 public void update(Node node
, Object obj
) {
50 beanToNode(createBeanWrapper(obj
), node
);
51 } catch (RepositoryException e
) {
52 throw new ArgeoException("Cannot update node " + node
+ " with "
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('/');
64 buf
.append(obj
.toString());
65 return buf
.toString();
68 public Node
save(Session session
, Object obj
) {
69 return save(session
, storagePath(obj
), obj
);
71 public Node
save(Session session
, String path
, Object obj
) {
73 BeanWrapper beanWrapper
= createBeanWrapper(obj
);
75 String parentPath
= JcrUtils
.parentPath(path
);
76 // find or create parent node
78 if (session
.itemExists(path
))
79 parentNode
= (Node
) session
.getItem(parentPath
);
81 parentNode
= JcrUtils
.mkdirs(session
, parentPath
, null,
84 if (primaryNodeType
!= null)
85 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
),
88 node
= parentNode
.addNode(JcrUtils
.lastPathElement(path
));
90 beanToNode(beanWrapper
, node
);
92 } catch (ArgeoException e
) {
94 } catch (Exception e
) {
95 throw new ArgeoException("Cannot save or update " + obj
+ " under "
100 public Object
load(Node node
) {
102 return nodeToBean(node
);
103 } catch (RepositoryException e
) {
104 throw new ArgeoException("Cannot load object from node " + node
, e
);
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
);
118 String clssName
= node
.getProperty(classProperty
).getValue()
121 if (log
.isTraceEnabled())
122 log
.debug("Map node " + node
.getPath() + " to bean " + clssName
);
124 BeanWrapper beanWrapper
= createBeanWrapper(loadClass(clssName
));
126 // process properties
127 PropertyIterator propIt
= node
.getProperties();
128 props
: while (propIt
.hasNext()) {
129 Property prop
= propIt
.nextProperty();
130 if (!beanWrapper
.isWritableProperty(prop
.getName()))
133 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(prop
135 Class propClass
= pd
.getPropertyType();
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
));
148 Object value
= asObject(prop
.getValue(), pd
.getPropertyType());
150 beanWrapper
.setPropertyValue(prop
.getName(), value
);
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
))
161 PropertyDescriptor pd
= beanWrapper
.getPropertyDescriptor(name
);
162 Class propClass
= pd
.getPropertyType();
165 if (propClass
!= null && List
.class.isAssignableFrom(propClass
)) {
166 String lstClass
= childNode
.getProperty(classProperty
)
170 lst
= (List
<Object
>) loadClass(lstClass
).newInstance();
171 } catch (Exception e
) {
172 lst
= new ArrayList
<Object
>();
175 NodeIterator valuesIt
= childNode
.getNodes();
176 while (valuesIt
.hasNext()) {
177 Node lstValueNode
= valuesIt
.nextNode();
178 Object lstValue
= nodeToBean(lstValueNode
);
182 beanWrapper
.setPropertyValue(name
, lst
);
187 if (propClass
!= null && Map
.class.isAssignableFrom(propClass
)) {
188 String mapClass
= childNode
.getProperty(classProperty
)
190 Map
<Object
, Object
> map
;
192 map
= (Map
<Object
, Object
>) loadClass(mapClass
)
194 } catch (Exception e
) {
195 map
= new HashMap
<Object
, Object
>();
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
))
207 Class keyPropClass
= classFromProperty(keyProp
);
208 if (keyPropClass
!= null) {
209 Object mapValue
= asObject(keyProp
.getValue(),
211 map
.put(key
, mapValue
);
216 NodeIterator keysIt
= childNode
.getNodes();
217 while (keysIt
.hasNext()) {
218 Node mapValueNode
= keysIt
.nextNode();
219 // FIXME: use property editor
220 Object key
= mapValueNode
.getName();
222 Object mapValue
= nodeToBean(mapValueNode
);
224 map
.put(key
, mapValue
);
226 beanWrapper
.setPropertyValue(name
, map
);
231 Object value
= nodeToBean(childNode
);
232 beanWrapper
.setPropertyValue(name
, value
);
235 return beanWrapper
.getWrappedInstance();
238 protected void beanToNode(BeanWrapper beanWrapper
, Node node
)
239 throws RepositoryException
{
241 if (nodeMapperProvider
!= null) {
242 NodeMapper nodeMapper
= nodeMapperProvider
.findNodeMapper(node
);
243 if (nodeMapper
!= null) {
244 nodeMapper
.update(node
, beanWrapper
.getWrappedInstance());
249 if (log
.isTraceEnabled())
250 log
.debug("Map bean to node " + node
.getPath());
252 properties
: for (PropertyDescriptor pd
: beanWrapper
253 .getPropertyDescriptors()) {
254 String name
= pd
.getName();
256 if (!beanWrapper
.isReadableProperty(name
))
257 continue properties
;// skip
259 Object value
= beanWrapper
.getPropertyValue(name
);
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();
270 // if (uuidProperty != null && uuidProperty.equals(name)) {
271 // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
272 // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
273 // continue properties;
276 if ("class".equals(name
)) {
277 if (classProperty
!= null) {
278 node
.setProperty(classProperty
, ((Class
<?
>) value
)
280 // TODO: store a class hierarchy?
285 if (value
instanceof Class
<?
>) {
286 node
.setProperty(name
, ((Class
<?
>) value
).getName());
290 Value val
= asValue(node
.getSession(), value
);
292 node
.setProperty(name
, val
);
296 if (value
instanceof List
<?
>) {
297 List
<?
> lst
= (List
<?
>) value
;
298 addList(node
, name
, lst
);
302 if (value
instanceof Map
<?
, ?
>) {
303 Map
<?
, ?
> map
= (Map
<?
, ?
>) value
;
304 addMap(node
, name
, map
);
308 BeanWrapper child
= createBeanWrapper(value
);
309 // TODO: delegate to another mapper
311 // TODO: deal with references
312 // Node childNode = findChildReference(session, child);
313 // if (childNode != null) {
314 // node.setProperty(name, childNode);
315 // continue properties;
318 // default case (recursive)
319 if (node
.hasNode(name
)) {// update
321 node
.getNode(name
).remove();
323 Node childNode
= node
.addNode(name
);
324 beanToNode(child
, childNode
);
328 protected void addList(Node node
, String name
, List
<?
> lst
)
329 throws RepositoryException
{
330 if (node
.hasNode(name
)) {// update
332 node
.getNode(name
).remove();
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;
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;
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
);
362 listNode
.setProperty(name
, values
);
366 protected void addMap(Node node
, String name
, Map
<?
, ?
> map
)
367 throws RepositoryException
{
368 if (node
.hasNode(name
)) {// update
370 node
.getNode(name
).remove();
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(),
381 if (key
instanceof CharSequence
)
382 keyStr
= key
.toString();
384 throw new ArgeoException(
385 "Cannot find property editor for class "
389 // keyStr = pe.getAsText();
391 // TODO: check string format
393 Value mapVal
= asValue(node
.getSession(), mapValue
);
395 mapNode
.setProperty(keyStr
, mapVal
);
397 Node entryNode
= mapNode
.addNode(keyStr
);
398 beanToNode(createBeanWrapper(mapValue
), entryNode
);
405 protected BeanWrapper
createBeanWrapper(Object obj
) {
406 return new BeanWrapperImpl(obj
);
409 protected BeanWrapper
createBeanWrapper(Class
<?
> clss
) {
410 return new BeanWrapperImpl(clss
);
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
);
441 protected Class
<?
> classFromProperty(Property property
)
442 throws RepositoryException
{
443 switch (property
.getType()) {
444 case PropertyType
.LONG
:
446 case PropertyType
.DOUBLE
:
448 case PropertyType
.STRING
:
450 case PropertyType
.BOOLEAN
:
451 return Boolean
.class;
452 case PropertyType
.DATE
:
453 return Calendar
.class;
454 case PropertyType
.NAME
:
457 throw new ArgeoException("Cannot find class for property "
458 + property
+ ", type="
459 + PropertyType
.nameFromValue(property
.getType()));
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();
487 protected Node
findChildReference(Session session
, BeanWrapper child
)
488 throws RepositoryException
{
489 if (child
.isReadableProperty(uuidProperty
)) {
490 String childUuid
= child
.getPropertyValue(uuidProperty
).toString();
492 return session
.getNodeByUUID(childUuid
);
493 } catch (ItemNotFoundException e
) {
494 if (strictUuidReference
)
495 throw new ArgeoException("No node found with uuid "
502 protected Class
<?
> loadClass(String name
) {
503 // log.debug("Class loader: " + classLoader);
505 return classLoader
.loadClass(name
);
506 } catch (ClassNotFoundException e
) {
507 throw new ArgeoException("Cannot load class " + name
, e
);
511 /** Returns itself. */
512 public NodeMapper
findNodeMapper(Node node
) {
516 protected String
propertyName(String name
) {
520 public void setVersioning(Boolean versioning
) {
521 this.versioning
= versioning
;
524 public void setUuidProperty(String uuidProperty
) {
525 this.uuidProperty
= uuidProperty
;
528 public void setClassProperty(String classProperty
) {
529 this.classProperty
= classProperty
;
532 public void setStrictUuidReference(Boolean strictUuidReference
) {
533 this.strictUuidReference
= strictUuidReference
;
536 public void setPrimaryNodeType(String primaryNodeType
) {
537 this.primaryNodeType
= primaryNodeType
;
540 public void setClassLoader(ClassLoader classLoader
) {
541 this.classLoader
= classLoader
;
544 public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider
) {
545 this.nodeMapperProvider
= nodeMapperProvider
;