]> git.aero2k.de Git - tmp/jakarta-migration.git/commitdiff
CSPACE-6802: To prevent creating more than one vocab/authority with the same shortid...
authorremillet <remillet@yahoo.com>
Mon, 31 Aug 2015 16:00:04 +0000 (09:00 -0700)
committerremillet <remillet@yahoo.com>
Mon, 31 Aug 2015 16:00:04 +0000 (09:00 -0700)
18 files changed:
3rdparty/nuxeo/nuxeo-server/6.0/config/proto-repo-config.xml
3rdparty/nuxeo/nuxeo-server/6.0/config/vcsconfig.sql.txt [new file with mode: 0644]
services/JaxRsServiceProvider/src/main/resources/log4j.properties
services/acquisition/client/src/test/java/org/collectionspace/services/client/test/AcquisitionAuthRefsTest.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/AuthorityResource.java
services/authority/service/src/main/java/org/collectionspace/services/common/vocabulary/nuxeo/AuthorityDocumentModelHandler.java
services/client/src/main/java/org/collectionspace/services/client/test/AbstractServiceTestImpl.java
services/client/src/main/java/org/collectionspace/services/client/test/BaseServiceTest.java
services/common/src/main/java/org/collectionspace/services/common/storage/StorageClient.java
services/common/src/main/java/org/collectionspace/services/common/storage/jpa/JpaStorageClientImpl.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/CoreSessionWrapper.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/NuxeoClientEmbedded.java
services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java
services/conditioncheck/client/src/test/java/org/collectionspace/services/client/test/ConditioncheckAuthRefsTest.java
services/movement/client/src/test/java/org/collectionspace/services/client/test/MovementAuthRefsTest.java
services/organization/client/src/test/java/org/collectionspace/services/client/test/OrgAuthorityAuthRefsTest.java
services/relation/client/src/test/java/org/collectionspace/services/client/test/RelationServiceTest.java
services/vocabulary/client/src/test/java/org/collectionspace/services/client/test/VocabularyServiceTest.java

index a6f7e3a467c43d4e7eb4d04d6e8f5d926ecfb3bb..6b7ec696e1d5824c67cd9983b30a3a3b120e5154 100644 (file)
@@ -26,6 +26,7 @@
                        <binaryStore path=""/> <!-- Default value will be repository/database name.  Can be partial or full system path.  Partial path is relative to Nuxeo's data dir -->
                        <xa-datasource>@XA_DATASOURCE@</xa-datasource> <!-- The transactional datasource for Nuxeo -->
                        <noDDL>false</noDDL>
+                       <sqlInitFile>vcsconfig.sql.txt</sqlInitFile> <!-- see https://doc.nuxeo.com/display/ADMINDOC/VCS+Configuration#VCSConfiguration-DatabaseCreationOption -->              
                        <aclOptimizations enabled="true"/>
                        <pathOptimizations enabled="true"/>
                        <idType>varchar</idType>
diff --git a/3rdparty/nuxeo/nuxeo-server/6.0/config/vcsconfig.sql.txt b/3rdparty/nuxeo/nuxeo-server/6.0/config/vcsconfig.sql.txt
new file mode 100644 (file)
index 0000000..c28de57
--- /dev/null
@@ -0,0 +1,14 @@
+#
+# A place to modify the Nuxeo database with SQL statements.
+# See https://doc.nuxeo.com/display/ADMINDOC/VCS+Configuration#VCSConfiguration-DatabaseCreationOption
+#
+
+
+#CATEGORY: afterTableCreation
+
+
+#
+# Add a unique constraint to the shortidentifier column of the vocabularies_common table.
+#
+LOG.INFO Adding a unique constraint to the shortidentifier column of the vocabularies_common table
+ALTER TABLE vocabularies_common add CONSTRAINT shortid_unique UNIQUE (shortidentifier);
\ No newline at end of file
index e0a0a53782f59124f8dbc8c32e60c5b38283ee2f..350c73c5605175a8ab8954cc356551dc905cce1c 100644 (file)
@@ -53,6 +53,7 @@ log4j.additivity.perf.collectionspace=false
 # CollectionSpace loggers and default levels - all loggers using the rootLogger if not otherwise specified
 #
 log4j.logger.org.collectionspace=WARN
+log4j.logger.org.collectionspace.services.nuxeo.client.java.NuxeoClientEmbedded=TRACE
 #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 8e70ee2e6c0ca759216d04af190d02872e3cc47a..9f54a4309dbd3156692959edf2c799b15fa6c82b 100644 (file)
@@ -70,7 +70,7 @@ public class AcquisitionAuthRefsTest extends BaseServiceTest<AbstractCommonList>
 
        // Instance variables specific to this test.
        //    final String SERVICE_PATH_COMPONENT = AcquisitionClient.SERVICE_PATH_COMPONENT;//"acquisitions";
-       final String PERSON_AUTHORITY_NAME = "TestPersonAuth";
+       final String PERSON_AUTHORITY_NAME = "TestPersonAuthForAquisitionTest";
        private String knownResourceId = null;
        private List<String> acquisitionIdsCreated = new ArrayList<String>();
        private List<String> personIdsCreated = new ArrayList<String>();
index c32d17efad3c797f606c4834f0cee3786fa8b064..867c53c1195bb34b9876bd11c453170d1e2a1a03 100644 (file)
@@ -347,20 +347,29 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
     }
 
 
-    @POST                                                                                                      //FIXME: REM - 5/1/2012 - We can probably remove this method.
-    public Response createAuthority(String xmlPayload) {       //REM - This method is never reached by the JAX-RS client -instead the "create" method in ResourceBase.java is getting called.
-        try {
-            PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
-            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(input);
-            DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
-            String csid = getRepositoryClient(ctx).create(ctx, handler);
-            UriBuilder path = UriBuilder.fromResource(resourceClass);
-            path.path("" + csid);
-            Response response = Response.created(path.build()).build();
-            return response;
-        } catch (Exception e) {
-            throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
-        }
+    @POST
+    public Response createAuthority(String xmlPayload) {
+       //
+       // Requests to create new authorities come in on new threads. Unfortunately, we need to synchronize those threads on this block because, as of 8/27/2015, we can't seem to get Nuxeo
+       // transaction code to deal with a database level UNIQUE constraint violations on the 'shortidentifier' column of the vocabularies_common table.
+       // Therefore, to prevent having multiple authorities with the same shortid, we need to synchronize
+       // the code that creates new authorities.  The authority document model handler will first check for authorities with the same short id before
+       // trying to create a new authority.
+       //
+       synchronized(AuthorityResource.class) {
+               try {
+                   PoxPayloadIn input = new PoxPayloadIn(xmlPayload);
+                   ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(input);
+                   DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
+                   String csid = getRepositoryClient(ctx).create(ctx, handler);
+                   UriBuilder path = UriBuilder.fromResource(resourceClass);
+                   path.path("" + csid);
+                   Response response = Response.created(path.build()).build();
+                   return response;
+               } catch (Exception e) {
+                   throw bigReThrow(e, ServiceMessages.CREATE_FAILED);
+               }
+       }
     }
 
     protected String buildWhereForAuthByName(String name) {
@@ -504,9 +513,10 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
      * 
      * @return the response
      */
-    @DELETE
+    @Deprecated
+//    @DELETE
     @Path("{csid}")
-    public Response deleteAuthority(@PathParam("csid") String csid) {
+    public Response old_deleteAuthority(@PathParam("csid") String csid) {
         if (logger.isDebugEnabled()) {
             logger.debug("deleteAuthority with csid=" + csid);
         }
@@ -520,6 +530,49 @@ public abstract class AuthorityResource<AuthCommon, AuthItemHandler>
             throw bigReThrow(e, ServiceMessages.DELETE_FAILED, csid);
         }
     }
+    
+    /**
+     * Delete authority
+     * 
+     * @param csid the csid or a URN specifier form -e.g., urn:cspace:name(OurMuseumPersonAuthority)
+     * 
+     * @return the response
+     */
+    @DELETE
+    @Path("{csid}")
+    public Response deleteAuthority(
+            @Context Request request,
+            @Context UriInfo ui,
+            @PathParam("csid") String specifier) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("deleteAuthority with specifier=" + specifier);
+        }
+        
+        try {
+            ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx = createServiceContext(ui);
+            DocumentHandler<?, AbstractCommonList, DocumentModel, DocumentModelList> handler = createDocumentHandler(ctx);
+
+            Specifier spec = getSpecifier(specifier, "getAuthority", "GET");
+            if (spec.form == SpecifierForm.CSID) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("deleteAuthority with csid=" + spec.value);
+                }
+                ensureCSID(spec.value, ServiceMessages.DELETE_FAILED, "Authority.csid");
+                getRepositoryClient(ctx).delete(ctx, spec.value, handler);
+            } else {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("deleteAuthority with specifier=" + spec.value);
+                }              
+                String whereClause = buildWhereForAuthByName(spec.value);
+                getRepositoryClient(ctx).deleteWithWhereClause(ctx, whereClause, handler);
+            }
+            
+            return Response.status(HttpResponseCodes.SC_OK).build();
+        } catch (Exception e) {
+            throw bigReThrow(e, ServiceMessages.DELETE_FAILED, specifier);
+        }
+    }
+    
 
     /*************************************************************************
      * Create an AuthorityItem - this is a sub-resource of Authority
index 6a5656a23fee7659c3e1a2c5bb8d23d2becd0790..137bd11b372d421b354c4e21cbf9aea4f7761b89 100644 (file)
@@ -31,6 +31,8 @@ import org.collectionspace.services.common.api.RefName;
 import org.collectionspace.services.common.api.RefName.Authority;
 import org.collectionspace.services.common.api.Tools;
 import org.collectionspace.services.common.context.ServiceContext;
+import org.collectionspace.services.common.document.DocumentException;
+import org.collectionspace.services.common.document.DocumentNotFoundException;
 import org.collectionspace.services.common.document.DocumentWrapper;
 import org.collectionspace.services.common.vocabulary.AuthorityJAXBSchema;
 import org.collectionspace.services.config.service.ObjectPartType;
@@ -86,18 +88,58 @@ public abstract class AuthorityDocumentModelHandler<AuthCommon>
         handleDisplayNameAsShortIdentifier(wrapDoc.getWrappedObject(), authorityCommonSchemaName);
         updateRefnameForAuthority(wrapDoc, authorityCommonSchemaName);//CSPACE-3178
     }
+    
+    protected String buildWhereForShortId(String name) {
+        return authorityCommonSchemaName
+                + ":" + AuthorityJAXBSchema.SHORT_IDENTIFIER
+                + "='" + name + "'";
+    }
+    
+    private boolean isUnique(DocumentModel docModel, String schemaName) throws DocumentException {
+       boolean result = true;
+       
+       ServiceContext ctx = this.getServiceContext();
+        String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
+       String nxqlWhereClause = buildWhereForShortId(shortIdentifier);
+       try {
+                       DocumentWrapper<DocumentModel> searchResultWrapper = getRepositoryClient(ctx).findDoc(ctx, nxqlWhereClause);
+                       if (searchResultWrapper != null) {
+                               result = false;
+                               if (logger.isInfoEnabled() == true) {
+                                       DocumentModel searchResult = searchResultWrapper.getWrappedObject();
+                                       String debugMsg = String.format("Could not create a new authority with a short identifier of '%s', because one already exists with the same short identifer: CSID = '%s'",
+                                                       shortIdentifier, searchResult.getName());
+                                       logger.trace(debugMsg);
+                               }
+                       }
+               } catch (DocumentNotFoundException e) {
+                       // Not a problem, just means we couldn't find another authority with that short ID
+               }
+       
+       return result;
+    }
 
     /**
      * If no short identifier was provided in the input payload,
-     * generate a short identifier from the display name.
+     * generate a short identifier from the display name. Either way though,
+     * the short identifier needs to be unique.
      */
     private void handleDisplayNameAsShortIdentifier(DocumentModel docModel, String schemaName) throws Exception {
         String shortIdentifier = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER);
         String displayName = (String) docModel.getProperty(schemaName, AuthorityJAXBSchema.DISPLAY_NAME);
         String shortDisplayName = "";
+        String generateShortIdentifier = null;
         if (Tools.isEmpty(shortIdentifier)) {
-            String generatedShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
-            docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, generatedShortIdentifier);
+               generateShortIdentifier = AuthorityIdentifierUtils.generateShortIdentifierFromDisplayName(displayName, shortDisplayName);
+            docModel.setProperty(schemaName, AuthorityJAXBSchema.SHORT_IDENTIFIER, shortIdentifier);
+        }
+        
+        if (isUnique(docModel, schemaName) == false) {
+               String shortId = generateShortIdentifier == null ? shortIdentifier : generateShortIdentifier;
+               String errMsgVerb = generateShortIdentifier == null ? "supplied" : "generated";
+               String errMsg = String.format("The %s short identifier '%s' was not unique, so the new authority could not be created.",
+                               errMsgVerb, shortId);
+               throw new DocumentException(errMsg);
         }
     }
  
index f1f6931643ea128399f2089d5f682afe00080bd4..8f1d058aba0914ad930985f0694ac7fd1f4903b8 100644 (file)
@@ -1117,7 +1117,8 @@ public abstract class AbstractServiceTestImpl<CLT, CPT, REQUEST_TYPE, RESPONSE_T
                 //
                 // Get the total count of non-deleted existing records
                 //
-                String parentCsid = this.createTestObject("REM_" + testName);
+               String identifier = String.format("Test_Workflow_%d", System.currentTimeMillis());
+                String parentCsid = this.createTestObject(identifier);
 
                 //
                 // Create 3 new items
index 9cde30bf120b787e5802519ffc9fb3ddaec1af0d..ac280a4cee50b123494ce874c70a5ea6f9dda8c6 100644 (file)
@@ -50,7 +50,6 @@ import org.apache.commons.httpclient.methods.PostMethod;
 import org.apache.commons.httpclient.methods.PutMethod;
 import org.apache.commons.httpclient.methods.StringRequestEntity;
 import org.apache.commons.io.FileUtils;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
@@ -58,7 +57,6 @@ import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeMethod;
 import org.testng.annotations.DataProvider;
 import org.w3c.dom.Document;
-
 import org.collectionspace.services.client.AuthorityClient;
 import org.collectionspace.services.client.CollectionSpaceClient;
 import org.collectionspace.services.client.PayloadInputPart;
@@ -155,6 +153,29 @@ public abstract class BaseServiceTest<CLT> {
             Response.Status.OK.getStatusCode();
     protected static final int STATUS_FORBIDDEN =
             Response.Status.FORBIDDEN.getStatusCode();
+    
+    //
+    // "Global flag to cancel cleanup() method
+    //
+    private static boolean cancelCleanup = false;
+    
+    //
+    // Decide if cleanup should happen
+    //
+    protected boolean cleanupCancelled() {
+       if (cancelCleanup == false) {
+               String noTestCleanup = System.getProperty(NO_TEST_CLEANUP);
+               if (Boolean.TRUE.toString().equalsIgnoreCase(noTestCleanup)) {
+                   cancelCleanup = true;
+               }
+       }
+        
+        return cancelCleanup;
+    }
+    
+    protected void cancelCleanup() {
+       cancelCleanup = true;
+    }
 
     /**
      * Instantiates a new base service test.
@@ -253,6 +274,22 @@ public abstract class BaseServiceTest<CLT> {
         testExpectedStatusCode = expectedStatusCode;
         testRequestType = reqType;
     }
+    
+    protected long randomPause(Random randomGenerator, long maxPauseMillis) {
+       long result = 0;
+       
+       if (maxPauseMillis != 0) {              
+               try {
+                               Thread.sleep(result = randomGenerator.nextInt(500));
+                       } catch (InterruptedException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                               result = -1;
+                       }
+       }
+       
+       return result;
+    }
 
     /**
      * Returns an error message indicating that the status code returned by a
@@ -700,13 +737,13 @@ public abstract class BaseServiceTest<CLT> {
      */
     @AfterClass(alwaysRun = true)
     public void cleanUp() {
-        String noTestCleanup = System.getProperty(NO_TEST_CLEANUP);
-        if (Boolean.TRUE.toString().equalsIgnoreCase(noTestCleanup)) {
+        if (cleanupCancelled() == true) {
             if (logger.isDebugEnabled()) {
                 logger.debug("Skipping Cleanup phase ...");
             }
             return;
         }
+        
         if (logger.isDebugEnabled()) {
             logger.debug("Cleaning up temporary resources created for testing ...");
         }
index 8c72ed8fa279e518ad39ce32dbbe51400778d3ad..9df4fbffcc19aee30b5c8bc0454dd54e996c7e59 100644 (file)
@@ -50,7 +50,16 @@ public interface StorageClient {
      * @throws DocumentException
      */
     void delete(ServiceContext ctx, String id) throws DocumentNotFoundException, DocumentException;
-
+    
+    /**
+     * Delete with an NXQL 'WHERE' clause.  If multiple documents match the clause, this method delete just
+     * the first document it finds.
+     * @param ctx
+     * @param specifier
+     * @throws DocumentNotFoundException
+     * @throws DocumentException
+     */
+    void deleteWithWhereClause(ServiceContext ctx, String whereClause, DocumentHandler handler) throws DocumentNotFoundException, DocumentException;
 
     /**
      * delete a entity from the persistence store
index 457f7ab11015722bf1572d80c7a5954424a4beb4..9e52abd0adf8b92ea1433e687cb7eb7a23a95b22 100644 (file)
@@ -19,7 +19,9 @@ package org.collectionspace.services.common.storage.jpa;
 
 import java.util.Date;
 import java.util.List;
+
 import javax.persistence.RollbackException;
+
 import java.sql.BatchUpdateException;
 
 import javax.persistence.EntityManager;
@@ -35,13 +37,11 @@ import org.collectionspace.services.common.document.DocumentHandler.Action;
 import org.collectionspace.services.common.document.DocumentWrapper;
 import org.collectionspace.services.common.document.DocumentWrapperImpl;
 import org.collectionspace.services.common.document.JaxbUtils;
-
 import org.collectionspace.services.common.storage.StorageClient;
 import org.collectionspace.services.common.context.ServiceContextProperties;
 import org.collectionspace.services.common.context.ServiceContext;
 import org.collectionspace.services.common.query.QueryContext;
 import org.collectionspace.services.lifecycle.TransitionDef;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -586,4 +586,11 @@ public class JpaStorageClientImpl implements StorageClient {
                        DocumentException {
                // Do nothing.  JPA services do not support workflow.
        }
+
+       @Override
+       public void deleteWithWhereClause(ServiceContext ctx, String whereClause,
+                       DocumentHandler handler) throws DocumentNotFoundException,
+                       DocumentException {
+        throw new UnsupportedOperationException();
+       }
 }
index 95ccdf1ce3136deaf2b3b6f62a7cb16ac5e3c7a2..cbfaeef69f0109d9dca49ebc32c72fd55f0e4afd 100644 (file)
@@ -2,6 +2,7 @@ package org.collectionspace.services.nuxeo.client.java;
 
 import java.security.Principal;
 
+import org.collectionspace.services.nuxeo.util.NuxeoUtils;
 import org.nuxeo.ecm.core.api.ClientException;
 import org.nuxeo.ecm.core.api.DocumentModel;
 import org.nuxeo.ecm.core.api.DocumentModelList;
@@ -12,11 +13,31 @@ import org.nuxeo.ecm.core.api.NoRollbackOnException;
 import org.nuxeo.ecm.core.api.event.DocumentEventTypes;
 import org.nuxeo.ecm.core.api.impl.LifeCycleFilter;
 import org.nuxeo.ecm.core.api.CoreSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class CoreSessionWrapper implements CoreSessionInterface {
 
        private CoreSession repoSession;
        
+    /** The logger. */
+    private static Logger logger = LoggerFactory.getLogger(CoreSessionWrapper.class);
+    
+    private void logQuery(String query) {
+       logger.debug(String.format("NXQL: %s", query));
+    }
+    
+    private void logQuery(String query, String queryType) {
+       logger.debug(String.format("Query Type: '%s' NXQL: %s", queryType, query));
+    }
+    
+    private void logQuery(String query, Filter filter, long limit,
+               long offset, boolean countTotal) {
+       logger.debug(String.format("Filter: '%s', Limit: '%d', Offset: '%d', Count Total?: %b, NXQL: %s",
+                       filter != null ? filter.toString() : "none", limit, offset, countTotal, query));
+    }
+    
+       
        public CoreSessionWrapper(CoreSession repoSession) {
                this.repoSession = repoSession;
        }
@@ -71,22 +92,26 @@ public class CoreSessionWrapper implements CoreSessionInterface {
        @Override
        public IterableQueryResult queryAndFetch(String query, String queryType,
             Object... params) throws ClientException {
+               logQuery(query, queryType);
                return repoSession.queryAndFetch(query, queryType, params);
        }
 
        @Override
        public DocumentModelList query(String query, Filter filter, long limit,
             long offset, boolean countTotal) throws ClientException {
+               logQuery(query, filter, limit, offset, countTotal);
                return repoSession.query(query, filter, limit, offset, countTotal);
        }
 
        @Override
     public DocumentModelList query(String query, int max) throws ClientException {
+               logQuery(query);
        return repoSession.query(query, max);
     }
     
        @Override
        public DocumentModelList query(String query) throws ClientException {
+               logQuery(query);
                return repoSession.query(query);
        }
        
index b6db6f339864070cdfbfb971785a54ca244acfa0..b3904c0aa8f87b2cdd2c856c6a1d8f7a5baa66d7 100644 (file)
@@ -58,6 +58,8 @@ public final class NuxeoClientEmbedded {
     private RepositoryManager repositoryMgr;
 
     private static final NuxeoClientEmbedded instance = new NuxeoClientEmbedded();
+
+       private static final int MAX_CREATE_TRANSACTION_ATTEMPTS = 5;
         
     /**
      * Constructs a new NuxeoClient. NOTE: Using {@link #getInstance()} instead
@@ -128,7 +130,54 @@ public final class NuxeoClientEmbedded {
      */
     public CoreSessionInterface openRepository(String repoName) throws Exception {
         return openRepository(repoName, ServiceContext.DEFAULT_TX_TIMEOUT);
-    }    
+    }
+    
+    private boolean startTransaction() {
+       boolean startedTransaction = false;
+       int attempts = 0;
+       
+       if (TransactionHelper.isTransactionActive() == false) {
+               while (startedTransaction == false && attempts <= MAX_CREATE_TRANSACTION_ATTEMPTS) {                    
+                       try {
+                               startedTransaction = TransactionHelper.startTransaction();
+                       } catch (Exception e) {
+                               String traceMsg = String.format("Could not start a new transaction on thread '%d'", Thread.currentThread().getId());
+                               logger.trace(traceMsg);
+                               boolean txState = TransactionHelper.isTransactionActive();
+                               txState = TransactionHelper.isNoTransaction();
+                               txState = TransactionHelper.isTransactionActiveOrMarkedRollback();
+                               txState = TransactionHelper.isTransactionMarkedRollback();
+                       }
+                       
+               if (startedTransaction == false) {
+                       long currentThreadId = Thread.currentThread().getId();
+                               boolean txState = TransactionHelper.isTransactionActive();
+                               txState = TransactionHelper.isNoTransaction();
+                               txState = TransactionHelper.isTransactionActiveOrMarkedRollback();
+                               txState = TransactionHelper.isTransactionMarkedRollback();
+                               
+                               if (TransactionHelper.isTransactionActiveOrMarkedRollback() == true) {
+                                       try {
+                                               TransactionHelper.commitOrRollbackTransaction();
+                                       } catch (Exception e) {
+                                               logger.error("Could not commit or rollback transaction.", e);
+                                       }
+                               }
+               }
+                       attempts++;
+               }
+       } else {
+               logger.warn("A request to start a new transaction was made, but a transaction is already open.");
+               startedTransaction = true;
+       }
+               
+               if (startedTransaction == false) {
+                       String errMsg = String.format("Attempted %d time(s) to start a new transaction, but failed.", attempts);
+               logger.error(errMsg);
+        }
+
+               return startedTransaction;
+    }
 
     public CoreSessionInterface openRepository(String repoName, int timeoutSeconds) throws Exception {
        CoreSessionInterface result = null;
@@ -157,9 +206,10 @@ public final class NuxeoClientEmbedded {
        //
        boolean startedTransaction = false;
        if (TransactionHelper.isTransactionActive() == false) {
-               startedTransaction = TransactionHelper.startTransaction();
+               startedTransaction = startTransaction();
                if (startedTransaction == false) {
-                       String errMsg = "Could not start a Nuxeo transaction with the TransactionHelper class.";
+                       String errMsg = String.format("Could not start a Nuxeo transaction with the TransactionHelper class on thread '%d'.",
+                                       Thread.currentThread().getId());
                        logger.error(errMsg);
                        throw new Exception(errMsg);
                }
@@ -190,12 +240,17 @@ public final class NuxeoClientEmbedded {
                logger.trace(String.format("Added a new repository instance to our repo list.  Current count is now: %d",
                                repositoryInstances.size()));
         } else {
+               //
+               // If we couldn't open a repo session, we need to close the transaction we started.
+               //
+               if (startedTransaction == true) {
+                       TransactionHelper.commitOrRollbackTransaction();
+               }
                String errMsg = String.format("Could not open a session to the Nuxeo repository='%s'", repoName);
                logger.error(errMsg);
                throw new Exception(errMsg);
         }      
        
-        
         return result;
     }
     
@@ -259,15 +314,18 @@ public final class NuxeoClientEmbedded {
        String key = repoSession.getSessionId();
        String name = repoSession.getRepositoryName();
 
+       //
+       // The caller should have already called the .save() method, but just in
+       // case they didn't, let's try calling it again.
+       //
         try {
                repoSession.save();
-               repoSession.close();
         } catch (Exception e) {
-               String errMsg = String.format("Possible data loss.  Could not save and/or close the Nuxeo repository name = '%s'.",
-                               name);
-               logger.error(errMsg, e);
+               String errMsg = String.format("Possible data loss.  Could not save and/or close the Nuxeo repository name = '%s'.", name);
+               logger.trace(errMsg, e);
                throw e;
         } finally {
+               repoSession.close();
                CoreSessionInterface wasRemoved = repositoryInstances.remove(key);
             if (logger.isTraceEnabled()) {
                if (wasRemoved != null) {
@@ -283,9 +341,11 @@ public final class NuxeoClientEmbedded {
             //
             if (TransactionHelper.isTransactionActiveOrMarkedRollback() == true) {
                TransactionHelper.commitOrRollbackTransaction();
-               UserTransaction ut = TransactionHelper.lookupUserTransaction();
-               TransactionManager tm = TransactionHelper.lookupTransactionManager();                   
                logger.trace(String.format("Transaction closed on thread '%d'", Thread.currentThread().getId()));
+            } else {
+               String warnMsg = String.format("Closed a Nuxeo repository session on thread '%d' without closing the containing transaction.",
+                               Thread.currentThread().getId());
+               logger.warn(warnMsg);
             }
         }
     }    
index ad8a81569e8187686b164781c7599dd753a82141..1330b01ada5ab97f1ace81bf8a200984a8dd9abf 100644 (file)
@@ -461,8 +461,16 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
         try {
             repoSession = getRepositorySession(ctx);
             wrapDoc = findDoc(repoSession, ctx, whereClause);
+        } catch (DocumentNotFoundException dnfe) {
+               throw dnfe;
+        } catch (DocumentException de) {
+               throw de;
         } catch (Exception e) {
-            throw new NuxeoDocumentException("Unable to create a Nuxeo repository session.", e);
+               if (repoSession == null) {
+                       throw new NuxeoDocumentException("Unable to create a Nuxeo repository session.", e);
+               } else {
+                       throw new NuxeoDocumentException("Unexpected Nuxeo exception.", e);
+               }
         } finally {
             if (repoSession != null) {
                 releaseRepositorySession(ctx, repoSession);
@@ -1452,6 +1460,26 @@ public class RepositoryJavaClientImpl implements RepositoryClient<PoxPayloadIn,
         }
     }
 
+    @Override
+       public void deleteWithWhereClause(@SuppressWarnings("rawtypes") ServiceContext ctx, String whereClause, 
+                       @SuppressWarnings("rawtypes") DocumentHandler handler) throws 
+                       DocumentNotFoundException, DocumentException {
+        if (ctx == null) {
+            throw new IllegalArgumentException(
+                    "delete(ctx, specifier): ctx is missing");
+        }
+        if (logger.isDebugEnabled()) {
+            logger.debug("Deleting document with whereClause=" + whereClause);
+        }
+        
+        DocumentWrapper<DocumentModel> foundDocWrapper = this.findDoc(ctx, whereClause);
+        if (foundDocWrapper != null) {
+               DocumentModel docModel = foundDocWrapper.getWrappedObject();
+               String csid = docModel.getName();
+               this.delete(ctx, csid, handler);
+        }
+    }
+    
     /**
      * delete a document from the Nuxeo repository
      *
index 589bd07e098542810a2583da5abf553671727b6f..1083f04844f9ab49933d16b6477d590652929147 100644 (file)
@@ -64,7 +64,7 @@ public class ConditioncheckAuthRefsTest extends BaseServiceTest<AbstractCommonLi
     // Instance variables specific to this test.
     final String SERVICE_NAME = "conditionchecks";
     final String SERVICE_PATH_COMPONENT = "conditionchecks";
-    final String PERSON_AUTHORITY_NAME = "TestPersonAuth";
+    final String PERSON_AUTHORITY_NAME = "TestPersonAuthForConditionCheck";
     private String knownResourceId = null;
     private List<String> conditioncheckIdsCreated = new ArrayList<String>();
     private List<String> personIdsCreated = new ArrayList<String>();
index bea5e5446959b73056c9da958020f48b787a13bc..3d80cd468126c4a78da56a49a50aa42b1ad650f4 100644 (file)
@@ -69,7 +69,7 @@ public class MovementAuthRefsTest extends BaseServiceTest<AbstractCommonList> {
     final String SERVICE_PATH_COMPONENT = "movements";
 
     // Instance variables specific to this test.
-    final String PERSON_AUTHORITY_NAME = "TestPersonAuth";
+    final String PERSON_AUTHORITY_NAME = "TestPersonAuthForMovementTest";
     private List<String> movementIdsCreated = new ArrayList<String>();
     private List<String> personIdsCreated = new ArrayList<String>();
     private String personAuthCSID = null;
index 58f7e586651024ee7de90e2f177276ae3b60c4d8..87fd6812073f885e826243782077b226b8f75437 100644 (file)
@@ -67,7 +67,7 @@ public class OrgAuthorityAuthRefsTest extends BaseServiceTest<AbstractCommonList
     private final Logger logger = LoggerFactory.getLogger(CLASS_NAME);
 
     // Instance variables specific to this test.
-    final String PERSON_AUTHORITY_NAME = "TestPersonAuth";
+    final String PERSON_AUTHORITY_NAME = "TestPersonAuthForOrgTest";
     final String ORG_AUTHORITY_NAME = "TestOrgAuth";
     
        @Override
index 99283dd980c7e5c18847d486927051ce0afc4bff..53e57fdb82e280b02960fbf0bf459e695f87901f 100644 (file)
@@ -61,7 +61,7 @@ public class RelationServiceTest extends AbstractPoxServiceTestImpl<RelationsCom
 
    /** The logger. */
     private final String CLASS_NAME = RelationServiceTest.class.getName();
-    private final String PERSON_AUTHORITY_NAME = "TestPersonAuth";
+    private final String PERSON_AUTHORITY_NAME = "TestPersonAuthForRelationTest";
     private final Logger logger = LoggerFactory.getLogger(CLASS_NAME);
     private List<String> personIdsCreated = new ArrayList<String>();
     
index bad57f61a611282698e9c8efc52ce082f447ecf0..6e4c4a9f35dbcbce476bbf717de2575081572bdd 100644 (file)
@@ -23,7 +23,6 @@
 package org.collectionspace.services.client.test;
 
 import java.util.HashMap;
-import javax.ws.rs.core.Response;
 
 import org.collectionspace.services.common.vocabulary.AuthorityItemJAXBSchema;
 import org.collectionspace.services.client.CollectionSpaceClient;
@@ -34,14 +33,13 @@ import org.collectionspace.services.client.VocabularyClient;
 import org.collectionspace.services.client.VocabularyClientUtils;
 import org.collectionspace.services.vocabulary.VocabulariesCommon;
 import org.collectionspace.services.vocabulary.VocabularyitemsCommon;
-
-import org.jboss.resteasy.client.ClientResponse;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.testng.Assert;
 import org.testng.annotations.Test;
 
+import javax.ws.rs.core.Response;
+
 /**
  * VocabularyServiceTest, carries out tests against a
  * deployed and running Vocabulary Service.
@@ -101,6 +99,55 @@ public class VocabularyServiceTest extends AbstractAuthorityServiceTest<Vocabula
             }
         }
     }
+    
+    @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
+               dependsOnMethods = {"createItem"})
+    public void createItemList(String testName) throws Exception {
+       knownAuthorityWithItems = createResource(testName, READITEMS_SHORT_IDENTIFIER);
+        for (int j = 0; j < nItemsToCreateInList; j++) {
+               createItemInAuthority(knownAuthorityWithItems);
+        }
+    }    
+    
+    @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
+               dependsOnMethods = {"CRUDTests"})
+    public void createWithNonuniqueShortId(String testName) throws Exception {
+        testSetup(STATUS_CREATED, ServiceRequestType.CREATE);
+
+        // Create a new vocabulary
+        VocabularyClient client = new VocabularyClient();
+        PoxPayloadOut multipart = VocabularyClientUtils.createEnumerationInstance(
+                "Vocab with non-unique Short Id", "nonunique", client.getCommonPartName());
+        Response res = client.create(multipart);
+        try {
+               assertStatusCode(res, testName);
+               String newId = extractId(res);
+               allResourceIdsCreated.add(newId); // save this so we can cleanup after ourselves
+        } finally {
+               if (res != null) {
+                res.close();
+            }
+        }
+        
+        //
+        // Now try to create a duplicate, we should fail because we're using a non-unique short id
+        // 
+        res = client.create(multipart);
+        try {
+               Assert.assertTrue(res.getStatus() != STATUS_CREATED, "Expect create to fail because of non unique short identifier.");
+        } catch (AssertionError ae) {
+               // We expected a failure, but we didn't get it.  Therefore, we need to cleanup
+               // the vocabulary we just created.
+               String newId = extractId(res);
+               allResourceIdsCreated.add(newId); // save this so we can cleanup after ourselves.
+               throw ae; // rethrow the exception
+        } finally {
+               if (res != null) {
+                res.close();
+            }
+        }
+    }
+    
 
     @Test(dataProvider = "testName", dataProviderClass = AbstractServiceTestImpl.class,
                dependsOnMethods = {"authorityTests"})