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;
/** The storage client. */
private StorageClient storageClient;
-
+
/**
* Extract id.
*
String[] segments = uri.split("/");
String id = segments[segments.length - 1];
return id;
- }
-
+ }
+
/**
* Instantiates a new abstract collection space resource.
*/
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;
/** The logger. */
final Logger logger = LoggerFactory.getLogger(AbstractServiceContextImpl.class);
+
/** The properties. */
Map<String, Object> properties = new HashMap<String, Object>();
/** The object part map. */
private Object currentRepositorySession;
/** A reference count for the current repository session */
private int currentRepoSesssionRefCount = 0;
-
+
/**
* Instantiates a new abstract service context impl.
*/
}
}
}
+
+ public int getTimeoutParam(UriInfo ui) {
+ int result = DEFAULT_TX_TIMEOUT;
+
+ MultivaluedMap<String, String> 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()
/** 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:
+ * <Resource name="TransactionManager" auth="Container" type="javax.transaction.TransactionManager"
+ * factory="org.nuxeo.runtime.jtajca.NuxeoTransactionManagerFactory"
+ * transactionTimeoutSeconds="300"/>
+ * 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
*/
*/
public SecurityContext getSecurityContext();
+ /**
+ * getTimeoutSecs();
+ */
+ public int getTimeoutSecs();
+
/**
* getUserId get authenticated user's userId
*/
* unknown.)
* @since 1.4
*/
- public TransactionException(Throwable cause) {
+ public TransactionException(Throwable cause) {
super(TRANSACTION_FAILED_MSG, cause);
setErrorCode(HTTP_CODE);
}
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;
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;
* 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 {
//
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));
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()));
}
}
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;
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;
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;
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;
}
public CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> 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);
}
/**
* @return the repository session
* @throws Exception the exception
*/
- public CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx, String repoName) throws Exception {
+ public CoreSessionInterface getRepositorySession(ServiceContext<PoxPayloadIn, PoxPayloadOut> ctx,
+ String repoName,
+ int timeoutSeconds) throws Exception {
CoreSessionInterface repoSession = null;
Profiler profiler = new Profiler("getRepositorySession():", 2);
//
if (repoSession == null) {
NuxeoClientEmbedded client = NuxeoConnectorEmbedded.getInstance().getClient();
- repoSession = client.openRepository(repoName);
+ repoSession = client.openRepository(repoName, timeoutSeconds);
} else {
if (logger.isDebugEnabled() == true) {
logger.warn("Reusing the current context's repository session.");
client.releaseRepository(repoSession); //repo session was acquired without a service context
}
} catch (TransactionRuntimeException tre) {
- TransactionException te = new TransactionException(tre);
+ String causeMsg = null;
+ Throwable cause = tre.getCause();
+ if (cause != null) {
+ causeMsg = cause.getMessage();
+ }
+
+ TransactionException te; // a CollectionSpace specific tx exception
+ if (causeMsg != null) {
+ te = new TransactionException(causeMsg, tre);
+ } else {
+ te = new TransactionException(tre);
+ }
+
logger.error(te.getMessage(), tre); // Log the standard transaction exception message, plus an exception-specific stack trace
throw te;
} catch (Exception e) {
- logger.error("Could not close the repository session", e);
+ logger.error("Could not close the repository session.", e);
// no need to throw this service specific exception
}
}
package org.collectionspace.services.imports;
import org.collectionspace.authentication.AuthN;
-import org.collectionspace.services.client.IClientQueryParams;
import org.collectionspace.services.client.PoxPayloadIn;
import org.collectionspace.services.client.PoxPayloadOut;
import org.collectionspace.services.common.AbstractCollectionSpaceResourceImpl;
import org.collectionspace.services.common.api.ZipTools;
import org.collectionspace.services.common.config.TenantBindingConfigReaderImpl;
import org.collectionspace.services.common.context.MultipartServiceContextFactory;
+import org.collectionspace.services.common.context.ServiceContext;
import org.collectionspace.services.common.context.ServiceContextFactory;
import org.collectionspace.services.config.tenant.RepositoryDomainType;
import org.collectionspace.services.config.tenant.TenantBindingType;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
-import org.xml.sax.InputSource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
+
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.xml.sax.InputSource;
// The modified Nuxeo ImportCommand from nuxeo's shell:
@Consumes({ "application/xml" })
public class ImportsResource extends AbstractCollectionSpaceResourceImpl<PoxPayloadIn, PoxPayloadOut> {
- 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
// }
// }
- private int getTimeoutParam(UriInfo ui) {
- int result = DEFAULT_TX_TIMEOUT;
-
- MultivaluedMap<String, String> 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
@POST
@Consumes("application/xml")
@Produces("application/xml")
- public javax.ws.rs.core.Response create(@Context UriInfo ui,
+ public Response create(@Context UriInfo ui,
String xmlPayload) {
String result = null;
- javax.ws.rs.core.Response.ResponseBuilder rb;
+ ResponseBuilder rb;
try {
- int timeout = getTimeoutParam(ui);
+ ServiceContext<PoxPayloadIn, PoxPayloadOut> 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();
@POST
@Consumes("multipart/form-data")
@Produces("application/xml")
- public javax.ws.rs.core.Response acceptUpload(@Context UriInfo ui,
+ public Response acceptUpload(@Context UriInfo ui,
@Context HttpServletRequest req, MultipartFormDataInput partFormData) {
- javax.ws.rs.core.Response response = null;
+ Response response = null;
StringBuffer resultBuf = new StringBuffer();
+
try {
+ ServiceContext<PoxPayloadIn, PoxPayloadOut> 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<String, List<InputPart>> partsMap = partFormData
- .getFormDataMap();
+ logger.trace("Preamble type is:" + preamble);
+
+ Map<String, List<InputPart>> partsMap = partFormData.getFormDataMap();
List<InputPart> 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,
}
//
- // This code was never finished to support the import of a zipped directory
+ // TODO: This code was never finished to support the import of a zipped directory
//
if (mediaType.equalsIgnoreCase("application/zip")) {
- logger.error("The Import service does not yet support .zip files."); //We should also send back a meaningful error message and status code here.
+ logger.error("The Import service does not yet support .zip files."); // We should also send back a meaningful error message and status code here.
fileStream = part.getBody(InputStream.class, null);
File zipfile = FileUtils.createTmpFile(fileStream,
getServiceName() + "_");
String zipfileName = zipfile.getCanonicalPath();
- if (logger.isTraceEnabled()) {
- logger.trace("Imports zip file saved to:"
- + zipfileName);
- }
+ logger.trace("Imports zip file saved to:" + zipfileName);
String baseOutputDir = FileTools.createTmpDir("imports-")
.getCanonicalPath();
File indir = new File(baseOutputDir + "/in");
indir.mkdir();
ZipTools.unzip(zipfileName, indir.getCanonicalPath());
- String result = "\r\n<zipResult>Zipfile " + zipfileName
- + "extracted to: " + indir.getCanonicalPath()
- + "</zipResult>";
- if (logger.isTraceEnabled()) {
- logger.trace(result);
- }
- long start = System.currentTimeMillis();
+ String result = "\r\n<zipResult>Zipfile " + zipfileName
+ + "extracted to: " + indir.getCanonicalPath()
+ + "</zipResult>";
+ 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;
}
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;
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 {
}
}
- 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;
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);
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;
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();
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() {
}
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
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);