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