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.api.versioning.VersioningService; 046import org.nuxeo.ecm.core.io.marshallers.json.document.DocumentModelJsonReader; 047import org.nuxeo.ecm.core.rest.DocumentObject; 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({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_JSON + "+esentity" }) 059public class JSONDocumentObject extends DocumentObject { 060 061 protected static final Log log = LogFactory.getLog(JSONDocumentObject.class); 062 063 private boolean isVersioning; 064 065 @Override 066 @GET 067 public DocumentModel doGet() { 068 return doc; 069 } 070 071 /** 072 * @return the document or the last version document in case of versioning handled 073 */ 074 @PUT 075 @Consumes(MediaType.APPLICATION_JSON) 076 public Response doPut(DocumentModel inputDoc, @Context HttpHeaders headers) { 077 DocumentModelJsonReader.applyPropertyValues(inputDoc, doc); 078 CoreSession session = ctx.getCoreSession(); 079 versioningDocFromHeaderIfExists(headers); 080 updateCommentFromHeader(headers); 081 try { 082 doc = session.saveDocument(doc); 083 session.save(); 084 } catch (ConcurrentUpdateException e) { 085 return Response.status(Status.CONFLICT).entity("Invalid change token").build(); 086 } 087 DocumentModel returnedDoc = isVersioning ? session.getLastDocumentVersion(doc.getRef()) : doc; 088 return Response.ok(returnedDoc).build(); 089 } 090 091 @POST 092 @Consumes(MediaType.APPLICATION_JSON) 093 public Response doPost(DocumentModel inputDoc) { 094 CoreSession session = ctx.getCoreSession(); 095 096 if (StringUtils.isBlank(inputDoc.getType()) || StringUtils.isBlank(inputDoc.getName())) { 097 return Response.status(Status.BAD_REQUEST).entity("type or name property is missing").build(); 098 } 099 100 DocumentModel createdDoc = session.createDocumentModel(doc.getPathAsString(), inputDoc.getName(), 101 inputDoc.getType()); 102 DocumentModelJsonReader.applyPropertyValues(inputDoc, createdDoc); 103 createdDoc = session.createDocument(createdDoc); 104 session.save(); 105 return Response.ok(createdDoc).status(Status.CREATED).build(); 106 } 107 108 @DELETE 109 public Response doDelete() { 110 super.doDelete(); 111 return Response.noContent().build(); 112 } 113 114 @Override 115 @Path("@search") 116 public Object search() { 117 return ctx.newAdapter(this, "search"); 118 } 119 120 @Override 121 public DocumentObject newDocument(String path) { 122 PathRef pathRef = new PathRef(doc.getPath().append(path).toString()); 123 DocumentModel doc = ctx.getCoreSession().getDocument(pathRef); 124 return (DocumentObject) ctx.newObject("Document", doc); 125 } 126 127 @Override 128 public DocumentObject newDocument(DocumentRef ref) { 129 DocumentModel doc = ctx.getCoreSession().getDocument(ref); 130 return (DocumentObject) ctx.newObject("Document", doc); 131 } 132 133 @Override 134 public DocumentObject newDocument(DocumentModel doc) { 135 return (DocumentObject) ctx.newObject("Document", doc); 136 } 137 138 /** 139 * In case of version option header presence, checkin the related document 140 * 141 * @param headers X-Versioning-Option or Source (for automatic versioning) Header 142 */ 143 private void versioningDocFromHeaderIfExists(HttpHeaders headers) { 144 isVersioning = false; 145 List<String> versionHeader = headers.getRequestHeader(RestConstants.X_VERSIONING_OPTION); 146 List<String> sourceHeader = headers.getRequestHeader(RestConstants.SOURCE); 147 if (versionHeader != null && !versionHeader.isEmpty()) { 148 VersioningOption versioningOption = VersioningOption.valueOf(versionHeader.get(0).toUpperCase()); 149 if (!versioningOption.equals(VersioningOption.NONE)) { 150 doc.putContextData(VersioningService.VERSIONING_OPTION, versioningOption); 151 isVersioning = true; 152 } 153 } else if (sourceHeader != null && !sourceHeader.isEmpty()) { 154 doc.putContextData(CoreSession.SOURCE, sourceHeader.get(0)); 155 isVersioning = true; 156 } 157 } 158 159 /** 160 * Fills the {@code doc} context data with a comment from the {@code Update-Comment} header if present. 161 * 162 * @since 9.3 163 */ 164 protected void updateCommentFromHeader(HttpHeaders headers) { 165 List<String> updateCommentHeader = headers.getRequestHeader(RestConstants.UPDATE_COMMENT_HEADER); 166 if (updateCommentHeader != null && !updateCommentHeader.isEmpty()) { 167 String comment = updateCommentHeader.get(0); 168 doc.putContextData("comment", comment); 169 } 170 } 171}