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