diff --git a/zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java b/zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java index 27263f63dc..541283be1f 100644 --- a/zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java +++ b/zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java @@ -272,7 +272,7 @@ public Map getStatistics( new HashMap(); StringBuilder query = new StringBuilder(); - query.append("select new map (tft.state as state, count(tft) as count, "); + query.append("select new map (tft.state as state, count(tft) as msgCount, "); query.append(" sum(tft.textFlow.wordCount) as wordCount, " + "tft.locale.localeId as locale) "); query.append("from HTextFlowTarget tft "); @@ -300,7 +300,7 @@ public Map getStatistics( // Collect the results for all states for (Map row : stats) { ContentState state = (ContentState) row.get("state"); - Long count = (Long) row.get("count"); + Long msgCount = (Long) row.get("msgCount"); Long wordCount = (Long) row.get("wordCount"); LocaleId localeId = (LocaleId) row.get("locale"); @@ -318,33 +318,18 @@ public Map getStatistics( transUnitWordsMap.put(localeId.getId(), transUnitWords); } - transUnitCount.set(state, count.intValue()); + transUnitCount.set(state, msgCount.intValue()); transUnitWords.set(state, wordCount.intValue()); } - Map totalCounts = - (Map) session - .createQuery( - "select new map ( count(tf) as count, " - + "sum(tf.wordCount) as wordCount ) " - + "from HTextFlow tf " - + "where tf.document.id = :id " - + "and tf.obsolete = false") - .setParameter("id", docId) - .setComment( - "DocumentDAO.getStatisticsMultipleLocales-words") - .setCacheable(true).uniqueResult(); - // Calculate the 'New' counts - Long totalCount = (Long) totalCounts.get("count"); - Long totalWordCount = (Long) totalCounts.get("wordCount"); for (TransUnitCount stat : transUnitCountMap.values()) { stat.set(ContentState.New, - StatisticsUtil.calculateUntranslated(totalCount, stat)); + StatisticsUtil.calculateUntranslated(new Long(stat.getTotal()), stat)); } for (TransUnitWords stat : transUnitWordsMap.values()) { stat.set(ContentState.New, - StatisticsUtil.calculateUntranslated(totalWordCount, stat)); + StatisticsUtil.calculateUntranslated(new Long(stat.getTotal()), stat)); } // Merge into a single Stats object diff --git a/zanata-war/src/main/java/org/zanata/events/TextFlowTargetStateEvent.java b/zanata-war/src/main/java/org/zanata/events/TextFlowTargetStateEvent.java index 0a9b1f6013..88d3391a55 100644 --- a/zanata-war/src/main/java/org/zanata/events/TextFlowTargetStateEvent.java +++ b/zanata-war/src/main/java/org/zanata/events/TextFlowTargetStateEvent.java @@ -36,9 +36,11 @@ public final class TextFlowTargetStateEvent { public static final String EVENT_NAME = "org.zanata.event.HTextFlowTranslated"; + private final Long versionId; private final Long documentId; private final Long textFlowId; private final LocaleId localeId; private final Long textFlowTargetId; private final ContentState newState; + private final ContentState previousState; } diff --git a/zanata-war/src/main/java/org/zanata/service/VersionStateCache.java b/zanata-war/src/main/java/org/zanata/service/VersionStateCache.java new file mode 100644 index 0000000000..08ca92fa33 --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/service/VersionStateCache.java @@ -0,0 +1,45 @@ +/* + * + * * Copyright 2013, 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; + +import org.zanata.common.AbstractTranslationCount; +import org.zanata.common.LocaleId; +import org.zanata.events.TextFlowTargetStateEvent; + +/** + * Defines a Cache Service for project version states. + * + * @author Alex Eng aeng@redhat.com + */ +public interface VersionStateCache { + + /** + * Informs the cache that a text flow has changed its state in a given + * locale. (It's really a Text Flow Target state) + * + */ + void textFlowStateUpdated(TextFlowTargetStateEvent event); + + AbstractTranslationCount getVersionStatistic(Long versionId, + LocaleId localeId); +} diff --git a/zanata-war/src/main/java/org/zanata/service/impl/StatisticsServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/StatisticsServiceImpl.java index a74a7bc895..4cadffb545 100644 --- a/zanata-war/src/main/java/org/zanata/service/impl/StatisticsServiceImpl.java +++ b/zanata-war/src/main/java/org/zanata/service/impl/StatisticsServiceImpl.java @@ -48,6 +48,7 @@ import org.zanata.rest.service.StatisticsResource; import org.zanata.rest.service.ZPathService; import org.zanata.service.TranslationStateCache; +import org.zanata.service.VersionStateCache; import org.zanata.util.DateUtil; import org.zanata.webtrans.shared.model.DocumentStatus; @@ -77,6 +78,9 @@ public class StatisticsServiceImpl implements StatisticsResource { @In private TranslationStateCache translationStateCacheImpl; + @In + private VersionStateCache versionStateCacheImpl; + // TODO Need to refactor this method to get Message statistic by default. // This is to be consistance with UI which uses message stats, and for // calculating remaining hours. @@ -346,16 +350,21 @@ public ContainerTranslationStatistics getDocStatistics(Long documentId, TranslationStatistics wordStatistics = result.getStats(localeId.getId(), StatUnit.WORD); - double remainingHours = - getRemainingHours(wordStatistics.getDraft(), - wordStatistics.getUntranslated()); + // double remainingHours = + // getRemainingHours(wordStatistics.getDraft(), + // wordStatistics.getUntranslated()); - wordStatistics.setRemainingHours(remainingHours); + // wordStatistics.setRemainingHours(remainingHours); TranslationStatistics msgStatistics = result.getStats(localeId.getId(), StatUnit.MESSAGE); - msgStatistics.setRemainingHours(remainingHours); + msgStatistics.setRemainingHours(wordStatistics.getRemainingHours()); return result; } + + public TranslationStatistics getVersionLocaleStatistic(Long versionId, + LocaleId localeId) { + return versionStateCacheImpl.getVersionStatistic(versionId, localeId); + } } diff --git a/zanata-war/src/main/java/org/zanata/service/impl/TranslationServiceImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/TranslationServiceImpl.java index b2cead7ef5..2c9663c712 100644 --- a/zanata-war/src/main/java/org/zanata/service/impl/TranslationServiceImpl.java +++ b/zanata-war/src/main/java/org/zanata/service/impl/TranslationServiceImpl.java @@ -293,10 +293,13 @@ private HLocale validateLocale(LocaleId localeId, /** * Sends out an event to signal that a Text Flow target has been translated */ - private void signalPostTranslateEvent(HTextFlowTarget hTextFlowTarget) { + private void signalPostTranslateEvent(HTextFlowTarget hTextFlowTarget, + ContentState oldState) { if (Events.exists()) { HTextFlow textFlow = hTextFlowTarget.getTextFlow(); - HDocument document = textFlow.getDocument(); + 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( @@ -304,14 +307,12 @@ private void signalPostTranslateEvent(HTextFlowTarget hTextFlowTarget) { // hTextFlowTarget.getLastChanged(), // hTextFlowTarget.getLastModifiedBy().getAccount().getUsername()); - Events.instance() - .raiseTransactionSuccessEvent( - TextFlowTargetStateEvent.EVENT_NAME, - new TextFlowTargetStateEvent(document.getId(), - textFlow.getId(), hTextFlowTarget - .getLocale().getLocaleId(), - hTextFlowTarget.getId(), hTextFlowTarget - .getState())); + Events.instance().raiseTransactionSuccessEvent( + TextFlowTargetStateEvent.EVENT_NAME, + new TextFlowTargetStateEvent(versionId, documentId, + textFlow.getId(), hTextFlowTarget.getLocale() + .getLocaleId(), hTextFlowTarget.getId(), + hTextFlowTarget.getState(), oldState)); } } @@ -319,6 +320,7 @@ private boolean translate(@Nonnull HTextFlowTarget hTextFlowTarget, @Nonnull List contentsToSave, ContentState requestedState, int nPlurals, Boolean requireTranslationReview) { boolean targetChanged = false; + ContentState currentState = hTextFlowTarget.getState(); targetChanged |= setContentIfChanged(hTextFlowTarget, contentsToSave); targetChanged |= setContentStateIfChanged(requestedState, hTextFlowTarget, @@ -338,7 +340,7 @@ private boolean translate(@Nonnull HTextFlowTarget hTextFlowTarget, // fire event after flush if (targetChanged || hTextFlowTarget.getVersionNum() == 0) { - this.signalPostTranslateEvent(hTextFlowTarget); + this.signalPostTranslateEvent(hTextFlowTarget, currentState); } return targetChanged; @@ -659,6 +661,7 @@ protected Boolean work() throws Exception { HTextFlowTarget hTarget = textFlowTargetDAO.getTextFlowTarget( textFlow, hLocale); + ContentState currentState = hTarget.getState(); if (mergeType == MergeType.IMPORT) { removedTargets.remove(hTarget); @@ -713,7 +716,7 @@ protected Boolean work() throws Exception { } textFlowTargetDAO.makePersistent(hTarget); } - signalPostTranslateEvent(hTarget); + signalPostTranslateEvent(hTarget, currentState); } personDAO.flush(); diff --git a/zanata-war/src/main/java/org/zanata/service/impl/VersionStateCacheImpl.java b/zanata-war/src/main/java/org/zanata/service/impl/VersionStateCacheImpl.java new file mode 100644 index 0000000000..fc483cd06e --- /dev/null +++ b/zanata-war/src/main/java/org/zanata/service/impl/VersionStateCacheImpl.java @@ -0,0 +1,139 @@ +/* + * + * * Copyright 2013, 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.io.Serializable; + +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import net.sf.ehcache.CacheManager; + +import org.jboss.seam.Component; +import org.jboss.seam.ScopeType; +import org.jboss.seam.annotations.Create; +import org.jboss.seam.annotations.Destroy; +import org.jboss.seam.annotations.Name; +import org.jboss.seam.annotations.Observer; +import org.jboss.seam.annotations.Scope; +import org.zanata.cache.CacheWrapper; +import org.zanata.cache.EhcacheWrapper; +import org.zanata.common.AbstractTranslationCount; +import org.zanata.common.LocaleId; +import org.zanata.common.TransUnitWords; +import org.zanata.dao.ProjectIterationDAO; +import org.zanata.events.TextFlowTargetStateEvent; +import org.zanata.rest.dto.stats.TranslationStatistics; +import org.zanata.service.VersionStateCache; + +import com.google.common.cache.CacheLoader; + +/** + * @author Alex Eng aeng@redhat.com + */ +@Name("versionStateCacheImpl") +@Scope(ScopeType.APPLICATION) +public class VersionStateCacheImpl implements VersionStateCache { + private static final String BASE = VersionStateCacheImpl.class.getName(); + + private static final String VERSION_STATISTIC_CACHE_NAME = BASE + + ".versionStatisticCache"; + + private CacheManager cacheManager; + + private CacheWrapper versionStatisticCache; + private CacheLoader versionStatisticLoader; + + public VersionStateCacheImpl() { + // constructor for Seam + this.versionStatisticLoader = new VersionStatisticLoader(); + } + + @Create + public void create() { + cacheManager = CacheManager.create(); + versionStatisticCache = + EhcacheWrapper.create(VERSION_STATISTIC_CACHE_NAME, + cacheManager, versionStatisticLoader); + } + + @Destroy + public void destroy() { + cacheManager.shutdown(); + } + + @Observer(TextFlowTargetStateEvent.EVENT_NAME) + @Override + public void textFlowStateUpdated(TextFlowTargetStateEvent event) { + VersionLocaleKey key = + new VersionLocaleKey(event.getVersionId(), event.getLocaleId()); + AbstractTranslationCount stats = versionStatisticCache.get(key); + + if (stats != null) { + stats.decrement(event.getPreviousState(), 1); + stats.increment(event.getNewState(), 1); + versionStatisticCache.put(key, stats); + } + } + + @Override + public AbstractTranslationCount getVersionStatistic(Long versionId, + LocaleId localeId) { + return versionStatisticCache.getWithLoader(new VersionLocaleKey( + versionId, localeId)); + } + + private static class VersionStatisticLoader extends + CacheLoader { + + ProjectIterationDAO getProjectIterationDAO() { + return (ProjectIterationDAO) Component + .getInstance(ProjectIterationDAO.class); + } + + @Override + public AbstractTranslationCount load(VersionLocaleKey key) + throws Exception { + + TransUnitWords transUnitWords = + getProjectIterationDAO().getWordStatsForContainer( + key.getVersionId(), key.getLocaleId()); + +// return new AbstractTranslationCount(transUnitWords, key.getLocaleId() +// .getId()); + return null; //TODO: Create new stats modal (memory efficiency). + } + } + + @AllArgsConstructor + @EqualsAndHashCode + public static final class VersionLocaleKey implements Serializable { + private static final long serialVersionUID = 1L; + + @Getter + private Long versionId; + + @Getter + private LocaleId localeId; + } +} diff --git a/zanata-war/src/main/resources/ehcache.xml b/zanata-war/src/main/resources/ehcache.xml index 2cb9763011..9f67eb36a4 100644 --- a/zanata-war/src/main/resources/ehcache.xml +++ b/zanata-war/src/main/resources/ehcache.xml @@ -80,4 +80,15 @@ statistics="true" /> + +