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;
29 import org.collectionspace.services.client.PoxPayloadIn;
30 import org.collectionspace.services.client.PoxPayloadOut;
31 import org.collectionspace.services.common.ResourceBase;
32 import org.collectionspace.services.common.ResourceMap;
33 import org.collectionspace.services.common.ServiceMain;
34 import org.collectionspace.services.common.api.RefName;
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.InvalidDocumentException;
40 import org.collectionspace.services.common.relation.RelationJAXBSchema;
41 import org.collectionspace.services.common.relation.nuxeo.RelationConstants;
42 import org.collectionspace.services.common.context.ServiceContext;
43 import org.collectionspace.services.common.repository.RepositoryClient;
44 import org.collectionspace.services.common.repository.RepositoryClientFactory;
45 import org.collectionspace.services.common.service.ServiceBindingType;
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;
52 import org.collectionspace.services.client.PersonAuthorityClient;
53 import org.collectionspace.services.client.OrgAuthorityClient;
54 import org.collectionspace.services.client.LocationAuthorityClient;
55 import org.collectionspace.services.client.TaxonomyAuthorityClient;
56 import org.collectionspace.services.client.PlaceAuthorityClient;
58 import org.collectionspace.services.common.document.DocumentWrapper;
59 import org.collectionspace.services.jaxb.AbstractCommonList;
60 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
61 import org.collectionspace.services.nuxeo.client.java.RepositoryJavaClientImpl;
62 import org.collectionspace.services.relation.RelationsDocListItem;
63 import org.nuxeo.ecm.core.api.ClientException;
64 import org.nuxeo.ecm.core.api.CoreSession;
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;
94 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
95 // Merge in the data from the payload
96 super.handleCreate(wrapDoc);
98 // And take care of ensuring all the values for the relation info are correct
99 populateSubjectAndObjectValues(wrapDoc);
103 public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
104 // Merge in the data from the payload
105 super.handleUpdate(wrapDoc);
107 // And take care of ensuring all the values for the relation info are correct
108 populateSubjectAndObjectValues(wrapDoc);
111 private void populateSubjectAndObjectValues(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
112 // Obtain document models for the subject and object of the relation, so that
113 // we ensure we have value docType, URI info. If the docModels support refNames,
114 // we will also set those.
115 // Note that this introduces another caching problem...
116 DocumentModel relationDocModel = wrapDoc.getWrappedObject();
117 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
118 RepositoryInstance repoSession = this.getRepositorySession();
120 DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
121 DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
123 // Use values from the subject and object document models to populate the
124 // relevant fields of the relation's own document model.
125 if (subjectDocModel != null) {
126 populateSubjectOrObjectValues(relationDocModel, subjectDocModel, SUBJ_DOC_MODEL);
128 if (objectDocModel != null) {
129 populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
134 public RelationsCommon getCommonPart() {
139 public void setCommonPart(RelationsCommon theRelation) {
140 this.relation = theRelation;
143 /**get associated Relation (for index/GET_ALL)
146 public RelationsCommonList getCommonPartList() {
151 public void setCommonPartList(RelationsCommonList theRelationList) {
152 this.relationList = theRelationList;
156 public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
158 throw new UnsupportedOperationException();
162 public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
163 throw new UnsupportedOperationException();
167 public RelationsCommonList extractCommonPartList(DocumentWrapper<DocumentModelList> wrapDoc) throws Exception {
168 RelationsCommonList relList = this.extractPagingInfo(new RelationsCommonList(), wrapDoc);
169 relList.setFieldsReturned("subjectCsid|relationshipType|predicateDisplayName|objectCsid|uri|csid|subject|object");
170 ServiceContext ctx = getServiceContext();
171 String serviceContextPath = getServiceContextPath();
173 TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
174 String serviceName = getServiceContext().getServiceName().toLowerCase();
175 ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
177 Iterator<DocumentModel> iter = wrapDoc.getWrappedObject().iterator();
178 while (iter.hasNext()) {
179 DocumentModel docModel = iter.next();
180 RelationListItem relListItem = getRelationListItem(ctx, sbt, tReader, docModel, serviceContextPath);
181 relList.getRelationListItem().add(relListItem);
186 /** Gets the relation list item, looking up the subject and object documents, and getting summary
187 * info via the objectName and objectNumber properties in tenant-bindings.
189 * @param sbt the ServiceBindingType of Relations service
190 * @param tReader the tenant-bindings reader, for looking up docnumber and docname
191 * @param docModel the doc model
192 * @param serviceContextPath the service context path
193 * @return the relation list item, with nested subject and object summary info.
194 * @throws Exception the exception
196 private RelationListItem getRelationListItem(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
197 ServiceBindingType sbt,
198 TenantBindingConfigReaderImpl tReader,
199 DocumentModel docModel,
200 String serviceContextPath) throws Exception {
201 RelationListItem relationListItem = new RelationListItem();
202 String id = getCsid(docModel);
203 relationListItem.setCsid(id);
205 relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
206 RelationJAXBSchema.SUBJECT_CSID));
208 String predicate = (String) docModel.getProperty(ctx.getCommonPartLabel(),
209 RelationJAXBSchema.RELATIONSHIP_TYPE);
210 relationListItem.setRelationshipType(predicate);
211 relationListItem.setPredicate(predicate); //predicate is new name for relationshipType.
212 relationListItem.setPredicateDisplayName((String) docModel.getProperty(ctx.getCommonPartLabel(),
213 RelationJAXBSchema.RELATIONSHIP_TYPE_DISPLAYNAME));
215 relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(),
216 RelationJAXBSchema.OBJECT_CSID));
218 relationListItem.setUri(serviceContextPath + id);
220 //Now fill in summary info for the related docs: subject and object.
221 String subjectCsid = relationListItem.getSubjectCsid();
222 String subjectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
223 RelationJAXBSchema.SUBJECT_DOCTYPE);
224 RelationsDocListItem subject = createRelationsDocListItem(ctx, sbt, subjectCsid, tReader, subjectDocumentType);
226 String subjectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
227 RelationJAXBSchema.SUBJECT_URI);
228 subject.setUri(subjectUri);
229 String subjectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
230 RelationJAXBSchema.SUBJECT_REFNAME);
231 subject.setRefName(subjectRefName);
232 relationListItem.setSubject(subject);
234 String objectCsid = relationListItem.getObjectCsid();
235 String objectDocumentType = (String) docModel.getProperty(ctx.getCommonPartLabel(),
236 RelationJAXBSchema.OBJECT_DOCTYPE);
237 RelationsDocListItem object = createRelationsDocListItem(ctx, sbt, objectCsid, tReader, objectDocumentType);
239 String objectUri = (String) docModel.getProperty(ctx.getCommonPartLabel(),
240 RelationJAXBSchema.OBJECT_URI);
241 object.setUri(objectUri);
242 String objectRefName = (String) docModel.getProperty(ctx.getCommonPartLabel(),
243 RelationJAXBSchema.OBJECT_REFNAME);
244 object.setRefName(objectRefName);
245 relationListItem.setObject(object);
247 return relationListItem;
250 // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
251 protected RelationsDocListItem createRelationsDocListItem(
252 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
253 ServiceBindingType sbt,
255 TenantBindingConfigReaderImpl tReader,
256 String documentType) throws Exception {
257 RelationsDocListItem item = new RelationsDocListItem();
258 item.setDocumentType(documentType);//this one comes from the record, as subjectDocumentType, objectDocumentType.
259 item.setCsid(itemCsid);
261 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(ctx, this.getRepositorySession(), itemCsid); //null if not found.
262 if (itemDocModel != null) {
263 String itemDocType = itemDocModel.getDocumentType().getName();
264 if (Tools.isBlank(documentType)) {
265 item.setDocumentType(itemDocType);
268 //TODO: ensure that itemDocType is really the entry point, i.e. servicename==doctype
269 //ServiceBindingType itemSbt2 = tReader.getServiceBinding(ctx.getTenantId(), itemDocType);
270 String propName = "ERROR-FINDING-PROP-VALUE";
271 ServiceBindingType itemSbt = tReader.getServiceBindingForDocType(ctx.getTenantId(), itemDocType);
273 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP);
274 String itemDocname = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NAME_PROP, itemDocModel);
275 if (propName == null || itemDocname == null) {
277 item.setName(itemDocname);
279 } catch (Throwable t) {
280 logger.error("====Error finding objectNameProperty: " + itemDocModel + " field " + ServiceBindingUtils.OBJ_NAME_PROP + "=" + propName
281 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
283 propName = "ERROR-FINDING-PROP-VALUE";
285 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
286 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
288 if (propName == null || itemDocnumber == null) {
290 item.setNumber(itemDocnumber);
292 } catch (Throwable t) {
293 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
294 + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
297 item.setError("INVALID: related object is absent");
298 // Laramie20110510 CSPACE-3739 throw the exception for 3739, otherwise, don't throw it.
299 //throw new Exception("INVALID: related object is absent "+itemCsid);
305 public String getQProperty(String prop) {
306 return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
309 private final boolean SUBJ_DOC_MODEL = true;
310 private final boolean OBJ_DOC_MODEL = false;
312 private DocumentModel getSubjectOrObjectDocModel(
313 RepositoryInstance repoSession,
314 DocumentModel relationDocModel,
315 boolean fSubject) throws Exception {
316 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
318 // Get the document model for the object of the relation.
319 String commonPartLabel = ctx.getCommonPartLabel();
322 DocumentModel docModel = null;
323 // FIXME: Currently assumes that the object CSID is valid if present
324 // in the incoming payload.
326 csid = (String) relationDocModel.getProperty(commonPartLabel,
327 (fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID));
328 } catch (PropertyException pe) {
329 // Per CSPACE-4468, ignore any property exception here.
330 // The objectCsid and/or subjectCsid field in a relation record
331 // can now be null (missing), because a refName value can be
332 // provided as an alternate identifier.
334 if (Tools.notBlank(csid)) {
335 RepositoryJavaClientImpl nuxeoRepoClient = (RepositoryJavaClientImpl)getRepositoryClient(ctx);
336 DocumentWrapper<DocumentModel> docWrapper = nuxeoRepoClient.getDocFromCsid(ctx, repoSession, csid);
337 docModel = docWrapper.getWrappedObject();
338 } else { // if (Tools.isBlank(objectCsid)) {
340 refName = (String) relationDocModel.getProperty(commonPartLabel,
341 (fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME));
342 docModel = ResourceBase.getDocModelForRefName(repoSession, refName, ctx.getResourceMap());
343 } catch (Exception e) {
344 throw new InvalidDocumentException(
345 "Relation record must have a CSID or refName to identify the object of the relation.", e);
349 throw new DocumentNotFoundException("Relation.getSubjectOrObjectDocModel could not find doc with CSID: "
350 +csid+" and/or refName: "+refName );
355 private void populateSubjectOrObjectValues(
356 DocumentModel relationDocModel,
357 DocumentModel subjectOrObjectDocModel,
359 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
361 HashMap<String,Object> properties = new HashMap<String,Object>();
363 String doctype = (String) subjectOrObjectDocModel.getType();
364 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
367 String csid = (String) subjectOrObjectDocModel.getName();
368 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
371 String uri = (String) subjectOrObjectDocModel.getProperty(COLLECTIONSPACE_CORE_SCHEMA,
372 COLLECTIONSPACE_CORE_URI);
373 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
376 String common_schema = getCommonSchemaNameForDocType(doctype);
378 if(common_schema!=null) {
379 String refname = (String)subjectOrObjectDocModel.getProperty(common_schema,
381 properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
384 } catch (ClientException ce) {
385 throw new RuntimeException(
386 "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
389 // FIXME: Call below is based solely on Nuxeo API docs; have not yet verified that it correctly updates existing
390 // property values in the target document model.
392 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
393 } catch (ClientException ce) {
394 throw new RuntimeException(
395 "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
399 private String getCommonSchemaNameForDocType(String docType) {
400 String common_schema = null;
402 // HACK - Use startsWith to allow for extension of schemas.
403 if(docType.startsWith("Person"))
404 common_schema = PersonAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
405 else if(docType.startsWith("Organization"))
406 common_schema = OrgAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
407 else if(docType.startsWith("Locationitem"))
408 common_schema = LocationAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
409 else if(docType.startsWith("Taxon"))
410 common_schema = TaxonomyAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
411 else if(docType.startsWith("Placeitem"))
412 common_schema = PlaceAuthorityClient.SERVICE_ITEM_COMMON_PART_NAME;
413 //else leave it null.
415 return common_schema;