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.ResourceBase;
33 import org.collectionspace.services.common.ServiceException;
34 import org.collectionspace.services.common.ServiceMain;
35 import org.collectionspace.services.common.api.RefName;
36 import org.collectionspace.services.common.api.Tools;
37 import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
38 import org.collectionspace.services.common.context.ServiceBindingUtils;
39 import org.collectionspace.services.common.document.DocumentNotFoundException;
40 import org.collectionspace.services.common.document.DocumentWrapper;
41 import org.collectionspace.services.common.document.InvalidDocumentException;
42 import org.collectionspace.services.common.relation.RelationJAXBSchema;
43 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
44 import org.collectionspace.services.common.context.ServiceContext;
45 import org.collectionspace.services.lifecycle.TransitionDef;
46 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
47 import org.collectionspace.services.relation.RelationsCommon;
48 import org.collectionspace.services.relation.RelationsCommonList;
49 import org.collectionspace.services.relation.RelationsCommonList.RelationListItem;
50 import org.collectionspace.services.relation.RelationsDocListItem;
53 import org.collectionspace.services.client.CollectionSpaceClient;
54 import org.collectionspace.services.client.PersonAuthorityClient;
55 import org.collectionspace.services.client.CitationAuthorityClient;
56 import org.collectionspace.services.client.OrgAuthorityClient;
57 import org.collectionspace.services.client.LocationAuthorityClient;
58 import org.collectionspace.services.client.TaxonomyAuthorityClient;
59 import org.collectionspace.services.client.PlaceAuthorityClient;
60 import org.collectionspace.services.client.WorkAuthorityClient;
61 import org.collectionspace.services.client.ConceptAuthorityClient;
62 import org.collectionspace.services.client.workflow.WorkflowClient;
63 import org.collectionspace.services.config.service.ServiceBindingType;
64 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
65 import org.collectionspace.services.nuxeo.client.java.RepositoryInstanceInterface;
66 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
67 import org.nuxeo.ecm.core.api.ClientException;
68 import org.nuxeo.ecm.core.api.DocumentModel;
69 import org.nuxeo.ecm.core.api.DocumentModelList;
70 import org.nuxeo.ecm.core.api.model.PropertyException;
71 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
72 import org.slf4j.Logger;
73 import org.slf4j.LoggerFactory;
76 * RelationDocumentModelHandler
78 * $LastChangedRevision: $
81 public class RelationDocumentModelHandler
82 extends RemoteDocumentModelHandlerImpl<RelationsCommon, RelationsCommonList> {
84 private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class);
86 * relation is used to stash JAXB object to use when handle is called
87 * for Action.CREATE, Action.UPDATE or Action.GET
89 private RelationsCommon relation;
91 * relationList is stashed when handle is called
94 private RelationsCommonList relationList;
96 private static final String ERROR_TERMS_IN_WORKFLOWSTATE = "Cannot modify a relationship if either end is in the workflow state: ";
99 private boolean subjectOrObjectInWorkflowState(DocumentWrapper<DocumentModel> wrapDoc, String workflowState) throws ServiceException {
100 boolean result = false;
101 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
102 String errMsg = ERROR_TERMS_IN_WORKFLOWSTATE + workflowState;
104 RepositoryInstanceInterface repoSession = this.getRepositorySession();
106 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
107 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
108 if (subjectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState) ||
109 objectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState)) {
112 } catch (Exception e) {
113 if (logger.isInfoEnabled() == true) {
114 logger.info(errMsg, e);
123 * 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,
124 * the WorkflowDocumentModelHandler class does the actual workflow transition.
126 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
128 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
130 String workflowState = transitionDef.getDestinationState();
131 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
132 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
133 "Cannot change a relationship if either end of it is in the workflow state: " + workflowState);
138 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
139 // Merge in the data from the payload
140 super.handleCreate(wrapDoc);
142 // And take care of ensuring all the values for the relation info are correct
143 populateSubjectAndObjectValues(wrapDoc);
145 // both subject and object cannot be locked
146 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
147 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
148 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
149 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
154 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
155 // Merge in the data from the payload
156 super.handleUpdate(wrapDoc);
158 // And take care of ensuring all the values for the relation info are correct
159 populateSubjectAndObjectValues(wrapDoc);
163 public void handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
164 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
165 // both subject and object cannot be locked
166 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
167 super.handleDelete(wrapDoc);
169 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
170 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
174 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
175 // Obtain document models for the subject and object of the relation, so that
176 // we ensure we have value docType, URI info. If the docModels support refNames,
177 // we will also set those.
178 // Note that this introduces another caching problem...
179 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
180 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
181 RepositoryInstanceInterface repoSession = this.getRepositorySession();
183 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
184 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
186 // Use values from the subject and object document models to populate the
187 // relevant fields of the relation's own document model.
188 if (subjectDocModel != null) {
189 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
191 if (objectDocModel != null) {
192 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
197 public RelationsCommon getCommonPart() {
202 public void setCommonPart(RelationsCommon theRelation) {
203 this.relation = theRelation;
206 /**get associated Relation (for index/GET_ALL)
209 public RelationsCommonList getCommonPartList() {
214 public void setCommonPartList(RelationsCommonList theRelationList) {
215 this.relationList = theRelationList;
219 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
221 throw new UnsupportedOperationException();
225 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
226 throw new UnsupportedOperationException();
230 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
231 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
232 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|relationshipMetaType|objectCsid|uri|csid|subject|object");
233 ServiceContext ctx = getServiceContext();
234 String serviceContextPath = getServiceContextPath();
236 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
237 String serviceName = getServiceContext().getServiceName().toLowerCase();
238 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
240 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
241 while (iter.hasNext()) {
242 DocumentModel docModel = iter.next();
243 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
244 relList.getRelationListItem().add(relListItem);
249 /** Gets the relation list item, looking up the subject and object documents, and getting summary
250 * info via the objectName and objectNumber properties in tenant-bindings.
252 * @param sbt the ServiceBindingType of Relations service
253 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
254 * @param docModel the doc model
255 * @param serviceContextPath the service context path
256 * @return the relation list item, with nested subject and object summary info.
257 * @throws Exception the exception
259 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
260 ServiceBindingType sbt,
261 TenantBindingConfigReaderImpl tReader,
262 DocumentModel docModel,
263 String serviceContextPath) throws Exception {
264 RelationListItem relationListItem = new RelationListItem();
265 String id = getCsid(docModel);
266 relationListItem.setCsid(id);
268 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
269 RelationJAXBSchema.SUBJECT_CSID));
271 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
272 RelationJAXBSchema.RELATIONSHIP_TYPE);
273 relationListItem.setRelationshipType(predicate);
274 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
275 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
276 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
278 relationListItem.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
279 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
280 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
281 RelationJAXBSchema.OBJECT_CSID));
283 relationListItem.setUri(serviceContextPath + id);
285 //Now fill in summary info for the related docs: subject and object.
286 String subjectCsid = relationListItem.getSubjectCsid();
287 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
288 RelationJAXBSchema.SUBJECT_DOCTYPE);
289 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
291 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
292 RelationJAXBSchema.SUBJECT_URI);
293 subject.setUri(subjectUri);
294 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
295 RelationJAXBSchema.SUBJECT_REFNAME);
296 subject.setRefName(subjectRefName);
297 relationListItem.setSubject(subject);
299 String objectCsid = relationListItem.getObjectCsid();
300 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
301 RelationJAXBSchema.OBJECT_DOCTYPE);
302 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
304 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
305 RelationJAXBSchema.OBJECT_URI);
306 object.setUri(objectUri);
307 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
308 RelationJAXBSchema.OBJECT_REFNAME);
309 object.setRefName(objectRefName);
310 relationListItem.setObject(object);
312 return relationListItem;
315 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
316 protected RelationsDocListItem createRelationsDocListItem(
317 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
318 ServiceBindingType sbt,
320 TenantBindingConfigReaderImpl tReader,
321 String documentType) throws Exception {
322 RelationsDocListItem item = new RelationsDocListItem();
323 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
324 item.setCsid(itemCsid);
326 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
327 if (itemDocModel != null) {
328 String itemDocType = itemDocModel.getDocumentType().getName();
329 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
330 if (Tools.isBlank(documentType)) {
331 item.setDocumentType(itemDocType);
334 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
335 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
336 String propName = "ERROR-FINDING-PROP-VALUE";
337 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
339 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
340 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
341 if (propName == null || itemDocname == null) {
343 item.setName(itemDocname);
345 } catch (Throwable t) {
346 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
347 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
349 propName = "ERROR-FINDING-PROP-VALUE";
351 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
352 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
354 if (propName == null || itemDocnumber == null) {
356 item.setNumber(itemDocnumber);
358 } catch (Throwable t) {
359 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
360 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
363 item.setError("INVALID: related object is absent");
364 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
365 //throw new Exception("INVALID: related object is absent "+itemCsid);
371 public String getQProperty(String prop) {
372 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
375 private final boolean SUBJ_DOC_MODEL = true;
376 private final boolean OBJ_DOC_MODEL = false;
378 private DocumentModel getSubjectOrObjectDocModel(
379 RepositoryInstanceInterface repoSession,
380 DocumentModel relationDocModel,
381 boolean fSubject) throws Exception {
382 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
384 // Get the document model for the object of the relation.
385 String commonPartLabel = ctx.getCommonPartLabel();
388 DocumentModel docModel = null;
389 // FIXME: Currently assumes that the object CSID is valid if present
390 // in the incoming payload.
392 csid = (String) relationDocModel.getProperty(commonPartLabel,
393 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
394 } catch (PropertyException pe) {
395 // Per CSPACE-4468, ignore any property exception here.
396 // The objectCsid and/or subjectCsid field in a relation record
397 // can now be null (missing), because a refName value can be
398 // provided as an alternate identifier.
400 if (Tools.notBlank(csid)) {
401 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
402 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
403 docModel = docWrapper.getWrappedObject();
404 } else { // if (Tools.isBlank(objectCsid)) {
406 refName = (String) relationDocModel.getProperty(commonPartLabel,
407 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
408 docModel = ResourceBase.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
409 } catch (Exception e) {
410 throw new InvalidDocumentException(
411 "Relation record must have a CSID or refName to identify the object of the relation.", e);
415 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
416 +csid+" and/or refName: "+refName );
421 private void populateSubjectOrObjectValues(
422 DocumentModel relationDocModel,
423 DocumentModel subjectOrObjectDocModel,
425 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
427 HashMap<String,Object> properties = new HashMap<String,Object>();
429 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
430 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
431 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
434 String csid = (String) subjectOrObjectDocModel.getName();
435 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
438 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
439 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
440 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
444 String common_schema = getCommonSchemaNameForDocType(doctype);
446 if(common_schema!=null) {
447 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
449 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
453 String refname = (String)
454 subjectOrObjectDocModel.getProperty(
455 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
456 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
457 properties.put((fSubject?
458 RelationJAXBSchema.SUBJECT_REFNAME
459 :RelationJAXBSchema.OBJECT_REFNAME),
461 } catch (ClientException ce) {
462 throw new RuntimeException(
463 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
466 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
467 // property values in the target document model.
469 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
470 } catch (ClientException ce) {
471 throw new RuntimeException(
472 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
477 private String getCommonSchemaNameForDocType(String docType) {
478 String common_schema = null;
480 // HACK - Use startsWith to allow for extension of schemas.
481 if(docType.startsWith("Person"))
482 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
483 else if(docType.startsWith("Citation"))
484 common_schema = CitationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
485 else if(docType.startsWith("Organization"))
486 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
487 else if(docType.startsWith("Locationitem"))
488 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
489 else if(docType.startsWith("Taxon"))
490 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
491 else if(docType.startsWith("Placeitem"))
492 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
493 else if(docType.startsWith("Conceptitem"))
494 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
495 //else leave it null.
497 return common_schema;