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.NuxeoRepositoryClientImpl;
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 * Will return 'true' if either the subject's or object's current workflow state *contain* the passed in workflow
93 * - will return 'true' if the subject's workflow state is "replicated_deleted" and the passed in workflow state is "replicated" or "deleted".
94 * - will return 'true' if the subject's or object's workflow state is "locked" and the passed in workflow state is "locked"
96 private boolean subjectOrObjectInWorkflowState(DocumentWrapper<DocumentModel> wrapDoc, String workflowState) throws ServiceException {
97 boolean result = false;
98 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
99 String errMsg = ERROR_TERMS_IN_WORKFLOWSTATE + workflowState;
101 CoreSessionInterface repoSession = this.getRepositorySession();
103 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
104 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
105 if (subjectDocModel.getCurrentLifeCycleState().contains(workflowState) ||
106 objectDocModel.getCurrentLifeCycleState().contains(workflowState)) {
109 } catch (Exception e) {
110 if (logger.isInfoEnabled() == true) {
111 logger.info(errMsg, e);
120 * 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
121 * if the transition is allowed. Until then, the WorkflowDocumentModelHandler class does the actual workflow transition.
123 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
125 public void handleWorkflowTransition(ServiceContext ctx, DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
127 if (subjectOrObjectInWorkflowState(wrapDoc, WorkflowClient.WORKFLOWSTATE_LOCKED) == true) {
128 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
129 "Cannot change a relationship if either end of it is in the workflow state: " + WorkflowClient.WORKFLOWSTATE_LOCKED);
134 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
135 // Merge in the data from the payload
136 super.handleCreate(wrapDoc);
138 // And take care of ensuring all the values for the relation info are correct
139 populateSubjectAndObjectValues(wrapDoc);
141 // We can't create a relationship record if either the subject or the object is in a locked workflow state
142 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
143 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
144 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
145 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
150 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
151 // Merge in the data from the payload
152 super.handleUpdate(wrapDoc);
154 // And take care of ensuring all the values for the relation info are correct
155 populateSubjectAndObjectValues(wrapDoc);
159 public boolean handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
160 boolean result = true;
162 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
163 // Neither the subject nor the object can 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);
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 CoreSessionInterface repoSession = this.getRepositorySession();
182 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
183 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
185 // Use values from the subject and object document models to populate the
186 // relevant fields of the relation's own document model.
187 if (subjectDocModel != null) {
188 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
190 if (objectDocModel != null) {
191 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
196 public RelationsCommon getCommonPart() {
201 public void setCommonPart(RelationsCommon theRelation) {
202 this.relation = theRelation;
205 /**get associated Relation (for index/GET_ALL)
208 public RelationsCommonList getCommonPartList() {
213 public void setCommonPartList(RelationsCommonList theRelationList) {
214 this.relationList = theRelationList;
218 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
220 throw new UnsupportedOperationException();
224 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
225 throw new UnsupportedOperationException();
229 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
230 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
231 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|relationshipMetaType|objectCsid|uri|csid|subject|object");
232 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
233 String serviceContextPath = getServiceContextPath();
235 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
236 String serviceName = getServiceContext().getServiceName().toLowerCase();
237 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
239 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
240 while (iter.hasNext()) {
241 DocumentModel docModel = iter.next();
242 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
243 relList.getRelationListItem().add(relListItem);
248 /** Gets the relation list item, looking up the subject and object documents, and getting summary
249 * info via the objectName and objectNumber properties in tenant-bindings.
251 * @param sbt the ServiceBindingType of Relations service
252 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
253 * @param docModel the doc model
254 * @param serviceContextPath the service context path
255 * @return the relation list item, with nested subject and object summary info.
256 * @throws Exception the exception
258 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
259 ServiceBindingType sbt,
260 TenantBindingConfigReaderImpl tReader,
261 DocumentModel docModel,
262 String serviceContextPath) throws Exception {
263 RelationListItem relationListItem = new RelationListItem();
264 String id = getCsid(docModel);
265 relationListItem.setCsid(id);
267 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
268 RelationJAXBSchema.SUBJECT_CSID));
270 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
271 RelationJAXBSchema.RELATIONSHIP_TYPE);
272 relationListItem.setRelationshipType(predicate);
273 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
274 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
275 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
277 relationListItem.setRelationshipMetaType((String) docModel.getProperty(ctx.getCommonPartLabel(),
278 RelationJAXBSchema.RELATIONSHIP_META_TYPE));
279 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
280 RelationJAXBSchema.OBJECT_CSID));
282 relationListItem.setUri(serviceContextPath + id);
284 //Now fill in summary info for the related docs: subject and object.
285 String subjectCsid = relationListItem.getSubjectCsid();
286 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
287 RelationJAXBSchema.SUBJECT_DOCTYPE);
288 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
290 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
291 RelationJAXBSchema.SUBJECT_URI);
292 subject.setUri(subjectUri);
293 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
294 RelationJAXBSchema.SUBJECT_REFNAME);
295 subject.setRefName(subjectRefName);
296 relationListItem.setSubject(subject);
298 String objectCsid = relationListItem.getObjectCsid();
299 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
300 RelationJAXBSchema.OBJECT_DOCTYPE);
301 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
303 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
304 RelationJAXBSchema.OBJECT_URI);
305 object.setUri(objectUri);
306 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
307 RelationJAXBSchema.OBJECT_REFNAME);
308 object.setRefName(objectRefName);
309 relationListItem.setObject(object);
311 return relationListItem;
314 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
315 protected RelationsDocListItem createRelationsDocListItem(
316 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
317 ServiceBindingType sbt,
319 TenantBindingConfigReaderImpl tReader,
320 String documentType) throws Exception {
321 RelationsDocListItem item = new RelationsDocListItem();
322 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
323 item.setCsid(itemCsid);
325 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
326 if (itemDocModel != null) {
327 String itemDocType = itemDocModel.getDocumentType().getName();
328 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
329 if (Tools.isBlank(documentType)) {
330 item.setDocumentType(itemDocType);
333 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
334 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
335 String propName = "ERROR-FINDING-PROP-VALUE";
336 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
338 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
339 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
340 if (propName == null || itemDocname == null) {
342 item.setName(itemDocname);
344 } catch (Throwable t) {
345 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
346 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
348 propName = "ERROR-FINDING-PROP-VALUE";
350 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
351 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
353 if (propName == null || itemDocnumber == null) {
355 item.setNumber(itemDocnumber);
357 } catch (Throwable t) {
358 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
359 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
362 item.setError("INVALID: related object is absent");
363 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
364 //throw new Exception("INVALID: related object is absent "+itemCsid);
370 public String getQProperty(String prop) {
371 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
374 private final boolean SUBJ_DOC_MODEL = true;
375 private final boolean OBJ_DOC_MODEL = false;
377 private DocumentModel getSubjectOrObjectDocModel(
378 CoreSessionInterface repoSession,
379 DocumentModel relationDocModel,
380 boolean fSubject) throws Exception {
381 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
383 // Get the document model for the object of the relation.
384 String commonPartLabel = ctx.getCommonPartLabel();
387 DocumentModel docModel = null;
388 // FIXME: Currently assumes that the object CSID is valid if present
389 // in the incoming payload.
391 csid = (String) relationDocModel.getProperty(commonPartLabel,
392 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
393 } catch (PropertyException pe) {
394 // Per CSPACE-4468, ignore any property exception here.
395 // The objectCsid and/or subjectCsid field in a relation record
396 // can now be null (missing), because a refName value can be
397 // provided as an alternate identifier.
399 if (Tools.notBlank(csid)) {
400 NuxeoRepositoryClientImpl nuxeoRepoClient = (NuxeoRepositoryClientImpl)getRepositoryClient(ctx);
401 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
402 docModel = docWrapper.getWrappedObject();
403 } else { // if (Tools.isBlank(objectCsid)) {
405 refName = (String) relationDocModel.getProperty(commonPartLabel,
406 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
407 docModel = NuxeoBasedResource.getDocModelForRefName(ctx, refName, ctx.getResourceMap());
408 } catch (Exception e) {
409 throw new InvalidDocumentException(
410 "Relation record must have a CSID or refName to identify the object of the relation.", e);
414 throw new DocumentNotFoundException("RelationDMH.getSubjectOrObjectDocModel could not find doc with CSID: "
415 +csid+" and/or refName: "+refName );
420 private void populateSubjectOrObjectValues(
421 DocumentModel relationDocModel,
422 DocumentModel subjectOrObjectDocModel,
424 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
426 HashMap<String,Object> properties = new HashMap<String,Object>();
428 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
429 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
430 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
433 String csid = (String) subjectOrObjectDocModel.getName();
434 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
437 String uri = (String) subjectOrObjectDocModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
438 CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
439 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
443 String common_schema = getCommonSchemaNameForDocType(doctype);
445 if(common_schema!=null) {
446 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
448 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
452 String refname = (String)
453 subjectOrObjectDocModel.getProperty(
454 CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
455 CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
456 properties.put((fSubject?
457 RelationJAXBSchema.SUBJECT_REFNAME
458 :RelationJAXBSchema.OBJECT_REFNAME),
460 } catch (ClientException ce) {
461 throw new RuntimeException(
462 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
465 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
466 // property values in the target document model.
468 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
469 } catch (ClientException ce) {
470 throw new RuntimeException(
471 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
476 public boolean supportsWorkflowStates() {