-
+
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;
+ }
}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+