]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
689319ae33dfbbd6cbdb962b8862b830d1b70371
[tmp/jakarta-migration.git] /
1 /**
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:
5
6  *  http://www.collectionspace.org
7  *  http://wiki.collectionspace.org
8
9  *  Copyright 2009 University of California at Berkeley
10
11  *  Licensed under the Educational Community License (ECL), Version 2.0.
12  *  You may not use this file except in compliance with this License.
13
14  *  You may obtain a copy of the ECL 2.0 License at
15
16  *  https://source.collectionspace.org/collection-space/LICENSE.txt
17
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.
23  */
24 package org.collectionspace.services.relation.nuxeo;
25
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.net.HttpURLConnection;
29
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;
51
52 // HACK HACK HACK
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;
60
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;
71
72 /**
73  * RelationDocumentModelHandler
74  *
75  * $LastChangedRevision: $
76  * $LastChangedDate: $
77  */
78 public class RelationDocumentModelHandler
79         extends RemoteDocumentModelHandlerImpl<RelationsCommon, RelationsCommonList> {
80
81     private final Logger logger = LoggerFactory.getLogger(RelationDocumentModelHandler.class);
82     /**
83      * relation is used to stash JAXB object to use when handle is called
84      * for Action.CREATE, Action.UPDATE or Action.GET
85      */
86     private RelationsCommon relation;
87     /**
88      * relationList is stashed when handle is called
89      * for ACTION.GET_ALL
90      */
91     private RelationsCommonList relationList;
92     
93     private static final String ERROR_TERMS_IN_WORKFLOWSTATE = "Cannot modify a relationship if either end is in the workflow state: ";
94
95     
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;
100                         
101         RepositoryInstance repoSession = this.getRepositorySession();
102         try {
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)) {
107                                 result = true;
108                         }
109                 } catch (Exception e) {
110                         if (logger.isInfoEnabled() == true) {
111                                 logger.info(errMsg, e);
112                         }
113                 }
114                 
115         return result;
116     }
117     
118         @Override
119         /*
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.
122          * 
123          * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#handleWorkflowTransition(org.collectionspace.services.common.document.DocumentWrapper, org.collectionspace.services.lifecycle.TransitionDef)
124          */
125         public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
126                         throws Exception {
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);
131                 }
132         }
133
134     @Override
135     public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
136         // Merge in the data from the payload
137         super.handleCreate(wrapDoc);
138
139         // And take care of ensuring all the values for the relation info are correct 
140         populateSubjectAndObjectValues(wrapDoc);
141         
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);
147         }
148     }
149
150     @Override
151     public void handleUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
152         // Merge in the data from the payload
153         super.handleUpdate(wrapDoc);
154         
155         // And take care of ensuring all the values for the relation info are correct 
156         populateSubjectAndObjectValues(wrapDoc);
157     }
158     
159     @Override
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);
165         } else {
166                 throw new ServiceException(HttpURLConnection.HTTP_FORBIDDEN,
167                     "Cannot delete a relationship if either end is in the workflow state: " + workflowState);
168         }
169     }
170     
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();
179         
180         DocumentModel subjectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, SUBJ_DOC_MODEL);
181         DocumentModel objectDocModel = getSubjectOrObjectDocModel(repoSession, relationDocModel, OBJ_DOC_MODEL);
182
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);
187         }
188         if (objectDocModel != null) {
189             populateSubjectOrObjectValues(relationDocModel, objectDocModel, OBJ_DOC_MODEL);
190         }
191     }
192
193     @Override
194     public RelationsCommon getCommonPart() {
195         return relation;
196     }
197
198     @Override
199     public void setCommonPart(RelationsCommon theRelation) {
200         this.relation = theRelation;
201     }
202
203     /**get associated Relation (for index/GET_ALL)
204      */
205     @Override
206     public RelationsCommonList getCommonPartList() {
207         return relationList;
208     }
209
210     @Override
211     public void setCommonPartList(RelationsCommonList theRelationList) {
212         this.relationList = theRelationList;
213     }
214
215     @Override
216     public RelationsCommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
217             throws Exception {
218         throw new UnsupportedOperationException();
219     }
220
221     @Override
222     public void fillCommonPart(RelationsCommon theRelation, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
223         throw new UnsupportedOperationException();
224     }
225
226     @Override
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();
232
233         TenantBindingConfigReaderImpl tReader = ServiceMain.getInstance().getTenantBindingConfigReader();
234         String serviceName = getServiceContext().getServiceName().toLowerCase();
235         ServiceBindingType sbt = tReader.getServiceBinding(ctx.getTenantId(), serviceName);
236
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);
242         }
243         return relList;
244     }
245
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.
248      * @param ctx the ctx
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
255      */
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);
264
265         relationListItem.setSubjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), 
266                                                                                                         RelationJAXBSchema.SUBJECT_CSID));
267
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));
274
275         relationListItem.setObjectCsid((String) docModel.getProperty(ctx.getCommonPartLabel(), 
276                                                                                                         RelationJAXBSchema.OBJECT_CSID));
277
278         relationListItem.setUri(serviceContextPath + id);
279
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);
285
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);
293
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);
298
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);
306
307         return relationListItem;
308     }
309
310     // DocumentModel itemDocModel = docModelFromCSID(ctx, itemCsid);
311     protected RelationsDocListItem createRelationsDocListItem(
312                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
313             ServiceBindingType sbt,
314             String itemCsid,
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);
320
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);
327             }
328
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);
333             try {
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) {
337                 } else {
338                     item.setName(itemDocname);
339                 }
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());
343             }
344             propName = "ERROR-FINDING-PROP-VALUE";
345             try {
346                 propName = ServiceBindingUtils.getPropertyValue(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP);
347                 String itemDocnumber = ServiceBindingUtils.getMappedFieldInDoc(itemSbt, ServiceBindingUtils.OBJ_NUMBER_PROP, itemDocModel);
348
349                 if (propName == null || itemDocnumber == null) {
350                 } else {
351                     item.setNumber(itemDocnumber);
352                 }
353             } catch (Throwable t) {
354                 logger.error("====Error finding objectNumberProperty: " + ServiceBindingUtils.OBJ_NUMBER_PROP + "=" + propName
355                         + " not found in itemDocType: " + itemDocType + " inner: " + t.getMessage());
356             }
357         } else {
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);
361         }
362         return item;
363     }
364
365     @Override
366     public String getQProperty(String prop) {
367         return "/" + RelationConstants.NUXEO_SCHEMA_ROOT_ELEMENT + "/" + prop;
368     }
369
370     private final boolean SUBJ_DOC_MODEL = true;
371     private final boolean OBJ_DOC_MODEL = false;
372     
373     private DocumentModel getSubjectOrObjectDocModel(
374                 RepositoryInstance repoSession,
375                 DocumentModel relationDocModel,
376                 boolean fSubject) throws Exception {
377         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
378         
379         // Get the document model for the object of the relation.
380         String commonPartLabel = ctx.getCommonPartLabel();
381         String csid = "";
382         String refName = "";
383         DocumentModel docModel = null;
384         // FIXME: Currently assumes that the object CSID is valid if present
385         // in the incoming payload.
386         try {
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.
394         }
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)) {
400             try {
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);
407             }
408         }
409         if(docModel==null) {
410                 throw new DocumentNotFoundException("Relation.getSubjectOrObjectDocModel could not find doc with CSID: "
411                                         +csid+" and/or refName: "+refName );
412         }
413         return docModel;
414     }
415     
416     private void populateSubjectOrObjectValues(
417                 DocumentModel relationDocModel, 
418                 DocumentModel subjectOrObjectDocModel,
419                 boolean fSubject ) {
420         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
421         
422         HashMap<String,Object> properties = new HashMap<String,Object>();
423         try {
424                 String doctype = subjectOrObjectDocModel.getDocumentType().getName();
425             doctype = ServiceBindingUtils.getUnqualifiedTenantDocType(doctype);
426                 properties.put((fSubject?RelationJAXBSchema.SUBJECT_DOCTYPE:RelationJAXBSchema.OBJECT_DOCTYPE),
427                                                         doctype);
428         
429                 String csid = (String) subjectOrObjectDocModel.getName();
430                 properties.put((fSubject?RelationJAXBSchema.SUBJECT_CSID:RelationJAXBSchema.OBJECT_CSID),
431                                                         csid);
432         
433                 String uri = (String) subjectOrObjectDocModel.getProperty(COLLECTIONSPACE_CORE_SCHEMA,
434                                                                                                                                         COLLECTIONSPACE_CORE_URI);
435                 properties.put((fSubject?RelationJAXBSchema.SUBJECT_URI:RelationJAXBSchema.OBJECT_URI),
436                                                         uri);
437                 
438                 String common_schema = getCommonSchemaNameForDocType(doctype);
439                 
440                 if(common_schema!=null) {
441                         String refname = (String)subjectOrObjectDocModel.getProperty(common_schema, 
442                                                                                                                         RefName.REFNAME );
443                     properties.put((fSubject?RelationJAXBSchema.SUBJECT_REFNAME:RelationJAXBSchema.OBJECT_REFNAME),
444                                 refname);
445                 }
446         } catch (ClientException ce) {
447             throw new RuntimeException(
448                     "populateSubjectOrObjectValues: Problem fetching field " + ce.getLocalizedMessage());
449         }
450
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.
453         try {
454                 relationDocModel.setProperties(ctx.getCommonPartLabel(), properties);
455         } catch (ClientException ce) {
456             throw new RuntimeException(
457                     "populateSubjectValues: Problem setting fields " + ce.getLocalizedMessage());
458         }
459     }
460     
461     private String getCommonSchemaNameForDocType(String docType) {
462         String common_schema = null;
463         if(docType!=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.
478         }
479         return common_schema;
480     }
481
482 }