Content URLs are now generated with an extra HOUR folder to handle high volume input in one day better

Added cleanup job for content stores
 - content is moved into (alf_data)/contentstore.deleted and mirrors the live content store
 - We'll make a call about disabling the trigger for the job, but currently it will fire at 4am


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2422 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Derek Hulley
2006-02-16 20:01:57 +00:00
parent 530b2b9026
commit 440fa299b4
15 changed files with 621 additions and 252 deletions

View File

@@ -8,6 +8,44 @@
<value>${dir.contentstore}</value>
</constructor-arg>
</bean>
<!-- deleted content will get pushed into this store, where it can be cleaned up at will -->
<bean id="deletedContentStore" class="org.alfresco.repo.content.filestore.FileContentStore">
<constructor-arg>
<value>${dir.contentstore.deleted}</value>
</constructor-arg>
</bean>
<!-- bean to move deleted content into the the backup store -->
<bean id="deletedContentBackupListener" class="org.alfresco.repo.content.cleanup.DeletedContentBackupCleanerListener" >
<property name="store">
<ref bean="deletedContentStore" />
</property>
</bean>
<!-- Performs the content cleanup -->
<bean id="contentStoreCleaner" class="org.alfresco.repo.content.cleanup.ContentStoreCleaner" >
<property name="dictionaryService">
<ref bean="dictionaryService" />
</property>
<property name="nodeDaoService" >
<ref bean="nodeDaoService" />
</property>
<property name="transactionService" >
<ref bean="transactionComponent" />
</property>
<property name="protectDays" >
<value>14</value>
</property>
<property name="stores" >
<list>
<ref bean="fileContentStore" />
</list>
</property>
<property name="listeners" >
<list>
<ref bean="deletedContentBackupListener" />
</list>
</property>
</bean>
<bean id="contentService" class="org.alfresco.repo.content.RoutingContentService" init-method="init">
<property name="transactionService">

View File

@@ -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

View File

@@ -52,32 +52,30 @@
</property>
</bean>
<bean id="fileContentStoreCleanerTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerBean">
<bean id="contentStoreCleanerTrigger" class="org.alfresco.util.TriggerBean">
<property name="jobDetail">
<bean id="fileContentStoreCleanerJobDetail" class="org.springframework.scheduling.quartz.JobDetailBean">
<property name="jobClass">
<value>org.alfresco.repo.content.ContentStoreCleanupJob</value>
<value>org.alfresco.repo.content.cleanup.ContentStoreCleanupJob</value>
</property>
<property name="jobDataAsMap">
<map>
<entry key="contentStore">
<ref bean="fileContentStore" />
</entry>
<entry key="searcher">
<ref bean="searchService" />
</entry>
<entry key="protectHours">
<value>24</value>
<entry key="contentStoreCleaner">
<ref bean="contentStoreCleaner" />
</entry>
</map>
</property>
</bean>
</property>
<property name="startDelay">
<value>600000</value><!-- start after 10 minutes -->
<!-- trigger at 4am -->
<property name="hour">
<value>04</value>
</property>
<property name="minute">
<value>00</value>
</property>
<property name="repeatInterval">
<value>3600000</value><!-- repeat every hour -->
<value>86400000</value> <!-- repeat daily -->
</property>
</bean>
@@ -153,9 +151,7 @@
<property name="triggers">
<list>
<ref bean="tempFileCleanerTrigger" />
<!--
<ref local="fileContentStoreCleanerTrigger"/>
-->
<ref local="contentStoreCleanerTrigger"/>
<ref bean="ftsIndexerTrigger" />
<ref bean="indexRecoveryTrigger" />
<ref bean="indexBackupTrigger" />

View File

@@ -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

View File

@@ -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.
* <p>
* The following parameters are required:
* <ul>
* <li><b>contentStore</b>: The content store bean to clean up</li>
* <li><b>searcher</b>: The index searcher that searches for content in the store</li>
* <li><b>protectHours</b>: The number of hours to protect content that isn't referenced</li>
* </ul>
*
* @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<String> 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();
}
}

View File

@@ -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!");
}
}
}

View File

@@ -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<ContentStore> stores;
private List<ContentStoreCleanerListener> listeners;
private int protectDays;
public ContentStoreCleaner()
{
this.stores = new ArrayList<ContentStore>(0);
this.listeners = new ArrayList<ContentStoreCleanerListener>(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<ContentStore> stores)
{
this.stores = stores;
}
/**
* @param listeners the listeners that can react to deletions
*/
public void setListeners(List<ContentStoreCleanerListener> 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<String> getValidUrls()
{
// wrap to make the request in a transaction
TransactionWork<List<String>> getUrlsWork = new TransactionWork<List<String>>()
{
public List<String> doWork() throws Exception
{
return nodeDaoService.getContentDataStrings();
};
};
// execute in READ-ONLY txn
List<String> contentDataStrings = TransactionUtil.executeInUserTransaction(
transactionService,
getUrlsWork,
true);
// get all valid URLs
Set<String> validUrls = new HashSet<String>(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<String> validUrls = getValidUrls();
// now clean each store in turn
for (ContentStore store : stores)
{
clean(validUrls, store);
}
}
private void clean(Set<String> validUrls, ContentStore store)
{
Date checkAllBeforeDate = new Date(System.currentTimeMillis() - (long) protectDays * 3600L * 1000L * 24L);
// get the store's URLs
Set<String> 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);
}
}
}
}

View File

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

View File

@@ -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<String> 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<String>(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());
}
}
}

View File

@@ -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}.
* <p>
* The following parameters are required:
* <ul>
* <li><b>contentStoreCleaner</b>: The content store cleaner bean</li>
* </ul>
*
* @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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -330,6 +330,17 @@
status.changeTxnId = :changeTxnId
</query>
<query name="node.GetContentDataStrings">
select distinct
props.stringValue
from
org.alfresco.repo.domain.hibernate.NodeImpl as node
join
node.properties props
where
props.stringValue like 'contentUrl%'
</query>
<query name="node.patch.GetNodesWithPersistedSerializableProperties">
select distinct
node

View File

@@ -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<String> contentDataStrings = nodeDaoService.getContentDataStrings();
assertNotNull(contentDataStrings);
assertTrue("ContentData not represented as a String in results",
contentDataStrings.contains(contentData.toString()));
}
}

View File

@@ -176,4 +176,12 @@ public interface NodeDaoService
* returns <code>null</code>.
*/
public NodeStatus getNodeStatus(String protocol, String identifier, String id);
/**
* Fetch all content data strings. These are all string values that begin
* with <b>contentUrl=</b>.
*
* @return Returns the string values for content data
*/
public List<String> getContentDataStrings();
}

View File

@@ -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<String> getContentDataStrings()
{
HibernateCallback callback = new HibernateCallback()
{
public Object doInHibernate(Session session)
{
Query query = session.getNamedQuery(HibernateNodeDaoServiceImpl.QUERY_GET_CONTENT_DATA_STRINGS);
return query.list();
}
};
List<String> queryResults = (List) getHibernateTemplate().execute(callback);
return queryResults;
}
}