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 void handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
153 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
154 // Neither the subject nor the object can be locked
155 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
156 super.handleDelete(wrapDoc);
158 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
159 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
163 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
164 // Obtain document models for the subject and object of the relation, so that
165 // we ensure we have value docType, URI info. If the docModels support refNames,
166 // we will also set those.
167 // Note that this introduces another caching problem...
168 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
169 CoreSessionInterface repoSession = this.getRepositorySession();
171 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
172 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
174 // Use values from the subject and object document models to populate the
175 // relevant fields of the relation's own document model.
176 if (subjectDocModel != null) {
177 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
179 if (objectDocModel != null) {
180 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
185 public RelationsCommon getCommonPart() {
190 public void setCommonPart(RelationsCommon theRelation) {
191 this.relation = theRelation;
194 /**get associated Relation (for index/GET_ALL)
197 public RelationsCommonList getCommonPartList() {
202 public void setCommonPartList(RelationsCommonList theRelationList) {
203 this.relationList = theRelationList;
207 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
209 throw new UnsupportedOperationException();
213 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
214 throw new UnsupportedOperationException();
218 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
219 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
220 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|relationshipMetaType|objectCsid|uri|csid|subject|object");
221 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
222 String serviceContextPath = getServiceContextPath();
224 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
225 String serviceName = getServiceContext().getServiceName().toLowerCase();
226 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
228 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
229 while (iter.hasNext()) {
230 DocumentModel docModel = iter.next();
231 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
232 relList.getRelationListItem().add(relListItem);
237 /** Gets the relation list item, looking up the subject and object documents, and getting summary
238 * info via the objectName and objectNumber properties in tenant-bindings.
240 * @param sbt the ServiceBindingType of Relations service
241 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
242 * @param docModel the doc model
243 * @param serviceContextPath the service context path
244 * @return the relation list item, with nested subject and object summary info.
245 * @throws Exception the exception
247 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
248 ServiceBindingType sbt,
249 TenantBindingConfigReaderImpl tReader,
250 DocumentModel docModel,
251 String serviceContextPath) throws Exception {
252 RelationListItem relationListItem = new RelationListItem();
253 String id = getCsid(docModel);
254 relationListItem.setCsid(id);
256 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
257 RelationJAXBSchema.SUBJECT_CSID));
259 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
260 RelationJAXBSchema.RELATIONSHIP_TYPE);
261 relationListItem.setRelationshipType(predicate);
262 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
263 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
264 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
266 relationListItem.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
267 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
268 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
269 RelationJAXBSchema.OBJECT_CSID));
271 relationListItem.setUri(serviceContextPath + id);
273 //Now fill in summary info for the related docs: subject and object.
274 String subjectCsid = relationListItem.getSubjectCsid();
275 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
276 RelationJAXBSchema.SUBJECT_DOCTYPE);
277 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
279 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
280 RelationJAXBSchema.SUBJECT_URI);
281 subject.setUri(subjectUri);
282 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
283 RelationJAXBSchema.SUBJECT_REFNAME);
284 subject.setRefName(subjectRefName);
285 relationListItem.setSubject(subject);
287 String objectCsid = relationListItem.getObjectCsid();
288 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
289 RelationJAXBSchema.OBJECT_DOCTYPE);
290 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
292 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
293 RelationJAXBSchema.OBJECT_URI);
294 object.setUri(objectUri);
295 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
296 RelationJAXBSchema.OBJECT_REFNAME);
297 object.setRefName(objectRefName);
298 relationListItem.setObject(object);
300 return relationListItem;
303 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
304 protected RelationsDocListItem createRelationsDocListItem(
305 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
306 ServiceBindingType sbt,
308 TenantBindingConfigReaderImpl tReader,
309 String documentType) throws Exception {
310 RelationsDocListItem item = new RelationsDocListItem();
311 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
312 item.setCsid(itemCsid);
314 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
315 if (itemDocModel != null) {
316 String itemDocType = itemDocModel.getDocumentType().getName();
317 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
318 if (Tools.isBlank(documentType)) {
319 item.setDocumentType(itemDocType);
322 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
323 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
324 String propName = "ERROR-FINDING-PROP-VALUE";
325 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
327 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
328 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
329 if (propName == null || itemDocname == null) {
331 item.setName(itemDocname);
333 } catch (Throwable t) {
334 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
335 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
337 propName = "ERROR-FINDING-PROP-VALUE";
339 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
340 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
342 if (propName == null || itemDocnumber == null) {
344 item.setNumber(itemDocnumber);
346 } catch (Throwable t) {
347 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
348 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
351 item.setError("INVALID: related object is absent");
352 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
353 //throw new Exception("INVALID: related object is absent "+itemCsid);
359 public String getQProperty(String prop) {
360 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
363 private final boolean SUBJ_DOC_MODEL = true;
364 private final boolean OBJ_DOC_MODEL = false;
366 private DocumentModel getSubjectOrObjectDocModel(
367 CoreSessionInterface repoSession,
368 DocumentModel relationDocModel,
369 boolean fSubject) throws Exception {
370 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
372 // Get the document model for the object of the relation.
373 String commonPartLabel = ctx.getCommonPartLabel();
376 DocumentModel docModel = null;
377 // FIXME: Currently assumes that the object CSID is valid if present
378 // in the incoming payload.
380 csid = (String) relationDocModel.getProperty(commonPartLabel,
381 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
382 } catch (PropertyException pe) {
383 // Per CSPACE-4468, ignore any property exception here.
384 // The objectCsid and/or subjectCsid field in a relation record
385 // can now be null (missing), because a refName value can be
386 // provided as an alternate identifier.
388 if (Tools.notBlank(csid)) {
389 RepositoryClientImpl nuxeoRepoClient = (RepositoryClientImpl)getRepositoryClient(ctx);
390 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
391 docModel = docWrapper.getWrappedObject();
392 } else { // if (Tools.isBlank(objectCsid)) {
394 refName = (String) relationDocModel.getProperty(commonPartLabel,
395 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
396 docModel = NuxeoBasedResource.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
397 } catch (Exception e) {
398 throw new InvalidDocumentException(
399 "Relation record must have a CSID or refName to identify the object of the relation.", e);
403 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
404 +csid+" and/or refName: "+refName );
409 private void populateSubjectOrObjectValues(
410 DocumentModel relationDocModel,
411 DocumentModel subjectOrObjectDocModel,
413 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
415 HashMap<String,Object> properties = new HashMap<String,Object>();
417 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
418 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
419 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
422 String csid = (String) subjectOrObjectDocModel.getName();
423 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
426 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
427 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
428 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
432 String common_schema = getCommonSchemaNameForDocType(doctype);
434 if(common_schema!=null) {
435 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
437 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
441 String refname = (String)
442 subjectOrObjectDocModel.getProperty(
443 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
444 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
445 properties.put((fSubject?
446 RelationJAXBSchema.SUBJECT_REFNAME
447 :RelationJAXBSchema.OBJECT_REFNAME),
449 } catch (ClientException ce) {
450 throw new RuntimeException(
451 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
454 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
455 // property values in the target document model.
457 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
458 } catch (ClientException ce) {
459 throw new RuntimeException(
460 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
465 private String getCommonSchemaNameForDocType(String docType) {
466 String common_schema = null;
468 // HACK - Use startsWith to allow for extension of schemas.
469 if(docType.startsWith("Person"))
470 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
471 else if(docType.startsWith("Citation"))
472 common_schema = CitationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
473 else if(docType.startsWith("Organization"))
474 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
475 else if(docType.startsWith("Locationitem"))
476 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
477 else if(docType.startsWith("Taxon"))
478 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
479 else if(docType.startsWith("Placeitem"))
480 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
481 else if(docType.startsWith("Conceptitem"))
482 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
483 //else leave it null.
485 return common_schema;