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}