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

Commit

Permalink
rhbz1023247 Prevent concurrent updates to Activity entities
Browse files Browse the repository at this point in the history
  • Loading branch information
seanf committed Oct 25, 2013
1 parent d16230a commit 110239a
Show file tree
Hide file tree
Showing 9 changed files with 169 additions and 71 deletions.
9 changes: 1 addition & 8 deletions pom.xml
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.zanata</groupId>
<artifactId>zanata-parent</artifactId>
<version>14</version>
<version>15-SNAPSHOT</version>
<relativePath>../parent</relativePath>
</parent>

Expand Down Expand Up @@ -189,13 +189,6 @@
<version>3.0</version>
</dependency>

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava-gwt</artifactId>
<version>11.0.2</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
Expand Down
Expand Up @@ -33,7 +33,7 @@
public final class DocumentUploadedEvent {
public static final String EVENT_NAME =
"org.zanata.event.HDocumentUploaded";

private final long actorId;
private final Long documentId;
private final boolean isSourceDocument;
private final LocaleId localeId;
Expand Down
Expand Up @@ -21,6 +21,8 @@

package org.zanata.events;

import javax.annotation.Nullable;

import lombok.Data;

import org.zanata.common.ContentState;
Expand All @@ -35,7 +37,8 @@
public final class TextFlowTargetStateEvent {
public static final String EVENT_NAME =
"org.zanata.event.HTextFlowTranslated";

// this may be null in the case of document uploads
private final @Nullable Long actorId;
private final Long documentId;
private final Long textFlowId;
private final LocaleId localeId;
Expand Down
Expand Up @@ -26,7 +26,6 @@
import org.zanata.common.ActivityType;
import org.zanata.exception.ZanataServiceException;
import org.zanata.model.Activity;
import org.zanata.model.HPerson;
import org.zanata.model.IsEntityWithType;
import org.zanata.model.type.EntityType;

Expand Down Expand Up @@ -74,13 +73,13 @@ List<Activity> findLatestActivities(long personId, int offset,
/**
* Log activity, records roll up in hourly basis
*
* @param actor
* @param actorId
* @param context
* @param target
* @param activityType
* @param wordCount
*/
void logActivity(HPerson actor, IsEntityWithType context,
void logActivity(long actorId, IsEntityWithType context,
IsEntityWithType target, ActivityType activityType, int wordCount);

/**
Expand Down
@@ -0,0 +1,54 @@
/*
* 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.util.concurrent.locks.Lock;

import lombok.Data;

import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;

import com.google.common.util.concurrent.Striped;

/**
* @author Sean Flanigan <a href="mailto:sflaniga@redhat.com">sflaniga@redhat.com</a>
*
*/
@Name("activityLockManager")
@AutoCreate
@Scope(ScopeType.APPLICATION)
public class ActivityLockManager {
private static final int NUM_STRIPES = Runtime.getRuntime().availableProcessors() * 4;
public ActivityLockManager() {
}

private Striped<Lock> stripedLock = Striped.lock(NUM_STRIPES);

public Lock getLock(Long personId) {
return stripedLock.get(personId);
}


}
Expand Up @@ -23,6 +23,7 @@
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.locks.Lock;

import javax.persistence.EntityManager;

Expand All @@ -39,6 +40,7 @@
import org.zanata.common.ActivityType;
import org.zanata.dao.ActivityDAO;
import org.zanata.dao.DocumentDAO;
import org.zanata.dao.PersonDAO;
import org.zanata.dao.ProjectIterationDAO;
import org.zanata.dao.TextFlowTargetDAO;
import org.zanata.events.DocumentUploadedEvent;
Expand Down Expand Up @@ -71,17 +73,27 @@ public class ActivityServiceImpl implements ActivityService {
@In
private DocumentDAO documentDAO;

@In
private PersonDAO personDAO;

@In
private ProjectIterationDAO projectIterationDAO;

@In
private EntityManager entityManager;

@In
private ActivityLockManager activityLockManager;

@Override
public Activity findActivity(long actorId, EntityType contextType,
long contextId, ActivityType activityType, Date actionTime) {
return activityDAO.findActivity(actorId, contextType, contextId,
activityType, DateUtils.truncate(actionTime, Calendar.HOUR));
activityType, getRoundedTime(actionTime));
}

private Date getRoundedTime(Date actionTime) {
return DateUtils.truncate(actionTime, Calendar.HOUR);
}

@Override
Expand All @@ -98,17 +110,27 @@ public List<Activity> findLatestActivities(long personId, int offset,
}

@Override
public void logActivity(HPerson actor, IsEntityWithType context,
public void logActivity(long actorId, IsEntityWithType context,
IsEntityWithType target, ActivityType activityType, int wordCount) {
Lock lock = activityLockManager.getLock(actorId);
try {
logActivityAlreadyLocked(actorId, context, target, activityType, wordCount);
} finally {
lock.unlock();
}
}

private void logActivityAlreadyLocked(long actorId, IsEntityWithType context,
IsEntityWithType target, ActivityType activityType, int wordCount) {
if (actor != null && context != null && activityType != null) {
if (context != null && activityType != null) {
Date currentActionTime = new Date();
Activity activity =
findActivity(actor.getId(), context.getEntityType(),
findActivity(actorId, context.getEntityType(),
context.getId(), activityType, currentActionTime);

if (activity != null) {
activity.updateActivity(currentActionTime, target, wordCount);
} else {
HPerson actor = personDAO.findById(actorId);
activity =
new Activity(actor, context, target, activityType,
wordCount);
Expand All @@ -130,16 +152,25 @@ public Object getEntity(EntityType entityType, long entityId) {
@Observer(TextFlowTargetStateEvent.EVENT_NAME)
@Transactional
public void logTextFlowStateUpdate(TextFlowTargetStateEvent event) {
HTextFlowTarget target =
textFlowTargetDAO.findById(event.getTextFlowTargetId(), false);
HDocument document = documentDAO.getById(event.getDocumentId());
ActivityType activityType =
event.getNewState().isReviewed() ? ActivityType.REVIEWED_TRANSLATION
: ActivityType.UPDATE_TRANSLATION;

logActivity(target.getLastModifiedBy(), document.getProjectIteration(),
target, activityType, target.getTextFlow().getWordCount()
.intValue());
Long actorId = event.getActorId();
if (actorId != null) {
Lock lock = activityLockManager.getLock(actorId);
try {
HTextFlowTarget target =
textFlowTargetDAO.findById(event.getTextFlowTargetId(),
false);
HDocument document = documentDAO.getById(event.getDocumentId());
ActivityType activityType =
event.getNewState().isReviewed() ? ActivityType.REVIEWED_TRANSLATION
: ActivityType.UPDATE_TRANSLATION;

logActivityAlreadyLocked(actorId,
document.getProjectIteration(), target, activityType,
target.getTextFlow().getWordCount().intValue());
} finally {
lock.unlock();
}
}
}

/**
Expand All @@ -148,14 +179,19 @@ public void logTextFlowStateUpdate(TextFlowTargetStateEvent event) {
@Observer(DocumentUploadedEvent.EVENT_NAME)
@Transactional
public void onDocumentUploaded(DocumentUploadedEvent event) {
HDocument document = documentDAO.getById(event.getDocumentId());
ActivityType activityType =
event.isSourceDocument() ? ActivityType.UPLOAD_SOURCE_DOCUMENT
: ActivityType.UPLOAD_TRANSLATION_DOCUMENT;

logActivity(document.getLastModifiedBy(),
document.getProjectIteration(), document, activityType,
getDocumentWordCount(document));
Lock lock = activityLockManager.getLock(event.getActorId());
try {
HDocument document = documentDAO.getById(event.getDocumentId());
ActivityType activityType =
event.isSourceDocument() ? ActivityType.UPLOAD_SOURCE_DOCUMENT
: ActivityType.UPLOAD_TRANSLATION_DOCUMENT;
HPerson actor = personDAO.findById(event.getActorId());
logActivityAlreadyLocked(actor.getId(),
document.getProjectIteration(), document, activityType,
getDocumentWordCount(document));
} finally {
lock.unlock();
}
}

private int getDocumentWordCount(HDocument document) {
Expand Down
Expand Up @@ -28,11 +28,13 @@
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.core.Events;
import org.jboss.seam.security.management.JpaIdentityStore;
import org.zanata.ApplicationConfiguration;
import org.zanata.dao.DocumentDAO;
import org.zanata.dao.ProjectIterationDAO;
import org.zanata.events.DocumentUploadedEvent;
import org.zanata.lock.Lock;
import org.zanata.model.HAccount;
import org.zanata.model.HDocument;
import org.zanata.model.HLocale;
import org.zanata.model.HProjectIteration;
Expand Down Expand Up @@ -77,6 +79,10 @@ public class DocumentServiceImpl implements DocumentService {

@In
private ApplicationConfiguration applicationConfiguration;
@In(value = JpaIdentityStore.AUTHENTICATED_USER, scope = ScopeType.SESSION,
required = false)
private HAccount authenticatedAccount;


@Override
@Transactional
Expand Down Expand Up @@ -148,10 +154,11 @@ public HDocument saveDocument(String projectSlug, String iterationSlug,
extensions, hLocale, nextDocRev);
documentDAO.flush();

long actorId = authenticatedAccount.getPerson().getId();
if (changed && Events.exists()) {
Events.instance().raiseTransactionSuccessEvent(
DocumentUploadedEvent.EVENT_NAME,
new DocumentUploadedEvent(document.getId(), true, hLocale
new DocumentUploadedEvent(actorId, document.getId(), true, hLocale
.getLocaleId()));
}

Expand Down

0 comments on commit 110239a

Please sign in to comment.