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

Commit

Permalink
rhbz996939 Check lock when uploading/clearing TM, report lock errors …
Browse files Browse the repository at this point in the history
…in web UI
  • Loading branch information
seanf committed Aug 16, 2013
1 parent 856da7d commit 831126f
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 29 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Expand Up @@ -32,7 +32,7 @@
<gwteventservice.version>1.2.1</gwteventservice.version>
<okapi.version>0.22</okapi.version>

<zanata.api.version>3.0.1</zanata.api.version>
<zanata.api.version>3.0.2-SNAPSHOT</zanata.api.version>
<!-- This should always be the previous version of the used api version above (but only 3.0.1 or later will work) -->
<zanata.api.compat.version>3.0.1</zanata.api.compat.version>
<zanata.client.version>3.0.1</zanata.client.version>
Expand Down
Expand Up @@ -20,29 +20,35 @@
*/
package org.zanata.action;

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

import java.io.Serializable;
import java.util.List;

import javax.faces.event.ValueChangeEvent;

import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;

import org.jboss.seam.Component;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
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.zanata.dao.TransMemoryDAO;
import org.zanata.exception.EntityMissingException;
import org.zanata.model.tm.TransMemory;
import org.zanata.process.ProcessHandle;
import org.zanata.process.RunnableProcess;
import org.zanata.rest.service.TranslationMemoryResourceService;
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;

/**
* Controller class for the Translation Memory UI.
Expand All @@ -51,11 +57,16 @@
*/
@Name("translationMemoryAction")
@Restrict("#{s:hasRole('admin')}")
@Slf4j
@Scope(ScopeType.PAGE)
public class TranslationMemoryAction extends EntityHome<TransMemory>
{
@In
private TransMemoryDAO transMemoryDAO;

@In
private TranslationMemoryResourceService translationMemoryResource;

@In
private SlugEntityService slugEntityServiceImpl;

Expand All @@ -64,6 +75,18 @@ public class TranslationMemoryAction extends EntityHome<TransMemory>

private List<TransMemory> transMemoryList;

/**
* Stores the last process handle, in page scope (ie for this user).
*/
private ProcessHandle myProcessHandle;

/**
* Stores the last process error, but only for the duration of the event.
*/
@In(scope=ScopeType.EVENT, required=false)
@Out(scope=ScopeType.EVENT, required=false)
private String myProcessError;

public List<TransMemory> getAllTranslationMemories()
{
if( transMemoryList == null )
Expand All @@ -89,35 +112,68 @@ public boolean validateSlug(String slug, String componentId)
return true;
}

@Restrict("#{s:hasRole('admin')}")
public void clearTransMemory(final String transMemorySlug)
{
this.myProcessHandle = new ProcessHandle();
processManagerServiceImpl.startProcess(
new RunnableProcess<ProcessHandle>()
{
@Override
protected void run(ProcessHandle handle) throws Throwable
{
TransMemoryDAO transMemoryDAO = (TransMemoryDAO) Component.getInstance(TransMemoryDAO.class);
transMemoryDAO.deleteTransMemoryContents(transMemorySlug);
TranslationMemoryResourceService tmResource = (TranslationMemoryResourceService) Component.getInstance("translationMemoryResource");
String msg = tmResource.deleteTranslationUnitsUnguarded(transMemorySlug).toString();
log.info(msg);
}
},
new ProcessHandle(),
myProcessHandle,
new ClearTransMemoryProcessKey(transMemorySlug)
);

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

private boolean isProcessing()
{
return myProcessHandle != null;
}

public boolean isProcessErrorPollEnabled()
{
// No need to poll just for process erorrs if we are already polling the table
return isProcessing() && !isTablePollEnabled();
}

/**
* Gets the error (if any) for this user's last Clear operation, if it finished since the last poll.
* NB: If the process has just finished, this method will return the error only until the event scope exists.
* @return
*/
public String getProcessError()
{
if (myProcessError != null) return myProcessError;
if (myProcessHandle != null && myProcessHandle.isFinished())
{
processManagerServiceImpl.removeIfInactive(myProcessHandle);
Throwable error = myProcessHandle.getError();
// remember the result, just until this event finishes
this.myProcessError = error != null ? error.getMessage() : "";
this.myProcessHandle = null;
return myProcessError;
}
return "";
}

@Transactional
public void deleteTransMemory(String transMemorySlug)
{
Optional<TransMemory> transMemory = transMemoryDAO.getBySlug(transMemorySlug);
if (transMemory.isPresent())
try
{
transMemoryDAO.makeTransient(transMemory.get());
translationMemoryResource.deleteTranslationMemory(transMemorySlug);
transMemoryList = null; // Force refresh next time list is requested
}
else
catch (EntityMissingException e)
{
FacesMessages.instance().addFromResourceBundle(ERROR, "jsf.transmemory.TransMemoryNotFound");
}
Expand Down
6 changes: 5 additions & 1 deletion zanata-war/src/main/java/org/zanata/dao/TransMemoryDAO.java
Expand Up @@ -78,9 +78,11 @@ public Optional<TransMemory> getBySlug(@Nonnull String slug)
* this process is broken into multiple transactions and could take a long time.
*
* @param slug Translation memory identifier to clear.
* @return the number of trans units deleted (not including variants)
*/
public void deleteTransMemoryContents(@Nonnull String slug)
public int deleteTransMemoryContents(@Nonnull String slug)
{
int totalDeleted = 0;
Optional<TransMemory> tm = getBySlug(slug);
if (!tm.isPresent())
{
Expand Down Expand Up @@ -118,6 +120,7 @@ public void deleteTransMemoryContents(@Nonnull String slug)
deleted = session.createQuery("delete TransMemoryUnit tu where tu in :tus")
.setParameterList("tus", toRemove)
.executeUpdate();
totalDeleted += deleted;
}
else
{
Expand All @@ -134,6 +137,7 @@ public void deleteTransMemoryContents(@Nonnull String slug)
}
}
while(deleted == batchSize);
return totalDeleted;
}

public @Nullable TransMemoryUnit findTranslationUnit(
Expand Down
Expand Up @@ -41,18 +41,21 @@
import org.zanata.dao.TransMemoryDAO;
import org.zanata.dao.TransMemoryStreamingDAO;
import org.zanata.exception.EntityMissingException;
import org.zanata.exception.ZanataServiceException;
import org.zanata.lock.Lock;
import org.zanata.model.HProject;
import org.zanata.model.HProjectIteration;
import org.zanata.model.HTextFlow;
import org.zanata.model.ITextFlow;
import org.zanata.model.tm.TransMemory;
import org.zanata.model.tm.TransMemoryUnit;
import org.zanata.service.LocaleService;
import org.zanata.service.LockManagerService;
import org.zanata.tmx.TMXParser;
import org.zanata.util.CloseableIterator;

import com.google.common.base.Optional;
@Name("translationMemoryResourceService")
@Name("translationMemoryResource")
@Path("/tm")
@Transactional(TransactionPropagationType.SUPPORTS)
@Slf4j
Expand All @@ -64,6 +67,8 @@ public class TranslationMemoryResourceService implements TranslationMemoryResour
@In
private LocaleService localeServiceImpl;
@In
private LockManagerService lockManagerServiceImpl;
@In
private RestSlugValidator restSlugValidator;
@In
private TextFlowStreamingDAO textFlowStreamDAO;
Expand Down Expand Up @@ -135,26 +140,87 @@ public Response getTranslationMemory(@Nonnull String slug)
@Restrict("#{s:hasRole('admin')}")
public Response updateTranslationMemory(String slug, InputStream input) throws Exception
{
Optional<TransMemory> tm = transMemoryDAO.getBySlug(slug);
tmxParser.parseAndSaveTMX(input, getTM(tm, slug));
return Response.ok().build();
Lock tmLock = lockTM(slug);
try
{
Optional<TransMemory> tm = transMemoryDAO.getBySlug(slug);
tmxParser.parseAndSaveTMX(input, getTM(tm, slug));
return Response.ok().build();
}
finally
{
lockManagerServiceImpl.release(tmLock);
}
}

private TransMemory getTM(Optional<TransMemory> tm, String slug)
{
if (!tm.isPresent())
{
throw new EntityMissingException("Translation memory " + slug + " was not found.");
throw new EntityMissingException("Translation memory '" + slug + "' was not found.");
}
return tm.get();
}

@Restrict("#{s:hasRole('admin')}")
public Object deleteTranslationMemory(String slug) throws EntityMissingException
{
Lock tmLock = lockTM(slug);
try
{
Optional<TransMemory> transMemory = transMemoryDAO.getBySlug(slug);
if (transMemory.isPresent())
{
transMemoryDAO.makeTransient(transMemory.get());
return "Translation memory '"+slug+"' deleted";
}
else
{
throw new EntityMissingException(slug);
}
}
finally
{
lockManagerServiceImpl.release(tmLock);
}
}

@Override
@Restrict("#{s:hasRole('admin')}")
public Response deleteTranslationUnits(String slug)
public Object deleteTranslationUnits(String slug)
{
transMemoryDAO.deleteTransMemoryContents(slug);
return Response.ok().build();
return deleteTranslationUnitsUnguarded(slug);
}

/**
* Deletes without checking security permissions (useful for background
* processes which do the check ahead of time).
* @param slug
* @return
*/
public Object deleteTranslationUnitsUnguarded(String slug)
{
Lock tmLock = lockTM(slug);
try
{
int numDeleted = transMemoryDAO.deleteTransMemoryContents(slug);
return numDeleted + " translation units deleted";
}
finally
{
lockManagerServiceImpl.release(tmLock);
}
}

private Lock lockTM(String slug)
{
Lock tmLock = new Lock("tm", slug);
String owner = lockManagerServiceImpl.attainLockOrReturnOwner(tmLock);
if (owner != null)
{
throw new ZanataServiceException("Translation Memory '"+slug+"' is locked by user: "+owner, 503);
}
return tmLock;
}

private Response buildTMX(
Expand Down
16 changes: 10 additions & 6 deletions zanata-war/src/main/webapp/tm/home.xhtml
Expand Up @@ -58,10 +58,11 @@
false);
req.onreadystatechange = function() {
if (req.readyState != 4) { return; }
if (req.status != 200) {
if (req.status == 503) {
alert("There was an error uploading the file: " + req.responseText + " (" + req.status + ")");
} else if (req.status != 200) {
alert("There was an error uploading the file: " + req.statusText + " (" + req.status + ")");
}
else {
} else {
alert("Successfully imported the file");
}
resetUploadDialog();
Expand Down Expand Up @@ -110,7 +111,9 @@

<s:fragment rendered="#{translationMemoryAction.allTranslationMemories.size() > 0}">
<h:form id="form">

<s:div id="processResult">
#{translationMemoryAction.processError}
</s:div>
<rich:dataTable id="tmList"
value="#{translationMemoryAction.allTranslationMemories}" var="tm">
<rich:column>
Expand Down Expand Up @@ -152,7 +155,7 @@
<s:fragment rendered="#{not translationMemoryAction.isTransMemoryBeingCleared(tm.slug)}">
<a4j:commandButton value="#{messages['jsf.Clear']}"
action="#{translationMemoryAction.clearTransMemory(tm.slug)}"
render="tmListPoll,tmList"
render="tmListPoll,tmList,processResult,processResultPoll"
onclick="return confirmClearTm()"/>
<a4j:commandButton value="#{messages['jsf.Delete']}"
action="#{translationMemoryAction.deleteTransMemory(tm.slug)}"
Expand All @@ -169,7 +172,8 @@

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

0 comments on commit 831126f

Please sign in to comment.