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.common.ReflectionMapper;
42 import org.collectionspace.services.common.XmlTools;
43 import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
44 import org.collectionspace.services.common.api.Tools;
45 import org.collectionspace.services.common.config.ServiceConfigUtils;
46 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
47 import org.collectionspace.services.common.context.ServiceContext;
48 import org.collectionspace.services.common.document.DocumentException;
49 import org.collectionspace.services.common.document.DocumentWrapper;
50 import org.collectionspace.services.common.query.QueryContext;
51 import org.collectionspace.services.common.relation.nuxeo.RelationsUtils;
52 import org.collectionspace.services.config.service.DocHandlerParams;
53 import org.collectionspace.services.config.service.ListResultField;
54 import org.collectionspace.services.jaxb.AbstractCommonList;
55 import org.collectionspace.services.nuxeo.client.java.CommonList;
56 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
57 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
58 import org.dom4j.Document;
59 import org.nuxeo.ecm.core.api.DocumentModel;
60 import org.nuxeo.ecm.core.api.DocumentModelList;
61 import org.slf4j.Logger;
62 import org.slf4j.LoggerFactory;
65 * This class is generified by the marker type T,
66 * where T is expected to map to something like BlobCommon, MediaCommon, ObjectexitCommon, etc.,
67 * and so on for every JAXB-generated schema class.
70 * $LastChangedRevision: $
74 public abstract class NuxeoDocumentModelHandler<T> extends RemoteDocumentModelHandlerImpl<T, AbstractCommonList> {
77 private final Logger logger = LoggerFactory.getLogger(this.getClass());
79 private AbstractCommonList commonList;
81 protected static final int NUM_STANDARD_LIST_RESULT_FIELDS = 5;
82 protected static final String STANDARD_LIST_CSID_FIELD = "csid";
83 protected static final String STANDARD_LIST_URI_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_URI;
84 protected static final String STANDARD_LIST_REFNAME_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME;
85 protected static final String STANDARD_LIST_UPDATED_AT_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT;
86 protected static final String STANDARD_LIST_WORKFLOW_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE;
87 protected static final String STANDARD_LIST_MARK_RT_FIELD = "related";
90 public AbstractCommonList getCommonPartList() {
95 public void setCommonPartList(AbstractCommonList aCommonList) {
96 this.commonList = aCommonList;
102 public T getCommonPart() {
103 return (T) commonPart;
107 public void setCommonPart(T commonPart) {
108 this.commonPart = commonPart;
112 * The entity type expected from the JAX-RS Response object. By default it is of type String. Child classes
113 * can override this if they need to.
115 protected Class<String> getEntityResponseType() {
119 protected Long getRevision(PoxPayload payload) {
122 Document document = payload.getDOMDocument();
123 String xmlRev = XmlTools.getElementValue(document, "//rev");
124 result = Long.valueOf(xmlRev);
129 protected List getItemList(PoxPayloadIn payloadIn) {
132 Document document = payloadIn.getDOMDocument();
133 result = XmlTools.getElementNodes(document, "//list-item");
139 * Subclass DocHandlers may override this method to control exact creation of the common list.
140 * This class instantiates an AbstractCommonList from the classname returned by getDocHandlerParams().AbstractCommonListClassname.
145 public AbstractCommonList createAbstractCommonListImpl() throws Exception {
146 // String classname = this.commonList.getClass().getName();
147 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
148 String classname = ServiceConfigUtils.getDocHandlerParams(ctx).getAbstractCommonListClassname();
149 if (classname == null) {
151 "in createAbstractCommonListImpl. getDocHandlerParams().getAbstractCommonListClassname() is null");
153 classname = classname.trim();
154 return (AbstractCommonList) (ReflectionMapper.instantiate(classname));
157 /** DocHandlerBase calls this method with the CSID as id */
158 public Object createItemForCommonList(DocumentModel docModel, String label, String id) throws Exception {
159 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
160 return createItemForCommonList(ServiceConfigUtils.getDocHandlerParams(ctx).getCommonListItemClassname(),
161 docModel, label, id, true);
164 public String getSummaryFields(AbstractCommonList theCommonList) throws DocumentException {
165 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
166 return ServiceConfigUtils.getDocHandlerParams(ctx).getSummaryFields();
169 public void setListItemArrayExtended(boolean isExtended) throws DocumentException {
170 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
171 ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().setExtended(isExtended);
174 public boolean isListItemArrayExtended() throws DocumentException {
175 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
176 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().isExtended();
179 public List<ListResultField> getListItemsArray() throws DocumentException {
180 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
181 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().getListResultField();
185 public T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
186 throw new UnsupportedOperationException();
190 public void fillCommonPart(T objectexitObject, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
191 throw new UnsupportedOperationException();
194 protected static String getRefname(DocumentModel docModel) throws Exception {
195 String result = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
196 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
200 public static String getUpdatedAtAsString(DocumentModel docModel) throws Exception {
201 GregorianCalendar cal = (GregorianCalendar) docModel.getProperty(
202 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
203 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT);
204 String updatedAt = GregorianCalendarDateTimeUtils.formatAsISO8601Timestamp(cal);
209 public AbstractCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
210 CommonList commonList = new CommonList();
211 String markRtSbj = null;
212 CoreSessionInterface repoSession = null;
213 RepositoryClientImpl repoClient = null;
214 boolean releaseRepoSession = false;
216 AbstractServiceContextImpl ctx = (AbstractServiceContextImpl) getServiceContext();
217 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
218 markRtSbj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT);
219 if (markRtSbj != null && markRtSbj.isEmpty())
223 if (markRtSbj != null) {
224 repoClient = (RepositoryClientImpl) this.getRepositoryClient(ctx);
225 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl) repoClient;
226 repoSession = this.getRepositorySession();
227 if (repoSession == null) {
228 repoSession = repoClient.getRepositorySession(ctx);
229 releaseRepoSession = true;
233 String commonSchema = getServiceContext().getCommonPartLabel();
234 extractPagingInfo(commonList, wrapDoc);
235 List<ListResultField> resultsFields = getListItemsArray();
236 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
237 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
238 if (markRtSbj != null) {
243 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.
244 fields[0] = STANDARD_LIST_CSID_FIELD;
245 fields[1] = STANDARD_LIST_URI_FIELD;
246 fields[2] = STANDARD_LIST_REFNAME_FIELD;
247 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
248 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
250 if (markRtSbj != null) {
251 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
254 for (int i = baseFields; i < nFields; i++) {
255 ListResultField field = resultsFields.get(i - baseFields);
256 fields[i] = field.getElement();
258 commonList.setFieldsReturned(fields);
259 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
260 HashMap<String, Object> item = new HashMap<String, Object>();
261 while (iter.hasNext()) {
262 DocumentModel docModel = iter.next();
263 String id = NuxeoUtils.getCsid(docModel);
264 item.put(STANDARD_LIST_CSID_FIELD, id);
265 if (markRtSbj != null) {
266 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null);
267 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
268 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
269 QueryContext queryContext = new QueryContext(ctx, whereClause);
270 queryContext.setDocType(IRelationsManager.DOC_TYPE);
271 String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
272 // Search for 1 relation that matches. 1 is enough to fail
274 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
275 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
277 String uri = getUri(docModel);
278 item.put(STANDARD_LIST_URI_FIELD, uri);
279 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
280 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
281 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
283 for (ListResultField field : resultsFields) {
284 String schema = field.getSchema();
285 if (schema == null || schema.trim().isEmpty()) {
286 schema = commonSchema;
288 Object value = getListResultValue(docModel, schema, field);
289 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
290 String strValue = (String) value;
291 if (strValue.trim().isEmpty() == true) {
292 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
296 item.put(field.getElement(), value);
299 commonList.addItem(item);
302 } catch (Exception e) {
303 if (logger.isDebugEnabled()) {
304 logger.debug("Caught exception ", e);
306 throw new DocumentException(e);
308 // If we got/aquired a new session then we're responsible for releasing it.
309 if (releaseRepoSession && repoSession != null) {
310 repoClient.releaseRepositorySession(ctx, repoSession);
317 // TODO - get rid of this if we can - appears to be unused.
319 public String getQProperty(String prop) throws DocumentException {
320 ServiceContext ctx = this.getServiceContext();
321 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
324 // ============= dublin core handling =======================================
327 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
328 super.fillAllParts(wrapDoc, action);
329 fillDublinCoreObject(wrapDoc);
333 * Fill dublin core object, but only if there are document handler parameters in the service
341 // TODO - Remove this?
342 // This look like it is never used in a sensible way. It just stuffs a static
343 // String that matches the service name into a bogus field.
344 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
345 DocHandlerParams.Params docHandlerParams = null;
347 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
348 } catch (Exception e) {
349 logger.warn(e.getMessage());
352 if (docHandlerParams != null) {
353 String title = docHandlerParams.getDublinCoreTitle();
354 if (Tools.isEmpty(title) == false) {
355 DocumentModel docModel = wrapDoc.getWrappedObject();
356 docModel.setPropertyValue("dublincore:title", title);
361 // ================== UTILITY METHODS ================================================
362 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
363 String setterName, String schema, String xpath) throws Exception {
364 // Object prop = docModel.getProperty(label, elementName);
365 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
366 return ReflectionMapper.callSetter(listItem, setterName, value);
369 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
370 return ReflectionMapper.callSetter(target, name, arg);
374 * @param commonListClassname
375 * is a package-qualified java classname, including inner class $ notation, such as
376 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
377 * @param includeStdFields
378 * set to true to have the method set Uri and Csid automatically, based on id param.
380 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
381 boolean includeStdFields) throws Exception {
382 // createItemForCommonList(docModel, label, id);
383 Object item = ReflectionMapper.instantiate(commonListClassname);
384 List<ListResultField> resultsFields = getListItemsArray();
385 for (ListResultField field : resultsFields) {
386 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
388 if (includeStdFields) {
389 callSimplePropertySetter(item, "setCsid", id);
390 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
397 * Subclasses should override this method if they don't want to automatically
398 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
399 * which will use introspection to create a summary list, and will find the primary
400 * field for you if specified.
402 public List createItemsList(AbstractCommonList commonList) throws Exception {
403 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
404 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
407 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
408 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
409 Class commonListClass = commonList.getClass();
410 Class[] types = new Class[] {};
412 Method m = commonListClass.getMethod(listItemMethodName, types);
413 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
414 } catch (NoSuchMethodException nsm) {
415 return new ArrayList();