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.marshallers.json.document.DocumentModelJsonWriter.ENTITY_TYPE;
023import static org.nuxeo.ecm.core.io.registry.reflect.Instantiations.SINGLETON;
024import static org.nuxeo.ecm.core.io.registry.reflect.Priorities.REFERENCE;
025
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.Serializable;
029import java.lang.reflect.ParameterizedType;
030import java.lang.reflect.Type;
031import java.util.List;
032
033import javax.ws.rs.core.MediaType;
034
035import org.apache.commons.lang.StringUtils;
036import org.apache.commons.lang3.reflect.TypeUtils;
037import org.codehaus.jackson.JsonNode;
038import org.nuxeo.ecm.core.api.Blob;
039import org.nuxeo.ecm.core.api.DocumentModel;
040import org.nuxeo.ecm.core.api.IdRef;
041import org.nuxeo.ecm.core.api.impl.DataModelImpl;
042import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
043import org.nuxeo.ecm.core.api.model.Property;
044import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
045import org.nuxeo.ecm.core.api.model.impl.ComplexProperty;
046import org.nuxeo.ecm.core.api.model.impl.ListProperty;
047import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
048import org.nuxeo.ecm.core.io.marshallers.json.EntityJsonReader;
049import org.nuxeo.ecm.core.io.registry.Reader;
050import org.nuxeo.ecm.core.io.registry.context.RenderingContext.SessionWrapper;
051import org.nuxeo.ecm.core.io.registry.reflect.Setup;
052import org.nuxeo.ecm.core.schema.DocumentType;
053import org.nuxeo.ecm.core.schema.SchemaManager;
054import org.nuxeo.ecm.core.schema.types.Field;
055import org.nuxeo.ecm.core.schema.types.Schema;
056import org.nuxeo.runtime.api.Framework;
057
058/**
059 * Convert Json as {@link DocumentModel}.
060 * <p>
061 * Format is (any additional json property is ignored):
062 *
063 * <pre>
064 * {
065 *   "entity-type": "document",
066 *   "uid": "EXISTING_DOCUMENT_UID", <- use it to update an existing document
067 *   "repository": "REPOSITORY_NAME" , <- explicitely specify the repository name
068 *   "name": "DOCUMENT_NAME", <- use it to create an new document
069 *   "type": "DOCUMENT_TYPE", <- use it to create an new document
070 *   "properties": ...  <-- see {@link DocumentPropertiesJsonReader}
071 * }
072 * </pre>
073 *
074 * </p>
075 *
076 * @since 7.2
077 */
078@Setup(mode = SINGLETON, priority = REFERENCE)
079public class DocumentModelJsonReader extends EntityJsonReader<DocumentModel> {
080
081    public static final String LEGACY_MODE_READER = "DocumentModelLegacyModeReader";
082
083    public DocumentModelJsonReader() {
084        super(ENTITY_TYPE);
085    }
086
087    @Override
088    public DocumentModel read(Class<?> clazz, Type genericType, MediaType mediaType, InputStream in) throws IOException {
089        Reader<DocumentModel> reader = ctx.getParameter(LEGACY_MODE_READER);
090        if (reader != null) {
091            DocumentModel doc = reader.read(clazz, genericType, mediaType, in);
092            return doc;
093        } else {
094            return super.read(clazz, genericType, mediaType, in);
095        }
096    }
097
098    @Override
099    protected DocumentModel readEntity(JsonNode jn) throws IOException {
100
101        SimpleDocumentModel simpleDoc = new SimpleDocumentModel();
102        String name = getStringField(jn, "name");
103        if (StringUtils.isNotBlank(name)) {
104            simpleDoc.setPathInfo(null, name);
105        }
106        String type = getStringField(jn, "type");
107        if (StringUtils.isNotBlank(type)) {
108            simpleDoc.setType(type);
109        }
110
111        JsonNode propsNode = jn.get("properties");
112        if (propsNode != null && !propsNode.isNull() && propsNode.isObject()) {
113            ParameterizedType genericType = TypeUtils.parameterize(List.class, Property.class);
114            List<Property> properties = readEntity(List.class, genericType, propsNode);
115            for (Property property : properties) {
116                String propertyName = property.getName();
117                // handle schema with no prefix
118                if (!propertyName.contains(":")) {
119                    propertyName = property.getField().getDeclaringType().getName() + ":" + propertyName;
120                }
121                simpleDoc.setPropertyValue(propertyName, property.getValue());
122            }
123        }
124
125        DocumentModel doc = null;
126
127        String uid = getStringField(jn, "uid");
128        if (StringUtils.isNotBlank(uid)) {
129            try (SessionWrapper wrapper = ctx.getSession(null)) {
130                doc = wrapper.getSession().getDocument(new IdRef(uid));
131            }
132            avoidBlobUpdate(simpleDoc, doc);
133            applyDirtyPropertyValues(simpleDoc, doc);
134        } else if (StringUtils.isNotBlank(type)) {
135            SimpleDocumentModel createdDoc = new SimpleDocumentModel();
136            if (StringUtils.isNotBlank(name)) {
137                createdDoc.setPathInfo(null, name);
138            }
139            createdDoc.setType(simpleDoc.getType());
140            applyAllPropertyValues(simpleDoc, createdDoc);
141            doc = createdDoc;
142        } else {
143            doc = simpleDoc;
144        }
145
146        return doc;
147    }
148
149    /**
150     * Avoid the blob updates. It's managed by custom ways.
151     */
152    private static void avoidBlobUpdate(DocumentModel docToClean, DocumentModel docRef) {
153        for (String schema : docToClean.getSchemas()) {
154            for (String field : docToClean.getDataModel(schema).getDirtyFields()) {
155                avoidBlobUpdate(docToClean.getProperty(field), docRef);
156            }
157        }
158    }
159
160    private static void avoidBlobUpdate(Property propToClean, DocumentModel docRef) {
161        if (propToClean instanceof BlobProperty) {
162            // if the blob used to exist
163            if (propToClean.getValue() == null) {
164                Serializable value = docRef.getPropertyValue(propToClean.getPath());
165                propToClean.setValue(value);
166            }
167        } else if (propToClean instanceof ComplexProperty) {
168            ComplexProperty complexPropToClean = (ComplexProperty) propToClean;
169            for (Field field : complexPropToClean.getType().getFields()) {
170                Property childPropToClean = complexPropToClean.get(field.getName().getLocalName());
171                avoidBlobUpdate(childPropToClean, docRef);
172            }
173        } else if (propToClean instanceof ListProperty) {
174            ListProperty listPropToClean = (ListProperty) propToClean;
175            for (int i = 0; i < listPropToClean.size(); i++) {
176                Property elPropToClean = listPropToClean.get(i);
177                avoidBlobUpdate(elPropToClean, docRef);
178            }
179        }
180    }
181
182    public static void applyPropertyValues(DocumentModel src, DocumentModel dst) {
183        avoidBlobUpdate(src, dst);
184        applyPropertyValues(src, dst, true);
185    }
186
187    public static void applyPropertyValues(DocumentModel src, DocumentModel dst, boolean dirtyOnly) {
188        // if not "dirty only", it handles all the schemas for the given type
189        // so it will trigger the default values initialization
190        if (dirtyOnly) {
191            applyDirtyPropertyValues(src, dst);
192        } else {
193            applyAllPropertyValues(src, dst);
194        }
195    }
196
197    public static void applyDirtyPropertyValues(DocumentModel src, DocumentModel dst) {
198        String[] schemas = src.getSchemas();
199        for (String schema : schemas) {
200            DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema);
201            DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema);
202            for (String field : fromDataModel.getDirtyFields()) {
203                Serializable data = (Serializable) fromDataModel.getData(field);
204                try {
205                    if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) {
206                        dataModel.setData(field, data);
207                    } else {
208                        dataModel.setData(field, decodeBlob(data));
209                    }
210                } catch (PropertyNotFoundException e) {
211                    continue;
212                }
213            }
214        }
215    }
216
217    public static void applyAllPropertyValues(DocumentModel src, DocumentModel dst) {
218        SchemaManager service = Framework.getService(SchemaManager.class);
219        DocumentType type = service.getDocumentType(src.getType());
220        String[] schemas = type.getSchemaNames();
221        for (String schemaName : schemas) {
222            Schema schema = service.getSchema(schemaName);
223            DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schemaName);
224            DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schemaName);
225            for (Field field : schema.getFields()) {
226                String fieldName = field.getName().getLocalName();
227                Serializable data = (Serializable) fromDataModel.getData(fieldName);
228                try {
229                    if (!(dataModel.getDocumentPart().get(fieldName) instanceof BlobProperty)) {
230                        dataModel.setData(fieldName, data);
231                    } else {
232                        dataModel.setData(fieldName, decodeBlob(data));
233                    }
234                } catch (PropertyNotFoundException e) {
235                    continue;
236                }
237            }
238        }
239    }
240
241    private static Serializable decodeBlob(Serializable data) {
242        if (data instanceof Blob) {
243            return data;
244        } else {
245            return null;
246        }
247    }
248
249}