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.RepositoryJavaClientImpl;
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(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
120 String workflowState = transitionDef.getDestinationState();
121 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
122 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
123 "Cannot change a relationship if either end of it is in the workflow state: " + workflowState);
128 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
129 // Merge in the data from the payload
130 super.handleCreate(wrapDoc);
132 // And take care of ensuring all the values for the relation info are correct
133 populateSubjectAndObjectValues(wrapDoc);
135 // both subject and object cannot be locked
136 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
137 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
138 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
139 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
144 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
145 // Merge in the data from the payload
146 super.handleUpdate(wrapDoc);
148 // And take care of ensuring all the values for the relation info are correct
149 populateSubjectAndObjectValues(wrapDoc);
153 public void handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
154 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
155 // both subject and object cannot be locked
156 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
157 super.handleDelete(wrapDoc);
159 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
160 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
164 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
165 // Obtain document models for the subject and object of the relation, so that
166 // we ensure we have value docType, URI info. If the docModels support refNames,
167 // we will also set those.
168 // Note that this introduces another caching problem...
169 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
170 CoreSessionInterface repoSession = this.getRepositorySession();
172 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
173 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
175 // Use values from the subject and object document models to populate the
176 // relevant fields of the relation's own document model.
177 if (subjectDocModel != null) {
178 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
180 if (objectDocModel != null) {
181 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
186 public RelationsCommon getCommonPart() {
191 public void setCommonPart(RelationsCommon theRelation) {
192 this.relation = theRelation;
195 /**get associated Relation (for index/GET_ALL)
198 public RelationsCommonList getCommonPartList() {
203 public void setCommonPartList(RelationsCommonList theRelationList) {
204 this.relationList = theRelationList;
208 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
210 throw new UnsupportedOperationException();
214 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
215 throw new UnsupportedOperationException();
219 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
220 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
221 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|relationshipMetaType|objectCsid|uri|csid|subject|object");
222 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
223 String serviceContextPath = getServiceContextPath();
225 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
226 String serviceName = getServiceContext().getServiceName().toLowerCase();
227 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
229 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
230 while (iter.hasNext()) {
231 DocumentModel docModel = iter.next();
232 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
233 relList.getRelationListItem().add(relListItem);
238 /** Gets the relation list item, looking up the subject and object documents, and getting summary
239 * info via the objectName and objectNumber properties in tenant-bindings.
241 * @param sbt the ServiceBindingType of Relations service
242 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
243 * @param docModel the doc model
244 * @param serviceContextPath the service context path
245 * @return the relation list item, with nested subject and object summary info.
246 * @throws Exception the exception
248 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
249 ServiceBindingType sbt,
250 TenantBindingConfigReaderImpl tReader,
251 DocumentModel docModel,
252 String serviceContextPath) throws Exception {
253 RelationListItem relationListItem = new RelationListItem();
254 String id = getCsid(docModel);
255 relationListItem.setCsid(id);
257 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
258 RelationJAXBSchema.SUBJECT_CSID));
260 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
261 RelationJAXBSchema.RELATIONSHIP_TYPE);
262 relationListItem.setRelationshipType(predicate);
263 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
264 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
265 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
267 relationListItem.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
268 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
269 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
270 RelationJAXBSchema.OBJECT_CSID));
272 relationListItem.setUri(serviceContextPath + id);
274 //Now fill in summary info for the related docs: subject and object.
275 String subjectCsid = relationListItem.getSubjectCsid();
276 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
277 RelationJAXBSchema.SUBJECT_DOCTYPE);
278 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
280 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
281 RelationJAXBSchema.SUBJECT_URI);
282 subject.setUri(subjectUri);
283 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
284 RelationJAXBSchema.SUBJECT_REFNAME);
285 subject.setRefName(subjectRefName);
286 relationListItem.setSubject(subject);
288 String objectCsid = relationListItem.getObjectCsid();
289 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
290 RelationJAXBSchema.OBJECT_DOCTYPE);
291 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
293 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
294 RelationJAXBSchema.OBJECT_URI);
295 object.setUri(objectUri);
296 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
297 RelationJAXBSchema.OBJECT_REFNAME);
298 object.setRefName(objectRefName);
299 relationListItem.setObject(object);
301 return relationListItem;
304 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
305 protected RelationsDocListItem createRelationsDocListItem(
306 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
307 ServiceBindingType sbt,
309 TenantBindingConfigReaderImpl tReader,
310 String documentType) throws Exception {
311 RelationsDocListItem item = new RelationsDocListItem();
312 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
313 item.setCsid(itemCsid);
315 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
316 if (itemDocModel != null) {
317 String itemDocType = itemDocModel.getDocumentType().getName();
318 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
319 if (Tools.isBlank(documentType)) {
320 item.setDocumentType(itemDocType);
323 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
324 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
325 String propName = "ERROR-FINDING-PROP-VALUE";
326 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
328 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
329 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
330 if (propName == null || itemDocname == null) {
332 item.setName(itemDocname);
334 } catch (Throwable t) {
335 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
336 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
338 propName = "ERROR-FINDING-PROP-VALUE";
340 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
341 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
343 if (propName == null || itemDocnumber == null) {
345 item.setNumber(itemDocnumber);
347 } catch (Throwable t) {
348 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
349 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
352 item.setError("INVALID: related object is absent");
353 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
354 //throw new Exception("INVALID: related object is absent "+itemCsid);
360 public String getQProperty(String prop) {
361 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
364 private final boolean SUBJ_DOC_MODEL = true;
365 private final boolean OBJ_DOC_MODEL = false;
367 private DocumentModel getSubjectOrObjectDocModel(
368 CoreSessionInterface repoSession,
369 DocumentModel relationDocModel,
370 boolean fSubject) throws Exception {
371 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
373 // Get the document model for the object of the relation.
374 String commonPartLabel = ctx.getCommonPartLabel();
377 DocumentModel docModel = null;
378 // FIXME: Currently assumes that the object CSID is valid if present
379 // in the incoming payload.
381 csid = (String) relationDocModel.getProperty(commonPartLabel,
382 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
383 } catch (PropertyException pe) {
384 // Per CSPACE-4468, ignore any property exception here.
385 // The objectCsid and/or subjectCsid field in a relation record
386 // can now be null (missing), because a refName value can be
387 // provided as an alternate identifier.
389 if (Tools.notBlank(csid)) {
390 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
391 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
392 docModel = docWrapper.getWrappedObject();
393 } else { // if (Tools.isBlank(objectCsid)) {
395 refName = (String) relationDocModel.getProperty(commonPartLabel,
396 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
397 docModel = NuxeoBasedResource.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
398 } catch (Exception e) {
399 throw new InvalidDocumentException(
400 "Relation record must have a CSID or refName to identify the object of the relation.", e);
404 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
405 +csid+" and/or refName: "+refName );
410 private void populateSubjectOrObjectValues(
411 DocumentModel relationDocModel,
412 DocumentModel subjectOrObjectDocModel,
414 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
416 HashMap<String,Object> properties = new HashMap<String,Object>();
418 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
419 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
420 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
423 String csid = (String) subjectOrObjectDocModel.getName();
424 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
427 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
428 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
429 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
433 String common_schema = getCommonSchemaNameForDocType(doctype);
435 if(common_schema!=null) {
436 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
438 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
442 String refname = (String)
443 subjectOrObjectDocModel.getProperty(
444 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
445 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
446 properties.put((fSubject?
447 RelationJAXBSchema.SUBJECT_REFNAME
448 :RelationJAXBSchema.OBJECT_REFNAME),
450 } catch (ClientException ce) {
451 throw new RuntimeException(
452 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
455 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
456 // property values in the target document model.
458 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
459 } catch (ClientException ce) {
460 throw new RuntimeException(
461 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
466 private String getCommonSchemaNameForDocType(String docType) {
467 String common_schema = null;
469 // HACK - Use startsWith to allow for extension of schemas.
470 if(docType.startsWith("Person"))
471 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
472 else if(docType.startsWith("Citation"))
473 common_schema = CitationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
474 else if(docType.startsWith("Organization"))
475 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
476 else if(docType.startsWith("Locationitem"))
477 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
478 else if(docType.startsWith("Taxon"))
479 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
480 else if(docType.startsWith("Placeitem"))
481 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
482 else if(docType.startsWith("Conceptitem"))
483 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
484 //else leave it null.
486 return common_schema;