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 String markRtSbj = null;
222 CoreSessionInterface repoSession = null;
223 RepositoryClientImpl repoClient = null;
224 boolean releaseRepoSession = false;
226 AbstractServiceContextImpl ctx = (AbstractServiceContextImpl) getServiceContext();
227 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
228 markRtSbj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT);
229 if (markRtSbj != null && markRtSbj.isEmpty())
233 if (markRtSbj != null) {
234 repoClient = (RepositoryClientImpl) this.getRepositoryClient(ctx);
235 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl) repoClient;
236 repoSession = this.getRepositorySession();
237 if (repoSession == null) {
238 repoSession = repoClient.getRepositorySession(ctx);
239 releaseRepoSession = true;
243 String commonSchema = getServiceContext().getCommonPartLabel();
244 extractPagingInfo(commonList, wrapDoc);
245 List<ListResultField> resultsFields = getListItemsArray();
246 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
247 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
248 if (markRtSbj != null) {
253 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.
254 fields[0] = STANDARD_LIST_CSID_FIELD;
255 fields[1] = STANDARD_LIST_URI_FIELD;
256 fields[2] = STANDARD_LIST_REFNAME_FIELD;
257 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
258 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
260 if (markRtSbj != null) {
261 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
264 for (int i = baseFields; i < nFields; i++) {
265 ListResultField field = resultsFields.get(i - baseFields);
266 fields[i] = field.getElement();
268 commonList.setFieldsReturned(fields);
269 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
270 HashMap<String, Object> item = new HashMap<String, Object>();
271 while (iter.hasNext()) {
272 DocumentModel docModel = iter.next();
273 String id = NuxeoUtils.getCsid(docModel);
274 item.put(STANDARD_LIST_CSID_FIELD, id);
275 if (markRtSbj != null) {
276 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null, null);
277 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
278 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
279 QueryContext queryContext = new QueryContext(ctx, whereClause);
280 queryContext.setDocType(IRelationsManager.DOC_TYPE);
281 String query = NuxeoUtils.buildNXQLQuery(queryContext);
282 // Search for 1 relation that matches. 1 is enough to fail
284 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
285 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
287 String uri = getUri(docModel);
288 item.put(STANDARD_LIST_URI_FIELD, uri);
289 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
290 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
291 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
293 for (ListResultField field : resultsFields) {
294 String schema = field.getSchema();
295 if (schema == null || schema.trim().isEmpty()) {
296 schema = commonSchema;
298 Object value = getListResultValue(docModel, schema, field);
299 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
300 String strValue = (String) value;
301 if (strValue.trim().isEmpty() == true) {
302 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
306 item.put(field.getElement(), value);
309 commonList.addItem(item);
312 } catch (Exception e) {
313 if (logger.isDebugEnabled()) {
314 logger.debug("Caught exception ", e);
316 throw new DocumentException(e);
318 // If we got/aquired a new session then we're responsible for releasing it.
319 if (releaseRepoSession && repoSession != null) {
320 repoClient.releaseRepositorySession(ctx, repoSession);
327 // TODO - get rid of this if we can - appears to be unused.
329 public String getQProperty(String prop) throws DocumentException {
330 ServiceContext ctx = this.getServiceContext();
331 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
334 // ============= dublin core handling =======================================
337 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
338 super.fillAllParts(wrapDoc, action);
339 fillDublinCoreObject(wrapDoc);
343 * Fill dublin core object, but only if there are document handler parameters in the service
351 // TODO - Remove this?
352 // This look like it is never used in a sensible way. It just stuffs a static
353 // String that matches the service name into a bogus field.
354 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
355 DocHandlerParams.Params docHandlerParams = null;
357 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
358 } catch (Exception e) {
359 logger.warn(e.getMessage());
362 if (docHandlerParams != null) {
363 String title = docHandlerParams.getDublinCoreTitle();
364 if (Tools.isEmpty(title) == false) {
365 DocumentModel docModel = wrapDoc.getWrappedObject();
366 docModel.setPropertyValue("dublincore:title", title);
371 // ================== UTILITY METHODS ================================================
372 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
373 String setterName, String schema, String xpath) throws Exception {
374 // Object prop = docModel.getProperty(label, elementName);
375 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
376 return ReflectionMapper.callSetter(listItem, setterName, value);
379 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
380 return ReflectionMapper.callSetter(target, name, arg);
384 * @param commonListClassname
385 * is a package-qualified java classname, including inner class $ notation, such as
386 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
387 * @param includeStdFields
388 * set to true to have the method set Uri and Csid automatically, based on id param.
390 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
391 boolean includeStdFields) throws Exception {
392 // createItemForCommonList(docModel, label, id);
393 Object item = ReflectionMapper.instantiate(commonListClassname);
394 List<ListResultField> resultsFields = getListItemsArray();
395 for (ListResultField field : resultsFields) {
396 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
398 if (includeStdFields) {
399 callSimplePropertySetter(item, "setCsid", id);
400 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
407 * Subclasses should override this method if they don't want to automatically
408 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
409 * which will use introspection to create a summary list, and will find the primary
410 * field for you if specified.
412 public List createItemsList(AbstractCommonList commonList) throws Exception {
413 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
414 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
417 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
418 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
419 Class commonListClass = commonList.getClass();
420 Class[] types = new Class[] {};
422 Method m = commonListClass.getMethod(listItemMethodName, types);
423 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
424 } catch (NoSuchMethodException nsm) {
425 return new ArrayList();