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}