]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
DRYD-819: Add public browser support for core and standard profiles.
authorRay Lee <ray.lee@lyrasis.org>
Tue, 14 Apr 2020 03:48:44 +0000 (23:48 -0400)
committerRay Lee <ray.lee@lyrasis.org>
Tue, 14 Apr 2020 03:48:44 +0000 (23:48 -0400)
15 files changed:
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/CSJsonESDocumentWriter.java [deleted file]
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/DefaultESDocumentWriter.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/TenantConfiguredESDocumentWriter.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/anthro/AnthroESDocumentWriter.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/java/org/collectionspace/services/nuxeo/elasticsearch/materials/MaterialsESDocumentWriter.java [new file with mode: 0644]
3rdparty/nuxeo/nuxeo-platform-elasticsearch/src/main/resources/OSGI-INF/elasticsearch-contrib.xml
3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/Reindex.java
3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/java/org/collectionspace/services/listener/ReindexSupport.java
3rdparty/nuxeo/nuxeo-platform-listener/reindex/src/main/resources/OSGI-INF/event-contrib.xml
3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-elasticsearch-extension.xml
services/common/src/main/cspace/config/services/tenants/anthro/anthro-tenant-bindings.delta.xml
services/common/src/main/cspace/config/services/tenants/fcart/fcart-tenant-bindings.delta.xml
services/common/src/main/cspace/config/services/tenants/materials/materials-tenant-bindings.delta.xml
services/common/src/main/cspace/config/services/tenants/tenant-bindings-proto-unified.xml
services/config/src/main/resources/tenant.xsd

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 (file)
index 8310650..0000000
+++ /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<String, String> 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<JsonNode> mediaCsids = new ArrayList<JsonNode>();
-
-                if (mediaDocs.size() > 0) {
-                    Iterator<DocumentModel> 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<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
-            List<String> commercialNames = findTermDisplayNamesWithFlag(termGroups, "commercial");
-            List<String> 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<String> holdingInstitutions = new LinkedHashSet<String>();
-
-            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<Map.Entry<String, JsonNode>> entries = denormValues.getFields();
-
-            while (entries.hasNext()) {
-                Map.Entry<String, JsonNode> 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<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
-        String primaryDisplayName = null;
-
-        if (termGroups.size() > 0) {
-            Map<String, Object> primaryTermGroup = termGroups.get(0);
-            primaryDisplayName = (String) primaryTermGroup.get("termDisplayName");
-        }
-
-        return primaryDisplayName;
-    }
-
-    private String findFirstTermDisplayNameWithFlag(List<Map<String, Object>> termGroups, String flagShortId) {
-        String termDisplayName = null;
-
-        for (Map<String, Object> 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<String> findTermDisplayNamesWithFlag(List<Map<String, Object>> termGroups, String flagShortId) {
-        List<String> termDisplayNames = new ArrayList<String>();
-
-        for (Map<String, Object> 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<String> getTermAttributionContributors(DocumentModel doc) {
-        Set orgs = new LinkedHashSet<String>();
-
-        List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionContributingGroupList");
-
-        for (Map<String, Object> group : groups) {
-            String org = (String) group.get("materialTermAttributionContributingOrganization");
-
-            if (StringUtils.isNotEmpty(org)) {
-                orgs.add(org);
-            }
-        }
-
-        return orgs;
-    }
-
-    private Set<String> getTermAttributionEditors(DocumentModel doc) {
-        Set orgs = new LinkedHashSet<String>();
-
-        List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionEditingGroupList");
-
-        for (Map<String, Object> group : groups) {
-            String org = (String) group.get("materialTermAttributionEditingOrganization");
-
-            if (StringUtils.isNotEmpty(org)) {
-                orgs.add(org);
-            }
-        }
-
-        return orgs;
-    }
-
-    private boolean isMediaPublished(DocumentModel mediaDoc) {
-        List<String> publishToValues = (List<String>) mediaDoc.getProperty("media_materials", "publishToList");
-        boolean isPublished = false;
-
-        for (int i=0; i<publishToValues.size(); i++) {
-            String value = publishToValues.get(i);
-            String shortId = RefNameUtils.getItemShortId(value);
-
-            if (shortId.equals("all") || shortId.equals("materialorder")) {
-                isPublished = true;
-                break;
-            }
-        }
-
-        return isPublished;
-    }
-
-    private List<JsonNode> jsonNodes(Collection<String> values) {
-        List<JsonNode> nodes = new ArrayList<JsonNode>();
-        Iterator<String> 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 (file)
index 0000000..78d10ac
--- /dev/null
@@ -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<String, String> 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<Map<String, Object>> prodDateGroupList = (List<Map<String, Object>>) 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<Map.Entry<String, JsonNode>> entries = denormValues.getFields();
+
+                       while (entries.hasNext()) {
+                               Map.Entry<String, JsonNode> 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<JsonNode> mediaCsids = new ArrayList<JsonNode>();
+
+               if (relationDocs.size() > 0) {
+                       Iterator<DocumentModel> 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<JsonNode> creditLines = new ArrayList<JsonNode>();
+
+               if (relationDocs.size() > 0) {
+                       Iterator<DocumentModel> 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<Map<String, Object>> titleGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "titleGroupList");
+               String primaryTitle = null;
+
+               if (titleGroups.size() > 0) {
+                       Map<String, Object> primaryTitleGroup = titleGroups.get(0);
+                       primaryTitle = (String) primaryTitleGroup.get("title");
+               }
+
+               if (StringUtils.isNotEmpty(primaryTitle)) {
+                       return primaryTitle;
+               }
+
+               List<Map<String, Object>> objectNameGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectNameList");
+               String primaryObjectName = null;
+
+               if (objectNameGroups.size() > 0) {
+                       Map<String, Object> 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<String> publishToValues = (List<String>) mediaDoc.getProperty("media_common", "publishToList");
+
+                       if (publishToValues != null) {
+                               for (int i=0; i<publishToValues.size(); i++) {
+                                       String value = publishToValues.get(i);
+                                       String shortId = RefNameUtils.getItemShortId(value);
+
+                                       if (shortId.equals("all") || shortId.equals("cspacepub")) {
+                                               isPublished = true;
+                                               break;
+                                       }
+                               }
+                       }
+               }
+
+               return isPublished;
+       }
+
+       private String getCreditLine(CoreSession session, String tenantId, String acquisitionCsid) {
+               String creditLine = null;
+               DocumentModel acquisitionDoc = getRecordByCsid(session, tenantId, "Acquisition", acquisitionCsid);
+
+               if (acquisitionDoc != null) {
+                       creditLine = (String) acquisitionDoc.getProperty("acquisitions_common", "creditLine");
+               }
+
+               return creditLine;
+       }
+
+       protected DocumentModel getRecordByCsid(CoreSession session, String tenantId, String recordType, String csid) {
+               String getRecordQuery = String.format("SELECT * FROM %s WHERE ecm:name = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", recordType, csid, tenantId);
+
+               DocumentModelList docs = session.query(getRecordQuery);
+
+               if (docs != null && docs.size() > 0) {
+                       return docs.get(0);
+               }
+
+               return null;
+       }
+
+       protected List<JsonNode> structDateToYearNodes(Map<String, Object> structDate) {
+               return structDatesToYearNodes(Arrays.asList(structDate));
+       }
+
+       protected List<JsonNode> structDatesToYearNodes(List<Map<String, Object>> structDates) {
+               Set<Integer> years = new HashSet<Integer>();
+
+               for (Map<String, Object> 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<Integer> yearList = new ArrayList<Integer>(years);
+               Collections.sort(yearList);
+
+               List<JsonNode> yearNodes = new ArrayList<JsonNode>();
+
+               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 (file)
index 0000000..eceecc5
--- /dev/null
@@ -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<String, String> 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 (file)
index 0000000..e3b041d
--- /dev/null
@@ -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<String, Object> fieldCollectionDateGroup = (Map<String, Object>) 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 (file)
index 0000000..5fbe3b6
--- /dev/null
@@ -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<JsonNode> mediaCsids = new ArrayList<JsonNode>();
+
+                               if (mediaDocs.size() > 0) {
+                                       Iterator<DocumentModel> 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<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
+                       List<String> commercialNames = findTermDisplayNamesWithFlag(termGroups, "commercial");
+                       List<String> 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<String> holdingInstitutions = new LinkedHashSet<String>();
+
+                       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<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
+               String primaryDisplayName = null;
+
+               if (termGroups.size() > 0) {
+                       Map<String, Object> primaryTermGroup = termGroups.get(0);
+                       primaryDisplayName = (String) primaryTermGroup.get("termDisplayName");
+               }
+
+               return primaryDisplayName;
+       }
+
+       private List<String> findTermDisplayNamesWithFlag(List<Map<String, Object>> termGroups, String flagShortId) {
+               List<String> termDisplayNames = new ArrayList<String>();
+
+               for (Map<String, Object> 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<String> getTermAttributionContributors(DocumentModel doc) {
+               Set orgs = new LinkedHashSet<String>();
+
+               List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionContributingGroupList");
+
+               for (Map<String, Object> group : groups) {
+                       String org = (String) group.get("materialTermAttributionContributingOrganization");
+
+                       if (StringUtils.isNotEmpty(org)) {
+                               orgs.add(org);
+                       }
+               }
+
+               return orgs;
+       }
+
+       private Set<String> getTermAttributionEditors(DocumentModel doc) {
+               Set orgs = new LinkedHashSet<String>();
+
+               List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionEditingGroupList");
+
+               for (Map<String, Object> group : groups) {
+                       String org = (String) group.get("materialTermAttributionEditingOrganization");
+
+                       if (StringUtils.isNotEmpty(org)) {
+                               orgs.add(org);
+                       }
+               }
+
+               return orgs;
+       }
+
+       private boolean isMediaPublished(DocumentModel mediaDoc) {
+               List<String> publishToValues = (List<String>) mediaDoc.getProperty("media_materials", "publishToList");
+               boolean isPublished = false;
+
+               if (publishToValues != null) {
+                       for (int i=0; i<publishToValues.size(); i++) {
+                               String value = publishToValues.get(i);
+                               String shortId = RefNameUtils.getItemShortId(value);
+
+                               if (shortId.equals("all") || shortId.equals("materialorder")) {
+                                       isPublished = true;
+                                       break;
+                               }
+                       }
+               }
+
+               return isPublished;
+       }
+
+       private List<JsonNode> jsonNodes(Collection<String> values) {
+               List<JsonNode> nodes = new ArrayList<JsonNode>();
+               Iterator<String> iterator = values.iterator();
+
+               while (iterator.hasNext()) {
+                       String value = iterator.next();
+
+                       nodes.add(new TextNode(value));
+               }
+
+               return nodes;
+       }
+}
index 962ddec1c5d754d94c514304c422566c957959b8..bf6bc594409993f8ee81626d1724a291136202b5 100644 (file)
@@ -2,6 +2,6 @@
 <component name="org.collectionspace.nuxeo.elasticsearch">
   <extension target="org.nuxeo.elasticsearch.ElasticSearchComponent"
     point="elasticSearchDocWriter">
-    <writer class="org.collectionspace.services.nuxeo.elasticsearch.CSJsonESDocumentWriter" />
+    <writer class="org.collectionspace.services.nuxeo.elasticsearch.TenantConfiguredESDocumentWriter" />
   </extension>
 </component>
index f645744e99770f68e0d33d4a49e1c2c8be526228..c32ca0a49637b3982ae8c402725faeec56c6651b 100644 (file)
@@ -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<String> prevPublishTo = (List<String>) eventContext.getProperty(PREV_PUBLISH_TO_KEY);
-            List<String> publishTo = (List<String>) 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<String> prevPublishTo = (List<String>) 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<String> publishTo = (List<String>) 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<String> prevRelatedCollectionObjectCsids = (List<String>) 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<String> collectionObjectCsids = new ArrayList<String>();
+
+               if (relationDocs.size() > 0) {
+                       Iterator<DocumentModel> 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);
+       }
 }
index a985566ca7efc9c3b1ba188990eccfa1f68cfbf0..422e5456e2905728368675ca0603d98b032d6c98 100644 (file)
@@ -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<String> publishTo = (List<String>) 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<String> collectionObjectCsids = new ArrayList<String>();
+
+               if (relationDocs.size() > 0) {
+                       Iterator<DocumentModel> 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<String> publishTo = (List<String>) 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;
+       }
 }
index 3be10bf5ac908f714e83a60b367e4e5998ffbab1..c320418696eda8884f4bce34bc978ab70d567958 100644 (file)
@@ -6,6 +6,7 @@
             <event>documentCreated</event>
             <event>documentModified</event>
             <event>documentRemoved</event>
+            <event>lifecycle_transition_event</event>
         </listener>
     </extension>
     <extension target="org.nuxeo.ecm.core.event.EventServiceComponent" point="listener">
index 84e2b8afea131b14224494d73bc34b6128e77e9c..b732c6c059afe4a744efcf8e12f951fec7fdd1b1 100644 (file)
@@ -8,25 +8,25 @@
 
     <settings>
       {
-        "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",
               ],
               "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",
                 "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"
+              ]
+            }
           }
         }
       }
         // 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"
           },
           "ecm:primaryType": {
             "type": "text",
-            "analyzer" : "doctype_analyzer"
+            "analyzer": "doctype_analyzer"
           }
         }
       }
index 7354e0b81b78eaba381b70f4ba89d498d69aceb8..4a816804a4d76fbceffd877c84ab4b7618f06645 100644 (file)
 <?xml version="1.0" encoding="UTF-8"?>
 <tenant:TenantBindingConfig
-        xmlns:merge='http://xmlmerge.el4j.elca.ch'
-        xmlns:tenant='http://collectionspace.org/services/config/tenant'>
-    
-    <!-- Add your changes, if any, within the following tag pair. -->
-    <!-- The value of the 'id' attribute, below, should match the corresponding -->
-    <!-- value in cspace/config/services/tenants/anthro-tenant-bindings-proto.xml -->
+               xmlns:merge='http://xmlmerge.el4j.elca.ch'
+               xmlns:tenant='http://collectionspace.org/services/config/tenant'>
 
-    <tenant:tenantBinding id="1500">
-    </tenant:tenantBinding>
+       <!-- Add your changes, if any, within the following tag pair. -->
+       <!-- The value of the 'id' attribute, below, should match the corresponding -->
+       <!-- value in cspace/config/services/tenants/anthro-tenant-bindings-proto.xml -->
 
+       <tenant:tenantBinding id="1500">
+               <tenant:elasticSearchDocumentWriter merge:action="replace">
+                       org.collectionspace.services.nuxeo.elasticsearch.anthro.AnthroESDocumentWriter
+               </tenant:elasticSearchDocumentWriter>
+
+               <tenant:elasticSearchIndexConfig>
+                       <tenant:mapping merge:action="replace">
+                               {
+                                       // 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"
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       </tenant:mapping>
+               </tenant:elasticSearchIndexConfig>
+       </tenant:tenantBinding>
 </tenant:TenantBindingConfig>
index 023f4c1df1d74aef91180a8b414a3afefdc7fc1a..90bdc32d74287e78afc61f5964863669e7b08445 100644 (file)
 <?xml version="1.0" encoding="UTF-8"?>
 <tenant:TenantBindingConfig
-        xmlns:merge='http://xmlmerge.el4j.elca.ch'
-        xmlns:tenant='http://collectionspace.org/services/config/tenant'>
+               xmlns:merge='http://xmlmerge.el4j.elca.ch'
+               xmlns:tenant='http://collectionspace.org/services/config/tenant'>
 
-    <!-- Add your changes, if any, within the following tag pair. -->
-    <!-- The value of the 'id' attribute, below, should match the corresponding -->
-    <!-- value in cspace/config/services/tenants/fcart-tenant-bindings-proto.xml -->
+       <!-- Add your changes, if any, within the following tag pair. -->
+       <!-- The value of the 'id' attribute, below, should match the corresponding -->
+       <!-- value in cspace/config/services/tenants/fcart-tenant-bindings-proto.xml -->
 
-    <tenant:tenantBinding id="1000">
-    </tenant:tenantBinding>
+       <tenant:tenantBinding id="1000">
+               <tenant:elasticSearchIndexConfig>
+                       <tenant:mapping merge:action="replace">
+                               {
+                                       // 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"
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       </tenant:mapping>
+               </tenant:elasticSearchIndexConfig>
+       </tenant:tenantBinding>
 </tenant:TenantBindingConfig>
index 3551155cb24ec7442a34f446e43f6e4feaf8de3f..b5b29b1d6b7469f4859de93ffd9edbb34f67ef1e 100644 (file)
@@ -8,18 +8,12 @@
     <!-- value in cspace/config/services/tenants/materials-tenant-bindings-proto.xml -->
 
     <tenant:tenantBinding id="2000">
-        <tenant:eventListenerConfigurations id="default" merge:matcher="id">
-            <tenant:eventListenerConfig id="ReindexSupport" merge:matcher="id">
-                <tenant:className>org.collectionspace.services.listener.ReindexSupport</tenant:className>
-            </tenant:eventListenerConfig>
-        </tenant:eventListenerConfigurations>
-
-        <tenant:serviceBindings id="Media" elasticsearchIndexed="true" merge:matcher="id" />
-        <tenant:serviceBindings id="CollectionObjects" elasticsearchIndexed="true" merge:matcher="id" />
-        <tenant:serviceBindings id="Materials" elasticsearchIndexed="true" merge:matcher="id" />
+        <tenant:elasticSearchDocumentWriter merge:action="replace">
+          org.collectionspace.services.nuxeo.elasticsearch.materials.MaterialsESDocumentWriter
+        </tenant:elasticSearchDocumentWriter>
 
         <tenant:elasticSearchIndexConfig>
-            <tenant:mapping>
+            <tenant:mapping merge:action="replace">
               {
                 // 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
                   },
 
                   "collectionspace_denorm:title": {
-                    "type": "text",
-                    "fielddata": true,
-                    "analyzer": "sorting_analyzer"
+                    "type": "keyword",
+                    "normalizer": "sorting_normalizer"
                   },
                   "collectionspace_denorm:commercialNames": {
                     "type": "text",
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "type": "keyword",
                     "fields": {
                       "shortid": {
-                        "type": "text",
-                        "analyzer": "refname_shortid_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_shortid_normalizer"
                       }
                     }
                   },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "analyzer": "refname_displayname_fulltext_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "analyzer": "refname_displayname_fulltext_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "analyzer": "refname_displayname_fulltext_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                     "copy_to": "all_field",
                     "fields": {
                       "displayName": {
-                        "type": "text",
-                        "fielddata": true,
-                        "analyzer": "refname_displayname_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_displayname_normalizer"
                       }
                     }
                   },
                         "copy_to": "all_field",
                         "fields": {
                           "displayName": {
-                            "type": "text",
-                            "fielddata": true,
-                            "analyzer": "refname_displayname_analyzer"
+                            "type": "keyword",
+                            "normalizer": "refname_displayname_normalizer"
                           }
                         }
                       }
                     "type": "keyword",
                     "fields": {
                       "shortid": {
-                        "type": "text",
-                        "analyzer": "refname_shortid_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_shortid_normalizer"
                       }
                     }
                   },
                         "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"
                           }
                         }
                       }
                     "type": "keyword",
                     "fields": {
                       "shortid": {
-                        "type": "text",
-                        "analyzer": "refname_shortid_analyzer"
+                        "type": "keyword",
+                        "normalizer": "refname_shortid_normalizer"
                       }
                     }
                   }
index 9a62f51a77f47ed22fbc573919931708bb1b174d..412d1823e566986efa8fc85315a988ee551409a3 100644 (file)
@@ -2,28 +2,34 @@
 <tenant:TenantBindingConfig xmlns:merge="http://xmlmerge.el4j.elca.ch" xmlns:tenant="http://collectionspace.org/services/config/tenant">
 
        <tenant:tenantBinding>
-           <tenant:eventListenerConfigurations id="default">
-               <tenant:eventListenerConfig id="UpdateObjectLocationOnMove">
-                   <tenant:className>org.collectionspace.services.listener.UpdateObjectLocationOnMove</tenant:className>
-                   <tenant:paramList id="default">
-                       <tenant:param>
-                           <tenant:key>key0</tenant:key>
-                           <tenant:value>value0</tenant:value>
-                           </tenant:param>
-                       <tenant:param>
-                           <tenant:key>key1</tenant:key>
-                           <tenant:value>value1</tenant:value>
-                           </tenant:param>
-                       <tenant:param>
-                           <tenant:key>key2</tenant:key>
-                           <tenant:value>value2</tenant:value>
-                           </tenant:param>
-                   </tenant:paramList>
-               </tenant:eventListenerConfig>
-               <tenant:eventListenerConfig id="UpdateRelationsOnDelete">
-                   <tenant:className>org.collectionspace.services.listener.UpdateRelationsOnDelete</tenant:className>
-               </tenant:eventListenerConfig>
-           </tenant:eventListenerConfigurations>
+               <tenant:eventListenerConfigurations id="default">
+                       <tenant:eventListenerConfig id="UpdateObjectLocationOnMove">
+                               <tenant:className>org.collectionspace.services.listener.UpdateObjectLocationOnMove</tenant:className>
+                               <tenant:paramList id="default">
+                                       <tenant:param>
+                                               <tenant:key>key0</tenant:key>
+                                               <tenant:value>value0</tenant:value>
+                                       </tenant:param>
+                                       <tenant:param>
+                                               <tenant:key>key1</tenant:key>
+                                               <tenant:value>value1</tenant:value>
+                                       </tenant:param>
+                                       <tenant:param>
+                                               <tenant:key>key2</tenant:key>
+                                               <tenant:value>value2</tenant:value>
+                                       </tenant:param>
+                               </tenant:paramList>
+                       </tenant:eventListenerConfig>
+                       <tenant:eventListenerConfig id="UpdateRelationsOnDelete">
+                               <tenant:className>org.collectionspace.services.listener.UpdateRelationsOnDelete</tenant:className>
+                       </tenant:eventListenerConfig>
+                       <tenant:eventListenerConfig id="ReindexSupport">
+                               <tenant:className>org.collectionspace.services.listener.ReindexSupport</tenant:className>
+                       </tenant:eventListenerConfig>
+                       <tenant:eventListenerConfig id="Reindex">
+                               <tenant:className>org.collectionspace.services.listener.Reindex</tenant:className>
+                       </tenant:eventListenerConfig>
+               </tenant:eventListenerConfigurations>
                <tenant:properties>
                        <!-- Controls whether term completion (aka partial term matching, aka autocomplete) searches will automatically insert
                                a leading wildcard. The default value is 'true', which will cause your search expression to be matched when found anywhere
                        </service:object>
                </tenant:serviceBindings>
                <!-- end reports-invoke service meta-data -->
-               
+
                <!-- begin report service meta-data -->
                <tenant:serviceBindings id="Reports" merge:matcher="id" name="Reports" type="utility" version="1.0">
                        <!-- other URI paths through which this service could be accessed -->
                        </service:object>
                </tenant:serviceBindings>
 
-       </tenant:tenantBinding>
+               <tenant:elasticSearchDocumentWriter>
+                       org.collectionspace.services.nuxeo.elasticsearch.DefaultESDocumentWriter
+               </tenant:elasticSearchDocumentWriter>
+
+               <tenant:elasticSearchIndexConfig>
+                       <tenant:mapping>
+                               {
+                                       // 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"
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       </tenant:mapping>
+               </tenant:elasticSearchIndexConfig>
+       </tenant:tenantBinding>
 </tenant:TenantBindingConfig>
index 592470dc6debb4145974575de08ef46268f93674..844ca79b34a5154558bd29b50bac1a77e81ecb23 100644 (file)
 
 -->
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-           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">
-
-    <xs:import namespace="http://collectionspace.org/services/config/types" schemaLocation="types.xsd" />
+                       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">
+
+       <xs:import namespace="http://collectionspace.org/services/config/types" schemaLocation="types.xsd" />
        <xs:import namespace="http://collectionspace.org/services/config/service" schemaLocation="service.xsd" />
-       
-    <xs:element name="TenantBindingConfig">
-        <xs:annotation>
-            <xs:documentation/>
-        </xs:annotation>
-        <xs:complexType>
-            <xs:sequence>
-                <xs:element name="tenantBinding" type="TenantBindingType" minOccurs="0" maxOccurs="1"/>
-            </xs:sequence>
-        </xs:complexType>
-    </xs:element>
-
-    <!--
-        TenantBindingType describes bindings for each tenant in CollectionSpace
-    -->
-    <xs:complexType name="TenantBindingType">
-        <xs:annotation>
-            <xs:documentation>Tenant bindings</xs:documentation>
-        </xs:annotation>
-        <xs:sequence>
-            <xs:element name="repositoryDomain" type="RepositoryDomainType" minOccurs="1" maxOccurs="unbounded"/>
-            <xs:element name="eventListenerConfigurations" type="EventListenerConfigurations" minOccurs="0" maxOccurs="1"/>
-            <xs:element name="binaryStorePath" type="xs:string" minOccurs="0" maxOccurs="1"/>
-            <xs:element name="properties" type="types:PropertyType" minOccurs="0" maxOccurs="unbounded"/>
-               <xs:element name="remoteClientConfigurations" type="RemoteClientConfigurations" minOccurs="0" maxOccurs="1"/>
-               <xs:element name="emailConfig" type="EmailConfig" minOccurs="0" maxOccurs="1"/>
-            <xs:element name="elasticSearchIndexConfig" type="ElasticSearchIndexConfig" minOccurs="0" maxOccurs="1"/>
-            <xs:element name="serviceBindings" type="service:ServiceBindingType" minOccurs="0" maxOccurs="unbounded"/>
+
+       <xs:element name="TenantBindingConfig">
+               <xs:annotation>
+                       <xs:documentation/>
+               </xs:annotation>
+               <xs:complexType>
+                       <xs:sequence>
+                               <xs:element name="tenantBinding" type="TenantBindingType" minOccurs="0" maxOccurs="1"/>
+                       </xs:sequence>
+               </xs:complexType>
+       </xs:element>
+
+       <!--
+               TenantBindingType describes bindings for each tenant in CollectionSpace
+       -->
+       <xs:complexType name="TenantBindingType">
+               <xs:annotation>
+                       <xs:documentation>Tenant bindings</xs:documentation>
+               </xs:annotation>
+               <xs:sequence>
+                       <xs:element name="repositoryDomain" type="RepositoryDomainType" minOccurs="1" maxOccurs="unbounded"/>
+                       <xs:element name="eventListenerConfigurations" type="EventListenerConfigurations" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="binaryStorePath" type="xs:string" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="properties" type="types:PropertyType" minOccurs="0" maxOccurs="unbounded"/>
+                       <xs:element name="remoteClientConfigurations" type="RemoteClientConfigurations" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="emailConfig" type="EmailConfig" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="elasticSearchDocumentWriter" type="xs:string" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="elasticSearchIndexConfig" type="ElasticSearchIndexConfig" minOccurs="0" maxOccurs="1"/>
+                       <xs:element name="serviceBindings" type="service:ServiceBindingType" minOccurs="0" maxOccurs="unbounded"/>
                        <!--
                                Processing all of the tenant bindings at startup involves several sub-tasks and is expensive (slow), so we'll compute the MD5 hash of the config and persist it in the corresponding tenant record.
                                Some of the startup sub-tasks can compare the MD5 hash of the current tenant bindings with that of the last startup to see if anything has changed.  If the hashes match, there's been no changes so
-                               a sub-task can decide whether to run or not.                              
-                       -->             
-               <xs:element name="configMD5Hash" type="xs:string" minOccurs="0" maxOccurs="1"/>                                 <!-- Transient, only used during startup.  Not part of config file. -->
-               <xs:element name="configChangedSinceLastStart" type="xs:boolean" minOccurs="0" maxOccurs="1"/>  <!-- Transient, only used during startup.  Not part of config file. -->
-        </xs:sequence>
-        <!-- tenant id, a UUID -->
-        <xs:attribute name="id" type="xs:ID" use="required"/>
-        <!-- domain name including subdomain but not TLD -->
-        <!-- e.g. hearstmuseum.berkeley or movingimage.us -->
-        <xs:attribute name="name" type="xs:string" use="required"/>
-        <!-- display name as Museum of Moving Images -->
-        <xs:attribute name="displayName" type="xs:string" use="required"/>
-        <xs:attribute name="version" type="types:VersionType" use="required"/>
-        <xs:attribute name="createDisabled" type="xs:boolean" use="optional" default="false"/>
-        <xs:attribute name="forceUpdate" type="xs:boolean" use="optional" default="false"/>
-
-    </xs:complexType>
-
-    <xs:complexType name="RepositoryDomainType">
-        <xs:annotation>
-            <xs:documentation>Repository domain configuartion</xs:documentation>
-        </xs:annotation>
-        <xs:sequence/>
-        <xs:attribute name="name" type="xs:string" use="required"/>
-        <xs:attribute name="storageName" type="xs:string" use="required"/>
-        <xs:attribute name="repositoryName" type="xs:string" use="optional" default="default"/>        
-        <xs:attribute name="repositoryClient" type="xs:string" use="optional" default="nuxeo-java"/>
-    </xs:complexType>
+                               a sub-task can decide whether to run or not.
+                       -->
+                       <xs:element name="configMD5Hash" type="xs:string" minOccurs="0" maxOccurs="1"/>                                 <!-- Transient, only used during startup.  Not part of config file. -->
+                       <xs:element name="configChangedSinceLastStart" type="xs:boolean" minOccurs="0" maxOccurs="1"/>  <!-- Transient, only used during startup.  Not part of config file. -->
+               </xs:sequence>
+               <!-- tenant id, a UUID -->
+               <xs:attribute name="id" type="xs:ID" use="required"/>
+               <!-- domain name including subdomain but not TLD -->
+               <!-- e.g. hearstmuseum.berkeley or movingimage.us -->
+               <xs:attribute name="name" type="xs:string" use="required"/>
+               <!-- display name as Museum of Moving Images -->
+               <xs:attribute name="displayName" type="xs:string" use="required"/>
+               <xs:attribute name="version" type="types:VersionType" use="required"/>
+               <xs:attribute name="createDisabled" type="xs:boolean" use="optional" default="false"/>
+               <xs:attribute name="forceUpdate" type="xs:boolean" use="optional" default="false"/>
+       </xs:complexType>
+
+       <xs:complexType name="RepositoryDomainType">
+               <xs:annotation>
+                       <xs:documentation>Repository domain configuartion</xs:documentation>
+               </xs:annotation>
+               <xs:sequence/>
+               <xs:attribute name="name" type="xs:string" use="required"/>
+               <xs:attribute name="storageName" type="xs:string" use="required"/>
+               <xs:attribute name="repositoryName" type="xs:string" use="optional" default="default"/>
+               <xs:attribute name="repositoryClient" type="xs:string" use="optional" default="nuxeo-java"/>
+       </xs:complexType>
 
        <xs:complexType name="RemoteClientConfigurations">
                <xs:sequence>
@@ -91,7 +91,7 @@
                </xs:sequence>
                <xs:attribute name="id" type="xs:string" use="required"/>
        </xs:complexType>
-       
+
        <xs:complexType name="RemoteClientConfig">
                <xs:annotation>
                        <xs:documentation>Connection details for a remote CollectionSpace instance.  Used for things like the Share Authority Server</xs:documentation>
                        <xs:element name="password" type="xs:string" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="tenantId" type="xs:string" minOccurs="0" maxOccurs="1"/>
                        <xs:element name="tenantName" type="xs:string" minOccurs="0" maxOccurs="1"/>
-                       <xs:element name="ssl" type="xs:string" minOccurs="0" maxOccurs="1"/>        
+                       <xs:element name="ssl" type="xs:string" minOccurs="0" maxOccurs="1"/>
                        <xs:element name="auth" type="xs:string" minOccurs="0" maxOccurs="1"/>
                </xs:sequence>
        </xs:complexType>
-       
+
        <xs:complexType name="EmailConfig">
                <xs:annotation>
                        <xs:documentation>Configuration for how a tenant sends email notifications</xs:documentation>
                        <xs:element name="baseurl" type="xs:string" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="from" type="xs:string" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="smtpConfig" type="SMTPConfig" minOccurs="1" maxOccurs="1"/>
-                       <xs:element name="passwordResetConfig" type="PasswordResetConfig" minOccurs="1" maxOccurs="1"/>                 
+                       <xs:element name="passwordResetConfig" type="PasswordResetConfig" minOccurs="1" maxOccurs="1"/>
                </xs:sequence>
        </xs:complexType>
 
                        <xs:element name="smtpAuth" type="SMTPAuthConfig" minOccurs="1" maxOccurs="1"/>
                </xs:sequence>
        </xs:complexType>
-       
+
        <xs:complexType name="SMTPAuthConfig">
                <xs:annotation>
                        <xs:documentation>SMTP authentication config for sending emails.</xs:documentation>
                </xs:sequence>
                <xs:attribute name="enabled" type="xs:boolean" use="optional" default="false"/>
        </xs:complexType>
-       
+
        <xs:complexType name="PasswordResetConfig">
                <xs:annotation>
                        <xs:documentation>Config for password resets</xs:documentation>
                </xs:annotation>
                <xs:sequence>
-                       <xs:element name="tokenExpirationSeconds" type="xs:integer" minOccurs="1" maxOccurs="1"/>               
+                       <xs:element name="tokenExpirationSeconds" type="xs:integer" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="loginpage" type="xs:string" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="subject" type="xs:string" minOccurs="1" maxOccurs="1"/>
                        <xs:element name="message" type="xs:string" minOccurs="1" maxOccurs="1"/>
                </xs:sequence>
        </xs:complexType>
-       
+
        <xs:complexType name="EventListenerConfigurations">
                <xs:sequence>
                        <xs:element name="eventListenerConfig" type="EventListenerConfig" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
        </xs:complexType>
-       
+
        <xs:complexType name="EventListenerConfig">
                <xs:annotation>
                        <xs:documentation>Connection details for a remote CollectionSpace instance.  Used for things like the Share Authority Server</xs:documentation>
                </xs:annotation>
                <xs:sequence>
                        <xs:element name="className" type="xs:string" minOccurs="1" maxOccurs="1"/>
-                       <xs:element name="paramList" type="ParamList" minOccurs="0" maxOccurs="1">
-                   
-            </xs:element>
+                       <xs:element name="paramList" type="ParamList" minOccurs="0" maxOccurs="1"/>
                </xs:sequence>
                <xs:attribute name="id" type="xs:string" use="required"/>
                <xs:attribute name="required" type="xs:boolean" use="optional" default="false"/>
        </xs:complexType>
-       
+
        <xs:complexType name="ParamList">
                <xs:sequence>
                        <xs:element name="param" type="Param" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
                <xs:attribute name="id" type="xs:string" use="required"/>
        </xs:complexType>
-       
+
        <xs:complexType name="Param">
-           <xs:sequence>
-               <xs:element name="key" type="xs:string" minOccurs="1" maxOccurs="1"/>
-               <xs:element name="value" type="xs:string" minOccurs="1" maxOccurs="1"/>
-               </xs:sequence>
+               <xs:sequence>
+                       <xs:element name="key" type="xs:string" minOccurs="1" maxOccurs="1"/>
+                       <xs:element name="value" type="xs:string" minOccurs="1" maxOccurs="1"/>
+               </xs:sequence>
        </xs:complexType>
 
 </xs:schema>