001/* 002 * (C) Copyright 2014-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 * Benoit Delbosc 018 * Florent Guillaume 019 */ 020package org.nuxeo.elasticsearch.io; 021 022import static org.nuxeo.ecm.core.api.security.SecurityConstants.BROWSE; 023import static org.nuxeo.ecm.core.api.security.SecurityConstants.EVERYONE; 024import static org.nuxeo.ecm.core.api.security.SecurityConstants.UNSUPPORTED_ACL; 025 026import java.io.IOException; 027import java.util.ArrayList; 028import java.util.Arrays; 029import java.util.Collection; 030import java.util.List; 031import java.util.Map; 032 033import javax.servlet.ServletRequest; 034 035import org.apache.commons.lang3.StringUtils; 036import org.nuxeo.ecm.automation.core.util.JSONPropertyWriter; 037import org.nuxeo.ecm.core.api.CoreSession; 038import org.nuxeo.ecm.core.api.DocumentModel; 039import org.nuxeo.ecm.core.api.DocumentRef; 040import org.nuxeo.ecm.core.api.model.Property; 041import org.nuxeo.ecm.core.api.security.ACE; 042import org.nuxeo.ecm.core.api.security.ACL; 043import org.nuxeo.ecm.core.api.security.ACP; 044import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 045import org.nuxeo.ecm.core.io.download.DownloadService; 046import org.nuxeo.ecm.core.schema.SchemaManager; 047import org.nuxeo.ecm.core.security.SecurityService; 048import org.nuxeo.ecm.platform.tag.TagService; 049import org.nuxeo.ecm.platform.web.common.vh.VirtualHostHelper; 050import org.nuxeo.runtime.api.Framework; 051 052import com.fasterxml.jackson.core.JsonGenerator; 053 054/** 055 * JSon writer that outputs a format ready to eat by elasticsearch. 056 * 057 * @since 5.9.3 058 */ 059public class JsonESDocumentWriter { 060 061 /** 062 * @since 7.2 063 */ 064 protected void writeSystemProperties(JsonGenerator jg, DocumentModel doc) throws IOException { 065 String docId = doc.getId(); 066 CoreSession session = doc.getCoreSession(); 067 jg.writeStringField("ecm:repository", doc.getRepositoryName()); 068 jg.writeStringField("ecm:uuid", docId); 069 jg.writeStringField("ecm:name", doc.getName()); 070 jg.writeStringField("ecm:title", doc.getTitle()); 071 072 String pathAsString = doc.getPathAsString(); 073 jg.writeStringField("ecm:path", pathAsString); 074 if (StringUtils.isNotBlank(pathAsString)) { 075 String[] split = pathAsString.split("/"); 076 if (split.length > 0) { 077 for (int i = 1; i < split.length; i++) { 078 jg.writeStringField("ecm:path@level" + i, split[i]); 079 } 080 } 081 jg.writeNumberField("ecm:path@depth", split.length); 082 } 083 084 jg.writeStringField("ecm:primaryType", doc.getType()); 085 DocumentRef parentRef = doc.getParentRef(); 086 if (parentRef != null) { 087 jg.writeStringField("ecm:parentId", parentRef.toString()); 088 } 089 jg.writeStringField("ecm:currentLifeCycleState", doc.getCurrentLifeCycleState()); 090 if (doc.isVersion()) { 091 jg.writeStringField("ecm:versionLabel", doc.getVersionLabel()); 092 jg.writeStringField("ecm:versionVersionableId", doc.getVersionSeriesId()); 093 } 094 jg.writeBooleanField("ecm:isCheckedIn", !doc.isCheckedOut()); 095 jg.writeBooleanField("ecm:isProxy", doc.isProxy()); 096 jg.writeBooleanField("ecm:isTrashed", doc.isTrashed()); 097 jg.writeBooleanField("ecm:isVersion", doc.isVersion()); 098 jg.writeBooleanField("ecm:isLatestVersion", doc.isLatestVersion()); 099 jg.writeBooleanField("ecm:isLatestMajorVersion", doc.isLatestMajorVersion()); 100 jg.writeArrayFieldStart("ecm:mixinType"); 101 for (String facet : doc.getFacets()) { 102 jg.writeString(facet); 103 } 104 jg.writeEndArray(); 105 TagService tagService = Framework.getService(TagService.class); 106 if (tagService != null && tagService.supportsTag(session, docId)) { 107 jg.writeArrayFieldStart("ecm:tag"); 108 for (String tag : tagService.getTags(session, docId)) { 109 jg.writeString(tag); 110 } 111 jg.writeEndArray(); 112 } 113 jg.writeStringField("ecm:changeToken", doc.getChangeToken()); 114 Long pos = doc.getPos(); 115 if (pos != null) { 116 jg.writeNumberField("ecm:pos", pos.longValue()); 117 } 118 // Add a positive ACL only 119 SecurityService securityService = Framework.getService(SecurityService.class); 120 List<String> browsePermissions = new ArrayList<>(Arrays.asList(securityService.getPermissionsToCheck(BROWSE))); 121 ACP acp = doc.getACP(); 122 if (acp == null) { 123 acp = new ACPImpl(); 124 } 125 jg.writeArrayFieldStart("ecm:acl"); 126 outerloop: for (ACL acl : acp.getACLs()) { 127 for (ACE ace : acl.getACEs()) { 128 if (ace.isGranted() && ace.isEffective() && browsePermissions.contains(ace.getPermission())) { 129 jg.writeString(ace.getUsername()); 130 } 131 if (ace.isDenied() && ace.isEffective()) { 132 if (!EVERYONE.equals(ace.getUsername())) { 133 jg.writeString(UNSUPPORTED_ACL); 134 } 135 break outerloop; 136 } 137 } 138 } 139 140 jg.writeEndArray(); 141 Map<String, String> bmap = doc.getBinaryFulltext(); 142 if (bmap != null && !bmap.isEmpty()) { 143 for (Map.Entry<String, String> item : bmap.entrySet()) { 144 String value = item.getValue(); 145 if (value != null) { 146 jg.writeStringField("ecm:" + item.getKey(), value); 147 } 148 } 149 } 150 } 151 152 /** 153 * @since 7.2 154 */ 155 protected void writeSchemas(JsonGenerator jg, DocumentModel doc, String[] schemas) throws IOException { 156 if (schemas == null || (schemas.length == 1 && "*".equals(schemas[0]))) { 157 schemas = doc.getSchemas(); 158 } 159 for (String schema : schemas) { 160 writeProperties(jg, doc, schema, null); 161 } 162 } 163 164 /** 165 * @since 7.2 166 */ 167 protected void writeContextParameters(JsonGenerator jg, DocumentModel doc, Map<String, String> contextParameters) 168 throws IOException { 169 if (contextParameters != null && !contextParameters.isEmpty()) { 170 for (Map.Entry<String, String> parameter : contextParameters.entrySet()) { 171 jg.writeStringField(parameter.getKey(), parameter.getValue()); 172 } 173 } 174 } 175 176 public void writeESDocument(JsonGenerator jg, DocumentModel doc, String[] schemas, 177 Map<String, String> contextParameters) throws IOException { 178 jg.writeStartObject(); 179 writeSystemProperties(jg, doc); 180 writeSchemas(jg, doc, schemas); 181 writeContextParameters(jg, doc, contextParameters); 182 jg.writeEndObject(); 183 jg.flush(); 184 } 185 186 protected static void writeProperties(JsonGenerator jg, DocumentModel doc, String schema, ServletRequest request) 187 throws IOException { 188 Collection<Property> properties = doc.getPropertyObjects(schema); 189 if (properties.isEmpty()) { 190 return; 191 } 192 193 SchemaManager schemaManager = Framework.getService(SchemaManager.class); 194 String prefix = schemaManager.getSchema(schema).getNamespace().prefix; 195 if (prefix == null || prefix.length() == 0) { 196 prefix = schema; 197 } 198 JSONPropertyWriter writer = JSONPropertyWriter.create().writeNull(false).writeEmpty(false).prefix(prefix); 199 200 if (request != null) { 201 DownloadService downloadService = Framework.getService(DownloadService.class); 202 String blobUrlPrefix = VirtualHostHelper.getBaseURL(request) 203 + downloadService.getDownloadUrl(doc, null, null) + "/"; 204 writer.filesBaseUrl(blobUrlPrefix); 205 } 206 207 for (Property p : properties) { 208 writer.writeProperty(jg, p); 209 } 210 } 211 212}