diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml
index bef5ea0518..b5fc56a5a2 100644
--- a/config/alfresco/content-services-context.xml
+++ b/config/alfresco/content-services-context.xml
@@ -7,6 +7,15 @@
${dir.contentstore}
+
+
+
+
+
+
+
diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml
index 9984231350..d8253d8b57 100644
--- a/config/alfresco/core-services-context.xml
+++ b/config/alfresco/core-services-context.xml
@@ -36,6 +36,9 @@
classpath:alfresco/repository.propertiesclasspath:alfresco/domain/transaction.properties
+
+
+ classpath*:alfresco/enterprise/repository.propertiesclasspath*:alfresco/module/*/alfresco-global.properties
@@ -521,6 +524,7 @@
+
diff --git a/config/alfresco/messages/action-config_ja.properties b/config/alfresco/messages/action-config_ja.properties
index b5ad0e7592..d0a68e6ac2 100755
--- a/config/alfresco/messages/action-config_ja.properties
+++ b/config/alfresco/messages/action-config_ja.properties
@@ -29,7 +29,7 @@ in-category.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u
in-category.category-aspect.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u30a2\u30b9\u30da\u30af\u30c8
in-category.category-value.display-label=\u30ab\u30c6\u30b4\u30ea\u306e\u5024
-is-subtype.title=\u30bf\u30a4\u30d7\u307e\u305f\u306f\u30b5\u30d6\u30bf\u30a4\u30d7\u306e\u30b3\u30f3\u30c6\u30f3\u30c4
+is-subtype.title=\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30bf\u30a4\u30d7\u307e\u305f\u306f\u30b5\u30d6\u30bf\u30a4\u30d7
is-subtype.description=\u30eb\u30fc\u30eb\u306f\u3001\u6307\u5b9a\u3055\u308c\u305f\u30bf\u30a4\u30d7\u307e\u305f\u306f\u305d\u306e\u30b5\u30d6\u30bf\u30a4\u30d7\u3067\u3042\u308b\u3059\u3079\u3066\u306e\u30a2\u30a4\u30c6\u30e0\u306b\u9069\u7528\u3055\u308c\u307e\u3059\u3002
is-subtype.type.display-label=\u30bf\u30a4\u30d7
diff --git a/config/alfresco/messages/patch-service_es.properties b/config/alfresco/messages/patch-service_es.properties
index ca54d72ba8..231ff48423 100755
--- a/config/alfresco/messages/patch-service_es.properties
+++ b/config/alfresco/messages/patch-service_es.properties
@@ -161,6 +161,8 @@ patch.wcmFolders.webprojects.result.created=The Web Projects folder was successf
patch.wcmFolders.webforms.result.exists=The Web Forms folder already exists: {0}
patch.wcmFolders.webforms.result.created=The Web Forms folder was successfully created: {0}
+patch.wcmDeployed.description=Adds the 'WCM Deployed' space to the company home folder.
+
patch.linkNodeExtension.description=Fixes link node file extensions to have a .url extension.
patch.linkNodeExtension.result=Fixed {0} link node file extensions. See file {1} for details.
patch.linkNodeExtension.err.unable_to_fix=Auto-fixing of link node file extensions failed. See file {0} for details.
diff --git a/config/alfresco/messages/patch-service_fr.properties b/config/alfresco/messages/patch-service_fr.properties
index ca54d72ba8..231ff48423 100755
--- a/config/alfresco/messages/patch-service_fr.properties
+++ b/config/alfresco/messages/patch-service_fr.properties
@@ -161,6 +161,8 @@ patch.wcmFolders.webprojects.result.created=The Web Projects folder was successf
patch.wcmFolders.webforms.result.exists=The Web Forms folder already exists: {0}
patch.wcmFolders.webforms.result.created=The Web Forms folder was successfully created: {0}
+patch.wcmDeployed.description=Adds the 'WCM Deployed' space to the company home folder.
+
patch.linkNodeExtension.description=Fixes link node file extensions to have a .url extension.
patch.linkNodeExtension.result=Fixed {0} link node file extensions. See file {1} for details.
patch.linkNodeExtension.err.unable_to_fix=Auto-fixing of link node file extensions failed. See file {0} for details.
diff --git a/config/alfresco/messages/patch-service_zh_CN.properties b/config/alfresco/messages/patch-service_zh_CN.properties
index b328dd8a15..231ff48423 100755
--- a/config/alfresco/messages/patch-service_zh_CN.properties
+++ b/config/alfresco/messages/patch-service_zh_CN.properties
@@ -472,8 +472,3 @@ patch.migrateTenantsFromAttrsToTable.result=Processed {0} tenants
patch.remoteCredentialsContainer.description=Patch to add the root folder for Shared Remote Credentials
patch.syncSetDefinitionsContainer.description=Patch to add the root folder for SyncSet Definitions
-
-patch.swsdpPatch.description=Patch to fix up the Sample: Web Site Design Project.
-patch.swsdpPatch.success=Successfully patched the Sample: Web Site Design Project.
-patch.swsdpPatch.skipped=Skipped, not required.
-patch.swsdpPatch.missingSurfConfig=surf-config folder is not present in Sample: Web Site Design Project.
\ No newline at end of file
diff --git a/config/alfresco/messages/rule-config_ja.properties b/config/alfresco/messages/rule-config_ja.properties
index 3e7b3910d5..6ac9e3616c 100755
--- a/config/alfresco/messages/rule-config_ja.properties
+++ b/config/alfresco/messages/rule-config_ja.properties
@@ -1,6 +1,6 @@
# Rule types
-inbound.display-label=\u30a2\u30a4\u30c6\u30e0\u306f\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u3067\u4f5c\u6210\u307e\u305f\u306f\u5165\u529b\u3055\u308c\u307e\u3059\u3002
-outbound.display-label=\u30a2\u30a4\u30c6\u30e0\u306f\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u304b\u3089\u524a\u9664\u307e\u305f\u306f\u6b8b\u3055\u308c\u307e\u3059\u3002
-update.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002
+inbound.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u4f5c\u6210\u307e\u305f\u306f\u5165\u529b\u3055\u308c\u305f\u6642
+outbound.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u304b\u3089\u524a\u9664\u307e\u305f\u306f\u9001\u308a\u51fa\u3055\u308c\u305f\u6642
+update.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u66f4\u65b0\u3055\u308c\u305f\u6642
inboundAndUpdate.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u4f5c\u6210\u3055\u308c\u3001\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u5165\u308b\u304b\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002
diff --git a/config/alfresco/model/imapModel.xml b/config/alfresco/model/imapModel.xml
index b221e081a9..3bc8243383 100644
--- a/config/alfresco/model/imapModel.xml
+++ b/config/alfresco/model/imapModel.xml
@@ -84,7 +84,7 @@
Attachments Folderfalse
- false
+ truecm:cmobject
diff --git a/config/alfresco/mt/mt-contentstore-context.xml b/config/alfresco/mt/mt-contentstore-context.xml
index 9362c1ca1d..014c625d4a 100644
--- a/config/alfresco/mt/mt-contentstore-context.xml
+++ b/config/alfresco/mt/mt-contentstore-context.xml
@@ -9,6 +9,7 @@
+
diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml
index 2f6a34a05c..e2c0dfce60 100644
--- a/config/alfresco/public-services-security-context.xml
+++ b/config/alfresco/public-services-security-context.xml
@@ -1108,27 +1108,6 @@
-
-
-
-
-
-
-
-
-
-
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getRemoteCredentials=ACL_ALLOW
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.storeRemoteCredentials=ACL_ALLOW
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.deleteRemoteCredentials=ACL_ALLOW
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getAlfrescoTicket=ACL_ALLOW
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.refetchAlfrescoTicket=ACL_ALLOW
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.registerRemoteSystem=ACL_METHOD.ROLE_ADMINISTRATOR
- org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.*=ACL_DENY
-
-
-
-
diff --git a/config/alfresco/remote-ticket-services-context.xml b/config/alfresco/remote-ticket-services-context.xml
index 5609b4637d..2ae0695324 100644
--- a/config/alfresco/remote-ticket-services-context.xml
+++ b/config/alfresco/remote-ticket-services-context.xml
@@ -25,7 +25,7 @@
-
+
@@ -60,6 +60,25 @@
+
+
+
+
+
+
+
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.storeRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.deleteRemoteCredentials=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.getAlfrescoTicket=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.refetchAlfrescoTicket=ACL_ALLOW
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.registerRemoteSystem=ACL_METHOD.ROLE_ADMINISTRATOR
+ org.alfresco.service.cmr.remoteticket.RemoteAlfrescoTicketService.*=ACL_DENY
+
+
+
+
+
@@ -82,4 +101,5 @@
+
diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties
index 717fcd29d5..c4cdd10d36 100644
--- a/config/alfresco/repository.properties
+++ b/config/alfresco/repository.properties
@@ -15,6 +15,13 @@ dir.cachedcontent=${dir.root}/cachedcontent
dir.auditcontentstore=${dir.root}/audit.contentstore
+# The value for the maximum permitted size in bytes of all content.
+# No value (or a negative long) will be taken to mean that no limit should be applied.
+# See content-services-context.xml
+system.content.maximumFileSizeLimit=
+
+
+
# The location for lucene index files
dir.indexes=${dir.root}/lucene-indexes
@@ -754,6 +761,17 @@ imap.config.server.mountPoints.default.rootPath=${protocols.rootPath}
imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP
imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED
+#Imap extraction settings
+#imap.attachments.mode:
+# SEPARATE -- All attachments for each email will be extracted to separate folder.
+# COMMON -- All attachments for all emails will be extracted to one folder.
+# SAME -- Attachments will be extracted to the same folder where email lies.
+imap.server.attachments.extraction.enabled=true
+imap.attachments.mode=SEPARATE
+imap.attachments.folder.store=${spaces.store}
+imap.attachments.folder.rootPath=/${spaces.company_home.childname}
+imap.attachments.folder.folderPath=Imap Attachments
+
# Activities Feed - refer to subsystem
# Feed max size (number of entries)
@@ -983,4 +1001,4 @@ ticket.cleanup.cronExpression=0 0 * * * ?
#
# Disable load of sample site
#
-sample.site.disabled=false
\ No newline at end of file
+sample.site.disabled=false
diff --git a/config/alfresco/subsystems/imap/default/imap-server-context.xml b/config/alfresco/subsystems/imap/default/imap-server-context.xml
index 1ab31a05b2..c1fdf8b9ad 100644
--- a/config/alfresco/subsystems/imap/default/imap-server-context.xml
+++ b/config/alfresco/subsystems/imap/default/imap-server-context.xml
@@ -84,6 +84,19 @@
+
+
+
+ ${imap.attachments.folder.store}
+
+
+ ${imap.attachments.folder.rootPath}
+
+
+ ${imap.attachments.folder.folderPath}
+
+
+
@@ -95,6 +108,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${imap.attachments.mode}
+
+
+
@@ -123,6 +154,9 @@
+
+
+
diff --git a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties
index e0e2e37aa3..1b103312ee 100644
--- a/config/alfresco/subsystems/thirdparty/default/swf-transform.properties
+++ b/config/alfresco/subsystems/thirdparty/default/swf-transform.properties
@@ -4,4 +4,4 @@ swf.exe=./bin/pdf2swf
# This option on pdf2swf improves the transformation of graphics-heavy pdfs. See ALF-3580.
# poly2bitmap improves the chances of successful transformation. On its own it reduces
# the resolution of embedded images. subpixels sets the dpi for embedded images.
-swf.encoder.params=-s poly2bitmap,subpixels=72
\ No newline at end of file
+swf.encoder.params=-s poly2bitmap -s subpixels=72
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/content/AbstractContentStore.java b/source/java/org/alfresco/repo/content/AbstractContentStore.java
index b03c706054..49d648171a 100644
--- a/source/java/org/alfresco/repo/content/AbstractContentStore.java
+++ b/source/java/org/alfresco/repo/content/AbstractContentStore.java
@@ -20,6 +20,7 @@ package org.alfresco.repo.content;
import java.util.Date;
+import org.alfresco.repo.content.ContentLimitProvider.NoLimitProvider;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -75,6 +76,8 @@ public abstract class AbstractContentStore implements ContentStore
}
return true;
}
+
+ protected ContentLimitProvider contentLimitProvider = new NoLimitProvider();
/**
* Splits the content URL into its component parts as separated by
@@ -309,4 +312,9 @@ public abstract class AbstractContentStore implements ContentStore
{
return ".";
}
+
+ public void setContentLimitProvider(ContentLimitProvider contentLimitProvider)
+ {
+ this.contentLimitProvider = contentLimitProvider;
+ }
}
diff --git a/source/java/org/alfresco/repo/content/AbstractContentWriter.java b/source/java/org/alfresco/repo/content/AbstractContentWriter.java
index c212e16b86..59fa0c7596 100644
--- a/source/java/org/alfresco/repo/content/AbstractContentWriter.java
+++ b/source/java/org/alfresco/repo/content/AbstractContentWriter.java
@@ -34,6 +34,7 @@ import java.util.ArrayList;
import java.util.List;
import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.content.ContentLimitProvider.NoLimitProvider;
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.repo.content.filestore.FileContentWriter;
import org.alfresco.service.cmr.repository.ContentAccessor;
@@ -67,6 +68,13 @@ public abstract class AbstractContentWriter extends AbstractContentAccessor impl
private MimetypeService mimetypeService;
private DoGuessingOnCloseListener guessingOnCloseListener;
+ /**
+ * This object provides a maximum size limit for content.
+ * @since Thor
+ */
+ private ContentLimitProvider limitProvider = new NoLimitProvider();
+ private LimitedStreamCopier sizeLimitedStreamCopier = new LimitedStreamCopier();
+
/**
* @param contentUrl the content URL
* @param existingContentReader a reader of a previous version of this content
@@ -85,6 +93,11 @@ public abstract class AbstractContentWriter extends AbstractContentAccessor impl
listeners.add(guessingOnCloseListener);
}
+ public void setContentLimitProvider(ContentLimitProvider limitProvider)
+ {
+ this.limitProvider = limitProvider;
+ }
+
/**
* Supplies the Mimetype Service to be used when guessing
* encoding and mimetype information.
@@ -164,6 +177,16 @@ public abstract class AbstractContentWriter extends AbstractContentAccessor impl
}
return reader;
}
+
+ /**
+ * This method returns the configured {@link ContentLimitProvider} for this writer.
+ * By default a {@link NoLimitProvider} will be returned.
+ * @since Thor
+ */
+ protected ContentLimitProvider getContentLimitProvider()
+ {
+ return this.limitProvider == null ? new NoLimitProvider() : this.limitProvider;
+ }
/**
* An automatically created listener sets the flag
@@ -470,44 +493,11 @@ public abstract class AbstractContentWriter extends AbstractContentAccessor impl
*/
private final int copyStreams(InputStream in, OutputStream out) throws IOException
{
- int byteCount = 0;
- IOException error = null;
- try
- {
- byte[] buffer = new byte[4096];
- int bytesRead = -1;
- while ((bytesRead = in.read(buffer)) != -1)
- {
- out.write(buffer, 0, bytesRead);
- byteCount += bytesRead;
- }
- out.flush();
- }
- finally
- {
- try
- {
- in.close();
- }
- catch (IOException e)
- {
- error = e;
- logger.error("Failed to close output stream: " + this, e);
- }
- try
- {
- out.close();
- }
- catch (IOException e)
- {
- error = e;
- logger.error("Failed to close output stream: " + this, e);
- }
- }
- if (error != null)
- {
- throw error;
- }
+ ContentLimitProvider contentLimitProvider = getContentLimitProvider();
+ final long sizeLimit = contentLimitProvider.getSizeLimit();
+
+ int byteCount = sizeLimitedStreamCopier.copyStreams(in, out, sizeLimit);
+
return byteCount;
}
diff --git a/source/java/org/alfresco/repo/content/ContentLimitProvider.java b/source/java/org/alfresco/repo/content/ContentLimitProvider.java
new file mode 100644
index 0000000000..ce8dbdaa55
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/ContentLimitProvider.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.content;
+
+/**
+ * Implementations of this interface must provide a byte size limit for pieces of Alfresco content.
+ * Simple implementations of this interface include:
+ *
+ *
{@link NoLimitProvider} which provides a value indicating no limit.
+ *
{@link SimpleFixedLimitProvider} which provides a fixed numerical limit value.
+ *
+ * It is possible that smarter implementations may be added at a future date.
+ *
+ * @author Neil Mc Erlean
+ * @since Thor
+ */
+public interface ContentLimitProvider
+{
+ /**
+ * A {@link ContentLimitProvider#getSizeLimit() limit} having this value is deemed not to be a limit.
+ */
+ public static final long NO_LIMIT = -1L;
+
+ /**
+ * This method returns a size limit in bytes.
+ */
+ long getSizeLimit();
+
+ /**
+ * A {@link ContentLimitProvider} which returns a fixed value.
+ */
+ public static class SimpleFixedLimitProvider implements ContentLimitProvider
+ {
+ private long limit;
+
+ public SimpleFixedLimitProvider()
+ {
+ // Default constructor for use as bean.
+ }
+ public SimpleFixedLimitProvider(long limit)
+ {
+ this.limit = limit;
+ }
+
+ /**
+ * This method sets a value for the limit. If the string does not {@link Long#parseLong(String) parse} to a
+ * java long, the {@link ContentLimitProvider#NO_LIMIT default value} will be applied instead.
+ *
+ * @param limit a String representing a valid Java long.
+ */
+ public void setSizeLimitString(String limit)
+ {
+ // A string parameter is used here in order to not to require end users to provide a value for the limit in a property
+ // file. This results in the empty string being injected to this method.
+ long longLimit = NO_LIMIT;
+ try
+ {
+ longLimit = Long.parseLong(limit);
+ } catch (NumberFormatException ignored)
+ {
+ // Intentionally empty
+ }
+ this.limit = longLimit;
+ }
+
+ @Override public long getSizeLimit()
+ {
+ return limit;
+ }
+ }
+
+ /**
+ * A {@link ContentLimitProvider} which returns a value indicating there is no limit.
+ */
+ public static class NoLimitProvider implements ContentLimitProvider
+ {
+ @Override public long getSizeLimit()
+ {
+ return NO_LIMIT;
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/ContentLimitViolationException.java b/source/java/org/alfresco/repo/content/ContentLimitViolationException.java
new file mode 100644
index 0000000000..38ed88edf0
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/ContentLimitViolationException.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.content;
+
+import org.alfresco.service.cmr.repository.ContentIOException;
+
+/**
+ * This exception represents a violation of the defined content limit.
+ *
+ * @author Neil Mc Erlean
+ * @since Thor
+ */
+public class ContentLimitViolationException extends ContentIOException
+{
+ private static final long serialVersionUID = -640491905977728606L;
+
+ public ContentLimitViolationException(String msg)
+ {
+ super(msg);
+ }
+
+ public ContentLimitViolationException(String msg, Throwable cause)
+ {
+ super(msg, cause);
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/LimitedStreamCopier.java b/source/java/org/alfresco/repo/content/LimitedStreamCopier.java
new file mode 100644
index 0000000000..d53f161f89
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/LimitedStreamCopier.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.content;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * This class is a simple utility to copy bytes from an {@link InputStream} to an {@link OutputStream}.
+ * The copy can be performed with an optional byte limit and as soon as this limit is reached,
+ * the copy will be stopped immediately and a {@link ContentLimitViolationException} will be thrown.
+ *
+ * @since Thor
+ */
+public final class LimitedStreamCopier
+{
+ private static final Log logger = LogFactory.getLog(LimitedStreamCopier.class);
+ private static final int BYTE_BUFFER_SIZE = 4096;
+
+ /**
+ * Copy of the the Spring FileCopyUtils, but does not silently absorb IOExceptions
+ * when the streams are closed. We require the stream write to happen successfully.
+ *
+ * Both streams are closed but any IOExceptions are thrown
+ *
+ * @param in the stream from which to read content.
+ * @param out the stream to which to write content.
+ * @param sizeLimit the maximum number of bytes that will be copied between the streams before a
+ * {@link ContentLimitViolationException} will be thrown.
+ * A negative number or zero will be deemed to mean 'no limit'.
+ */
+ public final int copyStreams(InputStream in, OutputStream out, long sizeLimit) throws IOException
+ {
+ int byteCount = 0;
+ IOException error = null;
+
+ long totalBytesRead = 0;
+
+ try
+ {
+ byte[] buffer = new byte[BYTE_BUFFER_SIZE];
+ int bytesRead = -1;
+ while ((bytesRead = in.read(buffer)) != -1)
+ {
+ // We are able to abort the copy immediately upon limit violation.
+ totalBytesRead += bytesRead;
+ if (sizeLimit > 0 && totalBytesRead > sizeLimit)
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Content size violation, limit = ")
+ .append(sizeLimit);
+
+ throw new ContentLimitViolationException(msg.toString());
+ }
+
+ out.write(buffer, 0, bytesRead);
+ byteCount += bytesRead;
+ }
+ out.flush();
+ }
+ finally
+ {
+ try
+ {
+ in.close();
+ }
+ catch (IOException e)
+ {
+ error = e;
+ logger.error("Failed to close output stream: " + this, e);
+ }
+ try
+ {
+ out.close();
+ }
+ catch (IOException e)
+ {
+ error = e;
+ logger.error("Failed to close output stream: " + this, e);
+ }
+ }
+ if (error != null)
+ {
+ throw error;
+ }
+ return byteCount;
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/LimitedStreamCopierTest.java b/source/java/org/alfresco/repo/content/LimitedStreamCopierTest.java
new file mode 100644
index 0000000000..d0a711bf01
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/LimitedStreamCopierTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2005-2011 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.content;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit test for {@link LimitedStreamCopier}.
+ *
+ * @author Neil Mc Erlean
+ * @since Thor
+ */
+public class LimitedStreamCopierTest
+{
+ private final static byte[] ZERO_BYTE_ARRAY = "".getBytes();
+ private final static byte[] SHORT_BYTE_ARRAY = "This string is shorter than the limit".getBytes();
+ private final static byte[] LIMIT_SIZED_BYTE_ARRAY = "This string's length exactly equals the limit".getBytes();
+ private final static byte[] LONG_BYTE_ARRAY = "This test string is longer than the limit. Tum te tum te tum te tum.".getBytes();
+
+ private final static int SIZE_LIMIT = LIMIT_SIZED_BYTE_ARRAY.length;
+
+ private LimitedStreamCopier streamCopier = new LimitedStreamCopier();
+ private InputStream in;
+ private ByteArrayOutputStream out;
+
+ @Before public void initStreamCopier()
+ {
+ out = new ByteArrayOutputStream();
+ }
+
+ /**
+ * Test copying a simple byte[]
+ */
+ @Test public void copyStreamSuccessful() throws Exception
+ {
+ in = new ByteArrayInputStream(SHORT_BYTE_ARRAY);
+
+ int bytesCopied = streamCopier.copyStreams(in, out, SIZE_LIMIT);
+
+ assertArrayEquals(SHORT_BYTE_ARRAY, out.toByteArray());
+ assertEquals(SHORT_BYTE_ARRAY.length, bytesCopied);
+ }
+
+ /**
+ * Test copying an empty byte[]
+ */
+ @Test public void copyStreamSuccessfulZeroBytes() throws Exception
+ {
+ in = new ByteArrayInputStream(ZERO_BYTE_ARRAY);
+
+ int bytesCopied = streamCopier.copyStreams(in, out, SIZE_LIMIT);
+
+ assertArrayEquals(ZERO_BYTE_ARRAY, out.toByteArray());
+ assertEquals(ZERO_BYTE_ARRAY.length, bytesCopied);
+ }
+
+ /**
+ * Test copying a byte[] that exceeds the limit.
+ */
+ @Test (expected=ContentLimitViolationException.class)
+ public void copyStreamUnsuccessfulLimitExceeded() throws Exception
+ {
+ in = new ByteArrayInputStream(LONG_BYTE_ARRAY);
+
+ int bytesCopied = streamCopier.copyStreams(in, out, SIZE_LIMIT);
+
+ assertArrayEquals(LONG_BYTE_ARRAY, out.toByteArray());
+ assertEquals(LONG_BYTE_ARRAY.length, bytesCopied);
+ }
+
+ /**
+ * Test copying a byte[] that equals the limit.
+ */
+ @Test
+ public void copyStreamSuccessfulLimitHit() throws Exception
+ {
+ in = new ByteArrayInputStream(LIMIT_SIZED_BYTE_ARRAY);
+
+ int bytesCopied = streamCopier.copyStreams(in, out, SIZE_LIMIT);
+
+ assertArrayEquals(LIMIT_SIZED_BYTE_ARRAY, out.toByteArray());
+ assertEquals(LIMIT_SIZED_BYTE_ARRAY.length, bytesCopied);
+ }
+
+ /**
+ * Test copying a simple byte[] with no limit sent.
+ */
+ @Test public void copyStreamSuccessfulBecauseLimitNotImposed() throws Exception
+ {
+ in = new ByteArrayInputStream(LONG_BYTE_ARRAY);
+
+ int bytesCopied = streamCopier.copyStreams(in, out, -1);
+
+ assertArrayEquals(LONG_BYTE_ARRAY, out.toByteArray());
+ assertEquals(LONG_BYTE_ARRAY.length, bytesCopied);
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java
index f9e2cef0e2..05acdc6d29 100644
--- a/source/java/org/alfresco/repo/content/filestore/FileContentStore.java
+++ b/source/java/org/alfresco/repo/content/filestore/FileContentStore.java
@@ -471,6 +471,11 @@ public class FileContentStore
}
// create the writer
FileContentWriter writer = new FileContentWriter(file, contentUrl, existingContentReader);
+
+ if (contentLimitProvider != null)
+ {
+ writer.setContentLimitProvider(contentLimitProvider);
+ }
writer.setAllowRandomAccess(allowRandomAccess);
// done
diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
index ecda2aeabd..f47d9db38c 100644
--- a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
+++ b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
@@ -24,6 +24,9 @@ import java.nio.ByteBuffer;
import org.alfresco.repo.content.AbstractWritableContentStoreTest;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentExistsException;
+import org.alfresco.repo.content.ContentLimitProvider;
+import org.alfresco.repo.content.ContentLimitProvider.SimpleFixedLimitProvider;
+import org.alfresco.repo.content.ContentLimitViolationException;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
@@ -204,6 +207,37 @@ public class FileContentStoreTest extends AbstractWritableContentStoreTest
assertDirExists(root, "");
}
+ /**
+ * This method tests that writing content with a configured {@link ContentLimitProvider limit} fails with
+ * the expected exception.
+ * @since Thor
+ */
+ public void testWriteFileWithSizeLimit() throws Exception
+ {
+ ContentWriter writer = getWriter();
+ assertEquals("Writer was of wrong type", FileContentWriter.class, writer.getClass());
+
+ FileContentWriter fileContentWriter = (FileContentWriter)writer;
+
+ // Set a maximum size limit for this writer. We use a limit of 3 bytes.
+ ContentLimitProvider limitProvider = new SimpleFixedLimitProvider(3);
+ fileContentWriter.setContentLimitProvider(limitProvider);
+
+ // Attempt to write content that will exceed the limit.
+ boolean expectedExceptionThrown = false;
+ try
+ {
+ writer.putContent("This will exceed the short limit.");
+ }
+ catch (ContentLimitViolationException clvx)
+ {
+ expectedExceptionThrown = true;
+ }
+
+ assertTrue("Expected exception not thrown.", expectedExceptionThrown);
+ assertTrue("Stream close not detected", writer.isClosed());
+ }
+
private void assertDirExists(File root, String dir)
{
diff --git a/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java b/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java
index 115b07324c..06dce6c618 100644
--- a/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java
+++ b/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -18,14 +18,94 @@
*/
package org.alfresco.repo.content.transform;
+import java.io.BufferedOutputStream;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+
+import net.sf.jooreports.converter.DocumentFamily;
+import net.sf.jooreports.converter.DocumentFormat;
+import net.sf.jooreports.converter.DocumentFormatRegistry;
+import net.sf.jooreports.converter.XmlDocumentFormatRegistry;
+import net.sf.jooreports.openoffice.connection.OpenOfficeException;
+
+import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.service.cmr.repository.ContentIOException;
+import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.MimetypeService;
+import org.alfresco.service.cmr.repository.TransformationOptions;
+import org.alfresco.util.TempFileProvider;
+import org.apache.commons.logging.Log;
+import org.apache.pdfbox.exceptions.COSVisitorException;
+import org.apache.pdfbox.pdmodel.PDDocument;
+import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.springframework.core.io.DefaultResourceLoader;
+import org.springframework.util.Assert;
+import org.springframework.util.FileCopyUtils;
/**
* A class providing basic OOo-related functionality shared by both
* {@link ContentTransformer}s and {@link ContentTransformerWorker}s.
*/
-public class OOoContentTransformerHelper extends ContentTransformerHelper
+public abstract class OOoContentTransformerHelper extends ContentTransformerHelper
{
+ private String documentFormatsConfiguration;
+ private DocumentFormatRegistry formatRegistry;
+
+ /**
+ * Set a non-default location from which to load the document format mappings.
+ *
+ * @param path
+ * a resource location supporting the file: or classpath: prefixes
+ */
+ public void setDocumentFormatsConfiguration(String path)
+ {
+ this.documentFormatsConfiguration = path;
+ }
+
+ protected abstract Log getLogger();
+
+ protected abstract String getTempFilePrefix();
+
+ public abstract boolean isAvailable();
+
+ protected abstract void convert(File tempFromFile, DocumentFormat sourceFormat, File tempToFile,
+ DocumentFormat targetFormat);
+
+ public void afterPropertiesSet() throws Exception
+ {
+ // load the document conversion configuration
+ if (documentFormatsConfiguration != null)
+ {
+ DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
+ try
+ {
+ InputStream is = resourceLoader.getResource(this.documentFormatsConfiguration).getInputStream();
+ formatRegistry = new XmlDocumentFormatRegistry(is);
+ // We do not need to explicitly close this InputStream as it is closed for us within the XmlDocumentFormatRegistry
+ }
+ catch (IOException e)
+ {
+ throw new AlfrescoRuntimeException(
+ "Unable to load document formats configuration file: "
+ + this.documentFormatsConfiguration);
+ }
+ }
+ else
+ {
+ formatRegistry = new XmlDocumentFormatRegistry();
+ }
+ }
+
/**
* There are some conversions that fail, despite the converter believing them possible.
* This method can be used by subclasses to check if a targetMimetype or source/target
@@ -54,4 +134,290 @@ public class OOoContentTransformerHelper extends ContentTransformerHelper
return false;
}
}
+
+ /**
+ * @see DocumentFormatRegistry
+ */
+ public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options)
+ {
+ // Use BinaryPassThroughContentTransformer if mimetypes are the same.
+ if (sourceMimetype.equals(targetMimetype))
+ {
+ return false;
+ }
+
+ if (!isAvailable())
+ {
+ // The connection management is must take care of this
+ return false;
+ }
+
+ if (isTransformationBlocked(sourceMimetype, targetMimetype))
+ {
+ if (getLogger().isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("Transformation from ")
+ .append(sourceMimetype).append(" to ")
+ .append(targetMimetype)
+ .append(" is blocked and therefore unavailable.");
+ getLogger().debug(msg.toString());
+ }
+ return false;
+ }
+
+ MimetypeService mimetypeService = getMimetypeService();
+ String sourceExtension = mimetypeService.getExtension(sourceMimetype);
+ String targetExtension = mimetypeService.getExtension(targetMimetype);
+ // query the registry for the source format
+ DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
+ if (sourceFormat == null)
+ {
+ // no document format
+ return false;
+ }
+ // query the registry for the target format
+ DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
+ if (targetFormat == null)
+ {
+ // no document format
+ return false;
+ }
+
+ // get the family of the target document
+ DocumentFamily sourceFamily = sourceFormat.getFamily();
+ // does the format support the conversion
+ if (!targetFormat.isExportableFrom(sourceFamily))
+ {
+ // unable to export from source family of documents to the target format
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+ /**
+ * This method produces an empty PDF file at the specified File location.
+ * Apache's PDFBox is used to create the PDF file.
+ */
+ private void produceEmptyPdfFile(File tempToFile)
+ {
+ // If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
+ // org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
+ // https://issues.apache.org/jira/browse/PDFBOX-914
+
+ PDDocument pdfDoc = null;
+ PDPageContentStream contentStream = null;
+ try
+ {
+ pdfDoc = new PDDocument();
+ PDPage pdfPage = new PDPage();
+ // Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
+ // that have literally nothing in them. So we'll put a content stream in it.
+ contentStream = new PDPageContentStream(pdfDoc, pdfPage);
+ pdfDoc.addPage(pdfPage);
+
+ // Now write the in-memory PDF document into the temporary file.
+ pdfDoc.save(tempToFile.getAbsolutePath());
+
+ }
+ catch (COSVisitorException cvx)
+ {
+ throw new ContentIOException("Error creating empty PDF file", cvx);
+ }
+ catch (IOException iox)
+ {
+ throw new ContentIOException("Error creating empty PDF file", iox);
+ }
+ finally
+ {
+ if (contentStream != null)
+ {
+ try
+ {
+ contentStream.close();
+ }
+ catch (IOException ignored)
+ {
+ // Intentionally empty
+ }
+ }
+ if (pdfDoc != null)
+ {
+ try
+ {
+ pdfDoc.close();
+ }
+ catch (IOException ignored)
+ {
+ // Intentionally empty.
+ }
+ }
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.alfresco.repo.content.transform.ContentTransformerWorker#getVersionString()
+ */
+ public String getVersionString()
+ {
+ return "";
+ }
+
+ public void transform(
+ ContentReader reader,
+ ContentWriter writer,
+ TransformationOptions options) throws Exception
+ {
+ if (isAvailable() == false)
+ {
+ throw new ContentIOException("Content conversion failed (unavailable): \n" +
+ " reader: " + reader + "\n" +
+ " writer: " + writer);
+ }
+
+ if (getLogger().isDebugEnabled())
+ {
+ StringBuilder msg = new StringBuilder();
+ msg.append("transforming content from ")
+ .append(reader.getMimetype())
+ .append(" to ")
+ .append(writer.getMimetype());
+ getLogger().debug(msg.toString());
+ }
+
+ String sourceMimetype = getMimetype(reader);
+ String targetMimetype = getMimetype(writer);
+
+ MimetypeService mimetypeService = getMimetypeService();
+ String sourceExtension = mimetypeService.getExtension(sourceMimetype);
+ String targetExtension = mimetypeService.getExtension(targetMimetype);
+ // query the registry for the source format
+ DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
+ if (sourceFormat == null)
+ {
+ // source format is not recognised
+ throw new ContentIOException("No OpenOffice document format for source extension: " + sourceExtension);
+ }
+ // query the registry for the target format
+ DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
+ if (targetFormat == null)
+ {
+ // target format is not recognised
+ throw new ContentIOException("No OpenOffice document format for target extension: " + targetExtension);
+ }
+ // get the family of the target document
+ DocumentFamily sourceFamily = sourceFormat.getFamily();
+ // does the format support the conversion
+ if (!targetFormat.isExportableFrom(sourceFamily))
+ {
+ throw new ContentIOException(
+ "OpenOffice conversion not supported: \n" +
+ " reader: " + reader + "\n" +
+ " writer: " + writer);
+ }
+
+ // create temporary files to convert from and to
+ File tempFromFile = TempFileProvider.createTempFile(
+ getTempFilePrefix()+"-source-",
+ "." + sourceExtension);
+ File tempToFile = TempFileProvider.createTempFile(
+ getTempFilePrefix()+"-target-",
+ "." + targetExtension);
+
+ // There is a bug (reported in ALF-219) whereby JooConverter (the Alfresco Community Edition's 3rd party
+ // OpenOffice connector library) struggles to handle zero-size files being transformed to pdf.
+ // For zero-length .html files, it throws NullPointerExceptions.
+ // For zero-length .txt files, it produces a pdf transformation, but it is not a conformant
+ // pdf file and cannot be viewed (contains no pages).
+ //
+ // For these reasons, if the file is of zero length, we will not use JooConverter & OpenOffice
+ // and will instead ask Apache PDFBox to produce an empty pdf file for us.
+ final long documentSize = reader.getSize();
+ if (documentSize == 0L)
+ {
+ produceEmptyPdfFile(tempToFile);
+ }
+ else
+ {
+ // download the content from the source reader
+ saveContentInFile(sourceMimetype, reader, tempFromFile);
+
+ // We have some content, so we'll use OpenOffice to render the pdf document.
+ // Currently, OpenOffice does a better job of rendering documents into PDF and so
+ // it is preferred over PDFBox.
+ try
+ {
+ convert(tempFromFile, sourceFormat, tempToFile, targetFormat);
+ }
+ catch (OpenOfficeException e)
+ {
+ throw new ContentIOException("OpenOffice server conversion failed: \n" +
+ " reader: " + reader + "\n" +
+ " writer: " + writer + "\n" +
+ " from file: " + tempFromFile + "\n" +
+ " to file: " + tempToFile,
+ e);
+ }
+ }
+
+ // upload the temp output to the writer given us
+ writer.putContent(tempToFile);
+
+ if (getLogger().isDebugEnabled())
+ {
+ getLogger().debug("transformation successful");
+ }
+ }
+
+ /**
+ * Populates a file with the content in the reader.
+ */
+ public void saveContentInFile(String sourceMimetype, ContentReader reader, File file) throws ContentIOException
+ {
+ String encoding = reader.getEncoding();
+ if (encodeAsUtf8(sourceMimetype, encoding))
+ {
+ saveContentInUtf8File(reader, file);
+ }
+ else
+ {
+ reader.getContent(file);
+ }
+ }
+
+ /**
+ * Returns {@code true} if the input file should be transformed to UTF8 encoding.
+ *
+ * OpenOffice/LibreOffice is unable to support the import of text files that are SHIFT JIS encoded
+ * (and others: windows-1252...) so transformed to UTF-8.
+ */
+ protected boolean encodeAsUtf8(String sourceMimetype, String encoding)
+ {
+ return MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && !"UTF-8".equals(encoding);
+ }
+
+ /**
+ * Populates a file with the content in the reader, but also converts the encoding to UTF-8.
+ */
+ private void saveContentInUtf8File(ContentReader reader, File file)
+ {
+ String encoding = reader.getEncoding();
+ try
+ {
+ Reader in = new InputStreamReader(reader.getContentInputStream(), encoding);
+ Writer out = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(file)), "UTF-8");
+
+ FileCopyUtils.copy(in, out); // both streams are closed
+ }
+ catch (IOException e)
+ {
+ throw new ContentIOException("Failed to copy content to file and convert "+encoding+" to UTF-8: \n" +
+ " file: " + file,
+ e);
+ }
+ }
}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerWorker.java
index fd5343c245..6b40421c9f 100644
--- a/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerWorker.java
+++ b/source/java/org/alfresco/repo/content/transform/OpenOfficeContentTransformerWorker.java
@@ -19,34 +19,20 @@
package org.alfresco.repo.content.transform;
import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import net.sf.jooreports.converter.DocumentFamily;
import net.sf.jooreports.converter.DocumentFormat;
-import net.sf.jooreports.converter.DocumentFormatRegistry;
-import net.sf.jooreports.converter.XmlDocumentFormatRegistry;
import net.sf.jooreports.openoffice.connection.OpenOfficeConnection;
-import net.sf.jooreports.openoffice.connection.OpenOfficeException;
import net.sf.jooreports.openoffice.converter.AbstractOpenOfficeDocumentConverter;
import net.sf.jooreports.openoffice.converter.OpenOfficeDocumentConverter;
-import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
-import org.alfresco.service.cmr.repository.ContentWriter;
-import org.alfresco.service.cmr.repository.MimetypeService;
-import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.util.PropertyCheck;
import org.alfresco.util.SocketOpenOfficeConnection;
-import org.alfresco.util.TempFileProvider;
-import org.alfresco.util.bean.BooleanBean;
-import org.apache.pdfbox.exceptions.COSVisitorException;
-import org.apache.pdfbox.pdmodel.PDDocument;
-import org.apache.pdfbox.pdmodel.PDPage;
-import org.apache.pdfbox.pdmodel.edit.PDPageContentStream;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
-import org.springframework.core.io.DefaultResourceLoader;
/**
* Makes use of the {@link http://sourceforge.net/projects/joott/JOOConverter} library to perform
@@ -58,10 +44,10 @@ import org.springframework.core.io.DefaultResourceLoader;
*/
public class OpenOfficeContentTransformerWorker extends OOoContentTransformerHelper implements ContentTransformerWorker, InitializingBean
{
+ private static Log logger = LogFactory.getLog(OpenOfficeContentTransformerWorker.class);
+
private OpenOfficeConnection connection;
private AbstractOpenOfficeDocumentConverter converter;
- private String documentFormatsConfiguration;
- private DocumentFormatRegistry formatRegistry;
/**
* @param connection
@@ -86,46 +72,30 @@ public class OpenOfficeContentTransformerWorker extends OOoContentTransformerHel
this.converter = converter;
}
- /**
- * Set a non-default location from which to load the document format mappings.
- *
- * @param path
- * a resource location supporting the file: or classpath: prefixes
- */
- public void setDocumentFormatsConfiguration(String path)
+ @Override
+ protected Log getLogger()
{
- this.documentFormatsConfiguration = path;
+ return logger;
}
+ @Override
+ protected String getTempFilePrefix()
+ {
+ return "OpenOfficeContentTransformer";
+ }
+
+ @Override
public boolean isAvailable()
{
return connection.isConnected();
}
+ @Override
public void afterPropertiesSet() throws Exception
{
PropertyCheck.mandatory("OpenOfficeContentTransformerWorker", "connection", connection);
- // load the document conversion configuration
- if (documentFormatsConfiguration != null)
- {
- DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
- try
- {
- InputStream is = resourceLoader.getResource(documentFormatsConfiguration).getInputStream();
- formatRegistry = new XmlDocumentFormatRegistry(is);
- }
- catch (IOException e)
- {
- throw new AlfrescoRuntimeException(
- "Unable to load document formats configuration file: " +
- documentFormatsConfiguration);
- }
- }
- else
- {
- formatRegistry = new XmlDocumentFormatRegistry();
- }
+ super.afterPropertiesSet();
// set up the converter
if (converter == null)
@@ -136,220 +106,25 @@ public class OpenOfficeContentTransformerWorker extends OOoContentTransformerHel
protected AbstractOpenOfficeDocumentConverter getDefaultConverter(OpenOfficeConnection connection)
{
- return (connection instanceof SocketOpenOfficeConnection)
+ return (connection instanceof SocketOpenOfficeConnection)
? ((SocketOpenOfficeConnection)connection).getDefaultConverter()
: new OpenOfficeDocumentConverter(connection);
}
- /**
- * @see DocumentFormatRegistry
- */
- public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options)
+ @Override
+ protected void convert(File tempFromFile, DocumentFormat sourceFormat, File tempToFile,
+ DocumentFormat targetFormat)
{
- // Use BinaryPassThroughContentTransformer if mimetypes are the same.
- if (sourceMimetype.equals(targetMimetype))
- {
- return false;
- }
-
- if (!isAvailable())
- {
- // The connection management is must take care of this
- return false;
- }
-
- if (isTransformationBlocked(sourceMimetype, targetMimetype))
- {
- return false;
- }
-
- MimetypeService mimetypeService = getMimetypeService();
- String sourceExtension = mimetypeService.getExtension(sourceMimetype);
- String targetExtension = mimetypeService.getExtension(targetMimetype);
- // query the registry for the source format
- DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
- if (sourceFormat == null)
- {
- // no document format
- return false;
- }
- // query the registry for the target format
- DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
- if (targetFormat == null)
- {
- // no document format
- return false;
- }
-
- // get the family of the target document
- DocumentFamily sourceFamily = sourceFormat.getFamily();
- // does the format support the conversion
- if (!targetFormat.isExportableFrom(sourceFamily))
- {
- // unable to export from source family of documents to the target format
- return false;
- }
- else
- {
- return true;
- }
+ converter.convert(tempFromFile, sourceFormat, tempToFile, targetFormat);
}
-
- public void transform(
- ContentReader reader,
- ContentWriter writer,
- TransformationOptions options) throws Exception
+
+ public void saveContentInFile(String sourceMimetype, ContentReader reader, File file) throws ContentIOException
{
- String sourceMimetype = getMimetype(reader);
- String targetMimetype = getMimetype(writer);
-
- MimetypeService mimetypeService = getMimetypeService();
- String sourceExtension = mimetypeService.getExtension(sourceMimetype);
- String targetExtension = mimetypeService.getExtension(targetMimetype);
- // query the registry for the source format
- DocumentFormat sourceFormat = formatRegistry.getFormatByFileExtension(sourceExtension);
- if (sourceFormat == null)
- {
- // source format is not recognised
- throw new ContentIOException("No OpenOffice document format for source extension: " + sourceExtension);
- }
- // query the registry for the target format
- DocumentFormat targetFormat = formatRegistry.getFormatByFileExtension(targetExtension);
- if (targetFormat == null)
- {
- // target format is not recognised
- throw new ContentIOException("No OpenOffice document format for target extension: " + targetExtension);
- }
- // get the family of the target document
- DocumentFamily sourceFamily = sourceFormat.getFamily();
- // does the format support the conversion
- if (!targetFormat.isExportableFrom(sourceFamily))
- {
- throw new ContentIOException(
- "OpenOffice conversion not supported: \n" +
- " reader: " + reader + "\n" +
- " writer: " + writer);
- }
-
- // create temporary files to convert from and to
- File tempFromFile = TempFileProvider.createTempFile(
- "OpenOfficeContentTransformer-source-",
- "." + sourceExtension);
- File tempToFile = TempFileProvider.createTempFile(
- "OpenOfficeContentTransformer-target-",
- "." + targetExtension);
-
+ // jooconverter does not handle non western chars by default in text files.
+ // Jodconverter does by setting properties it passes to soffice.
+ // The following patched method added to jooconverter, addes these properties.
+ converter.setTextUtf8(MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype));
- final long documentSize = reader.getSize();
-
- // download the content from the source reader
- reader.getContent(tempFromFile);
-
- // There is a bug (reported in ALF-219) whereby JooConverter (the Alfresco Community Edition's 3rd party
- // OpenOffice connector library) struggles to handle zero-size files being transformed to pdf.
- // For zero-length .html files, it throws NullPointerExceptions.
- // For zero-length .txt files, it produces a pdf transformation, but it is not a conformant
- // pdf file and cannot be viewed (contains no pages).
- //
- // For these reasons, if the file is of zero length, we will not use JooConverter & OpenOffice
- // and will instead ask Apache PDFBox to produce an empty pdf file for us.
- if (documentSize == 0L)
- {
- produceEmptyPdfFile(tempToFile);
- }
- else
- {
- // We have some content, so we'll use OpenOffice to render the pdf document.
- // Currently, OpenOffice does a better job of rendering documents into PDF and so
- // it is preferred over PDFBox.
- try
- {
- converter.convert(tempFromFile, sourceFormat, tempToFile, targetFormat);
- // conversion success
- }
- catch (OpenOfficeException e)
- {
- throw new ContentIOException("OpenOffice server conversion failed: \n" +
- " reader: " + reader + "\n" +
- " writer: " + writer + "\n" +
- " from file: " + tempFromFile + "\n" +
- " to file: " + tempToFile,
- e);
- }
- }
-
-
- // upload the temp output to the writer given us
- writer.putContent(tempToFile);
- }
-
- /**
- * This method produces an empty PDF file at the specified File location.
- * Apache's PDFBox is used to create the PDF file.
- */
- private void produceEmptyPdfFile(File tempToFile)
- {
- // If improvement PDFBOX-914 is incorporated, we can do this with a straight call to
- // org.apache.pdfbox.TextToPdf.createPDFFromText(new StringReader(""));
- // https://issues.apache.org/jira/browse/PDFBOX-914
-
- PDDocument pdfDoc = null;
- PDPageContentStream contentStream = null;
- try
- {
- pdfDoc = new PDDocument();
- PDPage pdfPage = new PDPage();
- // Even though, we want an empty PDF, some libs (e.g. PDFRenderer) object to PDFs
- // that have literally nothing in them. So we'll put a content stream in it.
- contentStream = new PDPageContentStream(pdfDoc, pdfPage);
- pdfDoc.addPage(pdfPage);
-
- // Now write the in-memory PDF document into the temporary file.
- pdfDoc.save(tempToFile.getAbsolutePath());
-
- }
- catch (COSVisitorException cvx)
- {
- throw new ContentIOException("Error creating empty PDF file", cvx);
- }
- catch (IOException iox)
- {
- throw new ContentIOException("Error creating empty PDF file", iox);
- }
- finally
- {
- if (contentStream != null)
- {
- try
- {
- contentStream.close();
- }
- catch (IOException ignored)
- {
- // Intentionally empty
- }
- }
- if (pdfDoc != null)
- {
- try
- {
- pdfDoc.close();
- }
- catch (IOException ignored)
- {
- // Intentionally empty.
- }
- }
- }
- }
-
- /*
- * (non-Javadoc)
- * @see org.alfresco.repo.content.transform.ContentTransformerWorker#getVersionString()
- */
- public String getVersionString()
- {
- // Actual version information owned by OpenOfficeConnectionTester
- return "";
+ super.saveContentInFile(sourceMimetype, reader, file);
}
}
diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
index f6797b4d97..edb558f05d 100644
--- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
+++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java
@@ -19,6 +19,8 @@
package org.alfresco.repo.descriptor;
import java.lang.reflect.Constructor;
+import java.util.HashSet;
+import java.util.Set;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
@@ -240,6 +242,10 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean
// Load heart-beat special service (even if disabled at the moment)
heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat");
+ for(LicenseChangeHandler handler : deferredHandlers)
+ {
+ licenseService.registerOnLicenseChange(handler);
+ }
// Now listen for future license changes
licenseService.registerOnLicenseChange(this);
@@ -671,4 +677,19 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean
LicenseMode.UNKNOWN);
}
}
+
+
+ Set deferredHandlers = new HashSet();
+ @Override
+ public void registerOnLicenseChange(LicenseChangeHandler callback)
+ {
+ if(licenseService != null)
+ {
+ licenseService.registerOnLicenseChange(callback);
+ }
+ else
+ {
+ deferredHandlers.add(callback);
+ }
+ }
}
diff --git a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
index 51b0db52b4..3ec0e7b6c7 100644
--- a/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
+++ b/source/java/org/alfresco/repo/imap/AlfrescoImapFolder.java
@@ -771,7 +771,7 @@ public class AlfrescoImapFolder extends AbstractImapFolder implements Serializab
if (extractAttachmentsEnabled)
{
- imapService.extractAttachments(folderFileInfo.getNodeRef(), messageFile.getNodeRef(), message);
+ imapService.extractAttachments(messageFile.getNodeRef(), message);
}
// Force persistence of the message to the repository
new IncomingImapMessage(messageFile, serviceRegistry, message);
diff --git a/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java b/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java
new file mode 100644
index 0000000000..892a0a5a6f
--- /dev/null
+++ b/source/java/org/alfresco/repo/imap/AttachmentsExtractor.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2005-2010 Alfresco Software Limited.
+ *
+ * This file is part of Alfresco
+ *
+ * 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 .
+ */
+package org.alfresco.repo.imap;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.ContentType;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeUtility;
+
+import org.alfresco.model.ContentModel;
+import org.alfresco.model.ImapModel;
+import org.alfresco.repo.security.authentication.AuthenticationUtil;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.model.FileFolderService;
+import org.alfresco.service.cmr.model.FileInfo;
+import org.alfresco.service.cmr.repository.ContentWriter;
+import org.alfresco.service.cmr.repository.NodeRef;
+import org.alfresco.service.cmr.repository.NodeService;
+import org.alfresco.service.cmr.security.PermissionService;
+import org.alfresco.util.config.RepositoryFolderConfigBean;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.FileCopyUtils;
+
+/**
+ * Extract attachments according to provided AttachmentsExtractorMode
+ *
+ * @since 3.4.7
+ */
+public class AttachmentsExtractor
+{
+ public static enum AttachmentsExtractorMode
+ {
+ /**
+ * Attachments will be extracted to the same folder where email lies.
+ */
+ SAME,
+ /**
+ * All attachments for all emails will be extracted to one folder.
+ */
+ COMMON,
+ /**
+ * All attachments for each email will be extracted to separate folder.
+ */
+ SEPARATE
+ }
+
+ private Log logger = LogFactory.getLog(AttachmentsExtractor.class);
+
+ private FileFolderService fileFolderService;
+ private NodeService nodeService;
+ private ServiceRegistry serviceRegistry;
+ private RepositoryFolderConfigBean attachmentsFolder;
+ private NodeRef attachmentsFolderRef;
+ private AttachmentsExtractorMode attachmentsExtractorMode;
+
+ public void setFileFolderService(FileFolderService fileFolderService)
+ {
+ this.fileFolderService = fileFolderService;
+ }
+
+ public void setNodeService(NodeService nodeService)
+ {
+ this.nodeService = nodeService;
+ }
+
+ public void setAttachmentsFolder(RepositoryFolderConfigBean attachmentsFolder)
+ {
+ this.attachmentsFolder = attachmentsFolder;
+ }
+
+ public void setServiceRegistry(ServiceRegistry serviceRegistry)
+ {
+ this.serviceRegistry = serviceRegistry;
+ }
+
+ public void setAttachmentsExtractorMode(String attachmentsExtractorMode)
+ {
+ this.attachmentsExtractorMode = AttachmentsExtractorMode.valueOf(attachmentsExtractorMode);
+ }
+
+ public void init()
+ {
+ attachmentsFolderRef = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork()
+ {
+ public NodeRef doWork() throws Exception
+ {
+ NodeRef attFolderRef = attachmentsFolder.getOrCreateFolderPath(serviceRegistry.getNamespaceService(), nodeService, serviceRegistry.getSearchService(), fileFolderService);
+ serviceRegistry.getPermissionService().setPermission(attFolderRef , PermissionService.ALL_AUTHORITIES, PermissionService.FULL_CONTROL, true);
+ return attFolderRef;
+ }
+ }, AuthenticationUtil.getSystemUserName());
+ }
+
+ public void extractAttachments(NodeRef messageRef, MimeMessage originalMessage) throws IOException, MessagingException
+ {
+ NodeRef attachmentsFolderRef = null;
+ switch (attachmentsExtractorMode)
+ {
+ case SAME:
+ attachmentsFolderRef = nodeService.getPrimaryParent(messageRef).getParentRef();
+ break;
+ case COMMON:
+ attachmentsFolderRef = this.attachmentsFolderRef;
+ break;
+ case SEPARATE:
+ default:
+ NodeRef parentFolder = nodeService.getPrimaryParent(messageRef).getParentRef();
+ String messageName = (String) nodeService.getProperty(messageRef, ContentModel.PROP_NAME);
+ String attachmentsFolderName = messageName + "-attachments";
+ attachmentsFolderRef = fileFolderService.create(parentFolder, attachmentsFolderName, ContentModel.TYPE_FOLDER).getNodeRef();
+ break;
+ }
+
+ nodeService.createAssociation(messageRef, attachmentsFolderRef, ImapModel.ASSOC_IMAP_ATTACHMENTS_FOLDER);
+
+ Object content = originalMessage.getContent();
+ if (content instanceof Multipart)
+ {
+ Multipart multipart = (Multipart) content;
+
+ for (int i = 0, n = multipart.getCount(); i < n; i++)
+ {
+ Part part = multipart.getBodyPart(i);
+ if ("attachment".equalsIgnoreCase(part.getDisposition()))
+ {
+ createAttachment(messageRef, attachmentsFolderRef, part);
+ }
+ }
+ }
+
+ }
+
+ private void createAttachment(NodeRef messageFile, NodeRef attachmentsFolderRef, Part part) throws MessagingException, IOException
+ {
+ String fileName = part.getFileName();
+ try
+ {
+ fileName = MimeUtility.decodeText(fileName);
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ if (logger.isWarnEnabled())
+ {
+ logger.warn("Cannot decode file name '" + fileName + "'", e);
+ }
+ }
+
+ ContentType contentType = new ContentType(part.getContentType());
+ NodeRef attachmentFile = fileFolderService.searchSimple(attachmentsFolderRef, fileName);
+ // The one possible behaviour
+ /*
+ if (result.size() > 0)
+ {
+ for (FileInfo fi : result)
+ {
+ fileFolderService.delete(fi.getNodeRef());
+ }
+ }
+ */
+ // And another one behaviour which will overwrite the content of the existing file. It is performance preferable.
+ if (attachmentFile == null)
+ {
+ FileInfo createdFile = fileFolderService.create(attachmentsFolderRef, fileName, ContentModel.TYPE_CONTENT);
+ nodeService.createAssociation(messageFile, createdFile.getNodeRef(), ImapModel.ASSOC_IMAP_ATTACHMENT);
+ attachmentFile = createdFile.getNodeRef();
+ }
+ else
+ {
+ String name = fileName;
+ String ext = "";
+ if (fileName.lastIndexOf(".") != -1)
+ {
+ int index = fileName.lastIndexOf(".");
+ name = fileName.substring(0, index);
+ ext = fileName.substring(index);
+
+ }
+
+ int copyNum = 0;
+ do
+ {
+ copyNum++;
+ } while (fileFolderService.searchSimple(attachmentsFolderRef, name + " (" + copyNum + ")" + ext) != null);
+
+ FileInfo createdFile = fileFolderService.create(attachmentsFolderRef, name + " (" + copyNum + ")" + ext, ContentModel.TYPE_CONTENT);
+ nodeService.createAssociation(messageFile, createdFile.getNodeRef(), ImapModel.ASSOC_IMAP_ATTACHMENT);
+ attachmentFile = createdFile.getNodeRef();
+
+ }
+
+ nodeService.setProperty(attachmentFile, ContentModel.PROP_DESCRIPTION, nodeService.getProperty(messageFile, ContentModel.PROP_NAME));
+
+ ContentWriter writer = fileFolderService.getWriter(attachmentFile);
+ writer.setMimetype(contentType.getBaseType());
+ OutputStream os = writer.getContentOutputStream();
+ FileCopyUtils.copy(part.getInputStream(), os);
+ }
+
+}
\ No newline at end of file
diff --git a/source/java/org/alfresco/repo/imap/ImapService.java b/source/java/org/alfresco/repo/imap/ImapService.java
index 4a666bc795..30ebddc27a 100644
--- a/source/java/org/alfresco/repo/imap/ImapService.java
+++ b/source/java/org/alfresco/repo/imap/ImapService.java
@@ -305,6 +305,16 @@ public interface ImapService
*/
public boolean getImapServerEnabled();
+ /**
+ * Extract attachments from message.
+ *
+ * @param messageRef nodeRef that represents message in Alfresco.
+ * @param originalMessage original message in eml format.
+ * @throws IOException
+ * @throws MessagingException
+ */
+ public void extractAttachments(NodeRef messageRef, MimeMessage originalMessage) throws IOException, MessagingException;
+
static class FolderStatus
{
public final int messageCount;
diff --git a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
index 849dcacc24..a7a826598a 100644
--- a/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
+++ b/source/java/org/alfresco/repo/imap/ImapServiceImpl.java
@@ -146,6 +146,7 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
private MimetypeService mimetypeService;
private NamespaceService namespaceService;
private SearchService searchService;
+ private AttachmentsExtractor attachmentsExtractor;
// Note that this cache need not be cluster synchronized, as it is keyed by the cluster-safe
// change token. Key is username, changeToken
@@ -271,6 +272,12 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
this.policyBehaviourFilter = policyFilter;
}
+
+ public void setAttachmentsExtractor(AttachmentsExtractor attachmentsExtractor)
+ {
+ this.attachmentsExtractor = attachmentsExtractor;
+ }
+
public void setImapHome(RepositoryFolderConfigBean imapHomeConfigBean)
{
this.imapHomeConfigBean = imapHomeConfigBean;
@@ -2091,6 +2098,11 @@ public class ImapServiceImpl implements ImapService, OnCreateChildAssociationPol
return searchService;
}
+ public void extractAttachments(NodeRef messageRef, MimeMessage originalMessage) throws IOException, MessagingException
+ {
+ attachmentsExtractor.extractAttachments(messageRef, originalMessage);
+ }
+
static class CacheItem
{
private Date modified;
diff --git a/source/java/org/alfresco/repo/lock/LockServiceImpl.java b/source/java/org/alfresco/repo/lock/LockServiceImpl.java
index c4102635fc..3ef4961a80 100644
--- a/source/java/org/alfresco/repo/lock/LockServiceImpl.java
+++ b/source/java/org/alfresco/repo/lock/LockServiceImpl.java
@@ -35,6 +35,7 @@ import org.alfresco.repo.copy.CopyDetails;
import org.alfresco.repo.copy.CopyServicePolicies;
import org.alfresco.repo.copy.DefaultCopyBehaviourCallback;
import org.alfresco.repo.node.NodeServicePolicies;
+import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.policy.JavaBehaviour;
import org.alfresco.repo.policy.PolicyComponent;
import org.alfresco.repo.policy.PolicyScope;
@@ -57,6 +58,7 @@ import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.namespace.QName;
+import org.alfresco.util.PropertyCheck;
/**
* Simple Lock service implementation
@@ -79,6 +81,7 @@ public class LockServiceImpl implements LockService,
private TenantService tenantService;
private AuthenticationService authenticationService;
private SearchService searchService;
+ private BehaviourFilter behaviourFilter;
private PolicyComponent policyComponent;
@@ -112,6 +115,13 @@ public class LockServiceImpl implements LockService,
*/
public void init()
{
+ PropertyCheck.mandatory(this, "nodeService", nodeService);
+ PropertyCheck.mandatory(this, "tenantService", tenantService);
+ PropertyCheck.mandatory(this, "authenticationService", authenticationService);
+ PropertyCheck.mandatory(this, "searchService", searchService);
+ PropertyCheck.mandatory(this, "behaviourFilter", behaviourFilter);
+ PropertyCheck.mandatory(this, "policyComponent", policyComponent);
+
// Register the various class behaviours to enable lock checking
this.policyComponent.bindAssociationBehaviour(
NodeServicePolicies.OnCreateChildAssociationPolicy.QNAME,
@@ -649,4 +659,26 @@ public class LockServiceImpl implements LockService,
NodeRef nodeRef = oldChildAssocRef.getChildRef();
checkForLock(nodeRef);
}
+
+ @Override
+ public void suspendLocks()
+ {
+ getBehaviourFilter().disableBehaviour(ContentModel.ASPECT_LOCKABLE);
+ }
+
+ @Override
+ public void enableLocks()
+ {
+ getBehaviourFilter().enableBehaviour(ContentModel.ASPECT_LOCKABLE);
+ }
+
+ public void setBehaviourFilter(BehaviourFilter behaviourFilter)
+ {
+ this.behaviourFilter = behaviourFilter;
+ }
+
+ public BehaviourFilter getBehaviourFilter()
+ {
+ return behaviourFilter;
+ }
}
diff --git a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
index 6819d48fa1..cd83b4459e 100644
--- a/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/AbstractNodeServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -32,6 +32,7 @@ import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateStorePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteChildAssociationPolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy;
+import org.alfresco.repo.node.NodeServicePolicies.BeforeMoveNodePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeRemoveAspectPolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeSetNodeTypePolicy;
import org.alfresco.repo.node.NodeServicePolicies.BeforeUpdateNodePolicy;
@@ -108,6 +109,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService
private ClassPolicyDelegate onCreateStoreDelegate;
private ClassPolicyDelegate beforeCreateNodeDelegate;
private ClassPolicyDelegate onCreateNodeDelegate;
+ private ClassPolicyDelegate beforeMoveNodeDelegate;
private ClassPolicyDelegate onMoveNodeDelegate;
private ClassPolicyDelegate beforeUpdateNodeDelegate;
private ClassPolicyDelegate onUpdateNodeDelegate;
@@ -198,6 +200,7 @@ public abstract class AbstractNodeServiceImpl implements NodeService
onCreateStoreDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateStorePolicy.class);
beforeCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeCreateNodePolicy.class);
onCreateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnCreateNodePolicy.class);
+ beforeMoveNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeMoveNodePolicy.class);
onMoveNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnMoveNodePolicy.class);
beforeUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeUpdateNodePolicy.class);
onUpdateNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnUpdateNodePolicy.class);
@@ -207,18 +210,18 @@ public abstract class AbstractNodeServiceImpl implements NodeService
beforeDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeDeleteNodePolicy.class);
onDeleteNodeDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnDeleteNodePolicy.class);
onRestoreNodePolicy = policyComponent.registerClassPolicy(NodeServicePolicies.OnRestoreNodePolicy.class);
-
+
beforeAddAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeAddAspectPolicy.class);
onAddAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnAddAspectPolicy.class);
beforeRemoveAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.BeforeRemoveAspectPolicy.class);
onRemoveAspectDelegate = policyComponent.registerClassPolicy(NodeServicePolicies.OnRemoveAspectPolicy.class);
-
+
beforeCreateNodeAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeCreateNodeAssociationPolicy.class);
onCreateNodeAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateNodeAssociationPolicy.class);
onCreateChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateChildAssociationPolicy.class);
beforeDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.BeforeDeleteChildAssociationPolicy.class);
onDeleteChildAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteChildAssociationPolicy.class);
-
+
onCreateAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnCreateAssociationPolicy.class);
onDeleteAssociationDelegate = policyComponent.registerAssociationPolicy(NodeServicePolicies.OnDeleteAssociationPolicy.class);
}
@@ -299,7 +302,26 @@ public abstract class AbstractNodeServiceImpl implements NodeService
NodeServicePolicies.OnCreateNodePolicy policy = onCreateNodeDelegate.get(childNodeRef, qnames);
policy.onCreateNode(childAssocRef);
}
-
+
+ /**
+ * @see NodeServicePolicies.BeforeMoveNodePolicy#onMoveNode(ChildAssociationRef, NodeRef)
+ */
+ protected void invokeBeforeMoveNode(ChildAssociationRef oldChildAssocRef, NodeRef newParentRef)
+ {
+ NodeRef childNodeRef = oldChildAssocRef.getChildRef();
+
+ if (ignorePolicy(childNodeRef))
+ {
+ return;
+ }
+
+ // get qnames to invoke against
+ Set qnames = getTypeAndAspectQNames(childNodeRef);
+ // execute policy for node type and aspects
+ NodeServicePolicies.BeforeMoveNodePolicy policy = beforeMoveNodeDelegate.get(childNodeRef, qnames);
+ policy.beforeMoveNode(oldChildAssocRef, newParentRef);
+ }
+
/**
* @see NodeServicePolicies.OnMoveNodePolicy#onMoveNode(ChildAssociationRef, ChildAssociationRef)
*/
diff --git a/source/java/org/alfresco/repo/node/NodeServicePolicies.java b/source/java/org/alfresco/repo/node/NodeServicePolicies.java
index 06bb39ea0a..d9891502c6 100644
--- a/source/java/org/alfresco/repo/node/NodeServicePolicies.java
+++ b/source/java/org/alfresco/repo/node/NodeServicePolicies.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -88,7 +88,21 @@ public interface NodeServicePolicies
*/
public void onCreateNode(ChildAssociationRef childAssocRef);
}
-
+
+ public interface BeforeMoveNodePolicy extends ClassPolicy
+ {
+ public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "beforeMoveNode");
+ /**
+ * Called before a node is moved.
+ *
+ * @param oldChildAssocRef the child association reference prior to the move
+ * @param newParentRef the new parent node reference
+ *
+ * @since 4.1
+ */
+ public void beforeMoveNode(ChildAssociationRef oldChildAssocRef, NodeRef newParentRef);
+ }
+
public interface OnMoveNodePolicy extends ClassPolicy
{
public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode");
diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
index 61155d9d0b..0a5e430544 100644
--- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
+++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -2394,15 +2394,19 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
{
// remove the deleted node from the list of new nodes
untrackNewNodeRef(nodeToMoveRef);
-
+
// track the deletion of this node - so we can prevent new associations to it.
trackDeletedNodeRef(nodeToMoveRef);
+ // The Node changes NodeRefs, so this is really the deletion of the old node and creation
+ // of a node in a new store as far as the clients are concerned.
+
invokeBeforeDeleteNode(nodeToMoveRef);
invokeBeforeCreateNode(newParentRef, assocTypeQName, assocQName, nodeToMoveTypeQName);
}
else
{
+ invokeBeforeMoveNode(oldParentAssocRef, newParentRef);
invokeBeforeDeleteChildAssociation(oldParentAssocRef);
}
@@ -2435,7 +2439,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl
// Propagate timestamps
propagateTimeStamps(oldParentAssocRef);
propagateTimeStamps(newParentAssocRef);
-
+
// The Node changes NodeRefs, so this is really the deletion of the old node and creation
// of a node in a new store as far as the clients are concerned.
invokeOnDeleteNode(oldParentAssocRef, nodeToMoveTypeQName, nodeToMoveAspectQNames, true);
diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java
index 12d234daf0..c672f79e70 100644
--- a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java
+++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java
@@ -124,13 +124,22 @@ public class RemoteConnectorServiceImpl implements RemoteConnectorService
String responseCharSet = httpRequest.getResponseCharSet();
String responseContentType = (responseContentTypeH != null ? responseContentTypeH.getValue() : null);
+ if(logger.isDebugEnabled())
+ {
+ logger.debug("response url=" + request.getURL() + ", length =" + httpRequest.getResponseContentLength() + ", responceContentType " + responseContentType + ", statusText =" + statusText );
+ }
// Decide on how best to handle the response, based on the size
// Ideally, we want to close the HttpClient resources immediately, but
// that isn't possible for very large responses
// If we can close immediately, it makes cleanup simpler and fool-proof
- if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE)
+ if (httpRequest.getResponseContentLength() > MAX_BUFFER_RESPONSE_SIZE || httpRequest.getResponseContentLength() == -1 )
{
+ if(logger.isTraceEnabled())
+ {
+ logger.trace("large response (or don't know length) url=" + request.getURL());
+ }
+
// Need to wrap the InputStream in something that'll close
InputStream wrappedStream = new HttpClientReleasingInputStream(httpRequest);
httpRequest = null;
@@ -141,6 +150,10 @@ public class RemoteConnectorServiceImpl implements RemoteConnectorService
}
else
{
+ if(logger.isTraceEnabled())
+ {
+ logger.debug("small response for url=" + request.getURL());
+ }
// Fairly small response, just keep the bytes and make life simple
response = new RemoteConnectorResponseImpl(request, responseContentType, responseCharSet,
status, responseHdrs, httpRequest.getResponseBody());
@@ -185,6 +198,10 @@ public class RemoteConnectorServiceImpl implements RemoteConnectorService
{
throw new RemoteConnectorServerException(status, statusText);
}
+ if(status == Status.STATUS_PRECONDITION_FAILED)
+ {
+ throw new RemoteConnectorClientException(status, statusText, response);
+ }
else
{
// Client request exceptions
diff --git a/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java
index efad24ff1d..fd0aa17b1e 100644
--- a/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java
+++ b/source/java/org/alfresco/repo/remotecredentials/PasswordCredentialsInfoImpl.java
@@ -55,4 +55,10 @@ public class PasswordCredentialsInfoImpl extends AbstractCredentialsImpl impleme
{
remotePassword = password;
}
+
+ public String toString()
+ {
+ return "Username & Password Credentials, Username="+getRemoteUsername()+" for System="+
+ getRemoteSystemName() + " stored @ " + getNodeRef();
+ }
}
diff --git a/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java b/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java
index ff544798b5..f8bda24d65 100644
--- a/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java
+++ b/source/java/org/alfresco/repo/remoteticket/AlfTicketRemoteAlfrescoTicketImpl.java
@@ -67,4 +67,9 @@ public class AlfTicketRemoteAlfrescoTicketImpl extends AbstractRemoteAlfrescoTic
{
return new Pair(TICKET_USERNAME, ticket);
}
+
+ public String toString()
+ {
+ return "Remote Alfresco Ticket: " + ticket;
+ }
}
diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java
index 9822894834..239b9031ab 100644
--- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java
+++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java
@@ -1116,7 +1116,13 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic
if (visibilityValue == null)
{
// Examine each permission to see if this is a public site or not
- Set permissions = this.permissionService.getAllSetPermissions(siteNodeRef);
+ Set permissions;
+ try {
+ permissions = this.permissionService.getAllSetPermissions(siteNodeRef);
+ } catch (AccessDeniedException ae){
+ // We might not have permission to examine the permissions
+ return visibility;
+ }
for (AccessPermission permission : permissions)
{
if (permission.getAuthority().equals(PermissionService.ALL_AUTHORITIES) == true &&
diff --git a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java
index 487663a0ec..508e8a2966 100644
--- a/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java
+++ b/source/java/org/alfresco/repo/tenant/MultiTNodeServiceInterceptor.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2005-2010 Alfresco Software Limited.
+ * Copyright (C) 2005-2012 Alfresco Software Limited.
*
* This file is part of Alfresco
*
@@ -31,6 +31,7 @@ import org.alfresco.model.ContentModel;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ChildAssociationRef;
+import org.alfresco.service.cmr.repository.InvalidNodeRefException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.Path;
import org.alfresco.service.cmr.repository.StoreRef;
@@ -134,7 +135,15 @@ public class MultiTNodeServiceInterceptor extends DelegatingIntroductionIntercep
}
// Make the call
- Object ret = invocation.proceed();
+ Object ret = null;
+ try
+ {
+ ret = invocation.proceed();
+ }
+ catch (InvalidNodeRefException inre)
+ {
+ throw new InvalidNodeRefException(inre.getMessage(), tenantService.getBaseName(inre.getNodeRef()));
+ }
if (methodName.equals("getProperty"))
{
@@ -246,7 +255,6 @@ public class MultiTNodeServiceInterceptor extends DelegatingIntroductionIntercep
/**
* Convert outbound collection to spoofed (ie. without tenant prefix) values.
*/
- @SuppressWarnings("unchecked")
private Collection