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 field = schema.getField(propertyName); 112 parent = new DocumentPartImpl(schema); 113 } 114 Property property = readProperty(parent, field, propertyNode.getValue()); 115 if (property != null) { 116 properties.add(property); 117 } 118 } 119 return properties; 120 } 121 122 protected Property readProperty(Property parent, Field field, JsonNode jn) throws IOException { 123 Property property = PropertyFactory.createProperty(parent, field, 0); 124 if (jn.isNull()) { 125 property.setValue(null); 126 } else if (property.isScalar()) { 127 fillScalarProperty(property, jn); 128 } else if (property.isList()) { 129 fillListProperty(property, jn); 130 } else { 131 if (!(property instanceof BlobProperty)) { 132 fillComplexProperty(property, jn); 133 } else { 134 Blob blob = readEntity(Blob.class, Blob.class, jn); 135 property.setValue(blob); 136 } 137 } 138 return property; 139 } 140 141 private void fillScalarProperty(Property property, JsonNode jn) throws IOException { 142 if ((property instanceof ArrayProperty) && jn.isArray()) { 143 List<Object> values = new ArrayList<Object>(); 144 Iterator<JsonNode> it = jn.getElements(); 145 JsonNode item; 146 Type fieldType = ((ListType) property.getType()).getFieldType(); 147 while (it.hasNext()) { 148 item = it.next(); 149 values.add(getScalarPropertyValue(property, item, fieldType)); 150 } 151 property.setValue(castArrayPropertyValue(((SimpleType) fieldType).getPrimitiveType(), values)); 152 } else { 153 property.setValue(getScalarPropertyValue(property, jn, property.getType())); 154 } 155 } 156 157 @SuppressWarnings({ "unchecked" }) 158 private <T> T[] castArrayPropertyValue(Type type, List<Object> values) throws IOException { 159 if (type instanceof StringType) { 160 return values.toArray((T[]) Array.newInstance(String.class, values.size())); 161 } else if (type instanceof BooleanType) { 162 return values.toArray((T[]) Array.newInstance(Boolean.class, values.size())); 163 } else if (type instanceof LongType) { 164 return values.toArray((T[]) Array.newInstance(Long.class, values.size())); 165 } else if (type instanceof DoubleType) { 166 return values.toArray((T[]) Array.newInstance(Double.class, values.size())); 167 } else if (type instanceof IntegerType) { 168 return values.toArray((T[]) Array.newInstance(Integer.class, values.size())); 169 } else if (type instanceof BinaryType) { 170 return values.toArray((T[]) Array.newInstance(Byte.class, values.size())); 171 } 172 throw new MarshallingException("Primitive type not found: " + type.getName()); 173 } 174 175 private Object getScalarPropertyValue(Property property, JsonNode jn, Type type) throws IOException { 176 Object value; 177 if (jn.isObject()) { 178 ObjectResolver resolver = type.getObjectResolver(); 179 if (resolver == null) { 180 throw new MarshallingException("Unable to parse the property " + property.getPath()); 181 } 182 Object object = null; 183 for (Class<?> clazz : resolver.getManagedClasses()) { 184 try { 185 object = readEntity(clazz, clazz, jn); 186 } catch (Exception e) { 187 continue; 188 } 189 } 190 if (object == null) { 191 throw new MarshallingException("Unable to parse the property " + property.getPath()); 192 } 193 value = resolver.getReference(object); 194 if (value == null) { 195 throw new MarshallingException("Property " + property.getPath() 196 + " value cannot be resolved by the matching resolver " + resolver.getName()); 197 } 198 } else { 199 value = getPropertyValue(((SimpleType) type).getPrimitiveType(), jn); 200 } 201 return value; 202 } 203 204 private Object getPropertyValue(Type type, JsonNode jn) throws IOException { 205 Object value; 206 if (jn.isNull()) { 207 value = null; 208 } else if (type instanceof BooleanType) { 209 value = jn.getValueAsBoolean(); 210 } else if (type instanceof LongType) { 211 value = jn.getValueAsLong(); 212 } else if (type instanceof DoubleType) { 213 value = jn.getValueAsDouble(); 214 } else if (type instanceof IntegerType) { 215 value = jn.getValueAsInt(); 216 } else if (type instanceof BinaryType) { 217 value = jn.getBinaryValue(); 218 } else { 219 value = type.decode(jn.getValueAsText()); 220 } 221 return value; 222 } 223 224 private void fillListProperty(Property property, JsonNode jn) throws IOException { 225 ListType listType = (ListType) property.getType(); 226 if (property instanceof ArrayProperty) { 227 fillScalarProperty(property, jn); 228 } else { 229 JsonNode elNode = null; 230 Iterator<JsonNode> it = jn.getElements(); 231 while (it.hasNext()) { 232 elNode = it.next(); 233 Property child = readProperty(property, listType.getField(), elNode); 234 property.addValue(child.getValue()); 235 } 236 } 237 } 238 239 private void fillComplexProperty(Property property, JsonNode jn) throws IOException { 240 Entry<String, JsonNode> elNode = null; 241 Iterator<Entry<String, JsonNode>> it = jn.getFields(); 242 ComplexProperty complexProperty = (ComplexProperty) property; 243 ComplexType type = complexProperty.getType(); 244 while (it.hasNext()) { 245 elNode = it.next(); 246 String elName = elNode.getKey(); 247 Field field = type.getField(elName); 248 if (field != null) { 249 Property child = readProperty(property, field, elNode.getValue()); 250 property.setValue(elName, child.getValue()); 251 } 252 } 253 } 254 255}