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.relation.nuxeo;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.net.HttpURLConnection;
30 import org.collectionspace.services.client.PoxPayloadIn;
31 import org.collectionspace.services.client.PoxPayloadOut;
32 import org.collectionspace.services.common.NuxeoBasedResource;
33 import org.collectionspace.services.common.ServiceException;
34 import org.collectionspace.services.common.ServiceMain;
35 import org.collectionspace.services.common.api.Tools;
36 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
37 import org.collectionspace.services.common.context.ServiceBindingUtils;
38 import org.collectionspace.services.common.document.DocumentNotFoundException;
39 import org.collectionspace.services.common.document.DocumentWrapper;
40 import org.collectionspace.services.common.document.InvalidDocumentException;
41 import org.collectionspace.services.common.relation.RelationJAXBSchema;
42 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
43 import org.collectionspace.services.common.context.ServiceContext;
44 import org.collectionspace.services.lifecycle.TransitionDef;
45 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
46 import org.collectionspace.services.relation.RelationsCommon;
47 import org.collectionspace.services.relation.RelationsCommonList;
48 import org.collectionspace.services.relation.RelationsCommonList.RelationListItem;
49 import org.collectionspace.services.relation.RelationsDocListItem;
50 import org.collectionspace.services.client.CollectionSpaceClient;
51 import org.collectionspace.services.client.workflow.WorkflowClient;
52 import org.collectionspace.services.config.service.ServiceBindingType;
53 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
54 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
55 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
57 import org.nuxeo.ecm.core.api.ClientException;
58 import org.nuxeo.ecm.core.api.DocumentModel;
59 import org.nuxeo.ecm.core.api.DocumentModelList;
60 import org.nuxeo.ecm.core.api.model.PropertyException;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * RelationDocumentModelHandler
68 * $LastChangedRevision: $
71 public class RelationDocumentModelHandler
72 extends RemoteDocumentModelHandlerImpl<RelationsCommon, RelationsCommonList> {
74 private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class);
76 * relation is used to stash JAXB object to use when handle is called
77 * for Action.CREATE, Action.UPDATE or Action.GET
79 private RelationsCommon relation;
81 * relationList is stashed when handle is called
84 private RelationsCommonList relationList;
86 private static final String ERROR_TERMS_IN_WORKFLOWSTATE = "Cannot modify a relationship if either end is in the workflow state: ";
89 private boolean subjectOrObjectInWorkflowState(DocumentWrapper<DocumentModel> wrapDoc, String workflowState) throws ServiceException {
90 boolean result = false;
91 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
92 String errMsg = ERROR_TERMS_IN_WORKFLOWSTATE + workflowState;
94 CoreSessionInterface repoSession = this.getRepositorySession();
96 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
97 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
98 if (subjectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState) ||
99 objectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState)) {
102 } catch (Exception e) {
103 if (logger.isInfoEnabled() == true) {
104 logger.info(errMsg, e);
113 * Until we rework the RepositoryClient to handle the workflow transition (just like it does for 'create', 'get', 'update', and 'delete', this method will only check to see if the transition is allowed. Until then,
114 * the WorkflowDocumentModelHandler class does the actual workflow transition.
116 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
118 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
120 if (subjectOrObjectInWorkflowState(wrapDoc, WorkflowClient.WORKFLOWSTATE_LOCKED) == true) {
121 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
122 "Cannot change a relationship if either end of it is in the workflow state: " + WorkflowClient.WORKFLOWSTATE_LOCKED);
127 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
128 // Merge in the data from the payload
129 super.handleCreate(wrapDoc);
131 // And take care of ensuring all the values for the relation info are correct
132 populateSubjectAndObjectValues(wrapDoc);
134 // Neither the subject nor the object can be locked
135 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
136 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
137 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
138 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
143 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
144 // Merge in the data from the payload
145 super.handleUpdate(wrapDoc);
147 // And take care of ensuring all the values for the relation info are correct
148 populateSubjectAndObjectValues(wrapDoc);
152 public boolean handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
153 boolean result = true;
155 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
156 // Neither the subject nor the object can be locked
157 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
158 super.handleDelete(wrapDoc);
160 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
161 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
167 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
168 // Obtain document models for the subject and object of the relation, so that
169 // we ensure we have value docType, URI info. If the docModels support refNames,
170 // we will also set those.
171 // Note that this introduces another caching problem...
172 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
173 CoreSessionInterface repoSession = this.getRepositorySession();
175 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
176 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
178 // Use values from the subject and object document models to populate the
179 // relevant fields of the relation's own document model.
180 if (subjectDocModel != null) {
181 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
183 if (objectDocModel != null) {
184 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
189 public RelationsCommon getCommonPart() {
194 public void setCommonPart(RelationsCommon theRelation) {
195 this.relation = theRelation;
198 /**get associated Relation (for index/GET_ALL)
201 public RelationsCommonList getCommonPartList() {
206 public void setCommonPartList(RelationsCommonList theRelationList) {
207 this.relationList = theRelationList;
211 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
213 throw new UnsupportedOperationException();
217 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
218 throw new UnsupportedOperationException();
222 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
223 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
224 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|relationshipMetaType|objectCsid|uri|csid|subject|object");
225 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
226 String serviceContextPath = getServiceContextPath();
228 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
229 String serviceName = getServiceContext().getServiceName().toLowerCase();
230 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
232 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
233 while (iter.hasNext()) {
234 DocumentModel docModel = iter.next();
235 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
236 relList.getRelationListItem().add(relListItem);
241 /** Gets the relation list item, looking up the subject and object documents, and getting summary
242 * info via the objectName and objectNumber properties in tenant-bindings.
244 * @param sbt the ServiceBindingType of Relations service
245 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
246 * @param docModel the doc model
247 * @param serviceContextPath the service context path
248 * @return the relation list item, with nested subject and object summary info.
249 * @throws Exception the exception
251 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
252 ServiceBindingType sbt,
253 TenantBindingConfigReaderImpl tReader,
254 DocumentModel docModel,
255 String serviceContextPath) throws Exception {
256 RelationListItem relationListItem = new RelationListItem();
257 String id = getCsid(docModel);
258 relationListItem.setCsid(id);
260 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
261 RelationJAXBSchema.SUBJECT_CSID));
263 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
264 RelationJAXBSchema.RELATIONSHIP_TYPE);
265 relationListItem.setRelationshipType(predicate);
266 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
267 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
268 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
270 relationListItem.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
271 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
272 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
273 RelationJAXBSchema.OBJECT_CSID));
275 relationListItem.setUri(serviceContextPath + id);
277 //Now fill in summary info for the related docs: subject and object.
278 String subjectCsid = relationListItem.getSubjectCsid();
279 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
280 RelationJAXBSchema.SUBJECT_DOCTYPE);
281 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
283 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
284 RelationJAXBSchema.SUBJECT_URI);
285 subject.setUri(subjectUri);
286 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
287 RelationJAXBSchema.SUBJECT_REFNAME);
288 subject.setRefName(subjectRefName);
289 relationListItem.setSubject(subject);
291 String objectCsid = relationListItem.getObjectCsid();
292 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
293 RelationJAXBSchema.OBJECT_DOCTYPE);
294 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
296 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
297 RelationJAXBSchema.OBJECT_URI);
298 object.setUri(objectUri);
299 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
300 RelationJAXBSchema.OBJECT_REFNAME);
301 object.setRefName(objectRefName);
302 relationListItem.setObject(object);
304 return relationListItem;
307 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
308 protected RelationsDocListItem createRelationsDocListItem(
309 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
310 ServiceBindingType sbt,
312 TenantBindingConfigReaderImpl tReader,
313 String documentType) throws Exception {
314 RelationsDocListItem item = new RelationsDocListItem();
315 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
316 item.setCsid(itemCsid);
318 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
319 if (itemDocModel != null) {
320 String itemDocType = itemDocModel.getDocumentType().getName();
321 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
322 if (Tools.isBlank(documentType)) {
323 item.setDocumentType(itemDocType);
326 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
327 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
328 String propName = "ERROR-FINDING-PROP-VALUE";
329 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
331 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
332 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
333 if (propName == null || itemDocname == null) {
335 item.setName(itemDocname);
337 } catch (Throwable t) {
338 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
339 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
341 propName = "ERROR-FINDING-PROP-VALUE";
343 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
344 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
346 if (propName == null || itemDocnumber == null) {
348 item.setNumber(itemDocnumber);
350 } catch (Throwable t) {
351 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
352 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
355 item.setError("INVALID: related object is absent");
356 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
357 //throw new Exception("INVALID: related object is absent "+itemCsid);
363 public String getQProperty(String prop) {
364 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
367 private final boolean SUBJ_DOC_MODEL = true;
368 private final boolean OBJ_DOC_MODEL = false;
370 private DocumentModel getSubjectOrObjectDocModel(
371 CoreSessionInterface repoSession,
372 DocumentModel relationDocModel,
373 boolean fSubject) throws Exception {
374 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
376 // Get the document model for the object of the relation.
377 String commonPartLabel = ctx.getCommonPartLabel();
380 DocumentModel docModel = null;
381 // FIXME: Currently assumes that the object CSID is valid if present
382 // in the incoming payload.
384 csid = (String) relationDocModel.getProperty(commonPartLabel,
385 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
386 } catch (PropertyException pe) {
387 // Per CSPACE-4468, ignore any property exception here.
388 // The objectCsid and/or subjectCsid field in a relation record
389 // can now be null (missing), because a refName value can be
390 // provided as an alternate identifier.
392 if (Tools.notBlank(csid)) {
393 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)getRepositoryClient(ctx);
394 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
395 docModel = docWrapper.getWrappedObject();
396 } else { // if (Tools.isBlank(objectCsid)) {
398 refName = (String) relationDocModel.getProperty(commonPartLabel,
399 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
400 docModel = NuxeoBasedResource.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
401 } catch (Exception e) {
402 throw new InvalidDocumentException(
403 "Relation record must have a CSID or refName to identify the object of the relation.", e);
407 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
408 +csid+" and/or refName: "+refName );
413 private void populateSubjectOrObjectValues(
414 DocumentModel relationDocModel,
415 DocumentModel subjectOrObjectDocModel,
417 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
419 HashMap<String,Object> properties = new HashMap<String,Object>();
421 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
422 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
423 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
426 String csid = (String) subjectOrObjectDocModel.getName();
427 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
430 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
431 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
432 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
436 String common_schema = getCommonSchemaNameForDocType(doctype);
438 if(common_schema!=null) {
439 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
441 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
445 String refname = (String)
446 subjectOrObjectDocModel.getProperty(
447 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
448 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
449 properties.put((fSubject?
450 RelationJAXBSchema.SUBJECT_REFNAME
451 :RelationJAXBSchema.OBJECT_REFNAME),
453 } catch (ClientException ce) {
454 throw new RuntimeException(
455 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
458 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
459 // property values in the target document model.
461 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
462 } catch (ClientException ce) {
463 throw new RuntimeException(
464 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
469 private String getCommonSchemaNameForDocType(String docType) {
470 String common_schema = null;
472 // HACK - Use startsWith to allow for extension of schemas.
473 if(docType.startsWith("Person"))
474 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
475 else if(docType.startsWith("Citation"))
476 common_schema = CitationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
477 else if(docType.startsWith("Organization"))
478 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
479 else if(docType.startsWith("Locationitem"))
480 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
481 else if(docType.startsWith("Taxon"))
482 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
483 else if(docType.startsWith("Placeitem"))
484 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
485 else if(docType.startsWith("Conceptitem"))
486 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
487 //else leave it null.
489 return common_schema;