]> git.aero2k.de Git - tmp/jakarta-migration.git/blob
4ba1a8707a641086670da959d3ce5399c7f1075b
[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_CONTEXT,
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 isProposed                                - 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 isProposed,
602                 boolean isSasItem) throws Exception {
603         Response result = null;
604         
605         // Note: must have the parentShortId, to do the create.
606         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "createAuthorityItem", "CREATE_ITEM", null);
607         AuthorityItemDocumentModelHandler handler = 
608                 (AuthorityItemDocumentModelHandler) createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
609         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
610         handler.setIsProposed(isProposed);
611         handler.setIsSASItem(isSasItem);
612         // Make the client call
613         String itemcsid = getRepositoryClient(ctx).create(ctx, handler);
614         
615         // Build the JAX-RS response
616         UriBuilder path = UriBuilder.fromResource(resourceClass);
617         path.path(parent.CSID + "/items/" + itemcsid);
618         result = Response.created(path.build()).build();
619
620         return result;
621     }
622
623     /**
624      * Called with an existing context.
625      * @param parentCtx
626      * @param parentIdentifier
627      * @param input
628      * @return
629      * @throws Exception
630      */
631     public Response createAuthorityItemWithParentContext(ServiceContext parentCtx,
632                 String parentIdentifier,
633                 PoxPayloadIn input,
634                 boolean shouldUpdateRevNumber,
635                 boolean isProposed,
636                 boolean isSASItem) throws Exception {
637         Response result = null;
638         
639         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input,
640                         parentCtx.getResourceMap(), parentCtx.getUriInfo());
641         if (parentCtx.getCurrentRepositorySession() != null) {
642                 ctx.setCurrentRepositorySession(parentCtx.getCurrentRepositorySession());
643         }
644         result = this.createAuthorityItem(ctx, parentIdentifier, shouldUpdateRevNumber, isProposed, isSASItem);
645
646         return result;
647     }
648         
649     /*************************************************************************
650      * Create an AuthorityItem - this is a sub-resource of Authority
651      * @param specifier either a CSID or one of the urn forms
652      * @return Authority item response
653      *************************************************************************/
654     @POST
655     @Path("{csid}/items")
656     public Response createAuthorityItem(
657                 @Context ResourceMap resourceMap,
658                 @Context UriInfo uriInfo, 
659                 @PathParam("csid") String parentIdentifier, // Either a CSID or a URN form -e.g., a8ad38ec-1d7d-4bf2-bd31 or urn:cspace:name(bugsbunny)
660                 String xmlPayload) {
661         Response result = null;
662         
663         try {
664             PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
665             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo);
666             result = this.createAuthorityItem(ctx, parentIdentifier, AuthorityServiceUtils.UPDATE_REV,
667                         AuthorityServiceUtils.PROPOSED, AuthorityServiceUtils.NOT_SAS_ITEM);
668         } catch (Exception e) {
669             throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
670         }
671         
672         return result;
673     }
674
675     @GET
676     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH)
677     public byte[] getItemWorkflow(
678             @PathParam("csid") String csid,
679             @PathParam("itemcsid") String itemcsid) {
680         PoxPayloadOut result = null;
681
682         try {
683             ServiceContext<PoxPayloadIn, PoxPayloadOut> parentCtx = createServiceContext(getItemServiceName());
684             String parentWorkspaceName = parentCtx.getRepositoryWorkspaceName();
685
686             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME);
687             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
688             ctx.setRespositoryWorkspaceName(parentWorkspaceName); //find the document in the parent's workspace
689             getRepositoryClient(ctx).get(ctx, itemcsid, handler);
690             result = ctx.getOutput();
691         } catch (Exception e) {
692             throw bigReThrow(e, ServiceMessages.READ_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid);
693         }
694         return result.getBytes();
695     }
696
697     //FIXME: This method is almost identical to the method org.collectionspace.services.common.updateWorkflowWithTransition() so
698     // they should be consolidated -be DRY (D)on't (R)epeat (Y)ourself.
699     @PUT
700     @Path("{csid}/items/{itemcsid}" + WorkflowClient.SERVICE_PATH + "/{transition}")
701     public byte[] updateItemWorkflowWithTransition(
702             @Context UriInfo uriInfo,
703             @PathParam("csid") String parentIdentifier,
704             @PathParam("itemcsid") String itemIdentifier,
705             @PathParam("transition") String transition) {
706         PoxPayloadOut result = null;
707         
708         try {
709             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
710             result = updateItemWorkflowWithTransition(ctx, 
711                         parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.UPDATE_REV);
712         } catch (Exception e) {
713             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, parentIdentifier);
714         }
715         
716         return result.getBytes();
717     }
718     
719     /**
720      * Update an authority item's workflow state.
721      * @param existingContext
722      * @param csid
723      * @param itemcsid
724      * @param transition
725      * @return
726      * @throws DocumentReferenceException 
727      */
728     public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext,
729             String parentIdentifier,
730             String itemIdentifier,
731             String transition,
732             boolean updateRevNumber) throws DocumentReferenceException {
733         PoxPayloadOut result = null;
734         
735         try {
736                 //
737                 // We need CSIDs for both the parent authority and the authority item
738                 //
739             CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(existingContext, parentIdentifier, "updateItemWorkflowWithTransition(parent)", "UPDATE_ITEM", null);
740             String itemCsid = lookupItemCSID(existingContext, itemIdentifier, csidAndShortId.CSID, "updateAuthorityItem(item)", "UPDATE_ITEM");
741
742             //
743                 // Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context
744                 //
745                 PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), 
746                                 WorkflowClient.SERVICE_COMMONPART_NAME);
747             MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input);
748             if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
749                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());// If a repo session is already open, we need to use it and not create a new one
750             }
751             //
752             // Create a service context and document handler for the target resource -not the workflow resource itself.
753             //
754             ServiceContext<PoxPayloadIn, PoxPayloadOut> targetCtx = createServiceContext(getItemServiceName(), existingContext.getUriInfo());
755             AuthorityItemDocumentModelHandler targetDocHandler = (AuthorityItemDocumentModelHandler) this.createDocumentHandler(targetCtx);
756             targetDocHandler.setShouldUpdateRevNumber(updateRevNumber);
757             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
758             //
759             // When looking for the document, we need to use the parent/target resource's workspace name -not the "workflow" workspace name
760             //
761             String targetWorkspaceName = targetCtx.getRepositoryWorkspaceName();
762             ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace
763             
764                 // Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler
765             TransitionDef transitionDef = getTransitionDef(targetCtx, transition);
766             ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef);
767             
768             WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx);
769             getRepositoryClient(ctx).update(ctx, itemCsid, handler);
770             result = ctx.getOutput();
771         } catch (DocumentReferenceException de) {
772                 throw de;
773         } catch (Exception e) {
774             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, itemIdentifier);
775         }
776         
777         return result;
778     }
779     
780     private PoxPayloadOut getAuthorityItem(
781                 ServiceContext ctx,
782             String parentIdentifier,
783             String itemIdentifier) throws Exception {
784         PoxPayloadOut result = null;
785         
786         String parentcsid = lookupParentCSID(ctx, parentIdentifier, "getAuthorityItem(parent)", "GET_ITEM", null);
787         // We omit the parentShortId, only needed when doing a create...
788         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createItemDocumentHandler(ctx, parentcsid, null);
789
790         Specifier itemSpec = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem(item)", "GET_ITEM");
791         if (itemSpec.form == SpecifierForm.CSID) {
792             // TODO should we assert that the item is in the passed vocab?
793             getRepositoryClient(ctx).get(ctx, itemSpec.value, handler);
794         } else {
795             String itemWhereClause =
796                         RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid);
797             DocumentFilter myFilter = new NuxeoDocumentFilter(itemWhereClause, 0, 1); // start at page 0 and get 1 item
798             handler.setDocumentFilter(myFilter);
799             getRepositoryClient(ctx).get(ctx, handler);
800         }
801         
802         result = (PoxPayloadOut) ctx.getOutput();
803         if (result != null) {
804                 String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY);
805                 if (inAuthority.equalsIgnoreCase(parentcsid) == false) {
806                         throw new Exception(String.format("Looked up item = '%s' and found with inAuthority = '%s', but expected inAuthority = '%s'.",
807                                         itemSpec.value, inAuthority, parentcsid));
808                 }
809         }
810         
811         return result;
812     }
813
814     public PoxPayloadOut getAuthorityItemWithExistingContext(
815                 ServiceContext existingCtx,
816             String parentIdentifier,
817             String itemIdentifier) throws Exception {
818         PoxPayloadOut result = null;
819         
820         ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo());
821         if (existingCtx.getCurrentRepositorySession() != null) {
822                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); // Reuse the current repo session if one exists
823                 ctx.setProperties(existingCtx.getProperties());
824         }
825         result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
826         
827         return result;
828     }
829     
830     /**
831      * Gets the authority item.
832      * 
833      * @param parentspecifier either a CSID or one of the urn forms
834      * @param itemspecifier either a CSID or one of the urn forms
835      * 
836      * @return the authority item
837      */
838     @GET
839     @Path("{csid}/items/{itemcsid}")
840     public byte[] getAuthorityItem(
841             @Context Request request,
842             @Context UriInfo uriInfo,
843                 @Context ResourceMap resourceMap,            
844             @PathParam("csid") String parentIdentifier,
845             @PathParam("itemcsid") String itemIdentifier) {
846         PoxPayloadOut result = null;
847         try {
848             RemoteServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = 
849                         (RemoteServiceContext<PoxPayloadIn, PoxPayloadOut>) createServiceContext(getItemServiceName(), resourceMap, uriInfo);
850
851             JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource
852             ctx.setJaxRsContext(jaxRsContext);
853             
854             result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier);
855         } catch (DocumentNotFoundException dnf) {
856             throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier));
857         } catch (Exception e) {
858             throw bigReThrow(e, ServiceMessages.GET_FAILED);
859         }
860                 
861         return result.getBytes();
862     }
863
864     /*
865      * Most of the authority child classes will/should use this implementation.  However, the Vocabulary service's item schema is
866      * different enough that it will have to override this method in it's resource class.
867      */
868     @Override
869         protected String getOrderByField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
870                 String result = null;
871
872                 result = NuxeoUtils.getPrimaryElPathPropertyName(
873                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
874                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
875
876                 return result;
877         }
878         
879     @Override
880         protected String getPartialTermMatchField(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx) {
881                 String result = null;
882                 
883                 result = NuxeoUtils.getMultiElPathPropertyName(
884                                 authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(),
885                                 AuthorityItemJAXBSchema.TERM_DISPLAY_NAME);
886
887                 return result;
888         }
889     
890     /**
891      * Gets the authorityItem list for the specified authority
892      * If partialPerm is specified, keywords will be ignored.
893      * 
894      * @param authorityIdentifier either a CSID or one of the urn forms
895      * @param partialTerm if non-null, matches partial terms
896      * @param keywords if non-null, matches terms in the keyword index for items
897      * @param ui passed to include additional parameters, like pagination controls
898      *
899      */
900     public AbstractCommonList getAuthorityItemList(ServiceContext existingContext,
901                 String authorityIdentifier,
902             UriInfo uriInfo) throws Exception {
903         AbstractCommonList result = null;
904         
905         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
906         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
907         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { // Merge some of the existing context properties with our new context
908                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());
909                 ctx.setProperties(existingContext.getProperties());
910         }
911             
912         String orderBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM);
913         String termStatus = queryParams.getFirst(SEARCH_TYPE_TERMSTATUS);
914         String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW);
915         String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS);
916         String partialTerm = queryParams.getFirst(IQueryManager.SEARCH_TYPE_PARTIALTERM);
917
918         // For the wildcard case, parentcsid is null, but docHandler will deal with this.
919         // We omit the parentShortId, only needed when doing a create...
920         String parentcsid = PARENT_WILDCARD.equals(authorityIdentifier) ? null :
921                         lookupParentCSID(ctx, authorityIdentifier, "getAuthorityItemList", "LIST", uriInfo);
922         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler =
923                 createItemDocumentHandler(ctx, parentcsid, null);
924         
925         DocumentFilter myFilter = handler.getDocumentFilter();
926         // If we are not wildcarding the parent, add a restriction
927         if (parentcsid != null) {
928             myFilter.appendWhereClause(authorityItemCommonSchemaName + ":"
929                     + AuthorityItemJAXBSchema.IN_AUTHORITY + "="
930                     + "'" + parentcsid + "'",
931                     IQueryManager.SEARCH_QUALIFIER_AND);
932         }
933
934         if (Tools.notBlank(termStatus)) {
935                 // Start with the qualified termStatus field
936                 String qualifiedTermStatusField = authorityItemCommonSchemaName + ":"
937                     + AuthorityItemJAXBSchema.TERM_STATUS;
938                 String[] filterTerms = termStatus.trim().split("\\|");
939                 String tsClause = QueryManager.createWhereClauseToFilterFromStringList(qualifiedTermStatusField, filterTerms, IQueryManager.FILTER_EXCLUDE);
940             myFilter.appendWhereClause(tsClause, IQueryManager.SEARCH_QUALIFIER_AND);
941         }
942
943         result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm);            
944         
945         return result;
946     }
947     
948     /**
949      * Gets the authorityItem list for the specified authority
950      * If partialPerm is specified, keywords will be ignored.
951      * 
952      * @param authorityIdentifier either a CSID or one of the urn forms
953      * @param partialTerm if non-null, matches partial terms
954      * @param keywords if non-null, matches terms in the keyword index for items
955      * @param ui passed to include additional parameters, like pagination controls
956      * 
957      * @return the authorityItem list
958      */
959     @GET
960     @Path("{csid}/items")
961     @Produces("application/xml")
962     public AbstractCommonList getAuthorityItemList(@PathParam("csid") String authorityIdentifier,
963             @Context UriInfo uriInfo) {
964         AbstractCommonList result = null;
965         
966         try {
967             result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo);    
968         } catch (Exception e) {
969             throw bigReThrow(e, ServiceMessages.LIST_FAILED);
970         }
971         
972         return result;
973     }
974
975     /**
976      * @return the name of the property used to specify references for items in this type of
977      * authority. For most authorities, it is ServiceBindingUtils.AUTH_REF_PROP ("authRef").
978      * Some types (like Vocabulary) use a separate property.
979      */
980     protected String getRefPropName() {
981         return ServiceBindingUtils.AUTH_REF_PROP;
982     }
983     
984     /**
985      * Gets the entities referencing this Authority item instance. The service type
986      * can be passed as a query param "type", and must match a configured type
987      * for the service bindings. If not set, the type defaults to
988      * ServiceBindingUtils.SERVICE_TYPE_PROCEDURE.
989      *
990      * @param parentspecifier either a CSID or one of the urn forms
991      * @param itemspecifier either a CSID or one of the urn forms
992      * @param ui the ui
993      * 
994      * @return the info for the referencing objects
995      */
996     @GET
997     @Path("{csid}/items/{itemcsid}/refObjs")
998     @Produces("application/xml")
999     public AuthorityRefDocList getReferencingObjects(
1000             @PathParam("csid") String parentSpecifier,
1001             @PathParam("itemcsid") String itemSpecifier,
1002             @Context UriTemplateRegistry uriTemplateRegistry,
1003             @Context UriInfo uriInfo) {
1004         AuthorityRefDocList authRefDocList = null;
1005         try {
1006             authRefDocList = getReferencingObjects(null, parentSpecifier, itemSpecifier, uriTemplateRegistry, uriInfo);
1007         } catch (Exception e) {
1008             throw bigReThrow(e, ServiceMessages.GET_FAILED);
1009         }
1010         
1011         if (authRefDocList == null) {
1012             Response response = Response.status(Response.Status.NOT_FOUND).entity(
1013                     "Get failed, the requested Item CSID:" + itemSpecifier + ": was not found.").type(
1014                     "text/plain").build();
1015             throw new CSWebApplicationException(response);
1016         }
1017         return authRefDocList;
1018     }
1019     
1020     public AuthorityRefDocList getReferencingObjects(
1021                 ServiceContext existingContext,
1022             String parentspecifier,
1023             String itemspecifier,
1024             UriTemplateRegistry uriTemplateRegistry,
1025             UriInfo uriInfo) throws Exception {
1026         AuthorityRefDocList authRefDocList = null;
1027  
1028         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1029         MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1030         //
1031         // Merge parts of existing context with our new context
1032         //
1033         if (existingContext != null && existingContext.getCurrentRepositorySession() != null) {
1034                 ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession());  // If one exists, use the existing repo session
1035                 ctx.setProperties(existingContext.getProperties());
1036         }
1037
1038         String parentcsid = lookupParentCSID(ctx, parentspecifier, "getReferencingObjects(parent)", "GET_ITEM_REF_OBJS", uriInfo);
1039         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getReferencingObjects(item)", "GET_ITEM_REF_OBJS");
1040
1041         List<String> serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP);
1042         if (serviceTypes == null || serviceTypes.isEmpty()) {
1043                 serviceTypes = ServiceBindingUtils.getCommonServiceTypes(true); //CSPACE-5359: Should now include objects, procedures, and authorities
1044         }
1045             
1046         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, null);
1047         authRefDocList = handler.getReferencingObjects(ctx, uriTemplateRegistry, serviceTypes, getRefPropName(), itemcsid);
1048
1049         return authRefDocList;
1050     }
1051
1052     /**
1053      * Gets the authority terms used in the indicated Authority item.
1054      *
1055      * @param parentspecifier either a CSID or one of the urn forms
1056      * @param itemspecifier either a CSID or one of the urn forms
1057      * @param ui passed to include additional parameters, like pagination controls
1058      *
1059      * @return the authority refs for the Authority item.
1060      */
1061     @GET
1062     @Path("{csid}/items/{itemcsid}/authorityrefs")
1063     @Produces("application/xml")
1064     public AuthorityRefList getAuthorityItemAuthorityRefs(
1065             @PathParam("csid") String parentspecifier,
1066             @PathParam("itemcsid") String itemspecifier,
1067             @Context UriInfo uriInfo) {
1068         AuthorityRefList authRefList = null;
1069         
1070         try {
1071             // Note that we have to create the service context for the Items, not the main service
1072             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1073             MultivaluedMap<String, String> queryParams = ctx.getQueryParams();
1074             String parentcsid = lookupParentCSID(parentspecifier, "getAuthorityItemAuthRefs(parent)", "GET_ITEM_AUTH_REFS", uriInfo);
1075             // We omit the parentShortId, only needed when doing a create...
1076             DocumentModelHandler<?, AbstractCommonList> handler =
1077                     (DocumentModelHandler<?, AbstractCommonList>)createItemDocumentHandler(ctx, parentcsid, null /*no parent short ID*/);
1078
1079             String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getAuthorityItemAuthRefs(item)", "GET_ITEM_AUTH_REFS");
1080
1081             List<RefNameServiceUtils.AuthRefConfigInfo> authRefsInfo = RefNameServiceUtils.getConfiguredAuthorityRefs(ctx);
1082             authRefList = handler.getAuthorityRefs(itemcsid, authRefsInfo);
1083         } catch (Exception e) {
1084             throw bigReThrow(e, ServiceMessages.GET_FAILED + " parentspecifier: " + parentspecifier + " itemspecifier:" + itemspecifier);
1085         }
1086         
1087         return authRefList;
1088     }
1089     
1090     /**
1091      * Synchronizes a local authority item with a share authority server (SAS) item.
1092      * @param ctx
1093      * @param parentIdentifier
1094      * @param itemIdentifier
1095      * @return
1096      * @throws Exception
1097      */
1098     private PoxPayloadOut synchronizeItem(
1099                 ServiceContext ctx,
1100             String parentIdentifier,
1101             String itemIdentifier) throws Exception {
1102         PoxPayloadOut result = null;
1103         AuthorityItemSpecifier specifier;
1104         boolean neededSync = false;
1105
1106         CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "syncAuthorityItem(parent)", "SYNC_ITEM", null);
1107         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier);
1108         handler.setIsProposed(AuthorityServiceUtils.NOT_PROPOSED); // In case it was formally locally proposed, clear the proposed flag
1109         handler.setIsSASItem(AuthorityServiceUtils.SAS_ITEM); // Since we're sync'ing, this is now a SAS controlled item
1110         // Create an authority item specifier
1111         Specifier parentSpecifier = Specifier.getSpecifier(parent.CSID, "getAuthority", "GET");
1112         Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier, "getAuthorityItem", "GET");
1113         specifier = new AuthorityItemSpecifier(parentSpecifier, itemSpecifier);
1114         //
1115         neededSync = getRepositoryClient(ctx).synchronize(ctx, specifier, handler);
1116         if (neededSync == true) {
1117                 result = (PoxPayloadOut) ctx.getOutput();
1118         }
1119         
1120         return result;
1121     }
1122
1123     /**
1124      * Using the parent and item ID, sync the local item with the SAS (shared authority server)
1125      * Used by the AuthorityItemDocumentModelHandler when synchronizing a list of remote authority items with a
1126      * local authority.  The parent context was created for the authority (parent) because the sync started there.
1127      * @param existingCtx
1128      * @param parentIdentifier
1129      * @param itemIdentifier
1130      * @return
1131      * @throws Exception
1132      */
1133     public PoxPayloadOut synchronizeItemWithExistingContext(
1134                 ServiceContext existingCtx,
1135             String parentIdentifier,
1136             String itemIdentifier
1137             ) throws Exception {
1138         PoxPayloadOut result = null;
1139         
1140         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(),
1141                         existingCtx.getResourceMap(),
1142                         existingCtx.getUriInfo());
1143         if (existingCtx.getCurrentRepositorySession() != null) {
1144                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1145         }
1146         result = synchronizeItem(ctx, parentIdentifier, itemIdentifier);
1147         
1148         return result;
1149     }
1150     
1151     /**
1152      * Synchronizes an authority item and with a Shared Authority Server (SAS) item.
1153      * 
1154      * @param specifier either CSIDs and/or one of the urn forms
1155      * 
1156      * @return the authority item if it was synchronized with SAS
1157      */
1158     @GET
1159     @Path("{csid}/items/{itemcsid}/sync")
1160     public byte[] synchronizeItem(
1161                 @Context ResourceMap resourceMap,
1162             @Context UriInfo uriInfo,
1163             @PathParam("csid") String parentIdentifier,
1164             @PathParam("itemcsid") String itemIdentifier) {
1165         byte[] result;
1166         boolean neededSync = false;
1167         PoxPayloadOut payloadOut = null;
1168         
1169         try {
1170             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), null, resourceMap, uriInfo);
1171             payloadOut = this.synchronizeItem(ctx, parentIdentifier, itemIdentifier);
1172             if (payloadOut != null) {
1173                 neededSync = true;
1174             }
1175         } catch (Exception e) {
1176             throw bigReThrow(e, ServiceMessages.SYNC_FAILED, itemIdentifier);
1177         }
1178
1179         //
1180         // If a sync was needed and was successful, return a copy of the updated resource.  Acts like an UPDATE.
1181         //
1182         if (neededSync == true) {
1183                 result = payloadOut.getBytes();
1184         } else {
1185                 result = String.format("Authority item resource '%s' was already in sync with shared authority server.",
1186                                 itemIdentifier).getBytes();
1187                 Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build();
1188             throw new CSWebApplicationException(response);
1189         }
1190         
1191         return result;
1192     }
1193     
1194     /**
1195      * Update authorityItem.
1196      * 
1197      * @param parentspecifier either a CSID or one of the urn forms
1198      * @param itemspecifier either a CSID or one of the urn forms
1199      *
1200      * @return the multipart output
1201      */
1202     @PUT
1203     @Path("{csid}/items/{itemcsid}")
1204     public byte[] updateAuthorityItem(
1205                 @Context ResourceMap resourceMap, 
1206             @Context UriInfo uriInfo,
1207             @PathParam("csid") String parentSpecifier,
1208             @PathParam("itemcsid") String itemSpecifier,
1209             String xmlPayload) {
1210         PoxPayloadOut result = null;
1211         
1212         try {
1213             PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload);
1214             result = updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate,
1215                         AuthorityServiceUtils.UPDATE_REV,                       // passing TRUE so rev num increases, passing
1216                         AuthorityServiceUtils.NO_CHANGE,        // don't change the state of the "proposed" field -we could be performing a sync or just a plain update
1217                         AuthorityServiceUtils.NO_CHANGE);       // don't change the state of the "sas" field -we could be performing a sync or just a plain update
1218         } catch (Exception e) {
1219             throw bigReThrow(e, ServiceMessages.UPDATE_FAILED);
1220         }
1221         
1222         return result.getBytes();
1223     }
1224     
1225     public PoxPayloadOut updateAuthorityItem(
1226                 ServiceContext itemServiceCtx, // Ok to be null.  Will be null on PUT calls, but not on sync calls
1227                 ResourceMap resourceMap, 
1228             UriInfo uriInfo,
1229             String parentspecifier,
1230             String itemspecifier,
1231             PoxPayloadIn theUpdate,
1232             boolean shouldUpdateRevNumber,
1233             Boolean isProposed,
1234             Boolean isSASItem
1235             ) throws Exception {
1236         PoxPayloadOut result = null;
1237         
1238         CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null);
1239         String parentcsid = csidAndShortId.CSID;
1240         String parentShortId = csidAndShortId.shortIdentifier;
1241         //
1242         // If the itemServiceCtx context is not null, use it.  Otherwise, create a new context
1243         //
1244         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = itemServiceCtx;
1245         if (ctx == null) {
1246                 ctx = createServiceContext(getItemServiceName(), theUpdate, resourceMap, uriInfo);
1247         } else {
1248                 ctx.setInput(theUpdate); // the update payload
1249         }
1250         
1251         String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null
1252
1253         // We omit the parentShortId, only needed when doing a create...
1254         AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, parentShortId);
1255         handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
1256         if (isProposed != null) {
1257                 handler.setIsProposed(isProposed);
1258         }
1259         if (isSASItem != null) {
1260                 handler.setIsSASItem(isSASItem);
1261         }
1262         getRepositoryClient(ctx).update(ctx, itemcsid, handler);
1263         result = ctx.getOutput();
1264
1265         return result;
1266     }
1267
1268     /**
1269      * Delete authorityItem.
1270      * 
1271      * @param parentIdentifier the parentcsid
1272      * @param itemIdentifier the itemcsid
1273      * 
1274      * @return the response
1275      */
1276     @DELETE
1277     @Path("{csid}/items/{itemcsid}")
1278     public Response deleteAuthorityItem(
1279             @Context UriInfo uriInfo,
1280             @PathParam("csid") String parentIdentifier,
1281             @PathParam("itemcsid") String itemIdentifier) {
1282         Response result = null;
1283
1284         ensureCSID(parentIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.parentcsid");
1285         ensureCSID(itemIdentifier, ServiceMessages.DELETE_FAILED, "AuthorityItem.itemcsid");
1286         if (logger.isDebugEnabled()) {
1287             logger.debug("deleteAuthorityItem with parentcsid=" + parentIdentifier + " and itemcsid=" + itemIdentifier);
1288         }
1289         
1290         try {
1291             ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo);
1292             deleteAuthorityItem(ctx, parentIdentifier, itemIdentifier, AuthorityServiceUtils.UPDATE_REV);
1293             result = Response.status(HttpResponseCodes.SC_OK).build();
1294         } catch (Exception e) {
1295             throw bigReThrow(e, ServiceMessages.DELETE_FAILED + "  itemcsid: " + itemIdentifier + " parentcsid:" + parentIdentifier);
1296         }
1297
1298         return result;
1299     }
1300
1301     /**
1302      * 
1303      * @param existingCtx
1304      * @param parentIdentifier
1305      * @param itemIdentifier
1306      * @throws Exception
1307      */
1308     public void deleteAuthorityItem(ServiceContext existingCtx,
1309             String parentIdentifier,
1310             String itemIdentifier,
1311             boolean shouldUpdateRevNumber
1312             ) throws Exception {
1313         Response result = null;
1314         
1315         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo());
1316         if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) {
1317                 ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
1318                 ctx.setProperties(existingCtx.getProperties());
1319         }
1320         
1321         String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1322         String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1323         
1324         DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
1325                 ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, shouldUpdateRevNumber);  // Sometimes we can only soft-delete, so if it is during a sync we dont' update the revision number
1326
1327         getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
1328     }
1329
1330     @GET
1331     @Path("{csid}/items/{itemcsid}/" + hierarchy)
1332     @Produces("application/xml")
1333     public String getHierarchy(
1334                 @PathParam("csid") String parentIdentifier,
1335             @PathParam("itemcsid") String itemIdentifier,
1336             @Context UriInfo uriInfo) throws Exception {
1337         String result = null;
1338
1339         try {
1340                 //
1341             // 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...?
1342                 //
1343             String calledUri = uriInfo.getPath();
1344             String uri = "/" + calledUri.substring(0, (calledUri.length() - ("/" + hierarchy).length()));
1345             ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), uriInfo);
1346             
1347             String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null);
1348             String itemcsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
1349             
1350             String direction = uriInfo.getQueryParameters().getFirst(Hierarchy.directionQP);
1351             if (Tools.notBlank(direction) && Hierarchy.direction_parents.equals(direction)) {
1352                 result = Hierarchy.surface(ctx, itemcsid, uri);
1353             } else {
1354                 result = Hierarchy.dive(ctx, itemcsid, uri);
1355             }            
1356         } catch (Exception e) {
1357             throw bigReThrow(e, "Error showing hierarchy for authority item: ", itemIdentifier);
1358         }
1359         
1360         return result;
1361     }
1362     
1363     /**
1364      * 
1365      * @param tenantId
1366      * @return
1367      */
1368     protected String getItemDocType(String tenantId) {
1369         return getDocType(tenantId, getItemServiceName());
1370     }
1371         
1372     /**
1373      * Returns a UriRegistry entry: a map of tenant-qualified URI templates
1374      * for the current resource, for all tenants
1375      * 
1376      * @return a map of URI templates for the current resource, for all tenants
1377      */
1378     @Override
1379     public Map<UriTemplateRegistryKey,StoredValuesUriTemplate> getUriRegistryEntries() {
1380         Map<UriTemplateRegistryKey,StoredValuesUriTemplate> uriRegistryEntriesMap =
1381                 super.getUriRegistryEntries();
1382         List<String> tenantIds = getTenantBindingsReader().getTenantIds();
1383         for (String tenantId : tenantIds) {
1384                 uriRegistryEntriesMap.putAll(getUriRegistryEntries(tenantId, getItemDocType(tenantId), UriTemplateFactory.ITEM));
1385         }
1386         return uriRegistryEntriesMap;
1387     }
1388   
1389 }