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.OrgAuthorityClient;
56 import org.collectionspace.services.client.LocationAuthorityClient;
57 import org.collectionspace.services.client.TaxonomyAuthorityClient;
58 import org.collectionspace.services.client.PlaceAuthorityClient;
59 import org.collectionspace.services.client.ConceptAuthorityClient;
60 import org.collectionspace.services.client.workflow.WorkflowClient;
62 import org.collectionspace.services.config.service.ServiceBindingType;
63 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
64 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
65 import org.nuxeo.ecm.core.api.ClientException;
66 import org.nuxeo.ecm.core.api.DocumentModel;
67 import org.nuxeo.ecm.core.api.DocumentModelList;
68 import org.nuxeo.ecm.core.api.model.PropertyException;
69 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
70 import org.slf4j.Logger;
71 import org.slf4j.LoggerFactory;
74 * RelationDocumentModelHandler
76 * $LastChangedRevision: $
79 public class RelationDocumentModelHandler
80 extends RemoteDocumentModelHandlerImpl<RelationsCommon, RelationsCommonList> {
82 private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class);
84 * relation is used to stash JAXB object to use when handle is called
85 * for Action.CREATE, Action.UPDATE or Action.GET
87 private RelationsCommon relation;
89 * relationList is stashed when handle is called
92 private RelationsCommonList relationList;
94 private static final String ERROR_TERMS_IN_WORKFLOWSTATE = "Cannot modify a relationship if either end is in the workflow state: ";
97 private boolean subjectOrObjectInWorkflowState(DocumentWrapper<DocumentModel> wrapDoc, String workflowState) throws ServiceException {
98 boolean result = false;
99 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
100 String errMsg = ERROR_TERMS_IN_WORKFLOWSTATE + workflowState;
102 RepositoryInstance repoSession = this.getRepositorySession();
104 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
105 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
106 if (subjectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState) ||
107 objectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(workflowState)) {
110 } catch (Exception e) {
111 if (logger.isInfoEnabled() == true) {
112 logger.info(errMsg, e);
121 * 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,
122 * the WorkflowDocumentModelHandler class does the actual workflow transition.
124 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
126 public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
128 String workflowState = transitionDef.getDestinationState();
129 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
130 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
131 "Cannot change a relationship if either end of it is in the workflow state: " + workflowState);
136 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
137 // Merge in the data from the payload
138 super.handleCreate(wrapDoc);
140 // And take care of ensuring all the values for the relation info are correct
141 populateSubjectAndObjectValues(wrapDoc);
143 // both subject and object cannot be locked
144 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
145 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
146 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
147 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
152 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
153 // Merge in the data from the payload
154 super.handleUpdate(wrapDoc);
156 // And take care of ensuring all the values for the relation info are correct
157 populateSubjectAndObjectValues(wrapDoc);
161 public void handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
162 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
163 // both subject and object cannot be locked
164 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
165 super.handleDelete(wrapDoc);
167 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
168 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
172 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
173 // Obtain document models for the subject and object of the relation, so that
174 // we ensure we have value docType, URI info. If the docModels support refNames,
175 // we will also set those.
176 // Note that this introduces another caching problem...
177 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
178 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
179 RepositoryInstance repoSession = this.getRepositorySession();
181 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
182 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
184 // Use values from the subject and object document models to populate the
185 // relevant fields of the relation's own document model.
186 if (subjectDocModel != null) {
187 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
189 if (objectDocModel != null) {
190 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
195 public RelationsCommon getCommonPart() {
200 public void setCommonPart(RelationsCommon theRelation) {
201 this.relation = theRelation;
204 /**get associated Relation (for index/GET_ALL)
207 public RelationsCommonList getCommonPartList() {
212 public void setCommonPartList(RelationsCommonList theRelationList) {
213 this.relationList = theRelationList;
217 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
219 throw new UnsupportedOperationException();
223 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
224 throw new UnsupportedOperationException();
228 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
229 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
230 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|objectCsid|uri|csid|subject|object");
231 ServiceContext ctx = getServiceContext();
232 String serviceContextPath = getServiceContextPath();
234 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
235 String serviceName = getServiceContext().getServiceName().toLowerCase();
236 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
238 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
239 while (iter.hasNext()) {
240 DocumentModel docModel = iter.next();
241 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
242 relList.getRelationListItem().add(relListItem);
247 /** Gets the relation list item, looking up the subject and object documents, and getting summary
248 * info via the objectName and objectNumber properties in tenant-bindings.
250 * @param sbt the ServiceBindingType of Relations service
251 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
252 * @param docModel the doc model
253 * @param serviceContextPath the service context path
254 * @return the relation list item, with nested subject and object summary info.
255 * @throws Exception the exception
257 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
258 ServiceBindingType sbt,
259 TenantBindingConfigReaderImpl tReader,
260 DocumentModel docModel,
261 String serviceContextPath) throws Exception {
262 RelationListItem relationListItem = new RelationListItem();
263 String id = getCsid(docModel);
264 relationListItem.setCsid(id);
266 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
267 RelationJAXBSchema.SUBJECT_CSID));
269 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
270 RelationJAXBSchema.RELATIONSHIP_TYPE);
271 relationListItem.setRelationshipType(predicate);
272 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
273 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
274 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
276 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
277 RelationJAXBSchema.OBJECT_CSID));
279 relationListItem.setUri(serviceContextPath + id);
281 //Now fill in summary info for the related docs: subject and object.
282 String subjectCsid = relationListItem.getSubjectCsid();
283 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
284 RelationJAXBSchema.SUBJECT_DOCTYPE);
285 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
287 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
288 RelationJAXBSchema.SUBJECT_URI);
289 subject.setUri(subjectUri);
290 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
291 RelationJAXBSchema.SUBJECT_REFNAME);
292 subject.setRefName(subjectRefName);
293 relationListItem.setSubject(subject);
295 String objectCsid = relationListItem.getObjectCsid();
296 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
297 RelationJAXBSchema.OBJECT_DOCTYPE);
298 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
300 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
301 RelationJAXBSchema.OBJECT_URI);
302 object.setUri(objectUri);
303 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
304 RelationJAXBSchema.OBJECT_REFNAME);
305 object.setRefName(objectRefName);
306 relationListItem.setObject(object);
308 return relationListItem;
311 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
312 protected RelationsDocListItem createRelationsDocListItem(
313 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
314 ServiceBindingType sbt,
316 TenantBindingConfigReaderImpl tReader,
317 String documentType) throws Exception {
318 RelationsDocListItem item = new RelationsDocListItem();
319 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
320 item.setCsid(itemCsid);
322 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
323 if (itemDocModel != null) {
324 String itemDocType = itemDocModel.getDocumentType().getName();
325 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
326 if (Tools.isBlank(documentType)) {
327 item.setDocumentType(itemDocType);
330 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
331 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
332 String propName = "ERROR-FINDING-PROP-VALUE";
333 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
335 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
336 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
337 if (propName == null || itemDocname == null) {
339 item.setName(itemDocname);
341 } catch (Throwable t) {
342 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
343 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
345 propName = "ERROR-FINDING-PROP-VALUE";
347 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
348 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
350 if (propName == null || itemDocnumber == null) {
352 item.setNumber(itemDocnumber);
354 } catch (Throwable t) {
355 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
356 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
359 item.setError("INVALID: related object is absent");
360 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
361 //throw new Exception("INVALID: related object is absent "+itemCsid);
367 public String getQProperty(String prop) {
368 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
371 private final boolean SUBJ_DOC_MODEL = true;
372 private final boolean OBJ_DOC_MODEL = false;
374 private DocumentModel getSubjectOrObjectDocModel(
375 RepositoryInstance repoSession,
376 DocumentModel relationDocModel,
377 boolean fSubject) throws Exception {
378 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
380 // Get the document model for the object of the relation.
381 String commonPartLabel = ctx.getCommonPartLabel();
384 DocumentModel docModel = null;
385 // FIXME: Currently assumes that the object CSID is valid if present
386 // in the incoming payload.
388 csid = (String) relationDocModel.getProperty(commonPartLabel,
389 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
390 } catch (PropertyException pe) {
391 // Per CSPACE-4468, ignore any property exception here.
392 // The objectCsid and/or subjectCsid field in a relation record
393 // can now be null (missing), because a refName value can be
394 // provided as an alternate identifier.
396 if (Tools.notBlank(csid)) {
397 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
398 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
399 docModel = docWrapper.getWrappedObject();
400 } else { // if (Tools.isBlank(objectCsid)) {
402 refName = (String) relationDocModel.getProperty(commonPartLabel,
403 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
404 docModel = ResourceBase.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
405 } catch (Exception e) {
406 throw new InvalidDocumentException(
407 "Relation record must have a CSID or refName to identify the object of the relation.", e);
411 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
412 +csid+" and/or refName: "+refName );
417 private void populateSubjectOrObjectValues(
418 DocumentModel relationDocModel,
419 DocumentModel subjectOrObjectDocModel,
421 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
423 HashMap<String,Object> properties = new HashMap<String,Object>();
425 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
426 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
427 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
430 String csid = (String) subjectOrObjectDocModel.getName();
431 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
434 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
435 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
436 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
440 String common_schema = getCommonSchemaNameForDocType(doctype);
442 if(common_schema!=null) {
443 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
445 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
449 String refname = (String)
450 subjectOrObjectDocModel.getProperty(
451 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
452 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
453 properties.put((fSubject?
454 RelationJAXBSchema.SUBJECT_REFNAME
455 :RelationJAXBSchema.OBJECT_REFNAME),
457 } catch (ClientException ce) {
458 throw new RuntimeException(
459 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
462 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
463 // property values in the target document model.
465 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
466 } catch (ClientException ce) {
467 throw new RuntimeException(
468 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
473 private String getCommonSchemaNameForDocType(String docType) {
474 String common_schema = null;
476 // HACK - Use startsWith to allow for extension of schemas.
477 if(docType.startsWith("Person"))
478 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
479 else if(docType.startsWith("Organization"))
480 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
481 else if(docType.startsWith("Locationitem"))
482 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
483 else if(docType.startsWith("Taxon"))
484 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
485 else if(docType.startsWith("Placeitem"))
486 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
487 else if(docType.startsWith("Conceptitem"))
488 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
489 //else leave it null.
491 return common_schema;