From 058532e22b448aa5e95ef64869558fe9585b498c Mon Sep 17 00:00:00 2001 From: remillet Date: Thu, 30 Apr 2015 14:21:58 -0700 Subject: [PATCH] CSPACE-6347: Now supporting 'impTimout' query param for most requests. --- .../AbstractCollectionSpaceResourceImpl.java | 8 +- .../context/AbstractServiceContextImpl.java | 29 +++++- .../common/context/ServiceContext.java | 19 ++++ .../common/document/TransactionException.java | 2 +- .../client/java/NuxeoClientEmbedded.java | 24 ++++- .../client/java/RepositoryJavaClientImpl.java | 31 +++--- .../services/imports/ImportsResource.java | 94 +++++++------------ .../services/imports/nuxeo/ImportCommand.java | 17 ++-- .../nuxeo/LoggedXMLDirectoryReader.java | 36 ++++++- 9 files changed, 168 insertions(+), 92 deletions(-) diff --git a/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java b/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java index 48e166563..5ec16b6f9 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/AbstractCollectionSpaceResourceImpl.java @@ -46,10 +46,8 @@ import org.collectionspace.services.common.repository.RepositoryClientFactory; import org.collectionspace.services.common.security.UnauthorizedException; import org.collectionspace.services.common.storage.StorageClient; import org.collectionspace.services.common.storage.jpa.JpaStorageClientImpl; - import org.jboss.resteasy.core.ResourceMethod; import org.jboss.resteasy.spi.HttpRequest; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -74,7 +72,7 @@ public abstract class AbstractCollectionSpaceResourceImpl /** The storage client. */ private StorageClient storageClient; - + /** * Extract id. * @@ -87,8 +85,8 @@ public abstract class AbstractCollectionSpaceResourceImpl String[] segments = uri.split("/"); String id = segments[segments.length - 1]; return id; - } - + } + /** * Instantiates a new abstract collection space resource. */ diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java index 076cd9079..f6b080220 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/AbstractServiceContextImpl.java @@ -27,10 +27,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; + import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; import org.collectionspace.authentication.spi.AuthNContext; +import org.collectionspace.services.client.IClientQueryParams; import org.collectionspace.services.client.IQueryManager; import org.collectionspace.services.client.workflow.WorkflowClient; import org.collectionspace.services.common.ServiceMain; @@ -77,6 +79,7 @@ public abstract class AbstractServiceContextImpl /** The logger. */ final Logger logger = LoggerFactory.getLogger(AbstractServiceContextImpl.class); + /** The properties. */ Map properties = new HashMap(); /** The object part map. */ @@ -101,7 +104,7 @@ public abstract class AbstractServiceContextImpl private Object currentRepositorySession; /** A reference count for the current repository session */ private int currentRepoSesssionRefCount = 0; - + /** * Instantiates a new abstract service context impl. */ @@ -165,6 +168,30 @@ public abstract class AbstractServiceContextImpl } } } + + public int getTimeoutParam(UriInfo ui) { + int result = DEFAULT_TX_TIMEOUT; + + MultivaluedMap queryParams = (ui == null) ? null : ui.getQueryParameters(); + if (queryParams != null) { + String timeoutString = queryParams.getFirst(IClientQueryParams.IMPORT_TIMEOUT_PARAM); + if (timeoutString != null) + try { + result = Integer.parseInt(timeoutString); + } catch (NumberFormatException e) { + logger.warn("Transaction timeout period parameter could not be parsed. The characters in the parameter string must all be decimal digits. The Import service will use the default timeout period instead.", + e); + } + } + + return result; + } + + @Override + public int getTimeoutSecs() { + UriInfo uriInfo = this.getUriInfo(); + return this.getTimeoutParam(uriInfo); + } /* (non-Javadoc) * @see org.collectionspace.services.common.context.ServiceContext#getCommonPartLabel() diff --git a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java index 1dd07be55..552732f56 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java +++ b/services/common/src/main/java/org/collectionspace/services/common/context/ServiceContext.java @@ -61,6 +61,20 @@ public interface ServiceContext { /** Used to qualify document types **/ public static final String TENANT_SUFFIX = "Tenant"; + /** + * Tells the TransactionManager to use the default value. The default value can + * be set in this file: + * services/JaxRsServiceProvider/src/main/webapp/META-INF/context.xml + * at this section: + * + * See the following documentation page for more details: + * http://docs.oracle.com/javaee/7/api/javax/transaction/TransactionManager.html#setTransactionTimeout(int) + * + */ + public static final int DEFAULT_TX_TIMEOUT = 0; + /* * Sets the current/open repository session */ @@ -81,6 +95,11 @@ public interface ServiceContext { */ public SecurityContext getSecurityContext(); + /** + * getTimeoutSecs(); + */ + public int getTimeoutSecs(); + /** * getUserId get authenticated user's userId */ diff --git a/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java b/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java index 92316e0d4..7f979b164 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java +++ b/services/common/src/main/java/org/collectionspace/services/common/document/TransactionException.java @@ -85,7 +85,7 @@ public class TransactionException extends DocumentException { * unknown.) * @since 1.4 */ - public TransactionException(Throwable cause) { + public TransactionException(Throwable cause) { super(TRANSACTION_FAILED_MSG, cause); setErrorCode(HTTP_CODE); } diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/NuxeoClientEmbedded.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/NuxeoClientEmbedded.java index 0547e446b..b6db6f339 100644 --- a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/NuxeoClientEmbedded.java +++ b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/NuxeoClientEmbedded.java @@ -25,9 +25,9 @@ import java.util.Iterator; import java.util.Map.Entry; import java.security.Principal; +import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.repository.RepositoryInstanceWrapperAdvice; import org.collectionspace.services.config.tenant.RepositoryDomainType; - import org.nuxeo.ecm.core.api.repository.Repository; import org.nuxeo.ecm.core.api.CoreInstance; import org.nuxeo.ecm.core.api.CoreSession; @@ -35,9 +35,11 @@ import org.nuxeo.ecm.core.api.NuxeoPrincipal; import org.nuxeo.ecm.core.api.SystemPrincipal; import org.nuxeo.ecm.core.api.repository.RepositoryManager; import org.nuxeo.runtime.api.Framework; +import org.nuxeo.runtime.jtajca.NuxeoContainer; import org.nuxeo.runtime.transaction.TransactionHelper; import javax.transaction.TransactionManager; +import javax.transaction.UserTransaction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -118,14 +120,14 @@ public final class NuxeoClientEmbedded { * Open a Nuxeo repo session using the passed in repoDomain and use the default tx timeout period */ public CoreSessionInterface openRepository(RepositoryDomainType repoDomain) throws Exception { - return openRepository(repoDomain.getRepositoryName(), -1); + return openRepository(repoDomain.getRepositoryName(), ServiceContext.DEFAULT_TX_TIMEOUT); } /* * Open a Nuxeo repo session using the passed in repoDomain and use the default tx timeout period */ public CoreSessionInterface openRepository(String repoName) throws Exception { - return openRepository(repoName, -1); + return openRepository(repoName, ServiceContext.DEFAULT_TX_TIMEOUT); } public CoreSessionInterface openRepository(String repoName, int timeoutSeconds) throws Exception { @@ -136,7 +138,14 @@ public final class NuxeoClientEmbedded { // if (timeoutSeconds > 0) { TransactionManager transactionMgr = TransactionHelper.lookupTransactionManager(); - transactionMgr.setTransactionTimeout(timeoutSeconds); + TransactionManager tm = NuxeoContainer.getTransactionManager(); + if (logger.isDebugEnabled()) { + if (tm != transactionMgr) { + logger.debug("TransactionHelper's manager is different than NuxeoContainer's."); + } + } + + transactionMgr.setTransactionTimeout(timeoutSeconds); // For the current thread only if (logger.isInfoEnabled()) { logger.info(String.format("Changing current request's transaction timeout period to %d seconds", timeoutSeconds)); @@ -268,9 +277,14 @@ public final class NuxeoClientEmbedded { logger.trace("Could not remove a repository instance from our repo list. Current count is now: " + repositoryInstances.size()); } - } + } + // + // Last but not least, try to commit the current Nuxeo-related transaction. + // 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())); } } diff --git a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java index 6d4f231b6..ad8a81569 100644 --- a/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java +++ b/services/common/src/main/java/org/collectionspace/services/nuxeo/client/java/RepositoryJavaClientImpl.java @@ -34,14 +34,12 @@ import javax.ws.rs.core.MultivaluedMap; import org.collectionspace.services.lifecycle.TransitionDef; import org.collectionspace.services.nuxeo.util.NuxeoUtils; - import org.collectionspace.services.client.CollectionSpaceClient; import org.collectionspace.services.client.IQueryManager; import org.collectionspace.services.client.PoxPayloadIn; import org.collectionspace.services.client.PoxPayloadOut; import org.collectionspace.services.client.Profiler; import org.collectionspace.services.client.workflow.WorkflowClient; - import org.collectionspace.services.common.context.ServiceContext; import org.collectionspace.services.common.query.QueryContext; import org.collectionspace.services.common.repository.RepositoryClient; @@ -63,7 +61,6 @@ import org.collectionspace.services.common.config.ConfigUtils; import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl; import org.collectionspace.services.common.config.TenantBindingUtils; import org.collectionspace.services.common.storage.PreparedStatementBuilder; - import org.collectionspace.services.config.tenant.TenantBindingType; import org.collectionspace.services.config.tenant.RepositoryDomainType; @@ -74,7 +71,6 @@ import org.apache.chemistry.opencmis.commons.enums.CmisVersion; import org.apache.chemistry.opencmis.commons.server.CallContext; import org.apache.chemistry.opencmis.server.impl.CallContextImpl; import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStreamFactory; - import org.nuxeo.common.utils.IdUtils; import org.nuxeo.ecm.core.api.ClientException; import org.nuxeo.ecm.core.api.DocumentModel; @@ -88,7 +84,6 @@ import org.nuxeo.ecm.core.api.PathRef; import org.nuxeo.runtime.transaction.TransactionRuntimeException; import org.nuxeo.ecm.core.opencmis.bindings.NuxeoCmisServiceFactory; import org.nuxeo.ecm.core.opencmis.impl.server.NuxeoCmisService; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1719,11 +1714,11 @@ public class RepositoryJavaClientImpl implements RepositoryClient ctx) throws Exception { - return getRepositorySession(ctx, ctx.getRepositoryName()); + return getRepositorySession(ctx, ctx.getRepositoryName(), ctx.getTimeoutSecs()); } public CoreSessionInterface getRepositorySession(String repoName) throws Exception { - return getRepositorySession(null, repoName); + return getRepositorySession(null, repoName, ServiceContext.DEFAULT_TX_TIMEOUT); } /** @@ -1733,7 +1728,9 @@ public class RepositoryJavaClientImpl implements RepositoryClient ctx, String repoName) throws Exception { + public CoreSessionInterface getRepositorySession(ServiceContext ctx, + String repoName, + int timeoutSeconds) throws Exception { CoreSessionInterface repoSession = null; Profiler profiler = new Profiler("getRepositorySession():", 2); @@ -1755,7 +1752,7 @@ public class RepositoryJavaClientImpl implements RepositoryClient { - private final static Logger logger = LoggerFactory.getLogger(TemplateExpander.class); + private final static Logger logger = LoggerFactory.getLogger(TemplateExpander.class); public static final String SERVICE_NAME = "imports"; public static final String SERVICE_PATH = "/" + SERVICE_NAME; - private static String NUXEO_SPACES_PATH_DELIMITER = "/"; - - private static final int DEFAULT_TX_TIMEOUT = 600; // timeout period in seconds + private static String NUXEO_SPACES_PATH_DELIMITER = "/"; /* * ASSUMPTION: All Nuxeo services of a given tenancy store their stuff in @@ -189,26 +189,6 @@ public class ImportsResource extends AbstractCollectionSpaceResourceImpl queryParams = ui.getQueryParameters(); - if (queryParams != null) { - String timeoutString = queryParams - .getFirst(IClientQueryParams.IMPORT_TIMEOUT_PARAM); - if (timeoutString != null) - try { - result = Integer.parseInt(timeoutString); - } catch (NumberFormatException e) { - logger.warn( - "Timeout period parameter could not be parsed. The characters in the parameter string must all be decimal digits. The Import service will use the default timeout period instead.", - e); - } - } - - return result; - } - /** * you can test this with something like: curl -X POST * http://localhost:8180/cspace-services/imports -i -u @@ -220,21 +200,22 @@ public class ImportsResource extends AbstractCollectionSpaceResourceImpl ctx = createServiceContext(ui); + int timeout = ctx.getTimeoutSecs(); // gets it from query param 'impTimout' or uses default if no query param specified // InputSource inputSource = payloadToInputSource(xmlPayload); // result = createFromInputSource(inputSource); String inputFilename = payloadToFilename(xmlPayload); result = createFromFilename(inputFilename, timeout); - rb = javax.ws.rs.core.Response.ok(); + rb = Response.ok(); } catch (Exception e) { result = Tools.errorToString(e, true); - rb = javax.ws.rs.core.Response - .status(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR); + rb = Response + .status(Response.Status.INTERNAL_SERVER_ERROR); } rb.entity(result); return rb.build(); @@ -423,25 +404,24 @@ public class ImportsResource extends AbstractCollectionSpaceResourceImpl ctx = createServiceContext(ui); + int timeout = ctx.getTimeoutSecs(); // gets it from query param 'impTimout' or uses default if no query param specified + InputStream fileStream = null; String preamble = partFormData.getPreamble(); - if (logger.isTraceEnabled()) { - logger.trace("Preamble type is:" + preamble); - } - Map> partsMap = partFormData - .getFormDataMap(); + logger.trace("Preamble type is:" + preamble); + + Map> partsMap = partFormData.getFormDataMap(); List fileParts = partsMap.get("file"); - int timeout = this.getTimeoutParam(ui); for (InputPart part : fileParts) { String mediaType = part.getMediaType().toString(); - if (logger.isTraceEnabled()) { - logger.trace("Media type is:" + mediaType); - } + logger.trace("Media type is:" + mediaType); if (mediaType.equalsIgnoreCase(MediaType.APPLICATION_XML) || mediaType.equalsIgnoreCase(MediaType.TEXT_XML)) { // FIXME For an alternate approach, potentially preferable, @@ -459,45 +439,41 @@ public class ImportsResource extends AbstractCollectionSpaceResourceImpl"; - if (logger.isTraceEnabled()) { - logger.trace(result); - } - long start = System.currentTimeMillis(); + String result = "\r\nZipfile " + zipfileName + + "extracted to: " + indir.getCanonicalPath() + + ""; + logger.trace(result); + // TODO: now call import service... resultBuf.append(result); continue; } } - javax.ws.rs.core.Response.ResponseBuilder rb = javax.ws.rs.core.Response - .ok(); + + Response.ResponseBuilder rb = Response.ok(); rb.entity(resultBuf.toString()); response = rb.build(); } catch (Exception e) { throw bigReThrow(e, ServiceMessages.CREATE_FAILED); } + return response; } diff --git a/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/ImportCommand.java b/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/ImportCommand.java index 6754b5e22..7e6671041 100644 --- a/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/ImportCommand.java +++ b/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/ImportCommand.java @@ -9,6 +9,7 @@ import java.util.TreeSet; import org.collectionspace.services.nuxeo.client.java.NuxeoClientEmbedded; import org.collectionspace.services.nuxeo.client.java.NuxeoConnectorEmbedded; import org.collectionspace.services.nuxeo.client.java.CoreSessionInterface; + import org.nuxeo.ecm.core.api.DocumentModel; import org.nuxeo.ecm.core.api.DocumentRef; import org.nuxeo.ecm.core.io.DocumentPipe; @@ -26,18 +27,18 @@ public class ImportCommand { private final Logger logger = LoggerFactory.getLogger(ImportCommand.class); - public String run(String src, String repoName, String workspacesPath, int timeOut) throws Exception { + public String run(String src, String repoName, String workspacesPath, int timeout) throws Exception { File file = new File(src); ///cspace way of configuring client and auth: NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient(); CoreSessionInterface repoSession = null; try { - repoSession = client.openRepository(repoName, timeOut); + repoSession = client.openRepository(repoName, timeout); if (logger.isDebugEnabled()) { String msg = String.format("Start of import is Local time: %tT", Calendar.getInstance()); logger.debug(msg); } - return importTree(repoSession, file, workspacesPath); + return importTree(repoSession, file, workspacesPath, timeout); } catch (Exception e) { throw e; } finally { @@ -49,7 +50,10 @@ public class ImportCommand { } } - String importTree(CoreSessionInterface repoSession, File file, String toPath) throws Exception { + /* + * If the import exceeds the number of seconds in 'timeout', we'll thrown an exception and rollback all import work + */ + String importTree(CoreSessionInterface repoSession, File file, String toPath, int timeout) throws Exception { Exception failed = null; DocumentReader reader = null; DocumentWriter writer = null; @@ -62,9 +66,10 @@ public class ImportCommand { int totalRecordsImported = 0; try { if (logger.isInfoEnabled()) { - logger.info("importTree reading file: " + file + (file != null ? " exists? " + file.exists() : " file param is null")); + logger.info("ImportCommand.importTree() method reading file: " + file + (file != null ? " exists? " + file.exists() : " file param is null")); + logger.info(String.format("ImportCommand.importTree() will timeout if import does not complete in %d seconds.", timeout)); } - reader = new LoggedXMLDirectoryReader(file); //our overload of XMLDirectoryReader. + reader = new LoggedXMLDirectoryReader(file, timeout); //our overload of XMLDirectoryReader. writer = new DocumentModelWriter(repoSession.getCoreSession(), toPath, 10); DocumentPipe pipe = new DocumentPipeImpl(10); // pipe.addTransformer(transformer); diff --git a/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/LoggedXMLDirectoryReader.java b/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/LoggedXMLDirectoryReader.java index fc5be016c..d87c3d9ca 100644 --- a/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/LoggedXMLDirectoryReader.java +++ b/services/imports/service/src/main/java/org/collectionspace/services/imports/nuxeo/LoggedXMLDirectoryReader.java @@ -7,9 +7,11 @@ import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; + import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.io.SAXReader; + import org.nuxeo.common.utils.FileTreeIterator; import org.nuxeo.common.utils.FileUtils; import org.nuxeo.common.utils.Path; @@ -19,13 +21,17 @@ import org.nuxeo.ecm.core.io.ExportedDocument; import org.nuxeo.ecm.core.io.impl.AbstractDocumentReader; import org.nuxeo.ecm.core.io.impl.ExportedDocumentImpl; import org.nuxeo.runtime.services.streaming.FileSource; +import org.nuxeo.runtime.transaction.TransactionHelper; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LoggedXMLDirectoryReader extends AbstractDocumentReader { - private final Logger logger = LoggerFactory.getLogger(LoggedXMLDirectoryReader.class); - + private static final int TIMEOUT_NEVER = 0; + private final Logger logger = LoggerFactory.getLogger(LoggedXMLDirectoryReader.class); + protected long timeoutMillis = TIMEOUT_NEVER; + protected int totalTimeLimitSecs = 0; // the number of seconds before we timeout protected Document loadXML(File file) throws IOException { String filename = file.getCanonicalPath(); @@ -60,10 +66,14 @@ public class LoggedXMLDirectoryReader extends AbstractDocumentReader { private FileTreeIterator iterator; public LoggedXMLDirectoryReader(String sourcePath) { - this(new File(sourcePath)); + this(new File(sourcePath), TIMEOUT_NEVER); } - public LoggedXMLDirectoryReader(File source) { + public LoggedXMLDirectoryReader(File source, int timeout) { + if (timeout > 0) { + this.totalTimeLimitSecs = timeout; + this.timeoutMillis = System.currentTimeMillis() + (timeout * 1000); // set the timeout milliseconds time + } this.source = source; iterator = new FileTreeIterator(source); iterator.setFilter(new FileFilter() { @@ -94,6 +104,19 @@ public class LoggedXMLDirectoryReader extends AbstractDocumentReader { } return result.toString(); } + + /* + * Returns 'true' if we've timed out. + */ + protected boolean hasTimedOut() { + boolean result = false; + + if (totalTimeLimitSecs > 0 && System.currentTimeMillis() > timeoutMillis) { + result = true; + } + + return result; + } @Override @@ -107,6 +130,11 @@ public class LoggedXMLDirectoryReader extends AbstractDocumentReader { ExportedDocument xdoc = new ExportedDocumentImpl(); for (File file : dir.listFiles()) { if (file.isFile()) { + if (hasTimedOut() == true) { // Check to see if the current transaction has already timed out. + TransactionHelper.setTransactionRollbackOnly(); + String errMsg = String.format("Import transaction timed out by exceeding %d seconds.", this.totalTimeLimitSecs); + throw new IOException(errMsg); + } String name = file.getName(); if (ExportConstants.DOCUMENT_FILE.equals(name)) { Document doc = loadXML(file); -- 2.47.3