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|relationshipMetaType|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.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
277 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
278 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
279 RelationJAXBSchema.OBJECT_CSID));
281 relationListItem.setUri(serviceContextPath + id);
283 //Now fill in summary info for the related docs: subject and object.
284 String subjectCsid = relationListItem.getSubjectCsid();
285 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
286 RelationJAXBSchema.SUBJECT_DOCTYPE);
287 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
289 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
290 RelationJAXBSchema.SUBJECT_URI);
291 subject.setUri(subjectUri);
292 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
293 RelationJAXBSchema.SUBJECT_REFNAME);
294 subject.setRefName(subjectRefName);
295 relationListItem.setSubject(subject);
297 String objectCsid = relationListItem.getObjectCsid();
298 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
299 RelationJAXBSchema.OBJECT_DOCTYPE);
300 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
302 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
303 RelationJAXBSchema.OBJECT_URI);
304 object.setUri(objectUri);
305 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
306 RelationJAXBSchema.OBJECT_REFNAME);
307 object.setRefName(objectRefName);
308 relationListItem.setObject(object);
310 return relationListItem;
313 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
314 protected RelationsDocListItem createRelationsDocListItem(
315 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
316 ServiceBindingType sbt,
318 TenantBindingConfigReaderImpl tReader,
319 String documentType) throws Exception {
320 RelationsDocListItem item = new RelationsDocListItem();
321 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
322 item.setCsid(itemCsid);
324 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
325 if (itemDocModel != null) {
326 String itemDocType = itemDocModel.getDocumentType().getName();
327 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
328 if (Tools.isBlank(documentType)) {
329 item.setDocumentType(itemDocType);
332 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
333 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
334 String propName = "ERROR-FINDING-PROP-VALUE";
335 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
337 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
338 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
339 if (propName == null || itemDocname == null) {
341 item.setName(itemDocname);
343 } catch (Throwable t) {
344 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
345 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
347 propName = "ERROR-FINDING-PROP-VALUE";
349 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
350 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
352 if (propName == null || itemDocnumber == null) {
354 item.setNumber(itemDocnumber);
356 } catch (Throwable t) {
357 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
358 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
361 item.setError("INVALID: related object is absent");
362 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
363 //throw new Exception("INVALID: related object is absent "+itemCsid);
369 public String getQProperty(String prop) {
370 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
373 private final boolean SUBJ_DOC_MODEL = true;
374 private final boolean OBJ_DOC_MODEL = false;
376 private DocumentModel getSubjectOrObjectDocModel(
377 RepositoryInstance repoSession,
378 DocumentModel relationDocModel,
379 boolean fSubject) throws Exception {
380 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
382 // Get the document model for the object of the relation.
383 String commonPartLabel = ctx.getCommonPartLabel();
386 DocumentModel docModel = null;
387 // FIXME: Currently assumes that the object CSID is valid if present
388 // in the incoming payload.
390 csid = (String) relationDocModel.getProperty(commonPartLabel,
391 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
392 } catch (PropertyException pe) {
393 // Per CSPACE-4468, ignore any property exception here.
394 // The objectCsid and/or subjectCsid field in a relation record
395 // can now be null (missing), because a refName value can be
396 // provided as an alternate identifier.
398 if (Tools.notBlank(csid)) {
399 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
400 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
401 docModel = docWrapper.getWrappedObject();
402 } else { // if (Tools.isBlank(objectCsid)) {
404 refName = (String) relationDocModel.getProperty(commonPartLabel,
405 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
406 docModel = ResourceBase.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
407 } catch (Exception e) {
408 throw new InvalidDocumentException(
409 "Relation record must have a CSID or refName to identify the object of the relation.", e);
413 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
414 +csid+" and/or refName: "+refName );
419 private void populateSubjectOrObjectValues(
420 DocumentModel relationDocModel,
421 DocumentModel subjectOrObjectDocModel,
423 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
425 HashMap<String,Object> properties = new HashMap<String,Object>();
427 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
428 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
429 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
432 String csid = (String) subjectOrObjectDocModel.getName();
433 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
436 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
437 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
438 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
442 String common_schema = getCommonSchemaNameForDocType(doctype);
444 if(common_schema!=null) {
445 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
447 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
451 String refname = (String)
452 subjectOrObjectDocModel.getProperty(
453 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
454 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
455 properties.put((fSubject?
456 RelationJAXBSchema.SUBJECT_REFNAME
457 :RelationJAXBSchema.OBJECT_REFNAME),
459 } catch (ClientException ce) {
460 throw new RuntimeException(
461 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
464 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
465 // property values in the target document model.
467 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
468 } catch (ClientException ce) {
469 throw new RuntimeException(
470 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
475 private String getCommonSchemaNameForDocType(String docType) {
476 String common_schema = null;
478 // HACK - Use startsWith to allow for extension of schemas.
479 if(docType.startsWith("Person"))
480 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
481 else if(docType.startsWith("Organization"))
482 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
483 else if(docType.startsWith("Locationitem"))
484 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
485 else if(docType.startsWith("Taxon"))
486 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
487 else if(docType.startsWith("Placeitem"))
488 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
489 else if(docType.startsWith("Conceptitem"))
490 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
491 //else leave it null.
493 return common_schema;