From 69efae19bf644737a8406a4c2c951cc61111c982 Mon Sep 17 00:00:00 2001 From: Ray Lee Date: Tue, 8 Oct 2019 12:58:51 -0700 Subject: [PATCH] DRYD-765: Sync items and relations in one pass. --- .../common/vocabulary/AuthorityResource.java | 360 +++++++++--------- .../vocabulary/AuthorityServiceUtils.java | 127 +++++- .../nuxeo/AuthorityDocumentModelHandler.java | 274 ++++++------- .../AuthorityItemDocumentModelHandler.java | 113 +----- 4 files changed, 435 insertions(+), 439 deletions(-) diff --git a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java index 3c52763d9..efca5dceb 100644 --- a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java +++ b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java @@ -115,12 +115,12 @@ import org.w3c.dom.Element; @Produces("application/xml") public abstract class AuthorityResource extends NuxeoBasedResource { - + final Logger logger = LoggerFactory.getLogger(AuthorityResource.class); final static String SEARCH_TYPE_TERMSTATUS = "ts"; public final static String hierarchy = "hierarchy"; - + private static final Integer PAGE_NUM_FROM_QUERYPARAMS = null; private static final Integer PAGE_SIZE_FROM_QUERYPARAMS = null; @@ -129,12 +129,12 @@ public abstract class AuthorityResource protected String authorityCommonSchemaName; protected String authorityItemCommonSchemaName; final static ClientType CLIENT_TYPE = ServiceMain.getInstance().getClientType(); //FIXME: REM - 3 Why is this field needed? I see no references to it. - + final static String FETCH_SHORT_ID = "_fetch_"; public final static String PARENT_WILDCARD = "_ALL_"; protected static final boolean DONT_INCLUDE_ITEMS = false; protected static final boolean INCLUDE_ITEMS = true; - + /** * Instantiates a new Authority resource. */ @@ -147,7 +147,7 @@ public abstract class AuthorityResource } public abstract String getItemServiceName(); - + public abstract String getItemTermInfoGroupXPathBase(); @Override @@ -162,12 +162,12 @@ public abstract class AuthorityResource /** * Creates the item document handler. - * + * * @param ctx the ctx * @param inAuthority the in vocabulary - * + * * @return the document handler - * + * * @throws Exception the exception */ protected DocumentHandler createItemDocumentHandler( @@ -206,7 +206,7 @@ public abstract class AuthorityResource public String getAuthShortIdentifier(ServiceContext ctx, String authCSID) throws DocumentNotFoundException, DocumentException { String shortIdentifier = null; - + try { AuthorityDocumentModelHandler handler = (AuthorityDocumentModelHandler) createDocumentHandler(ctx); shortIdentifier = handler.getShortIdentifier(ctx, authCSID, authorityCommonSchemaName); @@ -216,14 +216,14 @@ public abstract class AuthorityResource } throw new DocumentException(e); } - + return shortIdentifier; } protected String buildAuthorityRefNameBase( ServiceContext ctx, String shortIdentifier) { RefName.Authority authority = RefName.Authority.buildAuthority(ctx.getTenantName(), - ctx.getServiceName(), + ctx.getServiceName(), null, // Only use shortId form!!! shortIdentifier, null); return authority.toString(); @@ -240,7 +240,7 @@ public abstract class AuthorityResource parentspecifier, method, op, uriInfo); return tempResult.CSID; } - + protected String lookupParentCSID(ServiceContext ctx, String parentspecifier, String method, String op, UriInfo uriInfo) throws Exception { CsidAndShortIdentifier tempResult = lookupParentCSIDAndShortIdentifer(ctx, @@ -258,7 +258,7 @@ public abstract class AuthorityResource throws Exception { CsidAndShortIdentifier result = new CsidAndShortIdentifier(); Specifier parentSpec = Specifier.getSpecifier(parentIdentifier, method, op); - + String parentcsid; String parentShortIdentifier; if (parentSpec.form == SpecifierForm.CSID) { @@ -277,40 +277,42 @@ public abstract class AuthorityResource } parentcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, whereClause); //FIXME: REM - If the parent has been soft-deleted, should we be looking for the item? } - + result.CSID = parentcsid; result.shortIdentifier = parentShortIdentifier; - + return result; } - public String lookupItemCSID(ServiceContext existingContext, String itemspecifier, String parentcsid, String method, String op) + public String lookupItemCSID(ServiceContext existingContext, String itemSpecifier, String parentCsid, String method, String op) throws Exception { - String itemcsid; - - Specifier itemSpec = Specifier.getSpecifier(itemspecifier, method, op); + String itemCsid; + Specifier itemSpec = Specifier.getSpecifier(itemSpecifier, method, op); + if (itemSpec.form == SpecifierForm.CSID) { - itemcsid = itemSpec.value; + itemCsid = itemSpec.value; } else { - String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentcsid); - MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(getItemServiceName()); CoreSessionInterface repoSession = null; + MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(getItemServiceName()); + if (existingContext != null) { repoSession = (CoreSessionInterface) existingContext.getCurrentRepositorySession(); // We want to use the thread's current repo session } - itemcsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, itemWhereClause); //FIXME: REM - Should we be looking for the 'wf_deleted' query param and filtering on it? + + String itemWhereClause = RefNameServiceUtils.buildWhereForAuthItemByName(authorityItemCommonSchemaName, itemSpec.value, parentCsid); + itemCsid = getRepositoryClient(ctx).findDocCSID(repoSession, ctx, itemWhereClause); //FIXME: REM - Should we be looking for the 'wf_deleted' query param and filtering on it? } - - return itemcsid; + + return itemCsid; } /* - * Generally, callers will first call RefName.AuthorityItem.parse with a refName, and then + * Generally, callers will first call RefName.AuthorityItem.parse with a refName, and then * use the returned item.inAuthority.resource and a resourceMap to get a service-specific * Resource. They then call this method on that resource. */ @Override - public DocumentModel getDocModelForAuthorityItem(CoreSessionInterface repoSession, RefName.AuthorityItem item) + public DocumentModel getDocModelForAuthorityItem(CoreSessionInterface repoSession, RefName.AuthorityItem item) throws Exception, DocumentNotFoundException { if (item == null) { return null; @@ -318,7 +320,7 @@ public abstract class AuthorityResource String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, item.getParentShortIdentifier()); // Ensure we have the right context. ServiceContext ctx = createServiceContext(item.inAuthority.resource); - + // HACK - this really must be moved to the doc handler, not here. No Nuxeo specific stuff here! NuxeoRepositoryClientImpl client = (NuxeoRepositoryClientImpl)getRepositoryClient(ctx); String parentcsid = client.findDocCSID(repoSession, ctx, whereClause); @@ -348,7 +350,7 @@ public abstract class AuthorityResource PoxPayloadIn input = new PoxPayloadIn(xmlPayload); ServiceContext ctx = createServiceContext(input); DocumentHandler handler = createDocumentHandler(ctx); - + String csid = getRepositoryClient(ctx).create(ctx, handler); UriBuilder path = UriBuilder.fromResource(resourceClass); path.path("" + csid); @@ -362,18 +364,18 @@ public abstract class AuthorityResource protected boolean supportsReplicating(String tenantId, String serviceName) { boolean result = false; - + ServiceBindingType sb = getTenantBindingsReader().getServiceBinding(tenantId, getServiceName()); result = sb.isSupportsReplicating(); - + return result; } /** * Synchronizes the authority and its items/terms with a Shared Authority Server. - * + * * @param specifier either a CSID or one of the urn forms - * + * * @return the authority */ @POST @@ -387,11 +389,11 @@ public abstract class AuthorityResource boolean neededSync = false; PoxPayloadOut payloadOut = null; Specifier specifier; - + // // Prevent multiple SAS synchronizations from occurring simultaneously by synchronizing this method. // - synchronized(AuthorityResource.class) { + synchronized(AuthorityResource.class) { try { ServiceContext ctx = createServiceContext(uriInfo); /* @@ -408,7 +410,7 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.SYNC_FAILED, identifier); } - + // // If a sync was needed and was successful, return a copy of the updated resource. Acts like an UPDATE. // @@ -421,28 +423,28 @@ public abstract class AuthorityResource throw new CSWebApplicationException(response); } } - + return result; } - + /* * Builds a cached JAX-RS response. */ protected Response buildResponse(ServiceContext ctx, PoxPayloadOut payloadOut) { Response result = null; - + ResponseBuilder responseBuilder = Response.ok(payloadOut.getBytes()); this.setCacheControl(ctx, responseBuilder); - result = responseBuilder.build(); + result = responseBuilder.build(); return result; } /** * Gets the authority. - * + * * @param specifier either a CSID or one of the urn forms - * + * * @return the authority */ @GET @@ -457,7 +459,7 @@ public abstract class AuthorityResource uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut payloadout = null; - try { + try { // // If the specifier is a fully qualified authority term refname, then return the term payload in the response // @@ -484,7 +486,7 @@ public abstract class AuthorityResource return result; } - + protected PoxPayloadOut getAuthority( ServiceContext ctx, Request request, @@ -493,7 +495,7 @@ public abstract class AuthorityResource boolean includeItems) throws Exception { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut payloadout = null; - + DocumentHandler docHandler = createDocumentHandler(ctx); Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET"); if (spec.form == SpecifierForm.CSID) { @@ -515,13 +517,13 @@ public abstract class AuthorityResource } return payloadout; - } + } /** * Finds and populates the authority list. - * + * * @param ui the ui - * + * * @return the authority list */ @GET @@ -529,11 +531,11 @@ public abstract class AuthorityResource 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. uriInfo = new UriInfoWrapper(uriInfo); AbstractCommonList result = null; - + try { MultivaluedMap queryParams = uriInfo.getQueryParameters(); ServiceContext ctx = createServiceContext(uriInfo); - + DocumentHandler handler = createDocumentHandler(ctx); DocumentFilter myFilter = handler.getDocumentFilter(); // Need to make the default sort order for authority items @@ -549,16 +551,16 @@ public abstract class AuthorityResource myFilter.setWhereClause(authorityCommonSchemaName + ":refName='" + nameQ + "'"); } //getRepositoryClient(ctx).getFiltered(ctx, handler); # Something here? - String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS); + String advancedSearch = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_AS); result = search(ctx, handler, uriInfo, orderBy, null, advancedSearch, null); result = handler.getCommonPartList(); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED); } - + return result; } - + /** * Overriding this methods to see if we should update the revision number during the update. We don't * want to update the rev number of synchronization operations. @@ -576,7 +578,7 @@ public abstract class AuthorityResource getRepositoryClient(ctx).update(ctx, csid, handler); return ctx.getOutput(); } - + /** * Update authority. * @@ -589,7 +591,7 @@ public abstract class AuthorityResource public byte[] updateAuthority( @Context Request request, @Context ResourceMap resourceMap, - @Context UriInfo uriInfo, + @Context UriInfo uriInfo, @PathParam("csid") String specifier, String xmlPayload) { PoxPayloadOut result = null; @@ -606,10 +608,10 @@ public abstract class AuthorityResource } return result.getBytes(); } - + /** * Delete all the items in an authority list. - * + * * @param specifier * @param uriInfo * @return @@ -623,7 +625,7 @@ public abstract class AuthorityResource try { ServiceContext ctx = createServiceContext(uriInfo); RepositoryClient repoClient = this.getRepositoryClient(ctx); - + CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx); try { DocumentHandler handler = createDocumentHandler(ctx); @@ -646,12 +648,12 @@ public abstract class AuthorityResource throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier); } } - + /** * Delete authority - * + * * @param csid the csid or a URN specifier form -e.g., urn:cspace:name(OurMuseumPersonAuthority) - * + * * @return the response */ @DELETE @@ -665,12 +667,12 @@ public abstract class AuthorityResource if (logger.isDebugEnabled()) { logger.debug("deleteAuthority with specifier=" + specifier); } - + try { ServiceContext ctx = createServiceContext(uriInfo); Specifier spec = Specifier.getSpecifier(specifier, "getAuthority", "GET"); RepositoryClient repoClient = this.getRepositoryClient(ctx); - + CoreSessionInterface repoSession = repoClient.getRepositorySession(ctx); try { DocumentHandler handler = createDocumentHandler(ctx); @@ -694,7 +696,7 @@ public abstract class AuthorityResource } else { if (logger.isDebugEnabled()) { logger.debug("deleteAuthority with specifier=" + spec.value); - } + } String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, spec.value); getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler); } @@ -710,10 +712,10 @@ public abstract class AuthorityResource throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier); } } - + protected String getCsid(ListItem item) { String result = null; - + for (Element ele : item.getAny()) { String elementName = ele.getTagName().toLowerCase(); if (elementName.equals("csid")) { @@ -721,12 +723,12 @@ public abstract class AuthorityResource break; } } - + return result; } /** - * + * * @param ctx * @param parentspecifier - ID of the container. Can be URN or CSID form * @param shouldUpdateRevNumber - Indicates if the revision number should be updated on create -won't do this when synching with SAS @@ -739,17 +741,17 @@ public abstract class AuthorityResource boolean isProposed, boolean isSasItem) throws Exception { Response result = null; - + // Note: must have the parentShortId, to do the create. CsidAndShortIdentifier parent = lookupParentCSIDAndShortIdentifer(ctx, parentIdentifier, "createAuthorityItem", "CREATE_ITEM", null); - AuthorityItemDocumentModelHandler handler = + AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler) createItemDocumentHandler(ctx, parent.CSID, parent.shortIdentifier); handler.setShouldUpdateRevNumber(shouldUpdateRevNumber); handler.setIsProposed(isProposed); handler.setIsSASItem(isSasItem); // Make the client call String itemcsid = getRepositoryClient(ctx).create(ctx, handler); - + // Build the JAX-RS response UriBuilder path = UriBuilder.fromResource(resourceClass); path.path(parent.CSID + "/items/" + itemcsid); @@ -757,10 +759,10 @@ public abstract class AuthorityResource return result; } - + public PoxPayloadOut updateAuthorityItem( ServiceContext itemServiceCtx, // Ok to be null. Will be null on PUT calls, but not on sync calls - ResourceMap resourceMap, + ResourceMap resourceMap, UriInfo uriInfo, String parentspecifier, String itemspecifier, @@ -770,7 +772,7 @@ public abstract class AuthorityResource Boolean isSASItem ) throws Exception { PoxPayloadOut result = null; - + CsidAndShortIdentifier csidAndShortId = lookupParentCSIDAndShortIdentifer(itemServiceCtx, parentspecifier, "updateAuthorityItem(parent)", "UPDATE_ITEM", null); String parentcsid = csidAndShortId.CSID; String parentShortId = csidAndShortId.shortIdentifier; @@ -783,7 +785,7 @@ public abstract class AuthorityResource } else { ctx.setInput(theUpdate); // the update payload } - + String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "updateAuthorityItem(item)", "UPDATE_ITEM"); //use itemServiceCtx if it is not null // We omit the parentShortId, only needed when doing a create... @@ -803,12 +805,12 @@ public abstract class AuthorityResource handler.setIsSASItem(isSASItem); } } - + getRepositoryClient(ctx).update(ctx, itemcsid, handler); result = ctx.getOutput(); return result; - } + } /** * Called with an existing context. @@ -825,7 +827,7 @@ public abstract class AuthorityResource boolean isProposed, boolean isSASItem) throws Exception { Response result = null; - + ServiceContext ctx = createServiceContext(getItemServiceName(), input, parentCtx.getResourceMap(), parentCtx.getUriInfo()); if (parentCtx.getCurrentRepositorySession() != null) { @@ -835,7 +837,7 @@ public abstract class AuthorityResource return result; } - + /************************************************************************* * Create an AuthorityItem - this is a sub-resource of Authority * @param specifier either a CSID or one of the urn forms @@ -850,7 +852,7 @@ public abstract class AuthorityResource String xmlPayload) { uriInfo = new UriInfoWrapper(uriInfo); Response result = null; - + try { PoxPayloadIn input = new PoxPayloadIn(xmlPayload); ServiceContext ctx = createServiceContext(getItemServiceName(), input, resourceMap, uriInfo); @@ -896,7 +898,7 @@ public abstract class AuthorityResource @PathParam("csid") String specifier, @PathParam("transition") String transition) { PoxPayloadOut result = null; - + Specifier spec = Specifier.getSpecifier(specifier, "updateAuthority", "UPDATE"); String csid = null; try { @@ -905,10 +907,10 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, csid); } - + return result.getBytes(); } - + //FIXME: This method is almost identical to the method org.collectionspace.services.common.updateWorkflowWithTransition() so // they should be consolidated -be DRY (D)on't (R)epeat (Y)ourself. @PUT @@ -920,18 +922,18 @@ public abstract class AuthorityResource @PathParam("transition") String transition) { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut result = null; - + try { ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); - result = updateItemWorkflowWithTransition(ctx, + result = updateItemWorkflowWithTransition(ctx, parentIdentifier, itemIdentifier, transition, AuthorityServiceUtils.UPDATE_REV); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, parentIdentifier); } - + return result.getBytes(); } - + /** * Update an authority item's workflow state. * @param existingContext @@ -939,7 +941,7 @@ public abstract class AuthorityResource * @param itemcsid * @param transition * @return - * @throws DocumentReferenceException + * @throws DocumentReferenceException */ public PoxPayloadOut updateItemWorkflowWithTransition(ServiceContext existingContext, String parentIdentifier, @@ -947,7 +949,7 @@ public abstract class AuthorityResource String transition, boolean updateRevNumber) throws DocumentReferenceException { PoxPayloadOut result = null; - + try { // // We need CSIDs for both the parent authority and the authority item @@ -958,7 +960,7 @@ public abstract class AuthorityResource // // Create an empty workflow_commons input part and set it into a new "workflow" sub-resource context // - PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), + PoxPayloadIn input = new PoxPayloadIn(WorkflowClient.SERVICE_PAYLOAD_NAME, new WorkflowCommon(), WorkflowClient.SERVICE_COMMONPART_NAME); MultipartServiceContext ctx = (MultipartServiceContext) createServiceContext(WorkflowClient.SERVICE_NAME, input); if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { @@ -976,7 +978,7 @@ public abstract class AuthorityResource // String targetWorkspaceName = targetCtx.getRepositoryWorkspaceName(); ctx.setRespositoryWorkspaceName(targetWorkspaceName); //find the document in the parent's workspace - + // Get the type of transition we're being asked to make and store it as a context parameter -used by the workflow document handler TransitionDef transitionDef = getTransitionDef(targetCtx, transition); if (transitionDef == null) { @@ -984,7 +986,7 @@ public abstract class AuthorityResource itemIdentifier, transition)); } ctx.setProperty(WorkflowClient.TRANSITION_ID, transitionDef); - + WorkflowDocumentModelHandler handler = createWorkflowDocumentHandler(ctx); getRepositoryClient(ctx).update(ctx, itemCsid, handler); result = ctx.getOutput(); @@ -993,16 +995,16 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.UPDATE_FAILED + WorkflowClient.SERVICE_PAYLOAD_NAME, itemIdentifier); } - + return result; } - + protected PoxPayloadOut getAuthorityItem( ServiceContext ctx, String parentIdentifier, String itemIdentifier) throws Exception { PoxPayloadOut result = null; - + String parentcsid = lookupParentCSID(ctx, parentIdentifier, "getAuthorityItem(parent)", "GET_ITEM", null); // We omit the parentShortId, only needed when doing a create... DocumentHandler handler = createItemDocumentHandler(ctx, parentcsid, null); @@ -1018,7 +1020,7 @@ public abstract class AuthorityResource handler.setDocumentFilter(myFilter); getRepositoryClient(ctx).get(ctx, handler); } - + result = (PoxPayloadOut) ctx.getOutput(); if (result != null) { String inAuthority = XmlTools.getElementValue(result.getDOMDocument(), "//" + AuthorityItemJAXBSchema.IN_AUTHORITY); @@ -1027,7 +1029,7 @@ public abstract class AuthorityResource itemSpec.value, inAuthority, parentcsid)); } } - + return result; } @@ -1036,23 +1038,23 @@ public abstract class AuthorityResource String parentIdentifier, String itemIdentifier) throws Exception { PoxPayloadOut result = null; - + ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo()); if (existingCtx.getCurrentRepositorySession() != null) { ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); // Reuse the current repo session if one exists ctx.setProperties(existingCtx.getProperties()); } result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier); - + return result; } - + /** * Gets the authority item. - * + * * @param parentspecifier either a CSID or one of the urn forms * @param itemspecifier either a CSID or one of the urn forms - * + * * @return the authority item */ @GET @@ -1060,66 +1062,66 @@ public abstract class AuthorityResource public byte[] getAuthorityItem( @Context Request request, @Context UriInfo uriInfo, - @Context ResourceMap resourceMap, + @Context ResourceMap resourceMap, @PathParam("csid") String parentIdentifier, @PathParam("itemcsid") String itemIdentifier) { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut result = null; result = this.getAuthorityItemPayload(request, uriInfo, resourceMap, parentIdentifier, itemIdentifier); - + return result.getBytes(); } - - + + public PoxPayloadOut getAuthorityItemPayload( @Context Request request, @Context UriInfo uriInfo, - @Context ResourceMap resourceMap, + @Context ResourceMap resourceMap, @PathParam("csid") String parentIdentifier, @PathParam("itemcsid") String itemIdentifier) { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut result = null; try { - RemoteServiceContext ctx = + RemoteServiceContext ctx = (RemoteServiceContext) createServiceContext(getItemServiceName(), resourceMap, uriInfo); JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource ctx.setJaxRsContext(jaxRsContext); - + result = getAuthorityItem(ctx, parentIdentifier, itemIdentifier); } catch (DocumentNotFoundException dnf) { throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier)); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED); } - + return result; } - + public Response getAuthorityItemResponse( @Context Request request, @Context UriInfo uriInfo, - @Context ResourceMap resourceMap, + @Context ResourceMap resourceMap, @PathParam("csid") String parentIdentifier, @PathParam("itemcsid") String itemIdentifier) { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut payloadout = null; RemoteServiceContext ctx = null; - + try { ctx = (RemoteServiceContext) createServiceContext(getItemServiceName(), resourceMap, uriInfo); JaxRsContext jaxRsContext = new JaxRsContext(request, uriInfo); // Needed for getting account permissions part of the resource ctx.setJaxRsContext(jaxRsContext); - + payloadout = getAuthorityItem(ctx, parentIdentifier, itemIdentifier); } catch (DocumentNotFoundException dnf) { throw bigReThrow(dnf, ServiceMessages.resourceNotFoundMsg(itemIdentifier)); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED); } - + return buildResponse(ctx, payloadout); } @@ -1138,22 +1140,22 @@ public abstract class AuthorityResource return result; } - + @Override protected String getPartialTermMatchField(ServiceContext ctx) { String result = null; - + result = NuxeoUtils.getMultiElPathPropertyName( authorityItemCommonSchemaName, getItemTermInfoGroupXPathBase(), AuthorityItemJAXBSchema.TERM_DISPLAY_NAME); return result; } - + /** * Gets the authorityItem list for the specified authority * If partialPerm is specified, keywords will be ignored. - * + * * @param authorityIdentifier either a CSID or one of the urn forms * @param partialTerm if non-null, matches partial terms * @param keywords if non-null, matches terms in the keyword index for items @@ -1164,14 +1166,14 @@ public abstract class AuthorityResource String authorityIdentifier, UriInfo uriInfo) throws Exception { AbstractCommonList result = null; - + ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); MultivaluedMap queryParams = ctx.getQueryParams(); if (existingContext != null && existingContext.getCurrentRepositorySession() != null) { // Merge some of the existing context properties with our new context ctx.setCurrentRepositorySession(existingContext.getCurrentRepositorySession()); ctx.setProperties(existingContext.getProperties()); } - + String orderBy = queryParams.getFirst(IClientQueryParams.ORDER_BY_PARAM); String termStatus = queryParams.getFirst(SEARCH_TYPE_TERMSTATUS); String keywords = queryParams.getFirst(IQueryManager.SEARCH_TYPE_KEYWORDS_KW); @@ -1184,7 +1186,7 @@ public abstract class AuthorityResource lookupParentCSID(ctx, authorityIdentifier, "getAuthorityItemList", "LIST", uriInfo); DocumentHandler handler = createItemDocumentHandler(ctx, parentcsid, null); - + DocumentFilter myFilter = handler.getDocumentFilter(); // If we are not wildcarding the parent, add a restriction if (parentcsid != null) { @@ -1203,20 +1205,20 @@ public abstract class AuthorityResource myFilter.appendWhereClause(tsClause, IQueryManager.SEARCH_QUALIFIER_AND); } - result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm); - + result = search(ctx, handler, uriInfo, orderBy, keywords, advancedSearch, partialTerm); + return result; } - + /** * Gets the authorityItem list for the specified authority * If partialPerm is specified, keywords will be ignored. - * + * * @param authorityIdentifier either a CSID or one of the urn forms * @param partialTerm if non-null, matches partial terms * @param keywords if non-null, matches terms in the keyword index for items * @param ui passed to include additional parameters, like pagination controls - * + * * @return the authorityItem list */ @GET @@ -1226,13 +1228,13 @@ public abstract class AuthorityResource @Context UriInfo uriInfo) { uriInfo = new UriInfoWrapper(uriInfo); AbstractCommonList result = null; - + try { - result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo); + result = getAuthorityItemList(NULL_CONTEXT, authorityIdentifier, uriInfo); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.LIST_FAILED); } - + return result; } @@ -1244,7 +1246,7 @@ public abstract class AuthorityResource protected String getRefPropName() { return ServiceBindingUtils.AUTH_REF_PROP; } - + /** * Gets the entities referencing this Authority item instance. The service type * can be passed as a query param "type", and must match a configured type @@ -1254,7 +1256,7 @@ public abstract class AuthorityResource * @param parentspecifier either a CSID or one of the urn forms * @param itemspecifier either a CSID or one of the urn forms * @param ui the ui - * + * * @return the info for the referencing objects */ @GET @@ -1271,7 +1273,7 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED); } - + if (authRefDocList == null) { Response response = Response.status(Response.Status.NOT_FOUND).entity( "Get failed, the requested Item CSID:" + itemSpecifier + ": was not found.").type( @@ -1280,7 +1282,7 @@ public abstract class AuthorityResource } return authRefDocList; } - + public AuthorityRefDocList getReferencingObjects( ServiceContext existingContext, String parentspecifier, @@ -1291,7 +1293,7 @@ public abstract class AuthorityResource boolean useDefaultOrderByClause, boolean computeTotal) throws Exception { AuthorityRefDocList authRefDocList = null; - + ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); MultivaluedMap queryParams = ctx.getQueryParams(); // @@ -1304,13 +1306,13 @@ public abstract class AuthorityResource String parentcsid = lookupParentCSID(ctx, parentspecifier, "getReferencingObjects(parent)", "GET_ITEM_REF_OBJS", uriInfo); String itemcsid = lookupItemCSID(ctx, itemspecifier, parentcsid, "getReferencingObjects(item)", "GET_ITEM_REF_OBJS"); - + // Remove the "type" property from the query params - List serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP); + List serviceTypes = queryParams.remove(ServiceBindingUtils.SERVICE_TYPE_PROP); if (serviceTypes == null || serviceTypes.isEmpty()) { serviceTypes = ServiceBindingUtils.getCommonServiceTypes(true); //CSPACE-5359: Should now include objects, procedures, and authorities } - + AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler)createItemDocumentHandler(ctx, parentcsid, null); authRefDocList = handler.getReferencingObjects(ctx, serviceTypes, getRefPropName(), itemcsid, pageNum, pageSize, useDefaultOrderByClause, computeTotal); @@ -1335,7 +1337,7 @@ public abstract class AuthorityResource @Context UriInfo uriInfo) { uriInfo = new UriInfoWrapper(uriInfo); AuthorityRefList authRefList = null; - + try { // Note that we have to create the service context for the Items, not the main service ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); @@ -1351,10 +1353,10 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.GET_FAILED + " parentspecifier: " + parentspecifier + " itemspecifier:" + itemspecifier); } - + return authRefList; } - + /** * Synchronizes a local authority item with a share authority server (SAS) item. * @param ctx @@ -1386,7 +1388,7 @@ public abstract class AuthorityResource if (neededSync == true) { result = (PoxPayloadOut) ctx.getOutput(); } - + return result; } @@ -1407,24 +1409,24 @@ public abstract class AuthorityResource boolean syncHierarchicalRelationships ) throws Exception { PoxPayloadOut result = null; - + ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getResourceMap(), existingCtx.getUriInfo()); if (existingCtx.getCurrentRepositorySession() != null) { ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); - + } result = synchronizeItem(ctx, parentIdentifier, itemIdentifier, syncHierarchicalRelationships); - + return result; } - + /** * Synchronizes an authority item and with a Shared Authority Server (SAS) item. - * + * * @param specifier either CSIDs and/or one of the urn forms - * + * * @return the authority item if it was updated/synchronized with SAS item; otherwise empty */ @POST @@ -1438,7 +1440,7 @@ public abstract class AuthorityResource byte[] result; boolean neededSync = false; PoxPayloadOut payloadOut = null; - + try { ServiceContext ctx = createServiceContext(getItemServiceName(), null, resourceMap, uriInfo); payloadOut = this.synchronizeItem(ctx, parentIdentifier, itemIdentifier, true); @@ -1460,13 +1462,13 @@ public abstract class AuthorityResource Response response = Response.status(Response.Status.NOT_MODIFIED).entity(result).type("text/plain").build(); throw new CSWebApplicationException(response); } - + return result; } - + /** * Update authorityItem. - * + * * @param parentspecifier either a CSID or one of the urn forms * @param itemspecifier either a CSID or one of the urn forms * @@ -1475,14 +1477,14 @@ public abstract class AuthorityResource @PUT @Path("{csid}/items/{itemcsid}") public byte[] updateAuthorityItem( - @Context ResourceMap resourceMap, + @Context ResourceMap resourceMap, @Context UriInfo uriInfo, @PathParam("csid") String parentSpecifier, @PathParam("itemcsid") String itemSpecifier, String xmlPayload) { uriInfo = new UriInfoWrapper(uriInfo); PoxPayloadOut result = null; - + try { PoxPayloadIn theUpdate = new PoxPayloadIn(xmlPayload); result = updateAuthorityItem(null, resourceMap, uriInfo, parentSpecifier, itemSpecifier, theUpdate, @@ -1492,18 +1494,18 @@ public abstract class AuthorityResource } catch (Exception e) { throw bigReThrow(e, ServiceMessages.UPDATE_FAILED); } - + return result.getBytes(); } - + /** * Delete authorityItem. - * + * * @param parentIdentifier the parentcsid * @param itemIdentifier the itemcsid - * + * * @return the response */ @DELETE @@ -1520,7 +1522,7 @@ public abstract class AuthorityResource if (logger.isDebugEnabled()) { logger.debug("deleteAuthorityItem with parentcsid=" + parentIdentifier + " and itemcsid=" + itemIdentifier); } - + try { ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); deleteAuthorityItem(ctx, parentIdentifier, itemIdentifier, AuthorityServiceUtils.UPDATE_REV); @@ -1533,7 +1535,7 @@ public abstract class AuthorityResource } /** - * + * * @param existingCtx * @param parentIdentifier * @param itemIdentifier @@ -1545,13 +1547,13 @@ public abstract class AuthorityResource boolean shouldUpdateRevNumber ) throws Exception { boolean result = true; - + ServiceContext ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo()); if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) { ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession()); ctx.setProperties(existingCtx.getProperties()); } - + String parentcsid = null; try { parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null); @@ -1562,11 +1564,11 @@ public abstract class AuthorityResource throw de; } String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null - + AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler) createDocumentHandler(ctx); handler.setShouldUpdateRevNumber(shouldUpdateRevNumber); result = getRepositoryClient(ctx).delete(ctx, itemCsid, handler); - + return result; } @@ -1587,36 +1589,36 @@ public abstract class AuthorityResource String calledUri = uriInfo.getPath(); String uri = "/" + calledUri.substring(0, (calledUri.length() - ("/" + hierarchy).length())); ServiceContext ctx = createServiceContext(getItemServiceName(), uriInfo); - + String parentcsid = lookupParentCSID(ctx, parentIdentifier, "deleteAuthorityItem(parent)", "DELETE_ITEM", null); String itemcsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null - + String direction = uriInfo.getQueryParameters().getFirst(Hierarchy.directionQP); if (Tools.notBlank(direction) && Hierarchy.direction_parents.equals(direction)) { result = Hierarchy.surface(ctx, itemcsid, uri); } else { result = Hierarchy.dive(ctx, itemcsid, uri); - } + } } catch (Exception e) { throw bigReThrow(e, "Error showing hierarchy for authority item: ", itemIdentifier); } - + return result; } - + /** - * + * * @param tenantId * @return */ public String getItemDocType(String tenantId) { return getDocType(tenantId, getItemServiceName()); } - + /** * Returns a UriRegistry entry: a map of tenant-qualified URI templates * for the current resource, for all tenants - * + * * @return a map of URI templates for the current resource, for all tenants */ @Override @@ -1629,9 +1631,9 @@ public abstract class AuthorityResource } return uriRegistryEntriesMap; } - + /** - * + * */ @Override public ServiceDescription getDescription(ServiceContext ctx) { @@ -1643,22 +1645,22 @@ public abstract class AuthorityResource public Response createAuthority(String xmlPayload) { return this.createAuthority(null, null, xmlPayload); } - + protected String getCsid(ServiceContext ctx, Specifier specifier) throws Exception { String csid; - + if (ctx == null) { ctx = createServiceContext(getServiceName()); } - + if (specifier.form == SpecifierForm.CSID) { csid = specifier.value; } else { String whereClause = RefNameServiceUtils.buildWhereForAuthByName(authorityCommonSchemaName, specifier.value); csid = getRepositoryClient(ctx).findDocCSID(null, ctx, whereClause); } - + return csid; } - + } diff --git a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityServiceUtils.java b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityServiceUtils.java index 116599fe7..6bc5112c2 100644 --- a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityServiceUtils.java +++ b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityServiceUtils.java @@ -1,6 +1,8 @@ package org.collectionspace.services.common.vocabulary; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -9,6 +11,8 @@ import javax.ws.rs.core.Response; import org.collectionspace.services.client.AuthorityClient; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.common.ServiceMain; +import org.collectionspace.services.common.api.RefNameUtils; +import org.collectionspace.services.common.api.RefNameUtils.AuthorityTermInfo; import org.collectionspace.services.common.api.Tools; import org.collectionspace.services.common.context.MultipartServiceContextImpl; import org.collectionspace.services.common.context.ServiceContext; @@ -21,9 +25,12 @@ import org.collectionspace.services.config.tenant.RemoteClientConfigurations; import org.collectionspace.services.config.tenant.TenantBindingType; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.collectionspace.services.nuxeo.util.NuxeoUtils; +import org.dom4j.Document; +import org.dom4j.DocumentHelper; +import org.dom4j.Node; +import org.dom4j.XPath; import org.collectionspace.services.common.document.DocumentException; - -//import org.dom4j.DocumentException; +import org.collectionspace.services.common.document.DocumentNotFoundException; import org.nuxeo.ecm.core.api.DocumentModel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -220,6 +227,122 @@ public class AuthorityServiceUtils { return new PoxPayloadIn(localizedXmlBuffer.toString()); } + /** + * Localizes the relations list in an authority item payload from a remote server during SAS sync. + * + * Relations to items that do not exist in the local authority are removed. If the related item + * doesn't exist locally because it is new on the remote and hasn't been synced yet, the relations + * will be created when the new item is synced, because the new item's relations list will include + * them. + * + * The following elements are removed from each relation: uri, csid, subjectCsid, objectCsid, + * object/csid, object/uri, subject/csid, subject/uri. These apply only to the remote. By removing + * them, the relation will be created locally if necessary. + * + * @param ctx + * @param authorityResource + * @param parentCsid + * @param itemSpecifier + * @param payload + * @return + * @throws Exception + */ + public static PoxPayloadIn localizeRelations(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, Specifier itemSpecifier, PoxPayloadIn payload) throws Exception { + // TODO: Relations to items that don't exist need to be removed, because a create/update will fail + // if the subject/object of any supplied relation can't be found. Consider changing the create/update + // code to ignore any relations to items that don't exist, but still save the record and any other + // relations. This will speed up sync when many items have relations, since the checks to see if + // all related items exist locally can be skipped. + + String itemShortId = itemSpecifier.value; + Document document = payload.getDOMDocument(); + + Map namespaceUris = new HashMap(); + namespaceUris.put("rel", "http://collectionspace.org/services/relation"); + + XPath xPath = DocumentHelper.createXPath("//rel:relations-common-list/relation-list-item"); + xPath.setNamespaceURIs(namespaceUris); + + List listItemNodes = xPath.selectNodes(document); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Found %d relation list items", listItemNodes.size())); + } + + for (Node listItemNode : listItemNodes) { + String objectRefName = listItemNode.selectSingleNode("object/refName").getText(); + AuthorityTermInfo objectTermInfo = RefNameUtils.parseAuthorityTermInfo(objectRefName); + String objectShortId = objectTermInfo.name; + + if ( + !objectShortId.equals(itemShortId) + && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(objectShortId)) + ) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Omitting remote relation: object with short id %s does does not exist locally", objectShortId)); + } + + listItemNode.detach(); + continue; + } + + String subjectRefName = listItemNode.selectSingleNode("subject/refName").getText(); + AuthorityTermInfo subjectTermInfo = RefNameUtils.parseAuthorityTermInfo(subjectRefName); + String subjectShortId = subjectTermInfo.name; + + if ( + !subjectShortId.equals(itemShortId) + && !checkItemExists(ctx, authorityResource, parentCsid, Specifier.createShortIdURNValue(subjectShortId)) + ) { + if (logger.isDebugEnabled()) { + logger.debug(String.format("Omitting remote relation: subject with short id %s does does not exist locally", subjectShortId)); + } + + listItemNode.detach(); + continue; + } + + listItemNode.selectSingleNode("csid").detach(); + listItemNode.selectSingleNode("objectCsid").detach(); + listItemNode.selectSingleNode("subjectCsid").detach(); + listItemNode.selectSingleNode("uri").detach(); + listItemNode.selectSingleNode("object/csid").detach(); + listItemNode.selectSingleNode("object/uri").detach(); + listItemNode.selectSingleNode("subject/csid").detach(); + listItemNode.selectSingleNode("subject/uri").detach(); + } + + String xml = document.asXML(); + + if (logger.isTraceEnabled()) { + logger.trace("Prepared remote relations:\n" + xml); + } + + return new PoxPayloadIn(xml); + } + + /** + * Check if an item with a given short ID exists in a parent. + * + * @param ctx + * @param authorityResource + * @param parentCsid + * @param itemSpecifier + * @return true if the item exists, false otherwise. + * @throws Exception + */ + public static boolean checkItemExists(ServiceContext ctx, AuthorityResource authorityResource, String parentCsid, String itemSpecifier) throws Exception { + String itemCsid = null; + + try { + itemCsid = authorityResource.lookupItemCSID(ctx, itemSpecifier, parentCsid, "checkItemExists()", "CHECK_ITEM_EXISTS"); + } catch (DocumentNotFoundException e) { + itemCsid = null; + } + + return (itemCsid != null); + } + /** * Mark the authority item as deprecated. * diff --git a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java index 6c0232f18..c2e4ee96d 100644 --- a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java +++ b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java @@ -23,9 +23,10 @@ */ package org.collectionspace.services.common.vocabulary.nuxeo; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; @@ -36,6 +37,7 @@ import org.collectionspace.services.client.AuthorityClient; import org.collectionspace.services.client.PayloadInputPart; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; +import org.collectionspace.services.client.RelationClient; import org.collectionspace.services.client.XmlTools; import org.collectionspace.services.client.workflow.WorkflowClient; import org.collectionspace.services.common.ResourceMap; @@ -65,6 +67,7 @@ import org.collectionspace.services.nuxeo.client.java.NuxeoDocumentModelHandler; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; import org.collectionspace.services.nuxeo.client.java.NuxeoRepositoryClientImpl; import org.collectionspace.services.nuxeo.util.NuxeoUtils; +import org.collectionspace.services.relation.RelationsCommonList; import org.dom4j.Element; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; @@ -132,6 +135,7 @@ public abstract class AuthorityDocumentModelHandler // DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, specifier); if (docModel != null) { + String authorityCsid = docModel.getName(); Long localRev = (Long) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REV); String shortId = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.SHORT_IDENTIFIER); String remoteClientConfigName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REMOTECLIENT_CONFIG_NAME); // If set, contains the name of the remote client configuration (remoteClientConfigName) from the tenant bindings @@ -149,7 +153,7 @@ public abstract class AuthorityDocumentModelHandler // // First, sync all the authority items // - syncAllItems(ctx, sasSpecifier); // FIXME: We probably want to consider "paging" this instead of handling the entire set of items. + syncAllItems(ctx, authorityCsid, sasSpecifier); // FIXME: We probably want to consider "paging" this instead of handling the entire set of items. // // Next, sync the authority resource/record itself // @@ -166,7 +170,6 @@ public abstract class AuthorityDocumentModelHandler // String workflowState = docModel.getCurrentLifeCycleState(); if (workflowState.contains(WorkflowClient.WORKFLOWSTATE_REPLICATED) == false) { - String authorityCsid = docModel.getName(); authorityResource.updateWorkflowWithTransition(ctx, ctx.getUriInfo(), authorityCsid, WorkflowClient.WORKFLOWTRANSITION_REPLICATE); } } @@ -181,75 +184,68 @@ public abstract class AuthorityDocumentModelHandler } /* - * Get the list of authority items from the remote shared authority server (SAS) and try - * to synchronize them with the local items. If items exist on the remote but not the local, we'll create them. + * Get the list of authority items from the remote shared authority server (SAS) and + * synchronize them with the local authority items. If items exist on the remote but not the local, + * create them. */ - protected int syncAllItems(ServiceContext ctx, Specifier sasAuthoritySpecifier) throws Exception { - int result = -1; - int created = 0; - int synched = 0; - int alreadySynched = 0; - int deprecated = 0; - int totalItemsProcessed = 0; - ArrayList itemsInRemoteAuthority = new ArrayList(); - // - // Iterate over the list of items/terms in the remote authority - // + protected void syncAllItems(ServiceContext ctx, String parentCsid, Specifier sasAuthoritySpecifier) throws Exception { + int createdCount = 0; + int syncedCount = 0; + int alreadySyncedCount = 0; + int deletedCount = 0; + int totalProcessedCount = 0; + + Set remoteShortIds = new HashSet(); + + // Iterate over the list of items in the remote authority. + PoxPayloadIn itemListPayload = requestItemList(ctx, sasAuthoritySpecifier); - List itemList = getItemList(itemListPayload); - if (itemList != null) { - for (Element e:itemList) { + List itemElements = getItemList(itemListPayload); + + if (itemElements != null) { + for (Element e : itemElements) { String remoteRefName = XmlTools.getElementValue(e, AuthorityItemJAXBSchema.REF_NAME); - itemsInRemoteAuthority.add(XmlTools.getElementValue(e, AuthorityItemJAXBSchema.SHORT_IDENTIFIER)); - long status = syncRemoteItemWithLocalItem(ctx, remoteRefName); + String remoteShortId = XmlTools.getElementValue(e, AuthorityItemJAXBSchema.SHORT_IDENTIFIER); + + remoteShortIds.add(remoteShortId); + + long status = syncRemoteItem(ctx, parentCsid, remoteRefName); + if (status == 1) { - created++; + createdCount++; } else if (status == 0) { - synched++; + syncedCount++; } else { - alreadySynched++; + alreadySyncedCount++; } - totalItemsProcessed++; + + totalProcessedCount++; } } - // - // Now see if we need to deprecate or delete items that have been hard-deleted from the SAS but still exist - // locally. Subtract (remove) the list of remote items from the list of local items to determine which + + // Deprecate or delete items that have been hard-deleted from the SAS but still exist locally. + // Subtract (remove) the set of remote items from the set of local items to determine which // of the remote items have been hard deleted. - // - ArrayList itemsInLocalAuthority = getItemsInLocalAuthority(ctx, sasAuthoritySpecifier); - itemsInLocalAuthority.removeAll(itemsInRemoteAuthority); - if (itemsInLocalAuthority.size() > 0) { - ArrayList remainingItems = itemsInLocalAuthority; // now a subset of local items that no longer exist on the SAS, so we need to try to delete them (or mark them as deprecated if they still have records referencing them) - // - // We now need to either hard-delete or deprecate the remaining authorities - // - long processed = deleteOrDeprecateItems(ctx, sasAuthoritySpecifier, remainingItems); - if (processed != remainingItems.size()) { - throw new Exception("Encountered unexpected exception trying to delete or deprecated authority items during synchronization."); - } - } - // - // Now that we've sync'd all the items, we need to synchronize the hierarchy relationships - // - for (String itemShortId:itemsInRemoteAuthority) { - long status = syncRemoteItemRelationshipsWithLocalItem(ctx, sasAuthoritySpecifier, itemShortId); - if (status == 1) { - created++; - } else if (status == 0) { - synched++; - } else { - alreadySynched++; + + Set localShortIds = getItemsInLocalAuthority(ctx, sasAuthoritySpecifier); + + localShortIds.removeAll(remoteShortIds); + + if (localShortIds.size() > 0) { + // Delete the remaining items (or mark them as deprecated if they still have records referencing them). + + deletedCount = deleteOrDeprecateItems(ctx, sasAuthoritySpecifier, localShortIds); + + if (deletedCount != localShortIds.size()) { + throw new Exception("Error deleting or deprecating authority items during synchronization."); } - totalItemsProcessed++; } - logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed)); - logger.info(String.format("Number of items synchronized: %d", synched)); - logger.info(String.format("Number of items created during sync: %d", created)); - logger.info(String.format("Number not needing synchronization: %d", alreadySynched)); - - return result; + logger.info(String.format("Total number of items processed during sync: %d", totalProcessedCount)); + logger.info(String.format("Number of items synchronized: %d", syncedCount)); + logger.info(String.format("Number of items created during sync: %d", createdCount)); + logger.info(String.format("Number of items not needing synchronization: %d", alreadySyncedCount)); + logger.info(String.format("Number of items hard deleted on remote: %d", deletedCount)); } /** @@ -260,12 +256,12 @@ public abstract class AuthorityDocumentModelHandler * @return * @throws Exception */ - private long deleteOrDeprecateItems(ServiceContext ctx, Specifier authoritySpecifier, ArrayList itemShortIdList) throws Exception { - long result = 0; + private int deleteOrDeprecateItems(ServiceContext ctx, Specifier authoritySpecifier, Set itemShortIds) throws Exception { + int result = 0; AuthorityItemSpecifier authorityItemSpecificer = null; ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, false); // Don't update the revision number when we delete or deprecate the item - for (String itemShortId:itemShortIdList) { + for (String itemShortId:itemShortIds) { AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(); try { authorityItemSpecificer = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authoritySpecifier.value, @@ -290,9 +286,9 @@ public abstract class AuthorityDocumentModelHandler } if (logger.isWarnEnabled() == true) { - if (result != itemShortIdList.size()) { + if (result != itemShortIds.size()) { logger.warn(String.format("Unable to delete or deprecate some authority items during synchronization with SAS. Deleted or deprecated %d of %d. See the services log file for details.", - result, itemShortIdList.size())); + result, itemShortIds.size())); } } @@ -300,27 +296,29 @@ public abstract class AuthorityDocumentModelHandler } /** - * Gets the list of SAS related items in the local authority. We exlude items with the "proposed" flags because - * we want a list with only SAS created items. + * Gets the list of SAS related items in the local authority. Exludes items with the "proposed" flag to + * include only SAS created items. * - * We need to add pagination support to this call!!! + * FIXME: Add pagination support to this call!!! * * @param ctx * @param authoritySpecifier * @return * @throws Exception */ - private ArrayList getItemsInLocalAuthority(ServiceContext ctx, Specifier authoritySpecifier) throws Exception { - ArrayList result = new ArrayList(); + private Set getItemsInLocalAuthority(ServiceContext ctx, Specifier authoritySpecifier) throws Exception { + Set result = new HashSet(); ResourceMap resourceMap = ctx.getResourceMap(); String resourceName = ctx.getClient().getServiceName(); AuthorityResource authorityResource = (AuthorityResource) resourceMap.get(resourceName); AbstractCommonList acl = authorityResource.getAuthorityItemList(ctx, authoritySpecifier.getURNValue(), ctx.getUriInfo()); - List listItemList = acl.getListItem(); - for (ListItem listItem:listItemList) { + List listItems = acl.getListItem(); + + for (ListItem listItem : listItems) { Boolean proposed = getBooleanValue(listItem, AuthorityItemJAXBSchema.PROPOSED); + if (proposed == false) { // exclude "proposed" (i.e., local-only items) result.add(AbstractCommonListUtils.ListItemGetElementValue(listItem, AuthorityItemJAXBSchema.SHORT_IDENTIFIER)); } @@ -352,29 +350,36 @@ public abstract class AuthorityDocumentModelHandler * @param itemIdentifier - Must be in short-id-refname form -i.e., urn:cspace:name(shortid) * @throws Exception */ - protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier, Boolean syncHierarchicalRelationships) throws Exception { + protected void createLocalItem(ServiceContext ctx, String parentCsid, String parentIdentifier, String itemIdentifier, Boolean syncHierarchicalRelationships) throws Exception { // // Create a URN short ID specifier for the getting a copy of the remote authority item // - Specifier authoritySpecifier = Specifier.getSpecifier(parentIdentifier); + Specifier parentSpecifier = Specifier.getSpecifier(parentIdentifier); Specifier itemSpecifier = Specifier.getSpecifier(itemIdentifier); - AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(authoritySpecifier, itemSpecifier); + AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(parentSpecifier, itemSpecifier); // // Get the remote client configuration name // - DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, authoritySpecifier); + DocumentModel docModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, parentSpecifier); String remoteClientConfigName = (String) NuxeoUtils.getProperyValue(docModel, AuthorityJAXBSchema.REMOTECLIENT_CONFIG_NAME); // If set, contains the name of the remote client configuration (remoteClientConfigName) from the tenant bindings // // Get the remote payload // PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(sasAuthorityItemSpecifier, remoteClientConfigName, ctx.getServiceName(), getEntityResponseType(), syncHierarchicalRelationships); - sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); // We need to filter domain name part of any and all refnames in the payload + + AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(); + + // Remove remote uris and csids from relations, and remove relations to items that don't exist locally. + sasPayloadIn = AuthorityServiceUtils.localizeRelations(ctx, authorityResource, parentCsid, itemSpecifier, sasPayloadIn); + + // Localize domain name parts of refnames in the payload. + sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); + // // Using the payload from the remote server, create a local copy of the item // - AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(); - Response response = authorityResource.createAuthorityItemWithParentContext(ctx, authoritySpecifier.getURNValue(), + Response response = authorityResource.createAuthorityItemWithParentContext(ctx, parentSpecifier.getURNValue(), sasPayloadIn, AuthorityServiceUtils.DONT_UPDATE_REV, AuthorityServiceUtils.NOT_PROPOSED, AuthorityServiceUtils.SAS_ITEM); // // Check the response for successful POST result @@ -391,8 +396,8 @@ public abstract class AuthorityDocumentModelHandler } /** - * Try to synchronize a remote item (using its refName) with a local item. If the local doesn't yet - * exist, we'll create it. + * Synchronize a remote item (using its refName) with a local item. If the local doesn't yet + * exist, create it. * Result values: * -1 = sync not needed; i.e., already in sync * 0 = sync succeeded @@ -402,98 +407,55 @@ public abstract class AuthorityDocumentModelHandler * @return * @throws Exception */ - protected long syncRemoteItemWithLocalItem(ServiceContext ctx, String itemRefName) throws Exception { - long result = -1; - // - // Using the item refname (with no local CSID), create specifiers that we'll use to find the local versions - // + protected long syncRemoteItem(ServiceContext ctx, String parentCsid, String itemRefName) throws Exception { + if (logger.isInfoEnabled()) { + logger.info(String.format("Syncing remote item %s", itemRefName)); + } + + // Create specifiers to find the local item corresponding to the remote refname. + AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(itemRefName); String parentIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.inAuthority.name); String itemIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.name); - // - // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates) - // + + // Use the Authority JAX-RS resource to peform sync operations (creates and updates). + AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(); - PoxPayloadOut localItemPayloadOut; + PoxPayloadOut localItemPayload = null; + + // Find the local item. + try { - localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier); + localItemPayload = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier); } catch (DocumentNotFoundException dnf) { - // - // Document not found, means we need to create an item/term that exists only on the SAS - // - logger.info(String.format("Remote item with refname='%s' doesn't exist locally, so we'll create it.", itemRefName)); - createLocalItem(ctx, parentIdentifier, itemIdentifier, AuthorityClient.DONT_INCLUDE_RELATIONS); - return 1; // exit with status of 1 means we created a new authority item - } - // - // If we get here, we know the item exists both locally and remotely, so we need to synchronize them. - // - // - try { - PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, false); - if (theUpdate != null) { - result = 0; // means we needed to sync this item with SAS - logger.debug(String.format("Synced authority item %s in authority %s", - itemIdentifier, parentIdentifier)); - } - } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them. - result = -1; - logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.", - itemIdentifier)); + localItemPayload = null; } - return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item - } + // If no local item exists, create one. - /** - * Ensure the local items relationships look the same as the remote items' by synchronizing the hierarchy relationship records - * of the SAS item with the local item. - * - * @param ctx - * @param refName - * @return - * @throws Exception - */ - protected long syncRemoteItemRelationshipsWithLocalItem(ServiceContext ctx, Specifier authoritySpecifier, String itemShortId) throws Exception { - long result = -1; + if (localItemPayload == null) { + createLocalItem(ctx, parentCsid, parentIdentifier, itemIdentifier, AuthorityClient.INCLUDE_RELATIONS); - String parentIdentifier = authoritySpecifier.getURNValue(); - String itemIdentifier = Specifier.createShortIdURNValue(itemShortId); - // - // We'll use the Authority JAX-RS resource to peform sync operations (creates and updates) - // - AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(); - PoxPayloadOut localItemPayloadOut; - try { - MultivaluedMap queryParams = ctx.getQueryParams(); - localItemPayloadOut = authorityResource.getAuthorityItemWithExistingContext(ctx, parentIdentifier, itemIdentifier); - } catch (DocumentNotFoundException dnf) { - // - // Document not found, means we need to create an item/term that exists only on the SAS - // - logger.info(String.format("Remote item with short ID ='%s' doesn't exist locally, so we can't synchronize its relationships.", itemShortId)); - return result; + return 1; } - // - // If we get here, we know the item exists both locally and remotely, so we need to synchronize the hierarchy relationships. - // - // + + // Sync the local item with the remote item. + + PoxPayloadOut updatePayload = null; + try { - PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, AuthorityClient.INCLUDE_RELATIONS); - if (theUpdate != null) { - result = 0; // means we needed to sync this item with SAS - logger.debug(String.format("Synced authority item %s in authority %s", - itemIdentifier, parentIdentifier)); - } - } catch (DocumentReferenceException de) { // Exception for items that still have records/resource referencing them. - result = -1; - logger.error(String.format("Could not sync authority item = '%s' because it has existing records referencing it.", - itemIdentifier)); + updatePayload = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, AuthorityClient.INCLUDE_RELATIONS); + } catch (DocumentReferenceException de) { + logger.error(String.format("Could not sync item %s because it is referenced by other records", itemIdentifier)); } - return result; // -1 = no sync needed/possible, 0 = sync'd, 1 = created new item + if (updatePayload != null) { + logger.info(String.format("Synced item %s in authority %s", itemIdentifier, parentIdentifier)); + return 0; + } + return -1; } private void assertStatusCode(Response res, Specifier specifier, AuthorityClient client) throws Exception { diff --git a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java index c3490e530..68c11156e 100644 --- a/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java +++ b/services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java @@ -439,116 +439,19 @@ public abstract class AuthorityItemDocumentModelHandler } } - /** - * - * @param wrapDoc - * @return - * @throws Exception - */ - protected boolean handleRelationsSync(DocumentWrapper wrapDoc) throws Exception { - boolean result = false; - ServiceContext ctx = getServiceContext(); - - // - // Get information about the local authority item so we can compare with corresponding item on the shared authority server - // - AuthorityItemSpecifier authorityItemSpecifier = (AuthorityItemSpecifier) wrapDoc.getWrappedObject(); - DocumentModel itemDocModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), getAuthorityItemCommonSchemaName(), - authorityItemSpecifier); - if (itemDocModel == null) { - throw new DocumentNotFoundException(String.format("Could not find authority item resource with CSID='%s'", - authorityItemSpecifier.getItemSpecifier().value)); - } - Long localItemRev = (Long) NuxeoUtils.getProperyValue(itemDocModel, AuthorityItemJAXBSchema.REV); - Boolean localIsProposed = (Boolean) NuxeoUtils.getProperyValue(itemDocModel, AuthorityItemJAXBSchema.PROPOSED); - String localItemCsid = itemDocModel.getName(); - String localItemWorkflowState = itemDocModel.getCurrentLifeCycleState(); - String itemShortId = (String) NuxeoUtils.getProperyValue(itemDocModel, AuthorityItemJAXBSchema.SHORT_IDENTIFIER); - - // - // Now get the item's Authority (the parent) information - // - DocumentModel authorityDocModel = NuxeoUtils.getDocFromSpecifier(ctx, getRepositorySession(), authorityCommonSchemaName, - authorityItemSpecifier.getParentSpecifier()); - String authorityShortId = (String)NuxeoUtils.getProperyValue(authorityDocModel, AuthorityJAXBSchema.SHORT_IDENTIFIER); - String localParentCsid = authorityDocModel.getName(); - String remoteClientConfigName = (String)NuxeoUtils.getProperyValue(authorityDocModel, AuthorityJAXBSchema.REMOTECLIENT_CONFIG_NAME); - // - // Using the short IDs of the local authority and item, create URN specifiers and retrieve the SAS authority item - // - AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authorityShortId, itemShortId); - // Get the shared authority server's copy - PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(sasAuthorityItemSpecifier, - remoteClientConfigName, getAuthorityServicePath(), getEntityResponseType(), AuthorityClient.INCLUDE_RELATIONS); - - // - // Get the RelationsCommonList and remove the CSIDs since they are for remote items only. We'll use - // the refnames in the payload instead to find the local CSIDs - // - PayloadInputPart relationsCommonListPart = sasPayloadIn.getPart(RelationClient.SERVICE_COMMON_LIST_NAME); - relationsCommonListPart.clearElementBody(); // clear the existing DOM element that was created from the incoming XML payload - RelationsCommonList rcl = (RelationsCommonList) relationsCommonListPart.getBody(); // Get the JAX-B object and clear the CSID values - for (RelationsCommonList.RelationListItem listItem : rcl.getRelationListItem()) { - // clear the remote relation item's CSID - listItem.setCsid(null); - // clear the remote subject's CSID - listItem.setSubjectCsid(null); - listItem.getSubject().setCsid(null); - listItem.getSubject().setUri(null); - // clear the remote object's CSID - listItem.setObjectCsid(null); - listItem.getObject().setCsid(null); - listItem.getObject().setUri(null); - } - - // - // Remove all the payload parts except the relations part since we only want to sync the relationships - // - ArrayList newPartList = new ArrayList(); - newPartList.add(relationsCommonListPart); // add our CSID filtered RelationsCommonList part - sasPayloadIn.setParts(newPartList); - sasPayloadIn = new PoxPayloadIn(sasPayloadIn.toXML()); // Builds a new payload using the current set of parts -i.e., just the relations part - - sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); // We need to filter the domain name part of any and all refnames in the payload - AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(getAuthorityServicePath()); - PoxPayloadOut payloadOut = authorityResource.updateAuthorityItem(ctx, - ctx.getResourceMap(), - ctx.getUriInfo(), - localParentCsid, // parent's CSID - localItemCsid, // item's CSID - sasPayloadIn, // the payload from the remote SAS - AuthorityServiceUtils.DONT_UPDATE_REV, // don't update the parent's revision number - AuthorityServiceUtils.NOT_PROPOSED, // The items is not proposed, make it a real SAS item now - AuthorityServiceUtils.SAS_ITEM); // Since we're sync'ing, this must be a SAS item - if (payloadOut != null) { - ctx.setOutput(payloadOut); - result = true; - } - - return result; - } - @Override public boolean handleSync(DocumentWrapper wrapDoc) throws Exception { - boolean result = false; - - if (this.getShouldSyncHierarchicalRelationships() == true) { - result = handleRelationsSync(wrapDoc); - } else { - result = handlePayloadSync(wrapDoc); - } - - return result; + return handleItemSync(wrapDoc); } - /** + /** * * @param wrapDoc * @return * @throws Exception */ @SuppressWarnings({ "rawtypes", "unchecked" }) - protected boolean handlePayloadSync(DocumentWrapper wrapDoc) throws Exception { + protected boolean handleItemSync(DocumentWrapper wrapDoc) throws Exception { boolean result = false; ServiceContext ctx = getServiceContext(); @@ -583,15 +486,21 @@ public abstract class AuthorityItemDocumentModelHandler AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authorityShortId, itemShortId); // Get the shared authority server's copy PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadInFromRemoteServer(sasAuthorityItemSpecifier, - remoteClientConfigName, getAuthorityServicePath(), getEntityResponseType(), AuthorityClient.DONT_INCLUDE_RELATIONS); + remoteClientConfigName, getAuthorityServicePath(), getEntityResponseType(), AuthorityClient.INCLUDE_RELATIONS); Long sasRev = getRevision(sasPayloadIn); String sasWorkflowState = getWorkflowState(sasPayloadIn); // // If the shared authority item is newer, update our local copy // if (sasRev > localItemRev || localIsProposed || ctx.shouldForceSync()) { - sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); // We need to filter the domain name part of any and all refnames in the payload AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(getAuthorityServicePath()); + + // Remove remote uris and csids from relations, and remove relations to items that don't exist locally. + sasPayloadIn = AuthorityServiceUtils.localizeRelations(ctx, authorityResource, localParentCsid, authorityItemSpecifier.getItemSpecifier(), sasPayloadIn); + + // Localize domain name parts of refnames in the payload. + sasPayloadIn = AuthorityServiceUtils.localizeRefNameDomains(ctx, sasPayloadIn); + PoxPayloadOut payloadOut = authorityResource.updateAuthorityItem(ctx, ctx.getResourceMap(), ctx.getUriInfo(), -- 2.47.3