]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-6953: Simple hierarchy relations synchronization working.
authorremillet <remillet@yahoo.com>
Tue, 24 May 2016 21:29:09 +0000 (14:29 -0700)
committerremillet <remillet@yahoo.com>
Tue, 24 May 2016 21:29:09 +0000 (14:29 -0700)
21 files changed:
services/JaxRsServiceProvider/src/main/resources/log4j.properties
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityServiceUtils.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityItemDocumentModelHandler.java
services/client/src/main/java/org/collectionspace/services/client/AuthorityClient.java
services/client/src/main/java/org/collectionspace/services/client/AuthorityClientImpl.java
services/client/src/main/java/org/collectionspace/services/client/AuthorityProxy.java
services/client/src/main/java/org/collectionspace/services/client/PayloadPart.java
services/client/src/main/java/org/collectionspace/services/client/PoxPayload.java
services/client/src/main/java/org/collectionspace/services/client/PoxPayloadOut.java
services/client/src/main/java/org/collectionspace/services/client/test/AbstractAuthorityServiceTest.java
services/client/src/main/resources/sas-collectionspace-client.properties
services/common/src/main/java/org/collectionspace/services/common/context/MultipartServiceContextImpl.java
services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java
services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaRelationshipStorageClient.java
services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java
services/common/src/main/java/org/collectionspace/services/common/vocabulary/RefNameServiceUtils.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RemoteDocumentModelHandlerImpl.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryClientImpl.java
services/vocabulary/service/src/main/java/org/collectionspace/services/vocabulary/nuxeo/VocabularyItemValidatorHandler.java

index 89b5901f4d90cfc5a8d9a98ca60109c7cd4723f9..36e5757db6b249e1e1ed0aced491c3da1c48fcd8 100644 (file)
@@ -56,6 +56,7 @@ log4j.logger.org.collectionspace=WARN
 log4j.logger.org.collectionspace.services.nuxeo.client.java.NuxeoClientEmbedded=ERROR
 log4j.logger.org.collectionspace.services.nuxeo.client.java.RepositoryClientImpl=ERROR
 log4j.logger.org.collectionspace.services.common.security.SecurityInterceptor=TRACE
+log4j.logger.org.collectionspace.services.nuxeo.client.java.RemoteDocumentModelHandlerImpl=DEBUG
 #log4j.logger.org.collectionspace.services.nuxeo.client.java=ERROR
 #log4j.logger.org.collectionspace.services.common.storage.JDBCTools=ERROR
 #log4j.logger.org.collectionspace.services.common.profile.CSpaceFilter=ERROR
index 2ea1c6211bf722309d68e024df99a66f5d151f2e..760c5acd5559ef5f5ccb5050ff6c956b316673bf 100644 (file)
@@ -1120,7 +1120,8 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
      * @return
      * @throws Exception
      */
-    private PoxPayloadOut synchronizeItem(
+    @SuppressWarnings("unchecked")
+       private PoxPayloadOut synchronizeItem(
                ServiceContext ctx,
             String parentIdentifier,
             String itemIdentifier,
@@ -1170,6 +1171,7 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
                        existingCtx.getUriInfo());
         if (existingCtx.getCurrentRepositorySession() != null) {
                ctx.setCurrentRepositorySession(existingCtx.getCurrentRepositorySession());
+               
         }
         result = synchronizeItem(ctx, parentIdentifier, itemIdentifier, syncHierarchicalRelationships);
        
@@ -1334,12 +1336,12 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
      * @throws Exception
      */
     @SuppressWarnings("rawtypes")
-       public void deleteAuthorityItem(ServiceContext existingCtx,
+       public boolean deleteAuthorityItem(ServiceContext existingCtx,
             String parentIdentifier,
             String itemIdentifier,
             boolean shouldUpdateRevNumber
             ) throws Exception {
-       Response result = null;
+       boolean result = true;
        
         ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(getItemServiceName(), existingCtx.getUriInfo());
         if (existingCtx != null && existingCtx.getCurrentRepositorySession() != null) {
@@ -1356,10 +1358,11 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
         }
         String itemCsid = lookupItemCSID(ctx, itemIdentifier, parentcsid, "deleteAuthorityItem(item)", "DELETE_ITEM"); //use itemServiceCtx if it is not null
         
-        DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
-               ctx.setProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY, shouldUpdateRevNumber);  // Sometimes we can only soft-delete, so if it is during a sync we dont' update the revision number
-
-        getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
+        AuthorityItemDocumentModelHandler handler = (AuthorityItemDocumentModelHandler) createDocumentHandler(ctx);
+        handler.setShouldUpdateRevNumber(shouldUpdateRevNumber);
+        result = getRepositoryClient(ctx).delete(ctx, itemCsid, handler);
+        
+        return result;
     }
 
     @GET
index c3b72271f935931555e4e59b28d9e42d057fcd06..5b293110e5fde3205941acb3a0c91e99c618052a 100644 (file)
@@ -83,7 +83,7 @@ public class AuthorityServiceUtils {
        
        ServiceContext parentCtx = new MultipartServiceContextImpl(serviceName);
         AuthorityClient client = (AuthorityClient) parentCtx.getClient(CollectionSpaceClient.SAS_CLIENT_PROPERTIES_FILENAME);
-        Response res = client.readNamedItemInNamedAuthority(specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(),
+        Response res = client.readItem(specifier.getParentSpecifier().getURNValue(), specifier.getItemSpecifier().getURNValue(),
                        AuthorityClient.INCLUDE_DELETED_ITEMS, syncHierarchicalRelationships);
         
         try {
index 4a7e7865fd8d6a74c07e8430c4903c914fbee492..d6e857432988c3410daabbbf4309e985882b364e 100644 (file)
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
+import javax.ws.rs.core.MultivaluedMap;
 import javax.ws.rs.core.Response;
 
 import org.collectionspace.services.client.AbstractCommonListUtils;
@@ -39,6 +40,7 @@ import org.collectionspace.services.client.PoxPayloadOut;
 import org.collectionspace.services.client.XmlTools;
 import org.collectionspace.services.client.workflow.WorkflowClient;
 import org.collectionspace.services.common.ResourceMap;
+import org.collectionspace.services.common.api.CommonAPI;
 import org.collectionspace.services.common.api.RefName;
 import org.collectionspace.services.common.api.RefName.Authority;
 import org.collectionspace.services.common.api.RefNameUtils;
@@ -215,32 +217,27 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
        if (itemsInLocalAuthority.size() > 0) {
                ArrayList<String> 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-deleted or deprecate the remaining authorities
+               // We now need to either hard-delete or deprecate the remaining authorities
                //
-               long processed = deleteOrDeprecateItems(ctx, remainingItems);
+               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.");
                }
        }
        //
-       // We need to synchronize the hierarchy relationships
+       // Now that we've sync'd all the items, we need to synchronize the hierarchy relationships
        //
-        itemList = getItemList(sasPayloadInItemList); // Really need to re-request the sasPayload?  I don't think so.
-        if (itemList != null) {
-               for (Element e:itemList) {
-                       String remoteRefName = XmlTools.getElementValue(e, "refName");
-                       itemsInRemoteAuthority.add(remoteRefName);
-                       long status = syncRemoteItemRelationshipsWithLocalItem(ctx, remoteRefName);
-                       if (status == 1) {
-                               created++;
-                       } else if (status == 0) {
-                               synched++;
-                       } else {
-                               alreadySynched++;
-                       }
-                       totalItemsProcessed++;
-               }
-        }      
+       for (String itemShortId:itemsInRemoteAuthority) {
+               long status = syncRemoteItemRelationshipsWithLocalItem(ctx, sasAuthoritySpecifier, itemShortId);
+               if (status == 1) {
+                       created++;
+               } else if (status == 0) {
+                       synched++;
+               } else {
+                       alreadySynched++;
+               }
+               totalItemsProcessed++;
+       }
        
         logger.info(String.format("Total number of items processed during sync: %d", totalItemsProcessed));
         logger.info(String.format("Number of items synchronized: %d", synched));
@@ -257,40 +254,40 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
      * @return
      * @throws Exception
      */
-    private long deleteOrDeprecateItems(ServiceContext ctx, ArrayList<String> refNameList) throws Exception {
+    @SuppressWarnings("rawtypes")
+       private long deleteOrDeprecateItems(ServiceContext ctx, Specifier authoritySpecifier, ArrayList<String> itemShortIdList) throws Exception {
        long 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 itemRefName:refNameList) {
-               AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(itemRefName);
-            AuthorityItemSpecifier authorityItemSpecificer = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authorityTermInfo.inAuthority.name,
-                       authorityTermInfo.name);
-                       
+       for (String itemShortId:itemShortIdList) {
                AuthorityResource authorityResource = (AuthorityResource) ctx.getResource();
                try {
+               authorityItemSpecificer = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authoritySpecifier.value,
+                               itemShortId);
                        authorityResource.deleteAuthorityItem(ctx,
-                                       Specifier.createShortIdURNValue(authorityTermInfo.inAuthority.name),
-                                       Specifier.createShortIdURNValue(authorityTermInfo.name),
+                                       authorityItemSpecificer.getParentSpecifier().getURNValue(),
+                                       authorityItemSpecificer.getItemSpecifier().getURNValue(),
                                        AuthorityServiceUtils.DONT_UPDATE_REV); // Since we're sync'ing, we shouldn't update the revision number (obviously this only applies to soft-deletes since hard-deletes destroy the record)
                        result++;
                } catch (DocumentReferenceException de) {
-                       logger.info(String.format("Authority item '%s' has existing references and cannot be removed during sync.",
-                                       itemRefName), de);
+                       logger.info(String.format("Authority item with '%s' has existing references and cannot be removed during sync.",
+                                       authorityItemSpecificer), de);
                        boolean marked = AuthorityServiceUtils.markAuthorityItemAsDeprecated(ctx, authorityItemCommonSchemaName,
                                        authorityItemSpecificer);
                        if (marked == true) {
                                result++;
                        }
                } catch (Exception e) {
-                       logger.warn(String.format("Unable to delete authority item '%s'", itemRefName), e);
+                       logger.warn(String.format("Unable to delete authority item '%s'", authorityItemSpecificer), e);
                        throw e;
                }
        }
 
        if (logger.isWarnEnabled() == true) {
-               if (result != refNameList.size()) {
+               if (result != itemShortIdList.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, refNameList.size()));
+                                       result, itemShortIdList.size()));
                }
        }
        
@@ -350,7 +347,7 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
      * @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) throws Exception {
+    protected void createLocalItem(ServiceContext ctx, String parentIdentifier, String itemIdentifier, Boolean syncHierarchicalRelationships) throws Exception {
        //
        // Create a URN short ID specifier for the getting a copy of the remote authority item
        //
@@ -361,7 +358,7 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
         // Get the remote payload
         //
         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadIn(sasAuthorityItemSpecifier, 
-                       ctx.getServiceName(), getEntityResponseType());
+                       ctx.getServiceName(), getEntityResponseType(), syncHierarchicalRelationships);
         sasPayloadIn = AuthorityServiceUtils.filterRefnameDomains(ctx, sasPayloadIn); // We need to filter domain name part of any and all refnames in the payload
         //
         // Using the payload from the remote server, create a local copy of the item
@@ -415,7 +412,7 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
                // 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);
+               createLocalItem(ctx, parentIdentifier, itemIdentifier, AuthorityClient.DONT_INCLUDE_RELATIONS);
                return 1; // exit with status of 1 means we created a new authority item
        }
        //
@@ -447,41 +444,32 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
      * @return
      * @throws Exception
      */
-    protected long syncRemoteItemRelationshipsWithLocalItem(ServiceContext ctx, String itemRefName) throws Exception {
+    protected long syncRemoteItemRelationshipsWithLocalItem(ServiceContext ctx, Specifier authoritySpecifier, String itemShortId) throws Exception {
        long result = -1;
-       //
-       // WARNING: THIS CODE IS NOT IMPLEMENTED YET
-       //
-       if (result == -1) return result;
-       //
-       // Using the item refname (with no local CSID), create specifiers that we'll use to find the local versions
-       //
-       
        
-       AuthorityTermInfo authorityTermInfo = RefNameUtils.parseAuthorityTermInfo(itemRefName);
-       String parentIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.inAuthority.name);
-       String itemIdentifier = Specifier.createShortIdURNValue(authorityTermInfo.name);
+       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 refname='%s' doesn't exist locally, so we'll create it.", itemRefName));
-               createLocalItem(ctx, parentIdentifier, itemIdentifier);
-               return 1; // exit with status of 1 means we created a new authority item
+               logger.info(String.format("Remote item with short ID ='%s' doesn't exist locally, so we can't synchronize its relationships.", itemShortId));
+               return result;
        }
        //
-       // If we get here, we know the item exists both locally and remotely, so we need to synchronize them.
+       // If we get here, we know the item exists both locally and remotely, so we need to synchronize the hierarchy relationships.
        //
        //
        try {
-               PoxPayloadOut theUpdate = authorityResource.synchronizeItemWithExistingContext(ctx, parentIdentifier, itemIdentifier, true);
+               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("Sync'd authority item parent='%s' id='%s with SAS.  Updated payload is: \n%s",
index 38cff706a9b33774493a6823d337fa413a0a6589..c924de9a8399f3cc9eb92b043e13187313b11312 100644 (file)
@@ -25,8 +25,10 @@ package org.collectionspace.services.common.vocabulary.nuxeo;
 
 import org.collectionspace.services.client.AuthorityClient;
 import org.collectionspace.services.client.IQueryManager;
+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.workflow.WorkflowClient;
 import org.collectionspace.services.common.ServiceMain;
 import org.collectionspace.services.common.UriTemplateRegistry;
@@ -61,10 +63,12 @@ import org.collectionspace.services.vocabulary.VocabularyItemJAXBSchema;
 import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
 import org.nuxeo.ecm.core.api.model.PropertyException;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.ws.rs.core.MultivaluedMap;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
@@ -415,14 +419,118 @@ public abstract class AuthorityItemDocumentModelHandler<AICommon>
                }
        }
     }
-        
+    
     /**
-     * This method synchronizes/updates a single authority item resource.
-     * for the handleSync method, the wrapDoc argument contains a authority item specifier.
+     * 
+     * @param wrapDoc
+     * @return
+     * @throws Exception
      */
+    protected boolean handleRelationsSync(DocumentWrapper<Object> wrapDoc) throws Exception {
+       boolean result = false;
+       ServiceContext<PoxPayloadIn, PoxPayloadOut> 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();
+        //
+        // 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.requestPayloadIn(sasAuthorityItemSpecifier, 
+                       getAuthorityServicePath(), getEntityResponseType(), AuthorityClient.INCLUDE_RELATIONS);
+        Long sasRev = getRevision(sasPayloadIn);
+        String sasWorkflowState = getWorkflowState(sasPayloadIn);
+        
+        //
+        // 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<PayloadInputPart> newPartList = new ArrayList<PayloadInputPart>();
+        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.filterRefnameDomains(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<Object> wrapDoc) throws Exception {
        boolean result = false;
+
+       if (this.getShouldSyncHierarchicalRelationships() == true) {
+               result = handleRelationsSync(wrapDoc);
+       } else {
+               result = handlePayloadSync(wrapDoc);
+       }
+       
+       return result;
+    }
+    
+    /**
+     * 
+     * @param wrapDoc
+     * @return
+     * @throws Exception
+     */
+    protected boolean handlePayloadSync(DocumentWrapper<Object> wrapDoc) throws Exception {
+       boolean result = false;
        ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = getServiceContext();
        
         //
@@ -454,7 +562,7 @@ public abstract class AuthorityItemDocumentModelHandler<AICommon>
         AuthorityItemSpecifier sasAuthorityItemSpecifier = new AuthorityItemSpecifier(SpecifierForm.URN_NAME, authorityShortId, itemShortId);
         // Get the shared authority server's copy
         PoxPayloadIn sasPayloadIn = AuthorityServiceUtils.requestPayloadIn(sasAuthorityItemSpecifier, 
-                       getAuthorityServicePath(), getEntityResponseType());
+                       getAuthorityServicePath(), getEntityResponseType(), AuthorityClient.DONT_INCLUDE_RELATIONS);
         Long sasRev = getRevision(sasPayloadIn);
         String sasWorkflowState = getWorkflowState(sasPayloadIn);
         //
@@ -500,9 +608,6 @@ public abstract class AuthorityItemDocumentModelHandler<AICommon>
                }
                result = true;
         }
-        //
-        // We need to synchronize the hierarchy relationships here.
-        //
         
         return result;
     }
@@ -613,16 +718,22 @@ public abstract class AuthorityItemDocumentModelHandler<AICommon>
                        //
                        // If all the refs are to soft-deleted objects, we should soft-delete this authority item instead of hard-deleting it and instead of failing.
                        //
-                       Boolean shouldUpdateRev = (Boolean) ctx.getProperty(AuthorityServiceUtils.SHOULD_UPDATE_REV_PROPERTY);
                        String parentCsid = (String) NuxeoUtils.getProperyValue(docModel, AuthorityItemJAXBSchema.IN_AUTHORITY);
                        String itemCsid = docModel.getName();
                        AuthorityResource authorityResource = (AuthorityResource) ctx.getResource(getAuthorityServicePath());
                        authorityResource.updateItemWorkflowWithTransition(ctx, parentCsid, itemCsid, WorkflowClient.WORKFLOWTRANSITION_DELETE, 
-                                       shouldUpdateRev != null ? shouldUpdateRev : true);
-                       result = false; // Don't delete since we just soft-deleted it.
+                                       this.getShouldUpdateRevNumber());
+                       result = false; // Don't delete since we just soft-deleted it.                  
                }
        }
        
+               //
+               // Since we've changed the state of the parent by deleting (or soft-deleting) one of its items, we might need to update the parent rev number
+               //
+       if (getShouldUpdateRevNumber() == true) {
+               updateRevNumbers(wrapDoc);
+       }
+       
        return result;
     }
     
index d95b94480ed6f70834c1a235ee2ff004fc1e1e06..a1fe2043b260618ff8cfec2f88e01811e23885c7 100644 (file)
@@ -22,6 +22,7 @@ public interface AuthorityClient<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_TYPE, P e
     
     public static final Boolean INCLUDE_DELETED_ITEMS = true;
     public static final Boolean INCLUDE_RELATIONS = true;
+    public static final Boolean DONT_INCLUDE_RELATIONS = !INCLUDE_RELATIONS;
 
        /*
         * Basic CRUD operations
@@ -42,7 +43,10 @@ public interface AuthorityClient<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_TYPE, P e
     Response readItem(String vcsid, String csid);
     
     //(R)ead Item
-    Response readItem(String vcsid, String csid, Boolean includeDeleted);    
+    Response readItem(String vcsid, String csid, Boolean includeDeleted);
+
+    //(R)ead Item
+    Response readItem(String authShortId, String itemShortId, Boolean includeDeleted, Boolean includeRelations);
 
     //(U)pdate Item
     Response updateItem(String vcsid, String csid, PoxPayloadOut poxPayloadOut);
@@ -53,6 +57,10 @@ public interface AuthorityClient<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_TYPE, P e
     //(D)elete Item
     Response deleteItem(String vcsid, String csid);
     
+    //(D)elete Item
+    Response deleteNamedItemInNamedAuthority(String authShortId, String itemShortId);
+
+    
     // Get a list of objects that
     Response getReferencingObjects(
             String parentcsid,
index c2a805a4503e3084cd9dbb02ceb4c4b901f8e793..6309042c4aeef47b47494a08ca0032f88849c2d6 100644 (file)
@@ -36,12 +36,17 @@ public abstract class AuthorityClientImpl<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_
     //(R)ead Item
     @Override
        public Response readItem(String vcsid, String csid) {
-       return getProxy().readItem(vcsid, csid, INCLUDE_DELETE_TRUE);
+       return getProxy().readItem(vcsid, csid, INCLUDE_DELETE_TRUE, INCLUDE_RELATIONS_FALSE);
     }
     
     @Override
     public Response readItem(String vcsid, String csid, Boolean includeDeleted) {
-       return getProxy().readItem(vcsid, csid, includeDeleted.toString());
+       return getProxy().readItem(vcsid, csid, includeDeleted.toString(), INCLUDE_RELATIONS_FALSE);
+    }
+    
+    @Override
+    public Response readItem(String vcsid, String csid, Boolean includeDeleted, Boolean includeRelations) {
+       return getProxy().readItem(vcsid, csid, includeDeleted.toString(), includeRelations.toString());
     }
 
     //(U)pdate Item
@@ -61,6 +66,13 @@ public abstract class AuthorityClientImpl<AUTHORITY_COMMON_TYPE, AUTHORITY_ITEM_
        return getProxy().deleteItem(vcsid, csid);
     }
     
+    //(D)elete Item
+    @Override    
+    public Response deleteNamedItemInNamedAuthority(String authShortId, String itemShortId) {
+       return getProxy().deleteNamedItemInNamedAuthority(authShortId, itemShortId);
+    }
+    
+    
     @Override
        public Response getReferencingObjects( // ClientResponse<AuthorityRefDocList>
             String parentcsid,
index dec6f470d42d88be67e6e63578a8b75dacb30817..0648c0ea71808e53356bdac9093d1c9ab088e8b5 100644 (file)
@@ -34,7 +34,9 @@ public interface AuthorityProxy extends CollectionSpaceCommonListPoxProxy {
     @Path("/{vcsid}/items/{csid}")
     Response readItem(@PathParam("vcsid") String vcsid,
                @PathParam("csid") String csid,
-               @QueryParam(WorkflowClient.WORKFLOWSTATE_QUERY) String workflowState);
+               @QueryParam(WorkflowClient.WORKFLOWSTATE_QUERY) String workflowState,
+               @QueryParam(CommonAPI.showRelations_QP) String showRelations);
+    
     
     //(U)pdate Item
     @PUT
@@ -44,14 +46,24 @@ public interface AuthorityProxy extends CollectionSpaceCommonListPoxProxy {
     //(U)pdate Item    
     @PUT
     @Path("/urn:cspace:name({specifier})/items/urn:cspace:name({itemspecifier})")
-    Response updateNamedItemInNamedAuthority(@PathParam("specifier")String specifier,
-               @PathParam("itemspecifier")String itemspecifier, byte[] xmlPayload);
+    Response updateNamedItemInNamedAuthority(
+               @PathParam("specifier")String specifier,
+               @PathParam("itemspecifier")String itemspecifier, 
+               byte[] xmlPayload);
 
     //(D)elete Item
     @DELETE
     @Path("/{vcsid}/items/{csid}")
     Response deleteItem(@PathParam("vcsid") String vcsid, @PathParam("csid") String csid);
     
+    //(D)elete Item
+    @DELETE
+    @Path("/urn:cspace:name({specifier})/items/urn:cspace:name({itemspecifier})")
+    public Response deleteNamedItemInNamedAuthority(
+               @PathParam("specifier")String specifier,
+               @PathParam("itemspecifier")String itemspecifier);
+
+    
     /**
      * Get a list of objects that reference a given authority term.
      * 
index 2ba2f68ca4c968a6d4a00b8ad12eb07bb8de766b..73129346a65b53d3497259aa2300248bbeffab70 100644 (file)
@@ -47,6 +47,13 @@ public abstract class PayloadPart {
 
        abstract public String asXML();
        
+       /**
+        * Clear the root DOM element
+        */
+       public void clearElementBody() {
+               elementBody = null;
+       }
+       
        public Element asElement() {
                Element result = elementBody;
                // if we don't already have an Element, let's try to create one from a JAXB object
index 212277d30564d1078b19b83834b15674130febd6..76577afe9f04543c3016b9b86585d2f72e8f4bfe 100644 (file)
@@ -15,8 +15,8 @@ import javax.xml.bind.Unmarshaller;
 import javax.xml.transform.stream.StreamSource;
 
 import com.sun.xml.bind.api.impl.NameConverter;
-import org.apache.commons.io.FileUtils;
 
+import org.apache.commons.io.FileUtils;
 import org.dom4j.Document;
 import org.dom4j.DocumentException;
 import org.dom4j.DocumentHelper;
@@ -72,6 +72,48 @@ public abstract class PoxPayload<PT extends PayloadPart> {
                parseParts();
        }
        
+       /**
+        * Creates and returns an XML string representation of ourself.
+        *
+        * @return the string
+        */
+       public String toXML() {
+               String result = null;
+        Document document = createDOMFromParts();
+
+        result = document.asXML();
+               
+               if (logger.isTraceEnabled() == true) {
+                       logger.trace("\n\n<<<< Payload : BEGIN <<<<\n" + result + "\n<<<< Payload : END   <<<<\n");
+               }
+               
+               return result;
+       }
+       
+       protected Document createDOMFromParts() {
+               Document result = null;
+               
+        Document document = DocumentHelper.createDocument();
+        document.setXMLEncoding("UTF-8");
+        document.setName(getName());
+        Element root = document.addElement( "document" );
+        root.addAttribute("name", getName());        
+               
+               Iterator<PT> it = getParts().iterator();
+               while (it.hasNext() == true) {
+                       PT outPart = it.next();
+                       Element element = outPart.asElement();                  
+                       if (element != null) {
+                               root.add(element.detach());
+                       } else {
+                               //Add if (logger.isTraceEnabled() == true) logger.trace("Output part: " + outPart.getLabel() + " was empty.");
+                       }
+               }
+               result = document;
+                               
+               return result;
+       }
+       
        /**
         * Instantiates a new PoxPayload by parsing the payload into a DOM4j
         * Document instance
@@ -197,6 +239,15 @@ public abstract class PoxPayload<PT extends PayloadPart> {
        public List<PT> getParts() {
                return parts;
        }
+       
+       /**
+        * Set a new set of parts.
+        * 
+        * @param newParts
+        */
+       public void setParts(ArrayList<PT> newParts) {
+               this.parts = newParts;
+       }
                
        /**
         * Adds a POX part to the list of existing parts with the label 'label'.
@@ -219,7 +270,15 @@ public abstract class PoxPayload<PT extends PayloadPart> {
        public PT addPart(PT entity) {
                parts.add(entity);
                return entity;
-       }       
+       }
+       
+       /**
+        * Removes a POX part from our list of parts
+        * @param entity
+        */
+       public void removePart(PT entity) {
+               parts.remove(entity);
+       }
                
     /**
      * Gets the Java package name from the specified namespace.  This method
index 83583b4ed900ababfa54bacc2fa5e7ecc133610a..082f7d7b912e637b9a82148785cea5f61a0c997a 100644 (file)
@@ -75,49 +75,7 @@ public class PoxPayloadOut extends PoxPayload<PayloadOutputPart> {
        public String asXML() {
                return toXML();
        }
-       
-       /**
-        * Creates and returns an XML string representation of ourself.
-        *
-        * @return the string
-        */
-       public String toXML() {
-               String result = null;
-        Document document = createDOMFromParts();
-
-        result = document.asXML();
-               
-               if (logger.isTraceEnabled() == true) {
-                       logger.trace("\n\n<<<< Payload Out : BEGIN <<<<\n" + result + "\n<<<< Payload Out : END   <<<<\n");
-               }
-               
-               return result;
-       }
-       
-       private Document createDOMFromParts() {
-               Document result = null;
-               
-        Document document = DocumentHelper.createDocument();
-        document.setXMLEncoding("UTF-8");
-        document.setName(getName());
-        Element root = document.addElement( "document" );
-        root.addAttribute("name", getName());        
-               
-               Iterator<PayloadOutputPart> it = getParts().iterator();
-               while (it.hasNext() == true) {
-                       PayloadOutputPart outPart = it.next();
-                       Element element = outPart.asElement();                  
-                       if (element != null) {
-                               root.add(element.detach());
-                       } else {
-                               //Add if (logger.isTraceEnabled() == true) logger.trace("Output part: " + outPart.getLabel() + " was empty.");
-                       }
-               }
-               result = document;
                                
-               return result;
-       }
-               
        /**
         * Gets the DOM object that we created at init time.  This should never be null;
         *
index 5459dce866cf98fccb349ea14b144acd7d1a5508..99a5acad7fae986ce0ffa73d5f4d81e5f54c291c 100644 (file)
@@ -872,6 +872,59 @@ public abstract class AbstractAuthorityServiceTest<AUTHORITY_COMMON_TYPE, AUTHOR
         }
     }
     
+    @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
+               dependsOnMethods = {"updateLocalItemWithSync", "CRUDTests"})
+    public void deleteLocalItemWithSync(String testName) throws Exception {
+       final int itemIndexToDelete = 1;
+        //
+       // First check to see if we support sync.
+       //
+        AuthorityClient client = (AuthorityClient) getClientInstance();
+       if (client.supportsSync() == false) {
+               return; // Exit the test since this authority doesn't support synchronization
+       }        
+       
+        // Perform test setup for a DELETE.
+        setupDelete();
+        AUTHORITY_ITEM_TYPE theUpdate = null;
+
+        // Delete an item from the SAS server
+        AuthorityClient sasClient = (AuthorityClient) this.getSASClientInstance();
+        Response res = sasClient.deleteNamedItemInNamedAuthority(knownSASAuthorityResourceIdentifier, knownSASItemIdentifiersList.get(itemIndexToDelete));
+        try {
+               Assert.assertEquals(res.getStatus(), testExpectedStatusCode);   
+        } finally {
+               res.close();
+        }
+        
+        // Synchronize the local item's parent authority and verify the delete we just made
+        // to the SAS takes place locally after the sync -i.e., the local item should be deleted as well.
+        setupSync();
+        AuthorityClient localClient = (AuthorityClient) this.getClientInstance();
+       Response response = localClient.syncByName(knownSASAuthorityResourceIdentifier); // Notice we're using the Short ID (short ID is the same on the local and SAS)
+        try {
+               int statusCode = response.getStatus();
+               Assert.assertTrue(testRequestType.isValidStatusCode(statusCode), invalidStatusCodeMessage(testRequestType, statusCode));
+               Assert.assertEquals(statusCode, testExpectedStatusCode);
+        } finally {
+               response.close();
+        }        
+        
+        // Verify that the local item has been deleted.
+        setupReadNonExistent();
+        res = localClient.readNamedItemInNamedAuthority(knownSASAuthorityResourceIdentifier, knownSASItemIdentifiersList.get(itemIndexToDelete));
+        try {
+               Assert.assertEquals(res.getStatus(), testExpectedStatusCode);
+               knownSASItemIdentifiersList.remove(0); // remove it from our known set now that we've deleted it
+        } finally {
+               res.close();
+        }
+    }
+    
+    /**
+     * We create a new item on the SAS, perform a sync with the local authority, and verify the local authority contains a copy
+     * of the SAS item. 
+     */
     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
                dependsOnMethods = {"veryifySyncWithSAS", "CRUDTests"})
     public void updateLocalItemWithSync(String testName) throws Exception {
index 79c8762cca2c8f2f0a35e00835f3e45eac1ad360..a4e2462131fb645478ef5ed0ff792f3e3ae69f6c 100644 (file)
@@ -1,13 +1,13 @@
 #
 # URL of the CollectionSpace server and user credentials
 #
-cspace.url=http://qa.collectionspace.org:8180/cspace-services/
+cspace.url=http://localhost:8180/cspace-services/
 cspace.ssl=false
 cspace.auth=true
-cspace.user=admin@materials.collectionspace.org
+cspace.user=admin@testsci.collectionspace.org
 cspace.password=Administrator
 #
 # default tenant information
 #
-cspace.tenant=2000
-cspace.tenantID=materials.collectionspace.org
\ No newline at end of file
+cspace.tenant=2
+cspace.tenantID=testsci.collectionspace.org
\ No newline at end of file
index 7fd703394558bb265b5ab8fb6ecf55969afec9d9..d0e8f476c12efa9b2a57d96f9e0921875dd304c5 100644 (file)
@@ -115,7 +115,7 @@ public class MultipartServiceContextImpl
     @Deprecated
     public Object getInputPart(String label, Class clazz) throws IOException {
         return getInputPart(label);
-                    }
+    }
     
     /* (non-Javadoc)
      * @see org.collectionspace.services.common.context.MultipartServiceContext#getInputPart(java.lang.String, java.lang.Class)
index f0caf18458334050469fbd3e7130aec191d55673..6b9e51aaf81a6098fafcbbf4fdcf4e3c7ad07d8b 100644 (file)
@@ -72,7 +72,7 @@ public interface StorageClient {
      * @throws DocumentNotFoundException if entity not found
      * @throws DocumentException
      */
-    void delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException, DocumentException;
+    boolean delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException, DocumentException;
 
 
     /**
index e10113d105c1fbb5ba8ad4e87303e07e0f44b810..4c52dba953af06a5796f67f83ac33139bdb182cd 100644 (file)
@@ -384,9 +384,10 @@ public class JpaRelationshipStorageClient<T> extends JpaStorageClientImpl {
      * @throws DocumentException
      */
     @Override
-    public void delete(ServiceContext ctx, String id, DocumentHandler handler)
+    public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
             throws DocumentNotFoundException, DocumentException {
-
+       boolean result = true;
+       
         if (ctx == null) {
             throw new IllegalArgumentException(
                     "delete : ctx is missing");
@@ -431,6 +432,8 @@ public class JpaRelationshipStorageClient<T> extends JpaStorageClientImpl {
                 JpaStorageUtils.releaseEntityManagerFactory(emf);
             }
         }
+        
+        return result;
     }
 
     /**
index 560c355ee4abc9f3b31789110789573317796ef2..759719ffb3c0a2d0b729d8125a06ff50ec51e7ef 100644 (file)
@@ -458,8 +458,10 @@ public class JpaStorageClientImpl implements StorageClient {
      * @see org.collectionspace.services.common.storage.StorageClient#delete(org.collectionspace.services.common.context.ServiceContext, java.lang.String)
      */
     @Override
-    public void delete(ServiceContext ctx, String id, DocumentHandler handler)
+    public boolean delete(ServiceContext ctx, String id, DocumentHandler handler)
             throws DocumentNotFoundException, DocumentException {
+       boolean result = true;
+       
         if (ctx == null) {
             throw new IllegalArgumentException(
                     "delete(ctx, ix, handler): ctx is missing");
@@ -468,6 +470,7 @@ public class JpaStorageClientImpl implements StorageClient {
             throw new IllegalArgumentException(
                     "delete(ctx, ix, handler): handler is missing");
         }
+        
         EntityManagerFactory emf = null;
         EntityManager em = null;
         try {
@@ -510,6 +513,8 @@ public class JpaStorageClientImpl implements StorageClient {
                 JpaStorageUtils.releaseEntityManagerFactory(emf);
             }
         }
+        
+        return result;
     }
 
     /**
index 6db4c8d154be53812e3d9d34488d14133c763a9e..e692420dc85f50427d95ca7df3f57a4c76fefdfe 100644 (file)
@@ -210,6 +210,20 @@ public class RefNameServiceUtils {
        public Specifier getItemSpecifier() {
                return this.itemSpecifier;
        }
+       
+       @Override
+       public String toString() {
+               String result = "%s/items/%s";
+               
+               try {
+                               result = String.format(result, this.parentSpecifier.getURNValue(), this.itemSpecifier.getURNValue());
+                       } catch (Exception e) {
+                               result = "Unknown error trying to get string representation of Specifier.";
+                               logger.error(result, e);
+                       }
+               
+               return result;
+       }
     }
 
     public static class AuthRefConfigInfo {
index 94e0d35332adf3807a4361d4170863687f42c0b2..a135db4ed961c2ca13b839ba1ee576cd0b2bf28e 100644 (file)
@@ -1229,6 +1229,13 @@ public abstract class   RemoteDocumentModelHandlerImpl<T, TL>
             if (logger.isTraceEnabled()) {
                 logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " got existing relations.");
             }
+        } else {
+               // For debugging purpose only.
+            if (logger.isDebugEnabled()) {
+               String debugMsg = "AuthItemDocHndler.updateRelations for: " + itemCSID + " with an fUpdate value of FALSE.";
+                logger.debug(debugMsg);
+                throw new Exception(debugMsg);
+            }
         }
 
         for (RelationsCommonList.RelationListItem inboundItem : inboundList) {
@@ -1309,7 +1316,7 @@ public abstract class   RemoteDocumentModelHandlerImpl<T, TL>
         if (logger.isTraceEnabled()) {
             logger.trace("AuthItemDocHndler.updateRelations for: " + itemCSID + " done.");
         }
-        //We return all elements on the inbound list, since we have just worked to make them exist in the system
+        // We return all elements on the inbound list, since we have just worked to make them exist in the system
         // and be non-redundant, etc.  That list came from relationsCommonListBody, so it is still attached to it, just pass that back.
         return relationsCommonListBody;
     }
index 2529c99ec6061455d65f21259e3e7143dca43585..45e993869a455844e806cbc372eb987fb1618d89 100644 (file)
@@ -1595,8 +1595,10 @@ public class RepositoryClientImpl implements RepositoryClient<PoxPayloadIn, PoxP
      * @throws DocumentException
      */
     @Override
-    public void delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException,
+    public boolean delete(ServiceContext ctx, String id, DocumentHandler handler) throws DocumentNotFoundException,
             DocumentException, TransactionException {
+       boolean result = true;
+       
         if (ctx == null) {
             throw new IllegalArgumentException(
                     "delete(ctx, ix, handler): ctx is missing");
@@ -1619,6 +1621,8 @@ public class RepositoryClientImpl implements RepositoryClient<PoxPayloadIn, PoxP
                 ((DocumentModelHandler) handler).setRepositorySession(repoSession);
                 if (handler.handle(Action.DELETE, wrapDoc)) {
                        repoSession.removeDocument(docRef);
+                } else {
+                       result = false; // delete failed for some reason
                 }
             } catch (ClientException ce) {
                 String msg = logException(ce, "Could not find document to delete with CSID=" + id);
@@ -1638,6 +1642,8 @@ public class RepositoryClientImpl implements RepositoryClient<PoxPayloadIn, PoxP
                 releaseRepositorySession(ctx, repoSession);
             }
         }
+        
+        return result;
     }
 
     /* (non-Javadoc)
index e51a9be115fd504b6a2f846f42aa8292fd647b79..c483de0a3d7ea6eb4587eda0f80b87a0983f7dd5 100644 (file)
@@ -25,7 +25,9 @@ package org.collectionspace.services.vocabulary.nuxeo;
 
 import java.util.regex.Pattern;
 
+import org.collectionspace.services.relation.RelationsCommonList;
 import org.collectionspace.services.vocabulary.VocabularyitemsCommon;
+import org.collectionspace.services.client.RelationClient;
 import org.collectionspace.services.common.context.MultipartServiceContext;
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.document.DocumentHandler.Action;
@@ -60,36 +62,44 @@ public class VocabularyItemValidatorHandler implements ValidatorHandler {
         }
         
         try {
-            MultipartServiceContext mctx = (MultipartServiceContext) ctx;
-            VocabularyitemsCommon vocabItem = (VocabularyitemsCommon) mctx.getInputPart(mctx.getCommonPartLabel(),
-                    VocabularyitemsCommon.class);
-            String msg = "";
+            String errMessage = "";
             boolean invalid = false;
+            MultipartServiceContext mctx = (MultipartServiceContext) ctx;
+            VocabularyitemsCommon vocabItem = (VocabularyitemsCommon) mctx.getInputPart(mctx.getCommonPartLabel());
             
-            // Validation occurring on both creates and updates
-            String displayName = vocabItem.getDisplayName();
-            if ((displayName == null) || (displayName.trim().length() < 2)) {
-                invalid = true;
-                msg += "displayName must be non-null and contain at least 2 non-whitespace characters";
-            }
-            
-            // Validation specific to creates or updates
-            if (action.equals(Action.CREATE)) {
-                String shortId = vocabItem.getShortIdentifier();
-                // Per CSPACE-2215, shortIdentifier values that are null (missing)
-                // oe the empty string are now legally accepted in create payloads.
-                // In either of those cases, a short identifier will be synthesized from
-                // a display name or supplied in another manner.
-                if ((shortId != null) && (shortIdBadPattern.matcher(shortId).find())) {
+            if (vocabItem != null) {
+                   // Validation occurring on both creates and updates
+                   String displayName = vocabItem.getDisplayName();
+                   if (displayName == null || displayName.trim().length() < 2) {
+                       invalid = true;
+                       errMessage += "displayName must be non-null and contain at least 2 non-whitespace characters";
+                   }
+                   
+                   // Validation specific to creates or updates
+                   if (action.equals(Action.CREATE)) {
+                       String shortId = vocabItem.getShortIdentifier();
+                       // Per CSPACE-2215, shortIdentifier values that are null (missing
+                       // or the empty string) are now legally accepted in CREATE/POST payloads.
+                       // In either of these cases, a short identifier will be synthesized from
+                       // a display name or supplied in another manner.
+                       if ((shortId != null) && (shortIdBadPattern.matcher(shortId).find())) {
+                           invalid = true;
+                           errMessage += "shortIdentifier must only contain standard word characters";
+                       }
+                   } else if (action.equals(Action.UPDATE)) {
+                       // What is this ELSE clause for?
+                   }
+            } else {
+               RelationsCommonList rcl = (RelationsCommonList) mctx.getInputPart(RelationClient.SERVICE_COMMON_LIST_NAME);
+               if (rcl == null) {
                     invalid = true;
-                    msg += "shortIdentifier must only contain standard word characters";
-                }
-            } else if (action.equals(Action.UPDATE)) {
+                    errMessage += "The vocabulary item payload is missing both a common payload and relations part.  At lease one of these must exist in the payload.";
+               }               
             }
-
+            
             if (invalid) {
-                logger.error(msg);
-                throw new InvalidDocumentException(msg);
+                logger.error(errMessage);
+                throw new InvalidDocumentException(errMessage);
             }
         } catch (InvalidDocumentException ide) {
             throw ide;