From: Ray Lee Date: Thu, 3 Jan 2019 07:53:12 +0000 (-0800) Subject: DRYD-559: Initial implementation of ES document writer. X-Git-Url: https://git.aero2k.de/?a=commitdiff_plain;h=655de48c5c16021c74b3e2c927bb284f30fd9c93;p=tmp%2Fjakarta-migration.git DRYD-559: Initial implementation of ES document writer. --- diff --git a/3rdparty/nuxeo/build.xml b/3rdparty/nuxeo/build.xml index a1190749a..b65277bb4 100644 --- a/3rdparty/nuxeo/build.xml +++ b/3rdparty/nuxeo/build.xml @@ -146,11 +146,12 @@ - - + + - @@ -163,6 +164,7 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/pom.xml b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/pom.xml new file mode 100644 index 000000000..517a6d5fc --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + org.collectionspace.services.3rdparty.nuxeo + org.collectionspace.services + 5.2-SNAPSHOT + + org.collectionspace.services.3rdparty.nuxeo.elasticsearch + org.collectionspace.services.3rdparty.nuxeo.elasticsearch + http://maven.apache.org + + UTF-8 + + + + + org.nuxeo.ecm.core + nuxeo-core-api + ${nuxeo.core.version} + + + org.nuxeo.ecm.automation + nuxeo-automation-io + ${nuxeo.core.version} + + + org.collectionspace.services + org.collectionspace.services.common + ${project.version} + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-jar-plugin + + + src/main/resources/META-INF/MANIFEST.MF + + ${eclipseVersion} + 2 + + + + + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java new file mode 100644 index 000000000..810dd278c --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java @@ -0,0 +1,269 @@ +package org.collectionspace.services.nuxeo.elasticsearch; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.HttpHeaders; + +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jackson.JsonGenerator; +import org.codehaus.jackson.JsonNode; +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.node.ObjectNode; +import org.codehaus.jackson.node.TextNode; + +import org.collectionspace.services.common.api.RefNameUtils; + +import org.nuxeo.ecm.automation.jaxrs.io.documents.JsonESDocumentWriter; +import org.nuxeo.ecm.core.api.CoreSession; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.DocumentModelList; + +public class CSJsonESDocumentWriter extends JsonESDocumentWriter { + private static ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas, + Map contextParameters, HttpHeaders headers) + throws IOException { + + // Compute and store fields that should be indexed with this document in ElasticSearch. + // TODO: Make this configurable. This is currently hardcoded for the materials profile and + // the Material Order application. + + ObjectNode denormValues = objectMapper.createObjectNode(); + + String docType = doc.getType(); + + if (docType.startsWith("Materialitem")) { + CoreSession session = doc.getCoreSession(); + + // Store the csids of media records that reference this material authority item via the + // coverage field. + + String refName = (String) doc.getProperty("collectionspace_core", "refName"); + + if (StringUtils.isNotEmpty(refName)) { + String escapedRefName = refName.replace("'", "\\'"); + String mediaQuery = String.format("SELECT * FROM Media WHERE media_common:coverage = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '2000' ORDER BY media_common:identificationNumber", escapedRefName); + + DocumentModelList mediaDocs = session.query(mediaQuery); + List mediaCsids = new ArrayList(); + + if (mediaDocs.size() > 0) { + Iterator iterator = mediaDocs.iterator(); + + while (iterator.hasNext()) { + DocumentModel mediaDoc = iterator.next(); + + if (isMediaPublished(mediaDoc)) { + String mediaCsid = (String) mediaDoc.getName(); + + mediaCsids.add(new TextNode(mediaCsid)); + } + } + } + + denormValues.putArray("mediaCsid").addAll(mediaCsids); + } + + // Compute the title of the record for the public browser, and store it so that it can + // be used for sorting ES query results. + + String title = computeTitle(doc); + + if (title != null) { + denormValues.put("title", title); + } + + List> termGroups = (List>) doc.getProperty("materials_common", "materialTermGroupList"); + List commercialNames = findTermDisplayNamesWithFlag(termGroups, "commercial"); + List commonNames = findTermDisplayNamesWithFlag(termGroups, "common"); + + // Find and store the commercial names and common names for this item. This simplifies + // search and display in the Material Order application. + + if (commercialNames.size() > 0) { + denormValues.putArray("commercialNames").addAll(jsonNodes(commercialNames)); + } + + if (commonNames.size() > 0) { + denormValues.putArray("commonNames").addAll(jsonNodes(commonNames)); + } + } + + // Below is sample code for denormalizing fields from the computed current location (place + // item) into collection object documents. This was written for the public browser + // prototype for public art. + + /* + if (docType.startsWith("CollectionObject")) { + CoreSession session = doc.getCoreSession(); + + String refName = (String) doc.getProperty("collectionobjects_common", "computedCurrentLocation"); + + if (StringUtils.isNotEmpty(refName)) { + String escapedRefName = refName.replace("'", "\\'"); + String placeQuery = String.format("SELECT * FROM PlaceitemTenant5000 WHERE places_common:refName = '%s'", escapedRefName); + + DocumentModelList placeDocs = session.query(placeQuery, 1); + + if (placeDocs.size() > 0) { + DocumentModel placeDoc = placeDocs.get(0); + + String placementType = (String) placeDoc.getProperty("places_publicart:placementType").getValue(); + + if (placementType != null) { + denormValues.put("placementType", placementType); + } + + Property geoRefGroup; + + try { + geoRefGroup = placeDoc.getProperty("places_common:placeGeoRefGroupList/0"); + } catch (PropertyNotFoundException e) { + geoRefGroup = null; + } + + if (geoRefGroup != null) { + Double decimalLatitude = (Double) geoRefGroup.getValue("decimalLatitude"); + Double decimalLongitude = (Double) geoRefGroup.getValue("decimalLongitude"); + + if (decimalLatitude != null && decimalLongitude != null) { + ObjectNode geoPointNode = objectMapper.createObjectNode(); + + geoPointNode.put("lat", decimalLatitude); + geoPointNode.put("lon", decimalLongitude); + + denormValues.put("geoPoint", geoPointNode); + } + } + } + } + + String uri = (String) doc.getProperty("collectionobjects_core", "uri"); + String csid = uri.substring(uri.lastIndexOf('/') + 1); + String mediaQuery = String.format("SELECT media_common:blobCsid, media_common:title FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'Media'", csid); + + DocumentModelList mediaDocs = session.query(mediaQuery, 1); + + if (mediaDocs.size() > 0) { + + } + } + */ + + jg.writeStartObject(); + + writeSystemProperties(jg, doc); + writeSchemas(jg, doc, schemas); + writeContextParameters(jg, doc, contextParameters); + writeDenormValues(jg, doc, denormValues); + + jg.writeEndObject(); + jg.flush(); + } + + public void writeDenormValues(JsonGenerator jg, DocumentModel doc, ObjectNode denormValues) throws IOException { + if (denormValues != null && denormValues.size() > 0) { + if (jg.getCodec() == null) { + jg.setCodec(objectMapper); + } + + Iterator> entries = denormValues.getFields(); + + while (entries.hasNext()) { + Map.Entry entry = entries.next(); + + jg.writeFieldName("collectionspace_denorm:" + entry.getKey()); + jg.writeTree(entry.getValue()); + } + } + } + + /** + * Compute a title for the public browser. This needs to be indexed in ES so that it can + * be used for sorting. (Even if it's just extracting the primary value.) + */ + private String computeTitle(DocumentModel doc) { + List> termGroups = (List>) doc.getProperty("materials_common", "materialTermGroupList"); + String primaryDisplayName = null; + + if (termGroups.size() > 0) { + Map primaryTermGroup = termGroups.get(0); + primaryDisplayName = (String) primaryTermGroup.get("termDisplayName"); + } + + return primaryDisplayName; + } + + private String findFirstTermDisplayNameWithFlag(List> termGroups, String flagShortId) { + String termDisplayName = null; + + for (Map termGroup : termGroups) { + String termFlag = (String) termGroup.get("termFlag"); + + if (termFlag != null && termFlag.contains("(" + flagShortId + ")")) { + String candidateTermDisplayName = (String) termGroup.get("termDisplayName"); + + if (StringUtils.isNotEmpty(candidateTermDisplayName)) { + termDisplayName = candidateTermDisplayName; + break; + } + } + } + + return termDisplayName; + } + + private List findTermDisplayNamesWithFlag(List> termGroups, String flagShortId) { + List termDisplayNames = new ArrayList(); + + for (Map termGroup : termGroups) { + String termFlag = (String) termGroup.get("termFlag"); + + if (termFlag != null && termFlag.contains("(" + flagShortId + ")")) { + String candidateTermDisplayName = (String) termGroup.get("termDisplayName"); + + if (StringUtils.isNotEmpty(candidateTermDisplayName)) { + termDisplayNames.add(candidateTermDisplayName); + } + } + } + + return termDisplayNames; + } + + private boolean isMediaPublished(DocumentModel mediaDoc) { + List publishToValues = (List) mediaDoc.getProperty("media_materials", "publishToList"); + boolean isPublished = false; + + for (int i=0; i jsonNodes(List values) { + List nodes = new ArrayList(); + Iterator iterator = values.iterator(); + + while (iterator.hasNext()) { + String value = iterator.next(); + + nodes.add(new TextNode(value)); + } + + return nodes; + } +} diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/META-INF/MANIFEST.MF b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/META-INF/MANIFEST.MF new file mode 100644 index 000000000..cf42ab7f9 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 1 +Bundle-Name: org.collectionspace.nuxeo.elasticsearch +Bundle-SymbolicName: org.collectionspace.nuxeo.elasticsearch;singleton:=true +Bundle-Version: 1.0.0 +Bundle-Localization: plugin +Bundle-Vendor: Nuxeo +Require-Bundle: org.nuxeo.runtime, + org.nuxeo.ecm.core.api, + org.nuxeo.ecm.core, + org.nuxeo.ecm.webapp.core +Provide-Package: org.collectionspace.nuxeo.elasticsearch +Nuxeo-Component: OSGI-INF/elasticsearch-contrib.xml, diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/deployment-fragment.xml b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/deployment-fragment.xml new file mode 100644 index 000000000..270abbd5f --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/deployment-fragment.xml @@ -0,0 +1,10 @@ + + + + + + ${bundle.fileName} + + + + diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/elasticsearch-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/elasticsearch-contrib.xml new file mode 100644 index 000000000..962ddec1c --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/elasticsearch-contrib.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/3rdparty/nuxeo/pom.xml b/3rdparty/nuxeo/pom.xml index 3dd1652be..44b1f3e9d 100644 --- a/3rdparty/nuxeo/pom.xml +++ b/3rdparty/nuxeo/pom.xml @@ -16,7 +16,8 @@ nuxeo-platform-collectionspace nuxeo-platform-listener -