001/*
002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Nicolas Chapurlat <nchapurlat@nuxeo.com>
016 */
017
018package org.nuxeo.ecm.core.io.marshallers.json.document;
019
020import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
021import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
022
023import java.io.IOException;
024import java.lang.reflect.Array;
025import java.util.ArrayList;
026import java.util.Iterator;
027import java.util.List;
028import java.util.Map.Entry;
029
030import javax.inject.Inject;
031
032import org.codehaus.jackson.JsonNode;
033import org.nuxeo.ecm.core.api.Blob;
034import org.nuxeo.ecm.core.api.model.Property;
035import org.nuxeo.ecm.core.api.model.impl.ArrayProperty;
036import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
037import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl;
038import org.nuxeo.ecm.core.api.model.impl.PropertyFactory;
039import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
040import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonReader;
041import org.nuxeo.ecm.core.io.registry.MarshallingException;
042import org.nuxeo.ecm.core.io.registry.reflect.Setup;
043import org.nuxeo.ecm.core.schema.SchemaManager;
044import org.nuxeo.ecm.core.schema.types.ComplexType;
045import org.nuxeo.ecm.core.schema.types.Field;
046import org.nuxeo.ecm.core.schema.types.ListType;
047import org.nuxeo.ecm.core.schema.types.Schema;
048import org.nuxeo.ecm.core.schema.types.SimpleType;
049import org.nuxeo.ecm.core.schema.types.Type;
050import org.nuxeo.ecm.core.schema.types.primitives.BinaryType;
051import org.nuxeo.ecm.core.schema.types.primitives.BooleanType;
052import org.nuxeo.ecm.core.schema.types.primitives.DoubleType;
053import org.nuxeo.ecm.core.schema.types.primitives.IntegerType;
054import org.nuxeo.ecm.core.schema.types.primitives.LongType;
055import org.nuxeo.ecm.core.schema.types.primitives.StringType;
056import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver;
057
058/**
059 * Convert Json as {@link List<Property>}.
060 * <p>
061 * Format is:
062 *
063 * <pre>
064 * {
065 *   "schema1Prefix:stringProperty": "stringPropertyValue", <-- each property may be marshall as object if a resolver is associated with that property and if a marshaller exists for the object, in this case, the resulting property will have the corresponding reference value.
066 *   "schema1Prefix:booleanProperty": true|false,
067 *   "schema2Prefix:integerProperty": 123,
068 *   ...
069 *   "schema3Prefix:complexProperty": {
070 *      "subProperty": ...,
071 *      ...
072 *   },
073 *   "schema4Prefix:listProperty": [
074 *      ...
075 *   ]
076 * }
077 * </pre>
078 *
079 * </p>
080 *
081 * @since 7.2
082 */
083@Setup(mode = SINGLETON, priority = REFERENCE)
084public class DocumentPropertiesJsonReader extends AbstractJsonReader<List<Property>> {
085
086    public static String DEFAULT_SCHEMA_NAME = "DEFAULT_SCHEMA_NAME";
087
088    @Inject
089    private SchemaManager schemaManager;
090
091    @Override
092    public List<Property> read(JsonNode jn) throws IOException {
093        List<Property> properties = new ArrayList<Property>();
094        Iterator<Entry<String, JsonNode>> propertyNodes = jn.getFields();
095        while (propertyNodes.hasNext()) {
096            Entry<String, JsonNode> propertyNode = propertyNodes.next();
097            String propertyName = propertyNode.getKey();
098            Field field = null;
099            Property parent = null;
100            if (propertyName.contains(":")) {
101                field = schemaManager.getField(propertyName);
102                if (field == null) {
103                    continue;
104                }
105                parent = new DocumentPartImpl(field.getType().getSchema());
106            } else {
107                String shemaName = ctx.getParameter(DEFAULT_SCHEMA_NAME);
108                Schema schema = schemaManager.getSchema(shemaName);
109                field = schema.getField(propertyName);
110                parent = new DocumentPartImpl(schema);
111            }
112            Property property = readProperty(parent, field, propertyNode.getValue());
113            if (property != null) {
114                properties.add(property);
115            }
116        }
117        return properties;
118    }
119
120    protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException {
121        Property property = PropertyFactory.createProperty(parent, field, 0);
122        if (jn.isNull()) {
123            property.setValue(null);
124        } else if (property.isScalar()) {
125            fillScalarProperty(property, jn);
126        } else if (property.isList()) {
127            fillListProperty(property, jn);
128        } else {
129            if (!(property instanceof BlobProperty)) {
130                fillComplexProperty(property, jn);
131            } else {
132                Blob blob = readEntity(Blob.class, Blob.class, jn);
133                property.setValue(blob);
134            }
135        }
136        return property;
137    }
138
139    private void fillScalarProperty(Property property, JsonNode jn) throws IOException {
140        if ((property instanceof ArrayProperty) && jn.isArray()) {
141            List<Object> values = new ArrayList<Object>();
142            Iterator<JsonNode> it = jn.getElements();
143            JsonNode item;
144            Type fieldType = ((ListType) property.getType()).getFieldType();
145            while (it.hasNext()) {
146                item = it.next();
147                values.add(getScalarPropertyValue(property, item, fieldType));
148            }
149            property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values));
150        } else {
151            property.setValue(getScalarPropertyValue(property, jn, property.getType()));
152        }
153    }
154
155    @SuppressWarnings({ "unchecked" })
156    private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException {
157        if (type instanceof StringType) {
158            return values.toArray((T[]) Array.newInstance(String.class, values.size()));
159        } else if (type instanceof BooleanType) {
160            return values.toArray((T[]) Array.newInstance(Boolean.class, values.size()));
161        } else if (type instanceof LongType) {
162            return values.toArray((T[]) Array.newInstance(Long.class, values.size()));
163        } else if (type instanceof DoubleType) {
164            return values.toArray((T[]) Array.newInstance(Double.class, values.size()));
165        } else if (type instanceof IntegerType) {
166            return values.toArray((T[]) Array.newInstance(Integer.class, values.size()));
167        } else if (type instanceof BinaryType) {
168            return values.toArray((T[]) Array.newInstance(Byte.class, values.size()));
169        }
170        throw new MarshallingException("Primitive type not found: " + type.getName());
171    }
172
173    private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException {
174        Object value;
175        if (jn.isObject()) {
176            ObjectResolver resolver = type.getObjectResolver();
177            if (resolver == null) {
178                throw new MarshallingException("Unable to parse the property " + property.getPath());
179            }
180            Object object = null;
181            for (Class<?> clazz : resolver.getManagedClasses()) {
182                try {
183                    object = readEntity(clazz, clazz, jn);
184                } catch (Exception e) {
185                    continue;
186                }
187            }
188            if (object == null) {
189                throw new MarshallingException("Unable to parse the property " + property.getPath());
190            }
191            value = resolver.getReference(object);
192            if (value == null) {
193                throw new MarshallingException("Property " + property.getPath()
194                        + " value cannot be resolved by the matching resolver " + resolver.getName());
195            }
196        } else {
197            value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn);
198        }
199        return value;
200    }
201
202    private Object getPropertyValue(Type type, JsonNode jn) throws IOException {
203        Object value;
204        if (jn.isNull()) {
205            value = null;
206        } else if (type instanceof BooleanType) {
207            value = jn.getValueAsBoolean();
208        } else if (type instanceof LongType) {
209            value = jn.getValueAsLong();
210        } else if (type instanceof DoubleType) {
211            value = jn.getValueAsDouble();
212        } else if (type instanceof IntegerType) {
213            value = jn.getValueAsInt();
214        } else if (type instanceof BinaryType) {
215            value = jn.getBinaryValue();
216        } else {
217            value = type.decode(jn.getValueAsText());
218        }
219        return value;
220    }
221
222    private void fillListProperty(Property property, JsonNode jn) throws IOException {
223        ListType listType = (ListType) property.getType();
224        if (property instanceof ArrayProperty) {
225            fillScalarProperty(property, jn);
226        } else {
227            JsonNode elNode = null;
228            Iterator<JsonNode> it = jn.getElements();
229            while (it.hasNext()) {
230                elNode = it.next();
231                Property child = readProperty(property, listType.getField(), elNode);
232                property.addValue(child.getValue());
233            }
234        }
235    }
236
237    private void fillComplexProperty(Property property, JsonNode jn) throws IOException {
238        Entry<String, JsonNode> elNode = null;
239        Iterator<Entry<String, JsonNode>> it = jn.getFields();
240        ComplexProperty complexProperty = (ComplexProperty) property;
241        ComplexType type = complexProperty.getType();
242        while (it.hasNext()) {
243            elNode = it.next();
244            String elName = elNode.getKey();
245            Field field = type.getField(elName);
246            if (field != null) {
247                Property child = readProperty(property, field, elNode.getValue());
248                property.setValue(elName, child.getValue());
249            }
250        }
251    }
252
253}