1 package org.collectionspace.services.nuxeo.elasticsearch;
3 import java.io.IOException;
4 import java.util.ArrayList;
5 import java.util.Collection;
6 import java.util.HashSet;
7 import java.util.Iterator;
8 import java.util.LinkedHashSet;
13 import javax.ws.rs.core.HttpHeaders;
15 import org.apache.commons.lang3.StringUtils;
17 import org.codehaus.jackson.JsonGenerator;
18 import org.codehaus.jackson.JsonNode;
19 import org.codehaus.jackson.map.ObjectMapper;
20 import org.codehaus.jackson.node.ObjectNode;
21 import org.codehaus.jackson.node.TextNode;
23 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 CSJsonESDocumentWriter 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 // Compute and store fields that should be indexed with this document in ElasticSearch.
39 // TODO: Make this configurable. This is currently hardcoded for the materials profile and
40 // the Material Order application.
42 ObjectNode denormValues = objectMapper.createObjectNode();
44 String docType = doc.getType();
46 if (docType.startsWith("Materialitem")) {
47 CoreSession session = doc.getCoreSession();
49 // Store the csids of media records that reference this material authority item via the
52 String refName = (String) doc.getProperty("collectionspace_core", "refName");
54 if (StringUtils.isNotEmpty(refName)) {
55 String escapedRefName = refName.replace("'", "\\'");
56 String tenantId = (String) doc.getProperty("collectionspace_core", "tenantId");
57 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);
59 DocumentModelList mediaDocs = session.query(mediaQuery);
60 List<JsonNode> mediaCsids = new ArrayList<JsonNode>();
62 if (mediaDocs.size() > 0) {
63 Iterator<DocumentModel> iterator = mediaDocs.iterator();
65 while (iterator.hasNext()) {
66 DocumentModel mediaDoc = iterator.next();
68 if (isMediaPublished(mediaDoc)) {
69 String mediaCsid = (String) mediaDoc.getName();
71 mediaCsids.add(new TextNode(mediaCsid));
76 denormValues.putArray("mediaCsid").addAll(mediaCsids);
79 // Compute the title of the record for the public browser, and store it so that it can
80 // be used for sorting ES query results.
82 String title = computeTitle(doc);
85 denormValues.put("title", title);
88 List<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
89 List<String> commercialNames = findTermDisplayNamesWithFlag(termGroups, "commercial");
90 List<String> commonNames = findTermDisplayNamesWithFlag(termGroups, "common");
92 // Find and store the commercial names and common names for this item. This simplifies
93 // search and display in the Material Order application.
95 if (commercialNames.size() > 0) {
96 denormValues.putArray("commercialNames").addAll(jsonNodes(commercialNames));
99 if (commonNames.size() > 0) {
100 denormValues.putArray("commonNames").addAll(jsonNodes(commonNames));
103 // Combine term creator organizations and term editor organizations into a holding
104 // institutions field.
106 Set<String> holdingInstitutions = new LinkedHashSet<String>();
108 holdingInstitutions.addAll(getTermAttributionContributors(doc));
109 holdingInstitutions.addAll(getTermAttributionEditors(doc));
111 if (holdingInstitutions.size() > 0) {
112 denormValues.putArray("holdingInstitutions").addAll(jsonNodes(holdingInstitutions));
116 // Below is sample code for denormalizing fields from the computed current location (place
117 // item) into collection object documents. This was written for the public browser
118 // prototype for public art.
121 if (docType.startsWith("CollectionObject")) {
122 CoreSession session = doc.getCoreSession();
124 String refName = (String) doc.getProperty("collectionobjects_common", "computedCurrentLocation");
126 if (StringUtils.isNotEmpty(refName)) {
127 String escapedRefName = refName.replace("'", "\\'");
128 String placeQuery = String.format("SELECT * FROM PlaceitemTenant5000 WHERE places_common:refName = '%s'", escapedRefName);
130 DocumentModelList placeDocs = session.query(placeQuery, 1);
132 if (placeDocs.size() > 0) {
133 DocumentModel placeDoc = placeDocs.get(0);
135 String placementType = (String) placeDoc.getProperty("places_publicart:placementType").getValue();
137 if (placementType != null) {
138 denormValues.put("placementType", placementType);
141 Property geoRefGroup;
144 geoRefGroup = placeDoc.getProperty("places_common:placeGeoRefGroupList/0");
145 } catch (PropertyNotFoundException e) {
149 if (geoRefGroup != null) {
150 Double decimalLatitude = (Double) geoRefGroup.getValue("decimalLatitude");
151 Double decimalLongitude = (Double) geoRefGroup.getValue("decimalLongitude");
153 if (decimalLatitude != null && decimalLongitude != null) {
154 ObjectNode geoPointNode = objectMapper.createObjectNode();
156 geoPointNode.put("lat", decimalLatitude);
157 geoPointNode.put("lon", decimalLongitude);
159 denormValues.put("geoPoint", geoPointNode);
165 String uri = (String) doc.getProperty("collectionobjects_core", "uri");
166 String csid = uri.substring(uri.lastIndexOf('/') + 1);
167 String mediaQuery = String.format("SELECT media_common:blobCsid, media_common:title FROM Relation WHERE relations_common:subjectCsid = '%s' AND relations_common:objectDocumentType = 'Media'", csid);
169 DocumentModelList mediaDocs = session.query(mediaQuery, 1);
171 if (mediaDocs.size() > 0) {
177 jg.writeStartObject();
179 writeSystemProperties(jg, doc);
180 writeSchemas(jg, doc, schemas);
181 writeContextParameters(jg, doc, contextParameters);
182 writeDenormValues(jg, doc, denormValues);
188 public void writeDenormValues(JsonGenerator jg, DocumentModel doc, ObjectNode denormValues) throws IOException {
189 if (denormValues != null && denormValues.size() > 0) {
190 if (jg.getCodec() == null) {
191 jg.setCodec(objectMapper);
194 Iterator<Map.Entry<String, JsonNode>> entries = denormValues.getFields();
196 while (entries.hasNext()) {
197 Map.Entry<String, JsonNode> entry = entries.next();
199 jg.writeFieldName("collectionspace_denorm:" + entry.getKey());
200 jg.writeTree(entry.getValue());
206 * Compute a title for the public browser. This needs to be indexed in ES so that it can
207 * be used for sorting. (Even if it's just extracting the primary value.)
209 private String computeTitle(DocumentModel doc) {
210 List<Map<String, Object>> termGroups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermGroupList");
211 String primaryDisplayName = null;
213 if (termGroups.size() > 0) {
214 Map<String, Object> primaryTermGroup = termGroups.get(0);
215 primaryDisplayName = (String) primaryTermGroup.get("termDisplayName");
218 return primaryDisplayName;
221 private String findFirstTermDisplayNameWithFlag(List<Map<String, Object>> termGroups, String flagShortId) {
222 String termDisplayName = null;
224 for (Map<String, Object> termGroup : termGroups) {
225 String termFlag = (String) termGroup.get("termFlag");
227 if (termFlag != null && termFlag.contains("(" + flagShortId + ")")) {
228 String candidateTermDisplayName = (String) termGroup.get("termDisplayName");
230 if (StringUtils.isNotEmpty(candidateTermDisplayName)) {
231 termDisplayName = candidateTermDisplayName;
237 return termDisplayName;
240 private List<String> findTermDisplayNamesWithFlag(List<Map<String, Object>> termGroups, String flagShortId) {
241 List<String> termDisplayNames = new ArrayList<String>();
243 for (Map<String, Object> termGroup : termGroups) {
244 String termFlag = (String) termGroup.get("termFlag");
246 if (termFlag != null && termFlag.contains("(" + flagShortId + ")")) {
247 String candidateTermDisplayName = (String) termGroup.get("termDisplayName");
249 if (StringUtils.isNotEmpty(candidateTermDisplayName)) {
250 termDisplayNames.add(candidateTermDisplayName);
255 return termDisplayNames;
258 private Set<String> getTermAttributionContributors(DocumentModel doc) {
259 Set orgs = new LinkedHashSet<String>();
261 List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionContributingGroupList");
263 for (Map<String, Object> group : groups) {
264 String org = (String) group.get("materialTermAttributionContributingOrganization");
266 if (StringUtils.isNotEmpty(org)) {
274 private Set<String> getTermAttributionEditors(DocumentModel doc) {
275 Set orgs = new LinkedHashSet<String>();
277 List<Map<String, Object>> groups = (List<Map<String, Object>>) doc.getProperty("materials_common", "materialTermAttributionEditingGroupList");
279 for (Map<String, Object> group : groups) {
280 String org = (String) group.get("materialTermAttributionEditingOrganization");
282 if (StringUtils.isNotEmpty(org)) {
290 private boolean isMediaPublished(DocumentModel mediaDoc) {
291 List<String> publishToValues = (List<String>) mediaDoc.getProperty("media_materials", "publishToList");
292 boolean isPublished = false;
294 for (int i=0; i<publishToValues.size(); i++) {
295 String value = publishToValues.get(i);
296 String shortId = RefNameUtils.getItemShortId(value);
298 if (shortId.equals("all") || shortId.equals("materialorder")) {
307 private List<JsonNode> jsonNodes(Collection<String> values) {
308 List<JsonNode> nodes = new ArrayList<JsonNode>();
309 Iterator<String> iterator = values.iterator();
311 while (iterator.hasNext()) {
312 String value = iterator.next();
314 nodes.add(new TextNode(value));