/** * This file is part of the JCROM project. * Copyright (C) 2008-2014 - All rights reserved. * Authors: Olafur Gauti Gudmundsson, Nicolas Dos Santos * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jcrom; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.jcr.Binary; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.ValueFactory; import javafx.beans.property.ListProperty; import javafx.beans.property.MapProperty; import org.jcrom.annotations.JcrProperty; import org.jcrom.annotations.JcrProtectedProperty; import org.jcrom.annotations.JcrSerializedProperty; import org.jcrom.util.JcrUtils; import org.jcrom.util.NodeFilter; import org.jcrom.util.ReflectionUtils; import static org.jcrom.util.JavaFXUtils.*; /** * This class handles mappings of type @JcrProperty * * @author Olafur Gauti Gudmundsson * @author Nicolas Dos Santos */ public class PropertyMapper { private final Mapper mapper; public PropertyMapper(Mapper mapper) { this.mapper = mapper; } void mapPropertiesToMap(Object obj, Field field, Class<?> valueType, PropertyIterator propIterator, boolean ignoreReadOnlyProperties) throws RepositoryException, IOException, IllegalAccessException { Map<String, Object> map = new HashMap<String, Object>(); while (propIterator.hasNext()) { Property p = propIterator.nextProperty(); // we ignore the read-only properties added by the repository if (!ignoreReadOnlyProperties || (!p.getName().startsWith("jcr:") && !p.getName().startsWith(NamespaceRegistry.NAMESPACE_JCR) && !p.getName().startsWith("nt:") && !p.getName().startsWith(NamespaceRegistry.NAMESPACE_NT))) { if (valueType.isArray()) { if (p.getDefinition().isMultiple()) { map.put(p.getName(), valuesToArray(valueType.getComponentType(), p.getValues())); } else { Value[] values = new Value[1]; values[0] = p.getValue(); map.put(p.getName(), valuesToArray(valueType.getComponentType(), values)); } } else { map.put(p.getName(), JcrUtils.getValue(valueType, p.getValue())); } } } setObject(field, obj, map); } private Object[] valuesToArray(Class<?> type, Value[] values) throws RepositoryException, IOException { Object[] arr = (Object[]) Array.newInstance(type, values.length); for (int i = 0; i < values.length; i++) { arr[i] = JcrUtils.getValue(type, values[i]); } return arr; } void mapSerializedPropertyToField(Object obj, Field field, Node node, int depth, NodeFilter nodeFilter) throws RepositoryException, IOException, IllegalAccessException, ClassNotFoundException { String propertyName = getSerializedPropertyName(field); if (nodeFilter == null || nodeFilter.isIncluded(NodeFilter.PROPERTY_PREFIX + field.getName(), node, depth)) { if (node.hasProperty(propertyName)) { Property p = node.getProperty(propertyName); //field.set(obj, deserialize(p.getStream())); setObject(field, obj, deserialize(p.getBinary().getStream())); } } } protected String getSerializedPropertyName(Field field) { JcrSerializedProperty jcrProperty = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrSerializedProperty.class); String propertyName = field.getName(); if (!jcrProperty.name().equals(Mapper.DEFAULT_FIELDNAME)) { propertyName = jcrProperty.name(); } return propertyName; } protected String getPropertyName(Field field) { JcrProperty jcrProperty = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrProperty.class); String name = field.getName(); if (!jcrProperty.name().equals(Mapper.DEFAULT_FIELDNAME)) { name = jcrProperty.name(); } return name; } protected String getProtectedPropertyName(Field field) { JcrProtectedProperty jcrProperty = mapper.getJcrom().getAnnotationReader().getAnnotation(field, JcrProtectedProperty.class); String name = field.getName(); if (!jcrProperty.name().equals(Mapper.DEFAULT_FIELDNAME)) { name = jcrProperty.name(); } return name; } void mapPropertyToField(Object obj, Field field, Node node, int depth, NodeFilter nodeFilter) throws RepositoryException, IllegalAccessException, IOException { String name = getPropertyName(field); if (nodeFilter == null || nodeFilter.isIncluded(NodeFilter.PROPERTY_PREFIX + field.getName(), node, depth)) { if (isMap(field)) { // map of properties Class<?> valueType = ReflectionUtils.getParameterizedClass(field, 1); try { Node childrenContainer = node.getNode(name); PropertyIterator propIterator = childrenContainer.getProperties(); mapPropertiesToMap(obj, field, valueType, propIterator, true); } catch (PathNotFoundException pne) { // ignore here as the Field could have been added to the model // since the Node was created and not yet been populated. } } else { mapToField(name, field, obj, node); } } } void mapProtectedPropertyToField(Object obj, Field field, Node node) throws RepositoryException, IllegalAccessException, IOException { String name = getProtectedPropertyName(field); if (isMap(field)) { // map of properties Class<?> valueType = ReflectionUtils.getParameterizedClass(field, 1); Node childrenContainer = node.getNode(name); PropertyIterator propIterator = childrenContainer.getProperties(); mapPropertiesToMap(obj, field, valueType, propIterator, false); } else { mapToField(name, field, obj, node); } } void mapToField(String propertyName, Field field, Object obj, Node node) throws RepositoryException, IllegalAccessException, IOException { if (node.hasProperty(propertyName)) { Property p = node.getProperty(propertyName); if (ReflectionUtils.implementsInterface(field.getType(), List.class)) { // multi-value property (List) List<Object> properties = new ArrayList<Object>(); Class<?> paramClass = ReflectionUtils.getParameterizedClass(field); for (Value value : p.getValues()) { properties.add(JcrUtils.getValue(paramClass, value)); } setObject(field, obj, properties); } else if (ListProperty.class.isAssignableFrom(field.getType())) { ListProperty<Object> list = (ListProperty) field.get(obj); List<Object> properties = new ArrayList<Object>(); Class<?> paramClass = ReflectionUtils.getParameterizedClass(field); for (Value value : p.getValues()) { properties.add(JcrUtils.getValue(paramClass, value)); } list.setAll(properties); } else if (field.getType().isArray() && field.getType().getComponentType() != byte.class) { // multi-value property (array) Value[] values = p.getValues(); if (field.getType().getComponentType() == int.class) { int[] arr = new int[values.length]; for (int i = 0; i < values.length; i++) { arr[i] = (int) values[i].getDouble(); } setObject(field, obj, arr); } else if (field.getType().getComponentType() == long.class) { long[] arr = new long[values.length]; for (int i = 0; i < values.length; i++) { arr[i] = values[i].getLong(); } setObject(field, obj, arr); } else if (field.getType().getComponentType() == double.class) { double[] arr = new double[values.length]; for (int i = 0; i < values.length; i++) { arr[i] = values[i].getDouble(); } setObject(field, obj, arr); } else if (field.getType().getComponentType() == boolean.class) { boolean[] arr = new boolean[values.length]; for (int i = 0; i < values.length; i++) { arr[i] = values[i].getBoolean(); } setObject(field, obj, arr); } else if (field.getType().getComponentType() == Locale.class) { Locale[] arr = new Locale[values.length]; for (int i = 0; i < values.length; i++) { arr[i] = JcrUtils.parseLocale(values[i].getString()); } setObject(field, obj, arr); } else { Object[] arr = valuesToArray(field.getType().getComponentType(), values); setObject(field, obj, arr); } } else { // single-value property setObject(field, obj, getValue(field, obj, p)); } } } private void mapSerializedFieldToProperty(Field field, Object obj, Node node, int depth, NodeFilter nodeFilter) throws IllegalAccessException, RepositoryException, IOException { String propertyName = getSerializedPropertyName(field); Object fieldValue = getObject(field, obj); // make sure that this property is supposed to be updated if (nodeFilter == null || nodeFilter.isIncluded(NodeFilter.PROPERTY_PREFIX + field.getName(), node, depth)) { if (fieldValue != null) { // serialize and store //node.setProperty(propertyName, new ByteArrayInputStream(serialize(fieldValue))); ValueFactory valueFactory = node.getSession().getValueFactory(); Binary binary = valueFactory.createBinary(new ByteArrayInputStream(serialize(fieldValue))); node.setProperty(propertyName, binary); } else { // remove the value node.setProperty(propertyName, (Value) null); } } } void addSerializedProperty(Field field, Object obj, Node node) throws RepositoryException, IllegalAccessException, IOException { mapSerializedFieldToProperty(field, obj, node, NodeFilter.DEPTH_INFINITE, null); } void updateSerializedProperty(Field field, Object obj, Node node, int depth, NodeFilter nodeFilter) throws RepositoryException, IllegalAccessException, IOException { mapSerializedFieldToProperty(field, obj, node, depth, nodeFilter); } @SuppressWarnings("unchecked") private void addChildMap(Field field, Object obj, Node node, String nodeName, Mapper mapper) throws RepositoryException, IllegalAccessException { Map<String, Object> map = (Map<String, Object>) field.get(obj); boolean nullOrEmpty = map == null || map.isEmpty(); // remove the child node NodeIterator nodeIterator = node.getNodes(nodeName); while (nodeIterator.hasNext()) { nodeIterator.nextNode().remove(); } // add the map as a child node Node childContainer = node.addNode(mapper.getCleanName(nodeName)); if (!nullOrEmpty) { for (Map.Entry<String, Object> entry : map.entrySet()) { mapToProperty(entry.getKey(), ReflectionUtils.getParameterizedClass(field, 1), null, entry.getValue(), childContainer); } } } private void mapFieldToProperty(Field field, Object obj, Node node, int depth, NodeFilter nodeFilter, Mapper mapper) throws RepositoryException, IllegalAccessException { String name = getPropertyName(field); // make sure that this property is supposed to be updated if (nodeFilter == null || nodeFilter.isIncluded(NodeFilter.PROPERTY_PREFIX + field.getName(), node, depth)) { if (isMap(field)) { // this is a Map child, where we map the key/value pairs as properties addChildMap(field, obj, node, name, mapper); } else { // normal property Class<?> paramClass = isList(field.getType()) ? ReflectionUtils.getParameterizedClass(field) : null; mapToProperty(name, field.getType(), paramClass, field.get(obj), node); } } } void addProperty(Field field, Object obj, Node node, Mapper mapper) throws RepositoryException, IllegalAccessException { mapFieldToProperty(field, obj, node, NodeFilter.DEPTH_INFINITE, null, mapper); } void updateProperty(Field field, Object obj, Node node, int depth, NodeFilter nodeFilter, Mapper mapper) throws RepositoryException, IllegalAccessException { mapFieldToProperty(field, obj, node, depth, nodeFilter, mapper); } void mapToProperty(String propertyName, Class<?> type, Class<?> paramClass, Object propertyValue, Node node) throws RepositoryException { // make sure that the field value is not null if (propertyValue != null) { ValueFactory valueFactory = node.getSession().getValueFactory(); if (isList(type)) { // multi-value property List List<?> fieldValues = (List<?>) propertyValue; if (!fieldValues.isEmpty()) { Value[] values = new Value[fieldValues.size()]; for (int i = 0; i < fieldValues.size(); i++) { values[i] = JcrUtils.createValue(paramClass, fieldValues.get(i), valueFactory); } node.setProperty(propertyName, values); } else { node.setProperty(propertyName, new Value[0]); } } else if (type.isArray() && type.getComponentType() != byte.class) { // multi-value property array Value[] values; if (type.getComponentType() == int.class) { int[] ints = (int[]) propertyValue; values = new Value[ints.length]; for (int i = 0; i < ints.length; i++) { values[i] = JcrUtils.createValue(int.class, ints[i], valueFactory); } } else if (type.getComponentType() == long.class) { long[] longs = (long[]) propertyValue; values = new Value[longs.length]; for (int i = 0; i < longs.length; i++) { values[i] = JcrUtils.createValue(long.class, longs[i], valueFactory); } } else if (type.getComponentType() == double.class) { double[] doubles = (double[]) propertyValue; values = new Value[doubles.length]; for (int i = 0; i < doubles.length; i++) { values[i] = JcrUtils.createValue(double.class, doubles[i], valueFactory); } } else if (type.getComponentType() == boolean.class) { boolean[] booleans = (boolean[]) propertyValue; values = new Value[booleans.length]; for (int i = 0; i < booleans.length; i++) { values[i] = JcrUtils.createValue(boolean.class, booleans[i], valueFactory); } } else if (type.getComponentType() == Locale.class) { Locale[] locales = (Locale[]) propertyValue; values = new Value[locales.length]; for (int i = 0; i < locales.length; i++) { values[i] = JcrUtils.createValue(Locale.class, locales[i], valueFactory); } } else { // Object Object[] objects = (Object[]) propertyValue; values = new Value[objects.length]; for (int i = 0; i < objects.length; i++) { values[i] = JcrUtils.createValue(type.getComponentType(), objects[i], valueFactory); } } node.setProperty(propertyName, values); } else { // single-value property Value value = JcrUtils.createValue(type, propertyValue, valueFactory); if (value != null) { node.setProperty(propertyName, value); } } } else { // remove the value if (isList(type)) { node.setProperty(propertyName, (Value[]) null); } else { node.setProperty(propertyName, (Value) null); } } } /** * Serialize an object to a byte array. * * @param obj * the object to be serialized * @return the serialized object * @throws java.lang.Exception */ byte[] serialize(Object obj) throws IOException { // Serialize to a byte array ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream out = new ObjectOutputStream(bos); try { out.writeObject(obj); } finally { out.close(); } // Get the bytes of the serialized object return bos.toByteArray(); } /** * Deserialize an object from a byte array. * * @param bytes * @return * @throws java.lang.Exception */ private Object deserialize(InputStream byteStream) throws IOException, ClassNotFoundException { // Deserialize from a byte array ObjectInputStream in = new ObjectInputStream(byteStream); try { return in.readObject(); } finally { in.close(); } } }