diff --git a/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java b/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java
index 53f6bb83ec..409efb18c6 100644
--- a/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java
+++ b/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceFactory.java
@@ -55,6 +55,7 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory
private AlfrescoCmisExceptionInterceptor cmisExceptions;
private AlfrescoCmisServiceInterceptor cmisControl;
private AlfrescoCmisStreamInterceptor cmisStreams;
+ private CMISTransactionAwareHolderInterceptor cmisHolder;
private AuthorityService authorityService;
/**
@@ -133,6 +134,11 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory
this.cmisStreams = cmisStreams;
}
+ public void setCmisHolder(CMISTransactionAwareHolderInterceptor cmisHolder)
+ {
+ this.cmisHolder = cmisHolder;
+ }
+
@Override
public void init(Map parameters)
{
@@ -195,6 +201,7 @@ public class AlfrescoCmisServiceFactory extends AbstractServiceFactory
proxyFactory.addAdvice(cmisControl);
proxyFactory.addAdvice(cmisStreams);
proxyFactory.addAdvice(cmisTransactions);
+ proxyFactory.addAdvice(cmisHolder);
AlfrescoCmisService cmisService = (AlfrescoCmisService) proxyFactory.getProxy();
ConformanceCmisServiceWrapper wrapperService = new ConformanceCmisServiceWrapper(
diff --git a/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
index 834a4b7aa2..937d54064e 100644
--- a/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
+++ b/src/main/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java
@@ -26,13 +26,10 @@
package org.alfresco.opencmis;
import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
-import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -87,7 +84,6 @@ import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.namespace.RegexQNamePattern;
import org.alfresco.util.Pair;
-import org.alfresco.util.TempFileProvider;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
@@ -1299,31 +1295,15 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
connector.applyACL(nodeRef, type, addAces, removeAces);
// handle content
- File tempFile = null;
- try
+ if (contentStream != null)
{
- if (contentStream != null)
- {
- // write content
- String mimeType = parseMimeType(contentStream);
-
- // copy stream to temp file
- // OpenCMIS does this for us ....
- tempFile = copyToTempFile(contentStream);
- String encoding = getEncoding(tempFile, mimeType);
-
- ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
- writer.setMimetype(mimeType);
- writer.setEncoding(encoding);
- writer.putContent(tempFile);
- }
- }
- finally
- {
- if(tempFile != null)
- {
- removeTempFile(tempFile);
- }
+ // write content
+ String mimeType = parseMimeType(contentStream);
+ String encoding = getEncoding(contentStream.getStream(), mimeType);
+ ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
+ writer.setMimetype(mimeType);
+ writer.setEncoding(encoding);
+ writer.putContent(contentStream.getStream());
}
connector.extractMetadata(nodeRef);
@@ -1333,8 +1313,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
connector.applyVersioningState(nodeRef, versioningState);
- removeTempFile(tempFile);
-
String objectId = connector.createObjectId(nodeRef);
connector.getActivityPoster().postFileFolderAdded(nodeRef);
@@ -1597,21 +1575,11 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
public Void execute() throws Throwable
{
String mimeType = parseMimeType(contentStream);
- final File tempFile = copyToTempFile(contentStream);
- String encoding = getEncoding(tempFile, mimeType);
-
- try
- {
- ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
- writer.setMimetype(mimeType);
- writer.setEncoding(encoding);
- writer.putContent(tempFile);
- }
- finally
- {
- removeTempFile(tempFile);
- }
-
+ String encoding = getEncoding(contentStream.getStream(), mimeType);
+ ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
+ writer.setMimetype(mimeType);
+ writer.setEncoding(encoding);
+ writer.putContent(contentStream.getStream());
connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), nodeRef);
return null;
}
@@ -2358,9 +2326,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
final NodeRef nodeRef = info.getNodeRef();
final TypeDefinitionWrapper type = info.getType();
- // copy stream to temp file
- final File tempFile = copyToTempFile(contentStream);
-
// check in
// update PWC
connector.setProperties(nodeRef, type, properties,
@@ -2372,12 +2337,12 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
if (contentStream != null)
{
String mimeType = parseMimeType(contentStream);
- String encoding = getEncoding(tempFile, mimeType);
+ String encoding = getEncoding(contentStream.getStream(), mimeType);
// write content
ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef);
writer.setMimetype(mimeType);
writer.setEncoding(encoding);
- writer.putContent(tempFile);
+ writer.putContent(contentStream.getStream());
}
// create version properties
@@ -2395,8 +2360,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
connector.getActivityPoster().postFileFolderUpdated(info.isFolder(), newNodeRef);
objectId.setValue(connector.createObjectId(newNodeRef));
-
- removeTempFile(tempFile);
}
@Override
@@ -2893,7 +2856,6 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
}
}
}
-
return info;
}
@@ -3165,14 +3127,14 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
/**
* Inspired from NodesImpl.guessEncoding method.
*
- * @param tempFile can be null;
+ * @param inputStream can be null;
* @param mimeType can be null;
* @return the encoding detected. never null;
*/
- private String getEncoding(File tempFile, String mimeType)
+ private String getEncoding(InputStream inputStream, String mimeType)
{
String defaultEncoding = "UTF-8";
- if (tempFile == null)
+ if (inputStream == null)
{
return defaultEncoding;
}
@@ -3180,7 +3142,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
InputStream tfis = null;
try
{
- tfis = new BufferedInputStream(new FileInputStream(tempFile));
+ tfis = new BufferedInputStream(inputStream);
ContentCharsetFinder charsetFinder = connector.getMimetypeService().getContentCharsetFinder();
return charsetFinder.getCharset(tfis, mimeType).name();
}
@@ -3190,73 +3152,17 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr
}
finally
{
- closeInputStream(tfis);
- }
- }
-
- private void closeInputStream(InputStream tfis)
- {
- if (tfis != null)
- {
- try
+ if (tfis != null)
{
- tfis.close();
+ try
+ {
+ tfis.close();
+ }
+ catch (Exception e)
+ {
+ // nothing
+ }
}
- catch (Exception e)
- {
- // nothing
- }
- }
- }
-
- private File copyToTempFile(ContentStream contentStream)
- {
- if (contentStream == null)
- {
- return null;
- }
-
- int bufferSize = 40 * 1024;
- File result = null;
- try
- {
- InputStream in = null;
- if (contentStream.getStream() != null)
- {
- in = new BufferedInputStream(contentStream.getStream(), bufferSize);
- }
-
- result = TempFileProvider.createTempFile(in, "cmis", "content");
- }
- catch (Exception e)
- {
- throw new CmisStorageException("Unable to store content: " + e.getMessage(), e);
- }
-
- if ((contentStream.getLength() > -1) && (result == null || contentStream.getLength() != result.length()))
- {
- removeTempFile(result);
- throw new CmisStorageException("Expected " + contentStream.getLength() + " bytes but retrieved " +
- (result == null ? -1 :result.length()) + " bytes!");
- }
-
- return result;
- }
-
- private void removeTempFile(File tempFile)
- {
- if (tempFile == null)
- {
- return;
- }
-
- try
- {
- tempFile.delete();
- }
- catch (Exception e)
- {
- // ignore - file will be removed by TempFileProvider
}
}
diff --git a/src/main/java/org/alfresco/opencmis/AlfrescoCmisStreamInterceptor.java b/src/main/java/org/alfresco/opencmis/AlfrescoCmisStreamInterceptor.java
index e87947735b..a24ff7f6ce 100644
--- a/src/main/java/org/alfresco/opencmis/AlfrescoCmisStreamInterceptor.java
+++ b/src/main/java/org/alfresco/opencmis/AlfrescoCmisStreamInterceptor.java
@@ -1,44 +1,49 @@
-/*
- * #%L
- * Alfresco Repository
- * %%
- * Copyright (C) 2005 - 2016 Alfresco Software Limited
- * %%
- * This file is part of the Alfresco software.
- * If the software was purchased under a paid Alfresco license, the terms of
- * the paid license agreement will prevail. Otherwise, the software is
- * provided under the following open source license terms:
- *
- * Alfresco is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * Alfresco is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with Alfresco. If not, see .
- * #L%
- */
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2016 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
package org.alfresco.opencmis;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.content.filestore.FileContentReader;
import org.alfresco.service.cmr.repository.MimetypeService;
+import org.alfresco.util.TempFileProvider;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
+import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
/**
* Interceptor to deal with ContentStreams, determining mime type if appropriate.
*
- * Note: this used to cache content stream to a local file so that retrying transaction behaviour
- * worked properly; this is now done in the Chemsitry OpenCMIS layer so no need to do it again
- * here.
- *
* @author steveglover
* @author Alan Davis
* @since 4.0.2.26 / 4.1.4
@@ -57,25 +62,102 @@ public class AlfrescoCmisStreamInterceptor implements MethodInterceptor
public Object invoke(MethodInvocation mi) throws Throwable
{
- Class>[] parameterTypes = mi.getMethod().getParameterTypes();
- Object[] arguments = mi.getArguments();
- for (int i = 0; i < parameterTypes.length; i++)
+ List reusableContentStreams = null;
+ try
{
- if (arguments[i] instanceof ContentStreamImpl)
+ Class>[] parameterTypes = mi.getMethod().getParameterTypes();
+ Object[] arguments = mi.getArguments();
+ for (int i=0; i();
+ }
+ // reusable streams are required for buffering in case of tx retry
+ ReusableContentStream reusableContentStream = new ReusableContentStream(contentStream);
+
+ // ALF-18006
+ if (contentStream.getMimeType() == null)
+ {
+ String mimeType = mimetypeService.guessMimetype(reusableContentStream.getFileName(), new FileContentReader(reusableContentStream.file));
+ reusableContentStream.setMimeType(mimeType);
+ }
+
+ reusableContentStreams.add(reusableContentStream);
+ arguments[i] = reusableContentStream;
}
}
}
+ return mi.proceed();
+ }
+ finally
+ {
+ if (reusableContentStreams != null)
+ {
+ for (ReusableContentStream contentStream: reusableContentStreams)
+ {
+ contentStream.close();
+ }
+ }
+ }
+ }
+
+
+ private static class ReusableContentStream extends ContentStreamImpl
+ {
+ private static final long serialVersionUID = 8992465629472248502L;
+
+ private File file;
+
+ public ReusableContentStream(ContentStream contentStream) throws Exception
+ {
+ setLength(contentStream.getBigLength());
+ setMimeType(contentStream.getMimeType());
+ setFileName(contentStream.getFileName());
+ file = TempFileProvider.createTempFile(contentStream.getStream(), "cmis", "contentStream");
+ }
+
+ @Override
+ public InputStream getStream() {
+ InputStream stream = super.getStream();
+ if (stream == null && file != null)
+ {
+ try
+ {
+ stream = new FileInputStream(file)
+ {
+ @Override
+ public void close() throws IOException
+ {
+ setStream(null);
+ super.close();
+ }
+ };
+ }
+ catch (Exception e)
+ {
+ throw new AlfrescoRuntimeException("Expected to be able to reopen temporary file", e);
+ }
+ setStream(stream);
+ }
+ return stream;
+ }
+
+ public void close()
+ {
+ try
+ {
+ file.delete();
+ }
+ finally
+ {
+ file = null;
+ }
}
- return mi.proceed();
}
}
diff --git a/src/main/java/org/alfresco/opencmis/CMISTransactionAwareHolderInterceptor.java b/src/main/java/org/alfresco/opencmis/CMISTransactionAwareHolderInterceptor.java
new file mode 100644
index 0000000000..7cdb2027d2
--- /dev/null
+++ b/src/main/java/org/alfresco/opencmis/CMISTransactionAwareHolderInterceptor.java
@@ -0,0 +1,56 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2018 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.opencmis;
+
+import org.aopalliance.intercept.MethodInterceptor;
+import org.aopalliance.intercept.MethodInvocation;
+import org.apache.chemistry.opencmis.commons.spi.Holder;
+
+/**
+ * The interceptor wraps {@link Holder} class in {@link TransactionAwareHolder}.
+ * This is designed specifically for CMIS Service.
+ *
+ * @author alex.mukha
+ */
+public class CMISTransactionAwareHolderInterceptor implements MethodInterceptor
+{
+ @Override
+ @SuppressWarnings("unchecked")
+ public Object invoke(MethodInvocation invocation) throws Throwable
+ {
+ Class>[] parameterTypes = invocation.getMethod().getParameterTypes();
+ Object[] arguments = invocation.getArguments();
+ for (int i = 0; i < parameterTypes.length; i++)
+ {
+ if (Holder.class.isAssignableFrom(parameterTypes[i]) && arguments[i] != null)
+ {
+ TransactionAwareHolder txnHolder = new TransactionAwareHolder(((Holder) arguments[i]));
+ arguments[i] = txnHolder;
+ }
+ }
+ return invocation.proceed();
+ }
+}
diff --git a/src/main/java/org/alfresco/opencmis/TransactionAwareHolder.java b/src/main/java/org/alfresco/opencmis/TransactionAwareHolder.java
new file mode 100644
index 0000000000..a5c5991583
--- /dev/null
+++ b/src/main/java/org/alfresco/opencmis/TransactionAwareHolder.java
@@ -0,0 +1,126 @@
+/*
+ * #%L
+ * Alfresco Repository
+ * %%
+ * Copyright (C) 2005 - 2018 Alfresco Software Limited
+ * %%
+ * This file is part of the Alfresco software.
+ * If the software was purchased under a paid Alfresco license, the terms of
+ * the paid license agreement will prevail. Otherwise, the software is
+ * provided under the following open source license terms:
+ *
+ * Alfresco is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Alfresco is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with Alfresco. If not, see .
+ * #L%
+ */
+package org.alfresco.opencmis;
+
+import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
+import org.alfresco.repo.transaction.RetryingTransactionInterceptor;
+import org.alfresco.util.transaction.TransactionListenerAdapter;
+import org.apache.chemistry.opencmis.commons.spi.Holder;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
+
+/**
+ * A Tx aware wrapper around {@link Holder}.
+ *
+ *
+ * This wrapper is created in {@link CMISTransactionAwareHolderInterceptor}.
+ * It is designed to handle the state of the {@link Holder} in case of tx retries which are handled by
+ * {@link RetryingTransactionInterceptor}.
+ *
+ *
+ * There are a few things that influenced the implementation of this wrapper and need to be taken into account:
+ *
+ * -
+ * The wrapper is created in {@link CMISTransactionAwareHolderInterceptor} and is replacing the incoming
+ * parameter ({@link Holder}) in the call to {@link AlfrescoCmisServiceImpl}.
+ *
+ * -
+ * The calls to {@link AlfrescoCmisServiceImpl} generally return nothing, therefore the state
+ * of {@link Holder} or it's wrapper ({@link TransactionAwareHolder}) is modified inside
+ * the {@link AlfrescoCmisServiceImpl} and then read in CMIS layer.
+ *
+ * -
+ * The {@link CMISTransactionAwareHolderInterceptor} is called after {@link RetryingTransactionInterceptor}
+ * but due to internal counter in Spring AOP it is not called again if the tx is retried.
+ * The proxied service ({@link AlfrescoCmisServiceImpl}) is called straight away.
+ * Fortunately the parameter replacing is not required for the second time.
+ * The wrapper ({@link TransactionAwareHolder}) will still be used.
+ *
+ * -
+ * The {@link TxAwareHolderListener} is bound to the tx when the internal value is read.
+ * This is done this way because once the tx is rolled backed the listener list is cleared.
+ * The {@link TxAwareHolderListener} is still required to be called once the retry succeeds with a commit.
+ * The {@link CMISTransactionAwareHolderInterceptor} cannot recreate the {@link TransactionAwareHolder}
+ * as the interceptor is called only once.
+ * It is safe to bind the same listener many times as it is always the same object.
+ *
+ *
+ *
+ *
+ * @author alex.mukha
+ */
+public class TransactionAwareHolder extends Holder
+{
+ private Holder internalHolder;
+ private T value;
+ private TxAwareHolderListener txListener;
+
+ TransactionAwareHolder(Holder internalHolder)
+ {
+ this.internalHolder = internalHolder;
+ this.value = internalHolder.getValue();
+ txListener = new TxAwareHolderListener();
+ }
+
+ @Override
+ public T getValue()
+ {
+ if (TransactionSynchronizationManager.isSynchronizationActive())
+ {
+ AlfrescoTransactionSupport.bindListener(txListener);
+ }
+ return this.value;
+ }
+
+ @Override
+ public void setValue(T value)
+ {
+ this.value = value;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "TransactionAwareHolder{" +
+ "internalHolder=" + internalHolder +
+ ", value=" + value +
+ '}';
+ }
+
+ private class TxAwareHolderListener extends TransactionListenerAdapter
+ {
+ @Override
+ public void afterCommit()
+ {
+ internalHolder.setValue(getValue());
+ }
+
+ @Override
+ public void afterRollback()
+ {
+ setValue(internalHolder.getValue());
+ }
+ }
+}
diff --git a/src/main/resources/alfresco/opencmis-context.xml b/src/main/resources/alfresco/opencmis-context.xml
index a517072adb..8d07ac0952 100644
--- a/src/main/resources/alfresco/opencmis-context.xml
+++ b/src/main/resources/alfresco/opencmis-context.xml
@@ -84,9 +84,12 @@
+
+
+