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