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