1 package org.collectionspace.services.nuxeo.elasticsearch;
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Arrays;
6 import java.util.Calendar;
7 import java.util.Collections;
8 import java.util.GregorianCalendar;
9 import java.util.HashSet;
10 import java.util.Iterator;
11 import java.util.List;
15 import javax.ws.rs.core.HttpHeaders;
17 import org.apache.commons.lang3.StringUtils;
18 import org.codehaus.jackson.JsonGenerator;
19 import org.codehaus.jackson.JsonNode;
20 import org.codehaus.jackson.map.ObjectMapper;
21 import org.codehaus.jackson.node.IntNode;
22 import org.codehaus.jackson.node.ObjectNode;
23 import org.codehaus.jackson.node.TextNode;
24 import org.collectionspace.services.common.api.RefNameUtils;
25 import org.nuxeo.ecm.automation.jaxrs.io.documents.JsonESDocumentWriter;
26 import org.nuxeo.ecm.core.api.CoreSession;
27 import org.nuxeo.ecm.core.api.DocumentModel;
28 import org.nuxeo.ecm.core.api.DocumentModelList;
30 public class DefaultESDocumentWriter extends JsonESDocumentWriter {
31 private static ObjectMapper objectMapper = new ObjectMapper();
34 public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas,
35 Map<String, String> contextParameters, HttpHeaders headers)
38 ObjectNode denormValues = getDenormValues(doc);
40 jg.writeStartObject();
42 writeSystemProperties(jg, doc);
43 writeSchemas(jg, doc, schemas);
44 writeContextParameters(jg, doc, contextParameters);
45 writeDenormValues(jg, doc, denormValues);
51 public ObjectNode getDenormValues(DocumentModel doc) {
52 ObjectNode denormValues = objectMapper.createObjectNode();
53 String docType = doc.getType();
55 if (docType.startsWith("CollectionObject")) {
56 CoreSession session = doc.getCoreSession();
57 String csid = doc.getName();
58 String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId");
60 denormMediaRecords(session, csid, tenantId, denormValues);
61 denormAcquisitionRecords(session, csid, tenantId, denormValues);
62 denormExhibitionRecords(session, csid, tenantId, denormValues);
63 denormMaterialFields(doc, denormValues);
64 denormObjectNameFields(doc, denormValues);
66 // Compute the title of the record for the public browser, and store it so that it can
67 // be used for sorting ES query results.
69 String title = computeTitle(doc);
72 denormValues.put("title", title);
75 // Create a list of production years from the production date structured dates.
77 List<Map<String, Object>> prodDateGroupList = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectProductionDateGroupList");
79 denormValues.putArray("prodYears").addAll(structDatesToYearNodes(prodDateGroupList));
85 public void writeDenormValues(JsonGenerator jg, DocumentModel doc, ObjectNode denormValues) throws IOException {
86 if (denormValues != null && denormValues.size() > 0) {
87 if (jg.getCodec() == null) {
88 jg.setCodec(objectMapper);
91 Iterator<Map.Entry<String, JsonNode>> entries = denormValues.getFields();
93 while (entries.hasNext()) {
94 Map.Entry<String, JsonNode> entry = entries.next();
96 jg.writeFieldName("collectionspace_denorm:" + entry.getKey());
97 jg.writeTree(entry.getValue());
102 private void denormMediaRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
103 // Store the csid and alt text of media records that are related to this object.
105 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);
106 DocumentModelList relationDocs = session.query(relatedRecordQuery);
107 List<JsonNode> mediaCsids = new ArrayList<JsonNode>();
108 List<JsonNode> mediaAltTexts = new ArrayList<JsonNode>();
110 if (relationDocs.size() > 0) {
111 Iterator<DocumentModel> iterator = relationDocs.iterator();
113 while (iterator.hasNext()) {
114 DocumentModel relationDoc = iterator.next();
115 String mediaCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
116 DocumentModel mediaDoc = getRecordByCsid(session, tenantId, "Media", mediaCsid);
118 if (isMediaPublished(mediaDoc)) {
119 mediaCsids.add(new TextNode(mediaCsid));
121 String altText = (String) mediaDoc.getProperty("media_common", "altText");
123 if (altText == null) {
127 mediaAltTexts.add(new TextNode(altText));
132 denormValues.putArray("mediaCsid").addAll(mediaCsids);
133 denormValues.putArray("mediaAltText").addAll(mediaAltTexts);
134 denormValues.put("hasMedia", mediaCsids.size() > 0);
137 private void denormAcquisitionRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
138 // Store the credit lines of acquisition records that are related to this object.
140 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);
141 DocumentModelList relationDocs = session.query(relatedRecordQuery);
142 List<JsonNode> creditLines = new ArrayList<JsonNode>();
144 if (relationDocs.size() > 0) {
145 Iterator<DocumentModel> iterator = relationDocs.iterator();
147 while (iterator.hasNext()) {
148 DocumentModel relationDoc = iterator.next();
149 String acquisitionCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
150 String creditLine = getCreditLine(session, tenantId, acquisitionCsid);
152 if (creditLine != null && creditLine.length() > 0) {
153 creditLines.add(new TextNode(creditLine));
158 denormValues.putArray("creditLine").addAll(creditLines);
161 private void denormExhibitionRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
162 // Store the title, general note, and curatorial note of exhibition records that are published, and related to this object.
164 String relatedRecordQuery = String.format("SELECT * FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'Exhibition' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", csid, tenantId);
165 DocumentModelList relationDocs = session.query(relatedRecordQuery);
166 List<JsonNode> exhibitions = new ArrayList<JsonNode>();
168 if (relationDocs.size() > 0) {
169 Iterator<DocumentModel> iterator = relationDocs.iterator();
171 while (iterator.hasNext()) {
172 DocumentModel relationDoc = iterator.next();
173 String exhibitionCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
174 DocumentModel exhibitionDoc = getRecordByCsid(session, tenantId, "Exhibition", exhibitionCsid);
176 if (exhibitionDoc != null && isExhibitionPublished(exhibitionDoc)) {
177 ObjectNode exhibitionNode = objectMapper.createObjectNode();
179 String title = (String) exhibitionDoc.getProperty("exhibitions_common", "title");
180 String generalNote = (String) exhibitionDoc.getProperty("exhibitions_common", "generalNote");
181 String curatorialNote = (String) exhibitionDoc.getProperty("exhibitions_common", "curatorialNote");
183 exhibitionNode.put("title", title);
184 exhibitionNode.put("generalNote", generalNote);
185 exhibitionNode.put("curatorialNote", curatorialNote);
187 exhibitions.add(exhibitionNode);
192 denormValues.putArray("exhibition").addAll(exhibitions);
196 * Denormalize the material group list for a collectionobject in order to index the controlled or uncontrolled term
198 * @param doc the collectionobject document
199 * @param denormValues the json node for denormalized fields
201 private void denormMaterialFields(DocumentModel doc, ObjectNode denormValues) {
202 List<Map<String, Object>> materialGroupList =
203 (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "materialGroupList");
205 List<JsonNode> denormMaterials = new ArrayList<>();
206 for (Map<String, Object> materialGroup : materialGroupList) {
207 String controlledMaterial = (String) materialGroup.get("materialControlled");
208 if (controlledMaterial != null) {
209 final ObjectNode node = objectMapper.createObjectNode();
210 node.put("material", RefNameUtils.getDisplayName(controlledMaterial));
211 denormMaterials.add(node);
214 String material = (String) materialGroup.get("material");
215 if (material != null) {
216 final ObjectNode node = objectMapper.createObjectNode();
217 node.put("material", material);
218 denormMaterials.add(node);
222 denormValues.putArray("materialGroupList").addAll(denormMaterials);
226 * Denormalize the object name group list for a collectionobject in order to index the controlled and
229 * @param doc the collectionobject document
230 * @param denormValues the json node for denormalized fields
232 private void denormObjectNameFields(DocumentModel doc, ObjectNode denormValues) {
233 List<Map<String, Object>> objectNameList =
234 (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectNameList");
236 List<JsonNode> denormObjectNames = new ArrayList<>();
237 for (Map<String, Object> objectNameGroup : objectNameList) {
238 String controlledName = (String) objectNameGroup.get("objectNameControlled");
239 if (controlledName != null) {
240 final ObjectNode node = objectMapper.createObjectNode();
241 node.put("objectName", RefNameUtils.getDisplayName(controlledName));
242 denormObjectNames.add(node);
245 String objectName = (String) objectNameGroup.get("objectName");
246 if (objectName != null) {
247 final ObjectNode node = objectMapper.createObjectNode();
248 node.put("objectName", objectName);
249 denormObjectNames.add(node);
253 denormValues.putArray("objectNameList").addAll(denormObjectNames);
257 * Compute a title for the public browser. This needs to be indexed in ES so that it can
258 * be used for sorting. (Even if it's just extracting the primary value.)
260 protected String computeTitle(DocumentModel doc) {
261 List<Map<String, Object>> titleGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "titleGroupList");
262 String primaryTitle = null;
264 if (titleGroups.size() > 0) {
265 Map<String, Object> primaryTitleGroup = titleGroups.get(0);
266 primaryTitle = (String) primaryTitleGroup.get("title");
269 if (StringUtils.isNotEmpty(primaryTitle)) {
273 List<Map<String, Object>> objectNameGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectNameList");
274 String primaryObjectName = null;
276 if (objectNameGroups.size() > 0) {
277 Map<String, Object> primaryObjectNameGroup = objectNameGroups.get(0);
278 primaryObjectName = (String) primaryObjectNameGroup.get("objectNameControlled");
279 if (primaryObjectName == null) {
280 primaryObjectName = (String) primaryObjectNameGroup.get("objectName");
283 // The object might be a refname in some profiles/tenants. If it is, use only the display name.
286 String displayName = RefNameUtils.getDisplayName(primaryObjectName);
288 if (displayName != null) {
289 primaryObjectName = displayName;
292 catch (Exception e) {}
295 return primaryObjectName;
298 private boolean isPublished(DocumentModel doc, String publishedFieldPart, String publishedFieldName) {
299 boolean isPublished = false;
302 List<String> publishToValues = (List<String>) doc.getProperty(publishedFieldPart, publishedFieldName);
304 if (publishToValues != null) {
305 for (int i=0; i<publishToValues.size(); i++) {
306 String value = publishToValues.get(i);
307 String shortId = RefNameUtils.getItemShortId(value);
309 if (shortId.equals("all") || shortId.equals("cspacepub")) {
321 private boolean isMediaPublished(DocumentModel mediaDoc) {
322 return isPublished(mediaDoc, "media_common", "publishToList");
325 private boolean isExhibitionPublished(DocumentModel exhibitionDoc) {
326 return isPublished(exhibitionDoc, "exhibitions_common", "publishToList");
329 private String getCreditLine(CoreSession session, String tenantId, String acquisitionCsid) {
330 String creditLine = null;
331 DocumentModel acquisitionDoc = getRecordByCsid(session, tenantId, "Acquisition", acquisitionCsid);
333 if (acquisitionDoc != null) {
334 creditLine = (String) acquisitionDoc.getProperty("acquisitions_common", "creditLine");
340 protected DocumentModel getRecordByCsid(CoreSession session, String tenantId, String recordType, String csid) {
341 String getRecordQuery = String.format("SELECT * FROM %s WHERE ecm:name = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", recordType, csid, tenantId);
343 DocumentModelList docs = session.query(getRecordQuery);
345 if (docs != null && docs.size() > 0) {
352 protected List<JsonNode> structDateToYearNodes(Map<String, Object> structDate) {
353 return structDatesToYearNodes(Arrays.asList(structDate));
356 protected List<JsonNode> structDatesToYearNodes(List<Map<String, Object>> structDates) {
357 Set<Integer> years = new HashSet<Integer>();
359 for (Map<String, Object> structDate : structDates) {
360 if (structDate != null) {
361 GregorianCalendar earliestCalendar = (GregorianCalendar) structDate.get("dateEarliestScalarValue");
362 GregorianCalendar latestCalendar = (GregorianCalendar) structDate.get("dateLatestScalarValue");
364 if (earliestCalendar != null && latestCalendar != null) {
365 // Grr @ latest scalar value historically being exclusive.
366 // Subtract one day to make it inclusive.
367 latestCalendar.add(Calendar.DATE, -1);
369 Integer earliestYear = earliestCalendar.get(Calendar.YEAR);
370 Integer latestYear = latestCalendar.get(Calendar.YEAR);;
372 for (int year = earliestYear; year <= latestYear; year++) {
379 List<Integer> yearList = new ArrayList<Integer>(years);
380 Collections.sort(yearList);
382 List<JsonNode> yearNodes = new ArrayList<JsonNode>();
384 for (Integer year : yearList) {
385 yearNodes.add(new IntNode(year));