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();
251 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
252 int baseFields = 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);
274 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
275 HashMap<String, Object> item = new HashMap<String, Object>();
276 while (iter.hasNext()) {
277 DocumentModel docModel = iter.next();
278 String id = NuxeoUtils.getCsid(docModel);
279 item.put(STANDARD_LIST_CSID_FIELD, id);
280 if (markRtSbj != null || markRtSbjOrObj != null) {
281 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null, markRtSbjOrObj);
282 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
283 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
284 QueryContext queryContext = new QueryContext(ctx, whereClause);
285 queryContext.setDocType(IRelationsManager.DOC_TYPE);
286 String query = NuxeoUtils.buildNXQLQuery(queryContext);
287 // Search for 1 relation that matches. 1 is enough to fail
289 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
290 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
292 String uri = getUri(docModel);
293 item.put(STANDARD_LIST_URI_FIELD, uri);
294 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
295 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
296 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
298 for (ListResultField field : resultsFields) {
299 String schema = field.getSchema();
300 if (schema == null || schema.trim().isEmpty()) {
301 schema = commonSchema;
303 Object value = getListResultValue(docModel, schema, field);
304 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
305 String strValue = (String) value;
306 if (strValue.trim().isEmpty() == true) {
307 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
311 item.put(field.getElement(), value);
314 commonList.addItem(item);
317 } catch (Exception e) {
318 if (logger.isDebugEnabled()) {
319 logger.debug("Caught exception ", e);
321 throw new DocumentException(e);
323 // If we got/aquired a new session then we're responsible for releasing it.
324 if (releaseRepoSession && repoSession != null) {
325 repoClient.releaseRepositorySession(ctx, repoSession);
332 // TODO - get rid of this if we can - appears to be unused.
334 public String getQProperty(String prop) throws DocumentException {
335 ServiceContext ctx = this.getServiceContext();
336 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
339 // ============= dublin core handling =======================================
342 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
343 super.fillAllParts(wrapDoc, action);
344 fillDublinCoreObject(wrapDoc);
348 * Fill dublin core object, but only if there are document handler parameters in the service
356 // TODO - Remove this?
357 // This look like it is never used in a sensible way. It just stuffs a static
358 // String that matches the service name into a bogus field.
359 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
360 DocHandlerParams.Params docHandlerParams = null;
362 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
363 } catch (Exception e) {
364 logger.warn(e.getMessage());
367 if (docHandlerParams != null) {
368 String title = docHandlerParams.getDublinCoreTitle();
369 if (Tools.isEmpty(title) == false) {
370 DocumentModel docModel = wrapDoc.getWrappedObject();
371 docModel.setPropertyValue("dublincore:title", title);
376 // ================== UTILITY METHODS ================================================
377 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
378 String setterName, String schema, String xpath) throws Exception {
379 // Object prop = docModel.getProperty(label, elementName);
380 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
381 return ReflectionMapper.callSetter(listItem, setterName, value);
384 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
385 return ReflectionMapper.callSetter(target, name, arg);
389 * @param commonListClassname
390 * is a package-qualified java classname, including inner class $ notation, such as
391 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
392 * @param includeStdFields
393 * set to true to have the method set Uri and Csid automatically, based on id param.
395 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
396 boolean includeStdFields) throws Exception {
397 // createItemForCommonList(docModel, label, id);
398 Object item = ReflectionMapper.instantiate(commonListClassname);
399 List<ListResultField> resultsFields = getListItemsArray();
400 for (ListResultField field : resultsFields) {
401 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
403 if (includeStdFields) {
404 callSimplePropertySetter(item, "setCsid", id);
405 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
412 * Subclasses should override this method if they don't want to automatically
413 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
414 * which will use introspection to create a summary list, and will find the primary
415 * field for you if specified.
417 public List createItemsList(AbstractCommonList commonList) throws Exception {
418 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
419 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
422 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
423 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
424 Class commonListClass = commonList.getClass();
425 Class[] types = new Class[] {};
427 Method m = commonListClass.getMethod(listItemMethodName, types);
428 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
429 } catch (NoSuchMethodException nsm) {
430 return new ArrayList();