1 package org.collectionspace.services.advancedsearch;
3 import java.nio.charset.StandardCharsets;
4 import java.util.ArrayList;
5 import java.util.HashMap;
8 import javax.ws.rs.Consumes;
9 import javax.ws.rs.GET;
10 import javax.ws.rs.Path;
11 import javax.ws.rs.Produces;
12 import javax.ws.rs.core.Context;
13 import javax.ws.rs.core.MultivaluedMap;
14 import javax.ws.rs.core.Request;
15 import javax.ws.rs.core.Response;
16 import javax.ws.rs.core.UriInfo;
17 import javax.xml.datatype.DatatypeConfigurationException;
18 import javax.xml.datatype.DatatypeFactory;
19 import javax.xml.datatype.XMLGregorianCalendar;
21 import org.collectionspace.services.advancedsearch.AdvancedsearchCommonList.AdvancedsearchListItem;
22 import org.collectionspace.services.advancedsearch.model.BriefDescriptionListModel;
23 import org.collectionspace.services.advancedsearch.model.ContentConceptListModel;
24 import org.collectionspace.services.advancedsearch.model.ObjectNameListModel;
25 import org.collectionspace.services.advancedsearch.model.ResponsibleDepartmentsListModel;
26 import org.collectionspace.services.advancedsearch.model.TitleGroupListModel;
27 import org.collectionspace.services.client.AdvancedSearchClient;
28 import org.collectionspace.services.client.CollectionObjectClient;
29 import org.collectionspace.services.client.CollectionSpaceClient;
30 import org.collectionspace.services.client.IQueryManager;
31 import org.collectionspace.services.client.PayloadInputPart;
32 import org.collectionspace.services.client.PoxPayloadIn;
33 import org.collectionspace.services.collectionobject.CollectionObjectResource;
34 import org.collectionspace.services.collectionobject.CollectionobjectsCommon;
35 import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl;
36 import org.collectionspace.services.common.ResourceMap;
37 import org.collectionspace.services.common.UriInfoWrapper;
38 import org.collectionspace.services.common.context.RemoteServiceContextFactory;
39 import org.collectionspace.services.common.context.ServiceContextFactory;
40 import org.collectionspace.services.jaxb.AbstractCommonList;
41 import org.collectionspace.services.jaxb.AbstractCommonList.ListItem;
42 import org.collectionspace.services.media.MediaResource;
43 import org.dom4j.DocumentException;
44 import org.jboss.resteasy.spi.ResteasyProviderFactory;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
47 import org.w3c.dom.Element;
49 // FIXME: The *Client pattern should be deprecated; it's only really used in unit tests, and trying to use it in services creates authorization challenges. It's repeated here only to maintain parity with existing services.
51 * This class defines the advanced search endpoints.
53 @Path(AdvancedSearchClient.SERVICE_PATH)
54 @Consumes("application/xml")
55 @Produces("application/xml")
56 public class AdvancedSearch
57 extends AbstractCollectionSpaceResourceImpl<AdvancedsearchListItem, AdvancedsearchListItem> {
58 private static final String FIELDS_RETURNED = "uri|csid|refName|blobCsid|updatedAt|objectId|objectNumber|objectName|title|computedCurrentLocation|responsibleDepartments|responsibleDepartment|contentConcepts|briefDescription";
59 private static final String COMMON_PART_NAME = CollectionObjectClient.SERVICE_NAME + CollectionSpaceClient.PART_LABEL_SEPARATOR + CollectionSpaceClient.PART_COMMON_LABEL; // FIXME: it's not great to hardcode this here
61 private final Logger logger = LoggerFactory.getLogger(AdvancedSearch.class);
62 private final CollectionObjectResource cor = new CollectionObjectResource();
63 private final MediaResource mr = new MediaResource();
65 public AdvancedSearch() {
70 * Primary advanced search API endpoint.
72 * @param request The incoming request. Injected.
73 * @param uriInfo The URI info of the incoming request, including query parameters and other search control parameters. Injected.
74 * @return A possibly-empty AbstractCommonList of the advanced search results corresponding to the query
77 public AbstractCommonList getList(@Context Request request, @Context UriInfo uriInfo) {
78 logger.info("advancedsearch called with path: {}", uriInfo.getPath());
79 MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters(true);
80 logger.info("advancedsearch called with query params: {}", queryParams);
83 ObjectFactory objectFactory = new ObjectFactory();
84 AdvancedsearchCommonList resultsList = objectFactory.createAdvancedsearchCommonList();
85 // FIXME: this shouldn't be necessary?
86 resultsList.advancedsearchListItem = new ArrayList<AdvancedsearchListItem>();
88 // the logic here is to use CollectionObjectResource to perform the search, then
89 // loop over the results retrieving corresponding CollectionobjectsCommon objects,
90 // which have more fields
91 AbstractCommonList collectionObjectList = cor.getList(uriInfo);
92 List<ListItem> collectionObjectListItems = collectionObjectList.getListItem();
94 HashMap<String, String> collectionObjectValuesMap = new HashMap<String, String>();
95 for (ListItem item : collectionObjectListItems) {
96 // FIXME: is there no better way to do this? We should at least abstract this logic out of here
97 List<Element> els = item.getAny();
98 for (Element el : els) {
99 String elementName = el.getTagName();
100 String elementText = el.getTextContent();
101 collectionObjectValuesMap.put(elementName, elementText);
103 String csid = collectionObjectValuesMap.get("csid");
104 UriInfoWrapper wrappedUriInfo = new UriInfoWrapper(uriInfo);
105 List<String> blobCsids = findBlobCsids(csid, wrappedUriInfo);
108 * NOTE: code below is partly based on
109 * CollectionObjectServiceTest.readCollectionObjectCommonPart and
110 * AbstractPoxServiceTestImpl
112 ResourceMap resourceMap = ResteasyProviderFactory.getContextData(ResourceMap.class);
113 Response res = cor.get(request, resourceMap, uriInfo, csid);
114 int statusCode = res.getStatus();
115 logger.warn("advancedsearch: call to CollectionObjectResource for csid {} returned status {}", csid, statusCode);
116 CollectionobjectsCommon collectionObject = null;
117 // FIXME: is there no better way to do this? We should at least abstract this logic out of here
118 PoxPayloadIn input = null;
120 String responseXml = new String((byte[]) res.getEntity(),StandardCharsets.UTF_8);
121 input = new PoxPayloadIn(responseXml);
122 } catch (DocumentException e) {
123 // TODO: need better error handling
124 logger.error("advancedsearch: could not create PoxPayloadIn", e);
128 PayloadInputPart payloadInputPart = input.getPart(COMMON_PART_NAME);
129 if (null != payloadInputPart) {
130 collectionObject = (CollectionobjectsCommon) payloadInputPart.getBody();
133 // build up a listitem for the result list using the additional fields in CollectionObjectsCommon
134 if (null != collectionObject) {
135 AdvancedsearchListItem listItem = objectFactory.createAdvancedsearchCommonListAdvancedsearchListItem();
136 listItem.setBriefDescription(BriefDescriptionListModel
137 .briefDescriptionListToDisplayString(collectionObject.getBriefDescriptions()));
138 // TODO: collectionObject.getComputedCurrentLocation() is (can be?) a refname.
139 // code below extracts display name. there's probably something in RefName or
140 // similar to do this kind of thing see also
141 // ContentConceptListModel.displayNameFromRefName
142 String currLoc = collectionObject.getComputedCurrentLocation();
143 String currLocDisplayName = currLoc;
144 if (null != currLoc && currLoc.indexOf("'") < currLoc.lastIndexOf("'")) {
145 currLocDisplayName = currLoc.substring(currLoc.indexOf("'") + 1, currLoc.lastIndexOf("'"));
147 listItem.setComputedCurrentLocation(currLocDisplayName); // "Computed Current
150 // https://docs.google.com/spreadsheets/d/103jyxa2oCtt8U0IQ25xsOyIxqwKvPNXlcCtcjGlT5tQ/edit?gid=0#gid=0
151 listItem.setObjectName(
152 ObjectNameListModel.objectNameListToDisplayString(collectionObject.getObjectNameList()));
154 TitleGroupListModel.titleGroupListToDisplayString(collectionObject.getTitleGroupList()));
155 ResponsibleDepartmentsList rdl = ResponsibleDepartmentsListModel
156 .responsibleDepartmentListToResponsibleDepartmentsList(
157 collectionObject.getResponsibleDepartments());
158 listItem.setResponsibleDepartments(rdl);
159 listItem.setResponsibleDepartment(
160 ResponsibleDepartmentsListModel.responsibleDepartmentsListDisplayString(rdl));
162 listItem.setContentConcepts(
163 ContentConceptListModel.contentConceptListDisplayString(collectionObject.getContentConcepts()));
165 // from media resource
166 if (blobCsids.size() > 0) {
167 listItem.setBlobCsid(blobCsids.get(0));
170 // from collectionobject itself
171 listItem.setCsid(collectionObjectValuesMap.get("csid"));
172 listItem.setObjectId(collectionObjectValuesMap.get("objectId")); // "Identification Number: Display full
174 // https://docs.google.com/spreadsheets/d/103jyxa2oCtt8U0IQ25xsOyIxqwKvPNXlcCtcjGlT5tQ/edit?gid=0#gid=0
175 listItem.setObjectNumber(collectionObjectValuesMap.get("objectNumber"));
176 listItem.setRefName(collectionObjectValuesMap.get("refName"));
177 listItem.setUri(collectionObjectValuesMap.get("uri"));
179 XMLGregorianCalendar date = DatatypeFactory.newInstance()
180 .newXMLGregorianCalendar(collectionObjectValuesMap.get("updatedAt"));
181 listItem.setUpdatedAt(date); // "Last Updated Date: Display Date, if updated same day can we display
182 // x number of hours ago" from
183 // https://docs.google.com/spreadsheets/d/103jyxa2oCtt8U0IQ25xsOyIxqwKvPNXlcCtcjGlT5tQ/edit?gid=0#gid=0
184 } catch (DatatypeConfigurationException e) {
185 // FIXME need better error handling
186 logger.error("advancedsearch: could not create XMLGregorianCalendar for updatedAt ", e);
187 logger.error("advancedsearch: updatedAt: {}", collectionObjectValuesMap.get("updatedAt"));
190 // add the populated item to the results
191 resultsList.getAdvancedsearchListItem().add(listItem);
193 logger.warn("advancedsearch: could not find CollectionobjectsCommon associated with csid {}", csid);
198 // NOTE: I think this is necessary for the front end to know what to do with
199 // what's returned (?)
200 AbstractCommonList abstractList = (AbstractCommonList) resultsList;
201 abstractList.setItemsInPage(collectionObjectList.getItemsInPage());
202 abstractList.setPageNum(collectionObjectList.getPageNum());
203 abstractList.setPageSize(collectionObjectList.getPageSize());
204 abstractList.setTotalItems(collectionObjectList.getTotalItems());
205 // FIXME: is there a way to generate this rather than hardcode it?
206 abstractList.setFieldsReturned(FIELDS_RETURNED);
212 * Retrieves the blob CSIDs associated with a given object's CSID
214 * @param csid The CSID of an object whose associated blobs (thumbnails) is desired
215 * @param wrappedUriInfo The wrapped (mutable) UriInfo of the incoming query that ultimately triggered this call
216 * @return A possibly-empty list of strings of the blob CSIDs associated with CSID
218 private List<String> findBlobCsids(String csid, UriInfoWrapper wrappedUriInfo) {
219 MultivaluedMap<String, String> wrappedQueryParams = wrappedUriInfo.getQueryParameters();
220 wrappedQueryParams.clear();
221 wrappedQueryParams.add(IQueryManager.SEARCH_RELATED_TO_CSID_AS_SUBJECT, csid);
222 wrappedQueryParams.add("pgSz", "1");
223 wrappedQueryParams.add("pgNum", "0");
224 wrappedQueryParams.add("sortBy", "media_common:title");
225 AbstractCommonList associatedMedia = mr.getList(wrappedUriInfo);
226 HashMap<String, String> mediaResourceValuesMap = new HashMap<String, String>();
227 ArrayList<String> blobCsids = new ArrayList<String>();
228 for (ListItem item : associatedMedia.getListItem()) {
229 // FIXME: is there no better way to do this? we should at least abstract out this logic
230 List<Element> els = item.getAny();
231 for (Element el : els) {
232 String elementName = el.getTagName();
233 String elementText = el.getTextContent();
234 mediaResourceValuesMap.put(elementName, elementText);
236 String blobCsid = mediaResourceValuesMap.get("blobCsid");
237 if (null != blobCsid) {
238 blobCsids.add(blobCsid);
245 public Class<?> getCommonPartClass() {
250 public ServiceContextFactory<AdvancedsearchListItem, AdvancedsearchListItem> getServiceContextFactory() {
251 return (ServiceContextFactory<AdvancedsearchListItem, AdvancedsearchListItem>) RemoteServiceContextFactory
256 public String getServiceName() {
257 return AdvancedSearchClient.SERVICE_NAME;
261 protected String getVersionString() {