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