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                if (schema == null) {
112                    continue;
113                }
114                field = schema.getField(propertyName);
115                parent = new DocumentPartImpl(schema);
116            }
117            if (field == null) {
118                continue;
119            }
120            Property property = readProperty(parent, field, propertyNode.getValue());
121            if (property != null) {
122                properties.add(property);
123            }
124        }
125        return properties;
126    }
127
128    protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException {
129        Property property = PropertyFactory.createProperty(parent, field, 0);
130        if (jn.isNull()) {
131            property.setValue(null);
132        } else if (property.isScalar()) {
133            fillScalarProperty(property, jn);
134        } else if (property.isList()) {
135            fillListProperty(property, jn);
136        } else {
137            if (!(property instanceof BlobProperty)) {
138                fillComplexProperty(property, jn);
139            } else {
140                Blob blob = readEntity(Blob.class, Blob.class, jn);
141                property.setValue(blob);
142            }
143        }
144        return property;
145    }
146
147    private void fillScalarProperty(Property property, JsonNode jn) throws IOException {
148        if ((property instanceof ArrayProperty) && jn.isArray()) {
149            List<Object> values = new ArrayList<Object>();
150            Iterator<JsonNode> it = jn.getElements();
151            JsonNode item;
152            Type fieldType = ((ListType) property.getType()).getFieldType();
153            while (it.hasNext()) {
154                item = it.next();
155                values.add(getScalarPropertyValue(property, item, fieldType));
156            }
157            property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values));
158        } else {
159            property.setValue(getScalarPropertyValue(property, jn, property.getType()));
160        }
161    }
162
163    @SuppressWarnings({ "unchecked" })
164    private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException {
165        if (type instanceof StringType) {
166            return values.toArray((T[]) Array.newInstance(String.class, values.size()));
167        } else if (type instanceof BooleanType) {
168            return values.toArray((T[]) Array.newInstance(Boolean.class, values.size()));
169        } else if (type instanceof LongType) {
170            return values.toArray((T[]) Array.newInstance(Long.class, values.size()));
171        } else if (type instanceof DoubleType) {
172            return values.toArray((T[]) Array.newInstance(Double.class, values.size()));
173        } else if (type instanceof IntegerType) {
174            return values.toArray((T[]) Array.newInstance(Integer.class, values.size()));
175        } else if (type instanceof BinaryType) {
176            return values.toArray((T[]) Array.newInstance(Byte.class, values.size()));
177        }
178        throw new MarshallingException("Primitive type not found: " + type.getName());
179    }
180
181    private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException {
182        Object value;
183        if (jn.isObject()) {
184            ObjectResolver resolver = type.getObjectResolver();
185            if (resolver == null) {
186                throw new MarshallingException("Unable to parse the property " + property.getPath());
187            }
188            Object object = null;
189            for (Class<?> clazz : resolver.getManagedClasses()) {
190                try {
191                    object = readEntity(clazz, clazz, jn);
192                } catch (Exception e) {
193                    continue;
194                }
195            }
196            if (object == null) {
197                throw new MarshallingException("Unable to parse the property " + property.getPath());
198            }
199            value = resolver.getReference(object);
200            if (value == null) {
201                throw new MarshallingException("Property " + property.getPath()
202                        + " value cannot be resolved by the matching resolver " + resolver.getName());
203            }
204        } else {
205            value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn);
206        }
207        return value;
208    }
209
210    private Object getPropertyValue(Type type, JsonNode jn) throws IOException {
211        Object value;
212        if (jn.isNull()) {
213            value = null;
214        } else if (type instanceof BooleanType) {
215            value = jn.getValueAsBoolean();
216        } else if (type instanceof LongType) {
217            value = jn.getValueAsLong();
218        } else if (type instanceof DoubleType) {
219            value = jn.getValueAsDouble();
220        } else if (type instanceof IntegerType) {
221            value = jn.getValueAsInt();
222        } else if (type instanceof BinaryType) {
223            value = jn.getBinaryValue();
224        } else {
225            value = type.decode(jn.getValueAsText());
226        }
227        return value;
228    }
229
230    private void fillListProperty(Property property, JsonNode jn) throws IOException {
231        ListType listType = (ListType) property.getType();
232        if (property instanceof ArrayProperty) {
233            fillScalarProperty(property, jn);
234        } else {
235            JsonNode elNode = null;
236            Iterator<JsonNode> it = jn.getElements();
237            while (it.hasNext()) {
238                elNode = it.next();
239                Property child = readProperty(property, listType.getField(), elNode);
240                property.addValue(child.getValue());
241            }
242        }
243    }
244
245    private void fillComplexProperty(Property property, JsonNode jn) throws IOException {
246        Entry<String, JsonNode> elNode = null;
247        Iterator<Entry<String, JsonNode>> it = jn.getFields();
248        ComplexProperty complexProperty = (ComplexProperty) property;
249        ComplexType type = complexProperty.getType();
250        while (it.hasNext()) {
251            elNode = it.next();
252            String elName = elNode.getKey();
253            Field field = type.getField(elName);
254            if (field != null) {
255                Property child = readProperty(property, field, elNode.getValue());
256                property.setValue(elName, child.getValue());
257            }
258        }
259    }
260
261}