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.Iterator; 028import java.util.List; 029import java.util.Map.Entry; 030 031import javax.inject.Inject; 032 033import org.codehaus.jackson.JsonNode; 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.DoubleType; 054import org.nuxeo.ecm.core.schema.types.primitives.IntegerType; 055import org.nuxeo.ecm.core.schema.types.primitives.LongType; 056import org.nuxeo.ecm.core.schema.types.primitives.StringType; 057import org.nuxeo.ecm.core.schema.types.resolver.ObjectResolver; 058 059/** 060 * Convert Json as {@link List<Property>}. 061 * <p> 062 * Format is: 063 * 064 * <pre> 065 * { 066 * "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. 067 * "schema1Prefix:booleanProperty": true|false, 068 * "schema2Prefix:integerProperty": 123, 069 * ... 070 * "schema3Prefix:complexProperty": { 071 * "subProperty": ..., 072 * ... 073 * }, 074 * "schema4Prefix:listProperty": [ 075 * ... 076 * ] 077 * } 078 * </pre> 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<>(); 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; 099 Property parent; 100 if (propertyName.contains(":")) { 101 field = schemaManager.getField(propertyName); 102 if (field == null) { 103 continue; 104 } 105 parent = new DocumentPartImpl(field.getDeclaringType().getSchema()); 106 } else { 107 String shemaName = ctx.getParameter(DEFAULT_SCHEMA_NAME); 108 Schema schema = schemaManager.getSchema(shemaName); 109 if (schema == null) { 110 continue; 111 } 112 field = schema.getField(propertyName); 113 parent = new DocumentPartImpl(schema); 114 } 115 if (field == null) { 116 continue; 117 } 118 Property property = readProperty(parent, field, propertyNode.getValue()); 119 if (property != null) { 120 properties.add(property); 121 } 122 } 123 return properties; 124 } 125 126 protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException { 127 Property property = PropertyFactory.createProperty(parent, field, 0); 128 if (jn.isNull()) { 129 property.setValue(null); 130 } else if (property.isScalar()) { 131 fillScalarProperty(property, jn); 132 } else if (property.isList()) { 133 fillListProperty(property, jn); 134 } else { 135 if (!(property instanceof BlobProperty)) { 136 fillComplexProperty(property, jn); 137 } else { 138 Blob blob = readEntity(Blob.class, Blob.class, jn); 139 property.setValue(blob); 140 } 141 } 142 return property; 143 } 144 145 private void fillScalarProperty(Property property, JsonNode jn) throws IOException { 146 if ((property instanceof ArrayProperty) && jn.isArray()) { 147 List<Object> values = new ArrayList<>(); 148 Iterator<JsonNode> it = jn.getElements(); 149 JsonNode item; 150 Type fieldType = ((ListType) property.getType()).getFieldType(); 151 while (it.hasNext()) { 152 item = it.next(); 153 values.add(getScalarPropertyValue(property, item, fieldType)); 154 } 155 property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values)); 156 } else { 157 property.setValue(getScalarPropertyValue(property, jn, property.getType())); 158 } 159 } 160 161 @SuppressWarnings({ "unchecked" }) 162 private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException { 163 if (type instanceof StringType) { 164 return values.toArray((T[]) Array.newInstance(String.class, values.size())); 165 } else if (type instanceof BooleanType) { 166 return values.toArray((T[]) Array.newInstance(Boolean.class, values.size())); 167 } else if (type instanceof LongType) { 168 return values.toArray((T[]) Array.newInstance(Long.class, values.size())); 169 } else if (type instanceof DoubleType) { 170 return values.toArray((T[]) Array.newInstance(Double.class, values.size())); 171 } else if (type instanceof IntegerType) { 172 return values.toArray((T[]) Array.newInstance(Integer.class, values.size())); 173 } else if (type instanceof BinaryType) { 174 return values.toArray((T[]) Array.newInstance(Byte.class, values.size())); 175 } 176 throw new MarshallingException("Primitive type not found: " + type.getName()); 177 } 178 179 private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException { 180 Object value; 181 if (jn.isObject()) { 182 ObjectResolver resolver = type.getObjectResolver(); 183 if (resolver == null) { 184 if (type.getSuperType() != null && type.getSuperType() instanceof StringType) { 185 // Let's assume it is a blob of which content has to be stored in a string property. 186 Blob blob = readEntity(Blob.class, Blob.class, jn); 187 if (blob == null) { 188 throw new MarshallingException("Unable to parse the property " + property.getXPath()); 189 } 190 return blob.getString(); 191 } 192 } 193 Object object = null; 194 for (Class<?> clazz : resolver.getManagedClasses()) { 195 try { 196 object = readEntity(clazz, clazz, jn); 197 } catch (Exception e) { 198 continue; 199 } 200 } 201 if (object == null) { 202 throw new MarshallingException("Unable to parse the property " + property.getXPath()); 203 } 204 value = resolver.getReference(object); 205 if (value == null) { 206 throw new MarshallingException("Property " + property.getXPath() 207 + " value cannot be resolved by the matching resolver " + resolver.getName()); 208 } 209 } else { 210 value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn); 211 } 212 return value; 213 } 214 215 private Object getPropertyValue(Type type, JsonNode jn) throws IOException { 216 Object value; 217 if (jn.isNull()) { 218 value = null; 219 } else if (type instanceof BooleanType) { 220 value = jn.getValueAsBoolean(); 221 } else if (type instanceof LongType) { 222 value = jn.getValueAsLong(); 223 } else if (type instanceof DoubleType) { 224 value = jn.getValueAsDouble(); 225 } else if (type instanceof IntegerType) { 226 value = jn.getValueAsInt(); 227 } else if (type instanceof BinaryType) { 228 value = jn.getBinaryValue(); 229 } else { 230 value = type.decode(jn.getValueAsText()); 231 } 232 return value; 233 } 234 235 private void fillListProperty(Property property, JsonNode jn) throws IOException { 236 ListType listType = (ListType) property.getType(); 237 if (property instanceof ArrayProperty) { 238 fillScalarProperty(property, jn); 239 } else { 240 JsonNode elNode; 241 Iterator<JsonNode> it = jn.getElements(); 242 while (it.hasNext()) { 243 elNode = it.next(); 244 Property child = readProperty(property, listType.getField(), elNode); 245 property.addValue(child.getValue()); 246 } 247 } 248 } 249 250 private void fillComplexProperty(Property property, JsonNode jn) throws IOException { 251 Entry<String, JsonNode> elNode; 252 Iterator<Entry<String, JsonNode>> it = jn.getFields(); 253 ComplexProperty complexProperty = (ComplexProperty) property; 254 ComplexType type = complexProperty.getType(); 255 while (it.hasNext()) { 256 elNode = it.next(); 257 String elName = elNode.getKey(); 258 Field field = type.getField(elName); 259 if (field != null) { 260 Property child = readProperty(property, field, elNode.getValue()); 261 property.setValue(elName, child.getValue()); 262 } 263 } 264 } 265 266}