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.restapi.server.jaxrs;
018
019import java.util.List;
020
021import javax.ws.rs.Consumes;
022import javax.ws.rs.DELETE;
023import javax.ws.rs.GET;
024import javax.ws.rs.POST;
025import javax.ws.rs.PUT;
026import javax.ws.rs.Path;
027import javax.ws.rs.Produces;
028import javax.ws.rs.core.Context;
029import javax.ws.rs.core.HttpHeaders;
030import javax.ws.rs.core.MediaType;
031import javax.ws.rs.core.Response;
032import javax.ws.rs.core.Response.Status;
033
034import org.apache.commons.lang.StringUtils;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.nuxeo.ecm.core.api.CoreSession;
038import org.nuxeo.ecm.core.api.DocumentModel;
039import org.nuxeo.ecm.core.api.DocumentRef;
040import org.nuxeo.ecm.core.api.NuxeoException;
041import org.nuxeo.ecm.core.api.PathRef;
042import org.nuxeo.ecm.core.api.VersioningOption;
043import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonReader;
044import org.nuxeo.ecm.core.rest.DocumentObject;
045import org.nuxeo.ecm.core.versioning.VersioningService;
046import org.nuxeo.ecm.restapi.jaxrs.io.RestConstants;
047import org.nuxeo.ecm.webengine.WebException;
048import org.nuxeo.ecm.webengine.model.WebObject;
049
050/**
051 * This object basically overrides the default DocumentObject that doesn't know how to produce/consume JSON
052 *
053 * @since 5.7.2
054 */
055
056@WebObject(type = "Document")
057@Produces({ "application/json+nxentity", "application/json+esentity", MediaType.APPLICATION_JSON })
058public class JSONDocumentObject extends DocumentObject {
059
060    private static final String APPLICATION_JSON_NXENTITY = "application/json+nxentity";
061
062    protected static final Log log = LogFactory.getLog(JSONDocumentObject.class);
063
064    private boolean isVersioning;
065
066    @Override
067    @GET
068    public DocumentModel doGet() {
069        return doc;
070    }
071
072    /**
073     * @return the document or the last version document in case of versioning handled
074     */
075    @PUT
076    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
077    public DocumentModel doPut(DocumentModel inputDoc, @Context HttpHeaders headers) {
078        DocumentModelJsonReader.applyPropertyValues(inputDoc, doc);
079        CoreSession session = ctx.getCoreSession();
080        versioningDocFromHeaderIfExists(headers);
081        doc = session.saveDocument(doc);
082        session.save();
083        return isVersioning ? session.getLastDocumentVersion(doc.getRef()) : doc;
084    }
085
086    @POST
087    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
088    public Response doPost(DocumentModel inputDoc) {
089        CoreSession session = ctx.getCoreSession();
090
091        if (StringUtils.isBlank(inputDoc.getType()) || StringUtils.isBlank(inputDoc.getName())) {
092            return Response.status(Status.BAD_REQUEST).entity("type or name property is missing").build();
093        }
094
095        DocumentModel createdDoc = session.createDocumentModel(doc.getPathAsString(), inputDoc.getName(),
096                inputDoc.getType());
097        DocumentModelJsonReader.applyPropertyValues(inputDoc, createdDoc);
098        createdDoc = session.createDocument(createdDoc);
099        session.save();
100        return Response.ok(createdDoc).status(Status.CREATED).build();
101    }
102
103    @DELETE
104    @Consumes({ APPLICATION_JSON_NXENTITY, "application/json" })
105    public Response doDeleteJson() {
106        super.doDelete();
107        return Response.noContent().build();
108    }
109
110    @Override
111    @Path("@search")
112    public Object search() {
113        return ctx.newAdapter(this, "search");
114    }
115
116    @Override
117    public DocumentObject newDocument(String path) {
118        try {
119            PathRef pathRef = new PathRef(doc.getPath().append(path).toString());
120            DocumentModel doc = ctx.getCoreSession().getDocument(pathRef);
121            return (DocumentObject) ctx.newObject("Document", doc);
122        } catch (NuxeoException e) {
123            throw WebException.wrap(e);
124        }
125    }
126
127    @Override
128    public DocumentObject newDocument(DocumentRef ref) {
129        try {
130            DocumentModel doc = ctx.getCoreSession().getDocument(ref);
131            return (DocumentObject) ctx.newObject("Document", doc);
132        } catch (NuxeoException e) {
133            throw WebException.wrap(e);
134        }
135    }
136
137    @Override
138    public DocumentObject newDocument(DocumentModel doc) {
139        return (DocumentObject) ctx.newObject("Document", doc);
140    }
141
142    /**
143     * In case of version option header presence, checkin the related document
144     *
145     * @param headers X-Versioning-Option Header
146     */
147    private void versioningDocFromHeaderIfExists(HttpHeaders headers) {
148        isVersioning = false;
149        List<String> versionHeader = headers.getRequestHeader(RestConstants.X_VERSIONING_OPTION);
150        if (versionHeader != null && versionHeader.size() != 0) {
151            VersioningOption versioningOption = VersioningOption.valueOf(versionHeader.get(0).toUpperCase());
152            if (versioningOption != null && !versioningOption.equals(VersioningOption.NONE)) {
153                doc.putContextData(VersioningService.VERSIONING_OPTION, versioningOption);
154                isVersioning = true;
155            }
156        }
157    }
158}