/*
* Copyright 2012, Red Hat, Inc. and individual contributors as indicated by the
* @author tags. See the copyright.txt file in the distribution for a full
* listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this software; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
* site: http://www.fsf.org.
*/
package org.zanata.service.impl;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import javax.annotation.Nonnull;
import javax.persistence.EntityManager;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.hibernate.HibernateException;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.TransactionPropagationType;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.core.Events;
import org.jboss.seam.security.management.JpaIdentityStore;
import org.jboss.seam.util.Work;
import org.zanata.async.Async;
import org.zanata.async.AsyncTaskHandle;
import org.zanata.async.AsyncTaskResult;
import org.zanata.async.ContainsAsyncMethods;
import org.zanata.common.ContentState;
import org.zanata.common.LocaleId;
import org.zanata.common.MergeType;
import org.zanata.common.util.ContentStateUtil;
import org.zanata.dao.DocumentDAO;
import org.zanata.dao.PersonDAO;
import org.zanata.dao.ProjectIterationDAO;
import org.zanata.dao.TextFlowDAO;
import org.zanata.dao.TextFlowTargetDAO;
import org.zanata.events.DocumentUploadedEvent;
import org.zanata.events.TextFlowTargetStateEvent;
import org.zanata.exception.ZanataServiceException;
import org.zanata.i18n.Messages;
import org.zanata.lock.Lock;
import org.zanata.model.HAccount;
import org.zanata.model.HDocument;
import org.zanata.model.HLocale;
import org.zanata.model.HPerson;
import org.zanata.model.HProjectIteration;
import org.zanata.model.HSimpleComment;
import org.zanata.model.HTextFlow;
import org.zanata.model.HTextFlowTarget;
import org.zanata.model.HTextFlowTargetHistory;
import org.zanata.rest.dto.resource.TextFlowTarget;
import org.zanata.rest.dto.resource.TranslationsResource;
import org.zanata.rest.service.ResourceUtils;
import org.zanata.security.ZanataIdentity;
import org.zanata.service.ActivityService;
import org.zanata.service.LocaleService;
import org.zanata.service.LockManagerService;
import org.zanata.service.TranslationMergeService;
import org.zanata.service.TranslationService;
import org.zanata.service.ValidationService;
import org.zanata.util.ShortString;
import org.zanata.webtrans.shared.model.TransUnitId;
import org.zanata.webtrans.shared.model.TransUnitUpdateInfo;
import org.zanata.webtrans.shared.model.TransUnitUpdateRequest;
import org.zanata.webtrans.shared.model.ValidationAction;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
@Name("translationServiceImpl")
@Scope(ScopeType.STATELESS)
@Transactional
@ContainsAsyncMethods
@Slf4j
public class TranslationServiceImpl implements TranslationService {
@In
private EntityManager entityManager;
@In
private ProjectIterationDAO projectIterationDAO;
@In
private DocumentDAO documentDAO;
@In
private PersonDAO personDAO;
@In
private TextFlowDAO textFlowDAO;
@In
private TextFlowTargetDAO textFlowTargetDAO;
@In
private ResourceUtils resourceUtils;
@In
private LocaleService localeServiceImpl;
@In
private LockManagerService lockManagerServiceImpl;
@In
private ValidationService validationServiceImpl;
@In(value = JpaIdentityStore.AUTHENTICATED_USER, scope = ScopeType.SESSION,
required = false)
private HAccount authenticatedAccount;
@In
private ZanataIdentity identity;
@In
private TranslationMergeServiceFactory translationMergeServiceFactory;
@In
private Messages msgs;
@Override
public List<TranslationResult> translate(LocaleId localeId,
List<TransUnitUpdateRequest> translationRequests) {
return translate(localeId, translationRequests, true);
}
/**
* This is used when reverting translation
*
* @param localeId
* @param translationRequests
* @return
*/
private List<TranslationResult>
translateWithoutValidating(LocaleId localeId,
List<TransUnitUpdateRequest> translationRequests) {
return translate(localeId, translationRequests, false);
}
private List<TranslationResult> translate(LocaleId localeId,
List<TransUnitUpdateRequest> translationRequests,
boolean runValidation) {
List<TranslationResult> results = new ArrayList<TranslationResult>();
// avoid locale check if there is nothing to translate
if (translationRequests.isEmpty()) {
return results;
}
// single locale check - assumes update requests are all from the same
// project-iteration
HTextFlow sampleHTextFlow =
entityManager.find(HTextFlow.class, translationRequests.get(0)
.getTransUnitId().getValue());
HProjectIteration projectIteration =
sampleHTextFlow.getDocument().getProjectIteration();
HLocale hLocale = validateLocale(localeId, projectIteration);
// single permission check - assumes update requests are all from same
// project
validateReviewPermissionIfApplicable(translationRequests,
projectIteration, hLocale);
for (TransUnitUpdateRequest request : translationRequests) {
HTextFlow hTextFlow =
entityManager.find(HTextFlow.class, request
.getTransUnitId().getValue());
TranslationResultImpl result = new TranslationResultImpl();
if (runValidation) {
String validationMessage =
validateTranslations(request.getNewContentState(),
projectIteration, request.getTransUnitId()
.toString(), hTextFlow.getContents(),
request.getNewContents());
if (!StringUtils.isEmpty(validationMessage)) {
log.warn(validationMessage);
result.isSuccess = false;
result.errorMessage = validationMessage;
continue;
}
}
HTextFlowTarget hTextFlowTarget =
textFlowTargetDAO.getOrCreateTarget(hTextFlow, hLocale);
// if hTextFlowTarget is created, any further hibernate fetch will
// trigger an implicit flush
// (which will save this target even if it's not fully ready!!!)
if (request.hasTargetComment()) {
// FIXME this creates orphan comments, and replaces identical
// comments with copies
hTextFlowTarget.setComment(new HSimpleComment(request
.getTargetComment()));
}
result.baseVersion = hTextFlowTarget.getVersionNum();
result.baseContentState = hTextFlowTarget.getState();
if (request.getBaseTranslationVersion() == hTextFlowTarget
.getVersionNum()) {
try {
int nPlurals = getNumPlurals(hLocale, hTextFlow);
result.targetChanged =
translate(hTextFlowTarget,
request.getNewContents(),
request.getNewContentState(), nPlurals,
projectIteration
.getRequireTranslationReview());
result.isSuccess = true;
} catch (HibernateException e) {
result.isSuccess = false;
log.warn("HibernateException while translating");
}
} else {
// concurrent edits not allowed
String errorMessage =
"translation failed for textflow " + hTextFlow.getId()
+ ": base versionNum + "
+ request.getBaseTranslationVersion()
+ " does not match current versionNum "
+ hTextFlowTarget.getVersionNum();
log.warn(errorMessage);
result.errorMessage = errorMessage;
result.isVersionNumConflict = true;
result.isSuccess = false;
}
result.translatedTextFlowTarget = hTextFlowTarget;
results.add(result);
}
return results;
}
private void validateReviewPermissionIfApplicable(
List<TransUnitUpdateRequest> translationRequests,
HProjectIteration projectIteration, HLocale hLocale) {
Optional<TransUnitUpdateRequest> hasReviewRequest =
Iterables.tryFind(translationRequests,
new Predicate<TransUnitUpdateRequest>() {
@Override
public boolean apply(TransUnitUpdateRequest input) {
return isReviewState(input.getNewContentState());
}
});
if (hasReviewRequest.isPresent()) {
identity.checkPermission("translation-review",
projectIteration.getProject(), hLocale);
}
}
private static boolean isReviewState(ContentState contentState) {
return contentState == ContentState.Approved
|| contentState == ContentState.Rejected;
}
/**
* Generate a {@link HLocale} for the given localeId and check that
* translations for this locale are permitted.
*
* @param localeId
* @param projectIteration
* @return the valid hLocale
* @throws ZanataServiceException
* if the locale is not enabled for the project-iteration or
* server
*/
private HLocale validateLocale(LocaleId localeId,
HProjectIteration projectIteration) throws ZanataServiceException {
String projectSlug = projectIteration.getProject().getSlug();
return localeServiceImpl.validateLocaleByProjectIteration(localeId,
projectSlug, projectIteration.getSlug());
}
/**
* Sends out an event to signal that a Text Flow target has been translated
*/
private void signalPostTranslateEvent(Long actorId,
HTextFlowTarget hTextFlowTarget, ContentState oldState) {
if (Events.exists()) {
HTextFlow textFlow = hTextFlowTarget.getTextFlow();
Long documentId = textFlow.getDocument().getId();
Long versionId =
textFlow.getDocument().getProjectIteration().getId();
// TODO remove hasError from DocumentStatus, so that we can pass
// everything else directly to cache
// DocumentStatus docStatus = new DocumentStatus(
// new DocumentId(document.getId(), document.getDocId()), hasError,
// hTextFlowTarget.getLastChanged(),
// hTextFlowTarget.getLastModifiedBy().getAccount().getUsername());
Events.instance().raiseTransactionSuccessEvent(
TextFlowTargetStateEvent.EVENT_NAME,
new TextFlowTargetStateEvent(actorId, versionId,
documentId, textFlow.getId(), hTextFlowTarget
.getLocale().getLocaleId(), hTextFlowTarget
.getId(), hTextFlowTarget.getState(),
oldState));
}
}
private boolean translate(@Nonnull HTextFlowTarget hTextFlowTarget,
@Nonnull List<String> contentsToSave, ContentState requestedState,
int nPlurals, Boolean requireTranslationReview) {
boolean targetChanged = false;
ContentState currentState = hTextFlowTarget.getState();
targetChanged |= setContentIfChanged(hTextFlowTarget, contentsToSave);
targetChanged |=
setContentStateIfChanged(requestedState, hTextFlowTarget,
nPlurals, requireTranslationReview);
if (targetChanged || hTextFlowTarget.getVersionNum() == 0) {
HTextFlow textFlow = hTextFlowTarget.getTextFlow();
hTextFlowTarget.setVersionNum(hTextFlowTarget.getVersionNum() + 1);
hTextFlowTarget.setTextFlowRevision(textFlow.getRevision());
hTextFlowTarget.setLastModifiedBy(authenticatedAccount.getPerson());
log.debug("last modified by :{}", authenticatedAccount.getPerson()
.getName());
}
// save the target histories
entityManager.flush();
// fire event after flush
if (targetChanged || hTextFlowTarget.getVersionNum() == 0) {
this.signalPostTranslateEvent(authenticatedAccount.getPerson()
.getId(), hTextFlowTarget, currentState);
}
return targetChanged;
}
/**
* @return true if the content was changed, false otherwise
*/
private boolean setContentIfChanged(
@Nonnull HTextFlowTarget hTextFlowTarget,
@Nonnull List<String> contentsToSave) {
if (!contentsToSave.equals(hTextFlowTarget.getContents())) {
hTextFlowTarget.setContents(contentsToSave);
return true;
} else {
return false;
}
}
/**
* Check that requestedState is valid for the given content, adjust if
* necessary and set the new state if it has changed.
*
* @return true if the content state or contents list were updated, false
* otherwise
* @see #adjustContentsAndState(org.zanata.model.HTextFlowTarget, int,
* java.util.List)
*/
private boolean setContentStateIfChanged(
@Nonnull ContentState requestedState,
@Nonnull HTextFlowTarget target, int nPlurals,
boolean requireTranslationReview) {
boolean changed = false;
ContentState previousState = target.getState();
target.setState(requestedState);
ArrayList<String> warnings = new ArrayList<String>();
changed |= adjustContentsAndState(target, nPlurals, warnings);
for (String warning : warnings) {
log.warn(warning);
}
if (requireTranslationReview) {
if (isReviewState(target.getState())) {
// reviewer saved it
target.setReviewer(authenticatedAccount.getPerson());
if (previousState == ContentState.New) {
target.setTranslator(authenticatedAccount.getPerson());
}
} else {
target.setTranslator(authenticatedAccount.getPerson());
}
} else {
if (target.getState() == ContentState.Approved) {
target.setState(ContentState.Translated);
}
target.setTranslator(authenticatedAccount.getPerson());
}
if (target.getState() != previousState) {
changed = true;
}
return changed;
}
/**
* Checks target state against its contents. If necessary, modifies target
* state and generates a warning
*
* @param target
* HTextFlowTarget to check/modify
* @param nPlurals
* number of plurals for this locale for this message: use 1 if
* message does not support plurals
* @param warnings
* a warning string will be added if state is adjusted
* @return true if and only if some state was changed
*/
private static boolean adjustContentsAndState(
@Nonnull HTextFlowTarget target, int nPlurals,
@Nonnull List<String> warnings) {
ContentState oldState = target.getState();
String resId = target.getTextFlow().getResId();
boolean contentsChanged =
ensureContentsSize(target, nPlurals, resId, warnings);
List<String> contents = target.getContents();
target.setState(ContentStateUtil.determineState(oldState, contents,
resId, warnings));
boolean stateChanged = (oldState != target.getState());
return contentsChanged || stateChanged;
}
/**
* Ensures that target.contents has exactly legalSize elements
*
* @param target
* HTextFlowTarget to check/modify
* @param legalSize
* required number of contents
* @param resId
* ID of target
* @param warnings
* if elements were added or removed
* @return
*/
private static boolean ensureContentsSize(HTextFlowTarget target,
int legalSize, String resId, @Nonnull List<String> warnings) {
int contentsSize = target.getContents().size();
if (contentsSize < legalSize) {
String warning =
"Should have "
+ legalSize
+ " contents; adding empty strings: TextFlowTarget "
+ resId + " with contents: " + target.getContents();
warnings.add(warning);
List<String> newContents = new ArrayList<String>(legalSize);
newContents.addAll(target.getContents());
while (newContents.size() < legalSize) {
newContents.add("");
}
target.setContents(newContents);
return true;
} else if (contentsSize > legalSize) {
String warning =
"Should have "
+ legalSize
+ " contents; discarding extra strings: TextFlowTarget "
+ resId + " with contents: " + target.getContents();
warnings.add(warning);
List<String> newContents = new ArrayList<String>(legalSize);
for (int i = 0; i < contentsSize; i++) {
String content = target.getContents().get(i);
newContents.add(content);
}
target.setContents(newContents);
return true;
}
return false;
}
@Override
// This will not run in a transaction. Instead, transactions are controlled
// within the method itself.
@Transactional(TransactionPropagationType.NEVER)
@Async
public
Future<List<String>> translateAllInDocAsync(String projectSlug,
String iterationSlug, String docId, LocaleId locale,
TranslationsResource translations, Set<String> extensions,
MergeType mergeType, boolean lock,
AsyncTaskHandle handle) {
// Lock this document for push
Lock transLock = null;
if (lock) {
transLock =
new Lock(projectSlug, iterationSlug, docId, locale, "push");
lockManagerServiceImpl.attain(transLock);
}
List<String> messages = Lists.newArrayList();
try {
messages =
this.translateAllInDoc(projectSlug, iterationSlug, docId,
locale, translations, extensions, mergeType, handle);
} finally {
if (lock) {
lockManagerServiceImpl.release(transLock);
}
}
return AsyncTaskResult.taskResult(messages);
}
/**
* Run enforced validation check(Error) if target has changed and
* translation saving as 'Translated' or 'Approved'
*
* @param newState
* @param projectVersion
* @param targetId
* @param sources
* @param translations
* @return error messages
*/
private String validateTranslations(ContentState newState,
HProjectIteration projectVersion, String targetId,
List<String> sources, List<String> translations) {
String message = null;
if (newState.isTranslated()) {
List<String> validationMessages =
validationServiceImpl.validateWithServerRules(
projectVersion, sources, translations,
ValidationAction.State.Error);
if (!validationMessages.isEmpty()) {
StringBuilder sb = new StringBuilder();
for (String validationMessage : validationMessages) {
sb.append(validationMessage).append("\n");
}
message =
msgs.format("jsf.TranslationContainsError",
ShortString.shorten(translations.get(0)),
sb.toString());
}
}
return message;
}
@Override
public List<String> translateAllInDoc(final String projectSlug,
final String iterationSlug, final String docId,
final LocaleId locale, final TranslationsResource translations,
final Set<String> extensions, final MergeType mergeType) {
return translateAllInDoc(projectSlug, iterationSlug, docId, locale,
translations, extensions, mergeType, null);
}
@Override
public List<String> translateAllInDoc(final String projectSlug,
final String iterationSlug, final String docId,
final LocaleId locale, final TranslationsResource translations,
final Set<String> extensions, final MergeType mergeType,
AsyncTaskHandle handle) {
final HProjectIteration hProjectIteration =
projectIterationDAO.getBySlug(projectSlug, iterationSlug);
if (hProjectIteration == null) {
throw new ZanataServiceException("Version '" + iterationSlug
+ "' for project '" + projectSlug + "' ");
}
if (mergeType == MergeType.IMPORT) {
identity.checkPermission("import-translation", hProjectIteration);
}
ResourceUtils.validateExtensions(extensions);
log.debug("pass evaluate");
final HDocument document =
documentDAO.getByDocIdAndIteration(hProjectIteration, docId);
if (document == null || document.isObsolete()) {
throw new ZanataServiceException("A document was not found.", 404);
}
log.debug("start put translations entity:{}", translations);
boolean changed = false;
final HLocale hLocale =
localeServiceImpl.validateLocaleByProjectIteration(locale,
projectSlug, iterationSlug);
final Optional<AsyncTaskHandle> handleOp =
Optional.fromNullable(handle);
if (handleOp.isPresent()) {
handleOp.get().setMaxProgress(
translations.getTextFlowTargets().size());
}
try {
changed |= new Work<Boolean>() {
@Override
protected Boolean work() throws Exception {
// handle extensions
boolean changed =
resourceUtils
.transferFromTranslationsResourceExtensions(
translations.getExtensions(true),
document, extensions, hLocale,
mergeType);
return changed;
}
}.workInTransaction();
} catch (Exception e) {
throw new ZanataServiceException("Error during translation.", 500,
e);
}
// NB: removedTargets only applies for MergeType.IMPORT
final Collection<HTextFlowTarget> removedTargets =
new HashSet<HTextFlowTarget>();
final List<String> warnings = new ArrayList<String>();
if (mergeType == MergeType.IMPORT) {
for (HTextFlow textFlow : document.getTextFlows()) {
HTextFlowTarget hTarget =
textFlow.getTargets().get(hLocale.getId());
if (hTarget != null) {
removedTargets.add(hTarget);
}
}
}
// Break the target into batches
List<List<TextFlowTarget>> batches =
Lists.partition(translations.getTextFlowTargets(), BATCH_SIZE);
for (final List<TextFlowTarget> batch : batches) {
try {
SaveBatchWork work = new SaveBatchWork();
work.setExtensions(extensions);
work.setWarnings(warnings);
work.setLocale(hLocale);
work.setDocument(document);
work.setMergeType(mergeType);
work.setRemovedTargets(removedTargets);
work.setHandleOp(handleOp);
work.setProjectIterationId(hProjectIteration.getId());
work.setBatch(batch);
changed |= work.workInTransaction();
} catch (Exception e) {
throw new ZanataServiceException("Error during translation.",
500, e);
}
}
if (changed || !removedTargets.isEmpty()) {
try {
new Work<Void>() {
@Override
protected Void work() throws Exception {
for (HTextFlowTarget target : removedTargets) {
target =
textFlowTargetDAO.findById(target.getId(),
true); // need to refresh from
// persistence
target.clear();
}
textFlowTargetDAO.flush();
documentDAO.flush();
return null;
}
}.workInTransaction();
if (Events.exists()) {
Long actorId = authenticatedAccount.getPerson().getId();
Events.instance().raiseEvent(
DocumentUploadedEvent.EVENT_NAME,
new DocumentUploadedEvent(actorId,
document.getId(), false, hLocale
.getLocaleId()));
}
} catch (Exception e) {
throw new ZanataServiceException("Error during translation.",
500, e);
}
}
return warnings;
}
private int getNumPlurals(HLocale hLocale, HTextFlow textFlow) {
int nPlurals;
if (!textFlow.isPlural()) {
nPlurals = 1;
} else {
nPlurals =
resourceUtils
.getNumPlurals(textFlow.getDocument(), hLocale);
}
return nPlurals;
}
@Getter
@Setter
private final class SaveBatchWork extends Work<Boolean> {
private Set<String> extensions;
private List<String> warnings;
private HLocale locale;
private HDocument document;
private MergeType mergeType;
private Collection<HTextFlowTarget> removedTargets;
private Optional<AsyncTaskHandle> handleOp;
private Long projectIterationId;
private List<TextFlowTarget> batch;
@Override
protected Boolean work() throws Exception {
boolean changed = false;
// we need a fresh object in this session,
// so that it can lazily load associated objects
HProjectIteration iteration =
projectIterationDAO.findById(projectIterationId);
Map<String, HTextFlow> resIdToTextFlowMap = textFlowDAO.getByDocumentAndResIds(document, Lists.transform(
batch, new Function<TextFlowTarget, String>() {
@Override
public String apply(TextFlowTarget input) {
return input.getResId();
}
}));
final int numPlurals = resourceUtils
.getNumPlurals(document, locale);
for (TextFlowTarget incomingTarget : batch) {
String resId = incomingTarget.getResId();
String sourceHash = incomingTarget.getSourceHash();
HTextFlow textFlow = resIdToTextFlowMap.get(resId);
if (textFlow == null) {
// return warning for unknown resId to caller
String warning =
"Could not find TextFlow for TextFlowTarget "
+ resId + " with contents: "
+ incomingTarget.getContents();
warnings.add(warning);
log.warn("skipping TextFlowTarget with unknown resId: {}",
resId);
} else if (sourceHash != null
&& !sourceHash.equals(textFlow.getContentHash())) {
String warning =
MessageFormat
.format("TextFlowTarget {0} may be obsolete; "
+ "associated source hash: {1}; "
+ "expected hash is {2} for source: {3}",
resId, sourceHash,
textFlow.getContentHash(),
textFlow.getContents());
warnings.add(warning);
log.warn(
"skipping TextFlowTarget {} with unknown sourceHash: {}",
resId, sourceHash);
} else {
String validationMessage =
validateTranslations(incomingTarget.getState(),
iteration,
incomingTarget.getResId(),
textFlow.getContents(),
incomingTarget.getContents());
if (!StringUtils.isEmpty(validationMessage)) {
warnings.add(validationMessage);
log.warn(validationMessage);
continue;
}
int nPlurals = textFlow.isPlural() ? numPlurals : 1;
// we have eagerly loaded all targets upfront
HTextFlowTarget hTarget = textFlow.getTargets().get(locale.getId());
ContentState currentState = ContentState.New;
if (hTarget != null) {
currentState = hTarget.getState();
}
if (mergeType == MergeType.IMPORT) {
removedTargets.remove(hTarget);
}
TranslationMergeServiceFactory.MergeContext mergeContext =
new TranslationMergeServiceFactory.MergeContext(
mergeType, textFlow, locale, hTarget,
nPlurals);
TranslationMergeService mergeService =
translationMergeServiceFactory
.getMergeService(mergeContext);
boolean targetChanged =
mergeService.merge(incomingTarget, hTarget,
extensions);
if (hTarget == null) {
// in case hTarget was null, we need to
// retrieve it after merge
hTarget = textFlow.getTargets().get(locale.getId());
}
targetChanged |=
adjustContentsAndState(hTarget, nPlurals, warnings);
// update translation information if applicable
if (targetChanged) {
hTarget.setVersionNum(hTarget.getVersionNum() + 1);
changed = true;
Long actorId;
if (incomingTarget.getTranslator() != null) {
String email =
incomingTarget.getTranslator().getEmail();
HPerson hPerson = personDAO.findByEmail(email);
if (hPerson == null) {
hPerson = new HPerson();
hPerson.setEmail(email);
hPerson.setName(incomingTarget.getTranslator()
.getName());
personDAO.makePersistent(hPerson);
}
hTarget.setTranslator(hPerson);
hTarget.setLastModifiedBy(hPerson);
actorId = hPerson.getId();
} else {
hTarget.setTranslator(null);
hTarget.setLastModifiedBy(null);
actorId = null;
}
textFlowTargetDAO.makePersistent(hTarget);
signalPostTranslateEvent(actorId, hTarget, currentState);
}
}
if (handleOp.isPresent()) {
handleOp.get().increaseProgress(1);
}
}
// every batch will start with a new hibernate session therefore no
// need to call clear
textFlowTargetDAO.flush();
return changed;
}
}
public static class TranslationResultImpl implements TranslationResult {
private HTextFlowTarget translatedTextFlowTarget;
private boolean isSuccess;
private boolean targetChanged = false;
private boolean isVersionNumConflict = false;
private int baseVersion;
private ContentState baseContentState;
private String errorMessage;
@Override
public boolean isTranslationSuccessful() {
return isSuccess;
}
@Override
public boolean isVersionNumConflict() {
return isVersionNumConflict;
}
@Override
public boolean isTargetChanged() {
return targetChanged;
}
@Override
public HTextFlowTarget getTranslatedTextFlowTarget() {
return translatedTextFlowTarget;
}
@Override
public int getBaseVersionNum() {
return baseVersion;
}
@Override
public ContentState getBaseContentState() {
return baseContentState;
}
@Override
public String getErrorMessage() {
return errorMessage;
}
}
@Override
public List<TranslationResult> revertTranslations(LocaleId localeId,
List<TransUnitUpdateInfo> translationsToRevert) {
List<TranslationResult> results = new ArrayList<TranslationResult>();
List<TransUnitUpdateRequest> updateRequests =
new ArrayList<TransUnitUpdateRequest>();
if (!translationsToRevert.isEmpty()) {
HTextFlow sampleHTextFlow =
entityManager.find(HTextFlow.class, translationsToRevert
.get(0).getTransUnit().getId().getValue());
HLocale hLocale =
validateLocale(localeId, sampleHTextFlow.getDocument()
.getProjectIteration());
for (TransUnitUpdateInfo info : translationsToRevert) {
if (!info.isSuccess() || !info.isTargetChanged()) {
continue;
}
TransUnitId tuId = info.getTransUnit().getId();
HTextFlow hTextFlow =
entityManager.find(HTextFlow.class, tuId.getValue());
HTextFlowTarget hTextFlowTarget =
textFlowTargetDAO.getOrCreateTarget(hTextFlow, hLocale);
// check that version has not advanced
// TODO probably also want to check that source has not been
// updated
Integer versionNum = hTextFlowTarget.getVersionNum();
log.debug(
"about to revert hTextFlowTarget version {} to TransUnit version {}",
versionNum, info.getTransUnit().getVerNum());
if (versionNum.equals(info.getTransUnit().getVerNum())) {
// look up replaced version
HTextFlowTargetHistory oldTarget =
hTextFlowTarget.getHistory().get(
info.getPreviousVersionNum());
if (oldTarget != null) {
// generate request
List<String> oldContents = oldTarget.getContents();
ContentState oldState = oldTarget.getState();
TransUnitUpdateRequest request =
new TransUnitUpdateRequest(tuId, oldContents,
oldState, versionNum);
// add to list
updateRequests.add(request);
} else {
log.info(
"got null previous target for tu with id {}, version {}. Assuming previous state is untranslated",
hTextFlow.getId(), info.getPreviousVersionNum());
List<String> emptyContents = Lists.newArrayList();
for (int i = 0; i < hTextFlowTarget.getContents()
.size(); i++) {
emptyContents.add("");
}
TransUnitUpdateRequest request =
new TransUnitUpdateRequest(tuId, emptyContents,
ContentState.New, versionNum);
updateRequests.add(request);
}
} else {
log.info(
"attempt to revert target version {} for tu with id {}, but current version is {}. Not reverting.",
new Object[] { info.getTransUnit().getVerNum(),
tuId, versionNum });
results.add(buildFailResult(hTextFlowTarget));
}
}
}
results.addAll(translateWithoutValidating(localeId, updateRequests));
return results;
}
/**
* @param hTextFlowTarget
* @return
*/
private TranslationResultImpl buildFailResult(
HTextFlowTarget hTextFlowTarget) {
TranslationResultImpl result = new TranslationResultImpl();
result.baseVersion = hTextFlowTarget.getVersionNum();
result.baseContentState = hTextFlowTarget.getState();
result.isSuccess = false;
result.isVersionNumConflict = false;
result.translatedTextFlowTarget = hTextFlowTarget;
result.errorMessage = null;
return result;
}
}