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 RepositoryClientImpl 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 (markRtSbj != null && markRtSbj.isEmpty()) {
232 String markRtSbjOrObj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_EITHER);
233 if (markRtSbjOrObj != null && markRtSbjOrObj.isEmpty()) {
234 markRtSbjOrObj = null;
238 if (markRtSbj != null || markRtSbjOrObj != null) {
239 repoClient = (RepositoryClientImpl) this.getRepositoryClient(ctx);
240 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl) repoClient;
241 repoSession = this.getRepositorySession();
242 if (repoSession == null) {
243 repoSession = repoClient.getRepositorySession(ctx);
244 releaseRepoSession = true;
248 String commonSchema = getServiceContext().getCommonPartLabel();
249 extractPagingInfo(commonList, wrapDoc);
250 List<ListResultField> resultsFields = getListItemsArray(); // Get additional list result fields defined in the service bindings
251 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
252 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
253 if (markRtSbj != null || markRtSbjOrObj != null) {
258 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.
259 fields[0] = STANDARD_LIST_CSID_FIELD;
260 fields[1] = STANDARD_LIST_URI_FIELD;
261 fields[2] = STANDARD_LIST_REFNAME_FIELD;
262 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
263 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
265 if (markRtSbj != null || markRtSbjOrObj != null) {
266 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
269 for (int i = baseFields; i < nFields; i++) {
270 ListResultField field = resultsFields.get(i - baseFields);
271 fields[i] = field.getElement();
273 commonList.setFieldsReturned(fields);
275 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
276 HashMap<String, Object> item = new HashMap<String, Object>();
277 while (iter.hasNext()) {
278 DocumentModel docModel = iter.next();
279 String id = NuxeoUtils.getCsid(docModel);
280 item.put(STANDARD_LIST_CSID_FIELD, id);
283 // If the mark-related query param was set, check to see if the doc we're processing
284 // is related to the value specified in the mark-related query param.
286 if (markRtSbj != null || markRtSbjOrObj != null) {
287 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null, markRtSbjOrObj);
288 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
289 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
290 QueryContext queryContext = new QueryContext(ctx, whereClause);
291 queryContext.setDocType(IRelationsManager.DOC_TYPE);
292 String query = NuxeoUtils.buildNXQLQuery(queryContext);
293 // Search for 1 relation that matches. 1 is enough to fail
295 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
296 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
299 String uri = getUri(docModel);
300 item.put(STANDARD_LIST_URI_FIELD, uri);
301 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
302 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
303 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
305 for (ListResultField field : resultsFields) {
306 String schema = field.getSchema();
307 if (schema == null || schema.trim().isEmpty()) {
308 schema = commonSchema;
310 Object value = getListResultValue(docModel, schema, field);
311 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
312 String strValue = (String) value;
313 if (strValue.trim().isEmpty() == true) {
314 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
318 item.put(field.getElement(), value);
321 commonList.addItem(item);
324 } catch (Exception e) {
325 if (logger.isDebugEnabled()) {
326 logger.debug("Caught exception ", e);
328 throw new DocumentException(e);
330 // If we got/aquired a new session then we're responsible for releasing it.
331 if (releaseRepoSession && repoSession != null) {
332 repoClient.releaseRepositorySession(ctx, repoSession);
339 // TODO - get rid of this if we can - appears to be unused.
341 public String getQProperty(String prop) throws DocumentException {
342 ServiceContext ctx = this.getServiceContext();
343 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
346 // ============= dublin core handling =======================================
349 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
350 super.fillAllParts(wrapDoc, action);
351 fillDublinCoreObject(wrapDoc);
355 * Fill dublin core object, but only if there are document handler parameters in the service
363 // TODO - Remove this?
364 // This look like it is never used in a sensible way. It just stuffs a static
365 // String that matches the service name into a bogus field.
366 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
367 DocHandlerParams.Params docHandlerParams = null;
369 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
370 } catch (Exception e) {
371 logger.warn(e.getMessage());
374 if (docHandlerParams != null) {
375 String title = docHandlerParams.getDublinCoreTitle();
376 if (Tools.isEmpty(title) == false) {
377 DocumentModel docModel = wrapDoc.getWrappedObject();
378 docModel.setPropertyValue("dublincore:title", title);
383 // ================== UTILITY METHODS ================================================
384 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
385 String setterName, String schema, String xpath) throws Exception {
386 // Object prop = docModel.getProperty(label, elementName);
387 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
388 return ReflectionMapper.callSetter(listItem, setterName, value);
391 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
392 return ReflectionMapper.callSetter(target, name, arg);
396 * @param commonListClassname
397 * is a package-qualified java classname, including inner class $ notation, such as
398 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
399 * @param includeStdFields
400 * set to true to have the method set Uri and Csid automatically, based on id param.
402 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
403 boolean includeStdFields) throws Exception {
404 // createItemForCommonList(docModel, label, id);
405 Object item = ReflectionMapper.instantiate(commonListClassname);
406 List<ListResultField> resultsFields = getListItemsArray();
407 for (ListResultField field : resultsFields) {
408 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
410 if (includeStdFields) {
411 callSimplePropertySetter(item, "setCsid", id);
412 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
419 * Subclasses should override this method if they don't want to automatically
420 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
421 * which will use introspection to create a summary list, and will find the primary
422 * field for you if specified.
424 public List createItemsList(AbstractCommonList commonList) throws Exception {
425 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
426 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
429 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
430 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
431 Class commonListClass = commonList.getClass();
432 Class[] types = new Class[] {};
434 Method m = commonListClass.getMethod(listItemMethodName, types);
435 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
436 } catch (NoSuchMethodException nsm) {
437 return new ArrayList();