001/* 002 * (C) Copyright 2015-2017 Nuxeo (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 */ 019package org.nuxeo.ecm.core.io.marshallers.json.document; 020 021import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON; 022import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE; 023 024import java.io.IOException; 025import java.lang.reflect.Array; 026import java.util.ArrayList; 027import java.util.Calendar; 028import java.util.Iterator; 029import java.util.List; 030import java.util.Map.Entry; 031 032import javax.inject.Inject; 033 034import org.nuxeo.ecm.core.api.Blob; 035import org.nuxeo.ecm.core.api.model.Property; 036import org.nuxeo.ecm.core.api.model.impl.ArrayProperty; 037import org.nuxeo.ecm.core.api.model.impl.ComplexProperty; 038import org.nuxeo.ecm.core.api.model.impl.DocumentPartImpl; 039import org.nuxeo.ecm.core.api.model.impl.PropertyFactory; 040import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty; 041import org.nuxeo.ecm.core.io.marshallers.json.AbstractJsonReader; 042import org.nuxeo.ecm.core.io.registry.MarshallingException; 043import org.nuxeo.ecm.core.io.registry.reflect.Setup; 044import org.nuxeo.ecm.core.schema.SchemaManager; 045import org.nuxeo.ecm.core.schema.types.ComplexType; 046import org.nuxeo.ecm.core.schema.types.Field; 047import org.nuxeo.ecm.core.schema.types.ListType; 048import org.nuxeo.ecm.core.schema.types.Schema; 049import org.nuxeo.ecm.core.schema.types.SimpleType; 050import org.nuxeo.ecm.core.schema.types.Type; 051import org.nuxeo.ecm.core.schema.types.primitives.BinaryType; 052import org.nuxeo.ecm.core.schema.types.primitives.BooleanType; 053import org.nuxeo.ecm.core.schema.types.primitives.DateType; 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 060import com.fasterxml.jackson.databind.JsonNode; 061 062/** 063 * Convert Json as {@link List<Property>}. 064 * <p> 065 * Format is: 066 * 067 * <pre> 068 * { 069 * "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. 070 * "schema1Prefix:booleanProperty": true|false, 071 * "schema2Prefix:integerProperty": 123, 072 * ... 073 * "schema3Prefix:complexProperty": { 074 * "subProperty": ..., 075 * ... 076 * }, 077 * "schema4Prefix:listProperty": [ 078 * ... 079 * ] 080 * } 081 * </pre> 082 * </p> 083 * 084 * @since 7.2 085 */ 086@Setup(mode = SINGLETON, priority = REFERENCE) 087public class DocumentPropertiesJsonReader extends AbstractJsonReader<List<Property>> { 088 089 public static final String DEFAULT_SCHEMA_NAME = "DEFAULT_SCHEMA_NAME"; 090 091 @Inject 092 private SchemaManager schemaManager; 093 094 @Override 095 public List<Property> read(JsonNode jn) throws IOException { 096 List<Property> properties = new ArrayList<>(); 097 Iterator<Entry<String, JsonNode>> propertyNodes = jn.fields(); 098 while (propertyNodes.hasNext()) { 099 Entry<String, JsonNode> propertyNode = propertyNodes.next(); 100 String propertyName = propertyNode.getKey(); 101 Field field; 102 Property parent; 103 if (propertyName.contains(":")) { 104 field = schemaManager.getField(propertyName); 105 if (field == null) { 106 continue; 107 } 108 parent = new DocumentPartImpl(field.getDeclaringType().getSchema()); 109 } else { 110 String shemaName = ctx.getParameter(DEFAULT_SCHEMA_NAME); 111 Schema schema = schemaManager.getSchema(shemaName); 112 if (schema == null) { 113 continue; 114 } 115 field = schema.getField(propertyName); 116 parent = new DocumentPartImpl(schema); 117 } 118 if (field == null) { 119 continue; 120 } 121 Property property = readProperty(parent, field, propertyNode.getValue()); 122 if (property != null) { 123 properties.add(property); 124 } 125 } 126 return properties; 127 } 128 129 protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException { 130 Property property = PropertyFactory.createProperty(parent, field, 0); 131 if (jn.isNull()) { 132 property.setValue(null); 133 } else if (property.isScalar()) { 134 fillScalarProperty(property, jn); 135 } else if (property.isList()) { 136 fillListProperty(property, jn); 137 } else { 138 if (!(property instanceof BlobProperty)) { 139 fillComplexProperty(property, jn); 140 } else { 141 Blob blob = readEntity(Blob.class, Blob.class, jn); 142 property.setValue(blob); 143 } 144 } 145 return property; 146 } 147 148 private void fillScalarProperty(Property property, JsonNode jn) throws IOException { 149 if ((property instanceof ArrayProperty) && jn.isArray()) { 150 List<Object> values = new ArrayList<>(); 151 Iterator<JsonNode> it = jn.elements(); 152 JsonNode item; 153 Type fieldType = ((ListType) property.getType()).getFieldType(); 154 while (it.hasNext()) { 155 item = it.next(); 156 values.add(getScalarPropertyValue(property, item, fieldType)); 157 } 158 property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values)); 159 } else { 160 property.setValue(getScalarPropertyValue(property, jn, property.getType())); 161 } 162 } 163 164 @SuppressWarnings({ "unchecked" }) 165 private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException { 166 if (type instanceof StringType) { 167 return values.toArray((T[]) Array.newInstance(String.class, values.size())); 168 } else if (type instanceof BooleanType) { 169 return values.toArray((T[]) Array.newInstance(Boolean.class, values.size())); 170 } else if (type instanceof LongType) { 171 return values.toArray((T[]) Array.newInstance(Long.class, values.size())); 172 } else if (type instanceof DoubleType) { 173 return values.toArray((T[]) Array.newInstance(Double.class, values.size())); 174 } else if (type instanceof IntegerType) { 175 return values.toArray((T[]) Array.newInstance(Integer.class, values.size())); 176 } else if (type instanceof BinaryType) { 177 return values.toArray((T[]) Array.newInstance(Byte.class, values.size())); 178 } else if (type instanceof DateType) { 179 return values.toArray((T[]) Array.newInstance(Calendar.class, values.size())); 180 } 181 throw new MarshallingException("Primitive type not found: " + type.getName()); 182 } 183 184 private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException { 185 Object value; 186 if (jn.isObject()) { 187 ObjectResolver resolver = type.getObjectResolver(); 188 if (resolver == null) { 189 if (type.getSuperType() != null && type.getSuperType() instanceof StringType) { 190 // Let's assume it is a blob of which content has to be stored in a string property. 191 Blob blob = readEntity(Blob.class, Blob.class, jn); 192 if (blob == null) { 193 throw new MarshallingException("Unable to parse the property " + property.getXPath()); 194 } 195 return blob.getString(); 196 } 197 } 198 Object object = null; 199 for (Class<?> clazz : resolver.getManagedClasses()) { 200 try { 201 object = readEntity(clazz, clazz, jn); 202 } catch (Exception e) { 203 continue; 204 } 205 } 206 if (object == null) { 207 throw new MarshallingException("Unable to parse the property " + property.getXPath()); 208 } 209 value = resolver.getReference(object); 210 if (value == null) { 211 throw new MarshallingException("Property " + property.getXPath() 212 + " value cannot be resolved by the matching resolver " + resolver.getName()); 213 } 214 } else { 215 value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn); 216 } 217 return value; 218 } 219 220 private Object getPropertyValue(Type type, JsonNode jn) throws IOException { 221 Object value; 222 if (jn.isNull()) { 223 value = null; 224 } else if (type instanceof BooleanType) { 225 value = jn.asBoolean(); 226 } else if (type instanceof LongType) { 227 value = jn.asLong(); 228 } else if (type instanceof DoubleType) { 229 value = jn.asDouble(); 230 } else if (type instanceof IntegerType) { 231 value = jn.asInt(); 232 } else if (type instanceof BinaryType) { 233 value = jn.binaryValue(); 234 } else { 235 value = type.decode(jn.asText()); 236 } 237 return value; 238 } 239 240 private void fillListProperty(Property property, JsonNode jn) throws IOException { 241 ListType listType = (ListType) property.getType(); 242 if (property instanceof ArrayProperty) { 243 fillScalarProperty(property, jn); 244 } else { 245 JsonNode elNode; 246 Iterator<JsonNode> it = jn.elements(); 247 while (it.hasNext()) { 248 elNode = it.next(); 249 Property child = readProperty(property, listType.getField(), elNode); 250 property.addValue(child.getValue()); 251 } 252 } 253 } 254 255 private void fillComplexProperty(Property property, JsonNode jn) throws IOException { 256 Entry<String, JsonNode> elNode; 257 Iterator<Entry<String, JsonNode>> it = jn.fields(); 258 ComplexProperty complexProperty = (ComplexProperty) property; 259 ComplexType type = complexProperty.getType(); 260 while (it.hasNext()) { 261 elNode = it.next(); 262 String elName = elNode.getKey(); 263 Field field = type.getField(elName); 264 if (field != null) { 265 Property child = readProperty(property, field, elNode.getValue()); 266 property.setValue(elName, child.getValue()); 267 } 268 } 269 } 270 271}