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