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.PoxPayloadIn;
39 import org.collectionspace.services.client.PoxPayloadOut;
40 import org.collectionspace.services.common.ReflectionMapper;
41 import org.collectionspace.services.common.XmlTools;
42 import org.collectionspace.services.common.api.GregorianCalendarDateTimeUtils;
43 import org.collectionspace.services.common.api.Tools;
44 import org.collectionspace.services.common.config.ServiceConfigUtils;
45 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
46 import org.collectionspace.services.common.context.ServiceContext;
47 import org.collectionspace.services.common.document.DocumentException;
48 import org.collectionspace.services.common.document.DocumentWrapper;
49 import org.collectionspace.services.common.query.QueryContext;
50 import org.collectionspace.services.common.relation.nuxeo.RelationsUtils;
51 import org.collectionspace.services.config.service.DocHandlerParams;
52 import org.collectionspace.services.config.service.ListResultField;
53 import org.collectionspace.services.jaxb.AbstractCommonList;
54 import org.collectionspace.services.nuxeo.client.java.CommonList;
55 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
56 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
57 import org.dom4j.Document;
58 import org.nuxeo.ecm.core.api.DocumentModel;
59 import org.nuxeo.ecm.core.api.DocumentModelList;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
64 * This class is generified by the marker type T,
65 * where T is expected to map to something like BlobCommon, MediaCommon, ObjectexitCommon, etc.,
66 * and so on for every JAXB-generated schema class.
69 * $LastChangedRevision: $
73 public abstract class NuxeoDocumentModelHandler<T> extends RemoteDocumentModelHandlerImpl<T, AbstractCommonList> {
76 private final Logger logger = LoggerFactory.getLogger(this.getClass());
78 private AbstractCommonList commonList;
80 protected static final int NUM_STANDARD_LIST_RESULT_FIELDS = 5;
81 protected static final String STANDARD_LIST_CSID_FIELD = "csid";
82 protected static final String STANDARD_LIST_URI_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_URI;
83 protected static final String STANDARD_LIST_REFNAME_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME;
84 protected static final String STANDARD_LIST_UPDATED_AT_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT;
85 protected static final String STANDARD_LIST_WORKFLOW_FIELD = CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE;
86 protected static final String STANDARD_LIST_MARK_RT_FIELD = "related";
89 public AbstractCommonList getCommonPartList() {
94 public void setCommonPartList(AbstractCommonList aCommonList) {
95 this.commonList = aCommonList;
101 public T getCommonPart() {
102 return (T) commonPart;
106 public void setCommonPart(T commonPart) {
107 this.commonPart = commonPart;
111 * The entity type expected from the JAX-RS Response object. By default it is of type String. Child classes
112 * can override this if they need to.
114 protected Class<String> getEntityResponseType() {
118 protected Long getRevision(PoxPayloadIn payloadIn) {
121 Document document = payloadIn.getDOMDocument();
122 String xmlRev = XmlTools.getElementValue(document, "//rev");
123 result = Long.valueOf(xmlRev);
129 * Subclass DocHandlers may override this method to control exact creation of the common list.
130 * This class instantiates an AbstractCommonList from the classname returned by getDocHandlerParams().AbstractCommonListClassname.
135 public AbstractCommonList createAbstractCommonListImpl() throws Exception {
136 // String classname = this.commonList.getClass().getName();
137 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
138 String classname = ServiceConfigUtils.getDocHandlerParams(ctx).getAbstractCommonListClassname();
139 if (classname == null) {
141 "in createAbstractCommonListImpl. getDocHandlerParams().getAbstractCommonListClassname() is null");
143 classname = classname.trim();
144 return (AbstractCommonList) (ReflectionMapper.instantiate(classname));
147 /** DocHandlerBase calls this method with the CSID as id */
148 public Object createItemForCommonList(DocumentModel docModel, String label, String id) throws Exception {
149 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
150 return createItemForCommonList(ServiceConfigUtils.getDocHandlerParams(ctx).getCommonListItemClassname(),
151 docModel, label, id, true);
154 public String getSummaryFields(AbstractCommonList theCommonList) throws DocumentException {
155 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
156 return ServiceConfigUtils.getDocHandlerParams(ctx).getSummaryFields();
159 public void setListItemArrayExtended(boolean isExtended) throws DocumentException {
160 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
161 ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().setExtended(isExtended);
164 public boolean isListItemArrayExtended() throws DocumentException {
165 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
166 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().isExtended();
169 public List<ListResultField> getListItemsArray() throws DocumentException {
170 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
171 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().getListResultField();
175 public T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
176 throw new UnsupportedOperationException();
180 public void fillCommonPart(T objectexitObject, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
181 throw new UnsupportedOperationException();
184 protected static String getRefname(DocumentModel docModel) throws Exception {
185 String result = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
186 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
190 public static String getUpdatedAtAsString(DocumentModel docModel) throws Exception {
191 GregorianCalendar cal = (GregorianCalendar) docModel.getProperty(
192 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
193 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT);
194 String updatedAt = GregorianCalendarDateTimeUtils.formatAsISO8601Timestamp(cal);
199 public AbstractCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
200 CommonList commonList = new CommonList();
201 String markRtSbj = null;
202 CoreSessionInterface repoSession = null;
203 RepositoryClientImpl repoClient = null;
204 boolean releaseRepoSession = false;
206 AbstractServiceContextImpl ctx = (AbstractServiceContextImpl) getServiceContext();
207 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
208 markRtSbj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT);
209 if (markRtSbj != null && markRtSbj.isEmpty())
213 if (markRtSbj != null) {
214 repoClient = (RepositoryClientImpl) this.getRepositoryClient(ctx);
215 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl) repoClient;
216 repoSession = this.getRepositorySession();
217 if (repoSession == null) {
218 repoSession = repoClient.getRepositorySession(ctx);
219 releaseRepoSession = true;
223 String commonSchema = getServiceContext().getCommonPartLabel();
224 extractPagingInfo(commonList, wrapDoc);
225 List<ListResultField> resultsFields = getListItemsArray();
226 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
227 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
228 if (markRtSbj != null) {
233 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.
234 fields[0] = STANDARD_LIST_CSID_FIELD;
235 fields[1] = STANDARD_LIST_URI_FIELD;
236 fields[2] = STANDARD_LIST_REFNAME_FIELD;
237 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
238 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
240 if (markRtSbj != null) {
241 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
244 for (int i = baseFields; i < nFields; i++) {
245 ListResultField field = resultsFields.get(i - baseFields);
246 fields[i] = field.getElement();
248 commonList.setFieldsReturned(fields);
249 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
250 HashMap<String, Object> item = new HashMap<String, Object>();
251 while (iter.hasNext()) {
252 DocumentModel docModel = iter.next();
253 String id = NuxeoUtils.getCsid(docModel);
254 item.put(STANDARD_LIST_CSID_FIELD, id);
255 if (markRtSbj != null) {
256 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null);
257 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
258 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
259 QueryContext queryContext = new QueryContext(ctx, whereClause);
260 queryContext.setDocType(IRelationsManager.DOC_TYPE);
261 String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
262 // Search for 1 relation that matches. 1 is enough to fail
264 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
265 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
267 String uri = getUri(docModel);
268 item.put(STANDARD_LIST_URI_FIELD, uri);
269 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
270 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
271 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
273 for (ListResultField field : resultsFields) {
274 String schema = field.getSchema();
275 if (schema == null || schema.trim().isEmpty()) {
276 schema = commonSchema;
278 Object value = getListResultValue(docModel, schema, field);
279 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
280 String strValue = (String) value;
281 if (strValue.trim().isEmpty() == true) {
282 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
286 item.put(field.getElement(), value);
289 commonList.addItem(item);
292 } catch (Exception e) {
293 if (logger.isDebugEnabled()) {
294 logger.debug("Caught exception ", e);
296 throw new DocumentException(e);
298 // If we got/aquired a new session then we're responsible for releasing it.
299 if (releaseRepoSession && repoSession != null) {
300 repoClient.releaseRepositorySession(ctx, repoSession);
307 // TODO - get rid of this if we can - appears to be unused.
309 public String getQProperty(String prop) throws DocumentException {
310 ServiceContext ctx = this.getServiceContext();
311 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
314 // ============= dublin core handling =======================================
317 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
318 super.fillAllParts(wrapDoc, action);
319 fillDublinCoreObject(wrapDoc);
323 * Fill dublin core object, but only if there are document handler parameters in the service
331 // TODO - Remove this?
332 // This look like it is never used in a sensible way. It just stuffs a static
333 // String that matches the service name into a bogus field.
334 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
335 DocHandlerParams.Params docHandlerParams = null;
337 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
338 } catch (Exception e) {
339 logger.warn(e.getMessage());
342 if (docHandlerParams != null) {
343 String title = docHandlerParams.getDublinCoreTitle();
344 if (Tools.isEmpty(title) == false) {
345 DocumentModel docModel = wrapDoc.getWrappedObject();
346 docModel.setPropertyValue("dublincore:title", title);
351 // ================== UTILITY METHODS ================================================
352 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
353 String setterName, String schema, String xpath) throws Exception {
354 // Object prop = docModel.getProperty(label, elementName);
355 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
356 return ReflectionMapper.callSetter(listItem, setterName, value);
359 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
360 return ReflectionMapper.callSetter(target, name, arg);
364 * @param commonListClassname
365 * is a package-qualified java classname, including inner class $ notation, such as
366 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
367 * @param includeStdFields
368 * set to true to have the method set Uri and Csid automatically, based on id param.
370 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
371 boolean includeStdFields) throws Exception {
372 // createItemForCommonList(docModel, label, id);
373 Object item = ReflectionMapper.instantiate(commonListClassname);
374 List<ListResultField> resultsFields = getListItemsArray();
375 for (ListResultField field : resultsFields) {
376 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
378 if (includeStdFields) {
379 callSimplePropertySetter(item, "setCsid", id);
380 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
387 * Subclasses should override this method if they don't want to automatically
388 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
389 * which will use introspection to create a summary list, and will find the primary
390 * field for you if specified.
392 public List createItemsList(AbstractCommonList commonList) throws Exception {
393 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
394 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
397 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
398 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
399 Class commonListClass = commonList.getClass();
400 Class[] types = new Class[] {};
402 Method m = commonListClass.getMethod(listItemMethodName, types);
403 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
404 } catch (NoSuchMethodException nsm) {
405 return new ArrayList();