001/*
002 * (C) Copyright 2013 Nuxeo SA (http://nuxeo.com/) and contributors.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     dmetzler
016 */
017package org.nuxeo.ecm.automation.jaxrs.io.documents;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.io.Serializable;
022import java.lang.annotation.Annotation;
023import java.lang.reflect.Type;
024
025import javax.servlet.http.HttpServletRequest;
026import javax.ws.rs.Consumes;
027import javax.ws.rs.WebApplicationException;
028import javax.ws.rs.core.Context;
029import javax.ws.rs.core.MediaType;
030import javax.ws.rs.core.MultivaluedMap;
031import javax.ws.rs.core.Response;
032import javax.ws.rs.ext.MessageBodyReader;
033import javax.ws.rs.ext.Provider;
034
035import org.apache.commons.io.IOUtils;
036import org.apache.commons.lang.StringUtils;
037import org.apache.commons.logging.Log;
038import org.apache.commons.logging.LogFactory;
039import org.codehaus.jackson.JsonFactory;
040import org.codehaus.jackson.JsonNode;
041import org.codehaus.jackson.JsonParser;
042import org.codehaus.jackson.JsonToken;
043import org.nuxeo.ecm.automation.core.util.DocumentHelper;
044import org.nuxeo.ecm.automation.core.util.Properties;
045import org.nuxeo.ecm.core.api.Blob;
046import org.nuxeo.ecm.core.api.CoreSession;
047import org.nuxeo.ecm.core.api.DocumentModel;
048import org.nuxeo.ecm.core.api.IdRef;
049import org.nuxeo.ecm.core.api.impl.DataModelImpl;
050import org.nuxeo.ecm.core.api.impl.SimpleDocumentModel;
051import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
052import org.nuxeo.ecm.core.api.model.impl.primitives.BlobProperty;
053import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonReader;
054import org.nuxeo.ecm.webengine.WebException;
055import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.DocumentModelJsonReaderLegacy;
056import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.JsonCoreIODelegate;
057import org.nuxeo.ecm.webengine.jaxrs.session.SessionFactory;
058
059/**
060 * JAX-RS reader for a DocumentModel. If an id is given, it tries to reattach the document to the session. If not, it
061 * creates a ready to create DocumentModel filled with the properties found.
062 *
063 * @since 5.7.2
064 * @deprecated since 7.10 The Nuxeo JSON marshalling was migrated to nuxeo-core-io. This class is replaced by
065 *             {@link DocumentModelJsonReader} which is registered by default and available to marshal
066 *             {@link DocumentModel} from the Nuxeo Rest API thanks to the JAX-RS marshaller {@link JsonCoreIODelegate}
067 *             . On removal, need to remove also {@link DocumentModelJsonReaderLegacy} because it uses it using
068 *             reflexion.
069 */
070@Deprecated
071@Provider
072@Consumes({ "application/json+nxentity", "application/json" })
073public class JSONDocumentModelReader implements MessageBodyReader<DocumentModel> {
074
075    // private static final String REQUEST_BATCH_ID = "batchId";
076
077    protected static final Log log = LogFactory.getLog(JSONDocumentModelReader.class);
078
079    @Context
080    HttpServletRequest request;
081
082    @Context
083    JsonFactory factory;
084
085    @Override
086    public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
087        return DocumentModel.class.isAssignableFrom(type);
088    }
089
090    @Override
091    public DocumentModel readFrom(Class<DocumentModel> type, Type genericType, Annotation[] annotations,
092            MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
093            throws IOException, WebApplicationException {
094        String content = IOUtils.toString(entityStream);
095        if (content.isEmpty()) {
096            if (content.isEmpty()) {
097                throw new WebException("No content in request body", Response.Status.BAD_REQUEST.getStatusCode());
098            }
099
100        }
101
102        try {
103            return readRequest(content, httpHeaders);
104        } catch (IOException e) {
105            throw WebException.wrap(e);
106        }
107    }
108
109    private DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders) throws IOException {
110        return readRequest(content, httpHeaders, request);
111    }
112
113    protected DocumentModel readRequest(String content, MultivaluedMap<String, String> httpHeaders,
114            HttpServletRequest request) throws IOException {
115        JsonParser jp = factory.createJsonParser(content);
116        return readJson(jp, httpHeaders, request);
117    }
118
119    public static DocumentModel readJson(JsonParser jp, MultivaluedMap<String, String> httpHeaders,
120            HttpServletRequest request) throws IOException {
121        JsonToken tok = jp.nextToken();
122
123        // skip {
124        if (jp.getCurrentToken() == JsonToken.START_OBJECT) {
125            tok = jp.nextToken();
126        }
127        SimpleDocumentModel simpleDoc = new SimpleDocumentModel();
128        String type = null;
129        String name = null;
130        String uid = null;
131        while (tok != null && tok != JsonToken.END_OBJECT) {
132            String key = jp.getCurrentName();
133            jp.nextToken();
134            if ("properties".equals(key)) {
135                DocumentHelper.setJSONProperties(null, simpleDoc, readProperties(jp));
136            } else if ("name".equals(key)) {
137                name = jp.readValueAs(String.class);
138            } else if ("type".equals(key)) {
139                type = jp.readValueAs(String.class);
140            } else if ("uid".equals(key)) {
141                uid = jp.readValueAs(String.class);
142            } else if ("entity-type".equals(key)) {
143                String entityType = jp.readValueAs(String.class);
144                if (!"document".equals(entityType)) {
145                    throw new WebApplicationException(Response.Status.BAD_REQUEST);
146                }
147            } else {
148                log.debug("Unknown key: " + key);
149                jp.skipChildren();
150            }
151
152            tok = jp.nextToken();
153        }
154
155        if (tok == null) {
156            throw new IllegalArgumentException("Unexpected end of stream.");
157        }
158
159        if (StringUtils.isNotBlank(type)) {
160            simpleDoc.setType(type);
161        }
162
163        if (StringUtils.isNotBlank(name)) {
164            simpleDoc.setPathInfo(null, name);
165        }
166
167        // If a uid is specified, we try to get the doc from
168        // the core session
169        if (uid != null) {
170            CoreSession session = SessionFactory.getSession(request);
171            DocumentModel doc = session.getDocument(new IdRef(uid));
172            applyPropertyValues(simpleDoc, doc);
173            return doc;
174        } else {
175            return simpleDoc;
176        }
177
178    }
179
180    static Properties readProperties(JsonParser jp) throws IOException {
181        JsonNode node = jp.readValueAsTree();
182        return new Properties(node);
183
184    }
185
186    /**
187     * Decodes a Serializable to make it a blob.
188     *
189     * @since 5.9.1
190     */
191    private static Serializable decodeBlob(Serializable data) {
192        if (data instanceof Blob) {
193            return data;
194        } else {
195            return null;
196        }
197    }
198
199    public static void applyPropertyValues(DocumentModel src, DocumentModel dst) {
200        for (String schema : src.getSchemas()) {
201            DataModelImpl dataModel = (DataModelImpl) dst.getDataModel(schema);
202            DataModelImpl fromDataModel = (DataModelImpl) src.getDataModel(schema);
203
204            for (String field : fromDataModel.getDirtyFields()) {
205                Serializable data = (Serializable) fromDataModel.getData(field);
206                try {
207                    if (!(dataModel.getDocumentPart().get(field) instanceof BlobProperty)) {
208                        dataModel.setData(field, data);
209                    } else {
210                        dataModel.setData(field, decodeBlob(data));
211                    }
212                    // }
213                } catch (PropertyNotFoundException e) {
214                    log.warn(String.format("Trying to deserialize unexistent field : {%s}", field));
215                }
216            }
217        }
218    }
219
220}