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.common.vocabulary.nuxeo;
26 import java.util.ArrayList;
27 import java.util.Collection;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.ListIterator;
33 import org.collectionspace.services.client.AuthorityClient;
34 import org.collectionspace.services.client.PayloadInputPart;
35 import org.collectionspace.services.client.PayloadOutputPart;
36 import org.collectionspace.services.client.PoxPayloadIn;
37 import org.collectionspace.services.client.PoxPayloadOut;
38 import org.collectionspace.services.client.RelationClient;
39 //import org.collectionspace.services.common.authority.AuthorityItemRelations;
40 import org.collectionspace.services.common.api.CommonAPI;
41 import org.collectionspace.services.common.api.Tools;
42 import org.collectionspace.services.common.context.MultipartServiceContext;
43 import org.collectionspace.services.common.context.ServiceContext;
44 import org.collectionspace.services.common.document.DocumentWrapper;
45 import org.collectionspace.services.common.relation.IRelationsManager;
46 import org.collectionspace.services.common.service.ObjectPartType;
47 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
48 import org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl;
49 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
50 import org.collectionspace.services.relation.RelationResource;
51 import org.collectionspace.services.relation.RelationsCommon;
52 import org.collectionspace.services.relation.RelationsCommonList;
53 import org.collectionspace.services.relation.RelationsDocListItem;
54 import org.collectionspace.services.relation.RelationshipType;
55 import org.nuxeo.ecm.core.api.DocumentModel;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
59 import javax.management.relation.Relation;
60 import javax.ws.rs.core.MultivaluedMap;
61 import javax.ws.rs.core.UriInfo;
64 * AuthorityItemDocumentModelHandler
66 * $LastChangedRevision: $
69 public abstract class AuthorityItemDocumentModelHandler<AICommon, AICommonList>
70 extends RemoteDocumentModelHandlerImpl<AICommon, AICommonList> {
72 private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
74 private String authorityItemCommonSchemaName;
76 //private final Logger logger = LoggerFactory.getLogger(AuthorityItemDocumentModelHandler.class);
78 * item is used to stash JAXB object to use when handle is called
79 * for Action.CREATE, Action.UPDATE or Action.GET
81 protected AICommon item;
83 * itemList is stashed when handle is called
86 protected AICommonList itemList;
89 * inVocabulary is the parent Authority for this context
91 protected String inAuthority;
93 public AuthorityItemDocumentModelHandler(String authorityItemCommonSchemaName) {
94 this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
97 public String getInAuthority() {
101 public void setInAuthority(String inAuthority) {
102 this.inAuthority = inAuthority;
106 public String getUri(DocumentModel docModel) {
107 return getServiceContextPath()+inAuthority+"/"+ AuthorityClient.ITEMS+"/"+getCsid(docModel);
112 * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#handleCreate(org.collectionspace.services.common.document.DocumentWrapper)
115 public void handleCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
116 // first fill all the parts of the document
117 super.handleCreate(wrapDoc);
118 handleInAuthority(wrapDoc.getWrappedObject());
122 * Check the logic around the parent pointer. Note that we only need do this on
123 * create, since we have logic to make this read-only on update.
127 * @throws Exception the exception
129 private void handleInAuthority(DocumentModel docModel) throws Exception {
130 docModel.setProperty(authorityItemCommonSchemaName,
131 AuthorityItemJAXBSchema.IN_AUTHORITY, inAuthority);
136 * getCommonPart get associated item
140 public AICommon getCommonPart() {
145 public void setCommonPart(AICommon item) {
150 * getCommonPartList get associated item (for index/GET_ALL)
154 public AICommonList getCommonPartList() {
159 public void setCommonPartList(AICommonList itemList) {
160 this.itemList = itemList;
164 public AICommon extractCommonPart(DocumentWrapper<DocumentModel> wrapDoc)
166 throw new UnsupportedOperationException();
170 public void fillCommonPart(AICommon itemObject, DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
171 throw new UnsupportedOperationException();
175 * @see org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl#extractPart(org.nuxeo.ecm.core.api.DocumentModel, java.lang.String, org.collectionspace.services.common.service.ObjectPartType)
178 protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
180 Map<String, Object> unQObjectProperties = super.extractPart(docModel, schema, partMeta);
182 // Add the CSID to the common part
183 if (partMeta.getLabel().equalsIgnoreCase(authorityItemCommonSchemaName)) {
184 String csid = getCsid(docModel);//NuxeoUtils.extractId(docModel.getPathAsString());
185 unQObjectProperties.put("csid", csid);
188 return unQObjectProperties;
192 * Filters out AuthorityItemJAXBSchema.IN_AUTHORITY, to ensure that
193 * the parent link remains untouched.
194 * @param objectProps the properties parsed from the update payload
195 * @param partMeta metadata for the object to fill
198 public void filterReadOnlyPropertiesForPart(
199 Map<String, Object> objectProps, ObjectPartType partMeta) {
200 super.filterReadOnlyPropertiesForPart(objectProps, partMeta);
201 objectProps.remove(AuthorityItemJAXBSchema.IN_AUTHORITY);
202 objectProps.remove(AuthorityItemJAXBSchema.CSID);
206 public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
207 MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
208 super.extractAllParts(wrapDoc);
210 String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
211 if (Tools.isTrue(showSiblings)) {
212 showSiblings(wrapDoc, ctx);
213 return; // actual result is returned on ctx.addOutputPart();
216 String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
217 if (Tools.isTrue(showRelations)) {
218 showRelations(wrapDoc, ctx);
219 return; // actual result is returned on ctx.addOutputPart();
222 String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
223 if (Tools.isTrue(showAllRelations)) {
224 showAllRelations(wrapDoc, ctx);
225 return; // actual result is returned on ctx.addOutputPart();
229 /** @return null on parent not found
231 protected String getParentCSID(String thisCSID) throws Exception {
232 String parentCSID = null;
234 String predicate = RelationshipType.HAS_BROADER.value();
235 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
236 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
237 if (parentList != null) {
238 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
239 parentCSID = relationListItem.getObjectCsid();
242 } catch (Exception e) {
243 logger.error("Could not find parent for this: "+thisCSID, e);
248 public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
249 MultipartServiceContext ctx) throws Exception {
250 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
252 String predicate = RelationshipType.HAS_BROADER.value();
253 RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
254 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
256 RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
257 List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
259 //Assume that there are more children than parents. Will be true for parent/child, but maybe not for other relations.
260 //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
261 //Not optimal, but that's the current design spec.
263 for (RelationsCommonList.RelationListItem parent : parentList) {
264 childrenList.add(parent);
267 long childrenSize = childrenList.size();
268 childrenListOuter.setTotalItems(childrenSize);
269 childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage()+added);
271 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
272 ctx.addOutputPart(relationsPart);
275 public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
276 MultipartServiceContext ctx) throws Exception {
277 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
278 String parentCSID = getParentCSID(thisCSID);
279 if (parentCSID == null){
280 logger.warn("~~~~~\r\n~~~~ Could not find parent for this: "+thisCSID);
284 String predicate = RelationshipType.HAS_BROADER.value();
285 RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
286 List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
288 List<RelationsCommonList.RelationListItem> toRemoveList = newList();
291 RelationsCommonList.RelationListItem item = null;
292 for (RelationsCommonList.RelationListItem sibling : siblingList) {
293 if (thisCSID.equals(sibling.getSubjectCsid())){
294 toRemoveList.add(sibling); //IS_A copy of the main item, i.e. I have a parent that is my parent, so I'm in the list from the above query.
297 //rather than create an immutable iterator, I'm just putting the items to remove on a separate list, then looping over that list and removing.
298 for (RelationsCommonList.RelationListItem self : toRemoveList) {
299 removeFromList(siblingList, self);
302 long siblingSize = siblingList.size();
303 siblingListOuter.setTotalItems(siblingSize);
304 siblingListOuter.setItemsInPage(siblingSize);
306 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME,siblingListOuter);
307 ctx.addOutputPart(relationsPart);
310 public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
311 String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
313 RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null); // nulls are wildcards: predicate=*, and object=*
314 List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
316 RelationsCommonList objectListOuter = getRelations(null, thisCSID, null); // nulls are wildcards: subject=*, and predicate=*
317 List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
320 subjectList.addAll(objectList);
322 //now subjectList actually has records BOTH where thisCSID is subject and object.
323 long relatedSize = subjectList.size();
324 subjectListOuter.setTotalItems(relatedSize);
325 subjectListOuter.setItemsInPage(relatedSize);
327 PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME,subjectListOuter);
328 ctx.addOutputPart(relationsPart);
331 public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
332 super.fillAllParts(wrapDoc, action);
333 ServiceContext ctx = getServiceContext();
334 PoxPayloadIn input = (PoxPayloadIn)ctx.getInput();
335 DocumentModel documentModel = (wrapDoc.getWrappedObject());
336 String itemCsid = documentModel.getName();
338 //UPDATE and CREATE will call. Updates relations part
339 RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc);
341 PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);
342 ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
345 public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
346 super.completeUpdate(wrapDoc);
347 //now we add part for relations list
348 ServiceContext ctx = getServiceContext();
349 PayloadOutputPart foo = (PayloadOutputPart)ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
350 ((PoxPayloadOut)ctx.getOutput()).addPart(foo);
353 public RelationsCommonList updateRelations(String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc)
355 PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); //input.getPart("relations_common");
357 return null; //nothing to do--they didn't send a list of relations.
359 RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
361 ServiceContext ctx = getServiceContext();
362 UriInfo uriInfo = ctx.getUriInfo();
363 MultivaluedMap queryParams = uriInfo.getQueryParameters();
365 String predicate = RelationshipType.HAS_BROADER.value();
366 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
367 queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
368 queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
369 queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
370 queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
372 RelationsCommonList childListOuter = (new RelationResource()).getList(ctx.getUriInfo()); //magically knows all query params because they are in the context.
374 //Leave predicate, swap subject and object.
375 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
376 queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
377 queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
379 RelationsCommonList parentListOuter = (new RelationResource()).getList(ctx.getUriInfo());
381 go through inboundList, remove anything from childList that matches from childList
382 go through inboundList, remove anything from parentList that matches from parentList
383 go through parentList, delete all remaining
384 go through childList, delete all remaining
385 go through actionList, add all remaining.
386 check for duplicate children
387 check for more than one parent.
389 inboundList parentList childList actionList
390 ---------------- --------------- ---------------- ----------------
391 child-a parent-c child-a child-b
392 child-b parent-d child-c
395 String HAS_BROADER = RelationshipType.HAS_BROADER.value();
397 List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
398 List<RelationsCommonList.RelationListItem> actionList = newList();
399 List<RelationsCommonList.RelationListItem> childList = childListOuter.getRelationListItem();
400 List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
402 DocumentModel docModel = wrapDoc.getWrappedObject();
404 for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
405 if (inboundItem.getObject().getCsid().equalsIgnoreCase(CommonAPI.AuthorityItemCSID_REPLACE)){
406 inboundItem.setObjectCsid(itemCSID);
407 inboundItem.getObject().setCsid(itemCSID);
408 inboundItem.getObject().setUri(getUri(docModel));
410 if (inboundItem.getSubject().getCsid().equalsIgnoreCase(CommonAPI.AuthorityItemCSID_REPLACE)){
411 inboundItem.setSubjectCsid(itemCSID);
412 inboundItem.getSubject().setCsid(itemCSID);
413 inboundItem.getSubject().setUri(getUri(docModel));
415 if (inboundItem.getObject().getCsid().equals(itemCSID) && inboundItem.getPredicate().equals(HAS_BROADER)) {
416 //then this is an item that says we have a child.
417 RelationsCommonList.RelationListItem childItem = findInList(childList, inboundItem);
418 if (childItem != null){
419 removeFromList(childList, childItem); //exists, just take it off delete list
421 actionList.add(inboundItem); //doesn't exist as a child, but is a child. Add to additions list
423 } else if (inboundItem.getSubject().getCsid().equals(itemCSID) && inboundItem.getPredicate().equals(HAS_BROADER)) {
424 //then this is an item that says we have a parent
425 RelationsCommonList.RelationListItem parentItem = findInList(parentList, inboundItem);
426 if (parentItem != null){
427 removeFromList(parentList, parentItem); //exists, just take it off delete list
429 actionList.add(inboundItem); //doesn't exist as a parent, but is a parent. Add to additions list
433 System.out.println("\r\n\r\n================\r\n Element didn't match parent or child, but may have partial fields that match. inboundItem: "+inboundItem);
434 //not dealing with: hasNarrower or any other predicate.
437 deleteRelations(parentList, ctx); //todo: there are items appearing on both lists....april 20.
438 deleteRelations(childList, ctx);
439 createRelations(actionList, ctx);
440 //We return all elements on the inbound list, since we have just worked to make them exist in the system
441 // and be non-redundant, etc. That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
442 return relationsCommonListBody;
445 // this method calls the RelationResource to have it create the relations and persist them.
446 private void createRelations(List<RelationsCommonList.RelationListItem> inboundList, ServiceContext ctx){
447 for (RelationsCommonList.RelationListItem item : inboundList) {
448 RelationsCommon rc = new RelationsCommon();
449 //rc.setCsid(item.getCsid());
450 //todo: assignTo(item, rc);
451 RelationsDocListItem itemSubject = item.getSubject();
452 RelationsDocListItem itemObject = item.getObject();
454 String subjectCsid = itemSubject.getCsid();
455 rc.setDocumentId1(subjectCsid);
456 rc.setSubjectCsid(subjectCsid);
458 String objCsid = item.getObject().getCsid();
459 rc.setDocumentId2(objCsid);
460 rc.setObjectCsid(objCsid);
462 rc.setRelationshipType(item.getPredicate());
463 //RelationshipType foo = (RelationshipType.valueOf(item.getPredicate())) ;
464 //rc.setPredicate(foo); //this must be one of the type found in the enum in services/jaxb/src/main/resources/relations_common.xsd
466 rc.setDocumentType1(itemSubject.getDocumentType());
467 rc.setDocumentType2(itemObject.getDocumentType());
469 rc.setSubjectUri(itemSubject.getUri());
470 rc.setObjectUri(itemObject.getUri());
473 PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
474 PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
475 payloadOut.addPart(outputPart);
476 //System.out.println("\r\n==== TO CREATE: "+rc.getDocumentId1()+"==>"+rc.getPredicate()+"==>"+rc.getDocumentId2());
477 RelationResource relationResource = new RelationResource();
478 Object res = relationResource.create(ctx.getUriInfo(), payloadOut.toXML()); //NOTE ui recycled from above to pass in unknown query params.
481 private void deleteRelations(List<RelationsCommonList.RelationListItem> list,ServiceContext ctx){
483 for (RelationsCommonList.RelationListItem inboundItem : list) {
484 RelationResource relationResource = new RelationResource();
485 //System.out.println("\r\n==== TO DELETE: "+inboundItem.getCsid());
486 Object res = relationResource.delete(inboundItem.getCsid());
488 } catch (Throwable t){
489 String msg = "Unable to deleteRelations: "+ Tools.errorToString(t, true);
494 private List<RelationsCommonList.RelationListItem> newList(){
495 List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
498 protected List<RelationsCommonList.RelationListItem> cloneList(List<RelationsCommonList.RelationListItem> inboundList){
499 List<RelationsCommonList.RelationListItem> result = newList();
500 for (RelationsCommonList.RelationListItem item: inboundList){
505 private RelationsCommonList.RelationListItem findInList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item){
506 for (RelationsCommonList.RelationListItem listItem : list) {
507 if (itemsEqual(listItem, item)){ //equals must be defined, else
514 private boolean itemsEqual(RelationsCommonList.RelationListItem item, RelationsCommonList.RelationListItem item2){
515 if (item==null || item2==null){
518 RelationsDocListItem subj1 = item.getSubject();
519 RelationsDocListItem subj2 = item2.getSubject();
520 RelationsDocListItem obj1 = item.getObject();
521 RelationsDocListItem obj2 = item2.getObject();
523 return (subj1.getCsid().equals(subj2.getCsid()))
524 && (obj1.getCsid().equals(obj1.getCsid()))
525 && ( (item.getPredicate().equals(item2.getPredicate()))
526 && (item.getRelationshipType().equals(item2.getRelationshipType())) )
527 && (obj1.getDocumentType().equals(obj2.getDocumentType()))
528 && (subj1.getDocumentType().equals(subj2.getDocumentType())) ;
531 private void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item){
534 //================= TODO: move this to common, refactoring this and CollectionObjectResource.java
536 public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
537 ServiceContext ctx = getServiceContext();
538 MultivaluedMap queryParams = ctx.getQueryParams();
539 queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
540 queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
541 queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
543 RelationResource relationResource = new RelationResource();
544 RelationsCommonList relationsCommonList = relationResource.getList(ctx.getUriInfo());
545 return relationsCommonList;
548 //============================= END refactor ==========================