001/* 002 * (C) Copyright 2014 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 * Benoit Delbosc 018 */ 019package org.nuxeo.ecm.automation.jaxrs.io.documents; 020 021import static org.nuxeo.ecm.core.api.security.SecurityConstants.BROWSE; 022import static org.nuxeo.ecm.core.api.security.SecurityConstants.EVERYONE; 023import static org.nuxeo.ecm.core.api.security.SecurityConstants.UNSUPPORTED_ACL; 024 025import java.io.IOException; 026import java.io.OutputStream; 027import java.lang.annotation.Annotation; 028import java.lang.reflect.Type; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.List; 032import java.util.Map; 033 034import javax.ws.rs.Produces; 035import javax.ws.rs.core.HttpHeaders; 036import javax.ws.rs.core.MediaType; 037import javax.ws.rs.ext.Provider; 038 039import org.codehaus.jackson.JsonEncoding; 040import org.codehaus.jackson.JsonGenerator; 041import org.nuxeo.ecm.core.api.DocumentModel; 042import org.nuxeo.ecm.core.api.DocumentRef; 043import org.nuxeo.ecm.core.api.security.ACE; 044import org.nuxeo.ecm.core.api.security.ACL; 045import org.nuxeo.ecm.core.api.security.ACP; 046import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 047import org.nuxeo.ecm.core.security.SecurityService; 048import org.nuxeo.ecm.platform.tag.Tag; 049import org.nuxeo.ecm.platform.tag.TagService; 050import org.nuxeo.runtime.api.Framework; 051 052/** 053 * JSon writer that outputs a format ready to eat by elasticsearch. 054 * 055 * @since 5.9.3 056 */ 057@Provider 058@Produces({ JsonESDocumentWriter.MIME_TYPE }) 059// TODO: remove the use of JsonDocumentWriter - refactor the ES marshalling and use nuxeo-core-io marshalling instead 060@SuppressWarnings("deprecation") 061public class JsonESDocumentWriter extends JsonDocumentWriter { 062 063 public static final String MIME_TYPE = "application/json+esentity"; 064 065 @Override 066 public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { 067 return super.isWriteable(type, genericType, annotations, mediaType) && MIME_TYPE.equals(mediaType.toString()); 068 } 069 070 public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas, Map<String, String> contextParameters, 071 HttpHeaders headers) throws IOException { 072 073 jg.writeStartObject(); 074 writeSystemProperties(jg, doc); 075 writeSchemas(jg, doc, schemas); 076 writeContextParameters(jg, doc, contextParameters); 077 jg.writeEndObject(); 078 jg.flush(); 079 } 080 081 /** 082 * @since 7.2 083 */ 084 protected void writeSystemProperties(JsonGenerator jg, DocumentModel doc) throws IOException { 085 jg.writeStringField("ecm:repository", doc.getRepositoryName()); 086 jg.writeStringField("ecm:uuid", doc.getId()); 087 jg.writeStringField("ecm:name", doc.getName()); 088 jg.writeStringField("ecm:title", doc.getTitle()); 089 jg.writeStringField("ecm:path", doc.getPathAsString()); 090 jg.writeStringField("ecm:primaryType", doc.getType()); 091 DocumentRef parentRef = doc.getParentRef(); 092 if (parentRef != null) { 093 jg.writeStringField("ecm:parentId", parentRef.toString()); 094 } 095 jg.writeStringField("ecm:currentLifeCycleState", doc.getCurrentLifeCycleState()); 096 jg.writeStringField("ecm:versionLabel", doc.getVersionLabel()); 097 jg.writeBooleanField("ecm:isCheckedIn", !doc.isCheckedOut()); 098 jg.writeBooleanField("ecm:isProxy", doc.isProxy()); 099 jg.writeBooleanField("ecm:isVersion", doc.isVersion()); 100 jg.writeBooleanField("ecm:isLatestVersion", doc.isLatestVersion()); 101 jg.writeBooleanField("ecm:isLatestMajorVersion", doc.isLatestMajorVersion()); 102 jg.writeArrayFieldStart("ecm:mixinType"); 103 for (String facet : doc.getFacets()) { 104 jg.writeString(facet); 105 } 106 jg.writeEndArray(); 107 TagService tagService = Framework.getService(TagService.class); 108 if (tagService != null) { 109 jg.writeArrayFieldStart("ecm:tag"); 110 for (Tag tag : tagService.getDocumentTags(doc.getCoreSession(), doc.getId(), null, true)) { 111 jg.writeString(tag.getLabel()); 112 } 113 jg.writeEndArray(); 114 } 115 jg.writeStringField("ecm:changeToken", doc.getChangeToken()); 116 Long pos = doc.getPos(); 117 if (pos != null) { 118 jg.writeNumberField("ecm:pos", pos); 119 } 120 // Add a positive ACL only 121 SecurityService securityService = Framework.getService(SecurityService.class); 122 List<String> browsePermissions = new ArrayList<String>( 123 Arrays.asList(securityService.getPermissionsToCheck(BROWSE))); 124 ACP acp = doc.getACP(); 125 if (acp == null) { 126 acp = new ACPImpl(); 127 } 128 jg.writeArrayFieldStart("ecm:acl"); 129 outerloop: for (ACL acl : acp.getACLs()) { 130 for (ACE ace : acl.getACEs()) { 131 if (ace.isGranted() && ace.isEffective() && browsePermissions.contains(ace.getPermission())) { 132 jg.writeString(ace.getUsername()); 133 } 134 if (ace.isDenied() && ace.isEffective()) { 135 if (!EVERYONE.equals(ace.getUsername())) { 136 jg.writeString(UNSUPPORTED_ACL); 137 } 138 break outerloop; 139 } 140 } 141 } 142 143 jg.writeEndArray(); 144 Map<String, String> bmap = doc.getBinaryFulltext(); 145 if (bmap != null && !bmap.isEmpty()) { 146 for (Map.Entry<String, String> item : bmap.entrySet()) { 147 String value = item.getValue(); 148 if (value != null) { 149 jg.writeStringField("ecm:" + item.getKey(), value); 150 } 151 } 152 } 153 } 154 155 /** 156 * @since 7.2 157 */ 158 protected void writeSchemas(JsonGenerator jg, DocumentModel doc, String[] schemas) throws IOException { 159 if (schemas == null || (schemas.length == 1 && "*".equals(schemas[0]))) { 160 schemas = doc.getSchemas(); 161 } 162 for (String schema : schemas) { 163 writeProperties(jg, doc, schema, null); 164 } 165 } 166 167 /** 168 * @since 7.2 169 */ 170 protected void writeContextParameters(JsonGenerator jg, DocumentModel doc, Map<String, String> contextParameters) 171 throws IOException { 172 if (contextParameters != null && !contextParameters.isEmpty()) { 173 for (Map.Entry<String, String> parameter : contextParameters.entrySet()) { 174 jg.writeStringField(parameter.getKey(), parameter.getValue()); 175 } 176 } 177 } 178 179 @Override 180 public void writeDocument(OutputStream out, DocumentModel doc, String[] schemas, 181 Map<String, String> contextParameters) throws IOException { 182 writeDoc(factory.createJsonGenerator(out, JsonEncoding.UTF8), doc, schemas, contextParameters, headers); 183 } 184 185 public void writeESDocument(JsonGenerator jg, DocumentModel doc, String[] schemas, 186 Map<String, String> contextParameters) throws IOException { 187 writeDoc(jg, doc, schemas, contextParameters, null); 188 } 189}