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.api.GregorianCalendarDateTimeUtils;
42 import org.collectionspace.services.common.api.Tools;
43 import org.collectionspace.services.common.config.ServiceConfigUtils;
44 import org.collectionspace.services.common.context.AbstractServiceContextImpl;
45 import org.collectionspace.services.common.context.ServiceContext;
46 import org.collectionspace.services.common.document.DocumentException;
47 import org.collectionspace.services.common.document.DocumentWrapper;
48 import org.collectionspace.services.common.query.QueryContext;
49 import org.collectionspace.services.common.relation.nuxeo.RelationsUtils;
50 import org.collectionspace.services.config.service.DocHandlerParams;
51 import org.collectionspace.services.config.service.ListResultField;
52 import org.collectionspace.services.jaxb.AbstractCommonList;
53 import org.collectionspace.services.nuxeo.client.java.CommonList;
54 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
55 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
57 import org.nuxeo.ecm.core.api.DocumentModel;
58 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 * Subclass DocHandlers may override this method to control exact creation of the common list.
112 * This class instantiates an AbstractCommonList from the classname returned by getDocHandlerParams().AbstractCommonListClassname.
117 public AbstractCommonList createAbstractCommonListImpl() throws Exception {
118 // String classname = this.commonList.getClass().getName();
119 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
120 String classname = ServiceConfigUtils.getDocHandlerParams(ctx).getAbstractCommonListClassname();
121 if (classname == null) {
123 "in createAbstractCommonListImpl. getDocHandlerParams().getAbstractCommonListClassname() is null");
125 classname = classname.trim();
126 return (AbstractCommonList) (ReflectionMapper.instantiate(classname));
129 /** DocHandlerBase calls this method with the CSID as id */
130 public Object createItemForCommonList(DocumentModel docModel, String label, String id) throws Exception {
131 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
132 return createItemForCommonList(ServiceConfigUtils.getDocHandlerParams(ctx).getCommonListItemClassname(),
133 docModel, label, id, true);
136 public String getSummaryFields(AbstractCommonList theCommonList) throws DocumentException {
137 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
138 return ServiceConfigUtils.getDocHandlerParams(ctx).getSummaryFields();
141 public void setListItemArrayExtended(boolean isExtended) throws DocumentException {
142 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
143 ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().setExtended(isExtended);
146 public boolean isListItemArrayExtended() throws DocumentException {
147 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
148 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().isExtended();
151 public List<ListResultField> getListItemsArray() throws DocumentException {
152 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
153 return ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsFields().getListResultField();
157 public T extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
158 throw new UnsupportedOperationException();
162 public void fillCommonPart(T objectexitObject, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
163 throw new UnsupportedOperationException();
166 protected static String getRefname(DocumentModel docModel) throws Exception {
167 String result = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
168 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
172 public static String getUpdatedAtAsString(DocumentModel docModel) throws Exception {
173 GregorianCalendar cal = (GregorianCalendar) docModel.getProperty(
174 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
175 CollectionSpaceClient.COLLECTIONSPACE_CORE_UPDATED_AT);
176 String updatedAt = GregorianCalendarDateTimeUtils.formatAsISO8601Timestamp(cal);
181 public AbstractCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
182 CommonList commonList = new CommonList();
183 String markRtSbj = null;
184 CoreSessionInterface repoSession = null;
185 RepositoryClientImpl repoClient = null;
186 boolean releaseRepoSession = false;
188 AbstractServiceContextImpl ctx = (AbstractServiceContextImpl) getServiceContext();
189 MultivaluedMap<String, String> queryParams = getServiceContext().getQueryParams();
190 markRtSbj = queryParams.getFirst(IQueryManager.MARK_RELATED_TO_CSID_AS_SUBJECT);
191 if (markRtSbj != null && markRtSbj.isEmpty())
195 if (markRtSbj != null) {
196 repoClient = (RepositoryClientImpl) this.getRepositoryClient(ctx);
197 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl) repoClient;
198 repoSession = this.getRepositorySession();
199 if (repoSession == null) {
200 repoSession = repoClient.getRepositorySession(ctx);
201 releaseRepoSession = true;
205 String commonSchema = getServiceContext().getCommonPartLabel();
206 extractPagingInfo(commonList, wrapDoc);
207 List<ListResultField> resultsFields = getListItemsArray();
208 int nFields = resultsFields.size() + NUM_STANDARD_LIST_RESULT_FIELDS;
209 int baseFields = NUM_STANDARD_LIST_RESULT_FIELDS;
210 if (markRtSbj != null) {
215 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.
216 fields[0] = STANDARD_LIST_CSID_FIELD;
217 fields[1] = STANDARD_LIST_URI_FIELD;
218 fields[2] = STANDARD_LIST_REFNAME_FIELD;
219 fields[3] = STANDARD_LIST_UPDATED_AT_FIELD;
220 fields[4] = STANDARD_LIST_WORKFLOW_FIELD;
222 if (markRtSbj != null) {
223 fields[5] = STANDARD_LIST_MARK_RT_FIELD;
226 for (int i = baseFields; i < nFields; i++) {
227 ListResultField field = resultsFields.get(i - baseFields);
228 fields[i] = field.getElement();
230 commonList.setFieldsReturned(fields);
231 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
232 HashMap<String, Object> item = new HashMap<String, Object>();
233 while (iter.hasNext()) {
234 DocumentModel docModel = iter.next();
235 String id = NuxeoUtils.getCsid(docModel);
236 item.put(STANDARD_LIST_CSID_FIELD, id);
237 if (markRtSbj != null) {
238 String relationClause = RelationsUtils.buildWhereClause(markRtSbj, null, null, id, null);
239 String whereClause = relationClause + IQueryManager.SEARCH_QUALIFIER_AND
240 + NuxeoUtils.buildWorkflowNotDeletedWhereClause();
241 QueryContext queryContext = new QueryContext(ctx, whereClause);
242 queryContext.setDocType(IRelationsManager.DOC_TYPE);
243 String query = NuxeoUtils.buildNXQLQuery(ctx, queryContext);
244 // Search for 1 relation that matches. 1 is enough to fail
246 DocumentModelList docList = repoSession.query(query, null, 1, 0, false);
247 item.put(STANDARD_LIST_MARK_RT_FIELD, docList.isEmpty() ? "false" : "true");
249 String uri = getUri(docModel);
250 item.put(STANDARD_LIST_URI_FIELD, uri);
251 item.put(STANDARD_LIST_REFNAME_FIELD, getRefname(docModel));
252 item.put(STANDARD_LIST_UPDATED_AT_FIELD, getUpdatedAtAsString(docModel));
253 item.put(STANDARD_LIST_WORKFLOW_FIELD, docModel.getCurrentLifeCycleState());
255 for (ListResultField field : resultsFields) {
256 String schema = field.getSchema();
257 if (schema == null || schema.trim().isEmpty()) {
258 schema = commonSchema;
260 Object value = getListResultValue(docModel, schema, field);
261 if (value != null && value instanceof String) { // If it is String that is either null or empty, we set our value to null
262 String strValue = (String) value;
263 if (strValue.trim().isEmpty() == true) {
264 value = null; // We found an "empty" string value, so just set the value to null so we don't return anything.
268 item.put(field.getElement(), value);
271 commonList.addItem(item);
274 } catch (Exception e) {
275 if (logger.isDebugEnabled()) {
276 logger.debug("Caught exception ", e);
278 throw new DocumentException(e);
280 // If we got/aquired a new session then we're responsible for releasing it.
281 if (releaseRepoSession && repoSession != null) {
282 repoClient.releaseRepositorySession(ctx, repoSession);
289 // TODO - get rid of this if we can - appears to be unused.
291 public String getQProperty(String prop) throws DocumentException {
292 ServiceContext ctx = this.getServiceContext();
293 return ServiceConfigUtils.getDocHandlerParams(ctx).getSchemaName() + ":" + prop;
296 // ============= dublin core handling =======================================
299 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
300 super.fillAllParts(wrapDoc, action);
301 fillDublinCoreObject(wrapDoc);
305 * Fill dublin core object, but only if there are document handler parameters in the service
313 // TODO - Remove this?
314 // This look like it is never used in a sensible way. It just stuffs a static
315 // String that matches the service name into a bogus field.
316 protected void fillDublinCoreObject(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
317 DocHandlerParams.Params docHandlerParams = null;
319 docHandlerParams = ServiceConfigUtils.getDocHandlerParams(getServiceContext());
320 } catch (Exception e) {
321 logger.warn(e.getMessage());
324 if (docHandlerParams != null) {
325 String title = docHandlerParams.getDublinCoreTitle();
326 if (Tools.isEmpty(title) == false) {
327 DocumentModel docModel = wrapDoc.getWrappedObject();
328 docModel.setPropertyValue("dublincore:title", title);
333 // ================== UTILITY METHODS ================================================
334 public static ReflectionMapper.STATUS callPropertySetterWithXPathValue(DocumentModel docModel, Object listItem,
335 String setterName, String schema, String xpath) throws Exception {
336 // Object prop = docModel.getProperty(label, elementName);
337 String value = (String) NuxeoUtils.getXPathValue(docModel, schema, xpath);
338 return ReflectionMapper.callSetter(listItem, setterName, value);
341 public static ReflectionMapper.STATUS callSimplePropertySetter(Object target, String name, Object arg) {
342 return ReflectionMapper.callSetter(target, name, arg);
346 * @param commonListClassname
347 * is a package-qualified java classname, including inner class $ notation, such as
348 * "org.collectionspace.services.objectexit.ObjectexitCommonList$ObjectexitListItem".
349 * @param includeStdFields
350 * set to true to have the method set Uri and Csid automatically, based on id param.
352 public Object createItemForCommonList(String commonListClassname, DocumentModel docModel, String schema, String id,
353 boolean includeStdFields) throws Exception {
354 // createItemForCommonList(docModel, label, id);
355 Object item = ReflectionMapper.instantiate(commonListClassname);
356 List<ListResultField> resultsFields = getListItemsArray();
357 for (ListResultField field : resultsFields) {
358 callPropertySetterWithXPathValue(docModel, item, field.getSetter(), schema, field.getXpath());
360 if (includeStdFields) {
361 callSimplePropertySetter(item, "setCsid", id);
362 callSimplePropertySetter(item, "setUri", getServiceContextPath() + id);
369 * Subclasses should override this method if they don't want to automatically
370 * call List createItemsList(AbstractCommonList commonList, String listItemMethodName)
371 * which will use introspection to create a summary list, and will find the primary
372 * field for you if specified.
374 public List createItemsList(AbstractCommonList commonList) throws Exception {
375 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
376 return createItemsList(commonList, ServiceConfigUtils.getDocHandlerParams(ctx).getListResultsItemMethodName());
379 /** e.g. createItemsList(commonList, "getObjectexitListItem" */
380 public List createItemsList(AbstractCommonList commonList, String listItemMethodName) throws Exception {
381 Class commonListClass = commonList.getClass();
382 Class[] types = new Class[] {};
384 Method m = commonListClass.getMethod(listItemMethodName, types);
385 return (List) (ReflectionMapper.fireGetMethod(m, commonList));
386 } catch (NoSuchMethodException nsm) {
387 return new ArrayList();