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