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