diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 628fa96828..08c25bb1d7 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -8,6 +8,44 @@ ${dir.contentstore} + + + + + ${dir.contentstore.deleted} + + + + + + + + + + + + + + + + + + + + + 14 + + + + + + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 21651022ae..aa3520d251 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -3,6 +3,7 @@ dir.root=./alf_data dir.contentstore=${dir.root}/contentstore +dir.contentstore.deleted=${dir.root}/contentstore.deleted # The location for lucene index files diff --git a/config/alfresco/scheduled-jobs-context.xml b/config/alfresco/scheduled-jobs-context.xml index 437c509cb8..96c7ce7b62 100644 --- a/config/alfresco/scheduled-jobs-context.xml +++ b/config/alfresco/scheduled-jobs-context.xml @@ -52,32 +52,30 @@ - + - org.alfresco.repo.content.ContentStoreCleanupJob + org.alfresco.repo.content.cleanup.ContentStoreCleanupJob - - - - - - - - 24 + + - - 600000 + + + 04 + + + 00 - 3600000 + 86400000 @@ -153,9 +151,7 @@ - + diff --git a/source/java/org/alfresco/repo/content/AbstractContentStore.java b/source/java/org/alfresco/repo/content/AbstractContentStore.java index 8a5bdfc63b..92c0af1b3a 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentStore.java +++ b/source/java/org/alfresco/repo/content/AbstractContentStore.java @@ -59,12 +59,14 @@ public abstract class AbstractContentStore implements ContentStore int year = calendar.get(Calendar.YEAR); int month = calendar.get(Calendar.MONTH) + 1; // 0-based int day = calendar.get(Calendar.DAY_OF_MONTH); + int hour = calendar.get(Calendar.HOUR_OF_DAY); // create the URL StringBuilder sb = new StringBuilder(20); sb.append(STORE_PROTOCOL) .append(year).append('/') .append(month).append('/') .append(day).append('/') + .append(hour).append('/') .append(GUID.generate()).append(".bin"); String newContentUrl = sb.toString(); // done diff --git a/source/java/org/alfresco/repo/content/ContentStoreCleanupJob.java b/source/java/org/alfresco/repo/content/ContentStoreCleanupJob.java deleted file mode 100644 index 550d380b5c..0000000000 --- a/source/java/org/alfresco/repo/content/ContentStoreCleanupJob.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content; - -import java.util.Set; - -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.search.SearchService; -import org.quartz.Job; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; - -/** - * Removes all content form the store that is not referenced by any content node. - *

- * The following parameters are required: - *

    - *
  • contentStore: The content store bean to clean up
  • - *
  • searcher: The index searcher that searches for content in the store
  • - *
  • protectHours: The number of hours to protect content that isn't referenced
  • - *
- * - * @author Derek Hulley - */ -public class ContentStoreCleanupJob implements Job -{ - /** - * Gets all content URLs from the store, checks if it is in use by any node - * and deletes those that aren't. - */ - public void execute(JobExecutionContext context) throws JobExecutionException - { - JobDataMap jobData = context.getJobDetail().getJobDataMap(); - // extract the content store to use - Object contentStoreObj = jobData.get("contentStore"); - if (contentStoreObj == null || !(contentStoreObj instanceof ContentStore)) - { - throw new AlfrescoRuntimeException( - "ContentStoreCleanupJob data must contain valid 'contentStore' reference"); - } - ContentStore contentStore = (ContentStore) contentStoreObj; - // extract the search to use - Object searcherObj = jobData.get("searcher"); - if (searcherObj == null || !(searcherObj instanceof SearchService)) - { - throw new AlfrescoRuntimeException( - "ContentStoreCleanupJob data must contain valid 'searcher' reference"); - } - SearchService searcher = (SearchService) searcherObj; - // get the number of hourse to protect content - Object protectHoursObj = jobData.get("protectHours"); - if (protectHoursObj == null || !(protectHoursObj instanceof String)) - { - throw new AlfrescoRuntimeException( - "ContentStoreCleanupJob data must contain valid 'protectHours' value"); - } - long protectHours = 24L; - try - { - protectHours = Long.parseLong((String) protectHoursObj); - } - catch (NumberFormatException e) - { - throw new AlfrescoRuntimeException( - "ContentStoreCleanupJob data 'protectHours' value is not a valid integer"); - } - - long protectMillis = protectHours * 3600L * 1000L; // 3600s in an hour; 1000ms in a second - long now = System.currentTimeMillis(); - long lastModifiedSafeTimeMs = (now - protectMillis); // able to remove anything modified before this - - // get all URLs in the store - Set contentUrls = contentStore.getUrls(); - for (String contentUrl : contentUrls) - { - // TODO here we need to get hold of all the orphaned content in this store - - // not found - it is not in the repo, but check that it is old enough to delete - ContentReader reader = contentStore.getReader(contentUrl); - if (reader == null || !reader.exists()) - { - // gone missing in the meantime - continue; - } - long lastModified = reader.getLastModified(); - if (lastModified >= lastModifiedSafeTimeMs) - { - // not old enough - continue; - } - - // it is not in the repo and is old enough - boolean result = contentStore.delete(contentUrl); - System.out.println(contentUrl + ": " + Boolean.toString(result)); - } - - // TODO for now throw this exception to ensure that this job does not get run until - // the orphaned content can be correctly retrieved - throw new UnsupportedOperationException(); - } -} diff --git a/source/java/org/alfresco/repo/content/ContentStoreCleanupJobTest.java b/source/java/org/alfresco/repo/content/ContentStoreCleanupJobTest.java deleted file mode 100644 index c975cf68e8..0000000000 --- a/source/java/org/alfresco/repo/content/ContentStoreCleanupJobTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (C) 2005 Alfresco, Inc. - * - * Licensed under the Mozilla Public License version 1.1 - * with a permitted attribution clause. You may obtain a - * copy of the License at - * - * http://www.alfresco.org/legal/license.txt - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, - * either express or implied. See the License for the specific - * language governing permissions and limitations under the - * License. - */ -package org.alfresco.repo.content; - -import java.util.Date; - -import junit.framework.TestSuite; - -import org.alfresco.service.cmr.repository.ContentReader; -import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.util.BaseSpringTest; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.quartz.Scheduler; -import org.quartz.SchedulerFactory; -import org.quartz.impl.StdSchedulerFactory; -import org.quartz.impl.calendar.BaseCalendar; -import org.quartz.spi.TriggerFiredBundle; -import org.springframework.scheduling.quartz.SimpleTriggerBean; - -/** - * Content store cleanup job unit test - * - * @author Roy Wetherall - */ -public class ContentStoreCleanupJobTest extends BaseSpringTest -{ - private SimpleTriggerBean simpleTriggerBean; - private JobExecutionContext jobExecutionContext; - private ContentStoreCleanupJob job; - - private ContentStore contentStore; - private String url; - - /** - * This can be removed once the class being tested actually has a remote - * chance of working. - */ - public static TestSuite suite() - { - return new TestSuite(); - } - - /** - * On setup in transaction - */ - @Override - protected void onSetUpInTransaction() throws Exception - { - this.contentStore = (ContentStore)this.applicationContext.getBean("fileContentStore"); - this.simpleTriggerBean = (SimpleTriggerBean)this.applicationContext.getBean("fileContentStoreCleanerTrigger"); - - SchedulerFactory factory = new StdSchedulerFactory(); - Scheduler scheduler = factory.getScheduler(); - - // Set the protect hours to 0 for the purpose of this test - JobDataMap jobDataMap = this.simpleTriggerBean.getJobDetail().getJobDataMap(); - jobDataMap.put("protectHours", "0"); - this.simpleTriggerBean.getJobDetail().setJobDataMap(jobDataMap); - - this.job = new ContentStoreCleanupJob(); - TriggerFiredBundle triggerFiredBundle = new TriggerFiredBundle( - this.simpleTriggerBean.getJobDetail(), - this.simpleTriggerBean, - new BaseCalendar(), - false, - new Date(), - new Date(), - new Date(), - new Date()); - - this.jobExecutionContext = new JobExecutionContext(scheduler, triggerFiredBundle, job); - - ContentWriter contentWriter = this.contentStore.getWriter(null, null); - contentWriter.putContent("This is some content that I am going to delete."); - this.url = contentWriter.getContentUrl(); - } - - /** - * Test execute method - */ - public void testExecute() - { - try - { - ContentReader before = this.contentStore.getReader(this.url); - assertTrue(before.exists()); - - this.job.execute(this.jobExecutionContext); - - ContentReader after = this.contentStore.getReader(this.url); - assertFalse(after.exists()); - } - catch (JobExecutionException exception) - { - fail("Exception raised!"); - } - } -} diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java new file mode 100644 index 0000000000..973b479c78 --- /dev/null +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleaner.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.repo.transaction.TransactionUtil; +import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This component is responsible for finding orphaned content in a given + * content store or stores. Deletion handlers can be provided to ensure + * that the content is moved to another location prior to being removed + * from the store(s) being cleaned. + * + * @author Derek Hulley + */ +public class ContentStoreCleaner +{ + private static Log logger = LogFactory.getLog(ContentStoreCleaner.class); + + private DictionaryService dictionaryService; + private NodeDaoService nodeDaoService; + private TransactionService transactionService; + private List stores; + private List listeners; + private int protectDays; + + public ContentStoreCleaner() + { + this.stores = new ArrayList(0); + this.listeners = new ArrayList(0); + this.protectDays = 7; + } + + /** + * @param dictionaryService used to determine which properties are content properties + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * @param nodeDaoService used to get the property values + */ + public void setNodeDaoService(NodeDaoService nodeDaoService) + { + this.nodeDaoService = nodeDaoService; + } + + /** + * @param transactionService the component to ensure proper transactional wrapping + */ + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + /** + * @param stores the content stores to clean + */ + public void setStores(List stores) + { + this.stores = stores; + } + + /** + * @param listeners the listeners that can react to deletions + */ + public void setListeners(List listeners) + { + this.listeners = listeners; + } + + /** + * Set the minimum number of days old that orphaned content must be + * before deletion is possible. The default is 7 days. + * + * @param protectDays minimum age (in days) of deleted content + */ + public void setProtectDays(int protectDays) + { + this.protectDays = protectDays; + } + + /** + * Perform basic checks to ensure that the necessary dependencies were injected. + */ + private void checkProperties() + { + if (dictionaryService == null) + { + throw new AlfrescoRuntimeException("Property 'dictionaryService' not set"); + } + if (nodeDaoService == null) + { + throw new AlfrescoRuntimeException("Property 'nodeDaoService' not set"); + } + if (transactionService == null) + { + throw new AlfrescoRuntimeException("Property 'transactionService' not set"); + } + if (stores == null || stores.size() == 0) + { + throw new AlfrescoRuntimeException("Property 'stores' not set"); + } + if (listeners == null) + { + throw new AlfrescoRuntimeException("Property 'listeners' not set"); + } + } + + private Set getValidUrls() + { + // wrap to make the request in a transaction + TransactionWork> getUrlsWork = new TransactionWork>() + { + public List doWork() throws Exception + { + return nodeDaoService.getContentDataStrings(); + }; + }; + // execute in READ-ONLY txn + List contentDataStrings = TransactionUtil.executeInUserTransaction( + transactionService, + getUrlsWork, + true); + + // get all valid URLs + Set validUrls = new HashSet(contentDataStrings.size()); + // convert the strings to objects and extract the URL + for (String contentDataString : contentDataStrings) + { + ContentData contentData = ContentData.createContentProperty(contentDataString); + if (contentData.getContentUrl() != null) + { + // a URL was present + validUrls.add(contentData.getContentUrl()); + } + } + // done + if (logger.isDebugEnabled()) + { + logger.debug("Found " + validUrls.size() + " valid URLs in metadata"); + } + return validUrls; + } + + public void execute() + { + checkProperties(); + Set validUrls = getValidUrls(); + // now clean each store in turn + for (ContentStore store : stores) + { + clean(validUrls, store); + } + } + + private void clean(Set validUrls, ContentStore store) + { + Date checkAllBeforeDate = new Date(System.currentTimeMillis() - (long) protectDays * 3600L * 1000L * 24L); + // get the store's URLs + Set storeUrls = store.getUrls(null, checkAllBeforeDate); + // remove all URLs that occur in the validUrls + storeUrls.removeAll(validUrls); + // now clean the store + for (String url : storeUrls) + { + ContentReader sourceReader = store.getReader(url); + // announce this to the listeners + for (ContentStoreCleanerListener listener : listeners) + { + // get a fresh reader + ContentReader listenerReader = sourceReader.getReader(); + // call it + listener.beforeDelete(listenerReader); + } + // delete it + store.delete(url); + + if (logger.isDebugEnabled()) + { + logger.debug("Removed URL from store: \n" + + " Store: " + store + "\n" + + " URL: " + url); + } + } + } +} diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerListener.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerListener.java new file mode 100644 index 0000000000..79edbd31f1 --- /dev/null +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerListener.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; + +/** + * A listener that can be plugged into a + * {@link org.alfresco.repo.content.cleanup.ContentStoreCleaner cleaner} to + * move soon-to-be-deleted content to a new location. + * + * @author Derek Hulley + */ +public interface ContentStoreCleanerListener +{ + public void beforeDelete(ContentReader reader) throws ContentIOException; +} diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java new file mode 100644 index 0000000000..34aeeb1cf3 --- /dev/null +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanerTest.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.alfresco.repo.content.ContentStore; +import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.node.db.NodeDaoService; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.dictionary.DictionaryService; +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.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.alfresco.util.TempFileProvider; +import org.springframework.context.ApplicationContext; + +import junit.framework.TestCase; + +/** + * @see org.alfresco.repo.content.cleanup.ContentStoreCleaner + * + * @author Derek Hulley + */ +public class ContentStoreCleanerTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private ContentStoreCleaner cleaner; + private ContentStore store; + private ContentStoreCleanerListener listener; + private List deletedUrls; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean("ServiceRegistry"); + TransactionService transactionService = serviceRegistry.getTransactionService(); + DictionaryService dictionaryService = serviceRegistry.getDictionaryService(); + NodeDaoService nodeDaoService = (NodeDaoService) ctx.getBean("nodeDaoService"); + + // we need a store + store = new FileContentStore(TempFileProvider.getTempDir().getAbsolutePath()); + // and a listener + listener = new DummyCleanerListener(); + // initialise record of deleted URLs + deletedUrls = new ArrayList(5); + + // construct the test cleaner + cleaner = new ContentStoreCleaner(); + cleaner.setTransactionService(transactionService); + cleaner.setDictionaryService(dictionaryService); + cleaner.setNodeDaoService(nodeDaoService); + cleaner.setStores(Collections.singletonList(store)); + cleaner.setListeners(Collections.singletonList(listener)); + } + + public void testImmediateRemoval() throws Exception + { + cleaner.setProtectDays(0); + // add some content to the store + ContentWriter writer = store.getWriter(null, null); + writer.putContent("ABC"); + String contentUrl = writer.getContentUrl(); + + // fire the cleaner + cleaner.execute(); + + // the content should have disappeared as it is not in the database + assertFalse("Unprotected content was not deleted", store.exists(contentUrl)); + assertTrue("Content listener was not called with deletion", deletedUrls.contains(contentUrl)); + } + + public void testProtectedRemoval() throws Exception + { + cleaner.setProtectDays(1); + // add some content to the store + ContentWriter writer = store.getWriter(null, null); + writer.putContent("ABC"); + String contentUrl = writer.getContentUrl(); + + // fire the cleaner + cleaner.execute(); + + // the content should have disappeared as it is not in the database + assertTrue("Protected content was deleted", store.exists(contentUrl)); + assertFalse("Content listener was called with deletion of protected URL", deletedUrls.contains(contentUrl)); + } + + private class DummyCleanerListener implements ContentStoreCleanerListener + { + public void beforeDelete(ContentReader reader) throws ContentIOException + { + deletedUrls.add(reader.getContentUrl()); + } + } +} diff --git a/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanupJob.java b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanupJob.java new file mode 100644 index 0000000000..f2ab482494 --- /dev/null +++ b/source/java/org/alfresco/repo/content/cleanup/ContentStoreCleanupJob.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.quartz.Job; +import org.quartz.JobDataMap; +import org.quartz.JobExecutionContext; +import org.quartz.JobExecutionException; + +/** + * Triggers the deletion of unused content using a + * {@link org.alfresco.repo.content.cleanup.ContentStoreCleaner}. + *

+ * The following parameters are required: + *

    + *
  • contentStoreCleaner: The content store cleaner bean
  • + *
+ * + * @author Derek Hulley + */ +public class ContentStoreCleanupJob implements Job +{ + public ContentStoreCleanupJob() + { + } + + /** + * Calls the cleaner to do its work + */ + public void execute(JobExecutionContext context) throws JobExecutionException + { + JobDataMap jobData = context.getJobDetail().getJobDataMap(); + // extract the content cleaner to use + Object contentStoreCleanerObj = jobData.get("contentStoreCleaner"); + if (contentStoreCleanerObj == null || !(contentStoreCleanerObj instanceof ContentStoreCleaner)) + { + throw new AlfrescoRuntimeException( + "ContentStoreCleanupJob data must contain valid 'contentStoreCleaner' reference"); + } + ContentStoreCleaner contentStoreCleaner = (ContentStoreCleaner) contentStoreCleanerObj; + contentStoreCleaner.execute(); + } +} diff --git a/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java b/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java new file mode 100644 index 0000000000..df7817a640 --- /dev/null +++ b/source/java/org/alfresco/repo/content/cleanup/DeletedContentBackupCleanerListener.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * Licensed under the Mozilla Public License version 1.1 + * with a permitted attribution clause. You may obtain a + * copy of the License at + * + * http://www.alfresco.org/legal/license.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.alfresco.repo.content.cleanup; + +import org.alfresco.repo.content.ContentStore; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Listens for content that is about to be deleted and moves it into the store + * configured as the backup store. + * + * @author Derek Hulley + */ +public class DeletedContentBackupCleanerListener implements ContentStoreCleanerListener +{ + private static Log logger = LogFactory.getLog(DeletedContentBackupCleanerListener.class); + + private ContentStore store; + + public DeletedContentBackupCleanerListener() + { + } + + /** + * Set the store to copy soon-to-be-deleted content into + * + * @param store the deleted content backup store + */ + public void setStore(ContentStore store) + { + this.store = store; + } + + public void beforeDelete(ContentReader reader) throws ContentIOException + { + // write the content into the target store + ContentWriter writer = store.getWriter(null, reader.getContentUrl()); + // copy across + writer.putContent(reader); + // done + if (logger.isDebugEnabled()) + { + logger.debug("Moved content before deletion: \n" + + " URL: " + reader.getContentUrl() + "\n" + + " Store: " + store); + } + } +} diff --git a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml index 452d5f6051..133f2f13e7 100644 --- a/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml +++ b/source/java/org/alfresco/repo/domain/hibernate/Node.hbm.xml @@ -330,6 +330,17 @@ status.changeTxnId = :changeTxnId + + select distinct + props.stringValue + from + org.alfresco.repo.domain.hibernate.NodeImpl as node + join + node.properties props + where + props.stringValue like 'contentUrl%' + + select distinct node diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index 78fe904c47..db651d15e7 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -24,12 +24,14 @@ import java.util.Map; import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.domain.NodeStatus; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.TransactionUtil; import org.alfresco.repo.transaction.TransactionUtil.TransactionWork; import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; @@ -257,4 +259,22 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest throw e; } } + + /** + * Checks that the string_value retrieval against a property type is working + */ + public void testGetContentDataStringValues() throws Exception + { + ContentData contentData = new ContentData("abc", MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null); + // put this in as a random property + nodeService.setProperty( + rootNodeRef, + QName.createQName(NAMESPACE, "random"), + contentData); + // get a list of all content values + List contentDataStrings = nodeDaoService.getContentDataStrings(); + assertNotNull(contentDataStrings); + assertTrue("ContentData not represented as a String in results", + contentDataStrings.contains(contentData.toString())); + } } diff --git a/source/java/org/alfresco/repo/node/db/NodeDaoService.java b/source/java/org/alfresco/repo/node/db/NodeDaoService.java index c24c36b565..5b44d74bc3 100644 --- a/source/java/org/alfresco/repo/node/db/NodeDaoService.java +++ b/source/java/org/alfresco/repo/node/db/NodeDaoService.java @@ -176,4 +176,12 @@ public interface NodeDaoService * returns null. */ public NodeStatus getNodeStatus(String protocol, String identifier, String id); + + /** + * Fetch all content data strings. These are all string values that begin + * with contentUrl=. + * + * @return Returns the string values for content data + */ + public List getContentDataStrings(); } diff --git a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java index 470871a58a..5cdc7401e1 100644 --- a/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/hibernate/HibernateNodeDaoServiceImpl.java @@ -54,11 +54,11 @@ import org.springframework.orm.hibernate3.support.HibernateDaoSupport; */ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements NodeDaoService { - public static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; - public static final String QUERY_GET_CHILD_ASSOC = "node.GetChildAssoc"; - public static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; - public static final String QUERY_GET_NODE_ASSOC_TARGETS = "node.GetNodeAssocTargets"; - public static final String QUERY_GET_NODE_ASSOC_SOURCES = "node.GetNodeAssocSources"; + private static final String QUERY_GET_ALL_STORES = "store.GetAllStores"; + private static final String QUERY_GET_NODE_ASSOC = "node.GetNodeAssoc"; + private static final String QUERY_GET_NODE_ASSOC_TARGETS = "node.GetNodeAssocTargets"; + private static final String QUERY_GET_NODE_ASSOC_SOURCES = "node.GetNodeAssocSources"; + private static final String QUERY_GET_CONTENT_DATA_STRINGS = "node.GetContentDataStrings"; /** a uuid identifying this unique instance */ private String uuid; @@ -504,4 +504,39 @@ public class HibernateNodeDaoServiceImpl extends HibernateDaoSupport implements // remove instance getHibernateTemplate().delete(assoc); } + + @SuppressWarnings("unchecked") + public List getContentDataStrings() + { + HibernateCallback callback = new HibernateCallback() + { + public Object doInHibernate(Session session) + { + Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CONTENT_DATA_STRINGS); + return query.list(); + } + }; + List queryResults = (List) getHibernateTemplate().execute(callback); + return queryResults; + } } + + + + + + + + + + + + + + + + + + + +