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