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

Commit

Permalink
Implement cache for last modified information per document per locale
Browse files Browse the repository at this point in the history
  • Loading branch information
Alex Eng committed Mar 11, 2013
1 parent b164863 commit bc9a29b
Show file tree
Hide file tree
Showing 11 changed files with 197 additions and 28 deletions.
14 changes: 6 additions & 8 deletions zanata-war/src/main/java/org/zanata/dao/DocumentDAO.java
Expand Up @@ -24,7 +24,6 @@
import org.zanata.model.HLocale;
import org.zanata.model.HProjectIteration;
import org.zanata.model.HRawDocument;
import org.zanata.model.HTextFlowTarget;
import org.zanata.model.StatusCount;

@Name("documentDAO")
Expand Down Expand Up @@ -151,25 +150,24 @@ public Long getTotalWordCountForDocument(HDocument document)

}

public HTextFlowTarget getLastTranslated(Long docId, LocaleId localeId)
public Long getLastTranslatedTargetId(Long documentId, LocaleId localeId)
{
StringBuilder query = new StringBuilder();
query.append("from HTextFlowTarget tft ");
query.append("select tft.id from HTextFlowTarget tft ");
query.append("where tft.textFlow.document.id = :docId ");
query.append("and tft.locale.localeId = :localeId ");
query.append("order by tft.lastChanged DESC");

Query q = getSession().createQuery(query.toString());
q.setParameter("docId", docId);
q.setParameter("docId", documentId);
q.setParameter("localeId", localeId);
q.setCacheable(true);
q.setMaxResults(1);
q.setComment("DocumentDAO.getLastTranslated");
return (HTextFlowTarget) q.uniqueResult();

return (Long) q.uniqueResult();
}


/**
* @see ProjectIterationDAO#getStatisticsForContainer(Long, LocaleId)
* @param docId
Expand Down
12 changes: 12 additions & 0 deletions zanata-war/src/main/java/org/zanata/dao/TextFlowTargetDAO.java
Expand Up @@ -280,6 +280,18 @@ public HTextFlowTarget getTextFlowTarget(HTextFlow hTextFlow, HLocale hLocale)
.uniqueResult();
return hTextFlowTarget;
}

public HTextFlowTarget getTextFlowTarget(HTextFlow hTextFlow, LocaleId localeId)
{
HTextFlowTarget hTextFlowTarget =
(HTextFlowTarget)getSession().createQuery(
"select tft from HTextFlowTarget tft where tft.textFlow = :textFlow and tft.locale.localeId = :localeId")
.setParameter("textFlow", hTextFlow)
.setParameter("localeId", localeId)
.setComment("TextFlowTargetDAO.getTextFlowTarget")
.uniqueResult();
return hTextFlowTarget;
}

public HTextFlowTarget getLastTranslated(String projectSlug, String iterationSlug, LocaleId localeId)
{
Expand Down
Expand Up @@ -24,39 +24,51 @@
import org.apache.lucene.util.OpenBitSet;
import org.zanata.common.ContentState;
import org.zanata.common.LocaleId;
import org.zanata.model.HTextFlowTarget;

/**
* Defines a Cache Service for translation states.
*
* @author Carlos Munoz <a href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*
* @author Carlos Munoz <a
* href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*/
public interface TranslationStateCache
{
/**
* Returns a {@link OpenBitSet} of translated text flows, where the bits represent
* the Ids of {@link org.zanata.model.HTextFlow} entries that have been translated
* for the given Locale Id
*
* Returns a {@link OpenBitSet} of translated text flows, where the bits
* represent the Ids of {@link org.zanata.model.HTextFlow} entries that have
* been translated for the given Locale Id
*
* @param localeId
* @return An OpenBitSet
*/
OpenBitSet getTranslatedTextFlowIds(LocaleId localeId);

/**
* Returns a Lucene Filter which only returns {@link org.zanata.model.HTextFlow}s which have been translated
* for the given Locale Id
* Returns a Lucene Filter which only returns
* {@link org.zanata.model.HTextFlow}s which have been translated for the
* given Locale Id
*
* @param targetLocale
* @return
*/
Filter getFilter(LocaleId localeId);

/**
* Informs the cache that a text flow has changed its state in a given locale.
* (It's really a Text Flow Target state)
*
* Informs the cache that a text flow has changed its state in a given
* locale. (It's really a Text Flow Target state)
*
* @param textFlowId The id of the text flow that has changed state.
* @param localeId The locale for which state has changed.
* @param newState The new state after the change.
*/
void textFlowStateUpdated(Long textFlowId, LocaleId localeId, ContentState newState);

/**
* Returns last modified HTextFlowTarget for the given locale id of the documentId
* @param documentId
* @param localeId
* @return
*/
HTextFlowTarget getLastModifiedTextFlowTarget(Long documentId, LocaleId localeId);
}
Expand Up @@ -52,6 +52,7 @@
import org.zanata.rest.dto.stats.TranslationStatistics;
import org.zanata.rest.service.StatisticsResource;
import org.zanata.rest.service.ZPathService;
import org.zanata.service.TranslationStateCache;
import org.zanata.util.DateUtil;

/**
Expand Down Expand Up @@ -82,6 +83,9 @@ public class StatisticsServiceImpl implements StatisticsResource
@In
private ZPathService zPathService;

@In
private TranslationStateCache translationStateCacheImpl;

@Override
public ContainerTranslationStatistics getStatistics(String projectSlug, String iterationSlug, boolean includeDetails, boolean includeWordStats, String[] locales)
{
Expand Down Expand Up @@ -231,7 +235,7 @@ public ContainerTranslationStatistics getStatistics(String projectSlug, String i
{
count = stats.getUnitCount();
}
HTextFlowTarget target = documentDAO.getLastTranslated(document.getId(), locale);
HTextFlowTarget target = translationStateCacheImpl.getLastModifiedTextFlowTarget(document.getId(), locale);
TranslationStatistics transUnitStats = getMessageStats(count, locale, target);
transUnitStats.setRemainingHours(getRemainingHours(count.get(ContentState.NeedReview), count.get(ContentState.New)));
docStats.addStats(transUnitStats);
Expand Down
Expand Up @@ -20,6 +20,13 @@
*/
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.apache.lucene.search.Filter;
import org.apache.lucene.util.OpenBitSet;
import org.jboss.seam.ScopeType;
Expand All @@ -33,13 +40,15 @@
import org.zanata.cache.EhcacheWrapper;
import org.zanata.common.ContentState;
import org.zanata.common.LocaleId;
import org.zanata.dao.DocumentDAO;
import org.zanata.dao.TextFlowDAO;
import org.zanata.dao.TextFlowTargetDAO;
import org.zanata.model.HTextFlow;
import org.zanata.model.HTextFlowTarget;
import org.zanata.service.TranslationStateCache;

import com.google.common.cache.CacheLoader;

import net.sf.ehcache.CacheManager;

/**
* Default Implementation of the Translation State Cache.
*
Expand All @@ -53,29 +62,42 @@ public class TranslationStateCacheImpl implements TranslationStateCache
private static final String BASE = TranslationStateCacheImpl.class.getName();
private static final String CACHE_NAME = BASE+".filterCache";
private static final String TRANSLATED_TEXT_FLOW_CACHE_NAME = BASE+".translatedTextFlowCache";
private static final String LAST_MODIFIED_TFT_CACHE_NAME = BASE + ".lastModifiedCache";

@In
private TextFlowDAO textFlowDAO;

@In
private DocumentDAO documentDAO;

@In
private TextFlowTargetDAO textFlowTargetDAO;

private CacheManager cacheManager;
private CacheWrapper<LocaleId, TranslatedTextFlowFilter> filterCache;
private CacheWrapper<LocaleId, OpenBitSet> translatedTextFlowCache;
private CacheWrapper<TranslatedDocumentKey, Long> lastModifiedCache;
private CacheLoader<LocaleId, TranslatedTextFlowFilter> filterLoader;
private CacheLoader<LocaleId, OpenBitSet> bitsetLoader;
private CacheLoader<TranslatedDocumentKey, Long> lastModifiedLoader;

public TranslationStateCacheImpl()
{
// constructor for Seam
this.filterLoader = new FilterLoader();
this.bitsetLoader = new BitsetLoader();
this.lastModifiedLoader = new HTextFlowTargetIdLoader();
}

public TranslationStateCacheImpl(
CacheLoader<LocaleId, TranslatedTextFlowFilter> filterLoader,
CacheLoader<LocaleId, OpenBitSet> bitsetLoader)
CacheLoader<LocaleId, OpenBitSet> bitsetLoader, CacheLoader<TranslatedDocumentKey, Long> lastModifiedLoader, TextFlowTargetDAO textFlowTargetDAO)
{
// constructor for testing
this.filterLoader = filterLoader;
this.bitsetLoader = bitsetLoader;
this.lastModifiedLoader = lastModifiedLoader;
this.textFlowTargetDAO = textFlowTargetDAO;
}

@Create
Expand All @@ -84,6 +106,7 @@ public void create()
cacheManager = CacheManager.create();
filterCache = EhcacheWrapper.create(CACHE_NAME, cacheManager, filterLoader);
translatedTextFlowCache = EhcacheWrapper.create(TRANSLATED_TEXT_FLOW_CACHE_NAME, cacheManager, bitsetLoader);
lastModifiedCache = EhcacheWrapper.create(LAST_MODIFIED_TFT_CACHE_NAME, cacheManager, lastModifiedLoader);
}

@Destroy
Expand All @@ -103,6 +126,7 @@ public void textFlowStateUpdated(Long textFlowId, LocaleId localeId, ContentStat
{
updateTranslatedTextFlowCache(textFlowId, localeId, newState);
updateFilterCache(textFlowId, localeId, newState);
updateLastModifiedCache(textFlowId, localeId, newState);
}

@Override
Expand All @@ -111,6 +135,13 @@ public Filter getFilter(LocaleId locale)
return filterCache.getWithLoader(locale);
}

@Override
public HTextFlowTarget getLastModifiedTextFlowTarget(Long documentId, LocaleId localeId)
{
Long targetId = lastModifiedCache.getWithLoader(new TranslatedDocumentKey(documentId, localeId));
return textFlowTargetDAO.findById(targetId, false);
}

private void updateFilterCache(Long textFlowId, LocaleId localeId, ContentState newState)
{
TranslatedTextFlowFilter filter = filterCache.get(localeId);
Expand All @@ -120,6 +151,15 @@ private void updateFilterCache(Long textFlowId, LocaleId localeId, ContentState
}
}

private void updateLastModifiedCache(Long textFlowId, LocaleId localeId, ContentState newState)
{
HTextFlow tf = textFlowDAO.findById(textFlowId, false);
Long documentId = tf.getDocument().getId();

HTextFlowTarget target = textFlowTargetDAO.getTextFlowTarget(tf, localeId);
lastModifiedCache.put(new TranslatedDocumentKey(documentId, localeId), target.getId());
}

private void updateTranslatedTextFlowCache(Long textFlowId, LocaleId localeId, ContentState newState)
{
OpenBitSet bitSet = translatedTextFlowCache.get(localeId);
Expand Down Expand Up @@ -155,4 +195,26 @@ public TranslatedTextFlowFilter load(LocaleId localeId) throws Exception
}
}

private final class HTextFlowTargetIdLoader extends CacheLoader<TranslatedDocumentKey, Long>
{
@Override
public Long load(TranslatedDocumentKey key) throws Exception
{
return documentDAO.getLastTranslatedTargetId(key.getDocumentId(), key.getLocaleId());
}
}

@AllArgsConstructor
@EqualsAndHashCode
public static final class TranslatedDocumentKey implements Serializable
{
private static final long serialVersionUID = 1L;

@Getter
private Long documentId;

@Getter
private LocaleId localeId;
}

}
Expand Up @@ -23,6 +23,7 @@
import org.zanata.model.HTextFlowTarget;
import org.zanata.security.ZanataIdentity;
import org.zanata.service.TranslationFileService;
import org.zanata.service.TranslationStateCache;
import org.zanata.webtrans.server.ActionHandlerFor;
import org.zanata.webtrans.shared.model.DocumentId;
import org.zanata.webtrans.shared.model.DocumentInfo;
Expand All @@ -44,6 +45,9 @@ public class GetDocumentListHandler extends AbstractActionHandler<GetDocumentLis
@In
private DocumentDAO documentDAO;

@In
private TranslationStateCache translationStateCacheImpl;

@In
private TranslationFileService translationFileServiceImpl;

Expand All @@ -63,7 +67,7 @@ public GetDocumentListResult execute(GetDocumentList action, ExecutionContext co
{
DocumentId docId = new DocumentId(hDoc.getId(), hDoc.getDocId());
TranslationStats stats = documentDAO.getStatistics(hDoc.getId(), localeId);
HTextFlowTarget result = documentDAO.getLastTranslated(hDoc.getId(), localeId);
HTextFlowTarget result = translationStateCacheImpl.getLastModifiedTextFlowTarget(hDoc.getId(), localeId);

Date lastTranslatedDate = null;
String lastTranslatedBy = "";
Expand Down
11 changes: 11 additions & 0 deletions zanata-war/src/main/resources/META-INF/ehcache.xml
Expand Up @@ -57,5 +57,16 @@
memoryStoreEvictionPolicy="LRU"
statistics="true"
/>

<cache
name="org.zanata.service.impl.TranslationStateCacheImpl.lastModifiedCache"
maxElementsInMemory="400"
eternal="false"
timeToIdleSeconds="86400"
timeToLiveSeconds="0"
overflowToDisk="false"
memoryStoreEvictionPolicy="LRU"
statistics="true"
/>

</ehcache>
Expand Up @@ -36,17 +36,27 @@
import org.jboss.seam.mock.EnhancedMockHttpServletRequest;
import org.jboss.seam.mock.EnhancedMockHttpServletResponse;
import org.jboss.seam.mock.ResourceRequestEnvironment;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.zanata.ZanataRawRestTest;
import org.zanata.rest.dto.stats.ContainerTranslationStatistics;
import org.zanata.rest.dto.stats.TranslationStatistics;
import org.zanata.seam.SeamAutowire;
import org.zanata.service.TranslationStateCache;

/**
* @author Carlos Munoz <a href="mailto:camunoz@redhat.com">camunoz@redhat.com</a>
*/
@Test(groups = {"seam-tests"})
public class StatisticsRestTest extends ZanataRawRestTest
{
@Mock
private TranslationStateCache translationStateCacheImpl;

private SeamAutowire seam = SeamAutowire.instance();

@Override
protected void prepareDBUnitOperations()
{
Expand All @@ -57,6 +67,17 @@ protected void prepareDBUnitOperations()
beforeTestOperations.add(new DataSetOperation("org/zanata/test/model/TextFlowTestData.dbunit.xml", DatabaseOperation.CLEAN_INSERT));
}

@BeforeMethod
public void initializeSeam()
{
MockitoAnnotations.initMocks(this);

seam.reset()
.use("session", getSession())
.use("translationStateCacheImpl", translationStateCacheImpl)
.ignoreNonResolvable();
}

@Test
public void getIterationStatisticsXml() throws Exception
{
Expand Down

0 comments on commit bc9a29b

Please sign in to comment.