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