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 if (type.getSuperType() != null && type.getSuperType() instanceof StringType) { 187 // Let's assume it is a blob of which content has to be stored in a string property. 188 Blob blob = readEntity(Blob.class, Blob.class, jn); 189 if (blob == null) { 190 throw new MarshallingException("Unable to parse the property " + property.getPath()); 191 } 192 return blob.getString(); 193 } 194 } 195 Object object = null; 196 for (Class<?> clazz : resolver.getManagedClasses()) { 197 try { 198 object = readEntity(clazz, clazz, jn); 199 } catch (Exception e) { 200 continue; 201 } 202 } 203 if (object == null) { 204 throw new MarshallingException("Unable to parse the property " + property.getPath()); 205 } 206 value = resolver.getReference(object); 207 if (value == null) { 208 throw new MarshallingException("Property " + property.getPath() 209 + " value cannot be resolved by the matching resolver " + resolver.getName()); 210 } 211 } else { 212 value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn); 213 } 214 return value; 215 } 216 217 private Object getPropertyValue(Type type, JsonNode jn) throws IOException { 218 Object value; 219 if (jn.isNull()) { 220 value = null; 221 } else if (type instanceof BooleanType) { 222 value = jn.getValueAsBoolean(); 223 } else if (type instanceof LongType) { 224 value = jn.getValueAsLong(); 225 } else if (type instanceof DoubleType) { 226 value = jn.getValueAsDouble(); 227 } else if (type instanceof IntegerType) { 228 value = jn.getValueAsInt(); 229 } else if (type instanceof BinaryType) { 230 value = jn.getBinaryValue(); 231 } else { 232 value = type.decode(jn.getValueAsText()); 233 } 234 return value; 235 } 236 237 private void fillListProperty(Property property, JsonNode jn) throws IOException { 238 ListType listType = (ListType) property.getType(); 239 if (property instanceof ArrayProperty) { 240 fillScalarProperty(property, jn); 241 } else { 242 JsonNode elNode = null; 243 Iterator<JsonNode> it = jn.getElements(); 244 while (it.hasNext()) { 245 elNode = it.next(); 246 Property child = readProperty(property, listType.getField(), elNode); 247 property.addValue(child.getValue()); 248 } 249 } 250 } 251 252 private void fillComplexProperty(Property property, JsonNode jn) throws IOException { 253 Entry<String, JsonNode> elNode = null; 254 Iterator<Entry<String, JsonNode>> it = jn.getFields(); 255 ComplexProperty complexProperty = (ComplexProperty) property; 256 ComplexType type = complexProperty.getType(); 257 while (it.hasNext()) { 258 elNode = it.next(); 259 String elName = elNode.getKey(); 260 Field field = type.getField(elName); 261 if (field != null) { 262 Property child = readProperty(property, field, elNode.getValue()); 263 property.setValue(elName, child.getValue()); 264 } 265 } 266 } 267 268}