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