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