+++ /dev/null
-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;
- }
-}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+}
<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>
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;
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
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
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);
+ }
}
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;
* 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;
+ }
}
<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">
<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"
"preserve_original": true
}
},
- "tokenizer" : {
+ "tokenizer": {
"path_tokenizer": {
"delimiter": "/",
"type": "path_hierarchy"
"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"
}
}
}
<?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>
<?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>
<!-- 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"
}
}
}
<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>
-->
<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>
</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>