]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
039b2ec96188e7b5a909c4b7dce65e6a969aba3d
[tmp/jakarta-migration.git] /
1 package org.collectionspace.services.nuxeo.elasticsearch;
2
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;
12 import java.util.Map;
13 import java.util.Set;
14
15 import javax.ws.rs.core.HttpHeaders;
16
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;
29
30 public class DefaultESDocumentWriter extends JsonESDocumentWriter {
31         private static ObjectMapper objectMapper = new ObjectMapper();
32
33         @Override
34         public void writeDoc(JsonGenerator jg, DocumentModel doc, String[] schemas,
35                         Map<String, String> contextParameters, HttpHeaders headers)
36                         throws IOException {
37
38                 ObjectNode denormValues = getDenormValues(doc);
39
40                 jg.writeStartObject();
41
42                 writeSystemProperties(jg, doc);
43                 writeSchemas(jg, doc, schemas);
44                 writeContextParameters(jg, doc, contextParameters);
45                 writeDenormValues(jg, doc, denormValues);
46
47                 jg.writeEndObject();
48                 jg.flush();
49         }
50
51         public ObjectNode getDenormValues(DocumentModel doc) {
52                 ObjectNode denormValues = objectMapper.createObjectNode();
53                 String docType = doc.getType();
54
55                 if (docType.startsWith("CollectionObject")) {
56                         CoreSession session = doc.getCoreSession();
57                         String csid = doc.getName();
58                         String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId");
59
60                         denormMediaRecords(session, csid, tenantId, denormValues);
61                         denormAcquisitionRecords(session, csid, tenantId, denormValues);
62                         denormExhibitionRecords(session, csid, tenantId, denormValues);
63                         denormConceptFields(doc, denormValues);
64                         denormMaterialFields(doc, denormValues);
65                         denormObjectNameFields(doc, denormValues);
66
67                         // Compute the title of the record for the public browser, and store it so that it can
68                         // be used for sorting ES query results.
69
70                         String title = computeTitle(doc);
71
72                         if (title != null) {
73                                 denormValues.put("title", title);
74                         }
75
76                         // Create a list of production years from the production date structured dates.
77
78                         List<Map<String, Object>> prodDateGroupList = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectProductionDateGroupList");
79
80                         denormValues.putArray("prodYears").addAll(structDatesToYearNodes(prodDateGroupList));
81                 } else if ("Media".equals(docType) && isMediaPublished(doc)) {
82                         CoreSession session = doc.getCoreSession();
83                         String csid = doc.getName();
84                         String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId");
85
86                         // Add media-specific denormalized fields
87                         denormRelatedObjects(session, csid, tenantId, denormValues);
88                 }
89
90                 return denormValues;
91         }
92
93         public void writeDenormValues(JsonGenerator jg, DocumentModel doc, ObjectNode denormValues) throws IOException {
94                 if (denormValues != null && denormValues.size() > 0) {
95                         if (jg.getCodec() == null) {
96                                 jg.setCodec(objectMapper);
97                         }
98
99                         Iterator<Map.Entry<String, JsonNode>> entries = denormValues.getFields();
100
101                         while (entries.hasNext()) {
102                                 Map.Entry<String, JsonNode> entry = entries.next();
103
104                                 jg.writeFieldName("collectionspace_denorm:" + entry.getKey());
105                                 jg.writeTree(entry.getValue());
106                         }
107                 }
108         }
109
110         private void denormMediaRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
111                 // Store the csid and alt text of media records that are related to this object.
112
113                 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);
114                 DocumentModelList relationDocs = session.query(relatedRecordQuery);
115                 List<JsonNode> mediaCsids = new ArrayList<JsonNode>();
116                 List<JsonNode> mediaAltTexts = new ArrayList<JsonNode>();
117
118                 if (relationDocs.size() > 0) {
119                         Iterator<DocumentModel> iterator = relationDocs.iterator();
120
121                         while (iterator.hasNext()) {
122                                 DocumentModel relationDoc = iterator.next();
123                                 String mediaCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
124                                 DocumentModel mediaDoc = getRecordByCsid(session, tenantId, "Media", mediaCsid);
125
126                                 if (isMediaPublished(mediaDoc)) {
127                                         mediaCsids.add(new TextNode(mediaCsid));
128
129                                         String altText = (String) mediaDoc.getProperty("media_common", "altText");
130
131                                         if (altText == null) {
132                                                 altText = "";
133                                         }
134
135                                         mediaAltTexts.add(new TextNode(altText));
136                                 }
137                         }
138                 }
139
140                 denormValues.putArray("mediaCsid").addAll(mediaCsids);
141                 denormValues.putArray("mediaAltText").addAll(mediaAltTexts);
142                 denormValues.put("hasMedia", mediaCsids.size() > 0);
143         }
144
145         private void denormRelatedObjects(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
146                 // Store the objectCsid of objects that are related to this media.
147
148                 String relatedRecordQuery = String.format(
149                                 "SELECT * FROM Relation WHERE relations_common:objectCsid = '%s' AND relations_common:subjectDocumentType = 'CollectionObject' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'",
150                                 csid, tenantId);
151                 DocumentModelList relationDocs = session.query(relatedRecordQuery);
152                 List<JsonNode> objectCsids = new ArrayList<JsonNode>();
153
154                 if (relationDocs.size() > 0) {
155                         Iterator<DocumentModel> iterator = relationDocs.iterator();
156
157                         while (iterator.hasNext()) {
158                                 DocumentModel relationDoc = iterator.next();
159                                 String objectCsid = (String) relationDoc.getProperty("relations_common", "subjectCsid");
160
161                                 if (objectCsid != null) {
162                                         objectCsids.add(new TextNode(objectCsid));
163                                 }
164                         }
165                 }
166
167                 denormValues.putArray("objectCsid").addAll(objectCsids);
168         }
169
170         private void denormAcquisitionRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
171                 // Store the credit lines of acquisition records that are related to this object.
172
173                 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);
174                 DocumentModelList relationDocs = session.query(relatedRecordQuery);
175                 List<JsonNode> creditLines = new ArrayList<JsonNode>();
176
177                 if (relationDocs.size() > 0) {
178                         Iterator<DocumentModel> iterator = relationDocs.iterator();
179
180                         while (iterator.hasNext()) {
181                                 DocumentModel relationDoc = iterator.next();
182                                 String acquisitionCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
183                                 String creditLine = getCreditLine(session, tenantId, acquisitionCsid);
184
185                                 if (creditLine != null && creditLine.length() > 0) {
186                                         creditLines.add(new TextNode(creditLine));
187                                 }
188                         }
189                 }
190
191                 denormValues.putArray("creditLine").addAll(creditLines);
192 }
193
194 private void denormExhibitionRecords(CoreSession session, String csid, String tenantId, ObjectNode denormValues) {
195         // Store the title, general note, and curatorial note of exhibition records that are published, and related to this object.
196
197         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);
198         DocumentModelList relationDocs = session.query(relatedRecordQuery);
199         List<JsonNode> exhibitions = new ArrayList<JsonNode>();
200
201         if (relationDocs.size() > 0) {
202                 Iterator<DocumentModel> iterator = relationDocs.iterator();
203
204                 while (iterator.hasNext()) {
205                         DocumentModel relationDoc = iterator.next();
206                         String exhibitionCsid = (String) relationDoc.getProperty("relations_common", "objectCsid");
207                         DocumentModel exhibitionDoc = getRecordByCsid(session, tenantId, "Exhibition", exhibitionCsid);
208
209                         if (exhibitionDoc != null && isExhibitionPublished(exhibitionDoc)) {
210                                 ObjectNode exhibitionNode = objectMapper.createObjectNode();
211
212                                 String title = (String) exhibitionDoc.getProperty("exhibitions_common", "title");
213                                 String generalNote = (String) exhibitionDoc.getProperty("exhibitions_common", "generalNote");
214                                 String curatorialNote = (String) exhibitionDoc.getProperty("exhibitions_common", "curatorialNote");
215
216                                 exhibitionNode.put("title", title);
217                                 exhibitionNode.put("generalNote", generalNote);
218                                 exhibitionNode.put("curatorialNote", curatorialNote);
219
220                                 exhibitions.add(exhibitionNode);
221                         }
222                 }
223         }
224
225         denormValues.putArray("exhibition").addAll(exhibitions);
226 }
227
228         /**
229          * Denormalize the material group list for a collectionobject in order to index the controlled or uncontrolled term
230          *
231          * @param doc the collectionobject document
232          * @param denormValues the json node for denormalized fields
233          */
234         private void denormMaterialFields(DocumentModel doc, ObjectNode denormValues) {
235                 List<Map<String, Object>> materialGroupList =
236                         (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "materialGroupList");
237
238                 List<JsonNode> denormMaterials = new ArrayList<>();
239                 for (Map<String, Object> materialGroup : materialGroupList) {
240                         String controlledMaterial = (String) materialGroup.get("materialControlled");
241                         if (controlledMaterial != null) {
242                                 final ObjectNode node = objectMapper.createObjectNode();
243                                 node.put("material", RefNameUtils.getDisplayName(controlledMaterial));
244                                 denormMaterials.add(node);
245                         }
246
247                         String material = (String) materialGroup.get("material");
248                         if (material != null) {
249                                 final ObjectNode node = objectMapper.createObjectNode();
250                                 node.put("material", material);
251                                 denormMaterials.add(node);
252                         }
253                 }
254
255                 denormValues.putArray("materialGroupList").addAll(denormMaterials);
256         }
257
258         /**
259          * Denormalize the object name group list for a collectionobject in order to index the controlled and
260          * uncontrolled terms
261          *
262          * @param doc the collectionobject document
263          * @param denormValues the json node for denormalized fields
264          */
265         private void denormObjectNameFields(DocumentModel doc, ObjectNode denormValues) {
266                 List<Map<String, Object>> objectNameList =
267                         (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectNameList");
268
269                 List<JsonNode> denormObjectNames = new ArrayList<>();
270                 for (Map<String, Object> objectNameGroup  : objectNameList) {
271                         String controlledName = (String) objectNameGroup.get("objectNameControlled");
272                         if (controlledName != null) {
273                                 final ObjectNode node = objectMapper.createObjectNode();
274                                 node.put("objectName", RefNameUtils.getDisplayName(controlledName));
275                                 denormObjectNames.add(node);
276                         }
277
278                         String objectName = (String) objectNameGroup.get("objectName");
279                         if (objectName != null) {
280                                 final ObjectNode node = objectMapper.createObjectNode();
281                                 node.put("objectName", objectName);
282                                 denormObjectNames.add(node);
283                         }
284                 }
285
286                 denormValues.putArray("objectNameList").addAll(denormObjectNames);
287         }
288
289         /**
290          * Denormalize the content concept, content event, content person, and content organization
291          * fields for a collectionobject so that they are indexed under a single field
292          *
293          * @param doc the collectionobject document
294          * @param denormValues the json node for denormalized fields
295          */
296         private void denormConceptFields(final DocumentModel doc, final ObjectNode denormValues) {
297                 final List<JsonNode> denormContentSubject = new ArrayList<>();
298                 final List<String> fields = Arrays.asList("contentConcepts",
299                         "contentEvents",
300                         "contentPersons",
301                         "contentOrganizations");
302
303                 for (String field : fields) {
304                         List<String> contentList = (List<String>) doc.getProperty("collectionobjects_common", field);
305
306                         for (String content  : contentList) {
307                                 if (content != null) {
308                                         final ObjectNode node = objectMapper.createObjectNode();
309                                         node.put("subject", RefNameUtils.getDisplayName(content));
310                                         denormContentSubject.add(node);
311                                 }
312                         }
313                 }
314
315                 denormValues.putArray("contentSubjectList").addAll(denormContentSubject);
316         }
317
318         /**
319          * Compute a title for the public browser. This needs to be indexed in ES so that it can
320          * be used for sorting. (Even if it's just extracting the primary value.)
321          */
322         protected String computeTitle(DocumentModel doc) {
323                 List<Map<String, Object>> titleGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "titleGroupList");
324                 String primaryTitle = null;
325
326                 if (titleGroups.size() > 0) {
327                         Map<String, Object> primaryTitleGroup = titleGroups.get(0);
328                         primaryTitle = (String) primaryTitleGroup.get("title");
329                 }
330
331                 if (StringUtils.isNotEmpty(primaryTitle)) {
332                         return primaryTitle;
333                 }
334
335                 List<Map<String, Object>> objectNameGroups = (List<Map<String, Object>>) doc.getProperty("collectionobjects_common", "objectNameList");
336                 String primaryObjectName = null;
337
338                 if (objectNameGroups.size() > 0) {
339                         Map<String, Object> primaryObjectNameGroup = objectNameGroups.get(0);
340                         primaryObjectName = (String) primaryObjectNameGroup.get("objectNameControlled");
341                         if (primaryObjectName == null) {
342                                 primaryObjectName = (String) primaryObjectNameGroup.get("objectName");
343                         }
344
345                         // The object might be a refname in some profiles/tenants. If it is, use only the display name.
346
347                         try {
348                                 String displayName = RefNameUtils.getDisplayName(primaryObjectName);
349
350                                 if (displayName != null) {
351                                         primaryObjectName = displayName;
352                                 }
353                         }
354                         catch (Exception e) {}
355                 }
356
357                 return primaryObjectName;
358         }
359
360         private boolean isPublished(DocumentModel doc, String publishedFieldPart, String publishedFieldName) {
361                 boolean isPublished = false;
362
363                 if (doc != null) {
364                         List<String> publishToValues = (List<String>) doc.getProperty(publishedFieldPart, publishedFieldName);
365
366                         if (publishToValues != null) {
367                                 for (int i=0; i<publishToValues.size(); i++) {
368                                         String value = publishToValues.get(i);
369                                         String shortId = RefNameUtils.getItemShortId(value);
370
371                                         if (shortId.equals("all") || shortId.equals("cspacepub")) {
372                                                 isPublished = true;
373                                                 break;
374                                         }
375                                 }
376                         }
377                 }
378
379                 return isPublished;
380
381         }
382
383         private boolean isMediaPublished(DocumentModel mediaDoc) {
384                 return isPublished(mediaDoc, "media_common", "publishToList");
385         }
386
387         private boolean isExhibitionPublished(DocumentModel exhibitionDoc) {
388                 return isPublished(exhibitionDoc, "exhibitions_common", "publishToList");
389         }
390
391         private String getCreditLine(CoreSession session, String tenantId, String acquisitionCsid) {
392                 String creditLine = null;
393                 DocumentModel acquisitionDoc = getRecordByCsid(session, tenantId, "Acquisition", acquisitionCsid);
394
395                 if (acquisitionDoc != null) {
396                         creditLine = (String) acquisitionDoc.getProperty("acquisitions_common", "creditLine");
397                 }
398
399                 return creditLine;
400         }
401
402         protected DocumentModel getRecordByCsid(CoreSession session, String tenantId, String recordType, String csid) {
403                 String getRecordQuery = String.format("SELECT * FROM %s WHERE ecm:name = '%s' AND ecm:currentLifeCycleState = 'project' AND collectionspace_core:tenantId = '%s'", recordType, csid, tenantId);
404
405                 DocumentModelList docs = session.query(getRecordQuery);
406
407                 if (docs != null && docs.size() > 0) {
408                         return docs.get(0);
409                 }
410
411                 return null;
412         }
413
414         protected List<JsonNode> structDateToYearNodes(Map<String, Object> structDate) {
415                 return structDatesToYearNodes(Arrays.asList(structDate));
416         }
417
418         protected List<JsonNode> structDatesToYearNodes(List<Map<String, Object>> structDates) {
419                 Set<Integer> years = new HashSet<Integer>();
420
421                 for (Map<String, Object> structDate : structDates) {
422                         if (structDate != null) {
423                                 GregorianCalendar earliestCalendar = (GregorianCalendar) structDate.get("dateEarliestScalarValue");
424                                 GregorianCalendar latestCalendar = (GregorianCalendar) structDate.get("dateLatestScalarValue");
425
426                                 if (earliestCalendar != null && latestCalendar != null) {
427                                         // Grr @ latest scalar value historically being exclusive.
428                                         // Subtract one day to make it inclusive.
429                                         latestCalendar.add(Calendar.DATE, -1);
430
431                                         Integer earliestYear = earliestCalendar.get(Calendar.YEAR);
432                                         Integer latestYear = latestCalendar.get(Calendar.YEAR);;
433
434                                         for (int year = earliestYear; year <= latestYear; year++) {
435                                                 years.add(year);
436                                         }
437                                 }
438                         }
439                 }
440
441                 List<Integer> yearList = new ArrayList<Integer>(years);
442                 Collections.sort(yearList);
443
444                 List<JsonNode> yearNodes = new ArrayList<JsonNode>();
445
446                 for (Integer year : yearList) {
447                         yearNodes.add(new IntNode(year));
448                 }
449
450                 return yearNodes;
451         }
452 }