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.PersonAuthorityClient;
54 import org.collectionspace.services.client.OrgAuthorityClient;
55 import org.collectionspace.services.client.LocationAuthorityClient;
56 import org.collectionspace.services.client.TaxonomyAuthorityClient;
57 import org.collectionspace.services.client.PlaceAuthorityClient;
58 import org.collectionspace.services.client.ConceptAuthorityClient;
59 import org.collectionspace.services.client.workflow.WorkflowClient;
61 import org.collectionspace.services.config.service.ServiceBindingType;
62 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
63 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
64 import org.nuxeo.ecm.core.api.ClientException;
65 import org.nuxeo.ecm.core.api.DocumentModel;
66 import org.nuxeo.ecm.core.api.DocumentModelList;
67 import org.nuxeo.ecm.core.api.model.PropertyException;
68 import org.nuxeo.ecm.core.api.repository.RepositoryInstance;
69 import org.slf4j.Logger;
70 import org.slf4j.LoggerFactory;
73 * RelationDocumentModelHandler
75 * $LastChangedRevision: $
78 public class RelationDocumentModelHandler
79 extends RemoteDocumentModelHandlerImpl<RelationsCommon, RelationsCommonList> {
81 private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class);
83 * relation is used to stash JAXB object to use when handle is called
84 * for Action.CREATE, Action.UPDATE or Action.GET
86 private RelationsCommon relation;
88 * relationList is stashed when handle is called
91 private RelationsCommonList relationList;
93 private static final String ERROR_TERMS_IN_WORKFLOWSTATE = "Cannot modify a relationship if either end is in the workflow state: ";
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 RepositoryInstance 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().equalsIgnoreCase(workflowState) ||
106 objectDocModel.getCurrentLifeCycleState().equalsIgnoreCase(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 if the transition is allowed. Until then,
121 * 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(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
127 String workflowState = transitionDef.getDestinationState();
128 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
129 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
130 "Cannot change a relationship if either end of it is in the workflow state: " + workflowState);
135 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
136 // Merge in the data from the payload
137 super.handleCreate(wrapDoc);
139 // And take care of ensuring all the values for the relation info are correct
140 populateSubjectAndObjectValues(wrapDoc);
142 // both subject and object cannot be locked
143 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
144 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == true) {
145 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
146 "Cannot create a relationship if either end is in the workflow state: " + workflowState);
151 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
152 // Merge in the data from the payload
153 super.handleUpdate(wrapDoc);
155 // And take care of ensuring all the values for the relation info are correct
156 populateSubjectAndObjectValues(wrapDoc);
160 public void handleDelete(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
161 String workflowState = WorkflowClient.WORKFLOWSTATE_LOCKED;
162 // both subject and object cannot be locked
163 if (subjectOrObjectInWorkflowState(wrapDoc, workflowState) == false) {
164 super.handleDelete(wrapDoc);
166 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
167 "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
171 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
172 // Obtain document models for the subject and object of the relation, so that
173 // we ensure we have value docType, URI info. If the docModels support refNames,
174 // we will also set those.
175 // Note that this introduces another caching problem...
176 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
177 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
178 RepositoryInstance repoSession = this.getRepositorySession();
180 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
181 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
183 // Use values from the subject and object document models to populate the
184 // relevant fields of the relation's own document model.
185 if (subjectDocModel != null) {
186 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
188 if (objectDocModel != null) {
189 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
194 public RelationsCommon getCommonPart() {
199 public void setCommonPart(RelationsCommon theRelation) {
200 this.relation = theRelation;
203 /**get associated Relation (for index/GET_ALL)
206 public RelationsCommonList getCommonPartList() {
211 public void setCommonPartList(RelationsCommonList theRelationList) {
212 this.relationList = theRelationList;
216 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
218 throw new UnsupportedOperationException();
222 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
223 throw new UnsupportedOperationException();
227 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
228 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
229 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|objectCsid|uri|csid|subject|object");
230 ServiceContext ctx = getServiceContext();
231 String serviceContextPath = getServiceContextPath();
233 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
234 String serviceName = getServiceContext().getServiceName().toLowerCase();
235 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
237 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
238 while (iter.hasNext()) {
239 DocumentModel docModel = iter.next();
240 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
241 relList.getRelationListItem().add(relListItem);
246 /** Gets the relation list item, looking up the subject and object documents, and getting summary
247 * info via the objectName and objectNumber properties in tenant-bindings.
249 * @param sbt the ServiceBindingType of Relations service
250 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
251 * @param docModel the doc model
252 * @param serviceContextPath the service context path
253 * @return the relation list item, with nested subject and object summary info.
254 * @throws Exception the exception
256 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
257 ServiceBindingType sbt,
258 TenantBindingConfigReaderImpl tReader,
259 DocumentModel docModel,
260 String serviceContextPath) throws Exception {
261 RelationListItem relationListItem = new RelationListItem();
262 String id = getCsid(docModel);
263 relationListItem.setCsid(id);
265 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
266 RelationJAXBSchema.SUBJECT_CSID));
268 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
269 RelationJAXBSchema.RELATIONSHIP_TYPE);
270 relationListItem.setRelationshipType(predicate);
271 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
272 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
273 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
275 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
276 RelationJAXBSchema.OBJECT_CSID));
278 relationListItem.setUri(serviceContextPath + id);
280 //Now fill in summary info for the related docs: subject and object.
281 String subjectCsid = relationListItem.getSubjectCsid();
282 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
283 RelationJAXBSchema.SUBJECT_DOCTYPE);
284 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
286 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
287 RelationJAXBSchema.SUBJECT_URI);
288 subject.setUri(subjectUri);
289 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
290 RelationJAXBSchema.SUBJECT_REFNAME);
291 subject.setRefName(subjectRefName);
292 relationListItem.setSubject(subject);
294 String objectCsid = relationListItem.getObjectCsid();
295 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
296 RelationJAXBSchema.OBJECT_DOCTYPE);
297 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
299 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
300 RelationJAXBSchema.OBJECT_URI);
301 object.setUri(objectUri);
302 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
303 RelationJAXBSchema.OBJECT_REFNAME);
304 object.setRefName(objectRefName);
305 relationListItem.setObject(object);
307 return relationListItem;
310 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
311 protected RelationsDocListItem createRelationsDocListItem(
312 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
313 ServiceBindingType sbt,
315 TenantBindingConfigReaderImpl tReader,
316 String documentType) throws Exception {
317 RelationsDocListItem item = new RelationsDocListItem();
318 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
319 item.setCsid(itemCsid);
321 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
322 if (itemDocModel != null) {
323 String itemDocType = itemDocModel.getDocumentType().getName();
324 itemDocType = ServiceBindingUtils.getUnqualifiedTenantDocType(itemDocType);
325 if (Tools.isBlank(documentType)) {
326 item.setDocumentType(itemDocType);
329 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
330 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
331 String propName = "ERROR-FINDING-PROP-VALUE";
332 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
334 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
335 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
336 if (propName == null || itemDocname == null) {
338 item.setName(itemDocname);
340 } catch (Throwable t) {
341 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
342 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
344 propName = "ERROR-FINDING-PROP-VALUE";
346 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
347 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
349 if (propName == null || itemDocnumber == null) {
351 item.setNumber(itemDocnumber);
353 } catch (Throwable t) {
354 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
355 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
358 item.setError("INVALID: related object is absent");
359 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
360 //throw new Exception("INVALID: related object is absent "+itemCsid);
366 public String getQProperty(String prop) {
367 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
370 private final boolean SUBJ_DOC_MODEL = true;
371 private final boolean OBJ_DOC_MODEL = false;
373 private DocumentModel getSubjectOrObjectDocModel(
374 RepositoryInstance repoSession,
375 DocumentModel relationDocModel,
376 boolean fSubject) throws Exception {
377 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
379 // Get the document model for the object of the relation.
380 String commonPartLabel = ctx.getCommonPartLabel();
383 DocumentModel docModel = null;
384 // FIXME: Currently assumes that the object CSID is valid if present
385 // in the incoming payload.
387 csid = (String) relationDocModel.getProperty(commonPartLabel,
388 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
389 } catch (PropertyException pe) {
390 // Per CSPACE-4468, ignore any property exception here.
391 // The objectCsid and/or subjectCsid field in a relation record
392 // can now be null (missing), because a refName value can be
393 // provided as an alternate identifier.
395 if (Tools.notBlank(csid)) {
396 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
397 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
398 docModel = docWrapper.getWrappedObject();
399 } else { // if (Tools.isBlank(objectCsid)) {
401 refName = (String) relationDocModel.getProperty(commonPartLabel,
402 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
403 docModel = ResourceBase.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
404 } catch (Exception e) {
405 throw new InvalidDocumentException(
406 "Relation record must have a CSID or refName to identify the object of the relation.", e);
410 throw new DocumentNotFoundException("Relation.getSubjectOrObjectDocModel could not find doc with CSID: "
411 +csid+" and/or refName: "+refName );
416 private void populateSubjectOrObjectValues(
417 DocumentModel relationDocModel,
418 DocumentModel subjectOrObjectDocModel,
420 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
422 HashMap<String,Object> properties = new HashMap<String,Object>();
424 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
425 doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
426 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
429 String csid = (String) subjectOrObjectDocModel.getName();
430 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
433 String uri = (String) subjectOrObjectDocModel.getProperty(COLLECTIONSPACE_CORE_SCHEMA,
434 COLLECTIONSPACE_CORE_URI);
435 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
438 String common_schema = getCommonSchemaNameForDocType(doctype);
440 if(common_schema!=null) {
441 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
443 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
446 } catch (ClientException ce) {
447 throw new RuntimeException(
448 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
451 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
452 // property values in the target document model.
454 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
455 } catch (ClientException ce) {
456 throw new RuntimeException(
457 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
461 private String getCommonSchemaNameForDocType(String docType) {
462 String common_schema = null;
464 // HACK - Use startsWith to allow for extension of schemas.
465 if(docType.startsWith("Person"))
466 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
467 else if(docType.startsWith("Organization"))
468 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
469 else if(docType.startsWith("Locationitem"))
470 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
471 else if(docType.startsWith("Taxon"))
472 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
473 else if(docType.startsWith("Placeitem"))
474 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
475 else if(docType.startsWith("Conceptitem"))
476 common_schema = ConceptAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
477 //else leave it null.
479 return common_schema;