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