From 17c3f48871e8fcb09470c7bee3eca21f31076e18 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Mon, 13 Apr 2020 23:48:44 -0400 Subject: [PATCH] DRYD-819: Add public browser support for core and standard profiles. --- .../elasticsearch/CSJsonESDocumentWriter.java | 319 ------------------ .../DefaultESDocumentWriter.java | 259 ++++++++++++++ .../TenantConfiguredESDocumentWriter.java | 91 +++++ .../anthro/AnthroESDocumentWriter.java | 27 ++ .../materials/MaterialsESDocumentWriter.java | 264 +++++++++++++++ .../OSGI-INF/elasticsearch-contrib.xml | 2 +- .../services/listener/Reindex.java | 265 ++++++++++----- .../services/listener/ReindexSupport.java | 171 +++++++--- .../main/resources/OSGI-INF/event-contrib.xml | 1 + .../config/proto-elasticsearch-extension.xml | 102 +++--- .../anthro/anthro-tenant-bindings.delta.xml | 255 +++++++++++++- .../fcart/fcart-tenant-bindings.delta.xml | 229 ++++++++++++- .../materials-tenant-bindings.delta.xml | 192 +++++------ .../tenants/tenant-bindings-proto-unified.xml | 272 +++++++++++++-- services/config/src/main/resources/tenant.xsd | 164 +++++---- 15 files changed, 1874 insertions(+), 739 deletions(-) delete mode 100644 3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/DefaultESDocumentWriter.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/TenantConfiguredESDocumentWriter.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/anthro/AnthroESDocumentWriter.java create mode 100644 3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/materials/MaterialsESDocumentWriter.java 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 deleted file mode 100644 index 8310650d1..000000000 --- a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java +++ /dev/null @@ -1,319 +0,0 @@ -package org.collectionspace.services.nuxeo.elasticsearch; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -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 tenantId = (String) doc.getProperty("collectionspace_core", "tenantId"); - String mediaQuery = String.format("SELECT * FROM Media WHERE media_common:coverage = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s' ORDER BY media_common:identificationNumber", escapedRefName, tenantId); - - 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)); - } - - // Combine term creator organizations and term editor organizations into a holding - // institutions field. - - Set holdingInstitutions = new LinkedHashSet(); - - holdingInstitutions.addAll(getTermAttributionContributors(doc)); - holdingInstitutions.addAll(getTermAttributionEditors(doc)); - - if (holdingInstitutions.size() > 0) { - denormValues.putArray("holdingInstitutions").addAll(jsonNodes(holdingInstitutions)); - } - } - - // 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 Set getTermAttributionContributors(DocumentModel doc) { - Set orgs = new LinkedHashSet(); - - List> groups = (List>) doc.getProperty("materials_common", "materialTermAttributionContributingGroupList"); - - for (Map group : groups) { - String org = (String) group.get("materialTermAttributionContributingOrganization"); - - if (StringUtils.isNotEmpty(org)) { - orgs.add(org); - } - } - - return orgs; - } - - private Set getTermAttributionEditors(DocumentModel doc) { - Set orgs = new LinkedHashSet(); - - List> groups = (List>) doc.getProperty("materials_common", "materialTermAttributionEditingGroupList"); - - for (Map group : groups) { - String org = (String) group.get("materialTermAttributionEditingOrganization"); - - if (StringUtils.isNotEmpty(org)) { - orgs.add(org); - } - } - - return orgs; - } - - private boolean isMediaPublished(DocumentModel mediaDoc) { - List publishToValues = (List) mediaDoc.getProperty("media_materials", "publishToList"); - boolean isPublished = false; - - for (int i=0; i jsonNodes(Collection 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/java/org/collectionspace/services/nuxeo/elasticsearch/DefaultESDocumentWriter.java b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/DefaultESDocumentWriter.java new file mode 100644 index 000000000..78d10acde --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/DefaultESDocumentWriter.java @@ -0,0 +1,259 @@ +package org.collectionspace.services.nuxeo.elasticsearch; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.GregorianCalendar; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +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.IntNode; +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 DefaultESDocumentWriter extends JsonESDocumentWriter { + private static ObjectMapper objectMapper = new ObjectMapper(); + + @Override + public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas, + Map contextParameters, HttpHeaders headers) + throws IOException { + + ObjectNode denormValues = getDenormValues(doc); + + jg.writeStartObject(); + + writeSystemProperties(jg, doc); + writeSchemas(jg, doc, schemas); + writeContextParameters(jg, doc, contextParameters); + writeDenormValues(jg, doc, denormValues); + + jg.writeEndObject(); + jg.flush(); + } + + public ObjectNode getDenormValues(DocumentModel doc) { + ObjectNode denormValues = objectMapper.createObjectNode(); + String docType = doc.getType(); + + if (docType.startsWith("CollectionObject")) { + CoreSession session = doc.getCoreSession(); + String csid = doc.getName(); + String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId"); + + denormMediaRecords(session, csid, tenantId, denormValues); + denormAcquisitionRecords(session, csid, tenantId, denormValues); + + // 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); + } + + // Create a list of production years from the production date structured dates. + + List> prodDateGroupList = (List>) doc.getProperty("collectionobjects_common", "objectProductionDateGroupList"); + + denormValues.putArray("prodYears").addAll(structDatesToYearNodes(prodDateGroupList)); + } + + return denormValues; + } + + 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()); + } + } + } + + private void denormMediaRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) { + // Store the csids of media records that are related to this object. + + String relatedRecordQuery = String.format("SELECT * FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'Media' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", csid, tenantId); + DocumentModelList relationDocs = session.query(relatedRecordQuery); + List mediaCsids = new ArrayList(); + + if (relationDocs.size() > 0) { + Iterator iterator = relationDocs.iterator(); + + while (iterator.hasNext()) { + DocumentModel relationDoc = iterator.next(); + String mediaCsid = (String) relationDoc.getProperty("relations_common", "objectCsid"); + + if (isMediaPublished(session, tenantId, mediaCsid)) { + mediaCsids.add(new TextNode(mediaCsid)); + } + } + } + + denormValues.putArray("mediaCsid").addAll(mediaCsids); + denormValues.put("hasMedia", mediaCsids.size() > 0); + } + + private void denormAcquisitionRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) { + // Store the credit lines of acquisition records that are related to this object. + + String relatedRecordQuery = String.format("SELECT * FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'Acquisition' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", csid, tenantId); + DocumentModelList relationDocs = session.query(relatedRecordQuery); + List creditLines = new ArrayList(); + + if (relationDocs.size() > 0) { + Iterator iterator = relationDocs.iterator(); + + while (iterator.hasNext()) { + DocumentModel relationDoc = iterator.next(); + String acquisitionCsid = (String) relationDoc.getProperty("relations_common", "objectCsid"); + String creditLine = getCreditLine(session, tenantId, acquisitionCsid); + + if (creditLine != null && creditLine.length() > 0) { + creditLines.add(new TextNode(creditLine)); + } + } + } + + denormValues.putArray("creditLine").addAll(creditLines); +} + + /** + * 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> titleGroups = (List>) doc.getProperty("collectionobjects_common", "titleGroupList"); + String primaryTitle = null; + + if (titleGroups.size() > 0) { + Map primaryTitleGroup = titleGroups.get(0); + primaryTitle = (String) primaryTitleGroup.get("title"); + } + + if (StringUtils.isNotEmpty(primaryTitle)) { + return primaryTitle; + } + + List> objectNameGroups = (List>) doc.getProperty("collectionobjects_common", "objectNameList"); + String primaryObjectName = null; + + if (objectNameGroups.size() > 0) { + Map primaryObjectNameGroup = objectNameGroups.get(0); + primaryObjectName = (String) primaryObjectNameGroup.get("objectName"); + } + + return primaryObjectName; + } + + private boolean isMediaPublished(CoreSession session, String tenantId, String mediaCsid) { + boolean isPublished = false; + DocumentModel mediaDoc = getRecordByCsid(session, tenantId, "Media", mediaCsid); + + if (mediaDoc != null) { + List publishToValues = (List) mediaDoc.getProperty("media_common", "publishToList"); + + if (publishToValues != null) { + for (int i=0; i 0) { + return docs.get(0); + } + + return null; + } + + protected List structDateToYearNodes(Map structDate) { + return structDatesToYearNodes(Arrays.asList(structDate)); + } + + protected List structDatesToYearNodes(List> structDates) { + Set years = new HashSet(); + + for (Map structDate : structDates) { + if (structDate != null) { + GregorianCalendar earliestCalendar = (GregorianCalendar) structDate.get("dateEarliestScalarValue"); + GregorianCalendar latestCalendar = (GregorianCalendar) structDate.get("dateLatestScalarValue"); + + if (earliestCalendar != null && latestCalendar != null) { + // Grr @ latest scalar value historically being exclusive. + // Subtract one day to make it inclusive. + latestCalendar.add(Calendar.DATE, -1); + + Integer earliestYear = earliestCalendar.get(Calendar.YEAR); + Integer latestYear = latestCalendar.get(Calendar.YEAR);; + + for (int year = earliestYear; year <= latestYear; year++) { + years.add(year); + } + } + } + } + + List yearList = new ArrayList(years); + Collections.sort(yearList); + + List yearNodes = new ArrayList(); + + for (Integer year : yearList) { + yearNodes.add(new IntNode(year)); + } + + return yearNodes; + } +} diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/TenantConfiguredESDocumentWriter.java b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/TenantConfiguredESDocumentWriter.java new file mode 100644 index 000000000..eceecc58a --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/TenantConfiguredESDocumentWriter.java @@ -0,0 +1,91 @@ +package org.collectionspace.services.nuxeo.elasticsearch; + +import java.io.IOException; +import java.util.Map; + +import javax.ws.rs.core.HttpHeaders; + +import org.apache.commons.lang3.StringUtils; +import org.codehaus.jackson.JsonGenerator; +import org.collectionspace.services.client.CollectionSpaceClient; +import org.collectionspace.services.common.ServiceMain; +import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; +import org.collectionspace.services.config.service.ServiceBindingType; +import org.collectionspace.services.config.tenant.TenantBindingType; +import org.nuxeo.ecm.automation.jaxrs.io.documents.JsonESDocumentWriter; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A JsonESDocumentWriter that delegates to the class that is specified in the + * CSpace tenant binding file for the current tenant. + */ +public class TenantConfiguredESDocumentWriter extends JsonESDocumentWriter { + final Logger logger = LoggerFactory.getLogger(TenantConfiguredESDocumentWriter.class); + + @Override + public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas, Map contextParameters, + HttpHeaders headers) throws IOException { + + String tenantId = (String) doc.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA, CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID); + + if (tenantId == null) { + writeEmptyDoc(jg); + return; + } + + TenantBindingConfigReaderImpl tenantBindingConfigReader = ServiceMain.getInstance().getTenantBindingConfigReader(); + TenantBindingType tenantBindingType = tenantBindingConfigReader.getTenantBinding(tenantId); + ServiceBindingType serviceBinding = tenantBindingConfigReader.getServiceBindingForDocType(tenantId, doc.getType()); + String documentWriterClassName = tenantBindingType.getElasticSearchDocumentWriter(); + + if (!serviceBinding.isElasticsearchIndexed() || StringUtils.isBlank(documentWriterClassName)) { + writeEmptyDoc(jg); + return; + } + + documentWriterClassName = documentWriterClassName.trim(); + + ClassLoader loader = Thread.currentThread().getContextClassLoader(); + Class documentWriterClass = null; + + try { + documentWriterClass = loader.loadClass(documentWriterClassName); + } catch (ClassNotFoundException e) { + String msg = String.format("Unable to load ES document writer for tenant %s with class %s", tenantId, documentWriterClassName); + + throw new IOException(msg, e); + } + + if (TenantConfiguredESDocumentWriter.class.equals(documentWriterClass)) { + String msg = String.format("ES document writer class for tenant %s must not be TenantConfiguredESDocumentWriter", tenantId); + + throw new IOException(msg); + } + + if (!JsonESDocumentWriter.class.isAssignableFrom(documentWriterClass)) { + String msg = String.format("ES document writer for tenant %s of class %s is not a subclass of JsonESDocumentWriter", tenantId, documentWriterClassName); + + throw new IOException(msg); + } + + JsonESDocumentWriter documentWriter = null; + + try { + documentWriter = (JsonESDocumentWriter) documentWriterClass.newInstance(); + } catch(Exception e) { + String msg = String.format("Unable to instantiate ES document writer class: %s", documentWriterClassName); + + throw new IOException(msg, e); + } + + documentWriter.writeDoc(jg, doc, schemas, contextParameters, headers); + } + + private void writeEmptyDoc(JsonGenerator jg) throws IOException { + jg.writeStartObject(); + jg.writeEndObject(); + jg.flush(); + } +} diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/anthro/AnthroESDocumentWriter.java b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/anthro/AnthroESDocumentWriter.java new file mode 100644 index 000000000..e3b041d94 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/anthro/AnthroESDocumentWriter.java @@ -0,0 +1,27 @@ +package org.collectionspace.services.nuxeo.elasticsearch.anthro; + +import java.util.Map; + +import org.codehaus.jackson.node.ObjectNode; + +import org.collectionspace.services.nuxeo.elasticsearch.DefaultESDocumentWriter; +import org.nuxeo.ecm.core.api.DocumentModel; + +public class AnthroESDocumentWriter extends DefaultESDocumentWriter { + + @Override + public ObjectNode getDenormValues(DocumentModel doc) { + ObjectNode denormValues = super.getDenormValues(doc); + String docType = doc.getType(); + + if (docType.startsWith("CollectionObject")) { + // Create a list of collection years from the field collection date structured date. + + Map fieldCollectionDateGroup = (Map) doc.getProperty("collectionobjects_common", "fieldCollectionDateGroup"); + + denormValues.putArray("collectionYears").addAll(structDateToYearNodes(fieldCollectionDateGroup)); + } + + return denormValues; + } +} diff --git a/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/materials/MaterialsESDocumentWriter.java b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/materials/MaterialsESDocumentWriter.java new file mode 100644 index 000000000..5fbe3b660 --- /dev/null +++ b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/materials/MaterialsESDocumentWriter.java @@ -0,0 +1,264 @@ +package org.collectionspace.services.nuxeo.elasticsearch.materials; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang3.StringUtils; + +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.collectionspace.services.nuxeo.elasticsearch.DefaultESDocumentWriter; +import org.nuxeo.ecm.core.api.CoreSession; +import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.DocumentModelList; + +public class MaterialsESDocumentWriter extends DefaultESDocumentWriter { + + @Override + public ObjectNode getDenormValues(DocumentModel doc) { + ObjectMapper objectMapper = new ObjectMapper(); + 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 tenantId = (String) doc.getProperty("collectionspace_core", "tenantId"); + String mediaQuery = String.format("SELECT * FROM Media WHERE media_common:coverage = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s' ORDER BY media_common:identificationNumber", escapedRefName, tenantId); + + 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)); + } + + // Combine term creator organizations and term editor organizations into a holding + // institutions field. + + Set holdingInstitutions = new LinkedHashSet(); + + holdingInstitutions.addAll(getTermAttributionContributors(doc)); + holdingInstitutions.addAll(getTermAttributionEditors(doc)); + + if (holdingInstitutions.size() > 0) { + denormValues.putArray("holdingInstitutions").addAll(jsonNodes(holdingInstitutions)); + } + } + + // 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) { + + } + } + */ + + return denormValues; + } + + /** + * 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 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 Set getTermAttributionContributors(DocumentModel doc) { + Set orgs = new LinkedHashSet(); + + List> groups = (List>) doc.getProperty("materials_common", "materialTermAttributionContributingGroupList"); + + for (Map group : groups) { + String org = (String) group.get("materialTermAttributionContributingOrganization"); + + if (StringUtils.isNotEmpty(org)) { + orgs.add(org); + } + } + + return orgs; + } + + private Set getTermAttributionEditors(DocumentModel doc) { + Set orgs = new LinkedHashSet(); + + List> groups = (List>) doc.getProperty("materials_common", "materialTermAttributionEditingGroupList"); + + for (Map group : groups) { + String org = (String) group.get("materialTermAttributionEditingOrganization"); + + if (StringUtils.isNotEmpty(org)) { + orgs.add(org); + } + } + + return orgs; + } + + private boolean isMediaPublished(DocumentModel mediaDoc) { + List publishToValues = (List) mediaDoc.getProperty("media_materials", "publishToList"); + boolean isPublished = false; + + if (publishToValues != null) { + for (int i=0; i jsonNodes(Collection 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/OSGI-INF/elasticsearch-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/elasticsearch-contrib.xml index 962ddec1c..bf6bc5944 100644 --- 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 @@ -2,6 +2,6 @@ - + diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/Reindex.java b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/Reindex.java index f645744e9..c32ca0a49 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/Reindex.java +++ b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/Reindex.java @@ -1,16 +1,16 @@ package org.collectionspace.services.listener; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import org.apache.commons.collections.ListUtils; import org.apache.commons.lang3.StringUtils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - +import org.collectionspace.services.common.api.RefNameUtils; import org.collectionspace.services.nuxeo.listener.AbstractCSEventPostCommitListenerImpl; +import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; -import org.nuxeo.ecm.core.api.LifeCycleConstants; +import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.event.DocumentEventTypes; import org.nuxeo.ecm.core.event.Event; import org.nuxeo.ecm.core.event.EventBundle; @@ -18,6 +18,8 @@ import org.nuxeo.ecm.core.event.impl.DocumentEventContext; import org.nuxeo.elasticsearch.ElasticSearchComponent; import org.nuxeo.elasticsearch.api.ElasticSearchService; import org.nuxeo.runtime.api.Framework; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Event listener that triggers reindexing of records in Elasticsearch when an associated record @@ -29,83 +31,139 @@ import org.nuxeo.runtime.api.Framework; public class Reindex extends AbstractCSEventPostCommitListenerImpl { private static final Logger logger = LoggerFactory.getLogger(Reindex.class); - // FIXME: This listener runs asynchronously post-commit, so that reindexing records after a - // save does not hold up the save. + // This listener runs asynchronously post-commit, so that reindexing records after a + // save does not hold up the save. + + public static final String PREV_COVERAGE_KEY = "Reindex.PREV_COVERAGE"; + public static final String PREV_CREDIT_LINE_KEY = "Reindex.PREV_CREDIT_LINE"; + public static final String PREV_PUBLISH_TO_KEY = "Reindex.PREV_PUBLISH_TO"; + public static final String PREV_RELATED_COLLECTION_OBJECT_CSID_KEY = "Reindex.PREV_RELATED_COLLECTION_OBJECT_CSID"; + public static final String ELASTICSEARCH_ENABLED_PROP = "elasticsearch.enabled"; - public static final String PREV_COVERAGE_KEY = "Reindex.PREV_COVERAGE"; - public static final String PREV_PUBLISH_TO_KEY = "Reindex.PREV_PUBLISH_TO"; - public static final String ELASTICSEARCH_ENABLED_PROP = "elasticsearch.enabled"; - @Override public boolean shouldHandleEventBundle(EventBundle eventBundle) { - if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_ENABLED_PROP) && eventBundle.size() > 0) { - return true; - } - - return false; + if (Framework.isBooleanPropertyTrue(ELASTICSEARCH_ENABLED_PROP) && eventBundle.size() > 0) { + return true; + } + + return false; } - + @Override public boolean shouldHandleEvent(Event event) { - DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); - DocumentModel doc = eventContext.getSourceDocument(); - String docType = doc.getType(); - - if (docType.startsWith("Media")) { - return true; - } - - return false; + DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); + DocumentModel doc = eventContext.getSourceDocument(); + String docType = doc.getType(); + + if ( + docType.startsWith("Media") + || docType.startsWith("Relation") + || docType.startsWith("Acquisition") + ) { + return true; + } + + return false; } @Override @SuppressWarnings("unchecked") public void handleCSEvent(Event event) { - DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); - DocumentModel doc = eventContext.getSourceDocument(); - String eventName = event.getName(); - - // When a media record is created, reindex the material item that is referenced by its - // coverage field. - - // When a media record is updated and the coverage changed, reindex both the old and new - // referenced material items. - - // When a media record is deleted, reindex the material item that was referenced by its - // coverage field. - - // TODO: Make this configurable. This is currently hardcoded to the needs of the material - // profile/Material Order application. - - if ( - eventName.equals(DocumentEventTypes.DOCUMENT_CREATED) || - eventName.equals(DocumentEventTypes.DOCUMENT_UPDATED) - ) { - String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY); - String coverage = (String) doc.getProperty("media_common", "coverage"); - - List prevPublishTo = (List) eventContext.getProperty(PREV_PUBLISH_TO_KEY); - List publishTo = (List) doc.getProperty("media_materials", "publishToList"); - - if (doc.getCurrentLifeCycleState().equals(LifeCycleConstants.DELETED_STATE)) { - reindex(doc.getRepositoryName(), coverage); - } - else if ( - !ListUtils.isEqualList(prevPublishTo, publishTo) || - !StringUtils.equals(prevCoverage, coverage) - ) { - if (!StringUtils.equals(prevCoverage, coverage)) { - reindex(doc.getRepositoryName(), prevCoverage); - } - - reindex(doc.getRepositoryName(), coverage); - } - } - else if (eventName.equals(DocumentEventTypes.DOCUMENT_REMOVED)) { - String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY); - - reindex(doc.getRepositoryName(), prevCoverage); - } + DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); + DocumentModel doc = eventContext.getSourceDocument(); + String docType = doc.getType(); + String eventName = event.getName(); + + // TODO: Make this configurable. This is currently hardcoded to the needs of the standard + // profiles. + + if (docType.startsWith("Media")) { + // When a media record is created, reindex the material item that is referenced by its + // coverage field. + + // When a media record is updated and the coverage changed, reindex both the old and new + // referenced material items. + + // When a media record is deleted, reindex the material item that was referenced by its + // coverage field. + + if ( + eventName.equals(DocumentEventTypes.DOCUMENT_CREATED) || + eventName.equals(DocumentEventTypes.DOCUMENT_UPDATED) + ) { + String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY); + String coverage = (String) doc.getProperty("media_common", "coverage"); + + List prevPublishTo = (List) eventContext.getProperty(PREV_PUBLISH_TO_KEY); + + // Materials profile had publishToList defined in a local extension schema before + // that field was added to the common schema. + + List publishTo = (List) doc.getProperty( + doc.hasSchema("media_materials") ? "media_materials" : "media_common", + "publishToList"); + + if ( + !ListUtils.isEqualList(prevPublishTo, publishTo) || + !StringUtils.equals(prevCoverage, coverage) + ) { + if (!StringUtils.equals(prevCoverage, coverage)) { + reindexMaterial(doc.getRepositoryName(), prevCoverage); + } + + reindexMaterial(doc.getRepositoryName(), coverage); + + if (!ListUtils.isEqualList(prevPublishTo, publishTo)) { + reindexRelatedCollectionObjects(doc); + } + } + } + else if (eventName.equals("lifecycle_transition_event") && doc.getCurrentLifeCycleState().equals("deleted")) { + String coverage = (String) doc.getProperty("media_common", "coverage"); + + reindexMaterial(doc.getRepositoryName(), coverage); + } + else if (eventName.equals(DocumentEventTypes.DOCUMENT_REMOVED)) { + String prevCoverage = (String) eventContext.getProperty(PREV_COVERAGE_KEY); + + reindexMaterial(doc.getRepositoryName(), prevCoverage); + reindexPrevRelatedCollectionObjects(eventContext); + } + } + else if (docType.startsWith("Acquisition")) { + if (eventName.equals(DocumentEventTypes.DOCUMENT_UPDATED)) { + String prevCreditLine = (String) eventContext.getProperty(PREV_CREDIT_LINE_KEY); + String creditLine = (String) doc.getProperty("acquisitions_common", "creditLine"); + + if (!StringUtils.equals(prevCreditLine, creditLine)) { + reindexRelatedCollectionObjects(doc); + } + } + else if (eventName.equals(DocumentEventTypes.DOCUMENT_REMOVED)) { + reindexPrevRelatedCollectionObjects(eventContext); + } + } + else if (docType.startsWith("Relation")) { + if ( + eventName.equals(DocumentEventTypes.DOCUMENT_CREATED) + || (eventName.equals("lifecycle_transition_event") && doc.getCurrentLifeCycleState().equals("deleted")) + ) { + String subjectDocumentType = (String) doc.getProperty("relations_common", "subjectDocumentType"); + String objectDocumentType = (String) doc.getProperty("relations_common", "objectDocumentType"); + + if ( + (subjectDocumentType.equals("Media") || subjectDocumentType.equals("Acquisition")) + && objectDocumentType.equals("CollectionObject") + ) { + String collectionObjectCsid = (String) doc.getProperty("relations_common", "objectCsid"); + + reindexCollectionObject(doc.getRepositoryName(), collectionObjectCsid); + } + } + else if (eventName.equals(DocumentEventTypes.DOCUMENT_REMOVED)) { + reindexPrevRelatedCollectionObjects(eventContext); + } + } } @Override @@ -113,15 +171,62 @@ public class Reindex extends AbstractCSEventPostCommitListenerImpl { return logger; } - private void reindex(String repositoryName, String refName) { - if (StringUtils.isEmpty(refName)) { - return; - } + private void reindexMaterial(String repositoryName, String refName) { + if (StringUtils.isEmpty(refName) || !refName.startsWith(RefNameUtils.URN_PREFIX)) { + return; + } + + String escapedRefName = refName.replace("'", "\\'"); + String query = String.format("SELECT ecm:uuid FROM Materialitem WHERE collectionspace_core:refName = '%s'", escapedRefName); + + ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class); + es.runReindexingWorker(repositoryName, query); + } + + private void reindexPrevRelatedCollectionObjects(DocumentEventContext eventContext) { + List prevRelatedCollectionObjectCsids = (List) eventContext.getProperty(PREV_RELATED_COLLECTION_OBJECT_CSID_KEY); + + if (prevRelatedCollectionObjectCsids != null) { + for (String prevRelatedCollectionObjectCsid : prevRelatedCollectionObjectCsids) { + reindexCollectionObject(eventContext.getRepositoryName(), prevRelatedCollectionObjectCsid); + } + } + } + + private void reindexRelatedCollectionObjects(DocumentModel doc) { + CoreSession session = doc.getCoreSession(); + String repositoryName = doc.getRepositoryName(); + String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId"); + String csid = doc.getName(); + + String relatedRecordQuery = String.format("SELECT * FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'CollectionObject' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", csid, tenantId); + DocumentModelList relationDocs = session.query(relatedRecordQuery); + List collectionObjectCsids = new ArrayList(); + + if (relationDocs.size() > 0) { + Iterator iterator = relationDocs.iterator(); + + while (iterator.hasNext()) { + DocumentModel relationDoc = iterator.next(); + String collectionObjectCsid = (String) relationDoc.getProperty("relations_common", "objectCsid"); + + collectionObjectCsids.add(collectionObjectCsid); + } + } + + for (String collectionObjectCsid : collectionObjectCsids) { + reindexCollectionObject(repositoryName, collectionObjectCsid); + } + } + + private void reindexCollectionObject(String repositoryName, String csid) { + if (StringUtils.isEmpty(csid)) { + return; + } - String escapedRefName = refName.replace("'", "\\'"); - String query = String.format("SELECT ecm:uuid FROM Materialitem WHERE collectionspace_core:refName = '%s'", escapedRefName); + String query = String.format("SELECT ecm:uuid FROM CollectionObject WHERE ecm:name = '%s'", csid); - ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class); - es.runReindexingWorker(repositoryName, query); - } + ElasticSearchComponent es = (ElasticSearchComponent) Framework.getService(ElasticSearchService.class); + es.runReindexingWorker(repositoryName, query); + } } diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/ReindexSupport.java b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/ReindexSupport.java index a985566ca..422e5456e 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/ReindexSupport.java +++ b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/ReindexSupport.java @@ -1,14 +1,18 @@ package org.collectionspace.services.listener; import java.io.Serializable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.collectionspace.services.nuxeo.listener.AbstractCSEventSyncListenerImpl; - +import org.nuxeo.ecm.core.api.CoreSession; import org.nuxeo.ecm.core.api.DocumentModel; +import org.nuxeo.ecm.core.api.DocumentModelList; import org.nuxeo.ecm.core.api.event.CoreEventConstants; import org.nuxeo.ecm.core.api.event.DocumentEventTypes; import org.nuxeo.ecm.core.event.Event; @@ -21,60 +25,131 @@ import org.nuxeo.runtime.api.Framework; * to a post-modification/deletion event listener. Storing the previous/deleted values allows * the post-modification/deletion event listener to take action if a field value was changed, * or if a document was deleted that had a certain field value. - * + * * This is a separate class from the Reindex listener, because the Reindex listener should be * async and post-commit, so it must implement PostCommitEventListener. This listener must be - * synchronous and pre-commit, so it must implement EventListener. Nuxeo does not support + * synchronous and pre-commit, so it must implement EventListener. Nuxeo does not support * a single class that implements both PostCommitEventListener and EventListener (such a listener * will only run synchronously). */ public class ReindexSupport extends AbstractCSEventSyncListenerImpl { - private static final Logger logger = LoggerFactory.getLogger(ReindexSupport.class); + private static final Logger logger = LoggerFactory.getLogger(ReindexSupport.class); - @Override + @Override public boolean shouldHandleEvent(Event event) { - if (Framework.isBooleanPropertyTrue(Reindex.ELASTICSEARCH_ENABLED_PROP) && event instanceof DocumentEventContext) { - DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); - DocumentModel doc = eventContext.getSourceDocument(); - String docType = doc.getType(); - if (docType.startsWith("Media")) { - return true; - } - } - - return false; - } - + DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); + + if (Framework.isBooleanPropertyTrue(Reindex.ELASTICSEARCH_ENABLED_PROP) && eventContext instanceof DocumentEventContext) { + DocumentModel doc = eventContext.getSourceDocument(); + String docType = doc.getType(); + + if ( + docType.startsWith("Media") + || docType.startsWith("Relation") + || docType.startsWith("Acquisition") + ) { + return true; + } + } + + return false; + } + + @Override + @SuppressWarnings("unchecked") + public void handleCSEvent(Event event) { + // TODO: Make this configurable. This is currently hardcoded to the needs of the standard + // profiles. + + // For core/all profiles: + // - When a media record is about to be updated, store the value of the publishToList + // field. + + // For materials profile: + // - When a media record is about to be updated, store the value of the coverage field. + // - When a media record is about to be removed, store the value of the coverage field. + + DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); + DocumentModel doc = eventContext.getSourceDocument(); + String docType = doc.getType(); + String eventName = event.getName(); + + if (docType.startsWith("Media")) { + if (eventName.equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + DocumentModel previousDoc = (DocumentModel) eventContext.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + String coverage = (String) previousDoc.getProperty("media_common", "coverage"); + + // Materials profile had publishToList defined in a local extension schema before + // that field was added to the common schema. + + List publishTo = (List) previousDoc.getProperty( + previousDoc.hasSchema("media_materials") ? "media_materials" : "media_common", + "publishToList"); + + eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage); + eventContext.setProperty(Reindex.PREV_PUBLISH_TO_KEY, (Serializable) publishTo); + } + else if (eventName.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) { + String coverage = (String) doc.getProperty("media_common", "coverage"); + + eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage); + + storePrevRelatedCollectionObjects(eventContext, doc); + } + } + else if (docType.startsWith("Acquisition")) { + if (eventName.equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { + DocumentModel previousDoc = (DocumentModel) eventContext.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); + String creditLine = (String) previousDoc.getProperty("acquisitions_common", "creditLine"); + + eventContext.setProperty(Reindex.PREV_CREDIT_LINE_KEY, creditLine); + } + else if (eventName.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) { + storePrevRelatedCollectionObjects(eventContext, doc); + } + } + else if (docType.startsWith("Relation")) { + if (eventName.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) { + String subjectDocumentType = (String) doc.getProperty("relations_common", "subjectDocumentType"); + String objectDocumentType = (String) doc.getProperty("relations_common", "objectDocumentType"); + + if ( + (subjectDocumentType.equals("Media") || subjectDocumentType.equals("Acquisition")) + && objectDocumentType.equals("CollectionObject") + ) { + String collectionObjectCsid = (String) doc.getProperty("relations_common", "objectCsid"); + + eventContext.setProperty(Reindex.PREV_RELATED_COLLECTION_OBJECT_CSID_KEY, (Serializable) Arrays.asList(collectionObjectCsid)); + } + } + } + } + + private void storePrevRelatedCollectionObjects(DocumentEventContext eventContext, DocumentModel doc) { + CoreSession session = doc.getCoreSession(); + String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId"); + String csid = doc.getName(); + + String relatedRecordQuery = String.format("SELECT * FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'CollectionObject' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", csid, tenantId); + DocumentModelList relationDocs = session.query(relatedRecordQuery); + List collectionObjectCsids = new ArrayList(); + + if (relationDocs.size() > 0) { + Iterator iterator = relationDocs.iterator(); + + while (iterator.hasNext()) { + DocumentModel relationDoc = iterator.next(); + String collectionObjectCsid = (String) relationDoc.getProperty("relations_common", "objectCsid"); + + collectionObjectCsids.add(collectionObjectCsid); + } + } + + eventContext.setProperty(Reindex.PREV_RELATED_COLLECTION_OBJECT_CSID_KEY, (Serializable) collectionObjectCsids); + } + @Override - @SuppressWarnings("unchecked") - public void handleCSEvent(Event event) { - // When a media record is about to be updated, store the value of the coverage and - // publishToList fields. - - // When a media record is about to be removed, store the value of the coverage field. - - // TODO: Make this configurable. This is currently hardcoded to the needs of the material - // profile/Material Order application. - DocumentEventContext eventContext = (DocumentEventContext) event.getContext(); - DocumentModel doc = eventContext.getSourceDocument(); - String eventName = event.getName(); - - if (eventName.equals(DocumentEventTypes.BEFORE_DOC_UPDATE)) { - DocumentModel previousDoc = (DocumentModel) eventContext.getProperty(CoreEventConstants.PREVIOUS_DOCUMENT_MODEL); - String coverage = (String) previousDoc.getProperty("media_common", "coverage"); - List publishTo = (List) previousDoc.getProperty("media_materials", "publishToList"); - - eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage); - eventContext.setProperty(Reindex.PREV_PUBLISH_TO_KEY, (Serializable) publishTo); - } - else if (eventName.equals(DocumentEventTypes.ABOUT_TO_REMOVE)) { - String coverage = (String) doc.getProperty("media_common", "coverage"); - eventContext.setProperty(Reindex.PREV_COVERAGE_KEY, coverage); - } - } - - @Override - public Logger getLogger() { - return logger; - } + public Logger getLogger() { + return logger; + } } diff --git a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/resources/OSGI-INF/event-contrib.xml b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/resources/OSGI-INF/event-contrib.xml index 3be10bf5a..c32041869 100644 --- a/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/resources/OSGI-INF/event-contrib.xml +++ b/3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/resources/OSGI-INF/event-contrib.xml @@ -6,6 +6,7 @@ documentCreated documentModified documentRemoved + lifecycle_transition_event diff --git a/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-elasticsearch-extension.xml b/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-elasticsearch-extension.xml index 84e2b8afe..b732c6c05 100644 --- a/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-elasticsearch-extension.xml +++ b/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-elasticsearch-extension.xml @@ -8,25 +8,25 @@ { - "analysis" : { - "char_filter" : { - "refname_displayname_char_filter" : { - "type" : "pattern_replace", - "pattern" : "^.*?'(.*)'$", - "replacement" : "$1" - }, - "refname_shortid_char_filter" : { - "type" : "pattern_replace", - "pattern" : "^.*:item:name\\((.*?)\\).*$", - "replacement" : "$1" - }, - "doctype_tenant_unqualified_char_filter" : { - "type" : "pattern_replace", - "pattern" : "^(.*?)(Tenant.*)?$", - "replacement" : "$1" + "analysis": { + "char_filter": { + "refname_displayname_char_filter": { + "type": "pattern_replace", + "pattern": "^.*?'(.*)'$", + "replacement": "$1" + }, + "refname_shortid_char_filter": { + "type": "pattern_replace", + "pattern": "^.*:item:name\\((.*?)\\).*$", + "replacement": "$1" + }, + "doctype_tenant_unqualified_char_filter": { + "type": "pattern_replace", + "pattern": "^(.*?)(Tenant.*)?$", + "replacement": "$1" } }, - "filter" : { + "filter": { "truncate_filter": { "length": 256, "type": "truncate" @@ -50,7 +50,7 @@ "preserve_original": true } }, - "tokenizer" : { + "tokenizer": { "path_tokenizer": { "delimiter": "/", "type": "path_hierarchy" @@ -61,7 +61,7 @@ "max_gram": 12 } }, - "analyzer" : { + "analyzer": { "fulltext": { "filter": [ "word_delimiter_filter", @@ -92,40 +92,15 @@ ], "tokenizer": "ngram_tokenizer" }, - "refname_displayname_analyzer" : { - "type" : "custom", - "tokenizer" : "keyword", - "char_filter" : ["refname_displayname_char_filter"] - }, - "refname_displayname_fulltext_analyzer" : { - "char_filter" : ["refname_displayname_char_filter"], - "filter" : [ - "word_delimiter_filter", - "lowercase", - "en_stop_filter", - "en_stem_filter", - "asciifolding_filter" - ], - "type" : "custom", - "tokenizer" : "standard" - }, - "refname_shortid_analyzer" : { - "type" : "custom", - "tokenizer" : "keyword", - "char_filter" : ["refname_shortid_char_filter"] - }, - "doctype_analyzer" : { - "type" : "custom", - "tokenizer" : "keyword", - "char_filter" : ["doctype_tenant_unqualified_char_filter"] + "refname_shortid_analyzer": { + "type": "custom", + "tokenizer": "keyword", + "char_filter": ["refname_shortid_char_filter"] }, - "sorting_analyzer" : { - "filter" : [ - "lowercase", - "asciifolding" - ], - "type" : "custom", - "tokenizer" : "keyword" + "doctype_analyzer": { + "type": "custom", + "tokenizer": "keyword", + "char_filter": ["doctype_tenant_unqualified_char_filter"] }, "default": { "type": "custom", @@ -134,6 +109,23 @@ "truncate_filter" ] } + }, + "normalizer": { + "refname_displayname_normalizer": { + "type": "custom", + "char_filter": ["refname_displayname_char_filter"] + }, + "refname_shortid_normalizer": { + "type": "custom", + "char_filter": ["refname_shortid_char_filter"] + }, + "sorting_normalizer": { + "type": "custom", + "filter": [ + "lowercase", + "asciifolding" + ] + } } } } @@ -145,10 +137,10 @@ // index as small as possible. We may want to turn this on in the future, to support arbitrary // searches through Elasticsearch, e.g. NXQL queries for ad hoc reporting in the CSpace UI. "dynamic": false, - "_all" : { + "_all": { "enabled": false }, - "properties" : { + "properties": { "all_field": { "type": "text", "analyzer": "fulltext" @@ -158,7 +150,7 @@ }, "ecm:primaryType": { "type": "text", - "analyzer" : "doctype_analyzer" + "analyzer": "doctype_analyzer" } } } diff --git a/services/common/src/main/cspace/config/services/tenants/anthro/anthro-tenant-bindings.delta.xml b/services/common/src/main/cspace/config/services/tenants/anthro/anthro-tenant-bindings.delta.xml index 7354e0b81..4a816804a 100644 --- a/services/common/src/main/cspace/config/services/tenants/anthro/anthro-tenant-bindings.delta.xml +++ b/services/common/src/main/cspace/config/services/tenants/anthro/anthro-tenant-bindings.delta.xml @@ -1,13 +1,252 @@ - - - - + xmlns:merge='http://xmlmerge.el4j.elca.ch' + xmlns:tenant='http://collectionspace.org/services/config/tenant'> - - + + + + + + org.collectionspace.services.nuxeo.elasticsearch.anthro.AnthroESDocumentWriter + + + + + { + // For now, don't index a field unless there's a mapping explicitly defined. This keeps the + // index as small as possible. We may want to turn this on in the future, to support arbitrary + // searches through Elasticsearch, e.g. NXQL queries for ad hoc reporting in the CSpace UI. + "dynamic": false, + "_all" : { + "enabled": false + }, + "_source": { + "includes": [ + "collectionobjects_common:briefDescriptions", + "collectionobjects_common:collection", + "collectionobjects_common:colors", + "collectionobjects_common:computedCurrentLocation", + "collectionobjects_common:contentConcepts", + "collectionobjects_common:fieldCollectionDateGroup", + "collectionobjects_common:fieldCollectors", + "collectionobjects_common:materialGroupList", + "collectionobjects_common:measuredPartGroupList", + "collectionobjects_common:numberOfObjects", + "collectionobjects_common:objectNameList", + "collectionobjects_common:objectNumber", + "collectionobjects_common:objectProductionDateGroupList", + "collectionobjects_common:objectProductionOrganizationGroupList", + "collectionobjects_common:objectProductionPersonGroupList", + "collectionobjects_common:objectProductionPeopleGroupList", + "collectionobjects_common:objectProductionPlaceGroupList", + "collectionobjects_common:objectStatusList", + "collectionobjects_common:otherNumberList", + "collectionobjects_common:publishToList", + "collectionobjects_common:responsibleDepartments", + "collectionobjects_common:techniqueGroupList", + "collectionobjects_common:titleGroupList", + "collectionobjects_naturalhistory_extension:taxonomicIdentGroupList", + "collectionspace_core:*", + "collectionspace_denorm:*", + "ecm:currentLifeCycleState", + "ecm:name", + "ecm:primaryType", + "media_common:blobCsid" + ] + }, + "properties" : { + "all_field": { + "type": "text", + "analyzer": "fulltext" + }, + + "ecm:currentLifeCycleState": { + "type": "keyword" + }, + "ecm:name": { + "type": "keyword" + }, + "ecm:primaryType": { + "type": "text", + "analyzer" : "doctype_analyzer" + }, + + "collectionspace_core:createdAt": { + "type": "date", + "format": "date_time" + }, + "collectionobjects_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + }, + + "collectionspace_denorm:title": { + "type": "keyword", + "normalizer": "sorting_normalizer" + }, + "collectionspace_denorm:hasMedia": { + "type": "boolean" + }, + "collectionspace_denorm:prodYears": { + "type": "integer" + }, + "collectionspace_denorm:collectionYears": { + "type": "integer" + }, + + "collectionobjects_common:objectNumber": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:titleGroupList": { + "type": "object", + "properties": { + "title": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectNameList": { + "type": "object", + "properties": { + "objectName": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPersonGroupList": { + "type": "object", + "properties": { + "objectProductionPerson": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionOrganizationGroupList": { + "type": "object", + "properties": { + "objectProductionOrganization": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionPeopleGroupList": { + "type": "object", + "properties": { + "objectProductionPeople": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionDateGroupList": { + "type": "object", + "properties": { + "dateDisplayDate": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPlaceGroupList": { + "type": "object", + "properties": { + "objectProductionPlace": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:materialGroupList": { + "type": "object", + "properties": { + "material": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:colors": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:responsibleDepartments": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:contentConcepts": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + }, + "collectionobjects_common:techniqueGroupList": { + "type": "object", + "properties": { + "technique": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_naturalhistory_extension:taxonomicIdentGroupList": { + "type": "object", + "properties": { + "taxon": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + + "media_common:blobCsid": { + "type": "keyword" + }, + "media_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + } + } + } + + + diff --git a/services/common/src/main/cspace/config/services/tenants/fcart/fcart-tenant-bindings.delta.xml b/services/common/src/main/cspace/config/services/tenants/fcart/fcart-tenant-bindings.delta.xml index 023f4c1df..90bdc32d7 100644 --- a/services/common/src/main/cspace/config/services/tenants/fcart/fcart-tenant-bindings.delta.xml +++ b/services/common/src/main/cspace/config/services/tenants/fcart/fcart-tenant-bindings.delta.xml @@ -1,13 +1,228 @@ + xmlns:merge='http://xmlmerge.el4j.elca.ch' + xmlns:tenant='http://collectionspace.org/services/config/tenant'> - - - + + + - - + + + + { + // For now, don't index a field unless there's a mapping explicitly defined. This keeps the + // index as small as possible. We may want to turn this on in the future, to support arbitrary + // searches through Elasticsearch, e.g. NXQL queries for ad hoc reporting in the CSpace UI. + "dynamic": false, + "_all" : { + "enabled": false + }, + "_source": { + "includes": [ + "collectionobjects_common:briefDescriptions", + "collectionobjects_common:collection", + "collectionobjects_common:colors", + "collectionobjects_common:computedCurrentLocation", + "collectionobjects_common:contentConcepts", + "collectionobjects_common:materialGroupList", + "collectionobjects_common:measuredPartGroupList", + "collectionobjects_common:numberOfObjects", + "collectionobjects_common:objectNameList", + "collectionobjects_common:objectNumber", + "collectionobjects_common:objectProductionDateGroupList", + "collectionobjects_common:objectProductionOrganizationGroupList", + "collectionobjects_common:objectProductionPersonGroupList", + "collectionobjects_common:objectProductionPeopleGroupList", + "collectionobjects_common:objectProductionPlaceGroupList", + "collectionobjects_common:objectStatusList", + "collectionobjects_common:otherNumberList", + "collectionobjects_common:publishToList", + "collectionobjects_common:responsibleDepartments", + "collectionobjects_common:techniqueGroupList", + "collectionobjects_common:titleGroupList", + "collectionobjects_fineart:materialTechniqueDescription", + "collectionspace_core:*", + "collectionspace_denorm:*", + "ecm:currentLifeCycleState", + "ecm:name", + "ecm:primaryType", + "media_common:blobCsid" + ] + }, + "properties" : { + "all_field": { + "type": "text", + "analyzer": "fulltext" + }, + "ecm:currentLifeCycleState": { + "type": "keyword" + }, + "ecm:name": { + "type": "keyword" + }, + "ecm:primaryType": { + "type": "text", + "analyzer" : "doctype_analyzer" + }, + + "collectionspace_core:createdAt": { + "type": "date", + "format": "date_time" + }, + "collectionobjects_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + }, + + "collectionspace_denorm:title": { + "type": "keyword", + "normalizer": "sorting_normalizer" + }, + "collectionspace_denorm:hasMedia": { + "type": "boolean" + }, + "collectionspace_denorm:prodYears": { + "type": "integer" + }, + + "collectionobjects_common:objectNumber": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:titleGroupList": { + "type": "object", + "properties": { + "title": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectNameList": { + "type": "object", + "properties": { + "objectName": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPersonGroupList": { + "type": "object", + "properties": { + "objectProductionPerson": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionOrganizationGroupList": { + "type": "object", + "properties": { + "objectProductionOrganization": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionPeopleGroupList": { + "type": "object", + "properties": { + "objectProductionPeople": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionDateGroupList": { + "type": "object", + "properties": { + "dateDisplayDate": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPlaceGroupList": { + "type": "object", + "properties": { + "objectProductionPlace": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:materialGroupList": { + "type": "object", + "properties": { + "material": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:colors": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:responsibleDepartments": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:contentConcepts": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + }, + "collectionobjects_common:techniqueGroupList": { + "type": "object", + "properties": { + "technique": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + + "media_common:blobCsid": { + "type": "keyword" + }, + "media_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + } + } + } + + + diff --git a/services/common/src/main/cspace/config/services/tenants/materials/materials-tenant-bindings.delta.xml b/services/common/src/main/cspace/config/services/tenants/materials/materials-tenant-bindings.delta.xml index 3551155cb..b5b29b1d6 100644 --- a/services/common/src/main/cspace/config/services/tenants/materials/materials-tenant-bindings.delta.xml +++ b/services/common/src/main/cspace/config/services/tenants/materials/materials-tenant-bindings.delta.xml @@ -8,18 +8,12 @@ - - - org.collectionspace.services.listener.ReindexSupport - - - - - - + + org.collectionspace.services.nuxeo.elasticsearch.materials.MaterialsESDocumentWriter + - + { // For now, don't index a field unless there's a mapping explicitly defined. This keeps the // index as small as possible. We may want to turn this on in the future, to support arbitrary @@ -115,9 +109,8 @@ }, "collectionspace_denorm:title": { - "type": "text", - "fielddata": true, - "analyzer": "sorting_analyzer" + "type": "keyword", + "normalizer": "sorting_normalizer" }, "collectionspace_denorm:commercialNames": { "type": "text", @@ -132,9 +125,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -163,8 +155,8 @@ "type": "keyword", "fields": { "shortid": { - "type": "text", - "analyzer": "refname_shortid_analyzer" + "type": "keyword", + "normalizer": "refname_shortid_normalizer" } } }, @@ -176,9 +168,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -187,9 +178,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -198,9 +188,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -216,9 +205,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -230,8 +218,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "analyzer": "refname_displayname_fulltext_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -245,8 +233,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "analyzer": "refname_displayname_fulltext_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -260,8 +248,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "analyzer": "refname_displayname_fulltext_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -275,9 +263,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -288,9 +275,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -302,9 +288,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -319,9 +304,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -335,9 +319,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -351,9 +334,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -367,9 +349,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -383,9 +364,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -399,9 +379,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -415,9 +394,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -431,9 +409,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -447,9 +424,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -464,9 +440,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -480,9 +455,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -497,9 +471,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -511,9 +484,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -522,9 +494,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -533,9 +504,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -544,9 +514,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -555,9 +524,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -566,9 +534,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -577,9 +544,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } }, @@ -591,9 +557,8 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" } } } @@ -608,8 +573,8 @@ "type": "keyword", "fields": { "shortid": { - "type": "text", - "analyzer": "refname_shortid_analyzer" + "type": "keyword", + "normalizer": "refname_shortid_normalizer" } } }, @@ -621,13 +586,12 @@ "copy_to": "all_field", "fields": { "displayName": { - "type": "text", - "fielddata": true, - "analyzer": "refname_displayname_analyzer" + "type": "keyword", + "normalizer": "refname_displayname_normalizer" }, "shortid": { - "type": "text", - "analyzer": "refname_shortid_analyzer" + "type": "keyword", + "normalizer": "refname_shortid_normalizer" } } } @@ -638,8 +602,8 @@ "type": "keyword", "fields": { "shortid": { - "type": "text", - "analyzer": "refname_shortid_analyzer" + "type": "keyword", + "normalizer": "refname_shortid_normalizer" } } } diff --git a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml index 9a62f51a7..412d1823e 100644 --- a/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml +++ b/services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml @@ -2,28 +2,34 @@ - - - org.collectionspace.services.listener.UpdateObjectLocationOnMove - - - key0 - value0 - - - key1 - value1 - - - key2 - value2 - - - - - org.collectionspace.services.listener.UpdateRelationsOnDelete - - + + + org.collectionspace.services.listener.UpdateObjectLocationOnMove + + + key0 + value0 + + + key1 + value1 + + + key2 + value2 + + + + + org.collectionspace.services.listener.UpdateRelationsOnDelete + + + org.collectionspace.services.listener.ReindexSupport + + + org.collectionspace.services.listener.Reindex + + - + @@ -947,6 +953,224 @@ - + + org.collectionspace.services.nuxeo.elasticsearch.DefaultESDocumentWriter + + + + + { + // For now, don't index a field unless there's a mapping explicitly defined. This keeps the + // index as small as possible. We may want to turn this on in the future, to support arbitrary + // searches through Elasticsearch, e.g. NXQL queries for ad hoc reporting in the CSpace UI. + "dynamic": false, + "_all" : { + "enabled": false + }, + "_source": { + "includes": [ + "collectionobjects_common:briefDescriptions", + "collectionobjects_common:collection", + "collectionobjects_common:colors", + "collectionobjects_common:computedCurrentLocation", + "collectionobjects_common:contentConcepts", + "collectionobjects_common:materialGroupList", + "collectionobjects_common:measuredPartGroupList", + "collectionobjects_common:numberOfObjects", + "collectionobjects_common:objectNameList", + "collectionobjects_common:objectNumber", + "collectionobjects_common:objectProductionDateGroupList", + "collectionobjects_common:objectProductionOrganizationGroupList", + "collectionobjects_common:objectProductionPersonGroupList", + "collectionobjects_common:objectProductionPeopleGroupList", + "collectionobjects_common:objectProductionPlaceGroupList", + "collectionobjects_common:objectStatusList", + "collectionobjects_common:otherNumberList", + "collectionobjects_common:publishToList", + "collectionobjects_common:responsibleDepartments", + "collectionobjects_common:techniqueGroupList", + "collectionobjects_common:titleGroupList", + "collectionspace_core:*", + "collectionspace_denorm:*", + "ecm:currentLifeCycleState", + "ecm:name", + "ecm:primaryType", + "media_common:blobCsid" + ] + }, + "properties" : { + "all_field": { + "type": "text", + "analyzer": "fulltext" + }, + + "ecm:currentLifeCycleState": { + "type": "keyword" + }, + "ecm:name": { + "type": "keyword" + }, + "ecm:primaryType": { + "type": "text", + "analyzer" : "doctype_analyzer" + }, + "collectionspace_core:createdAt": { + "type": "date", + "format": "date_time" + }, + "collectionobjects_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + }, + + "collectionspace_denorm:title": { + "type": "keyword", + "normalizer": "sorting_normalizer" + }, + "collectionspace_denorm:hasMedia": { + "type": "boolean" + }, + "collectionspace_denorm:prodYears": { + "type": "integer" + }, + + "collectionobjects_common:objectNumber": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:titleGroupList": { + "type": "object", + "properties": { + "title": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectNameList": { + "type": "object", + "properties": { + "objectName": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPersonGroupList": { + "type": "object", + "properties": { + "objectProductionPerson": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionOrganizationGroupList": { + "type": "object", + "properties": { + "objectProductionOrganization": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + } + } + }, + "collectionobjects_common:objectProductionPeopleGroupList": { + "type": "object", + "properties": { + "objectProductionPeople": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionDateGroupList": { + "type": "object", + "properties": { + "dateDisplayDate": { + "type": "text", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:objectProductionPlaceGroupList": { + "type": "object", + "properties": { + "objectProductionPlace": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:materialGroupList": { + "type": "object", + "properties": { + "material": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + "collectionobjects_common:colors": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:responsibleDepartments": { + "type": "keyword", + "copy_to": "all_field" + }, + "collectionobjects_common:contentConcepts": { + "type": "keyword", + "copy_to": "all_field", + "fields": { + "displayName": { + "type": "keyword", + "normalizer": "refname_displayname_normalizer" + } + } + }, + "collectionobjects_common:techniqueGroupList": { + "type": "object", + "properties": { + "technique": { + "type": "keyword", + "copy_to": "all_field" + } + } + }, + + "media_common:blobCsid": { + "type": "keyword" + }, + "media_common:publishToList": { + "type": "keyword", + "fields": { + "shortid": { + "type": "keyword", + "normalizer": "refname_shortid_normalizer" + } + } + } + } + } + + + diff --git a/services/config/src/main/resources/tenant.xsd b/services/config/src/main/resources/tenant.xsd index 592470dc6..844ca79b3 100644 --- a/services/config/src/main/resources/tenant.xsd +++ b/services/config/src/main/resources/tenant.xsd @@ -15,75 +15,75 @@ --> - - + xmlns="http://collectionspace.org/services/config/tenant" + xmlns:types="http://collectionspace.org/services/config/types" + xmlns:service="http://collectionspace.org/services/config/service" + xmlns:remoteclientconfig="http://collectionspace.org/services/config/remoteclientconfig" + targetNamespace="http://collectionspace.org/services/config/tenant" + version="0.1" + elementFormDefault="qualified"> + + - - - - - - - - - - - - - - - - Tenant bindings - - - - - - - - - - + + + + + + + + + + + + + + + + Tenant bindings + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - Repository domain configuartion - - - - - - - + a sub-task can decide whether to run or not. + --> + + + + + + + + + + + + + + + + + + Repository domain configuartion + + + + + + + @@ -91,7 +91,7 @@ - + Connection details for a remote CollectionSpace instance. Used for things like the Share Authority Server @@ -103,11 +103,11 @@ - + - + Configuration for how a tenant sends email notifications @@ -116,7 +116,7 @@ - + @@ -141,7 +141,7 @@ - + SMTP authentication config for sending emails. @@ -152,51 +152,49 @@ - + Config for password resets - + - + - + Connection details for a remote CollectionSpace instance. Used for things like the Share Authority Server - - - + - + - + - - - - + + + + -- 2.47.3