View Javadoc

1   package org.jcr_blog.jcrmapping;
2   
3   import com.google.common.collect.Lists;
4   import com.google.common.collect.Sets;
5   import java.lang.reflect.Field;
6   import java.lang.reflect.ParameterizedType;
7   import java.lang.reflect.Type;
8   import java.util.ArrayList;
9   import java.util.Collection;
10  import java.util.List;
11  import java.util.Set;
12  import javax.enterprise.context.ApplicationScoped;
13  import javax.enterprise.inject.Any;
14  import javax.enterprise.inject.Instance;
15  import javax.inject.Inject;
16  import javax.inject.Named;
17  import javax.jcr.Node;
18  import javax.jcr.Property;
19  import javax.jcr.RepositoryException;
20  import javax.jcr.Value;
21  import javax.jcr.ValueFactory;
22  import javax.validation.constraints.NotNull;
23  import org.jcr_blog.jcrmapping.converter.CollectionConverter;
24  import org.jcr_blog.jcrmapping.converter.Converter;
25  import org.slf4j.Logger;
26  import scala.collection.Iterator;
27  
28  /**
29   * NodeConverterService uses the NodeConfiguration and PropertyConfiguration
30   * annotations to convert between java objects and jcr nodes.
31   *
32   * @author Sebastian Prehn <sebastian.prehn@planetswebdesign.de>
33   */
34  @Named()
35  @ApplicationScoped
36  public class NodeConverterService {
37  
38      @Inject
39      private Logger logger;
40      @Inject
41      @Any
42      private Instance<Converter> converters;
43      @Inject
44      @Any
45      private Instance<CollectionConverter> collectionConverters;
46  
47      public <E> E nodeToEntity(Node n, Class<E> clazz) throws NodeConverterException {
48          try {
49              E result = clazz.newInstance();
50              Class objOrSuper = clazz;
51              while (objOrSuper != null) {
52                  for (Field field : objOrSuper.getDeclaredFields()) {
53                      final PropertyConfiguration propertyConfiguration;
54                      if ((propertyConfiguration = field.getAnnotation(PropertyConfiguration.class)) == null) {
55                          continue;
56                      }
57  
58                      if (propertyConfiguration.special() == PropertyConfiguration.SpecialProperty.name) {
59                          field.setAccessible(true);
60                          field.set(result, createObject(field.getType(), n.getSession().getValueFactory().createValue(n.getName())));
61                          continue;
62                      }
63  
64                      if (propertyConfiguration.special() == PropertyConfiguration.SpecialProperty.path) {
65                          field.setAccessible(true);
66                          field.set(result, createObject(field.getType(), n.getSession().getValueFactory().createValue(n.getPath())));
67                          continue;
68                      }
69  
70                      String name = propertyConfiguration.name();
71                      if (name.isEmpty()) {
72                          name = field.getName();
73                      }
74  
75                      if (!n.hasProperty(name)) {
76                          logger.debug("node does not have a property by the name of {}. skipping.", name);
77                          continue;
78                      }
79  
80  
81                      final Property property = n.getProperty(name);
82  
83                      if (property.isMultiple()) {
84                          Type genericFieldType = field.getGenericType();
85  
86                          if (genericFieldType instanceof ParameterizedType) {
87                              ParameterizedType aType = (ParameterizedType) genericFieldType;
88                              Type[] fieldArgTypes = aType.getActualTypeArguments();
89                              if (fieldArgTypes.length != 1) {
90                                  // can not identify parameter
91                                  throw new UnsupportedOperationException("Currently only one generic parameter is supported.");
92                              }
93                              final Class fieldArgClass = (Class) fieldArgTypes[0];
94                              Value[] values = property.getValues();
95  
96                              Object objects = null;
97                              
98                              for (CollectionConverter converter : collectionConverters) {
99                                  if (converter.isApplicableValuesToObjects(field.getType())) {
100                                     objects = converter.toObjects(values, new CollectionConverter.CreateObjectCallback() {
101                                         @Override
102                                         public Object toObject(Value value) throws RepositoryException, NodeConverterException {
103                                             return createObject(fieldArgClass, value);
104                                         }
105                                     });
106                                     break;
107                                 }
108                             }
109 
110                             if(objects == null) {
111                                 throw new UnsupportedOperationException(String.format("Currently no collection converter applicable to class %s is registered.", field.getType()));
112                             }
113 
114                             field.setAccessible(true);
115                             field.set(result, objects);
116                         } else {
117                             throw new UnsupportedOperationException("Collections must be defined using parameterized types.");
118                         }
119                     } else {
120                         Value v = property.getValue();
121                         field.setAccessible(true);
122                         field.set(result, createObject(field.getType(), v));
123                     }
124                 }
125 
126                 objOrSuper = objOrSuper.getSuperclass();
127             }
128 
129             return result;
130         } catch (final Exception ex) {
131             throw new NodeConverterException(ex);
132         }
133     }
134 
135     /**
136      * Object factory from jcr values. Inverse mapping to createValue.
137      *
138      * @param <E>
139      * @param clazz
140      * @param value
141      * @return
142      * @throws RepositoryException
143      */
144     private <E> E createObject(Class<E> clazz, Value value) throws RepositoryException, NodeConverterException {
145 
146         for (Converter c : converters) {
147             if (c.isApplicableValueToObject(clazz)) {
148                 return (E) c.toObject(value);
149             }
150         }
151 
152         throw new UnsupportedOperationException(String.format("valueToObject - Currently no converter applicable to class %s is registered.", clazz));
153     }
154 
155     /**
156      * Wrapper arround valueFactory createValue. This wrapper applies custom
157      * object to value conversion rules.
158      *
159      * @param object
160      * @param valueFactory
161      * @return
162      * @throws RepositoryException
163      */
164     public Value createValue(Object object, ValueFactory valueFactory) throws RepositoryException, NodeConverterException {
165         if (object == null) {
166             return null;
167         }
168 
169         for (Converter c : converters) {
170             if (c.isApplicableObjectToValue(object)) {
171                 return c.toValue(object, valueFactory);
172             }
173         }
174 
175         throw new UnsupportedOperationException(String.format("objectToValue - Currently no converter applicable to  class %s is registered.", object.getClass()));
176     }
177 
178     public <E> Node getOrCreateNode(@NotNull final E entity, final Node parent) throws NodeConverterException {
179         try {
180             Class objOrSuper = entity.getClass();
181             while (objOrSuper != null) {
182                 for (Field field : objOrSuper.getDeclaredFields()) {
183                     final PropertyConfiguration propertyConfiguration;
184                     if ((propertyConfiguration = field.getAnnotation(PropertyConfiguration.class)) == null) {
185                         continue;
186                     }
187 
188                     if (propertyConfiguration.special() == PropertyConfiguration.SpecialProperty.name) {
189                         field.setAccessible(true);
190                         Object fieldValue = field.get(entity);
191                         if(fieldValue == null) {
192                             throw new NullPointerException("Field annotated with PropertyConfiguration.SpecialProperty.name must not be null");
193                         }
194                         String name = createValue(fieldValue, parent.getSession().getValueFactory()).toString();
195                         if (parent.hasNode(name)) {
196                             return parent.getNode(name);
197                         } else {
198                             return parent.addNode(name);
199                         }
200                     }
201 
202                 } // for fields
203                 objOrSuper = objOrSuper.getSuperclass();
204             }
205         } catch (final Exception ex) {
206             throw new NodeConverterException(ex);
207         }
208         throw new IllegalArgumentException("No entity field has been annotated with the SpecialProperty.name");
209     }
210 
211     public <E> void entityToNode(@NotNull final E entity, @NotNull final Node node) throws NodeConverterException {
212         try {
213             ValueFactory valueFactory = node.getSession().getValueFactory();
214 
215             Class objOrSuper = entity.getClass();
216             while (objOrSuper != null) {
217                 for (Field field : objOrSuper.getDeclaredFields()) {
218                     final PropertyConfiguration propertyConfiguration;
219                     if ((propertyConfiguration = field.getAnnotation(PropertyConfiguration.class)) == null) {
220                         continue;
221                     }
222 
223                     // skip special fields                  
224                     switch (propertyConfiguration.special()) {
225                         case name:
226                         case path:
227                             continue;
228                         default:
229                             break;
230                     }
231 
232                     String name = propertyConfiguration.name();
233                     if (name.isEmpty()) {
234                         name = field.getName();
235                     }
236 
237                     field.setAccessible(true);
238                     Object object = field.get(entity);
239                     if (object == null) {
240                         if (node.hasProperty(name)) {
241                             node.getProperty(name).remove();
242                         }
243                     } else {
244                         //if (field.getType().isAssignableFrom(Collection.class)) { // wrong, should be inverse
245                         //if (Collection.class.isAssignableFrom(field.getType())) { // should be equivalent to instance of
246                         if (object instanceof Collection) {
247                             Collection objects = (Collection) object;
248                             List<Value> values = Lists.newArrayListWithCapacity(objects.size());
249                             for (final Object o : objects) { // when migrating to scala use map
250                                 values.add(createValue(o, valueFactory));
251                             }
252                             node.setProperty(name, values.toArray(new Value[values.size()]));
253                         } else if (object instanceof scala.collection.Iterable) { // scala 
254                             Iterator iterator = ((scala.collection.Iterable) object).toIterator();
255                             List<Value> values = Lists.newArrayListWithCapacity(iterator.size());
256                             while (iterator.hasNext()) { // when migrating to scala use map
257                                 values.add(createValue(iterator.next(), valueFactory));
258                             }
259                             node.setProperty(name, values.toArray(new Value[values.size()]));
260                         } else {
261                             node.setProperty(name, createValue(object, valueFactory));
262                         }
263                     }
264 
265                 } // for fields
266                 objOrSuper = objOrSuper.getSuperclass();
267             }
268         } catch (final Exception ex) {
269             throw new NodeConverterException(ex);
270         }
271     }
272 }