/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * Nicolas Chapurlat <nchapurlat@nuxeo.com> */ package org.nuxeo.ecm.core.io.marshallers.json.document; import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import javax.inject.Inject; import org.codehaus.jackson.JsonNode; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.model.Property; import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; import org.nuxeo.ecm.core.api.model.impl.PropertyFactory; import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonReader; import org.nuxeo.ecm.core.io.registry.MarshallingException; import org.nuxeo.ecm.core.io.registry.reflect.Setup; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.schema.types.ComplexType; import org.nuxeo.ecm.core.schema.types.Field; import org.nuxeo.ecm.core.schema.types.ListType; import org.nuxeo.ecm.core.schema.types.Schema; import org.nuxeo.ecm.core.schema.types.SimpleType; import org.nuxeo.ecm.core.schema.types.Type; import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; import org.nuxeo.ecm.core.schema.types.primitives.DoubleType; import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; import org.nuxeo.ecm.core.schema.types.primitives.LongType; import org.nuxeo.ecm.core.schema.types.primitives.StringType; import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; /** * Convert Json as {@link List<Property>}. * <p> * Format is: * * <pre> * { * "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. * "schema1Prefix:booleanProperty": true|false, * "schema2Prefix:integerProperty": 123, * ... * "schema3Prefix:complexProperty": { * "subProperty": ..., * ... * }, * "schema4Prefix:listProperty": [ * ... * ] * } * </pre> * * </p> * * @since 7.2 */ @Setup(mode = SINGLETON, priority = REFERENCE) public class DocumentPropertiesJsonReader extends AbstractJsonReader<List<Property>> { public static String DEFAULT_SCHEMA_NAME = "DEFAULT_SCHEMA_NAME"; @Inject private SchemaManager schemaManager; @Override public List<Property> read(JsonNode jn) throws IOException { List<Property> properties = new ArrayList<Property>(); Iterator<Entry<String, JsonNode>> propertyNodes = jn.getFields(); while (propertyNodes.hasNext()) { Entry<String, JsonNode> propertyNode = propertyNodes.next(); String propertyName = propertyNode.getKey(); Field field = null; Property parent = null; if (propertyName.contains(":")) { field = schemaManager.getField(propertyName); if (field == null) { continue; } parent = new DocumentPartImpl(field.getType().getSchema()); } else { String shemaName = ctx.getParameter(DEFAULT_SCHEMA_NAME); Schema schema = schemaManager.getSchema(shemaName); if (schema == null) { continue; } field = schema.getField(propertyName); parent = new DocumentPartImpl(schema); } if (field == null) { continue; } Property property = readProperty(parent, field, propertyNode.getValue()); if (property != null) { properties.add(property); } } return properties; } protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException { Property property = PropertyFactory.createProperty(parent, field, 0); if (jn.isNull()) { property.setValue(null); } else if (property.isScalar()) { fillScalarProperty(property, jn); } else if (property.isList()) { fillListProperty(property, jn); } else { if (!(property instanceof BlobProperty)) { fillComplexProperty(property, jn); } else { Blob blob = readEntity(Blob.class, Blob.class, jn); property.setValue(blob); } } return property; } private void fillScalarProperty(Property property, JsonNode jn) throws IOException { if ((property instanceof ArrayProperty) && jn.isArray()) { List<Object> values = new ArrayList<Object>(); Iterator<JsonNode> it = jn.getElements(); JsonNode item; Type fieldType = ((ListType) property.getType()).getFieldType(); while (it.hasNext()) { item = it.next(); values.add(getScalarPropertyValue(property, item, fieldType)); } property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values)); } else { property.setValue(getScalarPropertyValue(property, jn, property.getType())); } } @SuppressWarnings({ "unchecked" }) private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException { if (type instanceof StringType) { return values.toArray((T[]) Array.newInstance(String.class, values.size())); } else if (type instanceof BooleanType) { return values.toArray((T[]) Array.newInstance(Boolean.class, values.size())); } else if (type instanceof LongType) { return values.toArray((T[]) Array.newInstance(Long.class, values.size())); } else if (type instanceof DoubleType) { return values.toArray((T[]) Array.newInstance(Double.class, values.size())); } else if (type instanceof IntegerType) { return values.toArray((T[]) Array.newInstance(Integer.class, values.size())); } else if (type instanceof BinaryType) { return values.toArray((T[]) Array.newInstance(Byte.class, values.size())); } throw new MarshallingException("Primitive type not found: " + type.getName()); } private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException { Object value; if (jn.isObject()) { ObjectResolver resolver = type.getObjectResolver(); if (resolver == null) { if (type.getSuperType() != null && type.getSuperType() instanceof StringType) { // Let's assume it is a blob of which content has to be stored in a string property. Blob blob = readEntity(Blob.class, Blob.class, jn); if (blob == null) { throw new MarshallingException("Unable to parse the property " + property.getXPath()); } return blob.getString(); } } Object object = null; for (Class<?> clazz : resolver.getManagedClasses()) { try { object = readEntity(clazz, clazz, jn); } catch (Exception e) { continue; } } if (object == null) { throw new MarshallingException("Unable to parse the property " + property.getXPath()); } value = resolver.getReference(object); if (value == null) { throw new MarshallingException("Property " + property.getXPath() + " value cannot be resolved by the matching resolver " + resolver.getName()); } } else { value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn); } return value; } private Object getPropertyValue(Type type, JsonNode jn) throws IOException { Object value; if (jn.isNull()) { value = null; } else if (type instanceof BooleanType) { value = jn.getValueAsBoolean(); } else if (type instanceof LongType) { value = jn.getValueAsLong(); } else if (type instanceof DoubleType) { value = jn.getValueAsDouble(); } else if (type instanceof IntegerType) { value = jn.getValueAsInt(); } else if (type instanceof BinaryType) { value = jn.getBinaryValue(); } else { value = type.decode(jn.getValueAsText()); } return value; } private void fillListProperty(Property property, JsonNode jn) throws IOException { ListType listType = (ListType) property.getType(); if (property instanceof ArrayProperty) { fillScalarProperty(property, jn); } else { JsonNode elNode = null; Iterator<JsonNode> it = jn.getElements(); while (it.hasNext()) { elNode = it.next(); Property child = readProperty(property, listType.getField(), elNode); property.addValue(child.getValue()); } } } private void fillComplexProperty(Property property, JsonNode jn) throws IOException { Entry<String, JsonNode> elNode = null; Iterator<Entry<String, JsonNode>> it = jn.getFields(); ComplexProperty complexProperty = (ComplexProperty) property; ComplexType type = complexProperty.getType(); while (it.hasNext()) { elNode = it.next(); String elName = elNode.getKey(); Field field = type.getField(elName); if (field != null) { Property child = readProperty(property, field, elNode.getValue()); property.setValue(elName, child.getValue()); } } } }