diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 9bca7d1851..008d58fc68 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -652,6 +652,7 @@ + diff --git a/config/alfresco/ibatis/activities-SqlMapConfig.xml b/config/alfresco/ibatis/activities-SqlMapConfig.xml index 33614e0544..009008dae4 100644 --- a/config/alfresco/ibatis/activities-SqlMapConfig.xml +++ b/config/alfresco/ibatis/activities-SqlMapConfig.xml @@ -13,6 +13,7 @@ + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml index 75546114f9..87cadd217b 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml @@ -78,233 +78,35 @@ values (#{id}, #{status}, #{activityData}, #{userId}, #{postDate}, #{activityType}, #{siteNetwork,jdbcType=VARCHAR}, #{appTool,jdbcType=VARCHAR}, #{jobTaskNode}, #{lastModified}) - - - - - - + select max(id) from alf_activity_feed + + + + - - + = #{minId} ]]> - order by post_date desc - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - delete from alf_activity_feed - where site_network = #{siteNetwork} - - - - - - - - delete from alf_activity_feed - where feed_user_id = #{feedUserId} - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + delete from alf_activity_feed + where site_network = #{siteNetwork} + + + + + + + + delete from alf_activity_feed + where feed_user_id = #{feedUserId} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-common-SqlMap.xml deleted file mode 100644 index 8d83323135..0000000000 --- a/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-common-SqlMap.xml +++ /dev/null @@ -1,393 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - insert into alf_activity_feed_control (feed_user_id, site_network, app_tool, last_modified) - values (#{feedUserId}, #{siteNetwork}, #{appTool}, #{lastModified}) - - - - insert into alf_activity_feed_control (id, feed_user_id, site_network, app_tool, last_modified) - values (#{id}, #{feedUserId}, #{siteNetwork,jdbcType=VARCHAR}, #{appTool,jdbcType=VARCHAR}, #{lastModified}) - - - - insert into alf_activity_feed (activity_type, activity_summary, activity_format, feed_user_id, post_user_id, post_date, post_id, site_network, app_tool, feed_date) - values (#{activityType}, #{activitySummary}, #{activitySummaryFormat}, #{feedUserId}, #{postUserId}, #{postDate}, #{postId}, #{siteNetwork}, #{appTool}, #{feedDate}) - - - - insert into alf_activity_feed (id, activity_type, activity_summary, activity_format, feed_user_id, post_user_id, post_date, post_id, site_network, app_tool, feed_date) - values (#{id}, #{activityType}, #{activitySummary,jdbcType=VARCHAR}, #{activitySummaryFormat,jdbcType=VARCHAR}, #{feedUserId,jdbcType=VARCHAR}, #{postUserId}, #{postDate}, #{postId,jdbcType=BIGINT}, #{siteNetwork,jdbcType=VARCHAR}, #{appTool,jdbcType=VARCHAR}, #{feedDate}) - - - - insert into alf_activity_post (status, activity_data, post_user_id, post_date, activity_type, site_network, app_tool, job_task_node, last_modified) - values (#{status}, #{activityData}, #{userId}, #{postDate}, #{activityType}, #{siteNetwork}, #{appTool}, #{jobTaskNode}, #{lastModified}) - - - - insert into alf_activity_post (sequence_id, status, activity_data, post_user_id, post_date, activity_type, site_network, app_tool, job_task_node, last_modified) - values (#{id}, #{status}, #{activityData}, #{userId}, #{postDate}, #{activityType}, #{siteNetwork,jdbcType=VARCHAR}, #{appTool,jdbcType=VARCHAR}, #{jobTaskNode}, #{lastModified}) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - delete from alf_activity_feed - where site_network = #{siteNetwork} - - - - - - - - delete from alf_activity_feed - where feed_user_id = #{feedUserId} - - - - - - - - - - - - - - - - - - - - - - - - - - #{status} - ]]> - - - - #{status} - ]]> - - - \ No newline at end of file diff --git a/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-select-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-select-SqlMap.xml new file mode 100644 index 0000000000..986bb45fff --- /dev/null +++ b/config/alfresco/ibatis/org.hibernate.dialect.MySQLInnoDBDialect/activities-select-SqlMap.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 89fb8e1ff4..abb15595c2 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -774,6 +774,8 @@ imap.attachments.folder.folderPath=Imap Attachments # Activities Feed - refer to subsystem +# Feed max ID range to limit maximum number of entries +activities.feed.max.idRange=1000000 # Feed max size (number of entries) activities.feed.max.size=100 # Feed max age (eg. 44640 mins => 31 days) diff --git a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml index c816d437b7..28db5aec44 100644 --- a/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml +++ b/config/alfresco/subsystems/ActivitiesFeed/default/activities-feed-context.xml @@ -28,10 +28,13 @@ - + + + ${activities.feed.max.idRange} + ${activities.feed.max.ageMins} diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java index be29b8859a..c83518c218 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java @@ -44,7 +44,7 @@ public class AlfrescoCmisServiceInterceptor implements MethodInterceptor } @Override - public synchronized Object invoke(MethodInvocation invocation) throws Throwable + public Object invoke(MethodInvocation invocation) throws Throwable { // Keep note of whether debug is required boolean debug = logger.isDebugEnabled(); diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java index 4a333da1d4..b706b492cd 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleaner.java @@ -19,28 +19,30 @@ package org.alfresco.repo.activities.feed.cleanup; import java.sql.SQLException; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.lock.JobLockService.JobLockRefreshCallback; +import org.alfresco.repo.lock.LockAcquisitionException; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.site.SiteModel; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.repo.transaction.TransactionalResourceHelper; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; -import org.alfresco.util.VmShutdownListener; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.quartz.JobExecutionException; @@ -55,15 +57,13 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy private static String KEY_DELETED_SITE_IDS = "feedCleaner.deletedSites"; private static String KEY_DELETED_USER_IDS = "feedCleaner.deletedUsers"; - private static VmShutdownListener vmShutdownListener = new VmShutdownListener(FeedCleaner.class.getName()); - + private int maxIdRange = 1000000; private int maxAgeMins = 0; - - private int maxFeedSize = -1; //unlimited + private int maxFeedSize = 100; private ActivityFeedDAO feedDAO; - private SiteService siteService; + private JobLockService jobLockService; private NodeService nodeService; private PolicyComponent policyComponent; @@ -76,11 +76,11 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy this.feedDAO = feedDAO; } - public void setSiteService(SiteService siteService) + public void setJobLockService(JobLockService jobLockService) { - this.siteService = siteService; + this.jobLockService = jobLockService; } - + public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -90,7 +90,16 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy { this.policyComponent = policyComponent; } - + + /** + * + * @param maxIdRange maximum difference between lowest and highest ID + */ + public void setMaxIdRange(int maxIdRange) + { + this.maxIdRange = maxIdRange; + } + public void setMaxAgeMins(int mins) { this.maxAgeMins = mins; @@ -114,6 +123,9 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy private void checkProperties() { PropertyCheck.mandatory(this, "feedDAO", feedDAO); + PropertyCheck.mandatory(this, "policyComponent", policyComponent); + PropertyCheck.mandatory(this, "nodeService", nodeService); + PropertyCheck.mandatory(this, "jobLockService", jobLockService); // check the max age and max feed size if ((maxAgeMins <= 0) && (maxFeedSize <= 0)) @@ -137,16 +149,97 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy deleteSiteTransactionListener = new FeedCleanerDeleteSiteTransactionListener(); } + private static final long LOCK_TTL = 60000L; // 1 minute + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "FeedCleaner"); public int execute() throws JobExecutionException { checkProperties(); + final AtomicBoolean keepGoing = new AtomicBoolean(true); + String lockToken = null; + try + { + // Lock + lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); + // Refresh to get callbacks + JobLockRefreshCallback callback = new JobLockRefreshCallback() + { + @Override + public void lockReleased() + { + keepGoing.set(false); + } + + @Override + public boolean isActive() + { + return keepGoing.get(); + } + }; + jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL, callback); + int cleaned = executeWithLock(keepGoing); + if (logger.isDebugEnabled()) + { + logger.debug("Cleaned " + cleaned + " feed entries."); + } + } + catch (LockAcquisitionException e) + { + if (logger.isDebugEnabled()) + { + logger.debug("Skipping feed cleaning. " + e.getMessage()); + } + } + finally + { + keepGoing.set(false); // Notify the refresh callback that we are done + if (lockToken != null) + { + jobLockService.releaseLock(lockToken, LOCK_QNAME); + } + } + return 0; + } + + /** + * Does the actual cleanup, expecting the lock to be maintained + * + * @param keepGoing true to continue but will switch to false to stop + * @return number of entries deleted through whatever means + */ + private int executeWithLock(final AtomicBoolean keepGoing) throws JobExecutionException + { + int maxIdRangeDeletedCount = 0; int maxAgeDeletedCount = 0; int maxSizeDeletedCount = 0; try { - if (maxAgeMins > 0) + /* + * ALF-15383 (DH 15/08/2012) + * Previously, we allowed maxFeedSize entries per user per site per format. + * This scaled badly because some users (especially under test conditions) + * were able to perform actions across many thousands of sites. If the size + * limit was 100 and the user belonged to 50K sites, we allowed 5M feed entries + * for that user. This may have been OK but for the fact that the queries + * doing the work are not covered by appropriate indexes to support the where + * and sort by clauses. + * In fact, give the current state of indexes, it is necessary to limit the absolute + * number of feed entries. We can't use count() queries (they are poor) and cannot + * reasonably sort by date and trim by count. Therefore I have introduced an + * absolute ID range trim that runs before everything else. + */ + + if (maxIdRange > 0 && keepGoing.get()) + { + maxIdRangeDeletedCount = feedDAO.deleteFeedEntries(maxIdRange); + if (logger.isTraceEnabled()) + { + logger.trace("Cleaned " + maxIdRangeDeletedCount + " entries to keep ID range of " + maxIdRange + "."); + } + } + + if (maxAgeMins > 0 && keepGoing.get()) { // clean old entries based on maxAgeMins @@ -155,114 +248,73 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy Date keepDate = new Date(keepTimeOffset); maxAgeDeletedCount = feedDAO.deleteFeedEntries(keepDate); - - if (maxAgeDeletedCount > 0) + if (logger.isTraceEnabled()) { - if (logger.isDebugEnabled()) - { - logger.debug("Cleaned " + maxAgeDeletedCount + " entries (upto " + keepDate + ", max age " + maxAgeMins + " mins)"); - } - } - else - { - if (logger.isTraceEnabled()) - { - logger.trace("Cleaned " + maxAgeDeletedCount + " entries (upto " + keepDate + ", max age " + maxAgeMins + " mins)"); - } + logger.trace("Cleaned " + maxAgeDeletedCount + " entries (upto " + keepDate + ", max age " + maxAgeMins + " mins)"); } } - if (maxFeedSize > 0) + // TODO: ALF-15511 + if (maxFeedSize > 0 && keepGoing.get()) { - // clean old entries based on maxFeedSize - - // return candidate feeds to clean - either site+format or user+format - List feeds = feedDAO.selectFeedsToClean(maxFeedSize); - - int feedCount = 0; - - for (ActivityFeedEntity feed : feeds) + // Get user+format feeds exceeding the required maximum + List userFeedsTooMany = feedDAO.selectUserFeedsToClean(maxFeedSize); + for (ActivityFeedEntity userFeedTooMany : userFeedsTooMany) { - String siteId = feed.getSiteNetwork(); - final String feedUserId = feed.getFeedUserId(); - String format = feed.getActivitySummaryFormat(); - - List feedToClean; - - int feedUserSiteCount = 0; - long numFeeds; - - if ((feedUserId == null) || (feedUserId.length() == 0)) + if (!keepGoing.get()) { - numFeeds = feedDAO.countSiteFeedEntries(siteId, format, -1); + break; } - else + String feedUserId = userFeedTooMany.getFeedUserId(); + String format = userFeedTooMany.getActivitySummaryFormat(); + // Rather than filter out the two usernames that indicate site-specific + // feed entries, we can just filter them out now. + if (feedUserId == null || feedUserId.length() == 0) { - numFeeds = feedDAO.countUserFeedEntries(feedUserId, format, null, false, false, -1L, -1); - - if(siteService != null) - { - // note: allow for fact that Share Activities dashlet currently uses userfeed within site context - feedUserSiteCount = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() - { - public Integer doWork() throws Exception - { - return siteService.listSites(feedUserId).size(); - } - }, AuthenticationUtil.SYSTEM_USER_NAME); - } + continue; } - - if (((feedUserSiteCount == 0) && (numFeeds > maxFeedSize)) || - ((numFeeds > (maxFeedSize * feedUserSiteCount)))) + // Get the feeds to keep + List feedsToKeep = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false, -1L, maxFeedSize); + // If the feeds have been removed, then ignore + if (feedsToKeep.size() < maxFeedSize) { - if ((feedUserId == null) || (feedUserId.length() == 0)) - { - feedToClean = feedDAO.selectSiteFeedEntries(siteId, format, -1); - } - else - { - feedToClean = feedDAO.selectUserFeedEntries(feedUserId, format, null, false, false, -1L, maxFeedSize); - } - - Date oldestFeedEntry = feedToClean.get(maxFeedSize-1).getPostDate(); - int deletedCount = 0; - - if ((feedUserId == null) || (feedUserId.length() == 0)) - { - deletedCount = feedDAO.deleteSiteFeedEntries(siteId, format, oldestFeedEntry); - } - else - { - deletedCount = feedDAO.deleteUserFeedEntries(feedUserId, format, oldestFeedEntry); - } - - if (deletedCount > 0) - { - maxSizeDeletedCount = maxSizeDeletedCount + deletedCount; - feedCount++; - - if (logger.isTraceEnabled()) - { - logger.trace("Cleaned " + deletedCount + " entries for ["+feed.getSiteNetwork()+", "+feed.getFeedUserId()+", "+feed.getActivitySummaryFormat()+"] (upto " + oldestFeedEntry + ")"); - } - } + continue; } - } - - if (maxSizeDeletedCount > 0) - { - if (logger.isDebugEnabled()) - { - logger.debug("Cleaned " + maxSizeDeletedCount + " entries across " + feedCount + " feeds (max feed size "+maxFeedSize+" entries)"); - } - } - else - { + // Get the last one + Date oldestFeedEntry = feedsToKeep.get(maxFeedSize-1).getPostDate(); + int deletedCount = feedDAO.deleteUserFeedEntries(feedUserId, format, oldestFeedEntry); if (logger.isTraceEnabled()) { - logger.trace("Cleaned " + maxSizeDeletedCount + " entries across " + feedCount + " feeds (max feed size "+maxFeedSize+" entries)"); + logger.trace("Cleaned " + deletedCount + " entries for user '" + feedUserId + "'."); } + maxSizeDeletedCount += deletedCount; + } + + // Get site+format feeds exceeding the required maximum + List siteFeedsTooMany = feedDAO.selectSiteFeedsToClean(maxFeedSize); + for (ActivityFeedEntity siteFeedTooMany : siteFeedsTooMany) + { + if (!keepGoing.get()) + { + break; + } + String siteId = siteFeedTooMany.getSiteNetwork(); + String format = siteFeedTooMany.getActivitySummaryFormat(); + // Get the feeds to keep + List feedsToKeep = feedDAO.selectSiteFeedEntries(siteId, format, maxFeedSize); + // If the feeds have been removed, then ignore + if (feedsToKeep.size() < maxFeedSize) + { + continue; + } + // Get the last one + Date oldestFeedEntry = feedsToKeep.get(maxFeedSize-1).getPostDate(); + int deletedCount = feedDAO.deleteSiteFeedEntries(siteId, format, oldestFeedEntry); + if (logger.isTraceEnabled()) + { + logger.trace("Cleaned " + deletedCount + " entries for site '" + siteId + "'."); + } + maxSizeDeletedCount += deletedCount; } } } @@ -273,8 +325,8 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy } catch (Throwable e) { - // If the VM is shutting down, then ignore - if (vmShutdownListener.isVmShuttingDown()) + // We were told to stop, which is also what will happen if the VM shuts down + if (!keepGoing.get()) { // Ignore } @@ -284,7 +336,7 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy } } - return (maxAgeDeletedCount + maxSizeDeletedCount); + return (maxIdRangeDeletedCount + maxAgeDeletedCount + maxSizeDeletedCount); } // behaviours @@ -294,35 +346,21 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy // dummy } - @SuppressWarnings("unchecked") public void beforeDeleteNodePerson(NodeRef personNodeRef) { String userId = (String)nodeService.getProperty(personNodeRef, ContentModel.PROP_USERNAME); - Set deletedUserIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_USER_IDS); - if (deletedUserIds == null) - { - deletedUserIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 - AlfrescoTransactionSupport.bindResource(KEY_DELETED_USER_IDS, deletedUserIds); - } - + Set deletedUserIds = TransactionalResourceHelper.getSet(KEY_DELETED_USER_IDS); deletedUserIds.add(userId); AlfrescoTransactionSupport.bindListener(deletePersonTransactionListener); } - @SuppressWarnings("unchecked") public void beforeDeleteNodeSite(NodeRef siteNodeRef) { String siteId = (String)nodeService.getProperty(siteNodeRef, ContentModel.PROP_NAME); - Set deletedSiteIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_SITE_IDS); - if (deletedSiteIds == null) - { - deletedSiteIds = Collections.newSetFromMap(new ConcurrentHashMap()); // Java 6 - AlfrescoTransactionSupport.bindResource(KEY_DELETED_SITE_IDS, deletedSiteIds); - } - + Set deletedSiteIds = TransactionalResourceHelper.getSet(KEY_DELETED_SITE_IDS); deletedSiteIds.add(siteId); AlfrescoTransactionSupport.bindListener(deleteSiteTransactionListener); @@ -330,24 +368,20 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy class FeedCleanerDeleteSiteTransactionListener extends TransactionListenerAdapter { - @SuppressWarnings("unchecked") @Override public void afterCommit() { - Set deletedSiteIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_SITE_IDS); - if (deletedSiteIds != null) + Set deletedSiteIds = TransactionalResourceHelper.getSet(KEY_DELETED_SITE_IDS); + for (String siteId : deletedSiteIds) { - for (String siteId : deletedSiteIds) + try { - try - { - // Since we are in post-commit, we do best-effort - feedDAO.deleteSiteFeedEntries(siteId); - } - catch (SQLException e) - { - logger.error("Activities feed cleanup for site '"+siteId+"' failed: ", e); - } + // Since we are in post-commit, we do best-effort + feedDAO.deleteSiteFeedEntries(siteId); + } + catch (SQLException e) + { + logger.error("Activities feed cleanup for site '"+siteId+"' failed: ", e); } } } @@ -355,24 +389,20 @@ public class FeedCleaner implements NodeServicePolicies.BeforeDeleteNodePolicy class FeedCleanerDeletePersonTransactionListener extends TransactionListenerAdapter { - @SuppressWarnings("unchecked") @Override public void afterCommit() { - Set deletedUserIds = (Set)AlfrescoTransactionSupport.getResource(KEY_DELETED_USER_IDS); - if (deletedUserIds != null) + Set deletedUserIds = TransactionalResourceHelper.getSet(KEY_DELETED_USER_IDS); + for (String userId : deletedUserIds) { - for (String userId : deletedUserIds) + try { - try - { - // Since we are in post-commit, we do best-effort - feedDAO.deleteUserFeedEntries(userId); - } - catch (SQLException e) - { - logger.error("Activities feed cleanup for user '"+userId+"' failed: ", e); - } + // Since we are in post-commit, we do best-effort + feedDAO.deleteUserFeedEntries(userId); + } + catch (SQLException e) + { + logger.error("Activities feed cleanup for user '"+userId+"' failed: ", e); } } } diff --git a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java index 0cd58c68fe..4e9f579e54 100644 --- a/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java +++ b/source/java/org/alfresco/repo/activities/feed/cleanup/FeedCleanerTest.java @@ -27,8 +27,11 @@ import junit.framework.TestCase; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.cmr.site.SiteService; import org.alfresco.service.cmr.site.SiteVisibility; @@ -71,6 +74,10 @@ public class FeedCleanerTest extends TestCase @Override public void setUp() throws Exception { + JobLockService jobLockService = (JobLockService) ctx.getBean("JobLockService"); + PolicyComponent policyComponent = (PolicyComponent) ctx.getBean("policyComponent"); + NodeService nodeService = (NodeService) ctx.getBean("NodeService"); + siteService = (SiteService) ctx.getBean("SiteService"); personService = (PersonService) ctx.getBean("PersonService"); feedDAO = (ActivityFeedDAO) ctx.getBean("feedDAO"); @@ -89,6 +96,9 @@ public class FeedCleanerTest extends TestCase // construct the test cleaner cleaner = new FeedCleaner(); cleaner.setFeedDAO(feedDAO); + cleaner.setPolicyComponent(policyComponent); + cleaner.setJobLockService(jobLockService); + cleaner.setNodeService(nodeService); } public void tearDown() throws Exception @@ -114,6 +124,31 @@ public class FeedCleanerTest extends TestCase // NOOP } + public void testMaxIdRange() throws Exception + { + // insert site feed entries for TEST_SITE_4 + for (int i = 0; i < 10; i++) + { + ActivityFeedEntity feedEntry = new ActivityFeedEntity(); + + feedEntry.setPostDate(new Date(System.currentTimeMillis()-(i*60*1000L))); + feedEntry.setActivitySummaryFormat("json"); + feedEntry.setSiteNetwork(TEST_SITE_4); + feedEntry.setActivityType("testActivityType"); + feedEntry.setPostUserId(TEST_USER_C); + feedEntry.setFeedUserId(""); + feedEntry.setFeedDate(new Date()); + + feedDAO.insertFeedEntry(feedEntry); + } + // Check + assertEquals(10, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + // Limit the ID range we will keep + cleaner.setMaxIdRange(5); + cleaner.execute(); + // Check + assertEquals(5, feedDAO.selectSiteFeedEntries(TEST_SITE_4, "json", -1).size()); + } public void testMaxAge() throws Exception { cleaner.setMaxFeedSize(0); diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java index 0591779c5f..e4ba84bbf7 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco @@ -14,45 +14,43 @@ * 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.domain.activities; - -import java.sql.SQLException; -import java.util.Date; -import java.util.List; - -/** - * Interface for activity feed DAO service - */ -public interface ActivityFeedDAO extends ActivitiesDAO -{ - public static final int MAX_LEN_USER_ID = 255; // needs to match schema: feed_user_id, post_user_id - public static final int MAX_LEN_SITE_ID = 255; // needs to match schema: site_network - public static final int MAX_LEN_ACTIVITY_TYPE = 255; // needs to match schema: activity_type - public static final int MAX_LEN_ACTIVITY_SUMMARY = 4000; // needs to match schema: activity_summary - public static final int MAX_LEN_ACTIVITY_FORMAT = 255; // needs to match schema: activity_format - public static final int MAX_LEN_APP_TOOL_ID = 36; // needs to match schema: app_tool - - public long insertFeedEntry(ActivityFeedEntity activityFeed) throws SQLException; - - public int deleteFeedEntries(Date keepDate) throws SQLException; - - public int deleteUserFeedEntries(String feedUserId, String format, Date keepDate) throws SQLException; - - public int deleteUserFeedEntries(String feedUserId) throws SQLException; - - public int deleteSiteFeedEntries(String siteId, String format, Date keepDate) throws SQLException; - - public int deleteSiteFeedEntries(String siteUserId) throws SQLException; - - public List selectFeedsToClean(int maxFeedSize) throws SQLException; - - public Long countUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; - - public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; - - public Long countSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; - - public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; -} + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.activities; + +import java.sql.SQLException; +import java.util.Date; +import java.util.List; + +/** + * Interface for activity feed DAO service + */ +public interface ActivityFeedDAO extends ActivitiesDAO +{ + public static final int MAX_LEN_USER_ID = 255; // needs to match schema: feed_user_id, post_user_id + public static final int MAX_LEN_SITE_ID = 255; // needs to match schema: site_network + public static final int MAX_LEN_ACTIVITY_TYPE = 255; // needs to match schema: activity_type + public static final int MAX_LEN_ACTIVITY_SUMMARY = 4000; // needs to match schema: activity_summary + public static final int MAX_LEN_ACTIVITY_FORMAT = 255; // needs to match schema: activity_format + public static final int MAX_LEN_APP_TOOL_ID = 36; // needs to match schema: app_tool + + public long insertFeedEntry(ActivityFeedEntity activityFeed) throws SQLException; + + public int deleteFeedEntries(Integer maxIdRange) throws SQLException; + public int deleteFeedEntries(Date keepDate) throws SQLException; + + public int deleteUserFeedEntries(String feedUserId, String format, Date keepDate) throws SQLException; + + public int deleteUserFeedEntries(String feedUserId) throws SQLException; + + public int deleteSiteFeedEntries(String siteId, String format, Date keepDate) throws SQLException; + + public int deleteSiteFeedEntries(String siteUserId) throws SQLException; + + public List selectSiteFeedsToClean(int maxFeedSize) throws SQLException; + public List selectUserFeedsToClean(int maxFeedSize) throws SQLException; + + public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedItems) throws SQLException; + + public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; +} diff --git a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java index 46c34975fd..6793303a79 100644 --- a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java @@ -31,6 +31,7 @@ import org.apache.ibatis.session.RowBounds; public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFeedDAO { + @Override public long insertFeedEntry(ActivityFeedEntity activityFeed) throws SQLException { template.insert("alfresco.activities.insert.insert_activity_feed", activityFeed); @@ -38,11 +39,30 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe return (id != null ? id : -1); } + @Override + public int deleteFeedEntries(Integer maxIdRange) throws SQLException + { + // Get the largest ID + Long maxId = (Long) template.selectOne("alfresco.activities.select_activity_feed_entries_max_id"); + if (maxId == null) + { + return 0; // This happens when there are no entries + } + Long minId = maxId - maxIdRange + 1; // The delete leaves the ID we pass in + if (minId <= 0) + { + return 0; + } + return template.delete("alfresco.activities.delete_activity_feed_entries_before_id", minId); + } + + @Override public int deleteFeedEntries(Date keepDate) throws SQLException { return template.delete("alfresco.activities.delete_activity_feed_entries_older_than_date", keepDate); } + @Override public int deleteSiteFeedEntries(String siteId) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -51,6 +71,7 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe return template.delete("alfresco.activities.delete_activity_feed_for_site_entries", params); } + @Override public int deleteSiteFeedEntries(String siteId, String format, Date keepDate) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -61,7 +82,7 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe return template.delete("alfresco.activities.delete_activity_feed_for_site_entries_older_than_date", params); } - + @Override public int deleteUserFeedEntries(String feedUserId, String format, Date keepDate) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -72,6 +93,7 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe return template.delete("alfresco.activities.delete_activity_feed_for_feeduser_entries_older_than_date", params); } + @Override public int deleteUserFeedEntries(String feedUserId) throws SQLException { ActivityFeedEntity params = new ActivityFeedEntity(); @@ -81,76 +103,21 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe } @SuppressWarnings("unchecked") - public List selectFeedsToClean(int maxFeedSize) throws SQLException + @Override + public List selectUserFeedsToClean(int maxFeedSize) throws SQLException { - return (List)template.selectList("alfresco.activities.select_activity_feed_greater_than_max", maxFeedSize); + return (List)template.selectList("alfresco.activities.select_activity_user_feeds_greater_than_max", maxFeedSize); } @SuppressWarnings("unchecked") - public Long countUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException + @Override + public List selectSiteFeedsToClean(int maxFeedSize) throws SQLException { - ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); - params.setFeedUserId(feedUserId); - params.setActivitySummaryFormat(format); - - if (minFeedId > -1) - { - params.setMinId(minFeedId); - } - - if (siteId != null) - { - if (excludeThisUser && excludeOtherUsers) - { - return Long.valueOf(0); - } - if ((!excludeThisUser) && (!excludeOtherUsers)) - { - // no excludes => everyone => where feed user is me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_and_site", params); - } - else if ((excludeThisUser) && (!excludeOtherUsers)) - { - // exclude feed user => others => where feed user is me and post user is not me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others_and_site", params); - } - else if ((excludeOtherUsers) && (!excludeThisUser)) - { - // exclude others => me => where feed user is me and post user is me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me_and_site", params); - } - } - else - { - // all sites - - if (excludeThisUser && excludeOtherUsers) - { - // effectively NOOP - return empty feed - return Long.valueOf(0); - } - if (!excludeThisUser && !excludeOtherUsers) - { - // no excludes => everyone => where feed user is me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser", params); - } - else if (excludeThisUser) - { - // exclude feed user => others => where feed user is me and post user is not me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others", params); - } - else if (excludeOtherUsers) - { - // exclude others => me => where feed user is me and post user is me - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me", params); - } - } - - // belts-and-braces - throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); + return (List)template.selectList("alfresco.activities.select_activity_site_feeds_greater_than_max", maxFeedSize); } - + @SuppressWarnings("unchecked") + @Override public List selectUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException { ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); @@ -178,17 +145,17 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe if ((!excludeThisUser) && (!excludeOtherUsers)) { // no excludes => everyone => where feed user is me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_and_site", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select.select_activity_feed_for_feeduser_and_site", params, rowBounds); } else if ((excludeThisUser) && (!excludeOtherUsers)) { // exclude feed user => others => where feed user is me and post user is not me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_feeduser_others_and_site", params, rowBounds); } else if ((excludeOtherUsers) && (!excludeThisUser)) { // exclude others => me => where feed user is me and post user is me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_feeduser_me_and_site", params, rowBounds); } } else @@ -203,17 +170,17 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe if (!excludeThisUser && !excludeOtherUsers) { // no excludes => everyone => where feed user is me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_feeduser", params, rowBounds); } else if (excludeThisUser) { // exclude feed user => others => where feed user is me and post user is not me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_feeduser_others", params, rowBounds); } else if (excludeOtherUsers) { // exclude others => me => where feed user is me and post user is me - return (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_feeduser_me", params, rowBounds); } } @@ -222,17 +189,7 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe } @SuppressWarnings("unchecked") - public Long countSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException - { - ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); - params.setSiteNetwork(siteId); - params.setActivitySummaryFormat(format); - - // for given site - return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_site", params); - } - - @SuppressWarnings("unchecked") + @Override public List selectSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException { ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); @@ -243,6 +200,6 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe RowBounds rowBounds = new RowBounds(RowBounds.NO_ROW_OFFSET, rowLimit); // for given site - return (List)template.selectList("alfresco.activities.select_activity_feed_for_site", params, rowBounds); + return (List)template.selectList("alfresco.activities.select.select_activity_feed_for_site", params, rowBounds); } } diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index f18bab63ad..4a89d01c41 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -72,6 +72,7 @@ import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSQLServerDialect; import org.alfresco.repo.domain.hibernate.dialect.AlfrescoSybaseAnywhereDialect; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.util.DatabaseMetaDataHelper; import org.alfresco.util.LogUtil; import org.alfresco.util.TempFileProvider; import org.alfresco.util.schemacomp.ExportDb; @@ -511,7 +512,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean */ private int countAppliedPatches(Configuration cfg, Connection connection) throws Exception { - String defaultSchema = cfg.getProperty("hibernate.default_schema"); + String defaultSchema = DatabaseMetaDataHelper.getSchema(connection); if (defaultSchema != null && defaultSchema.length() == 0) { defaultSchema = null; @@ -783,8 +784,8 @@ public class SchemaBootstrap extends AbstractLifecycleBean final Dialect dialect = Dialect.getDialect(cfg.getProperties()); String dialectStr = dialect.getClass().getSimpleName(); - // Initialise Activiti DB, using an unclosable connection. - initialiseActivitiDBSchema(new UnclosableConnection(connection), cfg); + // Initialise Activiti DB, using an unclosable connection + initialiseActivitiDBSchema(new UnclosableConnection(connection)); if (create) { @@ -877,7 +878,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean * * @param connection Connection to use the initialise DB schema */ - private void initialiseActivitiDBSchema(Connection connection, Configuration cfg) + private void initialiseActivitiDBSchema(Connection connection) { // create instance of activiti engine to initialise schema ProcessEngine engine = null; @@ -893,7 +894,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean buildProcessEngine(); // create or upgrade the DB schema - engine.getManagementService().databaseSchemaUpgrade(connection, null, cfg.getProperty("hibernate.default_schema")); + engine.getManagementService().databaseSchemaUpgrade(connection, null, DatabaseMetaDataHelper.getSchema(connection)); } finally { diff --git a/source/java/org/alfresco/repo/version/VersionableAspect.java b/source/java/org/alfresco/repo/version/VersionableAspect.java index 9fadf03a18..59058d48bd 100644 --- a/source/java/org/alfresco/repo/version/VersionableAspect.java +++ b/source/java/org/alfresco/repo/version/VersionableAspect.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -42,6 +42,9 @@ import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.lock.LockType; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; @@ -86,7 +89,9 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate /** The node service */ private NodeService nodeService; - + + private LockService lockService; + /** The Version service */ private VersionService versionService; @@ -140,7 +145,17 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate { this.nodeService = nodeService; } - + + /** + * Set the lock service + * + * @param lockService the lock service + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + /** * Sets the dictionary DAO. * @@ -436,10 +451,10 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate Map before, Map after) { - if ((this.nodeService.exists(nodeRef) == true) && + if ((this.nodeService.exists(nodeRef) == true) && + !isLockedOrReadOnly(nodeRef) && (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == true) && - (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false) && - (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == false)) + (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_TEMPORARY) == false)) { onUpdatePropertiesBehaviour.disable(); try @@ -507,6 +522,21 @@ public class VersionableAspect implements ContentServicePolicies.OnContentUpdate } } } + + /** + * Indicates if the node is unlocked or the current user has a WRITE_LOCK

+ * + * Ideally this would be a new method on the lockService, but cannot do this at the moment, + * as this method is being added as part of a hot fix, so a public service cannot change + * as the RM AMP might be installed and it has its own security context which would also need + * to reflect this change. + */ + private boolean isLockedOrReadOnly(NodeRef nodeRef) + { + LockStatus lockStatus = lockService.getLockStatus(nodeRef); + LockType lockType = lockService.getLockType(nodeRef); + return ! (lockStatus == LockStatus.NO_LOCK || (lockStatus == LockStatus.LOCK_OWNER && lockType == LockType.WRITE_LOCK)); + } /** * On create version implementation method diff --git a/source/java/org/alfresco/repo/version/VersionableAspectTest.java b/source/java/org/alfresco/repo/version/VersionableAspectTest.java new file mode 100644 index 0000000000..14d8e6e13c --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionableAspectTest.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2005-2012 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.version; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.StoreRef; +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.cmr.version.VersionType; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @author Dmitry Velichkevich + */ +public class VersionableAspectTest extends TestCase +{ + private static final String NAME_AND_EXT_DELIMETER = "."; + + private static final String NAME_AND_EXT_DELIMETER_REGEXP = "\\" + NAME_AND_EXT_DELIMETER; + + + private static final String ADMIN_CREDENTIAL = "admin"; + + private static final String ROOT_NODE_TERM = "PATH:\"/app\\:company_home\""; + + private static final String DOCUMENT_NAME = "ChildDocumentWithVersionLabel-.txt"; + + private static final String PARENT_FOLDER_NAME = "ParentFolder-" + System.currentTimeMillis(); + + private static final String TEST_CONTENT_01 = "Test Content version 0.1\n"; + private static final String TEST_CONTENT_10 = "Test Content version 1.0\n"; + + + private ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); + + private NodeService nodeService = (NodeService) applicationContext.getBean("nodeService"); + private LockService lockService = (LockService) applicationContext.getBean("lockService"); + private SearchService searchService = (SearchService) applicationContext.getBean("searchService"); + private ContentService contentService = (ContentService) applicationContext.getBean("contentService"); + private TransactionService transactionService = (TransactionService) applicationContext.getBean("transactionService"); + private CheckOutCheckInService checkOutCheckInService = (CheckOutCheckInService) applicationContext.getBean("checkOutCheckInService"); + private AuthenticationService authenticationService = (AuthenticationService) applicationContext.getBean("authenticationService"); + + private NodeRef document; + private NodeRef parentFolder; + + @Override + protected void setUp() throws Exception + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + authenticationService.authenticate(ADMIN_CREDENTIAL, ADMIN_CREDENTIAL.toCharArray()); + + ResultSet query = null; + NodeRef rootNode = null; + try + { + query = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_LUCENE, ROOT_NODE_TERM); + rootNode = query.getNodeRef(0); + } + finally + { + if (null != query) + { + query.close(); + } + } + + Map properties = new HashMap(); + properties.put(ContentModel.PROP_NAME, PARENT_FOLDER_NAME); + parentFolder = nodeService.createNode(rootNode, ContentModel.ASSOC_CONTAINS, QName.createQName(ContentModel.USER_MODEL_URI, PARENT_FOLDER_NAME), + ContentModel.TYPE_FOLDER, properties).getChildRef(); + + properties.clear(); + properties.put(ContentModel.PROP_NAME, DOCUMENT_NAME); + + document = nodeService.createNode(parentFolder, ContentModel.ASSOC_CONTAINS, QName.createQName(ContentModel.USER_MODEL_URI, DOCUMENT_NAME), + ContentModel.TYPE_CONTENT, properties).getChildRef(); + contentService.getWriter(document, ContentModel.PROP_CONTENT, true).putContent(TEST_CONTENT_01); + + if (!nodeService.hasAspect(document, ContentModel.ASPECT_VERSIONABLE)) + { + Map versionProperties = new HashMap(); + versionProperties.put(ContentModel.PROP_VERSION_LABEL, "0.1"); + versionProperties.put(ContentModel.PROP_INITIAL_VERSION, true); + versionProperties.put(ContentModel.PROP_VERSION_TYPE, VersionType.MINOR); + nodeService.addAspect(document, ContentModel.ASPECT_VERSIONABLE, versionProperties); + } + + return null; + } + }); + } + + @Override + protected void tearDown() throws Exception + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + if (null != parentFolder) + { + nodeService.deleteNode(parentFolder); + } + + authenticationService.clearCurrentSecurityContext(); + + return null; + } + }); + } + + public void testAutoVersionIncrementOnPropertiesUpdateAfterCheckInAlf14584() throws Exception + { + final String name02 = generateDocumentName(DOCUMENT_NAME, "0.2"); + final String name11 = generateDocumentName(DOCUMENT_NAME, "1.1"); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map properties = getAndAssertProperties(document, "0.1"); + + Serializable autoVersionProps = properties.get(ContentModel.PROP_AUTO_VERSION_PROPS); + assertNotNull(("Autoversion property is NULL! NodeRef = '" + document.toString() + "'"), autoVersionProps); + assertTrue(("Autoversion must be TRUE! NodeRef = '" + document.toString() + "'"), (Boolean) autoVersionProps); + + nodeService.setProperty(document, ContentModel.PROP_NAME, name02); + + return null; + } + }); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map properties = getAndAssertProperties(document, "0.2"); + assertEquals(name02, properties.get(ContentModel.PROP_NAME)); + + NodeRef workingCopy = checkOutCheckInService.checkout(document); + contentService.getWriter(workingCopy, ContentModel.PROP_CONTENT, true).putContent(TEST_CONTENT_10); + + Map versionProperties = new HashMap(); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + document = checkOutCheckInService.checkin(workingCopy, versionProperties); + + return null; + } + }); + + assertDocumentVersionAndName("1.0", name02); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeService.setProperty(document, ContentModel.PROP_NAME, name11); + + return null; + } + }); + + assertDocumentVersionAndName("1.1", name11); + } + + public void testAutoVersionIncrementOnPropertiesUpdateByLockOwnerAlf14584() throws Exception + { + final String name = generateDocumentName(DOCUMENT_NAME, "0.2"); + + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map properties = getAndAssertProperties(document, "0.1"); + + Serializable autoVersionProps = properties.get(ContentModel.PROP_AUTO_VERSION_PROPS); + assertNotNull(("Autoversion property is NULL! NodeRef = '" + document.toString() + "'"), autoVersionProps); + assertTrue(("Autoversion must be TRUE! NodeRef = '" + document.toString() + "'"), (Boolean) autoVersionProps); + + lockService.lock(document, LockType.WRITE_LOCK); + + LockStatus lockStatus = lockService.getLockStatus(document); + assertFalse( + ("Node with NodeRef = '" + document.toString() + "' must not be locked for " + AuthenticationUtil.getFullyAuthenticatedUser() + " user! The user is lock owner"), + isLocked(document)); + assertEquals(LockStatus.LOCK_OWNER, lockService.getLockStatus(document)); + + nodeService.setProperty(document, ContentModel.PROP_NAME, name); + + return null; + } + }); + + assertDocumentVersionAndName("0.2", name); + } + + // Copy of code from VersionableAspect which really should be in LockService + private boolean isLocked(NodeRef nodeRef) + { + LockStatus lockStatus = lockService.getLockStatus(nodeRef); + + return (LockStatus.NO_LOCK != lockStatus) && (LockStatus.LOCK_OWNER != lockStatus); + } + + private void assertDocumentVersionAndName(final String versionLabel, final String name) + { + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Map properties = getAndAssertProperties(document, versionLabel); + assertEquals(name, properties.get(ContentModel.PROP_NAME)); + + return null; + } + }, true); + } + + private Map getAndAssertProperties(NodeRef nodeRef, String versionLabel) + { + assertNotNull("NodeRef of document is NULL!", nodeRef); + + Map properties = nodeService.getProperties(nodeRef); + + assertNotNull(("Properties must not be NULL! NodeRef = '" + nodeRef.toString() + "'"), properties); + assertFalse(("Version specific properties can't be found! NodeRef = '" + nodeRef.toString() + "'"), properties.isEmpty()); + assertEquals(versionLabel, properties.get(ContentModel.PROP_VERSION_LABEL)); + + return properties; + } + + private String generateDocumentName(String namePattern, String versionLabel) + { + int i = 0; + String[] nameAndExt = namePattern.split(NAME_AND_EXT_DELIMETER_REGEXP); + StringBuilder result = new StringBuilder(nameAndExt[i++]).append(versionLabel).append(NAME_AND_EXT_DELIMETER).append(nameAndExt[i++]); + return result.toString(); + } +} diff --git a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowTask.java b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowTask.java index 658f273798..46a31a7fa9 100644 --- a/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowTask.java +++ b/source/java/org/alfresco/repo/workflow/jscript/JscriptWorkflowTask.java @@ -22,7 +22,7 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.Set; +import java.util.Map; import org.alfresco.model.ApplicationModel; import org.alfresco.model.ContentModel; @@ -30,16 +30,19 @@ import org.alfresco.repo.jscript.BaseScopableProcessorExtension; import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.repo.jscript.ScriptableHashMap; import org.alfresco.repo.jscript.ScriptableQNameMap; -import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.cmr.workflow.WorkflowTaskState; import org.alfresco.service.cmr.workflow.WorkflowTransition; import org.alfresco.service.namespace.NamespacePrefixResolver; import org.alfresco.service.namespace.NamespacePrefixResolverProvider; +import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.mozilla.javascript.Context; import org.mozilla.javascript.Scriptable; @@ -54,154 +57,37 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen { static final long serialVersionUID = -8285971359421912313L; - /** Unique ID for workflow task */ - private final String id; - - /** Name for workflow task */ - private final String name; - - /** Title for workflow task */ - private final String title; - - /** Description of workflow task */ - private final String description; - - /** Properties (key/value pairs) for this Workflow Task */ - private ScriptableQNameMap properties; - - /** Whether task is complete or not - 'true':complete, 'false':in-progress */ - private boolean complete = false; - - /** Whether task is pooled or not */ - private boolean pooled = false; - /** Service Registry object */ - private ServiceRegistry serviceRegistry; - - /** Available transitions * */ - private ScriptableHashMap transitions; - - /** Package resources * */ - private Scriptable packageResources; - - /** - * Creates a new instance of a workflow task (instance of a workflow task definition) - * - * @param id - * workflow task ID - * @param name - * workflow task name - * @param title - * workflow task title - * @param description - * workflow task description - * @param serviceRegistry - * Service Registry object - * @param properties - * @param transitions - * @param packageResources - */ - public JscriptWorkflowTask(final String id, final String name, final String title, final String description, final ServiceRegistry serviceRegistry, - final ScriptableQNameMap properties, final ScriptableHashMap transitions, Scriptable packageResources, - Scriptable scope) - { - this.id = id; - this.name = name; - this.title = title; - this.description = description; - this.serviceRegistry = serviceRegistry; - this.properties = properties; - this.transitions = transitions; - this.packageResources = packageResources; - this.setScope(scope); - } + private final ServiceRegistry serviceRegistry; + private final NodeService nodeService; + private final WorkflowService workflowService; + private final DictionaryService dictionaryService; + private MutableAuthenticationService authenticationService; + private final DefaultNamespaceProvider namespaceProvider; + + private WorkflowTask task; + /** * Creates a new instance of a workflow task from a WorkflowTask from the CMR workflow object model * - * @param cmrWorkflowTask + * @param task * an instance of WorkflowTask from CMR workflow object model * @param serviceRegistry * Service Registry object */ - public JscriptWorkflowTask(final WorkflowTask cmrWorkflowTask, final ServiceRegistry serviceRegistry, Scriptable scope) + public JscriptWorkflowTask(WorkflowTask task, + ServiceRegistry serviceRegistry, + Scriptable scope) { - this.id = cmrWorkflowTask.getId(); - this.name = cmrWorkflowTask.getName(); - this.title = cmrWorkflowTask.getTitle(); - this.description = cmrWorkflowTask.getDescription(); this.serviceRegistry = serviceRegistry; + this.namespaceProvider = new DefaultNamespaceProvider(serviceRegistry.getNamespaceService()); + this.workflowService = serviceRegistry.getWorkflowService(); + this.nodeService = serviceRegistry.getNodeService(); + this.dictionaryService = serviceRegistry.getDictionaryService(); + this.authenticationService = serviceRegistry.getAuthenticationService(); + this.task = task; this.setScope(scope); - - // instantiate ScriptableQNameMap properties - // from WorkflowTasks's Map properties - this.properties = new ScriptableQNameMap(new NamespacePrefixResolverProvider() - { - private static final long serialVersionUID = 4218645978524914678L; - - public NamespacePrefixResolver getNamespacePrefixResolver() - { - return serviceRegistry.getNamespaceService(); - } - }); - - Set keys = cmrWorkflowTask.getProperties().keySet(); - for (QName key : keys) - { - Serializable value = cmrWorkflowTask.getProperties().get(key); - this.properties.put(key.toString(), value); - } - - transitions = new ScriptableHashMap(); - for (WorkflowTransition transition : cmrWorkflowTask.getPath().getNode().getTransitions()) - { - transitions.put(transition.getId(), transition.getTitle()); - } - - // build package context .... should be centralised... YUK - // Needs to match org.alfresco.repo.template.Workflow.WorkflowTaskItem.getPackageResources - - List contents = serviceRegistry.getWorkflowService().getPackageContents(cmrWorkflowTask.getId()); - List resources = new ArrayList(contents.size()); - - NodeService nodeService = serviceRegistry.getNodeService(); - DictionaryService ddService = serviceRegistry.getDictionaryService(); - - for (NodeRef nodeRef : contents) - { - if (nodeRef.getStoreRef().getProtocol().equals(StoreRef.PROTOCOL_AVM)) - { - resources.add(nodeRef); - } - else - { - if (nodeService.exists(nodeRef)) - { - // find it's type so we can see if it's a node we are interested in - QName type = nodeService.getType(nodeRef); - - // make sure the type is defined in the data dictionary - if (ddService.getType(type) != null) - { - // look for content nodes or links to content - // NOTE: folders within workflow packages are ignored for now - if (ddService.isSubClass(type, ContentModel.TYPE_CONTENT) || ApplicationModel.TYPE_FILELINK.equals(type)) - { - resources.add(nodeRef); - } - } - } - } - } - - Object[] answer = new Object[resources.size()]; - for (int i = 0; i < resources.size(); i++) - { - // create our Node representation from the NodeRef - answer[i] = new ScriptNode(resources.get(i), serviceRegistry, getScope()); - } - packageResources = Context.getCurrentContext().newArray(getScope(), answer); - } /** @@ -211,7 +97,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public String getId() { - return id; + return task.getId(); } /** @@ -221,7 +107,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public String getName() { - return name; + return task.getName(); } /** @@ -231,7 +117,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public String getTitle() { - return title; + return task.getTitle(); } /** @@ -241,7 +127,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public String getDescription() { - return description; + return task.getDescription(); } /** @@ -251,20 +137,26 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public Scriptable getProperties() { + // instantiate ScriptableQNameMap properties + // from WorkflowTasks's Map properties + ScriptableQNameMap properties = new ScriptableQNameMap(namespaceProvider); + properties.putAll(task.getProperties()); return properties; } /** - * Sets the value of the properties property + * Sets the properties on the underlying {@link WorkflowTask}. * * @param properties * the properties to set */ public void setProperties(ScriptableQNameMap properties) { - this.properties = properties; + + Map qNameProps = properties.getMapOfQNames(); + this.task = workflowService.updateTask(task.getId(), qNameProps, null, null); } - + /** * Returns whether the task is complete 'true':complete, 'false':in-progress * @@ -272,7 +164,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public boolean isComplete() { - return complete; + return task.getState().equals(WorkflowTaskState.COMPLETED); } /** @@ -282,15 +174,13 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public boolean isPooled() { - if(properties != null) { - Collection actors = (Collection) properties.get(WorkflowModel.ASSOC_POOLED_ACTORS); - return actors != null && !actors.isEmpty(); - } - return false; + String authority = authenticationService.getCurrentUserName(); + return workflowService.isTaskClaimable(task, authority); } /** * @deprecated pooled state cannot be altered. + * */ @Deprecated public void setPooled(boolean pooled) @@ -306,7 +196,7 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public void endTask(String transitionId) { - serviceRegistry.getWorkflowService().endTask(this.id, transitionId); + workflowService.endTask(task.getId(), transitionId); } /** @@ -316,6 +206,11 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public ScriptableHashMap getTransitions() { + ScriptableHashMap transitions = new ScriptableHashMap(); + for (WorkflowTransition transition : task.getPath().getNode().getTransitions()) + { + transitions.put(transition.getId(), transition.getTitle()); + } return transitions; } @@ -326,7 +221,64 @@ public class JscriptWorkflowTask extends BaseScopableProcessorExtension implemen */ public Scriptable getPackageResources() { - return packageResources; + List contents = workflowService.getPackageContents(task.getId()); + List resources = new ArrayList(contents.size()); + + Collection allowedTypes = getAllowedPackageResourceTypes(); + for (NodeRef node : contents) + { + if (isAvmResource(node, allowedTypes)) + { + ScriptNode scriptNode = new ScriptNode(node, serviceRegistry, getScope()); + resources.add(scriptNode); + } + } + return Context.getCurrentContext().newArray(getScope(), resources.toArray()); } + private Collection getAllowedPackageResourceTypes() + { + // look for content nodes or links to content + // NOTE: folders within workflow packages are ignored for now + Collection allowedTypes = dictionaryService.getSubTypes(ContentModel.TYPE_CONTENT, true); + allowedTypes.addAll(dictionaryService.getSubTypes(ApplicationModel.TYPE_FILELINK, true)); + return allowedTypes; + } + + private boolean isAvmResource(NodeRef node, Collection allowedTypes) + { + if(isAvmNode(node)) + return true; + if (nodeService.exists(node)) + { + //Check if the node is one of the allowedTypes. + return allowedTypes.contains(nodeService.getType(node)); + } + return false; + } + + private boolean isAvmNode(NodeRef node) + { + return StoreRef.PROTOCOL_AVM.equals(node.getStoreRef().getProtocol()); + } + + private static class DefaultNamespaceProvider implements NamespacePrefixResolverProvider + { + private static final long serialVersionUID = -7015209142379905617L; + private final NamespaceService namespaceService; + + public DefaultNamespaceProvider(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * {@inheritDoc} + */ + public NamespacePrefixResolver getNamespacePrefixResolver() + { + return namespaceService; + } + + } } \ No newline at end of file diff --git a/source/java/org/alfresco/util/DatabaseMetaDataHelper.java b/source/java/org/alfresco/util/DatabaseMetaDataHelper.java new file mode 100644 index 0000000000..8997b3bca1 --- /dev/null +++ b/source/java/org/alfresco/util/DatabaseMetaDataHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2005-2012 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.util; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Helper class to collect all of our DatabaseMetaData interpretations in one place. + * + * @author sfrensley + * + */ +public class DatabaseMetaDataHelper { + + private static Log logger = LogFactory.getLog(DatabaseMetaDataHelper.class); + + /** + * Trys to determine the schema name from the DatabaseMetaData obtained from the Connection. + * @param connection A database connection + * @return + */ + public static String getSchema(Connection connection) + { + + if (connection == null) { + logger.error("Unable to determine schema due to null connection."); + return null; + } + + ResultSet schemas = null; + + try + { + final DatabaseMetaData dbmd = connection.getMetaData(); + + // Assume that if there are schemas, we want the one named after the connection user or the one called "dbo" (MS + // SQL hack) + String schema = null; + schemas = dbmd.getSchemas(); + while (schemas.next()) + { + final String thisSchema = schemas.getString("TABLE_SCHEM"); + if (thisSchema.equals(dbmd.getUserName()) || thisSchema.equalsIgnoreCase("dbo")) + { + schema = thisSchema; + break; + } + } + return schema; + } + catch (Exception e) + { + logger.error("Unable to determine current schema.",e); + } + finally + { + if (schemas != null) + { + try + { + schemas.close(); + } + catch (Exception e) + { + //noop + } + } + } + return null; + } +} diff --git a/source/java/org/alfresco/util/OpenOfficeCommandLine.java b/source/java/org/alfresco/util/OpenOfficeCommandLine.java index ea8f36cf17..cc0416c726 100644 --- a/source/java/org/alfresco/util/OpenOfficeCommandLine.java +++ b/source/java/org/alfresco/util/OpenOfficeCommandLine.java @@ -55,7 +55,7 @@ public class OpenOfficeCommandLine extends AbstractMap> if (variant.isLibreOffice3Dot5(officeHome)) { command.add("--accept=" + acceptValue); - if (variant.isMac()) + if (variant.isMac() && !variant.isLibreOffice3Dot6(officeHome)) { command.add("--env:UserInstallation=" + userInstallation); } @@ -70,7 +70,9 @@ public class OpenOfficeCommandLine extends AbstractMap> //command.add("--nolockcheck"); included by JOD command.add("--nologo"); command.add("--norestore"); - logger.info("Using GNU based LibreOffice command"+(variant.isMac() ? " on Mac" : "")+": "+command); + logger.info("Using GNU based LibreOffice "+ + (variant.isLibreOffice3Dot6(officeHome) ? "3.6" : "3.5")+" command"+ + (variant.isMac() ? " on Mac" : "")+": "+command); } else { diff --git a/source/java/org/alfresco/util/OpenOfficeVariant.java b/source/java/org/alfresco/util/OpenOfficeVariant.java index d645cc72f6..97b7001f4c 100644 --- a/source/java/org/alfresco/util/OpenOfficeVariant.java +++ b/source/java/org/alfresco/util/OpenOfficeVariant.java @@ -109,19 +109,31 @@ public class OpenOfficeVariant public boolean isLibreOffice3Dot5(File officeHome) { - logger.debug("System.getProperty(\"os.name\")="+System.getProperty("os.name")); - logger.debug("officeHome="+(officeHome == null ? null : "'"+officeHome.getAbsolutePath()+"'")); - logger.debug("basis-link:"+new File(officeHome, "basis-link").isFile()); - logger.debug(" ure-link:"+new File(officeHome, "ure-link").isFile()); - logger.debug("basis-link:"+new File(officeHome, "basis-link").isDirectory()); - logger.debug(" ure-link:"+new File(officeHome, "ure-link").isDirectory()); - + if (logger.isDebugEnabled()) + { + logger.debug("System.getProperty(\"os.name\")="+System.getProperty("os.name")); + logger.debug("officeHome="+(officeHome == null ? null : "'"+officeHome.getAbsolutePath()+"'")); + logger.debug("basis-link:"+new File(officeHome, "basis-link").isFile()); + logger.debug("basis-link:"+new File(officeHome, "basis-link").isDirectory()); + logger.debug(" ure-link:"+new File(officeHome, "ure-link").isFile()); + logger.debug(" ure-link:"+new File(officeHome, "ure-link").isDirectory()); + logger.debug(" NOTICE:"+new File(officeHome, "NOTICE").isFile()); + } return officeHome != null && !new File(officeHome, "basis-link").isFile() && (new File(officeHome, "ure-link").isFile() || new File(officeHome, "ure-link").isDirectory()); } + public boolean isLibreOffice3Dot6(File officeHome) + { + if (logger.isDebugEnabled()) + { + logger.debug(" NOTICE:"+new File(officeHome, "NOTICE").isFile()); + } + return isLibreOffice3Dot5(officeHome) && new File(officeHome, "NOTICE").isFile(); + } + public boolean isLinux() { return OS_NAME.startsWith("linux");