2 * This document is a part of the source code and related artifacts
3 * for CollectionSpace, an open source collections management system
4 * for museums and related institutions:
6 * http://www.collectionspace.org
7 * http://wiki.collectionspace.org
9 * Copyright 2009 University of California at Berkeley
11 * Licensed under the Educational Community License (ECL), Version 2.0.
12 * You may not use this file except in compliance with this License.
14 * You may obtain a copy of the ECL 2.0 License at
16 * https://source.collectionspace.org/collection-space/LICENSE.txt
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
24 package org.collectionspace.services.nuxeo.client.java;
26 import java.lang.reflect.Method;
27 import java.util.ArrayList;
28 import java.util.GregorianCalendar;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
33 import javax.ws.rs.core.MultivaluedMap;
35 import org.collectionspace.services.client.CollectionSpaceClient;
36 import org.collectionspace.services.client.IQueryManager;
37 import org.collectionspace.services.client.IRelationsManager;
38 import org.collectionspace.services.client.PoxPayload;
39 import org.collectionspace.services.client.PoxPayloadIn;
40 import org.collectionspace.services.client.PoxPayloadOut;
41 import org.collectionspace.services.client.XmlTools;
42 import org.collectionspace.services.client.workflow.WorkflowClient;
43 import org.collectionspace.services.common.ReflectionMapper;
44 import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
45 import org.collectionspace.services.common.api.Tools;
46 import org.collectionspace.services.common.config.ServiceConfigUtils;
47 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
48 import org.collectionspace.services.common.context.ServiceContext;
49 import org.collectionspace.services.common.document.DocumentException;
50 import org.collectionspace.services.common.document.DocumentWrapper;
51 import org.collectionspace.services.common.query.QueryContext;
52 import org.collectionspace.services.common.relation.nuxeo.RelationsUtils;
53 import org.collectionspace.services.config.service.DocHandlerParams;
54 import org.collectionspace.services.config.service.ListResultField;
55 import org.collectionspace.services.jaxb.AbstractCommonList;
56 import org.collectionspace.services.nuxeo.client.java.CommonList;
57 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
58 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
59 import org.dom4j.Document;
60 import org.nuxeo.ecm.core.api.DocumentModel;
61 import org.nuxeo.ecm.core.api.DocumentModelList;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * This class is generified by the marker type T,
67 * where T is expected to map to something like BlobCommon, MediaCommon, ObjectexitCommon, etc.,
68 * and so on for every JAXB-generated schema class.
71 * $LastChangedRevision: $
75 public abstract class NuxeoDocumentModelHandler<T> extends RemoteDocumentModelHandlerImpl<T, AbstractCommonList> {
78 private final Logger logger = LoggerFactory.getLogger(this.getClass());
80 private AbstractCommonList commonList;
82 protected static final int NUM_STANDARD_LIST_RESULT_FIELDS = 5;
83 protected static final String STANDARD_LIST_CSID_FIELD = "csid";
84 protected static final String STANDARD_LIST_URI_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_URI;
85 protected static final String STANDARD_LIST_REFNAME_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME;
86 protected static final String STANDARD_LIST_UPDATED_AT_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT;
87 protected static final String STANDARD_LIST_WORKFLOW_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE;
88 protected static final String STANDARD_LIST_MARK_RT_FIELD = "related";
91 public AbstractCommonList getCommonPartList() {
96 public void setCommonPartList(AbstractCommonList aCommonList) {
97 this.commonList = aCommonList;
100 private T commonPart;
103 public T getCommonPart() {
104 return (T) commonPart;
108 public void setCommonPart(T commonPart) {
109 this.commonPart = commonPart;
113 * The entity type expected from the JAX-RS Response object. By default it is of type String. Child classes
114 * can override this if they need to.
116 protected Class<String> getEntityResponseType() {
120 protected String getWorkflowState(PoxPayload payload) {
121 String result = null;
123 Document document = payload.getDOMDocument();
124 result = XmlTools.getElementValue(document, "//" + WorkflowClient.WORKFLOWSTATE_XML_ELEMENT_NAME);
129 protected Long getRevision(PoxPayload payload) {
132 Document document = payload.getDOMDocument();
133 String xmlRev = XmlTools.getElementValue(document, "//rev");
134 result = Long.valueOf(xmlRev);
139 protected List getItemList(PoxPayloadIn payloadIn) {
142 Document document = payloadIn.getDOMDocument();
143 result = XmlTools.getElementNodes(document, "//list-item");
149 * Subclass DocHandlers may override this method to control exact creation of the common list.
150 * This class instantiates an AbstractCommonList from the classname returned by getDocHandlerParams().AbstractCommonListClassname.
155 public AbstractCommonList createAbstractCommonListImpl() throws Exception {
156 // String classname = this.commonList.getClass().getName();
157 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
158 String classname = ServiceConfigUtils.getDocHandlerParams(ctx).getAbstractCommonListClassname();
159 if (classname == null) {
161 "in createAbstractCommonListImpl. getDocHandlerParams().getAbstractCommonListClassname() is null");
163 classname = classname.trim();
164 return (AbstractCommonList) (ReflectionMapper.instantiate(classname));
167 /** DocHandlerBase calls this method with the CSID as id */
168 public Object createItemForCommonList(DocumentModel docModel, String label, String id) throws Exception {
169 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
170 return createItemForCommonList(ServiceConfigUtils.getDocHandlerParams(ctx).getCommonListItemClassname(),
171 docModel, label, id, true);
174 public String getSummaryFields(AbstractCommonList theCommonList) throws DocumentException {
175 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
176 return ServiceConfigUtils.getDocHandlerParams(ctx).getSummaryFields();
179 public void setListItemArrayExtended(boolean isExtended) throws DocumentException {
180 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
181 ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().setExtended(isExtended);
184 public boolean isListItemArrayExtended() throws DocumentException {
185 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
186 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().isExtended();
189 public List<ListResultField> getListItemsArray() throws DocumentException {
190 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
191 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().getListResultField();
195 public T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
196 throw new UnsupportedOperationException();
200 public void fillCommonPart(T objectexitObject, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
201 throw new UnsupportedOperationException();
204 protected static String getRefname(DocumentModel docModel) throws Exception {
205 String result = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
206 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
210 public static String getUpdatedAtAsString(DocumentModel docModel) throws Exception {
211 GregorianCalendar cal = (GregorianCalendar) docModel.getProperty(
212 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
213 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT);
214 String updatedAt = GregorianCalendarDateTimeUtils.formatAsISO8601Timestamp(cal);
219 public AbstractCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
220 CommonList commonList = new CommonList();
221 CoreSessionInterface repoSession = null;
222 NuxeoRepositoryClientImpl repoClient = null;
223 boolean releaseRepoSession = false;
225 AbstractServiceContextImpl ctx = (AbstractServiceContextImpl) getServiceContext();
226 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
227 String markRtSbj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT);
228 if (Tools.isBlank(markRtSbj)) {
233 // We may be being asked to mark the record as related independent of whether it is the subject or object of a relationship.
235 String markRtSbjOrObj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_EITHER);
236 if (Tools.isBlank(markRtSbjOrObj)) {
237 markRtSbjOrObj = null;
239 if (Tools.isBlank(markRtSbj) == false) {
240 logger.warn(String.format("Ignoring query param %s=%s since overriding query param %s=%s exists.",
241 IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT, markRtSbj, IQueryManager.MARK_RELATED_TO_CSID_AS_EITHER, markRtSbjOrObj));
243 markRtSbj = markRtSbjOrObj; // Mark the record as related independent of whether it is the subject or object of a relationship
247 if (markRtSbj != null) {
248 repoClient = (NuxeoRepositoryClientImpl) this.getRepositoryClient(ctx);
249 NuxeoRepositoryClientImpl nuxeoRepoClient = (NuxeoRepositoryClientImpl) repoClient;
250 repoSession = this.getRepositorySession();
251 if (repoSession == null) {
252 repoSession = repoClient.getRepositorySession(ctx);
253 releaseRepoSession = true;
257 String commonSchema = getServiceContext().getCommonPartLabel();
258 extractPagingInfo(commonList, wrapDoc);
259 List<ListResultField> resultsFields = getListItemsArray(); // Get additional list result fields defined in the service bindings
260 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
261 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
262 if (markRtSbj != null) {
267 String fields[] = new String[nFields]; // REM - Why can't this just be a static array defined once at the top? Then there'd be no need for these hardcoded "[x]" statements and no need for NUM_STANDARD_LIST_RESULT_FIELDS constant as well.
268 fields[0] = STANDARD_LIST_CSID_FIELD;
269 fields[1] = STANDARD_LIST_URI_FIELD;
270 fields[2] = STANDARD_LIST_REFNAME_FIELD;
271 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
272 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
274 if (markRtSbj != null) {
275 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
278 for (int i = baseFields; i < nFields; i++) {
279 ListResultField field = resultsFields.get(i - baseFields);
280 fields[i] = field.getElement();
282 commonList.setFieldsReturned(fields);
284 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
285 HashMap<String, Object> item = new HashMap<String, Object>();
286 while (iter.hasNext()) {
287 DocumentModel docModel = iter.next();
288 String id = NuxeoUtils.getCsid(docModel);
289 item.put(STANDARD_LIST_CSID_FIELD, id);
292 // If the mark-related query param was set, check to see if the doc we're processing
293 // is related to the value specified in the mark-related query param.
295 if (markRtSbj != null) {
296 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null, markRtSbj == markRtSbjOrObj);
297 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
298 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
299 QueryContext queryContext = new QueryContext(ctx, whereClause);
300 queryContext.setDocType(IRelationsManager.DOC_TYPE);
301 String query = NuxeoUtils.buildNXQLQuery(queryContext);
302 // Search for 1 relation that matches. 1 is enough to fail
304 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
305 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
308 String uri = getUri(docModel);
309 item.put(STANDARD_LIST_URI_FIELD, uri);
310 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
311 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
312 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
314 for (ListResultField field : resultsFields) {
315 String schema = field.getSchema();
316 if (schema == null || schema.trim().isEmpty()) {
317 schema = commonSchema;
319 Object value = getListResultValue(docModel, schema, field);
320 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
321 String strValue = (String) value;
322 if (strValue.trim().isEmpty() == true) {
323 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
327 item.put(field.getElement(), value);
330 commonList.addItem(item);
333 } catch (Exception e) {
334 if (logger.isDebugEnabled()) {
335 logger.debug("Caught exception ", e);
337 throw new DocumentException(e);
339 // If we got/aquired a new session then we're responsible for releasing it.
340 if (releaseRepoSession && repoSession != null) {
341 repoClient.releaseRepositorySession(ctx, repoSession);
348 // TODO - get rid of this if we can - appears to be unused.
350 public String getQProperty(String prop) throws DocumentException {
351 ServiceContext ctx = this.getServiceContext();
352 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
355 // ============= dublin core handling =======================================
358 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
359 super.fillAllParts(wrapDoc, action);
360 fillDublinCoreObject(wrapDoc);
364 * Fill dublin core object, but only if there are document handler parameters in the service
372 // TODO - Remove this?
373 // This look like it is never used in a sensible way. It just stuffs a static
374 // String that matches the service name into a bogus field.
375 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
376 DocHandlerParams.Params docHandlerParams = null;
378 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
379 } catch (Exception e) {
380 logger.warn(e.getMessage());
383 if (docHandlerParams != null) {
384 String title = docHandlerParams.getDublinCoreTitle();
385 if (Tools.isEmpty(title) == false) {
386 DocumentModel docModel = wrapDoc.getWrappedObject();
387 docModel.setPropertyValue("dublincore:title", title);
392 // ================== UTILITY METHODS ================================================
393 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
394 String setterName, String schema, String xpath) throws Exception {
395 // Object prop = docModel.getProperty(label, elementName);
396 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
397 return ReflectionMapper.callSetter(listItem, setterName, value);
400 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
401 return ReflectionMapper.callSetter(target, name, arg);
405 * @param commonListClassname
406 * is a package-qualified java classname, including inner class $ notation, such as
407 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
408 * @param includeStdFields
409 * set to true to have the method set Uri and Csid automatically, based on id param.
411 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
412 boolean includeStdFields) throws Exception {
413 // createItemForCommonList(docModel, label, id);
414 Object item = ReflectionMapper.instantiate(commonListClassname);
415 List<ListResultField> resultsFields = getListItemsArray();
416 for (ListResultField field : resultsFields) {
417 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
419 if (includeStdFields) {
420 callSimplePropertySetter(item, "setCsid", id);
421 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
428 * Subclasses should override this method if they don't want to automatically
429 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
430 * which will use introspection to create a summary list, and will find the primary
431 * field for you if specified.
433 public List createItemsList(AbstractCommonList commonList) throws Exception {
434 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
435 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
438 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
439 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
440 Class commonListClass = commonList.getClass();
441 Class[] types = new Class[] {};
443 Method m = commonListClass.getMethod(listItemMethodName, types);
444 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
445 } catch (NoSuchMethodException nsm) {
446 return new ArrayList();
451 public boolean supportsWorkflowStates() {