]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
58ddcc3bfd0c0d939670649fc3c53b3067f73371
[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.common.vocabulary;
25
26 import java.util.List;
27 import java.util.Map;
28
29 import javax.ws.rs.Consumes;
30 import javax.ws.rs.DELETE;
31 import javax.ws.rs.GET;
32 import javax.ws.rs.POST;
33 import javax.ws.rs.PUT;
34 import javax.ws.rs.Path;
35 import javax.ws.rs.PathParam;
36 import javax.ws.rs.Produces;
37 import javax.ws.rs.core.Context;
38 import javax.ws.rs.core.MultivaluedMap;
39 import javax.ws.rs.core.Request;
40 import javax.ws.rs.core.Response;
41 import javax.ws.rs.core.UriBuilder;
42 import javax.ws.rs.core.UriInfo;
43 import javax.ws.rs.core.Response.ResponseBuilder;
44
45 import org.collectionspace.services.client.IClientQueryParams;
46 import org.collectionspace.services.client.IQueryManager;
47 import org.collectionspace.services.client.PoxPayloadIn;
48 import org.collectionspace.services.client.PoxPayloadOut;
49 import org.collectionspace.services.client.XmlTools;
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.ResourceMap;
54 import org.collectionspace.services.common.ServiceMain;
55 import org.collectionspace.services.common.ServiceMessages;
56 import org.collectionspace.services.common.StoredValuesUriTemplate;
57 import org.collectionspace.services.common.UriInfoWrapper;
58 import org.collectionspace.services.common.UriTemplateFactory;
59 import org.collectionspace.services.common.UriTemplateRegistry;
60 import org.collectionspace.services.common.UriTemplateRegistryKey;
61 import org.collectionspace.services.common.api.RefName;
62 import org.collectionspace.services.common.api.Tools;
63 import org.collectionspace.services.common.authorityref.AuthorityRefDocList;
64 import org.collectionspace.services.common.authorityref.AuthorityRefList;
65 import org.collectionspace.services.common.context.JaxRsContext;
66 import org.collectionspace.services.common.context.MultipartServiceContext;
67 import org.collectionspace.services.common.context.RemoteServiceContext;
68 import org.collectionspace.services.common.context.ServiceBindingUtils;
69 import org.collectionspace.services.common.context.ServiceContext;
70 import org.collectionspace.services.common.document.DocumentException;
71 import org.collectionspace.services.common.document.DocumentFilter;
72 import org.collectionspace.services.common.document.DocumentHandler;
73 import org.collectionspace.services.common.document.DocumentNotFoundException;
74 import org.collectionspace.services.common.document.DocumentReferenceException;
75 import org.collectionspace.services.common.document.DocumentWrapper;
76 import org.collectionspace.services.common.document.Hierarchy;
77 import org.collectionspace.services.common.query.QueryManager;
78 import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityDocumentModelHandler;
79 import org.collectionspace.services.common.vocabulary.nuxeo.AuthorityItemDocumentModelHandler;
80 import org.collectionspace.services.common.workflow.service.nuxeo.WorkflowDocumentModelHandler;
81 import org.collectionspace.services.config.ClientType;
82 import org.collectionspace.services.config.service.ServiceBindingType;
83 import org.collectionspace.services.jaxb.AbstractCommonList;
84 import org.collectionspace.services.lifecycle.TransitionDef;
85 import org.collectionspace.services.nuxeo.client.java.DocumentModelHandler;
86 import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface;
87 import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentFilter;
88 import org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl;
89 import org.collectionspace.services.nuxeo.util.NuxeoUtils;
90 import org.collectionspace.services.workflow.WorkflowCommon;
91 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.AuthorityItemSpecifier;
92 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.SpecifierForm;
93 import org.collectionspace.services.common.vocabulary.RefNameServiceUtils.Specifier;
94 import org.collectionspace.services.description.ServiceDescription;
95 import org.jboss.resteasy.util.HttpResponseCodes;
96 import org.nuxeo.ecm.core.api.DocumentModel;
97 import org.nuxeo.ecm.core.api.DocumentModelList;
98 import org.slf4j.Logger;
99 import org.slf4j.LoggerFactory;
100
101 /**
102  * The Class AuthorityResource.
103  */
104
105 @Consumes("application/xml")
106 @Produces("application/xml")
107 public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
108         extends NuxeoBasedResource {
109         
110         final static String SEARCH_TYPE_TERMSTATUS = "ts";
111     public final static String hierarchy = "hierarchy";
112
113     protected Class<AuthCommon> authCommonClass;
114     protected Class<?> resourceClass;
115     protected String authorityCommonSchemaName;
116     protected String authorityItemCommonSchemaName;
117     final static ClientType CLIENT_TYPE = ServiceMain.getInstance().getClientType(); //FIXME: REM - 3 Why is this field needed?  I see no references to it.
118         
119     final static String FETCH_SHORT_ID = "_fetch_";
120     public final static String PARENT_WILDCARD = "_ALL_";
121         
122     final Logger logger = LoggerFactory.getLogger(AuthorityResource.class);
123
124     /**
125      * Instantiates a new Authority resource.
126      */
127     public AuthorityResource(Class<AuthCommon> authCommonClass, Class<?> resourceClass,
128             String authorityCommonSchemaName, String authorityItemCommonSchemaName) {
129         this.authCommonClass = authCommonClass;
130         this.resourceClass = resourceClass;
131         this.authorityCommonSchemaName = authorityCommonSchemaName;
132         this.authorityItemCommonSchemaName = authorityItemCommonSchemaName;
133     }
134
135     public abstract String getItemServiceName();
136     
137     public abstract String getItemTermInfoGroupXPathBase();
138
139     @Override
140     protected String getVersionString() {
141         return "$LastChangedRevision: 2617 $";
142     }
143
144     @Override
145     public Class<AuthCommon> getCommonPartClass() {
146         return authCommonClass;
147     }
148
149     /**
150      * Creates the item document handler.
151      * 
152      * @param ctx the ctx
153      * @param inAuthority the in vocabulary
154      * 
155      * @return the document handler
156      * 
157      * @throws Exception the exception
158      */
159     protected DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> createItemDocumentHandler(
160             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
161             String inAuthority, String parentShortIdentifier)
162             throws Exception {
163         String authorityRefNameBase;
164         AuthorityItemDocumentModelHandler<?> docHandler;
165
166         if (parentShortIdentifier == null) {
167             authorityRefNameBase = null;
168         } else {
169             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getServiceName());
170             if (parentShortIdentifier.equals(FETCH_SHORT_ID)) { // We need to fetch this from the repo
171                 if (ctx.getCurrentRepositorySession() != null) {
172                         parentCtx.setCurrentRepositorySession(ctx.getCurrentRepositorySession()); // We need to use the current repo session if one exists
173                 }
174                 // Get from parent document
175                 parentShortIdentifier = getAuthShortIdentifier(parentCtx, inAuthority);
176             }
177             authorityRefNameBase = buildAuthorityRefNameBase(parentCtx, parentShortIdentifier);
178         }
179
180         docHandler = (AuthorityItemDocumentModelHandler<?>) createDocumentHandler(ctx,
181                 ctx.getCommonPartLabel(getItemServiceName()),
182                 authCommonClass);
183         // FIXME - Richard and Aron think the following three lines should
184         // be in the constructor for the AuthorityItemDocumentModelHandler
185         // because all three are required fields.
186         docHandler.setInAuthority(inAuthority);
187         docHandler.setAuthorityRefNameBase(authorityRefNameBase);
188         docHandler.setItemTermInfoGroupXPathBase(getItemTermInfoGroupXPathBase());
189         return docHandler;
190     }
191
192     public String getAuthShortIdentifier(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String authCSID)
193             throws DocumentNotFoundException, DocumentException {
194         String shortIdentifier = null;
195         
196         try {
197             AuthorityDocumentModelHandler<?> handler = (AuthorityDocumentModelHandler<?>) createDocumentHandler(ctx);
198             shortIdentifier = handler.getShortIdentifier(ctx, authCSID, authorityCommonSchemaName);
199         } catch (Exception e) {
200             if (logger.isDebugEnabled()) {
201                 logger.debug("Caught exception ", e);
202             }
203             throw new DocumentException(e);
204         }
205         
206         return shortIdentifier;
207     }
208
209     protected String buildAuthorityRefNameBase(
210             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String shortIdentifier) {
211         RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(),
212                 ctx.getServiceName(), 
213                 null,   // Only use shortId form!!!
214                 shortIdentifier, null);
215         return authority.toString();
216     }
217
218     public static class CsidAndShortIdentifier {
219         String CSID;
220         String shortIdentifier;
221     }
222
223         protected String lookupParentCSID(String parentspecifier, String method,
224                         String op, UriInfo uriInfo) throws Exception {
225                 CsidAndShortIdentifier tempResult = lookupParentCSIDAndShortIdentifer(NULL_CONTEXT,
226                                 parentspecifier, method, op, uriInfo);
227                 return tempResult.CSID;
228         }
229         
230         protected String lookupParentCSID(ServiceContext ctx, String parentspecifier, String method,
231                         String op, UriInfo uriInfo) throws Exception {
232                 CsidAndShortIdentifier tempResult = lookupParentCSIDAndShortIdentifer(ctx,
233                                 parentspecifier, method, op, uriInfo);
234                 return tempResult.CSID;
235         }
236
237
238     private CsidAndShortIdentifier lookupParentCSIDAndShortIdentifer(
239                 ServiceContext existingCtx, // Ok to be null
240                 String parentIdentifier,
241                 String method,
242                 String op,
243                 UriInfo uriInfo)
244             throws Exception {
245         CsidAndShortIdentifier result = new CsidAndShortIdentifier();
246         Specifier parentSpec = Specifier.getSpecifier(parentIdentifier, method, op);
247         
248         String parentcsid;
249         String parentShortIdentifier;
250         if (parentSpec.form == SpecifierForm.CSID) {
251             parentShortIdentifier = null;
252             parentcsid = parentSpec.value;
253             // Uncomment when app layer is ready to integrate
254             // Uncommented since refNames are currently only generated if not present - ADR CSPACE-3178
255             parentShortIdentifier = FETCH_SHORT_ID;
256         } else {
257             parentShortIdentifier = parentSpec.value;
258             String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, parentShortIdentifier);
259             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getServiceName(), uriInfo);
260             CoreSessionInterface repoSession = null;
261             if (existingCtx != null) {
262                 repoSession = (CoreSessionInterface) existingCtx.getCurrentRepositorySession();  // We want to use the thread's current repo session
263             }
264             parentcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, whereClause); //FIXME: REM - If the parent has been soft-deleted, should we be looking for the item?
265         }
266         
267         result.CSID = parentcsid;
268         result.shortIdentifier = parentShortIdentifier;
269         
270         return result;
271     }
272
273     public String lookupItemCSID(ServiceContext<PoxPayloadIn, PoxPayloadOut> existingContext, String itemspecifier, String parentcsid, String method, String op)
274             throws Exception {
275         String itemcsid;
276         
277         Specifier itemSpec = Specifier.getSpecifier(itemspecifier, method, op);
278         if (itemSpec.form == SpecifierForm.CSID) {
279             itemcsid = itemSpec.value;
280         } else {
281             String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
282                 MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(getItemServiceName());
283             CoreSessionInterface repoSession = null;
284             if (existingContext != null) {
285                 repoSession = (CoreSessionInterface) existingContext.getCurrentRepositorySession();  // We want to use the thread's current repo session
286             }
287             itemcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, itemWhereClause); //FIXME: REM - Should we be looking for the 'wf_deleted' query param and filtering on it?
288         }
289         
290         return itemcsid;
291     }
292
293     /*
294      * Generally, callers will first call RefName.AuthorityItem.parse with a refName, and then 
295      * use the returned item.inAuthority.resource and a resourceMap to get a service-specific
296      * Resource. They then call this method on that resource.
297      */
298     @Override
299         public DocumentModel getDocModelForAuthorityItem(CoreSessionInterface repoSession, RefName.AuthorityItem item) 
300                         throws Exception, DocumentNotFoundException {
301         if (item == null) {
302                 return null;
303         }
304         String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, item.getParentShortIdentifier());
305         // Ensure we have the right context.
306         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(item.inAuthority.resource);
307         
308         // HACK - this really must be moved to the doc handler, not here. No Nuxeo specific stuff here!
309         RepositoryClientImpl client = (RepositoryClientImpl)getRepositoryClient(ctx);
310         String parentcsid = client.findDocCSID(repoSession, ctx, whereClause);
311
312         String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, item.getShortIdentifier(), parentcsid);
313         ctx = createServiceContext(getItemServiceName());
314         DocumentWrapper<DocumentModel> docWrapper = client.findDoc(repoSession, ctx, itemWhereClause);
315         DocumentModel docModel = docWrapper.getWrappedObject();
316         return docModel;
317     }
318
319
320     @POST
321     public Response createAuthority(String xmlPayload) {
322         //
323         // Requests to create new authorities come in on new threads. Unfortunately, we need to synchronize those threads on this block because, as of 8/27/2015, we can't seem to get Nuxeo
324         // transaction code to deal with a database level UNIQUE constraint violations on the 'shortidentifier' column of the vocabularies_common table.
325         // Therefore, to prevent having multiple authorities with the same shortid, we need to synchronize
326         // the code that creates new authorities.  The authority document model handler will first check for authorities with the same short id before
327         // trying to create a new authority.
328         //
329         synchronized(AuthorityResource.class) {
330                 try {
331                     PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
332                     ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(input);
333                     DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
334                     
335                     String csid = getRepositoryClient(ctx).create(ctx, handler);
336                     UriBuilder path = UriBuilder.fromResource(resourceClass);
337                     path.path("" + csid);
338                     Response response = Response.created(path.build()).build();
339                     return response;
340                 } catch (Exception e) {
341                     throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
342                 }
343         }
344     }
345
346     protected boolean supportsReplicating(String tenantId, String serviceName) {
347         boolean result = false;
348         
349         ServiceBindingType sb = getTenantBindingsReader().getServiceBinding(tenantId, getServiceName());
350         result = sb.isSupportsReplicating();
351         
352         return result;
353     }
354
355     /**
356      * Synchronizes the authority and its items/terms with a Shared Authority Server.
357      * 
358      * @param specifier either a CSID or one of the urn forms
359      * 
360      * @return the authority
361      */
362     @POST
363     @Path("{csid}/sync")
364     public byte[] synchronize(
365             @Context Request request,
366             @Context UriInfo uriInfo,
367             @PathParam("csid") String identifier) {
368         uriInfo = new UriInfoWrapper(uriInfo);
369         byte[] result;
370         boolean neededSync = false;
371         PoxPayloadOut payloadOut = null;
372         Specifier specifier;
373         
374         //
375         // Prevent multiple SAS synchronizations from occurring simultaneously by synchronizing this method.
376         //
377         synchronized(AuthorityResource.class) {        
378                 try {
379                     ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(uriInfo);
380                     /*
381                      * Make sure this authority service supports synchronization
382                      */
383                     if (supportsReplicating(ctx.getTenantId(), ctx.getServiceName()) == false) {
384                         throw new DocumentException(Response.Status.FORBIDDEN.getStatusCode());
385                     }
386                     AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler)createDocumentHandler(ctx);
387                     specifier = Specifier.getSpecifier(identifier, "getAuthority", "GET");
388                     handler.setShouldUpdateRevNumber(AuthorityServiceUtils.DONT_UPDATE_REV); // Never update rev number on sync calls
389                     neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
390                     payloadOut = ctx.getOutput();
391                 } catch (Exception e) {
392                     throw bigReThrow(e, ServiceMessages.SYNC_FAILED, identifier);
393                 }
394         
395                 //
396                 // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
397                 //
398                 if (neededSync == true) {
399                         result = payloadOut.getBytes();
400                 } else {
401                         result = String.format("Authority resource '%s' was already in sync with shared authority server.",
402                                         specifier.value).getBytes();
403                         Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
404                     throw new CSWebApplicationException(response);
405                 }
406         }
407                 
408         return result;
409     }
410         
411     /**
412      * Gets the authority.
413      * 
414      * @param specifier either a CSID or one of the urn forms
415      * 
416      * @return the authority
417      */
418     @GET
419     @Path("{csid}")
420     @Override
421     public Response get(
422             @Context Request request,
423             @Context UriInfo uriInfo,
424             @PathParam("csid") String specifier) {
425         Response result = null;
426         uriInfo = new UriInfoWrapper(uriInfo);
427         PoxPayloadOut payloadout = null;
428         
429         try {
430             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(request, uriInfo);
431             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
432
433             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
434             if (spec.form == SpecifierForm.CSID) {
435                 if (logger.isDebugEnabled()) {
436                     logger.debug("getAuthority with csid=" + spec.value);
437                 }
438                 getRepositoryClient(ctx).get(ctx, spec.value, handler);
439             } else {
440                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
441                 DocumentFilter myFilter = new NuxeoDocumentFilter(whereClause, 0, 1);
442                 handler.setDocumentFilter(myFilter);
443                 getRepositoryClient(ctx).get(ctx, handler);
444             }
445             
446             payloadout = ctx.getOutput();
447             ResponseBuilder responseBuilder = Response.ok(payloadout.getBytes());
448             this.setCacheControl(ctx, responseBuilder);
449             result = responseBuilder.build();            
450         } catch (Exception e) {
451             throw bigReThrow(e, ServiceMessages.GET_FAILED, specifier);
452         }
453
454         if (result == null) {
455             Response response = Response.status(Response.Status.NOT_FOUND).entity(
456                     "GET request failed. The requested Authority specifier:" + specifier + ": was not found.").type(
457                     "text/plain").build();
458             throw new CSWebApplicationException(response);
459         }
460
461         return result;
462     }
463
464     /**
465      * Finds and populates the authority list.
466      * 
467      * @param ui the ui
468      * 
469      * @return the authority list
470      */
471     @GET
472     @Produces("application/xml")
473     public AbstractCommonList getAuthorityList(@Context UriInfo uriInfo) { //FIXME - REM 5/3/2012 - This is not reachable from the JAX-RS dispatcher.  Instead the equivalent method in ResourceBase is getting called.
474         uriInfo = new UriInfoWrapper(uriInfo);
475         AbstractCommonList result = null;
476         
477         try {
478             MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
479             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(uriInfo);
480             
481             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
482             DocumentFilter myFilter = handler.getDocumentFilter();
483             // Need to make the default sort order for authority items
484             // be on the displayName field
485             String sortBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
486             if (sortBy == null || sortBy.isEmpty()) {
487                 String qualifiedDisplayNameField = authorityCommonSchemaName + ":"
488                         + AuthorityItemJAXBSchema.DISPLAY_NAME;
489                 myFilter.setOrderByClause(qualifiedDisplayNameField);
490             }
491             String nameQ = queryParams.getFirst("refName");
492             if (nameQ != null) {
493                 myFilter.setWhereClause(authorityCommonSchemaName + ":refName='" + nameQ + "'");
494             }
495             getRepositoryClient(ctx).getFiltered(ctx, handler);
496             result = handler.getCommonPartList();
497         } catch (Exception e) {
498             throw bigReThrow(e, ServiceMessages.GET_FAILED);
499         }
500         
501         return result;
502     }
503     
504     /**
505      * Overriding this methods to see if we should update the revision number during the update.  We don't
506      * want to update the rev number of synchronization operations.
507      */
508     @Override
509     protected PoxPayloadOut update(String csid,
510             PoxPayloadIn theUpdate, // not used in this method, but could be used by an overriding method
511             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx)
512             throws Exception {
513         AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler) createDocumentHandler(ctx);
514         Boolean shouldUpdateRev = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
515         if (shouldUpdateRev != null) {
516                 handler.setShouldUpdateRevNumber(shouldUpdateRev);
517         }
518         getRepositoryClient(ctx).update(ctx, csid, handler);
519         return ctx.getOutput();
520     }
521     
522     /**
523      * Update authority.
524      *
525      * @param specifier the csid or id
526      *
527      * @return the multipart output
528      */
529     @PUT
530     @Path("{csid}")
531     public byte[] updateAuthority(
532             @PathParam("csid") String specifier,
533             String xmlPayload) {
534         PoxPayloadOut result = null;
535         try {
536             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
537             Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE");
538             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(theUpdate);
539             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
540             String csid;
541             if (spec.form == SpecifierForm.CSID) {
542                 csid = spec.value;
543             } else {
544                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
545                 csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause);
546             }
547             getRepositoryClient(ctx).update(ctx, csid, handler);
548             result = ctx.getOutput();
549         } catch (Exception e) {
550             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
551         }
552         return result.getBytes();
553     }
554
555     /**
556      * Delete authority.
557      * 
558      * @param csid the csid
559      * 
560      * @return the response
561      */
562     @Deprecated
563 //    @DELETE
564     @Path("{csid}")
565     public Response old_deleteAuthority(@PathParam("csid") String csid) {
566         if (logger.isDebugEnabled()) {
567             logger.debug("deleteAuthority with csid=" + csid);
568         }
569         try {
570             ensureCSID(csid, ServiceMessages.DELETE_FAILED, "Authority.csid");
571             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext();
572             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
573             getRepositoryClient(ctx).delete(ctx, csid, handler);
574             return Response.status(HttpResponseCodes.SC_OK).build();
575         } catch (Exception e) {
576             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid);
577         }
578     }
579     
580     /**
581      * Delete authority
582      * 
583      * @param csid the csid or a URN specifier form -e.g., urn:cspace:name(OurMuseumPersonAuthority)
584      * 
585      * @return the response
586      */
587     @DELETE
588     @Path("{csid}")
589     public Response deleteAuthority(
590             @Context Request request,
591             @Context UriInfo uriInfo,
592             @PathParam("csid") String specifier) {
593         uriInfo = new UriInfoWrapper(uriInfo);
594
595         if (logger.isDebugEnabled()) {
596             logger.debug("deleteAuthority with specifier=" + specifier);
597         }
598         
599         try {
600             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(uriInfo);
601             DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
602
603             Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET");
604             if (spec.form == SpecifierForm.CSID) {
605                 if (logger.isDebugEnabled()) {
606                     logger.debug("deleteAuthority with csid=" + spec.value);
607                 }
608                 ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid");
609                 getRepositoryClient(ctx).delete(ctx, spec.value, handler);
610             } else {
611                 if (logger.isDebugEnabled()) {
612                     logger.debug("deleteAuthority with specifier=" + spec.value);
613                 }               
614                 String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value);
615                 getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler);
616             }
617             
618             return Response.status(HttpResponseCodes.SC_OK).build();
619         } catch (Exception e) {
620             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier);
621         }
622     }
623     
624     /**
625      * 
626      * @param ctx
627      * @param parentspecifier           - ID of the container. Can be URN or CSID form
628      * @param shouldUpdateRevNumber - Indicates if the revision number should be updated on create -won't do this when synching with SAS
629      * @param isProposed                                - In a shared authority context, indicates if this item just a proposed item and not yet part of the SAS authority
630      * @return
631      * @throws Exception
632      */
633     protected Response createAuthorityItem(ServiceContext ctx, String parentIdentifier,
634                 boolean shouldUpdateRevNumber,
635                 boolean isProposed,
636                 boolean isSasItem) throws Exception {
637         Response result = null;
638         
639         // Note: must have the parentShortId, to do the create.
640         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "createAuthorityItem", "CREATE_ITEM", null);
641         AuthorityItemDocumentModelHandler handler = 
642                 (AuthorityItemDocumentModelHandler) createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
643         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
644         handler.setIsProposed(isProposed);
645         handler.setIsSASItem(isSasItem);
646         // Make the client call
647         String itemcsid = getRepositoryClient(ctx).create(ctx, handler);
648         
649         // Build the JAX-RS response
650         UriBuilder path = UriBuilder.fromResource(resourceClass);
651         path.path(parent.CSID + "/items/" + itemcsid);
652         result = Response.created(path.build()).build();
653
654         return result;
655     }
656
657     /**
658      * Called with an existing context.
659      * @param parentCtx
660      * @param parentIdentifier
661      * @param input
662      * @return
663      * @throws Exception
664      */
665     public Response createAuthorityItemWithParentContext(ServiceContext parentCtx,
666                 String parentIdentifier,
667                 PoxPayloadIn input,
668                 boolean shouldUpdateRevNumber,
669                 boolean isProposed,
670                 boolean isSASItem) throws Exception {
671         Response result = null;
672         
673         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input,
674                         parentCtx.getResourceMap(), parentCtx.getUriInfo());
675         if (parentCtx.getCurrentRepositorySession() != null) {
676                 ctx.setCurrentRepositorySession(parentCtx.getCurrentRepositorySession());
677         }
678         result = this.createAuthorityItem(ctx, parentIdentifier, shouldUpdateRevNumber, isProposed, isSASItem);
679
680         return result;
681     }
682         
683     /*************************************************************************
684      * Create an AuthorityItem - this is a sub-resource of Authority
685      * @param specifier either a CSID or one of the urn forms
686      * @return Authority item response
687      *************************************************************************/
688     @POST
689     @Path("{csid}/items")
690     public Response createAuthorityItem(
691                 @Context ResourceMap resourceMap,
692                 @Context UriInfo uriInfo, 
693                 @PathParam("csid") String parentIdentifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
694                 String xmlPayload) {
695         uriInfo = new UriInfoWrapper(uriInfo);
696         Response result = null;
697         
698         try {
699             PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
700             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo);
701             result = this.createAuthorityItem(ctx, parentIdentifier, AuthorityServiceUtils.UPDATE_REV,
702                         AuthorityServiceUtils.PROPOSED, AuthorityServiceUtils.NOT_SAS_ITEM);
703         } catch (Exception e) {
704             throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
705         }
706         
707         return result;
708     }
709
710     @GET
711     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH)
712     public byte[] getItemWorkflow(
713             @PathParam("csid") String csid,
714             @PathParam("itemcsid") String itemcsid) {
715         PoxPayloadOut result = null;
716
717         try {
718             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getItemServiceName());
719             String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
720
721             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME);
722             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
723             ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
724             getRepositoryClient(ctx).get(ctx, itemcsid, handler);
725             result = ctx.getOutput();
726         } catch (Exception e) {
727             throw bigReThrow(e, ServiceMessages.READ_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
728         }
729         return result.getBytes();
730     }
731
732     //FIXME: This method is almost identical to the method org.collectionspace.services.common.updateWorkflowWithTransition() so
733     // they should be consolidated -be DRY (D)on't (R)epeat (Y)ourself.
734     @PUT
735     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH + "/{transition}")
736     public byte[] updateItemWorkflowWithTransition(
737             @Context UriInfo uriInfo,
738             @PathParam("csid") String parentIdentifier,
739             @PathParam("itemcsid") String itemIdentifier,
740             @PathParam("transition") String transition) {
741         uriInfo = new UriInfoWrapper(uriInfo);
742         PoxPayloadOut result = null;
743         
744         try {
745             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
746             result = updateItemWorkflowWithTransition(ctx, 
747                         parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.UPDATE_REV);
748         } catch (Exception e) {
749             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, parentIdentifier);
750         }
751         
752         return result.getBytes();
753     }
754     
755     /**
756      * Update an authority item's workflow state.
757      * @param existingContext
758      * @param csid
759      * @param itemcsid
760      * @param transition
761      * @return
762      * @throws DocumentReferenceException 
763      */
764     public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext,
765             String parentIdentifier,
766             String itemIdentifier,
767             String transition,
768             boolean updateRevNumber) throws DocumentReferenceException {
769         PoxPayloadOut result = null;
770         
771         try {
772                 //
773                 // We need CSIDs for both the parent authority and the authority item
774                 //
775             CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(existingContext, parentIdentifier, "updateItemWorkflowWithTransition(parent)", "UPDATE_ITEM", null);
776             String itemCsid = lookupItemCSID(existingContext, itemIdentifier, csidAndShortId.CSID, "updateAuthorityItem(item)", "UPDATE_ITEM");
777
778             //
779                 // Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context
780                 //
781                 PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), 
782                                 WorkflowClient.SERVICE_COMMONPART_NAME);
783             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input);
784             if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
785                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());// If a repo session is already open, we need to use it and not create a new one
786             }
787             //
788             // Create a service context and document handler for the target resource -not the workflow resource itself.
789             //
790             ServiceContext<PoxPayloadIn, PoxPayloadOut> targetCtx = createServiceContext(getItemServiceName(), existingContext.getUriInfo());
791             AuthorityItemDocumentModelHandler targetDocHandler = (AuthorityItemDocumentModelHandler) this.createDocumentHandler(targetCtx);
792             targetDocHandler.setShouldUpdateRevNumber(updateRevNumber);
793             ctx.setProperty(WorkflowClient.TARGET_DOCHANDLER, targetDocHandler); //added as a context param for the workflow document handler -it will call the parent's dochandler "prepareForWorkflowTranstion" method
794             //
795             // When looking for the document, we need to use the parent/target resource's workspace name -not the "workflow" workspace name
796             //
797             String targetWorkspaceName = targetCtx.getRepositoryWorkspaceName();
798             ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace
799             
800                 // Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
801             TransitionDef transitionDef = getTransitionDef(targetCtx, transition);
802             if (transitionDef == null) {
803                 throw new DocumentException(String.format("The document with ID='%s' does not support the workflow transition '%s'.",
804                                 itemIdentifier, transition));
805             }
806             ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef);
807             
808             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
809             getRepositoryClient(ctx).update(ctx, itemCsid, handler);
810             result = ctx.getOutput();
811         } catch (DocumentReferenceException de) {
812                 throw de;
813         } catch (Exception e) {
814             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, itemIdentifier);
815         }
816         
817         return result;
818     }
819     
820     private PoxPayloadOut getAuthorityItem(
821                 ServiceContext ctx,
822             String parentIdentifier,
823             String itemIdentifier) throws Exception {
824         PoxPayloadOut result = null;
825         
826         String parentcsid = lookupParentCSID(ctx, parentIdentifier, "getAuthorityItem(parent)", "GET_ITEM", null);
827         // We omit the parentShortId, only needed when doing a create...
828         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createItemDocumentHandler(ctx, parentcsid, null);
829
830         Specifier itemSpec = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem(item)", "GET_ITEM");
831         if (itemSpec.form == SpecifierForm.CSID) {
832             // TODO should we assert that the item is in the passed vocab?
833             getRepositoryClient(ctx).get(ctx, itemSpec.value, handler);
834         } else {
835             String itemWhereClause =
836                         RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
837             DocumentFilter myFilter = new NuxeoDocumentFilter(itemWhereClause, 0, 1); // start at page 0 and get 1 item
838             handler.setDocumentFilter(myFilter);
839             getRepositoryClient(ctx).get(ctx, handler);
840         }
841         
842         result = (PoxPayloadOut) ctx.getOutput();
843         if (result != null) {
844                 String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY);
845                 if (inAuthority.equalsIgnoreCase(parentcsid) == false) {
846                         throw new Exception(String.format("Looked up item = '%s' and found with inAuthority = '%s', but expected inAuthority = '%s'.",
847                                         itemSpec.value, inAuthority, parentcsid));
848                 }
849         }
850         
851         return result;
852     }
853
854     public PoxPayloadOut getAuthorityItemWithExistingContext(
855                 ServiceContext existingCtx,
856             String parentIdentifier,
857             String itemIdentifier) throws Exception {
858         PoxPayloadOut result = null;
859         
860         ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo());
861         if (existingCtx.getCurrentRepositorySession() != null) {
862                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); // Reuse the current repo session if one exists
863                 ctx.setProperties(existingCtx.getProperties());
864         }
865         result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
866         
867         return result;
868     }
869     
870     /**
871      * Gets the authority item.
872      * 
873      * @param parentspecifier either a CSID or one of the urn forms
874      * @param itemspecifier either a CSID or one of the urn forms
875      * 
876      * @return the authority item
877      */
878     @GET
879     @Path("{csid}/items/{itemcsid}")
880     public byte[] getAuthorityItem(
881             @Context Request request,
882             @Context UriInfo uriInfo,
883                 @Context ResourceMap resourceMap,            
884             @PathParam("csid") String parentIdentifier,
885             @PathParam("itemcsid") String itemIdentifier) {
886         uriInfo = new UriInfoWrapper(uriInfo);
887         PoxPayloadOut result = null;
888         try {
889             RemoteServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = 
890                         (RemoteServiceContext<PoxPayloadIn, PoxPayloadOut>) createServiceContext(getItemServiceName(), resourceMap, uriInfo);
891
892             JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource
893             ctx.setJaxRsContext(jaxRsContext);
894             
895             result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
896         } catch (DocumentNotFoundException dnf) {
897             throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier));
898         } catch (Exception e) {
899             throw bigReThrow(e, ServiceMessages.GET_FAILED);
900         }
901                 
902         return result.getBytes();
903     }
904
905     /*
906      * Most of the authority child classes will/should use this implementation.  However, the Vocabulary service's item schema is
907      * different enough that it will have to override this method in it's resource class.
908      */
909     @Override
910         protected String getOrderByField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
911                 String result = null;
912
913                 result = NuxeoUtils.getPrimaryElPathPropertyName(
914                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
915                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
916
917                 return result;
918         }
919         
920     @Override
921         protected String getPartialTermMatchField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
922                 String result = null;
923                 
924                 result = NuxeoUtils.getMultiElPathPropertyName(
925                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
926                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
927
928                 return result;
929         }
930     
931     /**
932      * Gets the authorityItem list for the specified authority
933      * If partialPerm is specified, keywords will be ignored.
934      * 
935      * @param authorityIdentifier either a CSID or one of the urn forms
936      * @param partialTerm if non-null, matches partial terms
937      * @param keywords if non-null, matches terms in the keyword index for items
938      * @param ui passed to include additional parameters, like pagination controls
939      *
940      */
941     public AbstractCommonList getAuthorityItemList(ServiceContext existingContext,
942                 String authorityIdentifier,
943             UriInfo uriInfo) throws Exception {
944         AbstractCommonList result = null;
945         
946         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
947         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
948         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { // Merge some of the existing context properties with our new context
949                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());
950                 ctx.setProperties(existingContext.getProperties());
951         }
952             
953         String orderBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
954         String termStatus = queryParams.getFirst(SEARCH_TYPE_TERMSTATUS);
955         String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW);
956         String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS);
957         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
958
959         // For the wildcard case, parentcsid is null, but docHandler will deal with this.
960         // We omit the parentShortId, only needed when doing a create...
961         String parentcsid = PARENT_WILDCARD.equals(authorityIdentifier) ? null :
962                         lookupParentCSID(ctx, authorityIdentifier, "getAuthorityItemList", "LIST", uriInfo);
963         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler =
964                 createItemDocumentHandler(ctx, parentcsid, null);
965         
966         DocumentFilter myFilter = handler.getDocumentFilter();
967         // If we are not wildcarding the parent, add a restriction
968         if (parentcsid != null) {
969             myFilter.appendWhereClause(authorityItemCommonSchemaName + ":"
970                     + AuthorityItemJAXBSchema.IN_AUTHORITY + "="
971                     + "'" + parentcsid + "'",
972                     IQueryManager.SEARCH_QUALIFIER_AND);
973         }
974
975         if (Tools.notBlank(termStatus)) {
976                 // Start with the qualified termStatus field
977                 String qualifiedTermStatusField = authorityItemCommonSchemaName + ":"
978                     + AuthorityItemJAXBSchema.TERM_STATUS;
979                 String[] filterTerms = termStatus.trim().split("\\|");
980                 String tsClause = QueryManager.createWhereClauseToFilterFromStringList(qualifiedTermStatusField, filterTerms, IQueryManager.FILTER_EXCLUDE);
981             myFilter.appendWhereClause(tsClause, IQueryManager.SEARCH_QUALIFIER_AND);
982         }
983
984         result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm);            
985         
986         return result;
987     }
988     
989     /**
990      * Gets the authorityItem list for the specified authority
991      * If partialPerm is specified, keywords will be ignored.
992      * 
993      * @param authorityIdentifier either a CSID or one of the urn forms
994      * @param partialTerm if non-null, matches partial terms
995      * @param keywords if non-null, matches terms in the keyword index for items
996      * @param ui passed to include additional parameters, like pagination controls
997      * 
998      * @return the authorityItem list
999      */
1000     @GET
1001     @Path("{csid}/items")
1002     @Produces("application/xml")
1003     public AbstractCommonList getAuthorityItemList(@PathParam("csid") String authorityIdentifier,
1004             @Context UriInfo uriInfo) {
1005         uriInfo = new UriInfoWrapper(uriInfo);
1006         AbstractCommonList result = null;
1007         
1008         try {
1009             result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo);    
1010         } catch (Exception e) {
1011             throw bigReThrow(e, ServiceMessages.LIST_FAILED);
1012         }
1013         
1014         return result;
1015     }
1016
1017     /**
1018      * @return the name of the property used to specify references for items in this type of
1019      * authority. For most authorities, it is ServiceBindingUtils.AUTH_REF_PROP ("authRef").
1020      * Some types (like Vocabulary) use a separate property.
1021      */
1022     protected String getRefPropName() {
1023         return ServiceBindingUtils.AUTH_REF_PROP;
1024     }
1025     
1026     /**
1027      * Gets the entities referencing this Authority item instance. The service type
1028      * can be passed as a query param "type", and must match a configured type
1029      * for the service bindings. If not set, the type defaults to
1030      * ServiceBindingUtils.SERVICE_TYPE_PROCEDURE.
1031      *
1032      * @param parentspecifier either a CSID or one of the urn forms
1033      * @param itemspecifier either a CSID or one of the urn forms
1034      * @param ui the ui
1035      * 
1036      * @return the info for the referencing objects
1037      */
1038     @GET
1039     @Path("{csid}/items/{itemcsid}/refObjs")
1040     @Produces("application/xml")
1041     public AuthorityRefDocList getReferencingObjects(
1042             @PathParam("csid") String parentSpecifier,
1043             @PathParam("itemcsid") String itemSpecifier,
1044             @Context UriTemplateRegistry uriTemplateRegistry,
1045             @Context UriInfo uriInfo) {
1046         uriInfo = new UriInfoWrapper(uriInfo);
1047         AuthorityRefDocList authRefDocList = null;
1048         try {
1049             authRefDocList = getReferencingObjects(null, parentSpecifier, itemSpecifier, uriTemplateRegistry, uriInfo);
1050         } catch (Exception e) {
1051             throw bigReThrow(e, ServiceMessages.GET_FAILED);
1052         }
1053         
1054         if (authRefDocList == null) {
1055             Response response = Response.status(Response.Status.NOT_FOUND).entity(
1056                     "Get failed, the requested Item CSID:" + itemSpecifier + ": was not found.").type(
1057                     "text/plain").build();
1058             throw new CSWebApplicationException(response);
1059         }
1060         return authRefDocList;
1061     }
1062     
1063     public AuthorityRefDocList getReferencingObjects(
1064                 ServiceContext existingContext,
1065             String parentspecifier,
1066             String itemspecifier,
1067             UriTemplateRegistry uriTemplateRegistry,
1068             UriInfo uriInfo) throws Exception {
1069         //uriInfo = new UriInfoWrapper(uriInfo);
1070         AuthorityRefDocList authRefDocList = null;
1071  
1072         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1073         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1074         //
1075         // Merge parts of existing context with our new context
1076         //
1077         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
1078                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());  // If one exists, use the existing repo session
1079                 ctx.setProperties(existingContext.getProperties());
1080         }
1081
1082         String parentcsid = lookupParentCSID(ctx, parentspecifier, "getReferencingObjects(parent)", "GET_ITEM_REF_OBJS", uriInfo);
1083         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getReferencingObjects(item)", "GET_ITEM_REF_OBJS");
1084         
1085         // Remove the "type" property from the query params
1086         List<String> serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP);        
1087         if (serviceTypes == null || serviceTypes.isEmpty()) {
1088                 serviceTypes = ServiceBindingUtils.getCommonServiceTypes(true); //CSPACE-5359: Should now include objects, procedures, and authorities
1089         }
1090             
1091         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, null);
1092         authRefDocList = handler.getReferencingObjects(ctx, uriTemplateRegistry, serviceTypes, getRefPropName(), itemcsid);
1093
1094         return authRefDocList;
1095     }
1096
1097     /**
1098      * Gets the authority terms used in the indicated Authority item.
1099      *
1100      * @param parentspecifier either a CSID or one of the urn forms
1101      * @param itemspecifier either a CSID or one of the urn forms
1102      * @param ui passed to include additional parameters, like pagination controls
1103      *
1104      * @return the authority refs for the Authority item.
1105      */
1106     @GET
1107     @Path("{csid}/items/{itemcsid}/authorityrefs")
1108     @Produces("application/xml")
1109     public AuthorityRefList getAuthorityItemAuthorityRefs(
1110             @PathParam("csid") String parentspecifier,
1111             @PathParam("itemcsid") String itemspecifier,
1112             @Context UriInfo uriInfo) {
1113         uriInfo = new UriInfoWrapper(uriInfo);
1114         AuthorityRefList authRefList = null;
1115         
1116         try {
1117             // Note that we have to create the service context for the Items, not the main service
1118             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1119             MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1120             String parentcsid = lookupParentCSID(parentspecifier, "getAuthorityItemAuthRefs(parent)", "GET_ITEM_AUTH_REFS", uriInfo);
1121             // We omit the parentShortId, only needed when doing a create...
1122             DocumentModelHandler<?, AbstractCommonList> handler =
1123                     (DocumentModelHandler<?, AbstractCommonList>)createItemDocumentHandler(ctx, parentcsid, null /*no parent short ID*/);
1124
1125             String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getAuthorityItemAuthRefs(item)", "GET_ITEM_AUTH_REFS");
1126
1127             List<RefNameServiceUtils.AuthRefConfigInfo> authRefsInfo = RefNameServiceUtils.getConfiguredAuthorityRefs(ctx);
1128             authRefList = handler.getAuthorityRefs(itemcsid, authRefsInfo);
1129         } catch (Exception e) {
1130             throw bigReThrow(e, ServiceMessages.GET_FAILED + " parentspecifier: " + parentspecifier + " itemspecifier:" + itemspecifier);
1131         }
1132         
1133         return authRefList;
1134     }
1135     
1136     /**
1137      * Synchronizes a local authority item with a share authority server (SAS) item.
1138      * @param ctx
1139      * @param parentIdentifier
1140      * @param itemIdentifier
1141      * @return
1142      * @throws Exception
1143      */
1144     @SuppressWarnings("unchecked")
1145         private PoxPayloadOut synchronizeItem(
1146                 ServiceContext ctx,
1147             String parentIdentifier,
1148             String itemIdentifier,
1149             boolean syncHierarchicalRelationships) throws Exception {
1150         PoxPayloadOut result = null;
1151         AuthorityItemSpecifier specifier;
1152         boolean neededSync = false;
1153
1154         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "syncAuthorityItem(parent)", "SYNC_ITEM", null);
1155         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
1156         handler.setIsProposed(AuthorityServiceUtils.NOT_PROPOSED); // In case it was formally locally proposed, clear the proposed flag
1157         handler.setIsSASItem(AuthorityServiceUtils.SAS_ITEM); // Since we're sync'ing, this is now a SAS controlled item
1158         handler.setShouldSyncHierarchicalRelationships(syncHierarchicalRelationships);
1159         // Create an authority item specifier
1160         Specifier parentSpecifier = Specifier.getSpecifier(parent.CSID, "getAuthority", "GET");
1161         Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem", "GET");
1162         specifier = new AuthorityItemSpecifier(parentSpecifier, itemSpecifier);
1163         //
1164         neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
1165         if (neededSync == true) {
1166                 result = (PoxPayloadOut) ctx.getOutput();
1167         }
1168         
1169         return result;
1170     }
1171
1172     /**
1173      * Using the parent and item ID, sync the local item with the SAS (shared authority server)
1174      * Used by the AuthorityItemDocumentModelHandler when synchronizing a list of remote authority items with a
1175      * local authority.  The parent context was created for the authority (parent) because the sync started there.
1176      * @param existingCtx
1177      * @param parentIdentifier
1178      * @param itemIdentifier
1179      * @return
1180      * @throws Exception
1181      */
1182     public PoxPayloadOut synchronizeItemWithExistingContext(
1183                 ServiceContext existingCtx,
1184             String parentIdentifier,
1185             String itemIdentifier,
1186             boolean syncHierarchicalRelationships
1187             ) throws Exception {
1188         PoxPayloadOut result = null;
1189         
1190         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(),
1191                         existingCtx.getResourceMap(),
1192                         existingCtx.getUriInfo());
1193         if (existingCtx.getCurrentRepositorySession() != null) {
1194                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1195                 
1196         }
1197         result = synchronizeItem(ctx, parentIdentifier, itemIdentifier, syncHierarchicalRelationships);
1198         
1199         return result;
1200     }
1201     
1202     /**
1203      * Synchronizes an authority item and with a Shared Authority Server (SAS) item.
1204      * 
1205      * @param specifier either CSIDs and/or one of the urn forms
1206      * 
1207      * @return the authority item if it was updated/synchronized with SAS item; otherwise empty
1208      */
1209     @POST
1210     @Path("{csid}/items/{itemcsid}/sync")
1211     public byte[] synchronizeItem(
1212                 @Context ResourceMap resourceMap,
1213             @Context UriInfo uriInfo,
1214             @PathParam("csid") String parentIdentifier,
1215             @PathParam("itemcsid") String itemIdentifier) {
1216         uriInfo = new UriInfoWrapper(uriInfo);
1217         byte[] result;
1218         boolean neededSync = false;
1219         PoxPayloadOut payloadOut = null;
1220         
1221         try {
1222             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), null, resourceMap, uriInfo);
1223             payloadOut = this.synchronizeItem(ctx, parentIdentifier, itemIdentifier, true);
1224             if (payloadOut != null) {
1225                 neededSync = true;
1226             }
1227         } catch (Exception e) {
1228             throw bigReThrow(e, ServiceMessages.SYNC_FAILED, itemIdentifier);
1229         }
1230
1231         //
1232         // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
1233         //
1234         if (neededSync == true) {
1235                 result = payloadOut.getBytes();
1236         } else {
1237                 result = String.format("Authority item resource '%s' was already in sync with shared authority server.",
1238                                 itemIdentifier).getBytes();
1239                 Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
1240             throw new CSWebApplicationException(response);
1241         }
1242         
1243         return result;
1244     }
1245     
1246     /**
1247      * Update authorityItem.
1248      * 
1249      * @param parentspecifier either a CSID or one of the urn forms
1250      * @param itemspecifier either a CSID or one of the urn forms
1251      *
1252      * @return the multipart output
1253      */
1254     @PUT
1255     @Path("{csid}/items/{itemcsid}")
1256     public byte[] updateAuthorityItem(
1257                 @Context ResourceMap resourceMap, 
1258             @Context UriInfo uriInfo,
1259             @PathParam("csid") String parentSpecifier,
1260             @PathParam("itemcsid") String itemSpecifier,
1261             String xmlPayload) {
1262         uriInfo = new UriInfoWrapper(uriInfo);
1263         PoxPayloadOut result = null;
1264         
1265         try {
1266             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
1267             result = updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate,
1268                         AuthorityServiceUtils.UPDATE_REV,                       // passing TRUE so rev num increases, passing
1269                         AuthorityServiceUtils.NO_CHANGE,        // don't change the state of the "proposed" field -we could be performing a sync or just a plain update
1270                         AuthorityServiceUtils.NO_CHANGE);       // don't change the state of the "sas" field -we could be performing a sync or just a plain update
1271         } catch (Exception e) {
1272             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
1273         }
1274         
1275         return result.getBytes();
1276     }
1277     
1278     public PoxPayloadOut updateAuthorityItem(
1279                 ServiceContext itemServiceCtx, // Ok to be null.  Will be null on PUT calls, but not on sync calls
1280                 ResourceMap resourceMap, 
1281             UriInfo uriInfo,
1282             String parentspecifier,
1283             String itemspecifier,
1284             PoxPayloadIn theUpdate,
1285             boolean shouldUpdateRevNumber,
1286             Boolean isProposed,
1287             Boolean isSASItem
1288             ) throws Exception {
1289         PoxPayloadOut result = null;
1290         
1291         CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null);
1292         String parentcsid = csidAndShortId.CSID;
1293         String parentShortId = csidAndShortId.shortIdentifier;
1294         //
1295         // If the itemServiceCtx context is not null, use it.  Otherwise, create a new context
1296         //
1297         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = itemServiceCtx;
1298         if (ctx == null) {
1299                 ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo);
1300         } else {
1301                 ctx.setInput(theUpdate); // the update payload
1302         }
1303         
1304         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null
1305
1306         // We omit the parentShortId, only needed when doing a create...
1307         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId);
1308         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
1309         if (isProposed != null) {
1310                 handler.setIsProposed(isProposed);
1311         }
1312         if (isSASItem != null) {
1313                 handler.setIsSASItem(isSASItem);
1314         }
1315         getRepositoryClient(ctx).update(ctx, itemcsid, handler);
1316         result = ctx.getOutput();
1317
1318         return result;
1319     }
1320
1321     /**
1322      * Delete authorityItem.
1323      * 
1324      * @param parentIdentifier the parentcsid
1325      * @param itemIdentifier the itemcsid
1326      * 
1327      * @return the response
1328      */
1329     @DELETE
1330     @Path("{csid}/items/{itemcsid}")
1331     public Response deleteAuthorityItem(
1332             @Context UriInfo uriInfo,
1333             @PathParam("csid") String parentIdentifier,
1334             @PathParam("itemcsid") String itemIdentifier) {
1335         uriInfo = new UriInfoWrapper(uriInfo);
1336         Response result = null;
1337
1338         ensureCSID(parentIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.parentcsid");
1339         ensureCSID(itemIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.itemcsid");
1340         if (logger.isDebugEnabled()) {
1341             logger.debug("deleteAuthorityItem with parentcsid=" + parentIdentifier + " and itemcsid=" + itemIdentifier);
1342         }
1343         
1344         try {
1345             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
1346             deleteAuthorityItem(ctx, parentIdentifier, itemIdentifier, AuthorityServiceUtils.UPDATE_REV);
1347             result = Response.status(HttpResponseCodes.SC_OK).build();
1348         } catch (Exception e) {
1349             throw bigReThrow(e, ServiceMessages.DELETE_FAILED + "  itemcsid: " + itemIdentifier + " parentcsid:" + parentIdentifier);
1350         }
1351
1352         return result;
1353     }
1354
1355     /**
1356      * 
1357      * @param existingCtx
1358      * @param parentIdentifier
1359      * @param itemIdentifier
1360      * @throws Exception
1361      */
1362     @SuppressWarnings("rawtypes")
1363         public boolean deleteAuthorityItem(ServiceContext existingCtx,
1364             String parentIdentifier,
1365             String itemIdentifier,
1366             boolean shouldUpdateRevNumber
1367             ) throws Exception {
1368         boolean result = true;
1369         
1370         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo());
1371         if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) {
1372                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1373                 ctx.setProperties(existingCtx.getProperties());
1374         }
1375         
1376         String parentcsid = null;
1377         try {
1378                 parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1379         } catch (DocumentNotFoundException de) {
1380                 logger.warn(String.format("Could not find parent with ID='%s' when trying to delete item ID='%s'",
1381                                 parentIdentifier, itemIdentifier));
1382         }
1383         String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1384         
1385         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler) createDocumentHandler(ctx);
1386         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
1387         result = getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
1388         
1389         return result;
1390     }
1391
1392     @GET
1393     @Path("{csid}/items/{itemcsid}/" + hierarchy)
1394     @Produces("application/xml")
1395     public String getHierarchy(
1396                 @PathParam("csid") String parentIdentifier,
1397             @PathParam("itemcsid") String itemIdentifier,
1398             @Context UriInfo uriInfo) throws Exception {
1399         uriInfo = new UriInfoWrapper(uriInfo);
1400         String result = null;
1401
1402         try {
1403                 //
1404             // All items in dive can look at their child uri's to get uri.  So we calculate the very first one.  We could also do a GET and look at the common part uri field, but why...?
1405                 //
1406             String calledUri = uriInfo.getPath();
1407             String uri = "/" + calledUri.substring(0, (calledUri.length() - ("/" + hierarchy).length()));
1408             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1409             
1410             String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1411             String itemcsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1412             
1413             String direction = uriInfo.getQueryParameters().getFirst(Hierarchy.directionQP);
1414             if (Tools.notBlank(direction) && Hierarchy.direction_parents.equals(direction)) {
1415                 result = Hierarchy.surface(ctx, itemcsid, uri);
1416             } else {
1417                 result = Hierarchy.dive(ctx, itemcsid, uri);
1418             }            
1419         } catch (Exception e) {
1420             throw bigReThrow(e, "Error showing hierarchy for authority item: ", itemIdentifier);
1421         }
1422         
1423         return result;
1424     }
1425     
1426     /**
1427      * 
1428      * @param tenantId
1429      * @return
1430      */
1431     public String getItemDocType(String tenantId) {
1432         return getDocType(tenantId, getItemServiceName());
1433     }
1434         
1435     /**
1436      * Returns a UriRegistry entry: a map of tenant-qualified URI templates
1437      * for the current resource, for all tenants
1438      * 
1439      * @return a map of URI templates for the current resource, for all tenants
1440      */
1441     @Override
1442     public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries() {
1443         Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
1444                 super.getUriRegistryEntries();
1445         List<String> tenantIds = getTenantBindingsReader().getTenantIds();
1446         for (String tenantId : tenantIds) {
1447                 uriRegistryEntriesMap.putAll(getUriRegistryEntries(tenantId, getItemDocType(tenantId), UriTemplateFactory.ITEM));
1448         }
1449         return uriRegistryEntriesMap;
1450     }
1451     
1452     /**
1453      * 
1454      */
1455     public ServiceDescription getDescription(ServiceContext ctx) {
1456         ServiceDescription result = super.getDescription(ctx);
1457         result.setSubresourceDocumentType(this.getItemDocType(ctx.getTenantId()));
1458         return result;
1459     }    
1460     
1461 }