Skip to content
This repository has been archived by the owner on Nov 9, 2017. It is now read-only.

Commit

Permalink
rhbz995964 - Delete Trans Memories asynchronously.
Browse files Browse the repository at this point in the history
This is to prevent the container from cancelling long transactions while deleting large TMs. Also, the database might lock the table for too long.
  • Loading branch information
Carlos A. Munoz committed Aug 13, 2013
1 parent a79bebc commit 86db25a
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 22 deletions.
Expand Up @@ -20,21 +20,28 @@
*/
package org.zanata.action;

import java.io.Serializable;
import java.util.List;
import javax.faces.event.ValueChangeEvent;

import org.jboss.seam.Component;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.security.Restrict;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.framework.EntityHome;
import org.jboss.seam.international.StatusMessage;
import org.zanata.dao.TransMemoryDAO;
import org.zanata.model.tm.TransMemory;
import org.zanata.process.ProcessHandle;
import org.zanata.process.RunnableProcess;
import org.zanata.service.ProcessManagerService;
import org.zanata.service.SlugEntityService;
import com.google.common.base.Optional;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;

import static org.jboss.seam.international.StatusMessage.Severity.ERROR;

/**
Expand All @@ -52,6 +59,9 @@ public class TranslationMemoryAction extends EntityHome<TransMemory>
@In
private SlugEntityService slugEntityServiceImpl;

@In
private ProcessManagerService processManagerServiceImpl;

private List<TransMemory> transMemoryList;

public List<TransMemory> getAllTranslationMemories()
Expand Down Expand Up @@ -79,10 +89,22 @@ public boolean validateSlug(String slug, String componentId)
return true;
}

@Transactional
public void clearTransMemory(String transMemorySlug)
public void clearTransMemory(final String transMemorySlug)
{
transMemoryDAO.deleteTransMemoryContents(transMemorySlug);
processManagerServiceImpl.startProcess(
new RunnableProcess<ProcessHandle>()
{
@Override
protected void run(ProcessHandle handle) throws Throwable
{
TransMemoryDAO transMemoryDAO = (TransMemoryDAO) Component.getInstance(TransMemoryDAO.class);
transMemoryDAO.deleteTransMemoryContents(transMemorySlug);
}
},
new ProcessHandle(),
new ClearTransMemoryProcessKey(transMemorySlug)
);

transMemoryList = null; // Force refresh next time list is requested
}

Expand All @@ -92,7 +114,6 @@ public void deleteTransMemory(String transMemorySlug)
Optional<TransMemory> transMemory = transMemoryDAO.getBySlug(transMemorySlug);
if (transMemory.isPresent())
{
transMemoryDAO.deleteTransMemoryContents(transMemorySlug);
transMemoryDAO.makeTransient(transMemory.get());
transMemoryList = null; // Force refresh next time list is requested
}
Expand All @@ -102,6 +123,31 @@ public void deleteTransMemory(String transMemorySlug)
}
}

public boolean isTransMemoryBeingCleared(String transMemorySlug)
{
ProcessHandle handle = processManagerServiceImpl.getProcessHandle( new ClearTransMemoryProcessKey(transMemorySlug) );
return handle != null && !handle.isFinished();
}

public boolean deleteTransMemoryDisabled(String transMemorySlug)
{
// Translation memories have to be cleared before deleting them
return getTranslationMemorySize(transMemorySlug) > 0;
}

public boolean isTablePollEnabled()
{
// Poll is enabled only when there is something being cleared
for( TransMemory tm : transMemoryList )
{
if( isTransMemoryBeingCleared(tm.getSlug()) )
{
return true;
}
}
return false;
}

public long getTranslationMemorySize(String tmSlug)
{
return transMemoryDAO.getTranslationMemorySize(tmSlug);
Expand All @@ -112,4 +158,17 @@ public String cancel()
// Navigation logic in pages.xml
return "cancel";
}

/**
* Represents a key to index a translation memory clear process.
*
* NB: Eventually this class might need to live outside if there are
* other services that need to control this process.
*/
@AllArgsConstructor
@EqualsAndHashCode
private class ClearTransMemoryProcessKey implements Serializable
{
private String slug;
}
}
69 changes: 61 additions & 8 deletions zanata-war/src/main/java/org/zanata/dao/TransMemoryDAO.java
Expand Up @@ -20,17 +20,19 @@
*/
package org.zanata.dao;

import java.util.Iterator;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Session;
import org.hibernate.search.FullTextSession;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.transaction.Transaction;
import org.zanata.exception.EntityMissingException;
import org.zanata.model.tm.TransMemory;
import org.zanata.model.tm.TransMemoryUnit;
Expand All @@ -46,6 +48,10 @@
@AutoCreate
public class TransMemoryDAO extends AbstractDAOImpl<TransMemory, Long>
{

@In
private FullTextSession session;

public TransMemoryDAO()
{
super(TransMemory.class);
Expand All @@ -66,21 +72,68 @@ public Optional<TransMemory> getBySlug(@Nonnull String slug)
return Optional.absent();
}

@Transactional
/**
* Deletes the contents for a single translation memory.
* Because TMs could potentially contain very large numbers of translation units,
* this process is broken into multiple transactions and could take a long time.
*
* @param slug Translation memory identifier to clear.
*/
public void deleteTransMemoryContents(@Nonnull String slug)
{
Optional<TransMemory> tm = getBySlug(slug);
if (!tm.isPresent())
{
throw new EntityMissingException("Translation memory " + slug + " was not found.");
}
Iterator it = tm.get().getTranslationUnits().iterator();
while(it.hasNext())

final int batchSize = 1000;
int deleted;
do
{
getSession().delete(it.next());
it.remove();
try
{
Transaction.instance().begin();
}
catch (Exception e)
{
throw new RuntimeException(e);
}

List<TransMemoryUnit> toRemove =
session.createQuery("from TransMemoryUnit tu where tu.translationMemory = :tm")
.setParameter("tm", tm.get())
.setFirstResult(0)
.setFetchSize(batchSize)
.list();

// Remove each batch (Takes advantage of CASCADE deletes on the db)
for( TransMemoryUnit tmu : toRemove )
{
session.purge(TransMemoryUnit.class, tmu);
}

if( toRemove.size() > 0 )
{
deleted = session.createQuery("delete TransMemoryUnit tu where tu in :tus")
.setParameterList("tus", toRemove)
.executeUpdate();
}
else
{
deleted = 0;
}

try
{
Transaction.instance().commit();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
makePersistent(tm.get());
while(deleted == batchSize);
}

public @Nullable TransMemoryUnit findTranslationUnit(
Expand Down
19 changes: 18 additions & 1 deletion zanata-war/src/main/resources/db/changelogs/db.changelog-3.1.xml
Expand Up @@ -135,7 +135,24 @@
<addForeignKeyConstraint baseTableName="TransMemoryUnitVariant" baseColumnNames="trans_unit_id"
constraintName="FK_TransUnitVariant_TransUnit"
referencedTableName="TransMemoryUnit"
referencedColumnNames="id"/>
referencedColumnNames="id" />
</changeSet>

<changeSet id="5" author="camunoz@redhat.com">
<comment>Modify Trans Memory Foreign Keys to cascade delete.</comment>
<dropForeignKeyConstraint baseTableName="TransMemoryUnitVariant" constraintName="FK_TransUnitVariant_TransUnit"/>
<dropForeignKeyConstraint baseTableName="TransMemoryUnit" constraintName="FK_tmunit_trans_memory"/>

<addForeignKeyConstraint baseTableName="TransMemoryUnitVariant" baseColumnNames="trans_unit_id"
constraintName="FK_TransUnitVariant_TransUnit"
referencedTableName="TransMemoryUnit"
referencedColumnNames="id"
onDelete="CASCADE"/>
<addForeignKeyConstraint baseTableName="TransMemoryUnit" baseColumnNames="tm_id"
constraintName="FK_tmunit_trans_memory"
referencedTableName="TransMemory"
referencedColumnNames="id"
onDelete="CASCADE"/>
</changeSet>

<changeSet id="1" author="pahuang@redhat.com">
Expand Down
2 changes: 2 additions & 0 deletions zanata-war/src/main/resources/messages.properties
Expand Up @@ -729,10 +729,12 @@ jsf.processmanager.Duration=Duration
jsf.transmemory.Id=Id
jsf.transmemory.Title=Translation Memory
jsf.transmemory.Clear.Title=Clear the contents of a Translation Memory.
jsf.transmemory.Clearing.Message=Clearing...
jsf.transmemory.CreateNew = Create New
jsf.transmemory.ConfirmClearTM=Are you sure you want to remove all content from this Translation Memory?
jsf.transmemory.ConfirmDeleteTM=Are you sure you want to delete this Translation Memory? You will not be able to recover it.
jsf.transmemory.ConfirmExport=Are you sure you want to export this to a TMX file? This operation may take a long time.
jsf.transmemory.Delete.Title=A Translation Memory has to be cleared before deleting.
jsf.transmemory.Export=Export
jsf.transmemory.Export.Title=Export Translation Memory contents into a file.
jsf.transmemory.Import=Import
Expand Down
26 changes: 18 additions & 8 deletions zanata-war/src/main/webapp/tm/home.xhtml
Expand Up @@ -149,18 +149,28 @@
</rich:column>
<rich:column style="text-align: center">
<f:facet name="header">#{messages['jsf.Actions']}</f:facet>
<a4j:commandButton value="#{messages['jsf.Clear']}"
action="#{translationMemoryAction.clearTransMemory(tm.slug)}"
render="tmList"
onclick="return confirmClearTm()"/>
<a4j:commandButton value="#{messages['jsf.Delete']}"
action="#{translationMemoryAction.deleteTransMemory(tm.slug)}"
render="tmList"
onclick="return confirmDeleteTm()"/>
<s:fragment rendered="#{not translationMemoryAction.isTransMemoryBeingCleared(tm.slug)}">
<a4j:commandButton value="#{messages['jsf.Clear']}"
action="#{translationMemoryAction.clearTransMemory(tm.slug)}"
render="tmListPoll,tmList"
onclick="return confirmClearTm()"/>
<a4j:commandButton value="#{messages['jsf.Delete']}"
action="#{translationMemoryAction.deleteTransMemory(tm.slug)}"
render="tmList"
onclick="return confirmDeleteTm()"
disabled="#{translationMemoryAction.deleteTransMemoryDisabled(tm.slug)}"
title="#{translationMemoryAction.deleteTransMemoryDisabled(tm.slug) ? messages['jsf.transmemory.Delete.Title'] : ''}"/>
</s:fragment>
<s:fragment rendered="#{translationMemoryAction.isTransMemoryBeingCleared(tm.slug)}">
#{messages['jsf.transmemory.Clearing.Message']}
</s:fragment>
</rich:column>
</rich:dataTable>

</h:form>
<h:form>
<a4j:poll id="tmListPoll" interval="3000" render="tmListPoll,tmList" enabled="#{translationMemoryAction.tablePollEnabled}"/>
</h:form>
</s:fragment>
</s:decorate>

Expand Down

0 comments on commit 86db25a

Please sign in to comment.