001/*
002 * Copyright (c) 2006-2015 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the Eclipse Public License v1.0
006 * which accompanies this distribution, and is available at
007 * http://www.eclipse.org/legal/epl-v10.html
008 *
009 * Contributors:
010 *     Bogdan Stefanescu
011 */
012package org.nuxeo.ecm.automation.jaxrs.io.documents;
013
014import java.io.IOException;
015import java.io.OutputStream;
016import java.lang.annotation.Annotation;
017import java.lang.reflect.Type;
018import java.util.Calendar;
019import java.util.List;
020import java.util.Map;
021
022import javax.servlet.ServletRequest;
023import javax.servlet.http.HttpServletRequest;
024import javax.ws.rs.Produces;
025import javax.ws.rs.WebApplicationException;
026import javax.ws.rs.core.Context;
027import javax.ws.rs.core.HttpHeaders;
028import javax.ws.rs.core.MediaType;
029import javax.ws.rs.core.MultivaluedMap;
030import javax.ws.rs.ext.MessageBodyWriter;
031import javax.ws.rs.ext.Provider;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.codehaus.jackson.JsonEncoding;
036import org.codehaus.jackson.JsonFactory;
037import org.codehaus.jackson.JsonGenerationException;
038import org.codehaus.jackson.JsonGenerator;
039import org.joda.time.DateTime;
040import org.joda.time.format.ISODateTimeFormat;
041import org.nuxeo.common.utils.StringUtils;
042import org.nuxeo.ecm.automation.core.util.DateTimeFormat;
043import org.nuxeo.ecm.automation.core.util.JSONPropertyWriter;
044import org.nuxeo.ecm.automation.io.services.enricher.ContentEnricherService;
045import org.nuxeo.ecm.automation.io.services.enricher.HeaderDocEvaluationContext;
046import org.nuxeo.ecm.automation.io.services.enricher.RestEvaluationContext;
047import org.nuxeo.ecm.core.api.DocumentModel;
048import org.nuxeo.ecm.core.api.Lock;
049import org.nuxeo.ecm.core.api.PropertyException;
050import org.nuxeo.ecm.core.api.model.DocumentPart;
051import org.nuxeo.ecm.core.api.model.Property;
052import org.nuxeo.ecm.core.api.model.PropertyNotFoundException;
053import org.nuxeo.ecm.core.io.download.DownloadService;
054import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonWriter;
055import org.nuxeo.ecm.core.io.registry.MarshallerHelper;
056import org.nuxeo.ecm.core.io.registry.MarshallerRegistry;
057import org.nuxeo.ecm.core.schema.utils.DateParser;
058import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper;
059import org.nuxeo.ecm.webengine.jaxrs.coreiodelegate.JsonCoreIODelegate;
060import org.nuxeo.runtime.api.Framework;
061
062/**
063 * @deprecated this JAX-RS marshaller was migrated to {@link DocumentModelJsonWriter}. To use it in JAX-RS, use
064 *             {@link JsonCoreIODelegate} to forward JAX-RS marshalling to core io. To use it your code, please refer to
065 *             {@link MarshallerRegistry} service or use {@link MarshallerHelper}.
066 */
067@Deprecated
068@Provider
069@Produces({ "application/json+nxentity", "application/json" })
070public class JsonDocumentWriter implements MessageBodyWriter<DocumentModel> {
071
072    public static final String DOCUMENT_PROPERTIES_HEADER = "X-NXDocumentProperties";
073
074    private static final Log log = LogFactory.getLog(JsonDocumentWriter.class);
075
076    @Context
077    JsonFactory factory;
078
079    @Context
080    protected HttpHeaders headers;
081
082    @Context
083    private HttpServletRequest servletRequest;
084
085    @Override
086    public long getSize(DocumentModel arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4) {
087        return -1L;
088    }
089
090    @Override
091    public boolean isWriteable(Class<?> arg0, Type arg1, Annotation[] arg2, MediaType arg3) {
092        return DocumentModel.class.isAssignableFrom(arg0);
093    }
094
095    @Override
096    public void writeTo(DocumentModel doc, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4,
097            MultivaluedMap<String, Object> arg5, OutputStream out) throws IOException, WebApplicationException {
098        try {
099            // schema names: dublincore, file, ... or *
100            List<String> props = headers.getRequestHeader(DOCUMENT_PROPERTIES_HEADER);
101            String[] schemas = null;
102            if (props != null && !props.isEmpty()) {
103                schemas = StringUtils.split(props.get(0), ',', true);
104            }
105            writeDocument(out, doc, schemas);
106        } catch (IOException e) {
107            log.error("Failed to serialize document", e);
108            throw e;
109        }
110    }
111
112    public void writeDocument(OutputStream out, DocumentModel doc, String[] schemas) throws IOException {
113        writeDocument(out, doc, schemas, null);
114    }
115
116    /**
117     * @since 5.6
118     */
119    public void writeDocument(OutputStream out, DocumentModel doc, String[] schemas,
120            Map<String, String> contextParameters) throws IOException {
121        writeDocument(factory.createJsonGenerator(out, JsonEncoding.UTF8), doc, schemas, contextParameters, headers,
122                servletRequest);
123    }
124
125    public static void writeDocument(JsonGenerator jg, DocumentModel doc, String[] schemas, ServletRequest request)
126            throws IOException {
127        writeDocument(jg, doc, schemas, null, request);
128    }
129
130    /**
131     * @since 5.6
132     */
133    public static void writeDocument(JsonGenerator jg, DocumentModel doc, String[] schemas,
134            Map<String, String> contextParameters, ServletRequest request) throws IOException {
135        writeDocument(jg, doc, schemas, contextParameters, null, request);
136    }
137
138    /**
139     * @param jg a ready to user JSON generator
140     * @param doc the document to serialize
141     * @param schemas an array of schemas that must be serialized in the properties map
142     * @param contextParameters
143     * @param headers
144     * @param request the ServletRequest. If null blob URLs won't be generated.
145     * @since 5.7.3
146     */
147    public static void writeDocument(JsonGenerator jg, DocumentModel doc, String[] schemas,
148            Map<String, String> contextParameters, HttpHeaders headers, ServletRequest request) throws IOException {
149        jg.writeStartObject();
150        jg.writeStringField("entity-type", "document");
151        jg.writeStringField("repository", doc.getRepositoryName());
152        jg.writeStringField("uid", doc.getId());
153        jg.writeStringField("path", doc.getPathAsString());
154        jg.writeStringField("type", doc.getType());
155        jg.writeStringField("state", doc.getRef() != null ? doc.getCurrentLifeCycleState() : null);
156        jg.writeStringField("parentRef", doc.getParentRef() != null ? doc.getParentRef().toString() : null);
157        jg.writeStringField("versionLabel", doc.getVersionLabel());
158        jg.writeBooleanField("isCheckedOut", doc.isCheckedOut());
159        Lock lock = doc.getLockInfo();
160        if (lock != null) {
161            jg.writeStringField("lockOwner", lock.getOwner());
162            jg.writeStringField("lockCreated", ISODateTimeFormat.dateTime().print(new DateTime(lock.getCreated())));
163        }
164        jg.writeStringField("title", doc.getTitle());
165        try {
166            Calendar cal = (Calendar) doc.getPropertyValue("dc:modified");
167            if (cal != null) {
168                jg.writeStringField("lastModified", DateParser.formatW3CDateTime(cal.getTime()));
169            }
170        } catch (PropertyNotFoundException e) {
171            // ignore
172        }
173
174        if (schemas != null && schemas.length > 0) {
175            jg.writeObjectFieldStart("properties");
176            if (schemas.length == 1 && "*".equals(schemas[0])) {
177                // full document
178                for (String schema : doc.getSchemas()) {
179                    writeProperties(jg, doc, schema, request);
180                }
181            } else {
182                for (String schema : schemas) {
183                    writeProperties(jg, doc, schema, request);
184                }
185            }
186            jg.writeEndObject();
187        }
188
189        jg.writeArrayFieldStart("facets");
190        for (String facet : doc.getFacets()) {
191            jg.writeString(facet);
192        }
193        jg.writeEndArray();
194        jg.writeStringField("changeToken", doc.getChangeToken());
195
196        jg.writeObjectFieldStart("contextParameters");
197        if (contextParameters != null && !contextParameters.isEmpty()) {
198            for (Map.Entry<String, String> parameter : contextParameters.entrySet()) {
199                jg.writeStringField(parameter.getKey(), parameter.getValue());
200            }
201        }
202
203        writeRestContributions(jg, doc, headers, request);
204        jg.writeEndObject();
205
206        jg.writeEndObject();
207        jg.flush();
208    }
209
210    /**
211     * @param jg
212     * @param doc
213     * @param headers
214     * @throws IOException
215     * @throws JsonGenerationException
216     * @since 5.7.3
217     */
218    protected static void writeRestContributions(JsonGenerator jg, DocumentModel doc, HttpHeaders headers,
219            ServletRequest request) throws JsonGenerationException, IOException {
220        ContentEnricherService rcs = Framework.getLocalService(ContentEnricherService.class);
221        RestEvaluationContext ec = new HeaderDocEvaluationContext(doc, headers, request);
222        rcs.writeContext(jg, ec);
223    }
224
225    protected static void writeProperties(JsonGenerator jg, DocumentModel doc, String schema, ServletRequest request)
226            throws IOException {
227        DocumentPart part = doc.getPart(schema);
228        if (part == null) {
229            return;
230        }
231        String prefix = part.getSchema().getNamespace().prefix;
232        if (prefix == null || prefix.length() == 0) {
233            prefix = schema;
234        }
235        prefix = prefix + ":";
236
237        String blobUrlPrefix = null;
238        if (request != null) {
239            DownloadService downloadService = Framework.getService(DownloadService.class);
240            blobUrlPrefix = VirtualHostHelper.getBaseURL(request) + downloadService.getDownloadUrl(doc, null, null)
241                    + "/";
242        }
243
244        for (Property p : part.getChildren()) {
245            jg.writeFieldName(prefix + p.getField().getName().getLocalName());
246            writePropertyValue(jg, p, blobUrlPrefix);
247        }
248    }
249
250    /**
251     * Converts the value of the given core property to JSON format. The given filesBaseUrl is the baseUrl that can be
252     * used to locate blob content and is useful to generate blob urls.
253     */
254    public static void writePropertyValue(JsonGenerator jg, Property prop, String filesBaseUrl)
255            throws PropertyException, JsonGenerationException, IOException {
256        JSONPropertyWriter.writePropertyValue(jg, prop, DateTimeFormat.W3C, filesBaseUrl);
257    }
258
259}