/* * (C) Copyright 2014-2016 Nuxeo SA (http://nuxeo.com/) and others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Benoit Delbosc * Florent Guillaume */ package org.nuxeo.ecm.automation.jaxrs.io.documents; import static org.nuxeo.ecm.core.api.security.SecurityConstants.BROWSE; import static org.nuxeo.ecm.core.api.security.SecurityConstants.EVERYONE; import static org.nuxeo.ecm.core.api.security.SecurityConstants.UNSUPPORTED_ACL; import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import javax.servlet.ServletRequest; import javax.ws.rs.Produces; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.MessageBodyWriter; import javax.ws.rs.ext.Provider; import org.apache.commons.lang.StringUtils; import org.codehaus.jackson.JsonGenerator; import org.nuxeo.ecm.automation.core.util.JSONPropertyWriter; import org.nuxeo.ecm.automation.jaxrs.io.JsonHelper; import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.api.model.Property; import org.nuxeo.ecm.core.api.security.ACE; import org.nuxeo.ecm.core.api.security.ACL; import org.nuxeo.ecm.core.api.security.ACP; import org.nuxeo.ecm.core.api.security.impl.ACPImpl; import org.nuxeo.ecm.core.io.download.DownloadService; import org.nuxeo.ecm.core.schema.SchemaManager; import org.nuxeo.ecm.core.security.SecurityService; import org.nuxeo.ecm.platform.tag.Tag; import org.nuxeo.ecm.platform.tag.TagService; import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; import org.nuxeo.runtime.api.Framework; /** * JSon writer that outputs a format ready to eat by elasticsearch. * * @since 5.9.3 */ @Provider @Produces({ JsonESDocumentWriter.MIME_TYPE }) public class JsonESDocumentWriter implements MessageBodyWriter<DocumentModel> { public static final String MIME_TYPE = "application/json+esentity"; public static final String DOCUMENT_PROPERTIES_HEADER = "X-NXDocumentProperties"; @Context protected HttpHeaders headers; @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return DocumentModel.class.isAssignableFrom(type) && MIME_TYPE.equals(mediaType.toString()); } @Override public long getSize(DocumentModel arg0, Class<?> arg1, Type arg2, Annotation[] arg3, MediaType arg4) { return -1L; } @Override public void writeTo(DocumentModel doc, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { // schema names: dublincore, file, ... or * List<String> props = headers.getRequestHeader(DOCUMENT_PROPERTIES_HEADER); String[] schemas = null; if (props != null && !props.isEmpty()) { schemas = StringUtils.split(props.get(0), ", "); } writeDocument(entityStream, doc, schemas, null); } public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas, Map<String, String> contextParameters, HttpHeaders headers) throws IOException { jg.writeStartObject(); writeSystemProperties(jg, doc); writeSchemas(jg, doc, schemas); writeContextParameters(jg, doc, contextParameters); jg.writeEndObject(); jg.flush(); } /** * @since 7.2 */ protected void writeSystemProperties(JsonGenerator jg, DocumentModel doc) throws IOException { jg.writeStringField("ecm:repository", doc.getRepositoryName()); jg.writeStringField("ecm:uuid", doc.getId()); jg.writeStringField("ecm:name", doc.getName()); jg.writeStringField("ecm:title", doc.getTitle()); String pathAsString = doc.getPathAsString(); jg.writeStringField("ecm:path", pathAsString); if (StringUtils.isNotBlank(pathAsString)) { String[] split = pathAsString.split("/"); if (split.length > 0) { for (int i = 1; i < split.length; i++) { jg.writeStringField("ecm:path@level" + i, split[i]); } } jg.writeNumberField("ecm:path@depth", split.length); } jg.writeStringField("ecm:primaryType", doc.getType()); DocumentRef parentRef = doc.getParentRef(); if (parentRef != null) { jg.writeStringField("ecm:parentId", parentRef.toString()); } jg.writeStringField("ecm:currentLifeCycleState", doc.getCurrentLifeCycleState()); jg.writeStringField("ecm:versionLabel", doc.getVersionLabel()); jg.writeBooleanField("ecm:isCheckedIn", !doc.isCheckedOut()); jg.writeBooleanField("ecm:isProxy", doc.isProxy()); jg.writeBooleanField("ecm:isVersion", doc.isVersion()); jg.writeBooleanField("ecm:isLatestVersion", doc.isLatestVersion()); jg.writeBooleanField("ecm:isLatestMajorVersion", doc.isLatestMajorVersion()); jg.writeArrayFieldStart("ecm:mixinType"); for (String facet : doc.getFacets()) { jg.writeString(facet); } jg.writeEndArray(); TagService tagService = Framework.getService(TagService.class); if (tagService != null) { jg.writeArrayFieldStart("ecm:tag"); for (Tag tag : tagService.getDocumentTags(doc.getCoreSession(), doc.getId(), null, true)) { jg.writeString(tag.getLabel()); } jg.writeEndArray(); } jg.writeStringField("ecm:changeToken", doc.getChangeToken()); Long pos = doc.getPos(); if (pos != null) { jg.writeNumberField("ecm:pos", pos); } // Add a positive ACL only SecurityService securityService = Framework.getService(SecurityService.class); List<String> browsePermissions = new ArrayList<String>( Arrays.asList(securityService.getPermissionsToCheck(BROWSE))); ACP acp = doc.getACP(); if (acp == null) { acp = new ACPImpl(); } jg.writeArrayFieldStart("ecm:acl"); outerloop: for (ACL acl : acp.getACLs()) { for (ACE ace : acl.getACEs()) { if (ace.isGranted() && ace.isEffective() && browsePermissions.contains(ace.getPermission())) { jg.writeString(ace.getUsername()); } if (ace.isDenied() && ace.isEffective()) { if (!EVERYONE.equals(ace.getUsername())) { jg.writeString(UNSUPPORTED_ACL); } break outerloop; } } } jg.writeEndArray(); Map<String, String> bmap = doc.getBinaryFulltext(); if (bmap != null && !bmap.isEmpty()) { for (Map.Entry<String, String> item : bmap.entrySet()) { String value = item.getValue(); if (value != null) { jg.writeStringField("ecm:" + item.getKey(), value); } } } } /** * @since 7.2 */ protected void writeSchemas(JsonGenerator jg, DocumentModel doc, String[] schemas) throws IOException { if (schemas == null || (schemas.length == 1 && "*".equals(schemas[0]))) { schemas = doc.getSchemas(); } for (String schema : schemas) { writeProperties(jg, doc, schema, null); } } /** * @since 7.2 */ protected void writeContextParameters(JsonGenerator jg, DocumentModel doc, Map<String, String> contextParameters) throws IOException { if (contextParameters != null && !contextParameters.isEmpty()) { for (Map.Entry<String, String> parameter : contextParameters.entrySet()) { jg.writeStringField(parameter.getKey(), parameter.getValue()); } } } public void writeDocument(OutputStream out, DocumentModel doc, String[] schemas, Map<String, String> contextParameters) throws IOException { writeDoc(JsonHelper.createJsonGenerator(out), doc, schemas, contextParameters, headers); } public void writeESDocument(JsonGenerator jg, DocumentModel doc, String[] schemas, Map<String, String> contextParameters) throws IOException { writeDoc(jg, doc, schemas, contextParameters, null); } protected static void writeProperties(JsonGenerator jg, DocumentModel doc, String schema, ServletRequest request) throws IOException { Collection<Property> properties = doc.getPropertyObjects(schema); if (properties.isEmpty()) { return; } SchemaManager schemaManager = Framework.getService(SchemaManager.class); String prefix = schemaManager.getSchema(schema).getNamespace().prefix; if (prefix == null || prefix.length() == 0) { prefix = schema; } JSONPropertyWriter writer = JSONPropertyWriter.create().writeNull(false).writeEmpty(false).prefix(prefix); if (request != null) { DownloadService downloadService = Framework.getService(DownloadService.class); String blobUrlPrefix = VirtualHostHelper.getBaseURL(request) + downloadService.getDownloadUrl(doc, null, null) + "/"; writer.filesBaseUrl(blobUrlPrefix); } for (Property p : properties) { writer.writeProperty(jg, p); } } }