]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
a70327bea1f88c36d70273c710d5c0d17b22f5f2
[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.nuxeo.client.java;
25
26 import java.util.ArrayList;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Map.Entry;
31 import java.util.Set;
32
33 import javax.ws.rs.core.MediaType;
34 import javax.ws.rs.core.MultivaluedMap;
35 import javax.ws.rs.core.Response;
36 import javax.ws.rs.core.UriInfo;
37 import javax.xml.bind.JAXBElement;
38
39 import org.collectionspace.authentication.spi.AuthNContext;
40 import org.collectionspace.services.authorization.AccountPermission;
41 import org.collectionspace.services.jaxb.AbstractCommonList;
42 import org.collectionspace.services.lifecycle.TransitionDef;
43 import org.collectionspace.services.client.CollectionSpaceClient;
44 import org.collectionspace.services.client.PayloadInputPart;
45 import org.collectionspace.services.client.PayloadOutputPart;
46 import org.collectionspace.services.client.PoxPayloadIn;
47 import org.collectionspace.services.client.PoxPayloadOut;
48 import org.collectionspace.services.client.Profiler;
49 import org.collectionspace.services.client.RelationClient;
50 import org.collectionspace.services.client.workflow.WorkflowClient;
51 import org.collectionspace.services.common.CSWebApplicationException;
52 import org.collectionspace.services.common.NuxeoBasedResource;
53 import org.collectionspace.services.common.authorityref.AuthorityRefList;
54 import org.collectionspace.services.common.config.ServiceConfigUtils;
55 import org.collectionspace.services.common.context.JaxRsContext;
56 import org.collectionspace.services.common.context.MultipartServiceContext;
57 import org.collectionspace.services.common.context.ServiceBindingUtils;
58 import org.collectionspace.services.common.context.ServiceContext;
59 import org.collectionspace.services.common.document.BadRequestException;
60 import org.collectionspace.services.common.document.DocumentException;
61 import org.collectionspace.services.common.document.DocumentUtils;
62 import org.collectionspace.services.common.document.DocumentWrapper;
63 import org.collectionspace.services.common.document.DocumentFilter;
64 import org.collectionspace.services.client.IRelationsManager;
65 import org.collectionspace.services.common.relation.RelationResource;
66 import org.collectionspace.services.common.repository.RepositoryClient;
67 import org.collectionspace.services.common.security.SecurityUtils;
68 import org.collectionspace.services.common.storage.jpa.JpaStorageUtils;
69 import org.collectionspace.services.common.api.CommonAPI;
70 import org.collectionspace.services.common.api.RefNameUtils;
71 import org.collectionspace.services.common.api.Tools;
72 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils;
73 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthRefConfigInfo;
74 import org.collectionspace.services.config.service.DocHandlerParams;
75 import org.collectionspace.services.config.service.ListResultField;
76 import org.collectionspace.services.config.service.ObjectPartType;
77 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
78 import org.collectionspace.services.relation.RelationsCommon;
79 import org.collectionspace.services.relation.RelationsCommonList;
80 import org.collectionspace.services.relation.RelationsDocListItem;
81 import org.collectionspace.services.relation.RelationshipType;
82
83 import org.dom4j.Element;
84
85 import org.nuxeo.ecm.core.api.DocumentModel;
86 import org.nuxeo.ecm.core.api.DocumentModelList;
87 import org.nuxeo.ecm.core.api.model.PropertyException;
88
89 import org.slf4j.Logger;
90 import org.slf4j.LoggerFactory;
91
92 /**
93  * RemoteDocumentModelHandler
94  *
95  * $LastChangedRevision: $
96  * $LastChangedDate: $
97  * @param <T> 
98  * @param <TL> 
99  */
100 public abstract class   RemoteDocumentModelHandlerImpl<T, TL>
101         extends DocumentModelHandler<T, TL> {
102
103     /** The logger. */
104     private final Logger logger = LoggerFactory.getLogger(RemoteDocumentModelHandlerImpl.class);
105     private final static String CR = "\r\n";
106     private final static String EMPTYSTR = "";
107     
108     /* (non-Javadoc)
109      * @see org.collectionspace.services.common.document.AbstractDocumentHandlerImpl#setServiceContext(org.collectionspace.services.common.context.ServiceContext)
110      */
111     @Override
112     public void setServiceContext(ServiceContext ctx) {  //FIXME: Apply proper generics to ServiceContext<PoxPayloadIn, PoxPayloadOut>
113         if (ctx instanceof MultipartServiceContext) {
114             super.setServiceContext(ctx);
115         } else {
116             throw new IllegalArgumentException("setServiceContext requires instance of "
117                     + MultipartServiceContext.class.getName());
118         }
119     }
120     
121     @Override
122     protected String getRefnameDisplayName(DocumentWrapper<DocumentModel> docWrapper) {
123         return getRefnameDisplayName(docWrapper.getWrappedObject());
124     }
125         
126         private String getRefnameDisplayName(DocumentModel docModel) { // Look in the tenant bindings to see what field should be our display name for our refname value
127                 String result = null;
128                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
129                 
130         DocHandlerParams.Params params = null;
131         try {
132                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
133                         ListResultField field = params.getRefnameDisplayNameField();
134                         
135                         String schema = field.getSchema();
136                         if (schema == null || schema.trim().isEmpty()) {
137                                 schema = ctx.getCommonPartLabel();
138                         }
139                         
140                         result = getStringValue(docModel, schema, field);
141                 } catch (Exception e) {
142                         if (logger.isWarnEnabled()) {
143                                 logger.warn(String.format("Call failed to getRefnameDisplayName() for class %s", this.getClass().getName()));
144                         }
145                 }
146                 
147                 return result;
148         }
149         
150     @Override
151     public boolean supportsHierarchy() {
152         boolean result = false;
153         
154         DocHandlerParams.Params params = null;
155         try {
156                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
157                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
158                         Boolean bool = params.isSupportsHierarchy();
159                         if (bool != null) {
160                                 result = bool.booleanValue();
161                         }
162                 } catch (DocumentException e) {
163                         // TODO Auto-generated catch block
164                         String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
165                         if (logger.isWarnEnabled() == true) {
166                                 logger.warn(errMsg);
167                         }
168                 }
169         
170         return result;
171     }
172     
173     @Override
174     public boolean supportsVersioning() {
175         boolean result = false;
176         
177         DocHandlerParams.Params params = null;
178         try {
179                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
180                         params = ServiceConfigUtils.getDocHandlerParams(ctx);
181                         Boolean bool = params.isSupportsVersioning();
182                         if (bool != null) {
183                                 result = bool.booleanValue();
184                         }
185                 } catch (DocumentException e) {
186                         // TODO Auto-generated catch block
187                         String errMsg = String.format("Could not get document handler params from config bindings for class %s", this.getClass().getName());
188                         if (logger.isWarnEnabled() == true) {
189                                 logger.warn(errMsg);
190                         }
191                 }
192         
193         return result;
194     }
195     
196
197         @Override
198         public void handleWorkflowTransition(DocumentWrapper<DocumentModel> wrapDoc, TransitionDef transitionDef)
199                         throws Exception {
200                 // Do nothing by default, but children can override if they want.  The real workflow transition happens in the WorkflowDocumemtModelHandler class
201         }
202         
203     @Override
204     public void completeCreate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
205         super.completeCreate(wrapDoc);
206         if (supportsHierarchy() == true) {
207                 handleRelationsPayload(wrapDoc, false);
208         }
209     }
210         
211     /* NOTE: The authority item doc handler overrides (after calling) this method.  It performs refName updates.  In this
212      * method we just update any and all relationship records that use refNames that have changed.
213      * (non-Javadoc)
214      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#completeUpdate(org.collectionspace.services.common.document.DocumentWrapper)
215      */
216     @Override
217     public void completeUpdate(DocumentWrapper<DocumentModel> wrapDoc) throws Exception {
218         DocumentModel docModel = wrapDoc.getWrappedObject();
219         // We need to return at least those document part(s) and corresponding payloads that were received
220         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
221         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
222         PoxPayloadIn input = ctx.getInput();
223         if (input != null) {
224                 List<PayloadInputPart> inputParts = ctx.getInput().getParts();
225                 for (PayloadInputPart part : inputParts) {
226                     String partLabel = part.getLabel();
227                 try{
228                     ObjectPartType partMeta = partsMetaMap.get(partLabel);
229                     // CSPACE-4030 - generates NPE if the part is missing.
230                     if(partMeta!=null) {
231                             Map<String, Object> unQObjectProperties = extractPart(docModel, partLabel, partMeta);
232                             if(unQObjectProperties!=null) {
233                                 addOutputPart(unQObjectProperties, partLabel, partMeta);
234                             }
235                     }
236                 } catch (Throwable t){
237                     logger.error("Unable to addOutputPart: " + partLabel
238                                                + " in serviceContextPath: "+this.getServiceContextPath()
239                                                + " with URI: " + this.getServiceContext().getUriInfo().getPath()
240                                                + " error: " + t);
241                 }
242                 }
243         } else {
244                 if (logger.isWarnEnabled() == true) {
245                         logger.warn("MultipartInput part was null for document id = " +
246                                         docModel.getName());
247                 }
248         }
249         //
250         //  If the resource's service supports hierarchy then we need to perform a little more work
251         //
252         if (supportsHierarchy() == true) {
253             handleRelationsPayload(wrapDoc, true); // refNames in relations payload should refer to pre-updated record refName value
254             handleRefNameReferencesUpdate(); // if our refName changed, we need to update any and all relationship records that used the old one
255         }
256     }
257
258     /**
259      * Adds the output part.
260      *
261      * @param unQObjectProperties the un q object properties
262      * @param schema the schema
263      * @param partMeta the part meta
264      * @throws Exception the exception
265      * MediaType.APPLICATION_XML_TYPE
266      */
267     protected void addOutputPart(Map<String, Object> unQObjectProperties, String schema, ObjectPartType partMeta)
268             throws Exception {
269         Element doc = DocumentUtils.buildDocument(partMeta, schema,
270                 unQObjectProperties);
271         if (logger.isTraceEnabled() == true) {
272             logger.trace(doc.asXML());
273         }
274         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
275         ctx.addOutputPart(schema, doc, partMeta.getContent().getContentType());
276     }
277     
278     /**
279      * Extract paging info.
280      *
281      * @param commonsList the commons list
282      * @return the tL
283      * @throws Exception the exception
284      */
285     public TL extractPagingInfo(TL theCommonList, DocumentWrapper<DocumentModelList> wrapDoc)
286             throws Exception {
287         AbstractCommonList commonList = (AbstractCommonList) theCommonList;
288
289         DocumentFilter docFilter = this.getDocumentFilter();
290         long pageSize = docFilter.getPageSize();
291         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
292         // set the page size and page number
293         commonList.setPageNum(pageNum);
294         commonList.setPageSize(pageSize);
295         DocumentModelList docList = wrapDoc.getWrappedObject();
296         // Set num of items in list. this is useful to our testing framework.
297         commonList.setItemsInPage(docList.size());
298         // set the total result size
299         commonList.setTotalItems(docList.totalSize());
300
301         return (TL) commonList;
302     }
303
304     /* (non-Javadoc)
305      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#extractAllParts(org.collectionspace.services.common.document.DocumentWrapper)
306      */
307     @Override
308     public void extractAllParts(DocumentWrapper<DocumentModel> wrapDoc)
309             throws Exception {
310
311         DocumentModel docModel = wrapDoc.getWrappedObject();
312         String[] schemas = docModel.getDeclaredSchemas();
313         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
314         for (String schema : schemas) {
315             ObjectPartType partMeta = partsMetaMap.get(schema);
316             if (partMeta == null) {
317                 continue; // unknown part, ignore
318             }
319             Map<String, Object> unQObjectProperties = extractPart(docModel, schema, partMeta);
320             if(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA.equals(schema)) {
321                 addExtraCoreValues(docModel, unQObjectProperties);
322             }
323             addOutputPart(unQObjectProperties, schema, partMeta);
324         }
325         
326         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
327
328         if (supportsHierarchy() == true) {
329             String showSiblings = ctx.getQueryParams().getFirst(CommonAPI.showSiblings_QP);
330             if (Tools.isTrue(showSiblings)) {
331                 showSiblings(wrapDoc, ctx);
332                 return;   // actual result is returned on ctx.addOutputPart();
333             }
334
335             String showRelations = ctx.getQueryParams().getFirst(CommonAPI.showRelations_QP);
336             if (Tools.isTrue(showRelations)) {
337                 showRelations(wrapDoc, ctx);
338                 return;   // actual result is returned on ctx.addOutputPart();
339             }
340
341             String showAllRelations = ctx.getQueryParams().getFirst(CommonAPI.showAllRelations_QP);
342             if (Tools.isTrue(showAllRelations)) {
343                 showAllRelations(wrapDoc, ctx);
344                 return;   // actual result is returned on ctx.addOutputPart();
345             }
346         }
347         
348         String currentUser = ctx.getUserId();
349         if (currentUser.equalsIgnoreCase(AuthNContext.ANONYMOUS_USER) == false) {
350                 addAccountPermissionsPart();
351         }
352     }
353     
354     private void addExtraCoreValues(DocumentModel docModel, Map<String, Object> unQObjectProperties)
355                 throws Exception {
356         unQObjectProperties.put(CollectionSpaceClient.COLLECTIONSPACE_CORE_WORKFLOWSTATE, docModel.getCurrentLifeCycleState());
357     }
358     
359     private void addAccountPermissionsPart() throws Exception {
360         Profiler profiler = new Profiler("addAccountPermissionsPart():", 1);
361         profiler.start();
362         
363         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
364         String currentServiceName = ctx.getServiceName();
365         String workflowSubResource = "/";
366         JaxRsContext jaxRsContext = ctx.getJaxRsContext();
367         if (jaxRsContext != null) { // If not null then we're dealing with an authority item
368                 String resourceName = SecurityUtils.getResourceName(jaxRsContext.getUriInfo());
369                 workflowSubResource = workflowSubResource + resourceName + WorkflowClient.SERVICE_PATH + "/";
370         } else {
371                 workflowSubResource = workflowSubResource + currentServiceName + WorkflowClient.SERVICE_AUTHZ_SUFFIX;
372         }
373         AccountPermission accountPermission = JpaStorageUtils.getAccountPermissions(JpaStorageUtils.CS_CURRENT_USER,
374                         currentServiceName, workflowSubResource);
375         org.collectionspace.services.authorization.ObjectFactory objectFactory =
376                 new org.collectionspace.services.authorization.ObjectFactory();
377         JAXBElement<AccountPermission> ap = objectFactory.createAccountPermission(accountPermission);
378         PayloadOutputPart accountPermissionPart = new PayloadOutputPart("account_permission", ap); // REM - "account_permission" should be using a constant and not a literal
379         ctx.addOutputPart(accountPermissionPart);
380         
381         profiler.stop();
382     }
383
384     /* (non-Javadoc)
385      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#fillAllParts(org.collectionspace.services.common.document.DocumentWrapper)
386      */
387     @Override
388     public void fillAllParts(DocumentWrapper<DocumentModel> wrapDoc, Action action) throws Exception {
389
390         //TODO filling extension parts should be dynamic
391         //Nuxeo APIs lack to support stream/byte[] input, get/setting properties is
392         //not an ideal way of populating objects.
393         DocumentModel docModel = wrapDoc.getWrappedObject();
394         MultipartServiceContext ctx = (MultipartServiceContext) getServiceContext();
395         PoxPayloadIn input = ctx.getInput();
396         if (input == null || input.getParts().isEmpty()) {
397             String msg = String.format("No payload found for '%s' action.", action);
398             logger.error(msg + "Ctx=" + getServiceContext().toString());
399             throw new BadRequestException(msg);
400         }
401
402         Map<String, ObjectPartType> partsMetaMap = getServiceContext().getPartsMetadata();
403
404         //iterate over parts received and fill those parts
405         List<PayloadInputPart> inputParts = input.getParts();
406         for (PayloadInputPart part : inputParts) {
407
408             String partLabel = part.getLabel();
409             if (partLabel == null) {
410                 String msg = "Part label is missing or empty!";
411                 logger.error(msg + "Ctx=" + getServiceContext().toString());
412                 throw new BadRequestException(msg);
413             }
414
415             //skip if the part is not in metadata
416             ObjectPartType partMeta = partsMetaMap.get(partLabel);
417             if (partMeta == null) {
418                 continue;
419             }
420             fillPart(part, docModel, partMeta, action, ctx);
421         }
422     }
423
424     /**
425      * fillPart fills an XML part into given document model
426      * @param part to fill
427      * @param docModel for the given object
428      * @param partMeta metadata for the object to fill
429      * @throws Exception
430      */
431     protected void fillPart(PayloadInputPart part, DocumentModel docModel,
432             ObjectPartType partMeta, Action action, ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
433             throws Exception {
434         //check if this is an xml part
435         if (part.getMediaType().equals(MediaType.APPLICATION_XML_TYPE)) {
436                 Element element = part.getElementBody();
437             Map<String, Object> objectProps = DocumentUtils.parseProperties(partMeta, element, ctx);
438                 if (action == Action.UPDATE) {
439                     this.filterReadOnlyPropertiesForPart(objectProps, partMeta);
440                 }
441                 docModel.setProperties(partMeta.getLabel(), objectProps);
442             }
443         }
444
445     /**
446      * Filters out read only properties, so they cannot be set on update.
447      * TODO: add configuration support to do this generally
448      * @param objectProps the properties parsed from the update payload
449      * @param partMeta metadata for the object to fill
450      */
451     public void filterReadOnlyPropertiesForPart(
452             Map<String, Object> objectProps, ObjectPartType partMeta) {
453         // Should add in logic to filter most of the core items on update
454         if(partMeta.getLabel().equalsIgnoreCase(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA)) {
455                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_AT);
456                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_CREATED_BY);
457                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_URI);
458                 objectProps.remove(CollectionSpaceClient.COLLECTIONSPACE_CORE_TENANTID);
459                 // Note that the updatedAt/updatedBy fields are set internally
460                 // in DocumentModelHandler.handleCoreValues().
461         }
462     }
463
464     /**
465      * extractPart extracts an XML object from given DocumentModel
466      * @param docModel
467      * @param schema of the object to extract
468      * @param partMeta metadata for the object to extract
469      * @throws Exception
470      */
471     protected Map<String, Object> extractPart(DocumentModel docModel, String schema)
472             throws Exception {
473         return extractPart(docModel, schema, (Map<String, Object>)null);
474     }
475     
476     /**
477      * extractPart extracts an XML object from given DocumentModel
478      * @param docModel
479      * @param schema of the object to extract
480      * @param partMeta metadata for the object to extract
481      * @throws Exception
482      */
483     @Deprecated
484     protected Map<String, Object> extractPart(DocumentModel docModel, String schema, ObjectPartType partMeta)
485             throws Exception {
486         return extractPart(docModel, schema, partMeta, null);
487     }    
488
489     /**
490      * extractPart extracts an XML object from given DocumentModel
491      * @param docModel
492      * @param schema of the object to extract
493      * @param partMeta metadata for the object to extract
494      * @throws Exception
495      */
496     protected Map<String, Object> extractPart(
497             DocumentModel docModel, 
498             String schema,
499             Map<String, Object> addToMap)
500             throws Exception {
501         Map<String, Object> result = null;
502
503         Map<String, Object> objectProps = docModel.getProperties(schema);
504         if (objectProps != null) {
505                 //unqualify properties before sending the doc over the wire (to save bandwidh)
506                 //FIXME: is there a better way to avoid duplication of a Map/Collection?
507                 Map<String, Object> unQObjectProperties =
508                         (addToMap != null) ? addToMap : (new HashMap<String, Object>());
509                 Set<Entry<String, Object>> qualifiedEntries = objectProps.entrySet();
510                 for (Entry<String, Object> entry : qualifiedEntries) {
511                     String unqProp = getUnQProperty(entry.getKey());
512                     unQObjectProperties.put(unqProp, entry.getValue());
513                 }
514                 result = unQObjectProperties;
515         }
516
517         return result;
518     }
519     
520     /**
521      * extractPart extracts an XML object from given DocumentModel
522      * @param docModel
523      * @param schema of the object to extract
524      * @param partMeta metadata for the object to extract
525      * @throws Exception
526      */
527     @Deprecated
528     protected Map<String, Object> extractPart(
529             DocumentModel docModel, String schema, ObjectPartType partMeta,
530             Map<String, Object> addToMap)
531             throws Exception {
532         Map<String, Object> result = null;
533
534         result = this.extractPart(docModel, schema, addToMap);
535
536         return result;
537     }
538     
539     /* 
540     public String getStringPropertyFromDoc(
541                 ServiceContext ctx,
542                 String csid,
543                 String propertyXPath ) throws DocumentNotFoundException, DocumentException {
544         RepositoryInstance repoSession = null;
545         boolean releaseRepoSession = false;
546         String returnValue = null;
547
548         try{ 
549                 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
550                 repoSession = this.getRepositorySession();
551                 if (repoSession == null) {
552                         repoSession = repoClient.getRepositorySession();
553                         releaseRepoSession = true;
554                 }
555
556                 try {
557                         DocumentWrapper<DocumentModel> wrapper = repoClient.getDoc(repoSession, ctx, csid);
558                         DocumentModel docModel = wrapper.getWrappedObject();
559                         returnValue = (String) docModel.getPropertyValue(propertyXPath);
560                 } catch (PropertyException pe) {
561                         throw pe;
562                 } catch (DocumentException de) {
563                         throw de;
564                 } catch (Exception e) {
565                         if (logger.isDebugEnabled()) {
566                                 logger.debug("Caught exception ", e);
567                         }
568                         throw new DocumentException(e);
569                 } finally {
570                         if (releaseRepoSession && repoSession != null) {
571                                 repoClient.releaseRepositorySession(repoSession);
572                         }
573                 }
574         } catch (Exception e) {
575                 if (logger.isDebugEnabled()) {
576                         logger.debug("Caught exception ", e);
577                 }
578                 throw new DocumentException(e);
579         }               
580
581
582         if (logger.isWarnEnabled() == true) {
583                 logger.warn("Returned DocumentModel instance was created with a repository session that is now closed.");
584         }
585         return returnValue;
586     }
587      */
588
589     
590
591     /* (non-Javadoc)
592      * @see org.collectionspace.services.nuxeo.client.java.DocumentModelHandler#getAuthorityRefs(org.collectionspace.services.common.document.DocumentWrapper, java.util.List)
593      */
594     @Override
595     public AuthorityRefList getAuthorityRefs(
596             String csid,
597             List<AuthRefConfigInfo> authRefsInfo) throws PropertyException, Exception {
598
599         AuthorityRefList authRefList = new AuthorityRefList();
600         AbstractCommonList commonList = (AbstractCommonList) authRefList;
601         
602         DocumentFilter docFilter = this.getDocumentFilter();
603         long pageSize = docFilter.getPageSize();
604         long pageNum = pageSize != 0 ? docFilter.getOffset() / pageSize : pageSize;
605         // set the page size and page number
606         commonList.setPageNum(pageNum);
607         commonList.setPageSize(pageSize);
608         
609         List<AuthorityRefList.AuthorityRefItem> list = authRefList.getAuthorityRefItem();
610
611         try {
612                 int iFirstToUse = (int)(pageSize*pageNum);
613                 int nFoundInPage = 0;
614                 int nFoundTotal = 0;
615                 
616                 ArrayList<RefNameServiceUtils.AuthRefInfo> foundProps 
617                         = new ArrayList<RefNameServiceUtils.AuthRefInfo>();
618                 
619                 boolean releaseRepoSession = false;
620                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = this.getServiceContext();
621                 RepositoryClientImpl repoClient = (RepositoryClientImpl)this.getRepositoryClient(ctx);
622                 CoreSessionInterface repoSession = this.getRepositorySession();
623                 if (repoSession == null) {
624                         repoSession = repoClient.getRepositorySession(ctx);
625                         releaseRepoSession = true;
626                 }
627                 
628                 try {
629                         DocumentModel docModel = repoClient.getDoc(repoSession, ctx, csid).getWrappedObject();
630                         RefNameServiceUtils.findAuthRefPropertiesInDoc(docModel, authRefsInfo, null, foundProps);
631                         // Slightly goofy pagination support - how many refs do we expect from one object?
632                         for(RefNameServiceUtils.AuthRefInfo ari:foundProps) {
633                                 if((nFoundTotal >= iFirstToUse) && (nFoundInPage < pageSize)) {
634                                         if(appendToAuthRefsList(ari, list)) {
635                                                 nFoundInPage++;
636                                         nFoundTotal++;
637                                         }
638                                 } else {
639                                         nFoundTotal++;
640                                 }
641                         }
642                 } finally {
643                         if (releaseRepoSession == true) {
644                                 repoClient.releaseRepositorySession(ctx, repoSession);
645                         }
646                 }
647                 
648             // Set num of items in list. this is useful to our testing framework.
649             commonList.setItemsInPage(nFoundInPage);
650             // set the total result size
651             commonList.setTotalItems(nFoundTotal);
652             
653         } catch (PropertyException pe) {
654             String msg = "Attempted to retrieve value for invalid or missing authority field. "
655                     + "Check authority field properties in tenant bindings.";
656             logger.warn(msg, pe);
657             throw pe;
658         } catch (Exception e) {
659             if (logger.isDebugEnabled()) {
660                 logger.debug("Caught exception in getAuthorityRefs", e);
661             }
662             Response response = Response.status(
663                     Response.Status.INTERNAL_SERVER_ERROR).entity(
664                     "Failed to retrieve authority references").type(
665                     "text/plain").build();
666             throw new CSWebApplicationException(e, response);
667         }
668
669         return authRefList;
670     }
671
672     private boolean appendToAuthRefsList(RefNameServiceUtils.AuthRefInfo ari, 
673                                                 List<AuthorityRefList.AuthorityRefItem> list)
674             throws Exception {
675         String fieldName = ari.getQualifiedDisplayName();
676         try {
677                         String refNameValue = (String)ari.getProperty().getValue();
678                         AuthorityRefList.AuthorityRefItem item = authorityRefListItem(fieldName, refNameValue);
679                         if(item!=null) {        // ignore garbage values.
680                                 list.add(item);
681                                 return true;
682                         }
683         } catch(PropertyException pe) {
684                         logger.debug("PropertyException on: "+ari.getProperty().getPath()+pe.getLocalizedMessage());
685         }
686         return false;
687     }
688
689     private AuthorityRefList.AuthorityRefItem authorityRefListItem(String authRefFieldName, String refName) {
690
691         AuthorityRefList.AuthorityRefItem ilistItem = new AuthorityRefList.AuthorityRefItem();
692         try {
693             RefNameUtils.AuthorityTermInfo termInfo = RefNameUtils.parseAuthorityTermInfo(refName);
694             ilistItem.setRefName(refName);
695             ilistItem.setAuthDisplayName(termInfo.inAuthority.displayName);
696             ilistItem.setItemDisplayName(termInfo.displayName);
697             ilistItem.setSourceField(authRefFieldName);
698             ilistItem.setUri(termInfo.getRelativeUri());
699         } catch (Exception e) {
700                 logger.error("Trouble parsing refName from value: "+refName+" in field: "+authRefFieldName+e.getLocalizedMessage());
701                 ilistItem = null;
702         }
703         return ilistItem;
704     }
705
706     /**
707      * Returns the primary value from a list of values.
708      *
709      * Assumes that the first value is the primary value.
710      * This assumption may change when and if the primary value
711      * is identified explicitly.
712      *
713      * @param values a list of values.
714      * @param propertyName the name of a property through
715      *     which the value can be extracted.
716      * @return the primary value.
717     protected String primaryValueFromMultivalue(List<Object> values, String propertyName) {
718         String primaryValue = "";
719         if (values == null || values.size() == 0) {
720             return primaryValue;
721         }
722         Object value = values.get(0);
723         if (value instanceof String) {
724             if (value != null) {
725                 primaryValue = (String) value;
726             }
727        // Multivalue group of fields
728        } else if (value instanceof Map) {
729             if (value != null) {
730                 Map map = (Map) value;
731                 if (map.values().size() > 0) {
732                     if (map.get(propertyName) != null) {
733                       primaryValue = (String) map.get(propertyName);
734                     }
735                 }
736             }
737        } else {
738             logger.warn("Unexpected type for property " + propertyName
739                     + " in multivalue list: not String or Map.");
740        }
741        return primaryValue;
742     }
743      */
744
745     /**
746      * Gets a simple property from the document.
747      *
748      * For completeness, as this duplicates DocumentModel method. 
749      *
750      * @param docModel The document model to get info from
751      * @param schema The name of the schema (part)
752      * @param propertyName The simple scalar property type
753      * @return property value as String
754      */
755     protected String getSimpleStringProperty(DocumentModel docModel, String schema, String propName) {
756         String xpath = "/"+schema+":"+propName;
757         try {
758                 return (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
759         } catch(PropertyException pe) {
760                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Not a simple String property?"
761                                 +pe.getLocalizedMessage());
762         } catch(ClassCastException cce) {
763                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a scalar String property?"
764                                 +cce.getLocalizedMessage());
765         } catch(Exception e) {
766                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
767                                 +e.getLocalizedMessage());
768         }
769     }
770
771     /**
772      * Gets first of a repeating list of scalar values, as a String, from the document.
773      *
774      * @param docModel The document model to get info from
775      * @param schema The name of the schema (part)
776      * @param listName The name of the scalar list property
777      * @return first value in list, as a String, or empty string if the list is empty
778      */
779         protected String getFirstRepeatingStringProperty(DocumentModel docModel,
780                         String schema, String listName) {
781                 String xpath = "/" + schema + ":" + listName + "/[0]";
782                 try {
783                         return (String) NuxeoUtils.getProperyValue(docModel, xpath); // docModel.getPropertyValue(xpath);
784                 } catch (PropertyException pe) {
785                         throw new RuntimeException("Problem retrieving property {" + xpath
786                                         + "}. Not a repeating scalar?" + pe.getLocalizedMessage());
787                 } catch (IndexOutOfBoundsException ioobe) {
788                         // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
789                         return ""; // gracefully handle missing elements
790                 } catch (ClassCastException cce) {
791                         throw new RuntimeException("Problem retrieving property {" + xpath
792                                         + "} as String. Not a repeating String property?"
793                                         + cce.getLocalizedMessage());
794                 } catch (Exception e) {
795                         throw new RuntimeException("Unknown problem retrieving property {"
796                                         + xpath + "}." + e.getLocalizedMessage());
797                 }
798         }
799    
800     /**
801      * Gets first of a repeating list of scalar values, as a String, from the document.
802      *
803      * @param docModel The document model to get info from
804      * @param schema The name of the schema (part)
805      * @param listName The name of the scalar list property
806      * @return first value in list, as a String, or empty string if the list is empty
807      */
808     protected String getStringValueInPrimaryRepeatingComplexProperty(
809                 DocumentModel docModel, String schema, String complexPropertyName, String fieldName) {          
810         String result = null;
811         
812         String xpath = "/" + NuxeoUtils.getPrimaryXPathPropertyName(schema, complexPropertyName, fieldName);
813         try {
814                 result = (String) NuxeoUtils.getProperyValue(docModel, xpath); //docModel.getPropertyValue(xpath);
815         } catch(PropertyException pe) {
816                 throw new RuntimeException("Problem retrieving property {"+xpath+"}. Bad propertyNames?"
817                                 +pe.getLocalizedMessage());
818         } catch(IndexOutOfBoundsException ioobe) {
819                 // Nuxeo sometimes handles missing sub, and sometimes does not. Odd.
820                 result = "";    // gracefully handle missing elements
821         } catch(ClassCastException cce) {
822                 throw new RuntimeException("Problem retrieving property {"+xpath+"} as String. Not a String property?"
823                                 +cce.getLocalizedMessage());
824         } catch(NullPointerException npe) {
825                 // Getting here because of a bug in Nuxeo when value in repository is unknown/empty/null
826                 logger.warn(String.format("Nuxeo repo unexpectedly returned an Null Pointer Exception when asked for the value of {%s}.",
827                                 xpath));
828         } catch(Exception e) {
829                 throw new RuntimeException("Unknown problem retrieving property {"+xpath+"}."
830                                 +e.getLocalizedMessage());
831         }
832         
833         return result;
834     }
835    
836     /**
837      * Gets XPath value from schema. Note that only "/" and "[n]" are
838      * supported for xpath. Can omit grouping elements for repeating complex types, 
839      * e.g., "fieldList/[0]" can be used as shorthand for "fieldList/field[0]" and
840      * "fieldGroupList/[0]/field" can be used as shorthand for "fieldGroupList/fieldGroup[0]/field".
841      * If there are no entries for a list of scalars or for a list of complex types, 
842      * a 0 index expression (e.g., "fieldGroupList/[0]/field") will safely return an empty
843      * string. A non-zero index will throw an IndexOutOfBoundsException if there are not
844      * that many elements in the list. 
845      * N.B.: This does not follow the XPath spec - indices are 0-based, not 1-based.
846      *
847      * @param docModel The document model to get info from
848      * @param schema The name of the schema (part)
849      * @param xpath The XPath expression (without schema prefix)
850      * @return value the indicated property value as a String
851      * @throws DocumentException 
852      */
853         protected Object getListResultValue(DocumentModel docModel, // REM - CSPACE-5133
854                         String schema, ListResultField field) throws DocumentException {
855                 Object result = null;
856
857                 result = NuxeoUtils.getXPathValue(docModel, schema, field.getXpath());
858                 
859                 return result;
860         }
861         
862         protected String getStringValue(DocumentModel docModel,
863                         String schema, ListResultField field) throws DocumentException {
864                 String result = null;
865                 
866                 Object value = getListResultValue(docModel, schema, field);
867                 if (value != null && value instanceof String) {
868                         String strValue = (String) value;
869                         if (strValue.trim().isEmpty() == false) {
870                                 result = strValue;
871                         }
872                 }
873                 
874                 return result;
875         }
876    
877     protected void removeFromList(List<RelationsCommonList.RelationListItem> list, RelationsCommonList.RelationListItem item) {
878         list.remove(item);
879     }
880         
881     private void itemToString(StringBuilder sb, String prefix, RelationsCommonList.RelationListItem item ) {
882         sb.append(prefix);
883                 sb.append((item.getCsid()!= null)?item.getCsid():"NO CSID");
884         sb.append(": ["); 
885         sb.append((item.getSubject().getCsid()!=null)?item.getSubject().getCsid():item.getSubject().getRefName());
886         sb.append("]--");
887         sb.append(item.getPredicate());
888         sb.append("-->["); 
889         sb.append((item.getObject().getCsid()!=null)?item.getObject().getCsid():item.getObject().getRefName());
890         sb.append("]");
891     }
892     
893     private String dumpList(List<RelationsCommonList.RelationListItem> list, String label) {
894         StringBuilder sb = new StringBuilder();
895         String s;
896         if (list.size() > 0) {
897             sb.append("=========== " + label + " ==========" + CR);
898         }
899         for (RelationsCommonList.RelationListItem item : list) {
900                 itemToString(sb, "==  ", item);
901                 sb.append(CR);
902         }
903         return sb.toString();
904     }
905     
906     /** @return null on parent not found
907      */
908     protected String getParentCSID(String thisCSID) throws Exception {
909         String parentCSID = null;
910         try {
911             String predicate = RelationshipType.HAS_BROADER.value();
912             RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
913             List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
914             if (parentList != null) {
915                 if (parentList.size() == 0) {
916                     return null;
917                 }
918                 RelationsCommonList.RelationListItem relationListItem = parentList.get(0);
919                 parentCSID = relationListItem.getObjectCsid();
920             }
921             return parentCSID;
922         } catch (Exception e) {
923             logger.error("Could not find parent for this: " + thisCSID, e);
924             return null;
925         }
926     }
927     
928     protected List<RelationsCommonList.RelationListItem> newRelationsCommonList() {
929         List<RelationsCommonList.RelationListItem> result = new ArrayList<RelationsCommonList.RelationListItem>();
930         return result;
931     }
932
933     public void showSiblings(DocumentWrapper<DocumentModel> wrapDoc,
934             MultipartServiceContext ctx) throws Exception {
935         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
936         String parentCSID = getParentCSID(thisCSID);
937         if (parentCSID == null) {
938             logger.warn("~~~~~\r\n~~~~ Could not find parent for this: " + thisCSID);
939             return;
940         }
941
942         String predicate = RelationshipType.HAS_BROADER.value();
943         RelationsCommonList siblingListOuter = getRelations(null, parentCSID, predicate);
944         List<RelationsCommonList.RelationListItem> siblingList = siblingListOuter.getRelationListItem();
945
946         List<RelationsCommonList.RelationListItem> toRemoveList = newRelationsCommonList();
947
948
949         RelationsCommonList.RelationListItem item = null;
950         for (RelationsCommonList.RelationListItem sibling : siblingList) {
951             if (thisCSID.equals(sibling.getSubjectCsid())) {
952                 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.
953             }
954         }
955         //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.
956         for (RelationsCommonList.RelationListItem self : toRemoveList) {
957             removeFromList(siblingList, self);
958         }
959
960         long siblingSize = siblingList.size();
961         siblingListOuter.setTotalItems(siblingSize);
962         siblingListOuter.setItemsInPage(siblingSize);
963         if(logger.isTraceEnabled()) {
964             String dump = dumpList(siblingList, "Siblings of: "+thisCSID);
965             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showSiblings ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
966         }
967
968         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, siblingListOuter);
969         ctx.addOutputPart(relationsPart);
970     }
971     
972     public void showRelations(DocumentWrapper<DocumentModel> wrapDoc,
973             MultipartServiceContext ctx) throws Exception {
974         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
975
976         String predicate = RelationshipType.HAS_BROADER.value();
977         RelationsCommonList parentListOuter = getRelations(thisCSID, null, predicate);
978         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
979
980         RelationsCommonList childrenListOuter = getRelations(null, thisCSID, predicate);
981         List<RelationsCommonList.RelationListItem> childrenList = childrenListOuter.getRelationListItem();
982
983         if(logger.isTraceEnabled()) {
984             String dump = dumpLists(thisCSID, parentList, childrenList, null);
985             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
986         }
987         
988         //Assume that there are more children than parents.  Will be true for parent/child, but maybe not for other relations.
989         //Now add all parents to our childrenList, to be able to return just one list of consolidated results.
990         //Not optimal, but that's the current design spec.
991         long added = 0;
992         for (RelationsCommonList.RelationListItem parent : parentList) {
993             childrenList.add(parent);
994             added++;
995         }
996         long childrenSize = childrenList.size();
997         childrenListOuter.setTotalItems(childrenSize);
998         childrenListOuter.setItemsInPage(childrenListOuter.getItemsInPage() + added);
999
1000         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, childrenListOuter);
1001         ctx.addOutputPart(relationsPart);
1002     }
1003     
1004     public void showAllRelations(DocumentWrapper<DocumentModel> wrapDoc, MultipartServiceContext ctx) throws Exception {
1005         String thisCSID = NuxeoUtils.getCsid(wrapDoc.getWrappedObject());
1006
1007         RelationsCommonList subjectListOuter = getRelations(thisCSID, null, null);   //  nulls are wildcards:  predicate=*, and object=*
1008         List<RelationsCommonList.RelationListItem> subjectList = subjectListOuter.getRelationListItem();
1009
1010         RelationsCommonList objectListOuter = getRelations(null, thisCSID, null);   //  nulls are wildcards:  subject=*, and predicate=*
1011         List<RelationsCommonList.RelationListItem> objectList = objectListOuter.getRelationListItem();
1012
1013         if(logger.isTraceEnabled()) {
1014             String dump = dumpLists(thisCSID, subjectList, objectList, null);
1015             logger.trace("~~~~~~~~~~~~~~~~~~~~~~ showAllRelations ~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1016         }
1017         //  MERGE LISTS:
1018         subjectList.addAll(objectList);
1019
1020         //now subjectList actually has records BOTH where thisCSID is subject and object.
1021         long relatedSize = subjectList.size();
1022         subjectListOuter.setTotalItems(relatedSize);
1023         subjectListOuter.setItemsInPage(relatedSize);
1024
1025         PayloadOutputPart relationsPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, subjectListOuter);
1026         ctx.addOutputPart(relationsPart);
1027     }
1028
1029     private String dumpLists(String itemCSID,
1030             List<RelationsCommonList.RelationListItem> parentList,
1031             List<RelationsCommonList.RelationListItem> childList,
1032             List<RelationsCommonList.RelationListItem> actionList) {
1033         StringBuilder sb = new StringBuilder();
1034         sb.append("itemCSID: " + itemCSID + CR);
1035         if(parentList!=null) {
1036                 sb.append(dumpList(parentList, "parentList"));
1037         }
1038         if(childList!=null) {
1039                 sb.append(dumpList(childList, "childList"));
1040         }
1041         if(actionList!=null) {
1042                 sb.append(dumpList(actionList, "actionList"));
1043         }
1044         return sb.toString();
1045     }
1046     
1047     //================= TODO: move this to common, refactoring this and  CollectionObjectResource.java
1048     public RelationsCommonList getRelations(String subjectCSID, String objectCSID, String predicate) throws Exception {
1049         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1050         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1051         queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1052         queryParams.putSingle(IRelationsManager.SUBJECT_QP, subjectCSID);
1053         queryParams.putSingle(IRelationsManager.OBJECT_QP, objectCSID);
1054
1055         RelationResource relationResource = new RelationResource(); //is this still acting like a singleton as it should be?
1056         RelationsCommonList relationsCommonList = relationResource.getList(ctx);
1057         return relationsCommonList;
1058     }
1059     //============================= END TODO refactor ==========================
1060     
1061     // this method calls the RelationResource to have it create the relations and persist them.
1062     private void createRelations(List<RelationsCommonList.RelationListItem> inboundList,
1063                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) throws Exception {
1064         for (RelationsCommonList.RelationListItem item : inboundList) {
1065             RelationsCommon rc = new RelationsCommon();
1066             //rc.setCsid(item.getCsid());
1067             //todo: assignTo(item, rc);
1068             RelationsDocListItem itemSubject = item.getSubject();
1069             RelationsDocListItem itemObject = item.getObject();
1070
1071             // Set at least one of CSID and refName for Subject and Object
1072             // Either value might be null for for each of Subject and Object 
1073             String subjectCsid = itemSubject.getCsid();
1074             rc.setSubjectCsid(subjectCsid);
1075
1076             String objCsid = itemObject.getCsid();
1077             rc.setObjectCsid(objCsid);
1078
1079             rc.setSubjectRefName(itemSubject.getRefName());
1080             rc.setObjectRefName(itemObject.getRefName());
1081
1082             rc.setRelationshipType(item.getPredicate());
1083             rc.setRelationshipMetaType(item.getRelationshipMetaType());
1084             //RelationshipType  foo = (RelationshipType.valueOf(item.getPredicate())) ;
1085             //rc.setPredicate(foo);     //this must be one of the type found in the enum in  services/jaxb/src/main/resources/relations_common.xsd
1086
1087             // This is superfluous, since it will be fetched by the Relations Create logic.
1088             rc.setSubjectDocumentType(itemSubject.getDocumentType());
1089             rc.setObjectDocumentType(itemObject.getDocumentType());
1090
1091             // This is superfluous, since it will be fetched by the Relations Create logic.
1092             rc.setSubjectUri(itemSubject.getUri());
1093             rc.setObjectUri(itemObject.getUri());
1094             // May not have the info here. Only really require CSID or refName. 
1095             // Rest is handled in the Relation create mechanism
1096             //uriPointsToSameAuthority(itemSubject.getUri(), itemObject.getUri());
1097
1098             PoxPayloadOut payloadOut = new PoxPayloadOut(RelationClient.SERVICE_PAYLOAD_NAME);
1099             PayloadOutputPart outputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMONPART_NAME, rc);
1100             payloadOut.addPart(outputPart);
1101             RelationResource relationResource = new RelationResource();
1102             Response res = relationResource.create(ctx, ctx.getResourceMap(),
1103                     ctx.getUriInfo(), payloadOut.toXML());    //NOTE ui recycled from above to pass in unknown query params.
1104         }
1105     }
1106     
1107     // Note that item2 may be sparse (only refName, no CSID for subject or object)
1108     // But item1 must not be sparse 
1109     private boolean itemsEqual(RelationsCommonList.RelationListItem item1, RelationsCommonList.RelationListItem item2) {
1110         if (item1 == null || item2 == null) {
1111             return false;
1112         }
1113         RelationsDocListItem subj1 = item1.getSubject();
1114         RelationsDocListItem subj2 = item2.getSubject();
1115         RelationsDocListItem obj1 = item1.getObject();
1116         RelationsDocListItem obj2 = item2.getObject();
1117         
1118         String subj1Csid = subj1.getCsid();
1119         String subj2Csid = subj2.getCsid();
1120         String subj1RefName = subj1.getRefName();
1121         String subj2RefName = subj2.getRefName();
1122
1123         String obj1Csid = obj1.getCsid();
1124         String obj2Csid = obj2.getCsid();
1125         String obj1RefName = obj1.getRefName();
1126         String obj2RefName = obj2.getRefName();
1127         
1128         String item1Metatype = item1.getRelationshipMetaType();
1129         item1Metatype = item1Metatype != null ? item1Metatype : EMPTYSTR;
1130         
1131         String item2Metatype = item2.getRelationshipMetaType();
1132         item2Metatype = item2Metatype != null ? item2Metatype : EMPTYSTR;
1133         
1134         boolean isEqual = (subj1Csid.equals(subj2Csid) || ((subj2Csid==null)  && subj1RefName.equals(subj2RefName)))
1135                 && (obj1Csid.equals(obj2Csid)   || ((obj2Csid==null)   && obj1RefName.equals(obj2RefName)))
1136                 // predicate is proper, but still allow relationshipType
1137                 && (item1.getPredicate().equals(item2.getPredicate())
1138                         ||  ((item2.getPredicate()==null)  && item1.getRelationshipType().equals(item2.getRelationshipType())))
1139                 // Allow missing docTypes, so long as they do not conflict
1140                 && (obj1.getDocumentType().equals(obj2.getDocumentType()) || obj2.getDocumentType()==null)
1141                 && (subj1.getDocumentType().equals(subj2.getDocumentType()) || subj2.getDocumentType()==null)
1142                 && (item1Metatype.equalsIgnoreCase(item2Metatype));
1143         return isEqual;
1144     }
1145     
1146     // Note that the item argument may be sparse (only refName, no CSID for subject or object)
1147     // But the list items must not be sparse
1148     private RelationsCommonList.RelationListItem findInList(
1149                 List<RelationsCommonList.RelationListItem> list, 
1150                 RelationsCommonList.RelationListItem item) {
1151         RelationsCommonList.RelationListItem foundItem = null;
1152         for (RelationsCommonList.RelationListItem listItem : list) {
1153             if (itemsEqual(listItem, item)) {   //equals must be defined, else
1154                 foundItem = listItem;
1155                 break;
1156             }
1157         }
1158         return foundItem;
1159     }
1160     
1161     /**  updateRelations strategy:
1162      *
1163      *
1164     go through inboundList, remove anything from childList that matches  from childList
1165     go through inboundList, remove anything from parentList that matches  from parentList
1166     go through parentList, delete all remaining
1167     go through childList, delete all remaining
1168     go through actionList, add all remaining.
1169     check for duplicate children
1170     check for more than one parent.
1171     
1172     inboundList                           parentList                      childList          actionList
1173     ----------------                          ---------------                  ----------------       ----------------
1174     child-a                                   parent-c                        child-a             child-b
1175     child-b                                   parent-d                        child-c
1176     parent-a
1177      *
1178      *
1179      */
1180     private RelationsCommonList updateRelations(
1181             String itemCSID, PoxPayloadIn input, DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate)
1182             throws Exception {
1183         if (logger.isTraceEnabled()) {
1184             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID);
1185         }
1186         PayloadInputPart part = input.getPart(RelationClient.SERVICE_COMMON_LIST_NAME);        //input.getPart("relations_common");
1187         if (part == null) {
1188             return null;  //nothing to do--they didn't send a list of relations.
1189         }
1190         RelationsCommonList relationsCommonListBody = (RelationsCommonList) part.getBody();
1191         List<RelationsCommonList.RelationListItem> inboundList = relationsCommonListBody.getRelationListItem();
1192         List<RelationsCommonList.RelationListItem> actionList = newRelationsCommonList();
1193         List<RelationsCommonList.RelationListItem> childList = null;
1194         List<RelationsCommonList.RelationListItem> parentList = null;
1195         DocumentModel docModel = wrapDoc.getWrappedObject();
1196                 String itemRefName = (String) docModel.getProperty(CollectionSpaceClient.COLLECTIONSPACE_CORE_SCHEMA,
1197                         CollectionSpaceClient.COLLECTIONSPACE_CORE_REFNAME);
1198
1199                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1200         //Do magic replacement of ${itemCSID} and fix URI's.
1201         fixupInboundListItems(ctx, inboundList, docModel, itemCSID);
1202
1203         String HAS_BROADER = RelationshipType.HAS_BROADER.value();
1204         UriInfo uriInfo = ctx.getUriInfo();
1205         MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
1206
1207         if (fUpdate) {
1208             //Run getList() once as sent to get childListOuter:
1209             String predicate = RelationshipType.HAS_BROADER.value();
1210             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1211             queryParams.putSingle(IRelationsManager.SUBJECT_QP, null);
1212             queryParams.putSingle(IRelationsManager.SUBJECT_TYPE_QP, null);
1213             queryParams.putSingle(IRelationsManager.OBJECT_QP, itemCSID);
1214             queryParams.putSingle(IRelationsManager.OBJECT_TYPE_QP, null);
1215             
1216             RelationResource relationResource = new RelationResource();
1217             RelationsCommonList childListOuter = relationResource.getList(ctx);    // Knows all query params because they are in the context.
1218
1219             //Now run getList() again, leaving predicate, swapping subject and object, to get parentListOuter.
1220             queryParams.putSingle(IRelationsManager.PREDICATE_QP, predicate);
1221             queryParams.putSingle(IRelationsManager.SUBJECT_QP, itemCSID);
1222             queryParams.putSingle(IRelationsManager.OBJECT_QP, null);
1223             RelationsCommonList parentListOuter = relationResource.getList(ctx);
1224
1225
1226             childList = childListOuter.getRelationListItem();
1227             parentList = parentListOuter.getRelationListItem();
1228
1229             if (parentList.size() > 1) {
1230                 throw new Exception("Too many parents for object: " + itemCSID + " list: " + dumpList(parentList, "parentList"));
1231             }
1232
1233             if (logger.isTraceEnabled()) {
1234                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
1235             }
1236         }
1237
1238         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1239             // Note that the relations may specify the other (non-item) bit with a refName, not a CSID,
1240             // and so the CSID for those may be null
1241             if(inboundItem.getPredicate().equals(HAS_BROADER)) {
1242                 // Look for parents and children
1243                 if(itemCSID.equals(inboundItem.getObject().getCsid())
1244                                 || itemRefName.equals(inboundItem.getObject().getRefName())) {
1245                         //then this is an item that says we have a child.  That child is inboundItem
1246                         RelationsCommonList.RelationListItem childItem =
1247                                         (childList == null) ? null : findInList(childList, inboundItem);
1248                         if (childItem != null) {
1249                         if (logger.isTraceEnabled()) {
1250                                 StringBuilder sb = new StringBuilder();
1251                                 itemToString(sb, "== Child: ", childItem);
1252                             logger.trace("Found inboundChild in current child list: " + sb.toString());
1253                         }
1254                                 removeFromList(childList, childItem);    //exists, just take it off delete list
1255                         } else {
1256                         if (logger.isTraceEnabled()) {
1257                                 StringBuilder sb = new StringBuilder();
1258                                 itemToString(sb, "== Child: ", inboundItem);
1259                             logger.trace("inboundChild not in current child list, will add: " + sb.toString());
1260                         }
1261                                 actionList.add(inboundItem);   //doesn't exist as a child, but is a child.  Add to additions list
1262                                 String newChildCsid = inboundItem.getSubject().getCsid();
1263                                 if(newChildCsid == null) {
1264                                         String newChildRefName = inboundItem.getSubject().getRefName();
1265                                         if(newChildRefName==null) {
1266                                                 throw new RuntimeException("Child with no CSID or refName!");
1267                                         }
1268                             if (logger.isTraceEnabled()) {
1269                                 logger.trace("Fetching CSID for child with only refname: "+newChildRefName);
1270                             }
1271                                 DocumentModel newChildDocModel = 
1272                                         NuxeoBasedResource.getDocModelForRefName(this.getRepositorySession(), 
1273                                                         newChildRefName, getServiceContext().getResourceMap());
1274                                 newChildCsid = getCsid(newChildDocModel);
1275                                 }
1276                                 ensureChildHasNoOtherParents(ctx, queryParams, newChildCsid);
1277                         }
1278
1279                 } else if (itemCSID.equals(inboundItem.getSubject().getCsid())
1280                                         || itemRefName.equals(inboundItem.getSubject().getRefName())) {
1281                         //then this is an item that says we have a parent.  inboundItem is that parent.
1282                         RelationsCommonList.RelationListItem parentItem =
1283                                         (parentList == null) ? null : findInList(parentList, inboundItem);
1284                         if (parentItem != null) {
1285                                 removeFromList(parentList, parentItem);    //exists, just take it off delete list
1286                         } else {
1287                                 actionList.add(inboundItem);   //doesn't exist as a parent, but is a parent. Add to additions list
1288                         }
1289                 } else {
1290                     logger.error("Parent/Child Element didn't link to this item. inboundItem: " + inboundItem);
1291                 }
1292             } else {
1293                 logger.warn("Non-parent relation ignored. inboundItem: " + inboundItem);
1294             }
1295         }
1296         if (logger.isTraceEnabled()) {
1297             String dump = dumpLists(itemCSID, parentList, childList, actionList);
1298             logger.trace("~~~~~~~~~~~~~~~~~~~~~~dump~~~~~~~~~~~~~~~~~~~~~~~~" + CR + dump);
1299         }
1300         if (fUpdate) {
1301             if (logger.isTraceEnabled()) {
1302                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " deleting "
1303                         + parentList.size() + " existing parents and " + childList.size() + " existing children.");
1304             }
1305             deleteRelations(parentList, ctx, "parentList");               //todo: there are items appearing on both lists....april 20.
1306             deleteRelations(childList, ctx, "childList");
1307         }
1308         if (logger.isTraceEnabled()) {
1309             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " adding "
1310                     + actionList.size() + " new parents and children.");
1311         }
1312         createRelations(actionList, ctx);
1313         if (logger.isTraceEnabled()) {
1314             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
1315         }
1316         //We return all elements on the inbound list, since we have just worked to make them exist in the system
1317         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
1318         return relationsCommonListBody;
1319     }
1320     
1321     /** Performs substitution for ${itemCSID} (see CommonAPI.AuthorityItemCSID_REPLACE for constant)
1322      *   and sets URI correctly for related items.
1323      *   Operates directly on the items in the list.  Does not change the list ordering, does not add or remove any items.
1324      */
1325     protected void fixupInboundListItems(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1326             List<RelationsCommonList.RelationListItem> inboundList,
1327             DocumentModel docModel,
1328             String itemCSID) throws Exception {
1329         String thisURI = this.getUri(docModel);
1330         // WARNING:  the two code blocks below are almost identical  and seem to ask to be put in a generic method.
1331         //                    beware of the little diffs in  inboundItem.setObjectCsid(itemCSID); and   inboundItem.setSubjectCsid(itemCSID); in the two blocks.
1332         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
1333             RelationsDocListItem inboundItemObject = inboundItem.getObject();
1334             RelationsDocListItem inboundItemSubject = inboundItem.getSubject();
1335
1336             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemObject.getCsid())) {
1337                 inboundItem.setObjectCsid(itemCSID);
1338                 inboundItemObject.setCsid(itemCSID);
1339                 //inboundItemObject.setUri(getUri(docModel));
1340             } else {
1341                 /*
1342                 String objectCsid = inboundItemObject.getCsid();
1343                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, objectCsid);    //null if not found.
1344                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1345                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1346                 inboundItemObject.setUri(uri);    //CSPACE-4037
1347                  */
1348             }
1349             //uriPointsToSameAuthority(thisURI, inboundItemObject.getUri());    //CSPACE-4042
1350
1351             if (CommonAPI.AuthorityItemCSID_REPLACE.equalsIgnoreCase(inboundItemSubject.getCsid())) {
1352                 inboundItem.setSubjectCsid(itemCSID);
1353                 inboundItemSubject.setCsid(itemCSID);
1354                 //inboundItemSubject.setUri(getUri(docModel));
1355             } else {
1356                 /*
1357                 String subjectCsid = inboundItemSubject.getCsid();
1358                 DocumentModel itemDocModel = NuxeoUtils.getDocFromCsid(getRepositorySession(), ctx, subjectCsid);    //null if not found.
1359                 DocumentWrapper wrapper = new DocumentWrapperImpl(itemDocModel);
1360                 String uri = this.getRepositoryClient(ctx).getDocURI(wrapper);
1361                 inboundItemSubject.setUri(uri);    //CSPACE-4037
1362                  */
1363             }
1364             //uriPointsToSameAuthority(thisURI, inboundItemSubject.getUri());  //CSPACE-4042
1365
1366         }
1367     }
1368
1369     private void ensureChildHasNoOtherParents(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1370                 MultivaluedMap<String, String> queryParams, String childCSID) {
1371         logger.trace("ensureChildHasNoOtherParents for: " + childCSID );
1372         queryParams.putSingle(IRelationsManager.SUBJECT_QP, childCSID);
1373         queryParams.putSingle(IRelationsManager.PREDICATE_QP, RelationshipType.HAS_BROADER.value());
1374         queryParams.putSingle(IRelationsManager.OBJECT_QP, null);  //null means ANY
1375         
1376         RelationResource relationResource = new RelationResource();
1377         RelationsCommonList parentListOuter = relationResource.getList(ctx);
1378         List<RelationsCommonList.RelationListItem> parentList = parentListOuter.getRelationListItem();
1379         //logger.warn("ensureChildHasNoOtherParents preparing to delete relations on "+childCSID+"\'s parent list: \r\n"+dumpList(parentList, "duplicate parent list"));
1380         deleteRelations(parentList, ctx, "parentList-delete");
1381     }
1382
1383     private void deleteRelations(List<RelationsCommonList.RelationListItem> list,
1384                 ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
1385                 String listName) {
1386         try {
1387             for (RelationsCommonList.RelationListItem item : list) {
1388                 RelationResource relationResource = new RelationResource();
1389                 if(logger.isTraceEnabled()) {
1390                         StringBuilder sb = new StringBuilder();
1391                         itemToString(sb, "==== TO DELETE: ", item);
1392                         logger.trace(sb.toString());
1393                 }
1394                 Response res = relationResource.deleteWithParentCtx(ctx, item.getCsid());
1395                 if (logger.isDebugEnabled()) {
1396                         logger.debug("Status of authority item deleteRelations method call was: " + res.getStatus());
1397                 }
1398             }
1399         } catch (Throwable t) {
1400             String msg = "Unable to deleteRelations: " + Tools.errorToString(t, true);
1401             logger.error(msg);
1402         }
1403     }
1404     
1405     // Note that we must do this after we have completed the Update, so that the repository has the
1406     // info for the item itself. The relations code must call into the repo to get info for each end.
1407     // This could be optimized to pass in the parent docModel, since it will often be one end.
1408     // Nevertheless, we should complete the item save before we do work on the relations, especially
1409     // since a save on Create might fail, and we would not want to create relations for something
1410     // that may not be created...
1411     private void handleRelationsPayload(DocumentWrapper<DocumentModel> wrapDoc, boolean fUpdate) throws Exception {
1412         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1413         PoxPayloadIn input = ctx.getInput();
1414         DocumentModel documentModel = (wrapDoc.getWrappedObject());
1415         String itemCsid = documentModel.getName();
1416
1417         //Updates relations part
1418         RelationsCommonList relationsCommonList = updateRelations(itemCsid, input, wrapDoc, fUpdate);
1419
1420         PayloadOutputPart payloadOutputPart = new PayloadOutputPart(RelationClient.SERVICE_COMMON_LIST_NAME, relationsCommonList);  //FIXME: REM - We should check for a null relationsCommonList and not create the new common list payload
1421         ctx.setProperty(RelationClient.SERVICE_COMMON_LIST_NAME, payloadOutputPart);
1422
1423         //now we add part for relations list
1424         //ServiceContext ctx = getServiceContext();
1425         //PayloadOutputPart foo = (PayloadOutputPart) ctx.getProperty(RelationClient.SERVICE_COMMON_LIST_NAME);
1426         ctx.getOutput().addPart(payloadOutputPart);
1427     }
1428
1429     /**
1430      * Checks to see if the refName has changed, and if so, 
1431      * uses utilities to find all references and update them to use the new refName.
1432      * @throws Exception 
1433      */
1434     protected void handleRefNameReferencesUpdate() throws Exception {
1435         if (hasRefNameUpdate() == true) {
1436             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
1437             RepositoryClient<PoxPayloadIn, PoxPayloadOut> repoClient = getRepositoryClient(ctx);
1438             CoreSessionInterface repoSession = this.getRepositorySession();
1439             
1440             // Update all the relationship records that referred to the old refName
1441             RefNameServiceUtils.updateRefNamesInRelations(ctx, repoClient, repoSession,
1442                     oldRefNameOnUpdate, newRefNameOnUpdate);
1443         }
1444     }
1445     
1446     protected String getRefNameUpdate() {
1447         String result = null;
1448         
1449         if (hasRefNameUpdate() == true) {
1450                 result = newRefNameOnUpdate;
1451                 if (logger.isDebugEnabled() == true) {
1452                         logger.debug(String.format("There was a refName update.  New: %s Old: %s" ,
1453                                         newRefNameOnUpdate, oldRefNameOnUpdate));
1454                 }
1455         }
1456         
1457         return result;
1458     }
1459     
1460     /*
1461      * Note: The Vocabulary document handler overrides this method.
1462      */
1463     protected String getRefPropName() {
1464         return ServiceBindingUtils.AUTH_REF_PROP;
1465     }
1466
1467     
1468     
1469 }