From e37a3e56c52f6d00f02f323571b1a049a39cff70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Panzar?= Date: Sat, 26 Sep 2020 20:10:28 +0200 Subject: [PATCH] Migrate to latest Play version - addresses #92 - removes the AuthProvider from the repositories - fixes unit tests --- app/auth/AccessTokenAuthenticator.java | 5 +- app/criterias/AbstractContextCriteria.java | 3 +- app/criterias/AbstractSearchCriteria.java | 4 +- app/criterias/ContextCriteria.java | 2 + app/forms/ProjectForm.java | 46 --- app/forms/ProjectUserForm.java | 50 ---- app/importers/AbstractImporter.java | 23 +- app/importers/Importer.java | 3 +- app/importers/PropertiesImporter.java | 14 +- app/mappers/AccessTokenMapper.java | 1 - app/mappers/NotificationMapper.java | 4 +- app/models/ProjectUser.java | 2 - app/repositories/ModelRepository.java | 26 +- app/repositories/ProjectRepository.java | 2 +- .../impl/AbstractModelRepository.java | 8 +- .../impl/AccessTokenRepositoryImpl.java | 92 +----- app/repositories/impl/KeyRepositoryImpl.java | 95 +----- .../impl/LinkedAccountRepositoryImpl.java | 9 +- .../impl/LocaleRepositoryImpl.java | 69 +---- .../impl/LogEntryRepositoryImpl.java | 42 +-- .../impl/MessageRepositoryImpl.java | 177 +---------- .../impl/ProjectRepositoryImpl.java | 112 ++----- .../impl/ProjectUserRepositoryImpl.java | 73 +---- .../impl/UserFeatureFlagRepositoryImpl.java | 32 +- app/repositories/impl/UserRepositoryImpl.java | 33 +- app/services/MessageService.java | 2 - app/services/ModelService.java | 2 - app/services/ProjectService.java | 10 +- app/services/api/impl/AbstractApiService.java | 6 +- .../api/impl/AccessTokenApiServiceImpl.java | 3 +- .../api/impl/ActivityApiServiceImpl.java | 6 +- app/services/api/impl/KeyApiServiceImpl.java | 4 +- .../api/impl/LocaleApiServiceImpl.java | 6 +- .../api/impl/MessageApiServiceImpl.java | 5 +- .../api/impl/ProjectApiServiceImpl.java | 2 +- .../api/impl/ProjectUserApiServiceImpl.java | 3 +- app/services/api/impl/UserApiServiceImpl.java | 6 +- .../impl/UserFeatureFlagApiServiceImpl.java | 3 +- app/services/impl/AbstractModelService.java | 73 ++++- app/services/impl/AccessTokenServiceImpl.java | 92 +++++- app/services/impl/AuthProviderImpl.java | 41 ++- app/services/impl/KeyServiceImpl.java | 99 ++++-- .../impl/LinkedAccountServiceImpl.java | 5 +- app/services/impl/LocaleServiceImpl.java | 76 ++++- app/services/impl/LogEntryServiceImpl.java | 84 +++--- app/services/impl/MessageServiceImpl.java | 227 ++++++++++++-- app/services/impl/NoCacheServiceImpl.java | 44 ++- .../impl/NotificationServiceDummy.java | 7 +- app/services/impl/ProjectServiceImpl.java | 161 ++++++++-- app/services/impl/ProjectUserServiceImpl.java | 83 ++++- .../impl/UserFeatureFlagServiceImpl.java | 26 +- app/services/impl/UserServiceImpl.java | 40 ++- app/validators/AccessTokenByUserAndName.java | 28 -- .../AccessTokenByUserAndNameValidator.java | 53 ---- app/validators/CustomValidator.java | 8 + app/validators/ProjectNameUniqueChecker.java | 2 +- app/validators/ProjectNameValidator.java | 2 +- app/validators/ProjectUserModifyAllowed.java | 20 -- .../ProjectUserModifyAllowedValidator.java | 69 +---- app/validators/UserByUsername.java | 27 -- app/validators/UserByUsernameValidator.java | 49 --- app/validators/UsernameValidator.java | 6 +- src/it/java/controllers/LocalesApiTest.java | 6 +- .../AccessTokenServiceIntegrationTest.java | 17 +- .../services/KeyServiceIntegrationTest.java | 7 +- .../LocaleServiceIntegrationTest.java | 11 +- .../MessageServiceIntegrationTest.java | 11 +- .../ProjectServiceIntegrationTest.java | 8 +- .../services/UserServiceIntegrationTest.java | 27 +- src/test/java/dtos/LocaleTest.java | 39 ++- .../impl/ProjectUserRepositoryImplTest.java | 3 +- .../java/services/AccessTokenServiceTest.java | 101 +++---- src/test/java/services/KeyServiceTest.java | 163 ++++------ .../services/LinkedAccountServiceTest.java | 108 +++---- src/test/java/services/LocaleServiceTest.java | 106 +++---- .../java/services/LogEntryServiceTest.java | 117 +++----- .../java/services/MessageServiceTest.java | 109 +++---- .../java/services/ProjectServiceTest.java | 283 +++++++----------- src/test/java/services/UserServiceTest.java | 163 ++++------ .../impl/ProjectUserServiceImplTest.java | 19 +- src/test/java/tests/AbstractTest.java | 2 +- .../java/utils/AccessTokenRepositoryMock.java | 9 +- src/test/java/utils/CacheApiMock.java | 76 ----- src/test/java/utils/FormatUtilsTest.java | 116 +++++-- 84 files changed, 1668 insertions(+), 2140 deletions(-) delete mode 100644 app/forms/ProjectForm.java delete mode 100644 app/forms/ProjectUserForm.java delete mode 100644 app/validators/AccessTokenByUserAndName.java delete mode 100644 app/validators/AccessTokenByUserAndNameValidator.java create mode 100644 app/validators/CustomValidator.java delete mode 100644 app/validators/ProjectUserModifyAllowed.java delete mode 100644 app/validators/UserByUsername.java delete mode 100644 app/validators/UserByUsernameValidator.java delete mode 100644 src/test/java/utils/CacheApiMock.java diff --git a/app/auth/AccessTokenAuthenticator.java b/app/auth/AccessTokenAuthenticator.java index da97d2d0..5d32f59c 100644 --- a/app/auth/AccessTokenAuthenticator.java +++ b/app/auth/AccessTokenAuthenticator.java @@ -8,9 +8,6 @@ import play.inject.Injector; import services.AccessTokenService; -import javax.inject.Inject; -import javax.inject.Singleton; - public class AccessTokenAuthenticator implements Authenticator { private final Injector injector; @@ -26,7 +23,7 @@ public AccessTokenAuthenticator(Injector injector, String clientName) { public void validate(TokenCredentials credentials, WebContext context) { init(); - AccessToken accessToken = accessTokenService.byKey(credentials.getToken(), null) /* FIXME */; + AccessToken accessToken = accessTokenService.byKey(credentials.getToken(), null); if (accessToken == null) { throw new CredentialsException("Could not validate access token"); diff --git a/app/criterias/AbstractContextCriteria.java b/app/criterias/AbstractContextCriteria.java index b67e93ef..d1611879 100644 --- a/app/criterias/AbstractContextCriteria.java +++ b/app/criterias/AbstractContextCriteria.java @@ -14,7 +14,8 @@ public UUID getLoggedInUserId() { return loggedInUserId; } - public void setLoggedInUserId(UUID loggedInUserId) { + @Override + public void setLoggedInUserId(@CheckForNull UUID loggedInUserId) { this.loggedInUserId = loggedInUserId; } diff --git a/app/criterias/AbstractSearchCriteria.java b/app/criterias/AbstractSearchCriteria.java index b3dbcf2c..e93e6b27 100644 --- a/app/criterias/AbstractSearchCriteria.java +++ b/app/criterias/AbstractSearchCriteria.java @@ -6,6 +6,7 @@ import play.mvc.Http.Request; import utils.NumberUtils; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; import java.util.UUID; @@ -68,7 +69,8 @@ public UUID getLoggedInUserId() { return loggedInUserId; } - public void setLoggedInUserId(UUID loggedInUserId) { + @Override + public void setLoggedInUserId(@CheckForNull UUID loggedInUserId) { this.loggedInUserId = loggedInUserId; } diff --git a/app/criterias/ContextCriteria.java b/app/criterias/ContextCriteria.java index 4c341529..ccef83cc 100644 --- a/app/criterias/ContextCriteria.java +++ b/app/criterias/ContextCriteria.java @@ -7,4 +7,6 @@ public interface ContextCriteria extends FetchCriteria { @CheckForNull UUID getLoggedInUserId(); + + void setLoggedInUserId(@CheckForNull UUID loggedInUserId); } diff --git a/app/forms/ProjectForm.java b/app/forms/ProjectForm.java deleted file mode 100644 index 6afdfef9..00000000 --- a/app/forms/ProjectForm.java +++ /dev/null @@ -1,46 +0,0 @@ -package forms; - -import models.Project; -import play.data.Form; -import play.data.FormFactory; -import play.data.validation.Constraints; -import validators.ProjectName; - -/** - * @author resamsel - * @version 2 Sep 2016 - */ -public class ProjectForm { - - @Constraints.Required - @Constraints.MaxLength(Project.NAME_LENGTH) - @Constraints.Pattern("[a-zA-Z0-9_\\.-]*") - @ProjectName - private String name; - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public Project fill(Project in) { - in.name = name; - - return in; - } - - public static ProjectForm from(Project in) { - ProjectForm out = new ProjectForm(); - - out.name = in.name; - - return out; - } - - public static Form form(FormFactory formFactory) { - return formFactory.form(ProjectForm.class); - } -} diff --git a/app/forms/ProjectUserForm.java b/app/forms/ProjectUserForm.java deleted file mode 100644 index 14db1ba2..00000000 --- a/app/forms/ProjectUserForm.java +++ /dev/null @@ -1,50 +0,0 @@ -package forms; - -import models.ProjectRole; -import models.ProjectUser; -import models.User; -import play.data.Form; -import play.data.FormFactory; -import play.data.validation.Constraints; -import validators.UserByUsername; - -/** - * @author resamsel - * @version 2 Sep 2016 - */ -public class ProjectUserForm { - - @Constraints.Required - @Constraints.MaxLength(User.USERNAME_LENGTH) - @UserByUsername - private String username; - - @Constraints.Required - private ProjectRole role; - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public ProjectRole getRole() { - return role; - } - - public void setRole(ProjectRole role) { - this.role = role; - } - - public ProjectUser fill(ProjectUser in) { - in.role = role; - - return in; - } - - public static Form form(FormFactory formFactory) { - return formFactory.form(ProjectUserForm.class); - } -} diff --git a/app/importers/AbstractImporter.java b/app/importers/AbstractImporter.java index c7a50853..7f531e1c 100644 --- a/app/importers/AbstractImporter.java +++ b/app/importers/AbstractImporter.java @@ -8,6 +8,7 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import services.KeyService; import services.MessageService; @@ -49,24 +50,24 @@ public AbstractImporter(KeyService keyService, MessageService messageService) { } @Override - public void apply(File file, Locale locale) throws Exception { + public void apply(File file, Locale locale, Http.Request request) throws Exception { LOGGER.debug("Importing from file {}", file.getName()); Properties properties = retrieveProperties(new FileInputStream(file), locale); - load(locale, properties.stringPropertyNames()); + load(locale, properties.stringPropertyNames(), request); - saveKeys(locale, properties); - saveMessages(locale, properties); + saveKeys(locale, properties, request); + saveMessages(locale, properties, request); LOGGER.debug("Imported from file {}", file.getName()); } abstract Properties retrieveProperties(InputStream stream, Locale locale) throws Exception; - protected void load(Locale locale, Collection keyNames) { + protected void load(Locale locale, Collection keyNames, Http.Request request) { keys = keyService.findBy( - new KeyCriteria() + KeyCriteria.from(request) .withLimit(Integer.MAX_VALUE) .withProjectId(locale.project.id) .withNames(keyNames)) @@ -74,7 +75,7 @@ protected void load(Locale locale, Collection keyNames) { .stream() .collect(toMap(k -> k.name, a -> a)); messages = messageService.findBy( - new MessageCriteria() + MessageCriteria.from(request) .withLimit(Integer.MAX_VALUE) .withLocaleId(locale.id)) .getList() @@ -82,7 +83,7 @@ protected void load(Locale locale, Collection keyNames) { .collect(toMap(m -> m.key.name, a -> a)); } - void saveKeys(Locale locale, Properties properties) { + void saveKeys(Locale locale, Properties properties, Http.Request request) { List newKeys = new ArrayList<>(); for (String keyName : properties.stringPropertyNames()) { String value = (String) properties.get(keyName); @@ -97,12 +98,12 @@ void saveKeys(Locale locale, Properties properties) { } // Update keys cache - for (Key key : keyService.save(newKeys)) { + for (Key key : keyService.save(newKeys, request)) { keys.put(key.name, key); } } - void saveMessages(Locale locale, Properties properties) { + void saveMessages(Locale locale, Properties properties, Http.Request request) { List newMessages = new ArrayList<>(); for (String keyName : properties.stringPropertyNames()) { String value = (String) properties.get(keyName); @@ -132,6 +133,6 @@ void saveMessages(Locale locale, Properties properties) { } } - messageService.save(newMessages); + messageService.save(newMessages, request); } } diff --git a/app/importers/Importer.java b/app/importers/Importer.java index 1606431b..fc248c1d 100644 --- a/app/importers/Importer.java +++ b/app/importers/Importer.java @@ -1,9 +1,10 @@ package importers; import models.Locale; +import play.mvc.Http; import java.io.File; public interface Importer { - void apply(File file, Locale locale) throws Exception; + void apply(File file, Locale locale, Http.Request request) throws Exception; } diff --git a/app/importers/PropertiesImporter.java b/app/importers/PropertiesImporter.java index 07a78602..3c354b0f 100644 --- a/app/importers/PropertiesImporter.java +++ b/app/importers/PropertiesImporter.java @@ -3,6 +3,7 @@ import models.Locale; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import services.KeyService; import services.MessageService; @@ -18,16 +19,13 @@ public abstract class PropertiesImporter extends AbstractImporter implements Importer { private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesImporter.class); - /** - * - */ protected PropertiesImporter(KeyService keyService, MessageService messageService) { super(keyService, messageService); } /** * {@inheritDoc} - * + * * @throws IOException * @throws FileNotFoundException * @throws UnsupportedEncodingException @@ -44,15 +42,15 @@ Properties retrieveProperties(InputStream inputStream, Locale locale) throws IOE } @Override - public void apply(File file, Locale locale) throws Exception { + public void apply(File file, Locale locale, Http.Request request) throws Exception { LOGGER.debug("Importing from file {}", file.getName()); Properties properties = retrieveProperties(new FileInputStream(file), null); - load(locale, properties.stringPropertyNames()); + load(locale, properties.stringPropertyNames(), request); - saveKeys(locale, properties); - saveMessages(locale, properties); + saveKeys(locale, properties, request); + saveMessages(locale, properties, request); LOGGER.debug("Imported from file {}", file.getName()); } diff --git a/app/mappers/AccessTokenMapper.java b/app/mappers/AccessTokenMapper.java index f40bb96d..f6ec8dff 100644 --- a/app/mappers/AccessTokenMapper.java +++ b/app/mappers/AccessTokenMapper.java @@ -2,7 +2,6 @@ import dto.AccessToken; import models.User; -import play.mvc.Http; public class AccessTokenMapper { public static models.AccessToken toModel(dto.AccessToken in) { diff --git a/app/mappers/NotificationMapper.java b/app/mappers/NotificationMapper.java index 041ff425..b05b317a 100644 --- a/app/mappers/NotificationMapper.java +++ b/app/mappers/NotificationMapper.java @@ -35,11 +35,11 @@ public Notification toDto(SimpleActivity in, LogEntry activity, Http.Request req Notification out = new Notification(); out.id = in.getId(); - out.user = UserMapper.toDto(userService.byId(Notification.extractUuid(in.getActor()))); + out.user = UserMapper.toDto(userService.byId(Notification.extractUuid(in.getActor()), request)); out.verb = in.getVerb(); out.time = in.getTime() != null ? new Date(in.getTime().getTime()) : null; if (activity == null) - activity = logEntryService.byId(Notification.extractUuid(in.getForeignId())); + activity = logEntryService.byId(Notification.extractUuid(in.getForeignId()), request); if (activity != null) { out.activityId = activity.id; out.contentType = activity.getSimpleContentType(); diff --git a/app/models/ProjectUser.java b/app/models/ProjectUser.java index 537fa73e..578cc928 100644 --- a/app/models/ProjectUser.java +++ b/app/models/ProjectUser.java @@ -6,7 +6,6 @@ import org.joda.time.DateTime; import utils.CacheUtils; import validators.NameUnique; -import validators.ProjectUserModifyAllowed; import validators.ProjectUserOwnerExists; import validators.ProjectUserUniqueChecker; @@ -29,7 +28,6 @@ @Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"project_id", "user_id"})}) @NameUnique(checker = ProjectUserUniqueChecker.class, field = "user", message = "error.projectuserunique") @ProjectUserOwnerExists -@ProjectUserModifyAllowed public class ProjectUser implements Model { private static final int ROLE_LENGTH = 16; diff --git a/app/repositories/ModelRepository.java b/app/repositories/ModelRepository.java index 07bb1574..1f5bbd6a 100644 --- a/app/repositories/ModelRepository.java +++ b/app/repositories/ModelRepository.java @@ -18,14 +18,36 @@ public interface ModelRepository, ID, CRITERIA extends Ab T byId(GetCriteria criteria); + /** + * Create a new model in the database. Eventually, the {@link ModelRepository#save(Model)} method will be invoked. + * + * @param model the model including the ID + * @return the created model + * @throws javax.validation.ValidationException when either the ID is not unique, or the validation of the entity + * fails + */ T create(T model); + /** + * Update existing model in the database. If the model doesn't exist, a validation exception will be thrown. + * Eventually, the {@link ModelRepository#save(Model)} method will be invoked. + * + * @param model the model including the ID + * @return the updated model + * @throws javax.validation.ValidationException when either the ID is missing, the entity doesn't exist in the + * database, or the validation of the entity fails + */ T update(T model); /** - * Persist model to database. + * Save or update existing model in the database. Decides on the existence of the ID whether or not the entity needs + * to be saved or updated. + * + * @param model the model + * @return the saved model + * @throws javax.validation.ValidationException when the validation of the entity fails */ - T save(T t); + T save(T model); /** * Persist model collection to database. diff --git a/app/repositories/ProjectRepository.java b/app/repositories/ProjectRepository.java index f5c4c15c..92de88ec 100644 --- a/app/repositories/ProjectRepository.java +++ b/app/repositories/ProjectRepository.java @@ -33,7 +33,7 @@ FETCH_KEYS, singletonList(FETCH_KEYS), FETCH_LOCALES, singletonList(FETCH_LOCALES) ); - Project byOwnerAndName(String username, String name, String... fetches); + Project byOwnerAndName(String username, String name, UUID loggedInUserId, String... fetches); Map progress(List projectIds); } diff --git a/app/repositories/impl/AbstractModelRepository.java b/app/repositories/impl/AbstractModelRepository.java index 2132315e..1d996116 100644 --- a/app/repositories/impl/AbstractModelRepository.java +++ b/app/repositories/impl/AbstractModelRepository.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import criterias.AbstractSearchCriteria; import criterias.ContextCriteria; import criterias.GetCriteria; @@ -11,7 +10,6 @@ import org.slf4j.LoggerFactory; import repositories.ModelRepository; import repositories.Persistence; -import services.AuthProvider; import utils.QueryUtils; import javax.persistence.PersistenceException; @@ -39,14 +37,10 @@ public abstract class AbstractModelRepository, ID protected final Persistence persistence; protected final Validator validator; - protected final AuthProvider authProvider; - final ActivityActorRef activityActor; - AbstractModelRepository(Persistence persistence, Validator validator, AuthProvider authProvider, ActivityActorRef activityActor) { + AbstractModelRepository(Persistence persistence, Validator validator) { this.persistence = persistence; this.validator = validator; - this.authProvider = authProvider; - this.activityActor = activityActor; } public MODEL byId(GetCriteria criteria) { diff --git a/app/repositories/impl/AccessTokenRepositoryImpl.java b/app/repositories/impl/AccessTokenRepositoryImpl.java index 134ab518..2986fd4a 100644 --- a/app/repositories/impl/AccessTokenRepositoryImpl.java +++ b/app/repositories/impl/AccessTokenRepositoryImpl.java @@ -1,28 +1,21 @@ package repositories.impl; -import actors.ActivityActorRef; -import actors.ActivityProtocol.Activity; import criterias.AccessTokenCriteria; import criterias.ContextCriteria; import criterias.PagedListFactory; import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.Query; -import mappers.AccessTokenMapper; import models.AccessToken; -import models.ActionType; -import models.User; -import models.UserRole; import org.apache.commons.lang3.StringUtils; import repositories.AccessTokenRepository; import repositories.Persistence; -import services.AuthProvider; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; -import java.nio.charset.StandardCharsets; -import java.util.Base64; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -32,11 +25,8 @@ public class AccessTokenRepositoryImpl extends AccessTokenRepository { @Inject - public AccessTokenRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor) { - super(persistence, validator, authProvider, activityActor); + public AccessTokenRepositoryImpl(Persistence persistence, Validator validator) { + super(persistence, validator); } @Override @@ -75,80 +65,16 @@ public AccessToken byUserAndName(UUID userId, String name) { .findOne(); } - private Query fetch(List fetches) { - return fetch(fetches.toArray(new String[0])); - } - - private Query fetch(String... fetches) { - return createQuery(AccessToken.class, PROPERTIES_TO_FETCH, FETCH_MAP, fetches); - } - @Override protected Query createQuery(ContextCriteria criteria) { - return createQuery(AccessToken.class, PROPERTIES_TO_FETCH, FETCH_MAP, criteria.getFetches()); - } - - /** - * {@inheritDoc} - */ - @Override - protected void preSave(AccessToken t, boolean update) { - User loggedInUser = authProvider.loggedInUser(null) /* FIXME: will fail! */; - if (t.user == null || t.user.id == null - || (loggedInUser != null && t.user.id != loggedInUser.id && loggedInUser.role != UserRole.Admin)) { - // only allow admins to create access tokens for other users - t.user = loggedInUser; - } - if (StringUtils.isBlank(t.key)) { - t.key = generateKey(AccessToken.KEY_LENGTH); - } + return fetch(criteria.getFetches()); } - @Override - protected void prePersist(AccessToken t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>( - ActionType.Update, - authProvider.loggedInUser(null) /* FIXME: will fail! */, - null, - dto.AccessToken.class, - AccessTokenMapper.toDto(byId(t.id)), - AccessTokenMapper.toDto(t) - ), - null - ); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void postSave(AccessToken t, boolean update) { - if (!update) { - activityActor.tell( - new Activity<>( - ActionType.Create, - authProvider.loggedInUser(null) /* FIXME: will fail! */, - null, - dto.AccessToken.class, - null, - AccessTokenMapper.toDto(t) - ), - null - ); - } + private Query fetch(List fetches) { + return createQuery(AccessToken.class, PROPERTIES_TO_FETCH, FETCH_MAP, fetches); } - public static String generateKey(int length) { - String raw = Base64.getEncoder().encodeToString(String - .format("%s%s", UUID.randomUUID(), UUID.randomUUID()).getBytes(StandardCharsets.UTF_8)); - - if (raw.length() > length) { - raw = raw.substring(0, length); - } - - return raw.replace("+", "/"); + private Query fetch(String... fetches) { + return fetch(fetches != null ? Arrays.asList(fetches) : Collections.emptyList()); } } diff --git a/app/repositories/impl/KeyRepositoryImpl.java b/app/repositories/impl/KeyRepositoryImpl.java index a578713b..62437527 100644 --- a/app/repositories/impl/KeyRepositoryImpl.java +++ b/app/repositories/impl/KeyRepositoryImpl.java @@ -1,31 +1,21 @@ package repositories.impl; -import actors.ActivityActorRef; -import actors.ActivityProtocol.Activities; -import actors.ActivityProtocol.Activity; import com.google.common.collect.ImmutableMap; import criterias.ContextCriteria; import criterias.KeyCriteria; import criterias.PagedListFactory; -import dto.PermissionException; import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.Query; import io.ebean.RawSqlBuilder; -import mappers.KeyMapper; -import models.ActionType; import models.Key; import models.Message; import models.Project; -import models.ProjectRole; import models.Stat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import repositories.KeyRepository; -import repositories.MessageRepository; import repositories.Persistence; -import services.AuthProvider; -import services.PermissionService; import utils.QueryUtils; import javax.annotation.Nonnull; @@ -33,7 +23,6 @@ import javax.inject.Singleton; import javax.validation.Validator; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -57,24 +46,9 @@ public class KeyRepositoryImpl extends AbstractModelRepository progress(UUID projectId) { return stats.stream().collect(Collectors.toMap(stat -> stat.id, stat -> stat.count)); } - - /** - * {@inheritDoc} - */ - @Override - protected void prePersist(Key t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>( - ActionType.Update, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Key.class, - keyMapper.toDto(byId(t.id), null), keyMapper.toDto(t, null)), - null - ); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void postSave(Key t, boolean update) { - if (!update) { - activityActor.tell( - new Activity<>(ActionType.Create, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Key.class, null, keyMapper.toDto(t, null)), - null - ); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void preDelete(Key t) { - if (!permissionService - .hasPermissionAny(t.project.id, authProvider.loggedInUser(null) /* FIXME: will fail! */, ProjectRole.Owner, ProjectRole.Manager, - ProjectRole.Developer)) { - throw new PermissionException("User not allowed in project"); - } - - activityActor.tell( - new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Key.class, keyMapper.toDto(t, null), null), - null - ); - - messageRepository.delete(messageRepository.byKey(t)); - } - - /** - * {@inheritDoc} - */ - @Override - protected void preDelete(Collection t) { - activityActor.tell( - new Activities<>( - t.stream() - .map(k -> new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, k.project, dto.Key.class, - keyMapper.toDto(k, null), null)) - .collect(Collectors.toList())), - null - ); - - messageRepository - .delete(messageRepository.byKeys(t.stream().map(k -> k.id).collect(Collectors.toList()))); - } } diff --git a/app/repositories/impl/LinkedAccountRepositoryImpl.java b/app/repositories/impl/LinkedAccountRepositoryImpl.java index 62571eb5..6ba1607e 100644 --- a/app/repositories/impl/LinkedAccountRepositoryImpl.java +++ b/app/repositories/impl/LinkedAccountRepositoryImpl.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import criterias.ContextCriteria; import criterias.LinkedAccountCriteria; import criterias.PagedListFactory; @@ -10,7 +9,6 @@ import models.LinkedAccount; import repositories.LinkedAccountRepository; import repositories.Persistence; -import services.AuthProvider; import javax.inject.Inject; import javax.inject.Singleton; @@ -22,11 +20,8 @@ public class LinkedAccountRepositoryImpl extends LinkedAccountRepository { @Inject - public LinkedAccountRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor) { - super(persistence, validator, authProvider, activityActor); + public LinkedAccountRepositoryImpl(Persistence persistence, Validator validator) { + super(persistence, validator); } @Override diff --git a/app/repositories/impl/LocaleRepositoryImpl.java b/app/repositories/impl/LocaleRepositoryImpl.java index 8f22a98e..2221839d 100644 --- a/app/repositories/impl/LocaleRepositoryImpl.java +++ b/app/repositories/impl/LocaleRepositoryImpl.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import actors.ActivityProtocol.Activities; import actors.ActivityProtocol.Activity; import com.google.common.collect.ImmutableMap; @@ -26,7 +25,6 @@ import repositories.LocaleRepository; import repositories.MessageRepository; import repositories.Persistence; -import services.AuthProvider; import services.PermissionService; import utils.QueryUtils; @@ -69,12 +67,10 @@ public class LocaleRepositoryImpl extends public LocaleRepositoryImpl( Persistence persistence, Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor, MessageRepository messageRepository, PermissionService permissionService, LocaleMapper localeMapper) { - super(persistence, validator, authProvider, activityActor); + super(persistence, validator); this.messageRepository = messageRepository; this.permissionService = permissionService; @@ -228,67 +224,4 @@ public Locale byOwnerAndProjectAndName(String username, String projectName, Stri .eq("name", localeName) .findOne(); } - - // FIXME: pull pre/post persist logic to service!? - - /** - * {@inheritDoc} - */ - @Override - protected void prePersist(Locale t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>(ActionType.Update, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Locale.class, - localeMapper.toDto(byId(t.id), null), localeMapper.toDto(t, null)), - null - ); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void postSave(Locale t, boolean update) { - if (!update) { - activityActor.tell( - new Activity<>(ActionType.Create, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Locale.class, null, localeMapper.toDto(t, null)), - null - ); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void preDelete(Locale t) { - if (!permissionService - .hasPermissionAny(t.project.id, authProvider.loggedInUser(null) /* FIXME: will fail! */, ProjectRole.Owner, ProjectRole.Manager, - ProjectRole.Translator)) { - throw new PermissionException("User not allowed in project"); - } - - activityActor.tell( - new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.Locale.class, localeMapper.toDto(t, null), null), - null - ); - - messageRepository.delete(messageRepository.byLocale(t.id)); - } - - /** - * {@inheritDoc} - */ - @Override - public void preDelete(Collection t) { - activityActor.tell( - new Activities<>(t.stream().map(l -> new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, l.project, - dto.Locale.class, localeMapper.toDto(l, null), null)).collect(Collectors.toList())), - null - ); - - messageRepository.delete( - messageRepository.byLocales(t.stream().map(m -> m.id).collect(Collectors.toList()))); - } } diff --git a/app/repositories/impl/LogEntryRepositoryImpl.java b/app/repositories/impl/LogEntryRepositoryImpl.java index a3f2949d..b63dd1b8 100644 --- a/app/repositories/impl/LogEntryRepositoryImpl.java +++ b/app/repositories/impl/LogEntryRepositoryImpl.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import actors.NotificationActorRef; import actors.NotificationProtocol.PublishNotification; import criterias.ContextCriteria; @@ -11,34 +10,24 @@ import io.ebean.Query; import models.LogEntry; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import repositories.LogEntryRepository; import repositories.Persistence; -import services.AuthProvider; import utils.QueryUtils; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; -import java.util.Collection; import java.util.UUID; @Singleton public class LogEntryRepositoryImpl extends - AbstractModelRepository implements - LogEntryRepository { - private static final Logger LOGGER = LoggerFactory.getLogger(LogEntryRepositoryImpl.class); - + AbstractModelRepository implements + LogEntryRepository { private final NotificationActorRef notificationActor; @Inject - public LogEntryRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor, - NotificationActorRef notificationActor) { - super(persistence, validator, authProvider, activityActor); + public LogEntryRepositoryImpl(Persistence persistence, Validator validator, NotificationActorRef notificationActor) { + super(persistence, validator); this.notificationActor = notificationActor; } @@ -65,7 +54,7 @@ public PagedList findBy(LogEntryCriteria criteria) { @Override public LogEntry byId(UUID id, String... fetches) { return QueryUtils.fetch(persistence.find(LogEntry.class).setId(id).setDisableLazyLoading(true), - QueryUtils.mergeFetches(PROPERTIES_TO_FETCH, fetches)).findOne(); + QueryUtils.mergeFetches(PROPERTIES_TO_FETCH, fetches)).findOne(); } @Override @@ -82,7 +71,7 @@ private ExpressionList findQuery(LogEntryCriteria criteria) { if (StringUtils.isNoneEmpty(criteria.getSearch())) { query.disjunction().ilike("before", "%" + criteria.getSearch() + "%") - .ilike("after", "%" + criteria.getSearch() + "%").endJunction(); + .ilike("after", "%" + criteria.getSearch() + "%").endJunction(); } if (criteria.getUserId() != null) { @@ -109,25 +98,6 @@ protected Query createQuery(ContextCriteria criteria) { return createQuery(LogEntry.class, PROPERTIES_TO_FETCH, criteria.getFetches()); } - /** - * {@inheritDoc} - */ - @Override - public void preSave(LogEntry t, boolean update) { - if (t.user == null) { - t.user = authProvider.loggedInUser(null) /* FIXME: will fail! */; - LOGGER.debug("preSave(): user of log entry is {}", t.user); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void preSave(Collection t) { - t.forEach(e -> preSave(e, false)); - } - /** * {@inheritDoc} */ diff --git a/app/repositories/impl/MessageRepositoryImpl.java b/app/repositories/impl/MessageRepositoryImpl.java index 1aec1247..3040fda8 100644 --- a/app/repositories/impl/MessageRepositoryImpl.java +++ b/app/repositories/impl/MessageRepositoryImpl.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import actors.ActivityProtocol.Activities; import actors.ActivityProtocol.Activity; import actors.MessageWordCountActorRef; @@ -21,7 +20,6 @@ import org.slf4j.LoggerFactory; import repositories.MessageRepository; import repositories.Persistence; -import services.AuthProvider; import utils.MessageUtils; import utils.QueryUtils; @@ -53,12 +51,10 @@ public class MessageRepositoryImpl extends @Inject public MessageRepositoryImpl( Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor, - MessageWordCountActorRef messageWordCountActor, + Validator validator, + MessageWordCountActorRef messageWordCountActor, MessageMapper messageMapper) { - super(persistence, validator, authProvider, activityActor); + super(persistence, validator); this.messageWordCountActor = messageWordCountActor; this.messageMapper = messageMapper; @@ -169,171 +165,4 @@ public void preSave(Message t, boolean update) { messageWordCountActor.tell(new ChangeMessageWordCount(t.id, t.locale.project.id, t.locale.id, t.key.id, t.wordCount, t.wordCount - wordCount), null); } - - @Override - protected void prePersist(Message t, boolean update) { - if (update) { - Message existing = byId(t.id); - if (!Objects.equals(t.value, existing.value)) { - // Only track changes of messageĀ“s value - activityActor.tell(logEntryUpdate(t, existing), null); - } - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void preSave(Collection t) { - super.preSave(t); - - Map wordCount = t.stream() - .filter(m -> m.id != null) - .map(m -> { - int wc = MessageUtils.wordCount(m); - return new ChangeMessageWordCount( - m.id, - m.locale.project.id, - m.locale.id, - m.key.id, - wc, - wc - (m.wordCount != null ? m.wordCount : 0)); - }) - .collect(toMap(wc -> wc.messageId, wc -> wc, (a, b) -> a)); - - messageWordCountActor.tell(wordCount.values(), null); - - // Update model - t.stream() - .filter(m -> m.id != null) - .forEach(m -> m.wordCount = wordCount.getOrDefault(m.id, - new ChangeMessageWordCount(null, null, null, null, 0, 0)).wordCount); - t.stream() - .filter(m -> m.id == null) - .forEach(m -> m.wordCount = MessageUtils.wordCount(m)); - - List ids = t.stream().filter(m -> m.id != null).map(m -> m.id).collect(toList()); - Map messages = ids.size() > 0 ? byIds(ids) : Collections.emptyMap(); - - activityActor.tell( - new Activities<>(t.stream().filter( - // Only track changes of messageĀ“s value - m -> persistence.isNew(m) || !Objects - .equals(m.value, messages.get(m.id).value)) - .map(m -> persistence.isNew(m) ? logEntryCreate(m) - : logEntryUpdate(m, messages.get(m.id))) - .collect(toList())), - null - ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void postSave(Message t, boolean update) { - if (!update) { - activityActor.tell(logEntryCreate(t), null); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void postSave(Collection t) { - List noWordCountMessages = - t.stream().filter(m -> m.wordCount == null).collect(toList()); - Map wordCount = noWordCountMessages.stream().map(m -> { - int wc = MessageUtils.wordCount(m); - return new ChangeMessageWordCount(m.id, m.locale.project.id, m.locale.id, m.key.id, wc, - wc - (m.wordCount != null ? m.wordCount : 0)); - }).collect(toMap(wc -> wc.messageId, wc -> wc)); - - messageWordCountActor.tell(wordCount.values(), null); - - // Update model - noWordCountMessages.stream().filter(m -> m.id != null).forEach(m -> m.wordCount = wordCount - .getOrDefault(m.id, new ChangeMessageWordCount(null, null, null, null, 0, 0)).wordCount); - - try { - persist(noWordCountMessages); - } catch (Exception e) { - LOGGER.error("Error while persisting word count", e); - } - } - - /** - * {@inheritDoc} - */ - @Override - public void preDelete(Message t) { - activityActor.tell( - new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.key.project, dto.Message.class, messageMapper.toDto(t, null), - null), - null - ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void preDelete(Collection t) { - activityActor.tell( - new Activities<>(t.stream().map(m -> new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, m.key.project, - dto.Message.class, messageMapper.toDto(m, null), null)).collect(toList())), - null - ); - } - - /** - * {@inheritDoc} - */ - @Override - protected void postDelete(Message t) { - if (t.wordCount != null) { - messageWordCountActor.tell(new ChangeMessageWordCount(t.id, t.locale.project.id, t.locale.id, - t.key.id, 0, -t.wordCount), null); - } - } - - /** - * {@inheritDoc} - */ - @Override - protected void postDelete(Collection t) { - Map wordCount = - t.stream() - .filter(m -> m.wordCount != null) - .map( - m -> new ChangeMessageWordCount(m.id, m.locale.project.id, m.locale.id, m.key.id, 0, - -m.wordCount)) - .collect(toMap(wc -> wc.messageId, wc -> wc, (a, b) -> a)); - - messageWordCountActor.tell(wordCount.values(), null); - } - - private Activity logEntryCreate(Message message) { - return new Activity<>( - ActionType.Create, - authProvider.loggedInUser(null) /* FIXME: will fail! */, - message.key.project, - dto.Message.class, - null, - messageMapper.toDto(message, null) - ); - } - - private Activity logEntryUpdate(Message message, Message previous) { - return new Activity<>( - ActionType.Update, - authProvider.loggedInUser(null) /* FIXME: will fail! */, - message.key.project, - dto.Message.class, - messageMapper.toDto(previous, null), - messageMapper.toDto(message, null) - ); - } } diff --git a/app/repositories/impl/ProjectRepositoryImpl.java b/app/repositories/impl/ProjectRepositoryImpl.java index e1070a67..103d8f9f 100644 --- a/app/repositories/impl/ProjectRepositoryImpl.java +++ b/app/repositories/impl/ProjectRepositoryImpl.java @@ -1,42 +1,31 @@ package repositories.impl; -import actors.ActivityActorRef; -import actors.ActivityProtocol.Activities; -import actors.ActivityProtocol.Activity; import criterias.ContextCriteria; import criterias.DefaultContextCriteria; import criterias.GetCriteria; import criterias.PagedListFactory; import criterias.ProjectCriteria; import dto.NotFoundException; -import dto.PermissionException; import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.Query; import io.ebean.RawSqlBuilder; -import mappers.ProjectMapper; -import models.ActionType; import models.Project; import models.ProjectRole; import models.ProjectUser; import models.Stat; -import models.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import play.mvc.Http; import repositories.KeyRepository; import repositories.LocaleRepository; import repositories.Persistence; import repositories.ProjectRepository; -import services.AuthProvider; -import services.PermissionService; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; @@ -58,24 +47,17 @@ public class ProjectRepositoryImpl extends private final LocaleRepository localeRepository; private final KeyRepository keyRepository; - private final PermissionService permissionService; - private final ProjectMapper projectMapper; @Inject - public ProjectRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor, - LocaleRepository localeRepository, - KeyRepository keyRepository, - PermissionService permissionService, - ProjectMapper projectMapper) { - super(persistence, validator, authProvider, activityActor); + public ProjectRepositoryImpl( + Persistence persistence, + Validator validator, + LocaleRepository localeRepository, + KeyRepository keyRepository) { + super(persistence, validator); this.localeRepository = localeRepository; this.keyRepository = keyRepository; - this.permissionService = permissionService; - this.projectMapper = projectMapper; } @Override @@ -191,13 +173,13 @@ public Project byId(UUID id, String... fetches) { return null; } - return byId(GetCriteria.from(id, null /* FIXME */, fetches).withLoggedInUserId(authProvider.loggedInUserId(null) /* FIXME: will fail! */)); + return byId(GetCriteria.from(id, null , fetches)); } @Override - public Project byOwnerAndName(String username, String name, String... fetches) { + public Project byOwnerAndName(String username, String name, UUID loggedInUserId, String... fetches) { ContextCriteria criteria = new DefaultContextCriteria() - .withLoggedInUserId(authProvider.loggedInUserId(null) /* FIXME: will fail! */) + .withLoggedInUserId(loggedInUserId) .withFetches(fetches); return fetch( @@ -210,15 +192,9 @@ public Project byOwnerAndName(String username, String name, String... fetches) { ); } - /** - * {@inheritDoc} - */ @Override protected void preSave(Project t, boolean update) { persistence.markAsDirty(t); - if (t.owner == null || t.owner.id == null) { - t.owner = authProvider.loggedInUser(null) /* FIXME: will fail! */; - } if (t.members == null) { t.members = new ArrayList<>(); } @@ -227,31 +203,11 @@ protected void preSave(Project t, boolean update) { } } - @Override - protected void prePersist(Project t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>(ActionType.Update, authProvider.loggedInUser(null) /* FIXME: will fail! */, t, dto.Project.class, toDto(byId(t.id), null), toDto(t, null)), - null - ); - } - } - - /** - * {@inheritDoc} - */ @Override protected void postSave(Project t, boolean update) { super.postSave(t, update); persistence.refresh(t); persistence.refresh(t.owner); - - if (!update) { - activityActor.tell( - new Activity<>(ActionType.Create, authProvider.loggedInUser(null) /* FIXME: will fail! */, t, dto.Project.class, null, toDto(t, null)), - null - ); - } } /** @@ -259,54 +215,32 @@ protected void postSave(Project t, boolean update) { */ @Override public void delete(Project t) { - if (t == null || t.deleted) { - throw new NotFoundException(dto.Project.class.getSimpleName(), t != null ? t.id : null); - } - if (!permissionService - .hasPermissionAny(t.id, authProvider.loggedInUser(null) /* FIXME: will fail! */, ProjectRole.Owner, ProjectRole.Manager)) { - throw new PermissionException("User not allowed in project"); - } - localeRepository.delete(t.locales); keyRepository.delete(t.keys); - activityActor.tell( - new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, t, dto.Project.class, toDto(t, null), null), - null - ); - super.save(t.withName(String.format("%s-%s", t.id, t.name)).withDeleted(true)); } + @Override + protected void preDelete(Project t) { + super.preDelete(t); + + if (t == null || t.deleted) { + throw new NotFoundException(dto.Project.class.getSimpleName(), t != null ? t.id : null); + } + } + /** * {@inheritDoc} */ @Override public void delete(Collection t) { - User loggedInUser = authProvider.loggedInUser(null) /* FIXME: will fail! */; - for (Project p : t) { - if (p == null || p.deleted) { - throw new NotFoundException(dto.Project.class.getSimpleName(), p != null ? p.id : null); - } - if (!permissionService - .hasPermissionAny(p.id, loggedInUser, ProjectRole.Owner, ProjectRole.Manager)) { - throw new PermissionException("User not allowed in project"); - } - } - keyRepository .delete( t.stream().map(p -> p.keys).flatMap(Collection::stream).collect(toList())); localeRepository.delete( t.stream().map(p -> p.locales).flatMap(Collection::stream).collect(toList())); - activityActor.tell( - new Activities<>(t.stream() - .map(p -> new Activity<>(ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, p, dto.Project.class, toDto(p, null), null)) - .collect(toList())), - null - ); - super.save( t.stream().map(p -> p.withName(String.format("%s-%s", p.id, p.name)).withDeleted(true)) .collect(toList())); @@ -317,16 +251,6 @@ protected Query createQuery(ContextCriteria criteria) { return createQuery(Project.class, PROPERTIES_TO_FETCH, FETCH_MAP, criteria.getFetches()); } - private dto.Project toDto(Project t, Http.Request request) { - dto.Project out = projectMapper.toDto(t, request); - - out.keys = Collections.emptyList(); - out.locales = Collections.emptyList(); - out.messages = Collections.emptyList(); - - return out; - } - private Map roles(List projectIds, UUID userId) { List results = log( () -> persistence.createQuery(ProjectUser.class) diff --git a/app/repositories/impl/ProjectUserRepositoryImpl.java b/app/repositories/impl/ProjectUserRepositoryImpl.java index 9393dbf3..8bbc8e76 100644 --- a/app/repositories/impl/ProjectUserRepositoryImpl.java +++ b/app/repositories/impl/ProjectUserRepositoryImpl.java @@ -1,7 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; -import actors.ActivityProtocol.Activity; import actors.NotificationActorRef; import actors.NotificationProtocol.FollowNotification; import criterias.ContextCriteria; @@ -10,15 +8,12 @@ import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.Query; -import mappers.ProjectUserMapper; -import models.ActionType; import models.ProjectUser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import repositories.PagedListFactoryProvider; import repositories.Persistence; import repositories.ProjectUserRepository; -import services.AuthProvider; import utils.QueryUtils; import javax.inject.Inject; @@ -29,8 +24,8 @@ @Singleton public class ProjectUserRepositoryImpl extends - AbstractModelRepository implements - ProjectUserRepository { + AbstractModelRepository implements + ProjectUserRepository { private static final Logger LOGGER = LoggerFactory.getLogger(ProjectUserRepositoryImpl.class); @@ -38,13 +33,12 @@ public class ProjectUserRepositoryImpl extends private final PagedListFactory pagedListFactory; @Inject - public ProjectUserRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor, - NotificationActorRef notificationActor, - PagedListFactoryProvider pagedListFactoryProvider) { - super(persistence, validator, authProvider, activityActor); + public ProjectUserRepositoryImpl( + Persistence persistence, + Validator validator, + NotificationActorRef notificationActor, + PagedListFactoryProvider pagedListFactoryProvider) { + super(persistence, validator); this.notificationActor = notificationActor; this.pagedListFactory = pagedListFactoryProvider.get(); @@ -53,13 +47,13 @@ public ProjectUserRepositoryImpl(Persistence persistence, @Override public PagedList findBy(ProjectUserCriteria criteria) { return log(() -> pagedListFactory.createPagedList(findQuery(criteria).query()), LOGGER, - "findBy"); + "findBy"); } @Override public ProjectUser byId(Long id, String... propertiesToFetch) { return QueryUtils.fetch(persistence.find(ProjectUser.class), PROPERTIES_TO_FETCH, FETCH_MAP).where().idEq(id) - .findOne(); + .findOne(); } @Override @@ -86,9 +80,9 @@ private ExpressionList findQuery(ProjectUserCriteria criteria) { if (criteria.getSearch() != null) { query.disjunction() - .ilike("user.name", "%" + criteria.getSearch() + "%") - .ilike("user.username", "%" + criteria.getSearch() + "%") - .endJunction(); + .ilike("user.name", "%" + criteria.getSearch() + "%") + .ilike("user.username", "%" + criteria.getSearch() + "%") + .endJunction(); } criteria.paged(query); @@ -101,53 +95,12 @@ protected Query createQuery(ContextCriteria criteria) { return createQuery(ProjectUser.class, PROPERTIES_TO_FETCH, FETCH_MAP, criteria.getFetches()); } - /** - * {@inheritDoc} - */ - @Override - protected void prePersist(ProjectUser t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>( - ActionType.Update, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.ProjectUser.class, toDto(byId(t.id)), toDto(t) - ), - null - ); - } - } - - /** - * {@inheritDoc} - */ @Override protected void postSave(ProjectUser t, boolean update) { if (!update) { persistence.refresh(t.user); - activityActor.tell( - new Activity<>( - ActionType.Create, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.ProjectUser.class, null, toDto(t) - ), - null - ); } notificationActor.tell(new FollowNotification(t.user.id, t.project.id), null); } - - /** - * {@inheritDoc} - */ - @Override - public void preDelete(ProjectUser t) { - activityActor.tell( - new Activity<>( - ActionType.Delete, authProvider.loggedInUser(null) /* FIXME: will fail! */, t.project, dto.ProjectUser.class, toDto(t), null - ), - null - ); - } - - private dto.ProjectUser toDto(ProjectUser t) { - return ProjectUserMapper.toDto(t); - } } diff --git a/app/repositories/impl/UserFeatureFlagRepositoryImpl.java b/app/repositories/impl/UserFeatureFlagRepositoryImpl.java index f90ef238..d1395b0a 100644 --- a/app/repositories/impl/UserFeatureFlagRepositoryImpl.java +++ b/app/repositories/impl/UserFeatureFlagRepositoryImpl.java @@ -1,6 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; import criterias.ContextCriteria; import criterias.PagedListFactory; import criterias.UserFeatureFlagCriteria; @@ -8,13 +7,10 @@ import io.ebean.PagedList; import io.ebean.Query; import models.Feature; -import models.User; import models.UserFeatureFlag; -import models.UserRole; import org.apache.commons.lang3.StringUtils; import repositories.Persistence; import repositories.UserFeatureFlagRepository; -import services.AuthProvider; import javax.inject.Inject; import javax.inject.Singleton; @@ -30,11 +26,8 @@ public class UserFeatureFlagRepositoryImpl extends UserFeatureFlagRepository { @Inject - public UserFeatureFlagRepositoryImpl(Persistence persistence, - Validator validator, - AuthProvider authProvider, - ActivityActorRef activityActor) { - super(persistence, validator, authProvider, activityActor); + public UserFeatureFlagRepositoryImpl(Persistence persistence, Validator validator) { + super(persistence, validator); } @Override @@ -66,10 +59,10 @@ public UserFeatureFlag byId(UUID id, String... fetches) { @Override public UserFeatureFlag byUserIdAndFeature(UUID userId, Feature feature) { return fetch() - .where() - .eq("user.id", userId) - .eq("feature", feature.getName()) - .findOne(); + .where() + .eq("user.id", userId) + .eq("feature", feature.getName()) + .findOne(); } @Override @@ -84,17 +77,4 @@ private Query fetch(String... fetches) { private Query fetch(List fetches) { return createQuery(UserFeatureFlag.class, PROPERTIES_TO_FETCH, fetches); } - - /** - * {@inheritDoc} - */ - @Override - protected void preSave(UserFeatureFlag t, boolean update) { - User loggedInUser = authProvider.loggedInUser(null) /* FIXME: will fail! */; - if (t.user == null || t.user.id == null - || (loggedInUser != null && t.user.id != loggedInUser.id && loggedInUser.role != UserRole.Admin)) { - // only allow admins to create access tokens for other users - t.user = loggedInUser; - } - } } diff --git a/app/repositories/impl/UserRepositoryImpl.java b/app/repositories/impl/UserRepositoryImpl.java index 14df0d65..ef9b4d47 100644 --- a/app/repositories/impl/UserRepositoryImpl.java +++ b/app/repositories/impl/UserRepositoryImpl.java @@ -1,7 +1,5 @@ package repositories.impl; -import actors.ActivityActorRef; -import actors.ActivityProtocol.Activity; import criterias.ContextCriteria; import criterias.PagedListFactory; import criterias.UserCriteria; @@ -9,15 +7,12 @@ import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.Query; -import mappers.UserMapper; -import models.ActionType; import models.User; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import repositories.Persistence; import repositories.UserRepository; -import services.AuthProvider; import utils.QueryUtils; import javax.inject.Inject; @@ -35,16 +30,9 @@ public class UserRepositoryImpl extends AbstractModelRepository settings, boolean return persistence.update(user); } - /** - * {@inheritDoc} - */ @Override protected void preSave(User t, boolean update) { if (t.email != null) { @@ -198,18 +183,4 @@ protected void preSave(User t, boolean update) { t.username = String.valueOf(ThreadLocalRandom.current().nextLong()); } } - - @Override - protected void prePersist(User t, boolean update) { - if (update) { - activityActor.tell( - new Activity<>(ActionType.Update, authProvider.loggedInUser(null) /* FIXME: will fail! */, null, dto.User.class, toDto(byId(t.id)), toDto(t)), - null - ); - } - } - - private dto.User toDto(User t) { - return UserMapper.toDto(t); - } } diff --git a/app/services/MessageService.java b/app/services/MessageService.java index fe73c744..d067418d 100644 --- a/app/services/MessageService.java +++ b/app/services/MessageService.java @@ -26,6 +26,4 @@ public interface MessageService extends ModelService latest(Project project, int limit); } diff --git a/app/services/ModelService.java b/app/services/ModelService.java index 5dffe3b4..d947d65d 100644 --- a/app/services/ModelService.java +++ b/app/services/ModelService.java @@ -24,8 +24,6 @@ public interface ModelService, ID, CRITERIA extends Abstr T update(T model, Http.Request request); - T save(T t, Http.Request request); - Collection save(Collection t, Http.Request request); void delete(T t, Http.Request request); diff --git a/app/services/ProjectService.java b/app/services/ProjectService.java index bb4eaed0..651e360b 100644 --- a/app/services/ProjectService.java +++ b/app/services/ProjectService.java @@ -24,15 +24,15 @@ public interface ProjectService extends ModelService getAggregates(LogEntryCriteria criteria) { * {@inheritDoc} */ @Override - protected LogEntry toModel(dto.Activity in) { - return ActivityMapper.toModel(in, service.byId(in.id, null /* FIXME */)); + protected LogEntry toModel(Activity in, Http.Request request) { + return ActivityMapper.toModel(in, service.byId(in.id, request)); } } diff --git a/app/services/api/impl/KeyApiServiceImpl.java b/app/services/api/impl/KeyApiServiceImpl.java index 6e75d07a..ad495b48 100644 --- a/app/services/api/impl/KeyApiServiceImpl.java +++ b/app/services/api/impl/KeyApiServiceImpl.java @@ -57,7 +57,7 @@ public dto.Key byOwnerAndProjectAndName(Http.Request request, String username, S * {@inheritDoc} */ @Override - protected Key toModel(dto.Key in) { - return KeyMapper.toModel(in, projectService.byId(in.projectId, null /* FIXME */)); + protected Key toModel(dto.Key in, Http.Request request) { + return KeyMapper.toModel(in, projectService.byId(in.projectId, request)); } } diff --git a/app/services/api/impl/LocaleApiServiceImpl.java b/app/services/api/impl/LocaleApiServiceImpl.java index 2d0bda08..f0cf92ff 100644 --- a/app/services/api/impl/LocaleApiServiceImpl.java +++ b/app/services/api/impl/LocaleApiServiceImpl.java @@ -111,7 +111,7 @@ public dto.Locale upload(UUID localeId, Request request) { .orElseThrow(() -> new IllegalArgumentException("File type " + form.getFileType() + " not supported yet")); try { - importer.apply(messages.getRef(), locale); + importer.apply(messages.getRef(), locale, request); } catch (Exception e) { LOGGER.error("Error while importing messages", e); } @@ -141,7 +141,7 @@ public Result download(Request request, UUID localeId, String fileType) { * {@inheritDoc} */ @Override - protected Locale toModel(dto.Locale in) { - return LocaleMapper.toModel(in, projectService.byId(GetCriteria.from(in.projectId, null /* FIXME */))); + protected Locale toModel(dto.Locale in, Request request) { + return LocaleMapper.toModel(in, projectService.byId(GetCriteria.from(in.projectId, request))); } } diff --git a/app/services/api/impl/MessageApiServiceImpl.java b/app/services/api/impl/MessageApiServiceImpl.java index 16e7f8de..4d01fc97 100644 --- a/app/services/api/impl/MessageApiServiceImpl.java +++ b/app/services/api/impl/MessageApiServiceImpl.java @@ -4,6 +4,7 @@ import mappers.MessageMapper; import models.Message; import models.Scope; +import play.mvc.Http; import services.KeyService; import services.LocaleService; import services.MessageService; @@ -45,7 +46,7 @@ protected MessageApiServiceImpl(MessageService messageService, LocaleService loc * {@inheritDoc} */ @Override - protected Message toModel(dto.Message in) { - return MessageMapper.toModel(in, localeService.byId(in.localeId, null /* FIXME */), keyService.byId(in.keyId, null /* FIXME */)); + protected Message toModel(dto.Message in, Http.Request request) { + return MessageMapper.toModel(in, localeService.byId(in.localeId, request), keyService.byId(in.keyId, request)); } } diff --git a/app/services/api/impl/ProjectApiServiceImpl.java b/app/services/api/impl/ProjectApiServiceImpl.java index 7d475f73..bc3241ab 100644 --- a/app/services/api/impl/ProjectApiServiceImpl.java +++ b/app/services/api/impl/ProjectApiServiceImpl.java @@ -195,7 +195,7 @@ public SearchResponse search(Http.Request request, UUID projectId, SearchForm se * {@inheritDoc} */ @Override - protected Project toModel(dto.Project in) { + protected Project toModel(dto.Project in, Http.Request request) { return ProjectMapper.toModel(in); } } diff --git a/app/services/api/impl/ProjectUserApiServiceImpl.java b/app/services/api/impl/ProjectUserApiServiceImpl.java index d3ddacc1..d144af52 100644 --- a/app/services/api/impl/ProjectUserApiServiceImpl.java +++ b/app/services/api/impl/ProjectUserApiServiceImpl.java @@ -4,6 +4,7 @@ import mappers.ProjectUserMapper; import models.ProjectUser; import models.Scope; +import play.mvc.Http; import services.PermissionService; import services.ProjectUserService; import services.api.ProjectUserApiService; @@ -35,7 +36,7 @@ protected ProjectUserApiServiceImpl(ProjectUserService projectUserService, Permi * {@inheritDoc} */ @Override - protected ProjectUser toModel(dto.ProjectUser in) { + protected ProjectUser toModel(dto.ProjectUser in, Http.Request request) { return ProjectUserMapper.toModel(in); } } diff --git a/app/services/api/impl/UserApiServiceImpl.java b/app/services/api/impl/UserApiServiceImpl.java index 084fe0ec..53e245fd 100644 --- a/app/services/api/impl/UserApiServiceImpl.java +++ b/app/services/api/impl/UserApiServiceImpl.java @@ -69,7 +69,7 @@ protected Function getDtoMapper(Http.Request request) { @Override public dto.User create(Http.Request request) { - User user = toModel(toDto(request.body().asJson())); + User user = toModel(toDto(request.body().asJson()), request); authProvider.loggedInProfile(request).ifPresent(profile -> { LinkedAccount linkedAccount = new LinkedAccount().withUser(user); @@ -127,7 +127,7 @@ public dto.User updateSettings(UUID userId, JsonNode json, Http.Request request) * {@inheritDoc} */ @Override - protected User toModel(dto.User in) { - return UserMapper.toModel(in, service.byId(GetCriteria.from(in.id, null /* FIXME */))); + protected User toModel(dto.User in, Http.Request request) { + return UserMapper.toModel(in, service.byId(GetCriteria.from(in.id, request))); } } diff --git a/app/services/api/impl/UserFeatureFlagApiServiceImpl.java b/app/services/api/impl/UserFeatureFlagApiServiceImpl.java index 807fd0ce..55d9b84c 100644 --- a/app/services/api/impl/UserFeatureFlagApiServiceImpl.java +++ b/app/services/api/impl/UserFeatureFlagApiServiceImpl.java @@ -4,6 +4,7 @@ import mappers.UserFeatureFlagMapper; import models.Scope; import models.UserFeatureFlag; +import play.mvc.Http; import services.PermissionService; import services.UserFeatureFlagService; import services.api.UserFeatureFlagApiService; @@ -30,7 +31,7 @@ public UserFeatureFlagApiServiceImpl(UserFeatureFlagService service, PermissionS } @Override - protected UserFeatureFlag toModel(dto.UserFeatureFlag in) { + protected UserFeatureFlag toModel(dto.UserFeatureFlag in, Http.Request request) { return UserFeatureFlagMapper.toModel(in); } } diff --git a/app/services/impl/AbstractModelService.java b/app/services/impl/AbstractModelService.java index 526d5e05..0f2d9780 100644 --- a/app/services/impl/AbstractModelService.java +++ b/app/services/impl/AbstractModelService.java @@ -1,8 +1,9 @@ package services.impl; +import actors.ActivityActorRef; +import criterias.AbstractSearchCriteria; import criterias.GetCriteria; import io.ebean.PagedList; -import criterias.AbstractSearchCriteria; import models.Model; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,6 +32,7 @@ public abstract class AbstractModelService, ID, C protected final Validator validator; protected final LogEntryService logEntryService; protected final AuthProvider authProvider; + protected final ActivityActorRef activityActor; protected final CacheService cache; final ModelRepository modelRepository; private final BiFunction cacheKeyGetter; @@ -38,13 +40,14 @@ public abstract class AbstractModelService, ID, C public AbstractModelService(Validator validator, CacheService cache, ModelRepository modelRepository, BiFunction cacheKeyGetter, LogEntryService logEntryService, - AuthProvider authProvider) { + AuthProvider authProvider, ActivityActorRef activityActor) { this.validator = validator; this.cache = cache; this.modelRepository = modelRepository; this.cacheKeyGetter = cacheKeyGetter; this.logEntryService = logEntryService; this.authProvider = authProvider; + this.activityActor = activityActor; } /** @@ -71,12 +74,7 @@ public MODEL byId(ID id, Http.Request request, String... fetches) { return null; } - return postGet(cache.getOrElseUpdate( - cacheKeyGetter.apply(id, fetches), - () -> modelRepository.byId(id, fetches), - 60 - ), - request); + return byId(GetCriteria.from(id, request, fetches)); } @Override @@ -85,6 +83,10 @@ public MODEL byId(GetCriteria criteria) { return null; } + if (criteria.getLoggedInUserId() == null) { + criteria.setLoggedInUserId(authProvider.loggedInUserId(criteria.getRequest())); + } + return postGet(cache.getOrElseUpdate( cacheKeyGetter.apply(criteria.getId(), criteria.getFetches().toArray(new String[0])), () -> modelRepository.byId(criteria), @@ -105,9 +107,11 @@ public MODEL create(MODEL model, Http.Request request) { LOGGER.debug("create({})", model); preCreate(model, request); + preSave(model, request); - MODEL m = modelRepository.save(model); + MODEL m = modelRepository.create(model); + postSave(m, request); postCreate(m, request); return m; @@ -127,13 +131,26 @@ protected void postCreate(MODEL t, Http.Request request) { public MODEL update(MODEL t, Http.Request request) { LOGGER.debug("update({})", t); + preUpdate(t, request); + preSave(t, request); + MODEL m = modelRepository.update(t); + postSave(m, request); postUpdate(m, request); return m; } + protected void preUpdate(MODEL t, Http.Request request) { + } + + protected void preSave(MODEL t, Http.Request request) { + } + + protected void postSave(MODEL t, Http.Request request) { + } + protected MODEL postUpdate(MODEL t, Http.Request request) { cache.removeByPrefix(cacheKeyGetter.apply(t.getId(), new String[0])); cache.set(cacheKeyGetter.apply(t.getId(), new String[0]), t, 60); @@ -142,28 +159,52 @@ protected MODEL postUpdate(MODEL t, Http.Request request) { } @Override - public MODEL save(MODEL t, Http.Request request) { - return modelRepository.save(t); + public Collection save(Collection t, Http.Request request) { + preSave(t, request); + + Collection m = modelRepository.save(t); + + postSave(m, request); + + return m; } - @Override - public Collection save(Collection t, Http.Request request) { - return modelRepository.save(t); + protected void preSave(Collection t, Http.Request request) { + t.forEach(e -> preSave(e, request)); + } + + protected void postSave(Collection t, Http.Request request) { + t.forEach(e -> postSave(e, request)); } @Override public void delete(MODEL t, Http.Request request) { + preDelete(t, request); + modelRepository.delete(t); postDelete(t, request); } + protected void preDelete(MODEL t, Http.Request request) { + } + + protected void postDelete(MODEL t, Http.Request request) { + cache.removeByPrefix(cacheKeyGetter.apply(t.getId(), new String[0])); + } + @Override public void delete(Collection t, Http.Request request) { + preDelete(t, request); + modelRepository.delete(t); + + postDelete(t, request); } - protected void postDelete(MODEL t, Http.Request request) { - cache.removeByPrefix(cacheKeyGetter.apply(t.getId(), new String[0])); + protected void preDelete(Collection t, Http.Request request) { + } + + protected void postDelete(Collection t, Http.Request request) { } } diff --git a/app/services/impl/AccessTokenServiceImpl.java b/app/services/impl/AccessTokenServiceImpl.java index eee9cf7f..be2b963c 100644 --- a/app/services/impl/AccessTokenServiceImpl.java +++ b/app/services/impl/AccessTokenServiceImpl.java @@ -1,20 +1,32 @@ package services.impl; -import io.ebean.PagedList; +import actors.ActivityActorRef; +import actors.ActivityProtocol; import criterias.AccessTokenCriteria; +import criterias.GetCriteria; +import io.ebean.PagedList; +import mappers.AccessTokenMapper; import models.AccessToken; import models.ActionType; import models.User; import models.UserRole; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.mvc.Http; import repositories.AccessTokenRepository; -import services.*; +import services.AccessTokenService; +import services.AuthProvider; +import services.CacheService; +import services.LogEntryService; +import services.MetricService; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.UUID; import static utils.Stopwatch.log; @@ -24,7 +36,7 @@ */ @Singleton public class AccessTokenServiceImpl extends - AbstractModelService implements AccessTokenService { + AbstractModelService implements AccessTokenService { private static final Logger LOGGER = LoggerFactory.getLogger(AccessTokenServiceImpl.class); @@ -34,10 +46,14 @@ public class AccessTokenServiceImpl extends @Inject public AccessTokenServiceImpl( - Validator validator, CacheService cache, AuthProvider authProvider, - AccessTokenRepository accessTokenRepository, LogEntryService logEntryService, - MetricService metricService) { - super(validator, cache, accessTokenRepository, AccessToken::getCacheKey, logEntryService, authProvider); + Validator validator, + CacheService cache, + AuthProvider authProvider, + AccessTokenRepository accessTokenRepository, + LogEntryService logEntryService, + MetricService metricService, + ActivityActorRef activityActor) { + super(validator, cache, accessTokenRepository, AccessToken::getCacheKey, logEntryService, authProvider, activityActor); this.cache = cache; this.accessTokenRepository = accessTokenRepository; @@ -68,9 +84,6 @@ protected AccessToken postGet(AccessToken accessToken, Http.Request request) { return super.postGet(accessToken, request); } - /** - * {@inheritDoc} - */ @Override public AccessToken byKey(String accessTokenKey, Http.Request request) { metricService.logEvent(AccessToken.class, ActionType.Read); @@ -90,6 +103,51 @@ protected void postCreate(AccessToken t, Http.Request request) { // When user has been created cache.removeByPrefix("accessToken:criteria:"); + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Create, + authProvider.loggedInUser(request), + null, + dto.AccessToken.class, + null, + AccessTokenMapper.toDto(t) + ), + null + ); + } + + @Override + protected void preUpdate(AccessToken t, Http.Request request) { + super.preUpdate(t, request); + + if (StringUtils.isBlank(t.key)) { + t.key = generateKey(AccessToken.KEY_LENGTH); + } + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Update, + authProvider.loggedInUser(request), + null, + dto.AccessToken.class, + AccessTokenMapper.toDto(modelRepository.byId(GetCriteria.from(t.id, request))), + AccessTokenMapper.toDto(t) + ), + null + ); + } + + @Override + protected void preSave(AccessToken t, Http.Request request) { + super.preSave(t, request); + + User loggedInUser = authProvider.loggedInUser(request); + if (t.user == null || t.user.id == null + || (loggedInUser != null && t.user.id != loggedInUser.id && loggedInUser.role != UserRole.Admin)) { + // only allow admins to create access tokens for other users + t.user = loggedInUser; + } } @Override @@ -104,9 +162,6 @@ protected AccessToken postUpdate(AccessToken t, Http.Request request) { return t; } - /** - * {@inheritDoc} - */ @Override protected void postDelete(AccessToken t, Http.Request request) { super.postDelete(t, request); @@ -116,4 +171,15 @@ protected void postDelete(AccessToken t, Http.Request request) { // When locale has been deleted, the locale cache needs to be invalidated cache.removeByPrefix("accessToken:criteria:"); } + + public static String generateKey(int length) { + String raw = Base64.getEncoder().encodeToString(String + .format("%s%s", UUID.randomUUID(), UUID.randomUUID()).getBytes(StandardCharsets.UTF_8)); + + if (raw.length() > length) { + raw = raw.substring(0, length); + } + + return raw.replace("+", "/"); + } } diff --git a/app/services/impl/AuthProviderImpl.java b/app/services/impl/AuthProviderImpl.java index cf27f559..341732c3 100644 --- a/app/services/impl/AuthProviderImpl.java +++ b/app/services/impl/AuthProviderImpl.java @@ -25,6 +25,10 @@ @Singleton public class AuthProviderImpl implements AuthProvider { + private static final TypedKey ATTRIBUTE_USER_ID = TypedKey.create("translatr:userId"); + private static final TypedKey ATTRIBUTE_USER = TypedKey.create("translatr:user"); + private static final TypedKey ATTRIBUTE_PROFILE = TypedKey.create("translatr:profile"); + private final Injector injector; private final PlaySessionStore sessionStore; @@ -44,6 +48,11 @@ public AuthProviderImpl( @Override public User loggedInUser(Http.Request request) { + Optional loggedInUserOptional = request.attrs().getOptional(ATTRIBUTE_USER); + if (loggedInUserOptional.isPresent()) { + return loggedInUserOptional.get(); + } + // Logged-in via auth plugin? Optional profile = loggedInProfile(request); if (profile.isPresent()) { @@ -52,21 +61,15 @@ public User loggedInUser(Http.Request request) { return null; } - String id = profile.get().getId(); - TypedKey attributeKey = TypedKey.create(clientName + ":" + id); - Optional optionalUser = request.attrs().getOptional(attributeKey); - if (optionalUser.isPresent()) { - return optionalUser.get(); - } - // Needed to avoid circular dependency of AuthProvider -> UserService -> AuthProvider - UserService userService = injector.instanceOf(UserService.class); + UserService userService = getUserService(); User user = USER_MAPPER.getOrDefault(clientName, (service, p) -> service.byLinkedAccount(p.getClientName(), p.getId())) .apply(userService, profile.get()); - request.addAttr(attributeKey, user); + request.addAttr(ATTRIBUTE_USER_ID, user.id); + request.addAttr(ATTRIBUTE_USER, user); return user; } @@ -76,6 +79,15 @@ public User loggedInUser(Http.Request request) { @Override public UUID loggedInUserId(Http.Request request) { + if (request == null) { + return null; + } + + Optional loggedInUserIdOptional = request.attrs().getOptional(ATTRIBUTE_USER_ID); + if (loggedInUserIdOptional.isPresent()) { + return loggedInUserIdOptional.get(); + } + User loggedInUser = loggedInUser(request); if (loggedInUser == null) { @@ -87,7 +99,16 @@ public UUID loggedInUserId(Http.Request request) { @Override public Optional loggedInProfile(Http.Request request) { - return loggedInProfile(new PlayWebContext(request, sessionStore)); + Optional cacheProfile = request.attrs().getOptional(ATTRIBUTE_PROFILE); + if (cacheProfile.isPresent()) { + return cacheProfile; + } + + Optional profileOptional = loggedInProfile(new PlayWebContext(request, sessionStore)); + + profileOptional.ifPresent(commonProfile -> request.addAttr(ATTRIBUTE_PROFILE, commonProfile)); + + return profileOptional; } @Override diff --git a/app/services/impl/KeyServiceImpl.java b/app/services/impl/KeyServiceImpl.java index 4686329d..65f4cfa2 100644 --- a/app/services/impl/KeyServiceImpl.java +++ b/app/services/impl/KeyServiceImpl.java @@ -1,23 +1,31 @@ package services.impl; +import actors.ActivityActorRef; +import actors.ActivityProtocol; import criterias.GetCriteria; +import dto.PermissionException; import io.ebean.PagedList; import criterias.KeyCriteria; +import mappers.KeyMapper; import models.ActionType; import models.Key; import models.Project; +import models.ProjectRole; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.mvc.Http; import repositories.KeyRepository; +import repositories.MessageRepository; import repositories.Persistence; import services.*; import javax.inject.Inject; import javax.validation.Validator; +import java.util.Collection; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.stream.Collectors; import static utils.Stopwatch.log; @@ -26,26 +34,30 @@ * @version 29 Aug 2016 */ public class KeyServiceImpl extends AbstractModelService - implements KeyService { + implements KeyService { private static final Logger LOGGER = LoggerFactory.getLogger(KeyServiceImpl.class); private final KeyRepository keyRepository; private final Persistence persistence; private final MetricService metricService; - - private final CacheService cache; + private final KeyMapper keyMapper; + private final PermissionService permissionService; + private final MessageRepository messageRepository; @Inject public KeyServiceImpl(Validator validator, CacheService cache, KeyRepository keyRepository, LogEntryService logEntryService, Persistence persistence, AuthProvider authProvider, - MetricService metricService) { - super(validator, cache, keyRepository, Key::getCacheKey, logEntryService, authProvider); + MetricService metricService, ActivityActorRef activityActor, KeyMapper keyMapper, + PermissionService permissionService, MessageRepository messageRepository) { + super(validator, cache, keyRepository, Key::getCacheKey, logEntryService, authProvider, activityActor); - this.cache = cache; this.keyRepository = keyRepository; this.persistence = persistence; this.metricService = metricService; + this.keyMapper = keyMapper; + this.permissionService = permissionService; + this.messageRepository = messageRepository; } /** @@ -70,10 +82,10 @@ public void increaseWordCountBy(UUID keyId, int wordCountDiff, Http.Request requ key.wordCount += wordCountDiff; log( - () -> modelRepository.persist(key), - LOGGER, - "Increased word count by %d", - wordCountDiff + () -> modelRepository.persist(key), + LOGGER, + "Increased word count by %d", + wordCountDiff ); } @@ -84,7 +96,7 @@ public void increaseWordCountBy(UUID keyId, int wordCountDiff, Http.Request requ public void resetWordCount(UUID projectId) { try { persistence.createSqlUpdate("update key set word_count = null where project_id = :projectId") - .setParameter("projectId", projectId).execute(); + .setParameter("projectId", projectId).execute(); } catch (Exception e) { LOGGER.error("Error while resetting word count", e); } @@ -128,6 +140,18 @@ protected Key postGet(Key key, Http.Request request) { return super.postGet(key, request); } + @Override + protected void preUpdate(Key t, Http.Request request) { + super.preUpdate(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Update, authProvider.loggedInUser(request), t.project, dto.Key.class, + keyMapper.toDto(byId(GetCriteria.from(t.id, request)), request), keyMapper.toDto(t, request)), + null + ); + } + @Override protected void postCreate(Key t, Http.Request request) { super.postCreate(t, request); @@ -142,10 +166,17 @@ protected void postCreate(Key t, Http.Request request) { } cache.removeByPrefix("key:criteria:" + t.project.id); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Create, authProvider.loggedInUser(request), t.project, dto.Key.class, null, keyMapper.toDto(t, request)), + null + ); } @Override protected Key postUpdate(Key t, Http.Request request) { + super.postUpdate(t, request); + metricService.logEvent(Key.class, ActionType.Update); Optional existing = cache.get(Key.getCacheKey(t.id)); @@ -155,28 +186,41 @@ protected Key postUpdate(Key t, Http.Request request) { cache.removeByPrefix(getCacheKey(t.project.id, "")); } - super.postUpdate(t, request); - // When locale has been updated, the locale cache needs to be invalidated cache.removeByPrefix("key:criteria:" + t.project.id); return t; } - /** - * {@inheritDoc} - */ + @Override + protected void preDelete(Key t, Http.Request request) { + super.preDelete(t, request); + + if (!permissionService + .hasPermissionAny(t.project.id, authProvider.loggedInUser(request), ProjectRole.Owner, ProjectRole.Manager, + ProjectRole.Developer)) { + throw new PermissionException("User not allowed in project"); + } + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), t.project, dto.Key.class, keyMapper.toDto(t, request), null), + null + ); + + messageRepository.delete(messageRepository.byKey(t)); + } + @Override protected void postDelete(Key t, Http.Request request) { + super.postDelete(t, request); + metricService.logEvent(Key.class, ActionType.Delete); - Key existing = byId(t.id, request); + Key existing = byId(GetCriteria.from(t.id, request)); if (existing != null) { cache.removeByPrefix(getCacheKey(existing.project.id, existing.name)); } - super.postDelete(t, request); - // When key has been deleted, the project cache needs to be invalidated cache.removeByPrefix(Project.getCacheKey(t.project.id)); cache.removeByPrefix(Project.getCacheKey(t.project.owner.username, t.project.name)); @@ -185,6 +229,23 @@ protected void postDelete(Key t, Http.Request request) { cache.removeByPrefix("key:criteria:" + t.project.id); } + @Override + protected void preDelete(Collection t, Http.Request request) { + super.preDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activities<>( + t.stream() + .map(k -> new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), k.project, dto.Key.class, + keyMapper.toDto(k, request), null)) + .collect(Collectors.toList())), + null + ); + + messageRepository + .delete(messageRepository.byKeys(t.stream().map(k -> k.id).collect(Collectors.toList()))); + } + private static String getCacheKey(UUID projectId, String keyName) { return String.format("key:project:%s:name:%s", projectId, keyName); } diff --git a/app/services/impl/LinkedAccountServiceImpl.java b/app/services/impl/LinkedAccountServiceImpl.java index d6d2cefc..d6e352df 100644 --- a/app/services/impl/LinkedAccountServiceImpl.java +++ b/app/services/impl/LinkedAccountServiceImpl.java @@ -1,5 +1,6 @@ package services.impl; +import actors.ActivityActorRef; import io.ebean.PagedList; import criterias.LinkedAccountCriteria; import models.ActionType; @@ -26,8 +27,8 @@ public class LinkedAccountServiceImpl @Inject public LinkedAccountServiceImpl(Validator validator, CacheService cache, LinkedAccountRepository linkedAccountRepository, LogEntryService logEntryService, - AuthProvider authProvider, MetricService metricService) { - super(validator, cache, linkedAccountRepository, LinkedAccount::getCacheKey, logEntryService, authProvider); + AuthProvider authProvider, MetricService metricService, ActivityActorRef activityActor) { + super(validator, cache, linkedAccountRepository, LinkedAccount::getCacheKey, logEntryService, authProvider, activityActor); this.metricService = metricService; } diff --git a/app/services/impl/LocaleServiceImpl.java b/app/services/impl/LocaleServiceImpl.java index f3c24bff..13cd417b 100644 --- a/app/services/impl/LocaleServiceImpl.java +++ b/app/services/impl/LocaleServiceImpl.java @@ -1,23 +1,31 @@ package services.impl; +import actors.ActivityActorRef; +import actors.ActivityProtocol; import criterias.GetCriteria; +import dto.PermissionException; import io.ebean.PagedList; import criterias.LocaleCriteria; +import mappers.LocaleMapper; import models.ActionType; import models.Locale; import models.Project; +import models.ProjectRole; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.mvc.Http; import repositories.LocaleRepository; +import repositories.MessageRepository; import repositories.Persistence; import services.*; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; +import java.util.Collection; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; import static utils.Stopwatch.log; @@ -27,23 +35,31 @@ */ @Singleton public class LocaleServiceImpl extends AbstractModelService - implements LocaleService { + implements LocaleService { private static final Logger LOGGER = LoggerFactory.getLogger(LocaleServiceImpl.class); private final LocaleRepository localeRepository; private final Persistence persistence; private final MetricService metricService; + private final LocaleMapper localeMapper; + private final PermissionService permissionService; + private final MessageRepository messageRepository; @Inject public LocaleServiceImpl(Validator validator, CacheService cache, LocaleRepository localeRepository, LogEntryService logEntryService, - Persistence persistence, AuthProvider authProvider, MetricService metricService) { - super(validator, cache, localeRepository, Locale::getCacheKey, logEntryService, authProvider); + Persistence persistence, AuthProvider authProvider, MetricService metricService, + ActivityActorRef activityActor, LocaleMapper localeMapper, PermissionService permissionService, + MessageRepository messageRepository) { + super(validator, cache, localeRepository, Locale::getCacheKey, logEntryService, authProvider, activityActor); this.localeRepository = localeRepository; this.persistence = persistence; this.metricService = metricService; + this.localeMapper = localeMapper; + this.permissionService = permissionService; + this.messageRepository = messageRepository; } @Override @@ -135,6 +151,22 @@ protected void postCreate(Locale t, Http.Request request) { } cache.removeByPrefix("locale:criteria:" + t.project.id); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Create, authProvider.loggedInUser(request), t.project, dto.Locale.class, null, localeMapper.toDto(t, request)), + null + ); + } + + @Override + protected void preUpdate(Locale t, Http.Request request) { + super.preUpdate(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Update, authProvider.loggedInUser(request), t.project, dto.Locale.class, + localeMapper.toDto(byId(GetCriteria.from(t.id, request)), request), localeMapper.toDto(t, request)), + null + ); } @Override @@ -149,9 +181,24 @@ protected Locale postUpdate(Locale t, Http.Request request) { return t; } - /** - * {@inheritDoc} - */ + @Override + public void preDelete(Locale t, Http.Request request) { + super.preDelete(t, request); + + if (!permissionService + .hasPermissionAny(t.project.id, authProvider.loggedInUser(request), ProjectRole.Owner, ProjectRole.Manager, + ProjectRole.Translator)) { + throw new PermissionException("User not allowed in project"); + } + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), t.project, dto.Locale.class, localeMapper.toDto(t, request), null), + null + ); + + messageRepository.delete(messageRepository.byLocale(t.id)); + } + @Override protected void postDelete(Locale t, Http.Request request) { super.postDelete(t, request); @@ -165,4 +212,21 @@ protected void postDelete(Locale t, Http.Request request) { // When locale has been deleted, the locale cache needs to be invalidated cache.removeByPrefix("locale:criteria:" + t.project.id); } + + /** + * {@inheritDoc} + */ + @Override + public void preDelete(Collection t, Http.Request request) { + super.preDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activities<>(t.stream().map(l -> new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), l.project, + dto.Locale.class, localeMapper.toDto(l, null), null)).collect(Collectors.toList())), + null + ); + + messageRepository.delete( + messageRepository.byLocales(t.stream().map(m -> m.id).collect(Collectors.toList()))); + } } diff --git a/app/services/impl/LogEntryServiceImpl.java b/app/services/impl/LogEntryServiceImpl.java index a72497d1..a88f0986 100644 --- a/app/services/impl/LogEntryServiceImpl.java +++ b/app/services/impl/LogEntryServiceImpl.java @@ -1,11 +1,12 @@ package services.impl; +import actors.ActivityActorRef; +import criterias.LogEntryCriteria; +import criterias.PagedListFactory; import io.ebean.ExpressionList; import io.ebean.PagedList; import io.ebean.RawSql; import io.ebean.RawSqlBuilder; -import criterias.LogEntryCriteria; -import criterias.PagedListFactory; import models.Aggregate; import models.LogEntry; import org.slf4j.Logger; @@ -32,19 +33,24 @@ */ @Singleton public class LogEntryServiceImpl extends AbstractModelService - implements LogEntryService { + implements LogEntryService { private static final Logger LOGGER = LoggerFactory.getLogger(LogEntryServiceImpl.class); private static final String H2_COLUMN_MILLIS = - "datediff('millisecond', timestamp '1970-01-01 00:00:00', parsedatetime(formatdatetime(when_created, 'yyyy-MM-dd HH:00:00'), 'yyyy-MM-dd HH:mm:ss'))*1000"; + "datediff('millisecond', timestamp '1970-01-01 00:00:00', parsedatetime(formatdatetime(when_created, 'yyyy-MM-dd HH:00:00'), 'yyyy-MM-dd HH:mm:ss'))*1000"; private final LogEntryRepository logEntryRepository; private final Persistence persistence; @Inject - public LogEntryServiceImpl(Validator validator, CacheService cache, - LogEntryRepository logEntryRepository, Persistence persistence, AuthProvider authProvider) { - super(validator, cache, logEntryRepository, LogEntry::getCacheKey, null, authProvider); + public LogEntryServiceImpl( + Validator validator, + CacheService cache, + LogEntryRepository logEntryRepository, + Persistence persistence, + AuthProvider authProvider, + ActivityActorRef activityActor) { + super(validator, cache, logEntryRepository, LogEntry::getCacheKey, null, authProvider, activityActor); this.logEntryRepository = logEntryRepository; this.persistence = persistence; @@ -53,20 +59,20 @@ public LogEntryServiceImpl(Validator validator, CacheService cache, @Override public int countBy(LogEntryCriteria criteria) { return cache.getOrElseUpdate( - criteria.getCacheKey(), - () -> logEntryRepository.countBy(criteria), - 60 + criteria.getCacheKey(), + () -> logEntryRepository.countBy(criteria), + 60 ); } @Override public PagedList getAggregates(LogEntryCriteria criteria) { ExpressionList query = - persistence.createQuery(Aggregate.class) - .setRawSql(getAggregatesRawSql()) - .setMaxRows(Optional.ofNullable(criteria.getLimit()).orElse(1000)) - .setFirstRow(Optional.ofNullable(criteria.getOffset()).orElse(0)) - .where(); + persistence.createQuery(Aggregate.class) + .setRawSql(getAggregatesRawSql()) + .setMaxRows(Optional.ofNullable(criteria.getLimit()).orElse(1000)) + .setFirstRow(Optional.ofNullable(criteria.getOffset()).orElse(0)) + .where(); if (criteria.getProjectId() != null) { query.eq("project_id", criteria.getProjectId()); @@ -77,16 +83,16 @@ public PagedList getAggregates(LogEntryCriteria criteria) { } String cacheKey = - String.format("activity:aggregates:%s:%s", criteria.getUserId(), criteria.getProjectId()); + String.format("activity:aggregates:%s:%s", criteria.getUserId(), criteria.getProjectId()); return log( - () -> cache.getOrElseUpdate( - cacheKey, - () -> PagedListFactory.create(query, criteria.hasFetch(FETCH_COUNT)), - 60 - ), - LOGGER, - "Retrieving log entry aggregates" + () -> cache.getOrElseUpdate( + cacheKey, + () -> PagedListFactory.create(query, criteria.hasFetch(FETCH_COUNT)), + 60 + ), + LOGGER, + "Retrieving log entry aggregates" ); } @@ -94,20 +100,30 @@ private RawSql getAggregatesRawSql() { String dbpName = persistence.getDatabasePlatformName(); if ("h2".equals(dbpName)) { return RawSqlBuilder - .parse(String.format( - "select %1$s as millis, count(*) as cnt from log_entry group by %1$s order by 1", - H2_COLUMN_MILLIS)) - .columnMapping(H2_COLUMN_MILLIS, "millis") - .columnMapping("count(*)", "value") - .create(); + .parse(String.format( + "select %1$s as millis, count(*) as cnt from log_entry group by %1$s order by 1", + H2_COLUMN_MILLIS)) + .columnMapping(H2_COLUMN_MILLIS, "millis") + .columnMapping("count(*)", "value") + .create(); } return RawSqlBuilder - .parse( - "select when_created::date as date, count(*) as cnt from log_entry group by 1 order by 1") - .columnMapping("date", "date") - .columnMapping("cnt", "value") - .create(); + .parse( + "select when_created::date as date, count(*) as cnt from log_entry group by 1 order by 1") + .columnMapping("date", "date") + .columnMapping("cnt", "value") + .create(); + } + + @Override + protected void preSave(LogEntry t, Http.Request request) { + super.preSave(t, request); + + if (t.user == null) { + t.user = authProvider.loggedInUser(request); + LOGGER.debug("preSave(): user of log entry is {}", t.user); + } } @Override diff --git a/app/services/impl/MessageServiceImpl.java b/app/services/impl/MessageServiceImpl.java index 91c14804..07dc5877 100644 --- a/app/services/impl/MessageServiceImpl.java +++ b/app/services/impl/MessageServiceImpl.java @@ -1,23 +1,42 @@ package services.impl; +import actors.ActivityActorRef; +import actors.ActivityProtocol; +import actors.MessageWordCountActorRef; +import actors.WordCountProtocol; +import criterias.GetCriteria; +import criterias.MessageCriteria; import io.ebean.Ebean; import io.ebean.PagedList; -import criterias.MessageCriteria; +import mappers.MessageMapper; import models.ActionType; import models.Message; import models.Project; +import models.User; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import play.mvc.Http; import repositories.MessageRepository; -import services.*; +import repositories.Persistence; +import services.AuthProvider; +import services.CacheService; +import services.LogEntryService; +import services.MessageService; +import services.MetricService; +import utils.MessageUtils; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.UUID; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; import static utils.Stopwatch.log; /** @@ -26,21 +45,35 @@ */ @Singleton public class MessageServiceImpl extends AbstractModelService - implements MessageService { + implements MessageService { private static final Logger LOGGER = LoggerFactory.getLogger(MessageServiceImpl.class); private final MessageRepository messageRepository; private final MetricService metricService; + private final MessageWordCountActorRef messageWordCountActor; + private final Persistence persistence; + private final MessageMapper messageMapper; @Inject - public MessageServiceImpl(Validator validator, CacheService cache, - MessageRepository messageRepository, LogEntryService logEntryService, - AuthProvider authProvider, MetricService metricService) { - super(validator, cache, messageRepository, Message::getCacheKey, logEntryService, authProvider); + public MessageServiceImpl( + Validator validator, + CacheService cache, + MessageRepository messageRepository, + LogEntryService logEntryService, + AuthProvider authProvider, + MetricService metricService, + ActivityActorRef activityActor, + MessageWordCountActorRef messageWordCountActor, + Persistence persistence, + MessageMapper messageMapper) { + super(validator, cache, messageRepository, Message::getCacheKey, logEntryService, authProvider, activityActor); this.messageRepository = messageRepository; this.metricService = metricService; + this.messageWordCountActor = messageWordCountActor; + this.persistence = persistence; + this.messageMapper = messageMapper; } /** @@ -49,12 +82,12 @@ public MessageServiceImpl(Validator validator, CacheService cache, @Override public int countBy(Project project) { return log( - () -> cache.getOrElseUpdate( - String.format("project:id:%s:message:countByProject", project.id), - () -> messageRepository.countBy(project), - 60), - LOGGER, - "countBy" + () -> cache.getOrElseUpdate( + String.format("project:id:%s:message:countByProject", project.id), + () -> messageRepository.countBy(project), + 60), + LOGGER, + "countBy" ); } @@ -65,23 +98,14 @@ public int countBy(Project project) { public void resetWordCount(UUID projectId) { try { Ebean - .createSqlUpdate( - "update message set word_count = null from locale where message.locale_id = locale.id and locale.project_id = :projectId") - .setParameter("projectId", projectId).execute(); + .createSqlUpdate( + "update message set word_count = null from locale where message.locale_id = locale.id and locale.project_id = :projectId") + .setParameter("projectId", projectId).execute(); } catch (Exception e) { LOGGER.error("Error while resetting word count", e); } } - @Override - public List latest(Project project, int limit) { - return cache.getOrElseUpdate( - String.format("project:id:%s:latest:messages:%d", project.id, limit), - () -> messageRepository.latest(project, limit), - 60 - ); - } - @Override protected PagedList postFind(PagedList pagedList, Http.Request request) { metricService.logEvent(Message.class, ActionType.Read); @@ -108,6 +132,8 @@ protected void postCreate(Message t, Http.Request request) { cache.removeByPrefix(String.format("message:criteria:%s", t.key.project.id)); cache.removeByPrefix(String.format("locale:criteria:%s", t.key.project.id)); cache.removeByPrefix(String.format("key:criteria:%s", t.key.project.id)); + + activityActor.tell(logEntryCreate(t, authProvider.loggedInUser(request)), null); } @Override @@ -129,5 +155,156 @@ protected void postDelete(Message t, Http.Request request) { super.postDelete(t, request); metricService.logEvent(Message.class, ActionType.Delete); + + if (t.wordCount != null) { + messageWordCountActor.tell(new WordCountProtocol.ChangeMessageWordCount(t.id, t.locale.project.id, t.locale.id, + t.key.id, 0, -t.wordCount), null); + } + } + + @Override + protected void preUpdate(Message t, Http.Request request) { + super.preUpdate(t, request); + + Message existing = byId(GetCriteria.from(t.id, request)); + if (!Objects.equals(t.value, existing.value)) { + // Only track changes of messageĀ“s value + activityActor.tell(logEntryUpdate(t, existing, authProvider.loggedInUser(request)), null); + } + } + + @Override + protected void preSave(Collection t, Http.Request request) { + super.preSave(t, request); + + Map wordCount = t.stream() + .filter(m -> m.id != null) + .map(m -> { + int wc = MessageUtils.wordCount(m); + return new WordCountProtocol.ChangeMessageWordCount( + m.id, + m.locale.project.id, + m.locale.id, + m.key.id, + wc, + wc - (m.wordCount != null ? m.wordCount : 0)); + }) + .collect(toMap(wc -> wc.messageId, wc -> wc, (a, b) -> a)); + + messageWordCountActor.tell(wordCount.values(), null); + + // Update model + t.stream() + .filter(m -> m.id != null) + .forEach(m -> m.wordCount = wordCount.getOrDefault(m.id, + new WordCountProtocol.ChangeMessageWordCount(null, null, null, null, 0, 0)).wordCount); + t.stream() + .filter(m -> m.id == null) + .forEach(m -> m.wordCount = MessageUtils.wordCount(m)); + + List ids = t.stream().filter(m -> m.id != null).map(m -> m.id).collect(toList()); + Map messages = ids.size() > 0 ? messageRepository.byIds(ids) : Collections.emptyMap(); + User loggedInUser = authProvider.loggedInUser(request); + + activityActor.tell( + new ActivityProtocol.Activities<>(t.stream().filter( + // Only track changes of messageĀ“s value + m -> persistence.isNew(m) || !Objects + .equals(m.value, messages.get(m.id).value)) + .map(m -> persistence.isNew(m) ? logEntryCreate(m, loggedInUser) + : logEntryUpdate(m, messages.get(m.id), loggedInUser)) + .collect(toList())), + null + ); + } + + @Override + protected void postSave(Collection t, Http.Request request) { + super.postSave(t, request); + + List noWordCountMessages = + t.stream().filter(m -> m.wordCount == null).collect(toList()); + Map wordCount = noWordCountMessages.stream().map(m -> { + int wc = MessageUtils.wordCount(m); + return new WordCountProtocol.ChangeMessageWordCount(m.id, m.locale.project.id, m.locale.id, m.key.id, wc, + wc - (m.wordCount != null ? m.wordCount : 0)); + }).collect(toMap(wc -> wc.messageId, wc -> wc)); + + messageWordCountActor.tell(wordCount.values(), null); + + // Update model + noWordCountMessages.stream().filter(m -> m.id != null).forEach(m -> m.wordCount = wordCount + .getOrDefault(m.id, new WordCountProtocol.ChangeMessageWordCount(null, null, null, null, 0, 0)).wordCount); + + try { + save(noWordCountMessages, request); + } catch (Exception e) { + LOGGER.error("Error while persisting word count", e); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void preDelete(Message t, Http.Request request) { + super.preDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), t.key.project, dto.Message.class, messageMapper.toDto(t, request), + null), + null + ); + } + + @Override + protected void preDelete(Collection t, Http.Request request) { + super.preDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activities<>(t.stream().map(m -> new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), m.key.project, + dto.Message.class, messageMapper.toDto(m, request), null)).collect(toList())), + null + ); + } + + /** + * {@inheritDoc} + */ + @Override + protected void postDelete(Collection t, Http.Request request) { + super.postDelete(t, request); + + Map wordCount = + t.stream() + .filter(m -> m.wordCount != null) + .map( + m -> new WordCountProtocol.ChangeMessageWordCount(m.id, m.locale.project.id, m.locale.id, m.key.id, 0, + -m.wordCount)) + .collect(toMap(wc -> wc.messageId, wc -> wc, (a, b) -> a)); + + messageWordCountActor.tell(wordCount.values(), null); + } + + private ActivityProtocol.Activity logEntryCreate(Message message, User loggedInUser) { + return new ActivityProtocol.Activity<>( + ActionType.Create, + loggedInUser, + message.key.project, + dto.Message.class, + null, + messageMapper.toDto(message, null) + ); + } + + private ActivityProtocol.Activity logEntryUpdate(Message message, Message previous, User loggedInUser) { + return new ActivityProtocol.Activity<>( + ActionType.Update, + loggedInUser, + message.key.project, + dto.Message.class, + messageMapper.toDto(previous, null), + messageMapper.toDto(message, null) + ); } } diff --git a/app/services/impl/NoCacheServiceImpl.java b/app/services/impl/NoCacheServiceImpl.java index add3ff8a..9029136c 100644 --- a/app/services/impl/NoCacheServiceImpl.java +++ b/app/services/impl/NoCacheServiceImpl.java @@ -1,17 +1,19 @@ package services.impl; -import play.cache.SyncCacheApi; import services.CacheService; import javax.inject.Inject; import javax.inject.Singleton; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; +import java.util.function.Predicate; @Singleton -public class NoCacheServiceImpl extends CacheServiceImpl implements CacheService { +public class NoCacheServiceImpl implements CacheService { @Inject - public NoCacheServiceImpl(SyncCacheApi cache) { - super(cache); + public NoCacheServiceImpl() { } @Override @@ -23,6 +25,23 @@ public T getOrElseUpdate(String key, Callable block) { } } + @Override + public void set(String key, Object value, int expiration) { + } + + @Override + public void set(String key, Object value) { + } + + @Override + public void remove(String key) { + } + + @Override + public Optional get(String key) { + return Optional.empty(); + } + @Override public T getOrElseUpdate(String key, Callable block, int expiration) { try { @@ -31,4 +50,21 @@ public T getOrElseUpdate(String key, Callable block, int expiration) { return null; } } + + @Override + public Map keys() { + return Collections.emptyMap(); + } + + @Override + public void removeByPrefix(String prefix) { + } + + @Override + public void removeAll(Predicate filter) { + } + + @Override + public void removeAll() { + } } diff --git a/app/services/impl/NotificationServiceDummy.java b/app/services/impl/NotificationServiceDummy.java index 7099455b..72d78071 100644 --- a/app/services/impl/NotificationServiceDummy.java +++ b/app/services/impl/NotificationServiceDummy.java @@ -5,13 +5,12 @@ import io.getstream.client.model.activities.AggregatedActivity; import io.getstream.client.model.activities.SimpleActivity; import io.getstream.client.model.beans.StreamResponse; -import java.io.IOException; -import java.util.UUID; import models.ActionType; -import models.Project; -import models.User; import services.NotificationService; +import java.io.IOException; +import java.util.UUID; + /** * @author resamsel * @version 19 May 2017 diff --git a/app/services/impl/ProjectServiceImpl.java b/app/services/impl/ProjectServiceImpl.java index 0ab3bfa4..46878341 100644 --- a/app/services/impl/ProjectServiceImpl.java +++ b/app/services/impl/ProjectServiceImpl.java @@ -1,8 +1,13 @@ package services.impl; +import actors.ActivityActorRef; +import actors.ActivityProtocol; import criterias.GetCriteria; -import io.ebean.PagedList; import criterias.ProjectCriteria; +import dto.NotFoundException; +import dto.PermissionException; +import io.ebean.PagedList; +import mappers.ProjectMapper; import models.ActionType; import models.Locale; import models.Project; @@ -21,12 +26,15 @@ import services.LogEntryService; import services.MessageService; import services.MetricService; +import services.PermissionService; import services.ProjectService; import services.ProjectUserService; import javax.inject.Inject; import javax.inject.Singleton; import javax.validation.Validator; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -41,7 +49,7 @@ */ @Singleton public class ProjectServiceImpl extends AbstractModelService - implements ProjectService { + implements ProjectService { private static final Logger LOGGER = LoggerFactory.getLogger(ProjectServiceImpl.class); @@ -52,14 +60,26 @@ public class ProjectServiceImpl extends AbstractModelService postGet(cache.getOrElseUpdate( Project.getCacheKey(username, name, fetches), - () -> projectRepository.byOwnerAndName(username, name, fetches), + () -> projectRepository.byOwnerAndName(username, name, authProvider.loggedInUserId(request), fetches), 10 * 30 ), request), LOGGER, @@ -88,17 +110,18 @@ public Project byOwnerAndName(String username, String name, Http.Request request /** * {@inheritDoc} + * @return */ @Override - public void increaseWordCountBy(UUID projectId, int wordCountDiff, Http.Request request) { + public Project increaseWordCountBy(UUID projectId, int wordCountDiff, Http.Request request) { if (wordCountDiff == 0) { LOGGER.debug("Not changing word count"); - return; + return null; } Project project = byId(GetCriteria.from(projectId, request)); if (project == null) { - return; + return null; } if (project.wordCount == null) { @@ -107,37 +130,39 @@ public void increaseWordCountBy(UUID projectId, int wordCountDiff, Http.Request project.wordCount += wordCountDiff; log( - () -> modelRepository.save(project), - LOGGER, - "Increased word count by %d", - wordCountDiff + () -> modelRepository.save(project), + LOGGER, + "Increased word count by %d", + wordCountDiff ); - postUpdate(project, request); + return postUpdate(project, request); } /** * {@inheritDoc} */ @Override - public void resetWordCount(UUID projectId, Http.Request request) { - Project project = byId(projectId, request, ProjectRepository.FETCH_LOCALES); + public Project resetWordCount(UUID projectId, Http.Request request) { + Project project = byId(GetCriteria.from(projectId, request, ProjectRepository.FETCH_LOCALES)); List localeIds = project.locales.stream().map(Locale::getId).collect(toList()); project.wordCount = null; modelRepository.save(project); - postUpdate(project, request); + Project updated = postUpdate(project, request); localeService.resetWordCount(projectId); keyService.resetWordCount(projectId); messageService.resetWordCount(projectId); messageService.save(messageRepository.byLocales(localeIds), request); + + return updated; } @Override - public void changeOwner(Project project, User owner, Http.Request request) { + public Project changeOwner(Project project, User owner, Http.Request request) { LOGGER.debug("changeOwner(project={}, owner={})", project, owner); requireNonNull(project, "project"); @@ -145,17 +170,17 @@ public void changeOwner(Project project, User owner, Http.Request request) { // Make old owner a member of type Manager ProjectUser ownerRole = project.members.stream() - .filter(m -> m.role == ProjectRole.Owner) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Project has no owner")); + .filter(m -> m.role == ProjectRole.Owner) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Project has no owner")); ownerRole.role = ProjectRole.Manager; // Make new owner a member of type Owner ProjectUser newOwnerRole = project.members.stream() - .filter(m -> m.user.id.equals(owner.id)) - .findFirst() - .orElseGet(() -> new ProjectUser(ProjectRole.Manager).withProject(project).withUser(owner)); + .filter(m -> m.user.id.equals(owner.id)) + .findFirst() + .orElseGet(() -> new ProjectUser(ProjectRole.Manager).withProject(project).withUser(owner)); newOwnerRole.role = ProjectRole.Owner; @@ -163,9 +188,12 @@ public void changeOwner(Project project, User owner, Http.Request request) { project.members.add(newOwnerRole); } - update(project.withOwner(owner), request); + Project updated = update(project.withOwner(owner), request); + projectUserService.update(ownerRole, request); projectUserService.update(newOwnerRole, request); + + return updated; } @Override @@ -201,6 +229,11 @@ protected void postCreate(Project t, Http.Request request) { // When project has been created, the project cache needs to be invalidated cache.removeByPrefix("project:criteria:"); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Create, authProvider.loggedInUser(request), t, dto.Project.class, null, toDto(t, request)), + null + ); } @Override @@ -218,17 +251,31 @@ protected Project postUpdate(Project t, Http.Request request) { )); } else { cache.removeByPrefix(Project.getCacheKey( - requireNonNull(t.owner, "owner").username, - "" + requireNonNull(t.owner, "owner").username, + "" )); } return super.postUpdate(t, request); } - /** - * {@inheritDoc} - */ + @Override + protected void preDelete(Project t, Http.Request request) { + super.preDelete(t, request); + + User loggedInUser = authProvider.loggedInUser(request); + + if (!permissionService + .hasPermissionAny(t.id, loggedInUser, ProjectRole.Owner, ProjectRole.Manager)) { + throw new PermissionException("User not allowed in project"); + } + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Delete, loggedInUser, t, dto.Project.class, toDto(t, request), null), + null + ); + } + @Override protected void postDelete(Project t, Http.Request request) { super.postDelete(t, request); @@ -240,4 +287,52 @@ protected void postDelete(Project t, Http.Request request) { cache.removeByPrefix(Project.getCacheKey(t.owner.username, t.name)); } + + @Override + protected void preDelete(Collection t, Http.Request request) { + super.preDelete(t, request); + + User loggedInUser = authProvider.loggedInUser(request); + for (Project p : t) { + if (p == null || p.deleted) { + throw new NotFoundException(dto.Project.class.getSimpleName(), p != null ? p.id : null); + } + if (!permissionService + .hasPermissionAny(p.id, loggedInUser, ProjectRole.Owner, ProjectRole.Manager)) { + throw new PermissionException("User not allowed in project"); + } + } + } + + @Override + protected void postDelete(Collection t, Http.Request request) { + super.postDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activities<>(t.stream() + .map(p -> new ActivityProtocol.Activity<>(ActionType.Delete, authProvider.loggedInUser(request), p, dto.Project.class, toDto(p, request), null)) + .collect(toList())), + null + ); + } + + @Override + protected void preUpdate(Project t, Http.Request request) { + super.preUpdate(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Update, authProvider.loggedInUser(request), t, dto.Project.class, toDto(byId(GetCriteria.from(t.id, request)), request), toDto(t, request)), + null + ); + } + + private dto.Project toDto(Project t, Http.Request request) { + dto.Project out = projectMapper.toDto(t, request); + + out.keys = Collections.emptyList(); + out.locales = Collections.emptyList(); + out.messages = Collections.emptyList(); + + return out; + } } diff --git a/app/services/impl/ProjectUserServiceImpl.java b/app/services/impl/ProjectUserServiceImpl.java index 3d0714d6..d50139e6 100644 --- a/app/services/impl/ProjectUserServiceImpl.java +++ b/app/services/impl/ProjectUserServiceImpl.java @@ -1,12 +1,22 @@ package services.impl; -import io.ebean.PagedList; +import actors.ActivityActorRef; +import actors.ActivityProtocol; +import criterias.GetCriteria; import criterias.ProjectUserCriteria; +import dto.PermissionException; +import io.ebean.PagedList; +import mappers.ProjectUserMapper; import models.ActionType; import models.ProjectUser; import play.mvc.Http; import repositories.ProjectUserRepository; -import services.*; +import services.AuthProvider; +import services.CacheService; +import services.LogEntryService; +import services.MetricService; +import services.ProjectUserService; +import validators.ProjectUserModifyAllowedValidator; import javax.inject.Inject; import javax.inject.Singleton; @@ -18,19 +28,27 @@ */ @Singleton public class ProjectUserServiceImpl extends - AbstractModelService implements ProjectUserService { + AbstractModelService implements ProjectUserService { private final ProjectUserRepository projectUserRepository; private final MetricService metricService; + private final ProjectUserModifyAllowedValidator projectUserModifyAllowedValidator; @Inject - public ProjectUserServiceImpl(Validator validator, CacheService cache, - ProjectUserRepository projectUserRepository, LogEntryService logEntryService, - AuthProvider authProvider, MetricService metricService) { - super(validator, cache, projectUserRepository, ProjectUser::getCacheKey, logEntryService, authProvider); + public ProjectUserServiceImpl( + Validator validator, + CacheService cache, + ProjectUserRepository projectUserRepository, + LogEntryService logEntryService, + AuthProvider authProvider, + MetricService metricService, + ActivityActorRef activityActor, + ProjectUserModifyAllowedValidator projectUserModifyAllowedValidator) { + super(validator, cache, projectUserRepository, ProjectUser::getCacheKey, logEntryService, authProvider, activityActor); this.projectUserRepository = projectUserRepository; this.metricService = metricService; + this.projectUserModifyAllowedValidator = projectUserModifyAllowedValidator; } @Override @@ -55,6 +73,15 @@ protected ProjectUser postGet(ProjectUser projectUser, Http.Request request) { return super.postGet(projectUser, request); } + @Override + protected void preCreate(ProjectUser t, Http.Request request) { + super.preCreate(t, request); + + if (!projectUserModifyAllowedValidator.isValid(t, request)) { + throw new PermissionException("member.invalid"); + } + } + @Override protected void postCreate(ProjectUser t, Http.Request request) { super.postCreate(t, request); @@ -62,6 +89,29 @@ protected void postCreate(ProjectUser t, Http.Request request) { metricService.logEvent(ProjectUser.class, ActionType.Create); cache.removeByPrefix("member:criteria:" + t.project.id); + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Create, authProvider.loggedInUser(request), t.project, dto.ProjectUser.class, null, toDto(t) + ), + null + ); + } + + @Override + protected void preUpdate(ProjectUser t, Http.Request request) { + super.preUpdate(t, request); + + if (!projectUserModifyAllowedValidator.isValid(t, request)) { + throw new PermissionException("member.invalid"); + } + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Update, authProvider.loggedInUser(request), t.project, dto.ProjectUser.class, toDto(byId(GetCriteria.from(t.id, request))), toDto(t) + ), + null + ); } @Override @@ -76,9 +126,18 @@ protected ProjectUser postUpdate(ProjectUser t, Http.Request request) { return t; } - /** - * {@inheritDoc} - */ + @Override + protected void preDelete(ProjectUser t, Http.Request request) { + super.preDelete(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>( + ActionType.Delete, authProvider.loggedInUser(request), t.project, dto.ProjectUser.class, toDto(t), null + ), + null + ); + } + @Override protected void postDelete(ProjectUser t, Http.Request request) { super.postDelete(t, request); @@ -88,4 +147,8 @@ protected void postDelete(ProjectUser t, Http.Request request) { // When member has been deleted, the key cache needs to be invalidated cache.removeByPrefix("member:criteria:" + t.project.id); } + + private dto.ProjectUser toDto(ProjectUser t) { + return ProjectUserMapper.toDto(t); + } } diff --git a/app/services/impl/UserFeatureFlagServiceImpl.java b/app/services/impl/UserFeatureFlagServiceImpl.java index 493d3150..52848d25 100644 --- a/app/services/impl/UserFeatureFlagServiceImpl.java +++ b/app/services/impl/UserFeatureFlagServiceImpl.java @@ -1,5 +1,6 @@ package services.impl; +import actors.ActivityActorRef; import io.ebean.PagedList; import criterias.UserFeatureFlagCriteria; import models.ActionType; @@ -23,10 +24,15 @@ public class UserFeatureFlagServiceImpl extends @Inject public UserFeatureFlagServiceImpl( - Validator validator, CacheService cache, AuthProvider authProvider, - UserFeatureFlagRepository userFeatureFlagRepository, LogEntryService logEntryService, - MetricService metricService) { - super(validator, cache, userFeatureFlagRepository, UserFeatureFlag::getCacheKey, logEntryService, authProvider); + Validator validator, + CacheService cache, + AuthProvider authProvider, + UserFeatureFlagRepository userFeatureFlagRepository, + LogEntryService logEntryService, + MetricService metricService, + ActivityActorRef activityActor) { + super(validator, cache, userFeatureFlagRepository, UserFeatureFlag::getCacheKey, logEntryService, authProvider, activityActor); + this.metricService = metricService; } @@ -61,6 +67,18 @@ protected void postCreate(UserFeatureFlag t, Http.Request request) { metricService.logEvent(UserFeatureFlag.class, ActionType.Create); } + @Override + protected void preSave(UserFeatureFlag t, Http.Request request) { + super.preSave(t, request); + + User loggedInUser = authProvider.loggedInUser(request); + if (t.user == null || t.user.id == null + || (loggedInUser != null && t.user.id != loggedInUser.id && loggedInUser.role != UserRole.Admin)) { + // only allow admins to create access tokens for other users + t.user = loggedInUser; + } + } + @Override protected UserFeatureFlag postUpdate(UserFeatureFlag t, Http.Request request) { super.postUpdate(t, request); diff --git a/app/services/impl/UserServiceImpl.java b/app/services/impl/UserServiceImpl.java index ee4c43a0..3936407d 100644 --- a/app/services/impl/UserServiceImpl.java +++ b/app/services/impl/UserServiceImpl.java @@ -1,12 +1,16 @@ package services.impl; +import actors.ActivityActorRef; +import actors.ActivityProtocol; import criterias.AccessTokenCriteria; +import criterias.GetCriteria; import criterias.LinkedAccountCriteria; import criterias.LogEntryCriteria; import criterias.ProjectCriteria; import criterias.ProjectUserCriteria; import criterias.UserCriteria; import io.ebean.PagedList; +import mappers.UserMapper; import models.ActionType; import models.Setting; import models.User; @@ -56,11 +60,19 @@ public class UserServiceImpl extends AbstractModelService cached = cache.get(User.getCacheKey(t.getId())); cached.ifPresent(user -> cache.removeByPrefix(User.getCacheKey(user.username))); } @@ -201,6 +215,16 @@ protected void postCreate(User t, Http.Request request) { cache.removeByPrefix("user:criteria:"); } + @Override + protected void preUpdate(User t, Http.Request request) { + super.preUpdate(t, request); + + activityActor.tell( + new ActivityProtocol.Activity<>(ActionType.Update, authProvider.loggedInUser(request), null, dto.User.class, toDto(byId(GetCriteria.from(t.id, request))), toDto(t)), + null + ); + } + @Override protected User postUpdate(User t, Http.Request request) { super.postUpdate(t, request); @@ -210,7 +234,7 @@ protected User postUpdate(User t, Http.Request request) { // When user has been updated, the user cache needs to be invalidated cache.removeByPrefix("user:criteria:"); - return byId(t.id, request).updateFrom(t); + return byId(GetCriteria.from(t.id, request)).updateFrom(t); } /** @@ -225,4 +249,8 @@ protected void postDelete(User t, Http.Request request) { // When locale has been deleted, the locale cache needs to be invalidated cache.removeByPrefix("user:criteria:"); } + + private dto.User toDto(User t) { + return UserMapper.toDto(t); + } } diff --git a/app/validators/AccessTokenByUserAndName.java b/app/validators/AccessTokenByUserAndName.java deleted file mode 100644 index 9669e7d2..00000000 --- a/app/validators/AccessTokenByUserAndName.java +++ /dev/null @@ -1,28 +0,0 @@ -package validators; - -import play.data.Form; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * - * @author resamsel - * @version 6 Oct 2016 - */ -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = AccessTokenByUserAndNameValidator.class) -@Form.Display(name = "constraint.accesstokenbyuserandname") -public @interface AccessTokenByUserAndName -{ - String message() default AccessTokenByUserAndNameValidator.MESSAGE; - - Class[] groups() default {}; - - Class[] payload() default {}; -} diff --git a/app/validators/AccessTokenByUserAndNameValidator.java b/app/validators/AccessTokenByUserAndNameValidator.java deleted file mode 100644 index f21e1c8f..00000000 --- a/app/validators/AccessTokenByUserAndNameValidator.java +++ /dev/null @@ -1,53 +0,0 @@ -package validators; - -import play.data.validation.Constraints; -import play.libs.F.Tuple; -import repositories.AccessTokenRepository; -import services.AuthProvider; - -import javax.inject.Inject; -import javax.validation.ConstraintValidator; - -/** - * @author resamsel - * @version 6 Oct 2016 - */ -public class AccessTokenByUserAndNameValidator extends Constraints.Validator - implements ConstraintValidator { - - public static final String MESSAGE = "error.accesstokenbyuserandname"; - - private final AccessTokenRepository accessTokenRepository; - private final AuthProvider authProvider; - - @Inject - public AccessTokenByUserAndNameValidator(AccessTokenRepository accessTokenRepository, - AuthProvider authProvider) { - this.accessTokenRepository = accessTokenRepository; - this.authProvider = authProvider; - } - - /** - * {@inheritDoc} - */ - @Override - public void initialize(AccessTokenByUserAndName constraintAnnotation) { - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isValid(Object object) { - return object != null && object instanceof String - && accessTokenRepository.byUserAndName(authProvider.loggedInUserId(null) /* FIXME: will fail! */, (String) object) == null; - } - - /** - * {@inheritDoc} - */ - @Override - public Tuple getErrorMessageKey() { - return null; - } -} diff --git a/app/validators/CustomValidator.java b/app/validators/CustomValidator.java new file mode 100644 index 00000000..96606c2c --- /dev/null +++ b/app/validators/CustomValidator.java @@ -0,0 +1,8 @@ +package validators; + +import models.Model; +import play.mvc.Http; + +public interface CustomValidator> { + boolean isValid(T model, Http.Request request); +} diff --git a/app/validators/ProjectNameUniqueChecker.java b/app/validators/ProjectNameUniqueChecker.java index 9618deb9..9539ce78 100644 --- a/app/validators/ProjectNameUniqueChecker.java +++ b/app/validators/ProjectNameUniqueChecker.java @@ -31,7 +31,7 @@ public boolean isValid(Object o) { } Project t = (Project) o; - Project existing = projectService.byOwnerAndName(t.owner.username, t.name, null /* FIXME */); + Project existing = projectService.byOwnerAndName(t.owner.username, t.name, null); LOGGER.debug("existing project: {}", existing); diff --git a/app/validators/ProjectNameValidator.java b/app/validators/ProjectNameValidator.java index 595f31f1..554626c5 100644 --- a/app/validators/ProjectNameValidator.java +++ b/app/validators/ProjectNameValidator.java @@ -29,6 +29,6 @@ public void initialize(ProjectName constraintAnnotation) { */ @Override public boolean isValid(Object object, ConstraintValidatorContext constraintContext) { - return object != null && object instanceof String && !excluded.contains(object); + return object instanceof String && !excluded.contains(object); } } diff --git a/app/validators/ProjectUserModifyAllowed.java b/app/validators/ProjectUserModifyAllowed.java deleted file mode 100644 index a4575c63..00000000 --- a/app/validators/ProjectUserModifyAllowed.java +++ /dev/null @@ -1,20 +0,0 @@ -package validators; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = ProjectUserModifyAllowedValidator.class) -public @interface ProjectUserModifyAllowed { - - String message() default ProjectUserModifyAllowedValidator.MESSAGE; - - Class[] groups() default {}; - - Class[] payload() default {}; -} diff --git a/app/validators/ProjectUserModifyAllowedValidator.java b/app/validators/ProjectUserModifyAllowedValidator.java index 45055ffe..4529f6f9 100644 --- a/app/validators/ProjectUserModifyAllowedValidator.java +++ b/app/validators/ProjectUserModifyAllowedValidator.java @@ -5,17 +5,12 @@ import models.ProjectRole; import models.ProjectUser; import models.User; -import play.i18n.Lang; -import play.i18n.MessagesApi; -import play.inject.Injector; +import play.mvc.Http; import repositories.ProjectUserRepository; import services.AuthProvider; import javax.inject.Inject; import javax.inject.Singleton; -import javax.validation.ConstraintValidator; -import javax.validation.ConstraintValidatorContext; -import java.util.Locale; import java.util.Objects; /** @@ -23,46 +18,25 @@ * @version 23 Jan 2017 */ @Singleton -public class ProjectUserModifyAllowedValidator implements ConstraintValidator { +public class ProjectUserModifyAllowedValidator implements CustomValidator { - public static final String MESSAGE = "error.notamember"; - private static final String OWNER_ROLE_PERMISSION_MESSAGE = "error.memberrolepermission.owner"; - private static final String ANY_ROLE_PERMISSION_MESSAGE = "error.memberrolepermission.any"; private final ProjectUserRepository projectUserRepository; - private final Injector injector; private final AuthProvider authProvider; - private MessagesApi messagesApi; - private Lang lang; - @Inject public ProjectUserModifyAllowedValidator( - ProjectUserRepository projectUserRepository, Injector injector, AuthProvider authProvider) { + ProjectUserRepository projectUserRepository, AuthProvider authProvider) { this.projectUserRepository = projectUserRepository; - this.injector = injector; this.authProvider = authProvider; } @Override - public void initialize(ProjectUserModifyAllowed constraintAnnotation) { - this.messagesApi = injector.instanceOf(MessagesApi.class); -// Http.Context ctx = contextProvider.getOrNull(); -// if (ctx != null) { -// this.lang = ctx.lang(); -// } else { - this.lang = new Lang(Locale.ENGLISH); -// } - } - - @Override - public boolean isValid(ProjectUser value, ConstraintValidatorContext context) { - if (value == null) { - context.buildConstraintViolationWithTemplate("Value required") - .addConstraintViolation(); + public boolean isValid(ProjectUser model, Http.Request request) { + if (model == null) { return false; } - User loggedInUser = authProvider.loggedInUser(null) /* FIXME: will fail! */; + User loggedInUser = authProvider.loggedInUser(request); if (loggedInUser == null) { // Anonymous users are allowed to do nothing return false; @@ -73,42 +47,21 @@ public boolean isValid(ProjectUser value, ConstraintValidatorContext context) { return true; } - if (value.project.owner != null && Objects.equals(loggedInUser.id, value.project.owner.id)) { + if (model.project.owner != null && Objects.equals(loggedInUser.id, model.project.owner.id)) { // loggedInUser is owner return true; } PagedList found = projectUserRepository.findBy( - new ProjectUserCriteria().withProjectId(value.project.id).withUserId(loggedInUser.id)); - if (found == null || found.getList() == null || found.getList().isEmpty()) { + new ProjectUserCriteria().withProjectId(model.project.id).withUserId(loggedInUser.id)); + if (found == null || found.getList().isEmpty()) { // Logged-in-user is not member of project - context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(messagesApi.get(lang, MESSAGE)) - .addPropertyNode("user") - .addConstraintViolation(); return false; } ProjectUser member = found.getList().get(0); // Is the logged-in-user allowed to set the Owner role? - if (member.role == ProjectRole.Owner) { - // Owners are allowed to do anything - return true; - } - - if (member.role == ProjectRole.Manager && value.role == ProjectRole.Owner) { - // Owner role cannot be set by a Manager - context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(messagesApi.get(lang, OWNER_ROLE_PERMISSION_MESSAGE)) - .addPropertyNode("role") - .addConstraintViolation(); - return false; - } - - context.disableDefaultConstraintViolation(); - context.buildConstraintViolationWithTemplate(messagesApi.get(lang, ANY_ROLE_PERMISSION_MESSAGE)) - .addPropertyNode("role") - .addConstraintViolation(); - return false; + // Owners are allowed to do anything + return member.role == ProjectRole.Owner; } } diff --git a/app/validators/UserByUsername.java b/app/validators/UserByUsername.java deleted file mode 100644 index 30a88e51..00000000 --- a/app/validators/UserByUsername.java +++ /dev/null @@ -1,27 +0,0 @@ -package validators; - -import play.data.Form; - -import javax.validation.Constraint; -import javax.validation.Payload; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * @author resamsel - * @version 6 Oct 2016 - */ -@Target(ElementType.FIELD) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = UserByUsernameValidator.class) -@Form.Display(name = "constraint.userbyusername") -public @interface UserByUsername { - - String message() default UserByUsernameValidator.MESSAGE; - - Class[] groups() default {}; - - Class[] payload() default {}; -} diff --git a/app/validators/UserByUsernameValidator.java b/app/validators/UserByUsernameValidator.java deleted file mode 100644 index 05f4d391..00000000 --- a/app/validators/UserByUsernameValidator.java +++ /dev/null @@ -1,49 +0,0 @@ -package validators; - -import play.data.validation.Constraints; -import play.libs.F.Tuple; -import services.UserService; - -import javax.inject.Inject; -import javax.validation.ConstraintValidator; - -/** - * @author resamsel - * @version 6 Oct 2016 - */ -public class UserByUsernameValidator extends Constraints.Validator - implements ConstraintValidator { - - public static final String MESSAGE = "error.userbyusername"; - - private UserService userService; - - @Inject - public UserByUsernameValidator(UserService userService) { - this.userService = userService; - } - - /** - * {@inheritDoc} - */ - @Override - public void initialize(UserByUsername constraintAnnotation) { - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isValid(Object object) { - return object != null && object instanceof String - && userService.byUsername((String) object, null /* FIXME */) != null; - } - - /** - * {@inheritDoc} - */ - @Override - public Tuple getErrorMessageKey() { - return null; - } -} diff --git a/app/validators/UsernameValidator.java b/app/validators/UsernameValidator.java index b12de7c7..6226fefa 100644 --- a/app/validators/UsernameValidator.java +++ b/app/validators/UsernameValidator.java @@ -1,8 +1,6 @@ package validators; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @@ -16,14 +14,12 @@ */ public class UsernameValidator implements ConstraintValidator { - private static final Logger LOGGER = LoggerFactory.getLogger(UsernameValidator.class); - public static final String MESSAGE = "error.username.invalid"; private List excluded; @Override public void initialize(Username constraintAnnotation) { - excluded = Arrays.asList(constraintAnnotation.excluded().split(",")).stream() + excluded = Arrays.stream(constraintAnnotation.excluded().split(",")) .map(StringUtils::strip).collect(Collectors.toList()); } diff --git a/src/it/java/controllers/LocalesApiTest.java b/src/it/java/controllers/LocalesApiTest.java index 398e8d76..7332aa9d 100644 --- a/src/it/java/controllers/LocalesApiTest.java +++ b/src/it/java/controllers/LocalesApiTest.java @@ -30,6 +30,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static play.inject.Bindings.bind; +import static play.mvc.Results.ok; import static play.test.Helpers.route; import static utils.TestFactory.requestAs; import static utils.UserRepositoryMock.byUsername; @@ -96,17 +97,18 @@ protected Binding[] createBindings() { ProjectUserService.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation())) ); + LocaleMapper localeMapper = mock(LocaleMapper.class); when(accessTokenService.byKey(eq(accessToken.key), any())).thenReturn(accessToken); when(projectUserService.findBy(any())) .thenReturn(PagedListFactory.create(project1.members)); when(localeApiService .byOwnerAndProjectAndName(request, eq(johnSmith.username), eq(project1.name), eq(locale.name))) - .thenReturn(LocaleMapper.toDto(locale)); + .thenReturn(localeMapper.toDto(locale, request)); when(localeApiService.byOwnerAndProjectAndName(request, eq("a"), eq("b"), eq("c"))) .thenThrow(new NotFoundException(dto.Locale.class.getName(), "c")); when(localeApiService.download(request, eq(locale.id), eq("java_properties"))) - .thenReturn("".getBytes()); + .thenReturn(ok("".getBytes())); //noinspection unchecked return ArrayUtils.addAll( diff --git a/src/it/java/services/AccessTokenServiceIntegrationTest.java b/src/it/java/services/AccessTokenServiceIntegrationTest.java index d242a34f..f8bab285 100644 --- a/src/it/java/services/AccessTokenServiceIntegrationTest.java +++ b/src/it/java/services/AccessTokenServiceIntegrationTest.java @@ -1,15 +1,16 @@ package services; -import static org.assertj.core.api.Assertions.assertThat; - import criterias.AccessTokenCriteria; import criterias.UserCriteria; import models.AccessToken; import models.User; import org.junit.Test; -import services.AccessTokenService; +import play.mvc.Http; import tests.AbstractDatabaseTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + /** * @author resamsel * @version 28 Jan 2017 @@ -20,29 +21,31 @@ public class AccessTokenServiceIntegrationTest extends AbstractDatabaseTest { @Test public void find() { + Http.Request request = mock(Http.Request.class); assertThat(accessTokenService.findBy(new AccessTokenCriteria()).getList()).hasSize(0); User user = createUser("user1", "a@b.c"); - createAccessToken(user); + createAccessToken(user, request); assertThat(userService.findBy(new UserCriteria()).getList()).hasSize(1); } - private AccessToken createAccessToken(User user) { + private AccessToken createAccessToken(User user, Http.Request request) { AccessToken out = new AccessToken(); out.user = user; out.name = "accessToken1"; - accessTokenService.create(out); + accessTokenService.create(out, request); return out; } @Test public void create() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); - AccessToken accessToken = createAccessToken(user); + AccessToken accessToken = createAccessToken(user, request); assertThat(accessToken.name).as("AccessToken.name").isEqualTo("accessToken1"); assertThat(accessToken.key).as("AccessToken.key").isNotNull(); diff --git a/src/it/java/services/KeyServiceIntegrationTest.java b/src/it/java/services/KeyServiceIntegrationTest.java index 391945bf..07415147 100644 --- a/src/it/java/services/KeyServiceIntegrationTest.java +++ b/src/it/java/services/KeyServiceIntegrationTest.java @@ -5,11 +5,13 @@ import models.User; import org.junit.Ignore; import org.junit.Test; +import play.mvc.Http; import tests.AbstractTest; import javax.inject.Inject; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * @author resamsel @@ -24,9 +26,10 @@ public class KeyServiceIntegrationTest extends AbstractTest { @Test @Ignore("FIXME: fails with strange exception") public void create() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); - Project project = projectService.create(new Project().withOwner(user).withName("blubbb")); - Key key = keyService.create(new Key(project, "key.one")); + Project project = projectService.create(new Project().withOwner(user).withName("blubbb"), request); + Key key = keyService.create(new Key(project, "key.one"), request); assertThat(key.name).isEqualTo("key.one"); } diff --git a/src/it/java/services/LocaleServiceIntegrationTest.java b/src/it/java/services/LocaleServiceIntegrationTest.java index 63e873e0..bc0be88a 100644 --- a/src/it/java/services/LocaleServiceIntegrationTest.java +++ b/src/it/java/services/LocaleServiceIntegrationTest.java @@ -5,11 +5,13 @@ import models.User; import org.junit.Ignore; import org.junit.Test; +import play.mvc.Http; import tests.AbstractTest; import javax.inject.Inject; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; /** @@ -26,12 +28,13 @@ public class LocaleServiceIntegrationTest extends AbstractTest { @Ignore("FIXME: fails with strange exception") public void create() { User user = createUser("user1", "user1@resamsel.com"); + Http.Request request = mock(Http.Request.class); - when(authProvider.loggedInUser()).thenReturn(user); - when(authProvider.loggedInUserId()).thenReturn(user.id); + when(authProvider.loggedInUser(request)).thenReturn(user); + when(authProvider.loggedInUserId(request)).thenReturn(user.id); - Project project = projectService.create(new Project().withOwner(user).withName("blubbb")); - Locale locale = localeService.create(new Locale(project, "de")); + Project project = projectService.create(new Project().withOwner(user).withName("blubbb"), request); + Locale locale = localeService.create(new Locale(project, "de"), request); assertThat(locale.name).isEqualTo("de"); } diff --git a/src/it/java/services/MessageServiceIntegrationTest.java b/src/it/java/services/MessageServiceIntegrationTest.java index 1bce0d08..8691cd7d 100644 --- a/src/it/java/services/MessageServiceIntegrationTest.java +++ b/src/it/java/services/MessageServiceIntegrationTest.java @@ -7,11 +7,13 @@ import models.User; import org.junit.Ignore; import org.junit.Test; +import play.mvc.Http; import tests.AbstractTest; import javax.inject.Inject; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; /** * @author resamsel @@ -31,11 +33,12 @@ public class MessageServiceIntegrationTest extends AbstractTest { @Test @Ignore("FIXME: fails with strange exception") public void create() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); - Project project = projectService.create(new Project().withOwner(user).withName("blubbb")); - Key key = keyService.create(new Key(project, "key.one")); - Locale locale = localeService.create(new Locale(project, "de")); - Message message = messageService.create(new Message(locale, key, "Message One")); + Project project = projectService.create(new Project().withOwner(user).withName("blubbb"), request); + Key key = keyService.create(new Key(project, "key.one"), request); + Locale locale = localeService.create(new Locale(project, "de"), request); + Message message = messageService.create(new Message(locale, key, "Message One"), request); assertThat(message.id).isNotNull(); assertThat(message.value).isEqualTo("Message One"); diff --git a/src/it/java/services/ProjectServiceIntegrationTest.java b/src/it/java/services/ProjectServiceIntegrationTest.java index 78d45c0b..0e1cd77d 100644 --- a/src/it/java/services/ProjectServiceIntegrationTest.java +++ b/src/it/java/services/ProjectServiceIntegrationTest.java @@ -1,9 +1,12 @@ package services; +import criterias.GetCriteria; import models.Project; import models.User; import org.junit.Ignore; import org.junit.Test; +import org.mockito.Mockito; +import play.mvc.Http; import tests.AbstractTest; import javax.inject.Inject; @@ -23,11 +26,12 @@ public class ProjectServiceIntegrationTest extends AbstractTest { @Ignore("FIXME: fails with strange exception") public void create() { User user = createUser("user1", "user1@resamsel.com"); - Project project = projectService.create(new Project().withOwner(user).withName("blubbb")); + Http.Request request = Mockito.mock(Http.Request.class); + Project project = projectService.create(new Project().withOwner(user).withName("blubbb"), request); assertThat(project.id).isNotNull(); - project = projectService.byId(project.id, FETCH_MEMBERS); + project = projectService.byId(GetCriteria.from(project.id, null, FETCH_MEMBERS)); assertThat(project).ownerNameIsEqualTo("user1"); assertThat(project).nameIsEqualTo("blubbb"); diff --git a/src/it/java/services/UserServiceIntegrationTest.java b/src/it/java/services/UserServiceIntegrationTest.java index b5cb0a1a..5c3015b7 100644 --- a/src/it/java/services/UserServiceIntegrationTest.java +++ b/src/it/java/services/UserServiceIntegrationTest.java @@ -1,11 +1,14 @@ package services; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import criterias.GetCriteria; import criterias.UserCriteria; import java.util.UUID; import models.User; import org.junit.Test; +import play.mvc.Http; import tests.AbstractDatabaseTest; /** @@ -27,9 +30,11 @@ public void find() { @Test public void findInactive() { + Http.Request request = mock(Http.Request.class); + assertThat(userService.findBy(new UserCriteria()).getList()).hasSize(0); - userService.update(createUser("user1", "user1@resamsel.com").withActive(false)); + userService.update(createUser("user1", "user1@resamsel.com").withActive(false), request); createUser("user2", "user2@resamsel.com"); createUser("user3", "user3@resamsel.com"); @@ -38,23 +43,25 @@ public void findInactive() { @Test public void get() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); - assertThat(userService.byId(user.id).id).isEqualTo(user.id); + assertThat(userService.byId(user.id, request).id).isEqualTo(user.id); } @Test public void getUnknownId() { - assertThat(userService.byId(UUID.randomUUID())).isNull(); + assertThat(userService.byId(GetCriteria.from(UUID.randomUUID(), mock(Http.Request.class)))).isNull(); } @Test public void getInactive() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); - userService.update(user.withActive(false)); + userService.update(user.withActive(false), request); - assertThat(userService.byId(user.id).id).isEqualTo(user.id); + assertThat(userService.byId(user.id, request).id).isEqualTo(user.id); } @Test @@ -66,24 +73,26 @@ public void create() { @Test public void update() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); assertThat(user.email).isEqualTo("user1@resamsel.com"); - userService.update(user.withEmail("a@b.c")); + userService.update(user.withEmail("a@b.c"), request); - assertThat(userService.byId(user.id).email).isEqualTo("a@b.c"); + assertThat(userService.byId(user.id, request).email).isEqualTo("a@b.c"); } @Test public void delete() { + Http.Request request = mock(Http.Request.class); User user = createUser("user1", "user1@resamsel.com"); assertThat(user).isNotNull(); assertThat(user.id).isNotNull(); - userService.delete(user); + userService.delete(user, request); - assertThat(userService.byId(user.id)).isNull(); + assertThat(userService.byId(user.id, request)).isNull(); } } diff --git a/src/test/java/dtos/LocaleTest.java b/src/test/java/dtos/LocaleTest.java index cd082fa0..abf9b868 100644 --- a/src/test/java/dtos/LocaleTest.java +++ b/src/test/java/dtos/LocaleTest.java @@ -1,18 +1,49 @@ package dtos; -import assertions.CustomAssertions; +import dto.Locale; import mappers.LocaleMapper; +import mappers.MessageMapper; +import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import play.mvc.Http; import tests.AbstractLocaleTest; +import utils.FormatUtils; +import static assertions.CustomAssertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) public class LocaleTest extends AbstractLocaleTest { + @Mock + private FormatUtils formatUtils; + @Mock + private MessageMapper messageMapper; + + private LocaleMapper target; + + @Before + public void setUp() { + target = new LocaleMapper(formatUtils, messageMapper); + } @Test public void testDisplayName() { - java.util.Locale.setDefault(java.util.Locale.forLanguageTag("de")); - + // given + Http.Request request = mock(Http.Request.class); models.Locale model = new models.Locale(); model.name = "de"; - CustomAssertions.assertThat(LocaleMapper.toDto(model)).displayNameIsEqualTo("Deutsch"); + + when(formatUtils.formatDisplayName(eq(model), eq(request))).thenReturn("Deutsch"); + + // when + Locale actual = target.toDto(model, request); + + // then + assertThat(actual).displayNameIsEqualTo("Deutsch"); } } diff --git a/src/test/java/repositories/impl/ProjectUserRepositoryImplTest.java b/src/test/java/repositories/impl/ProjectUserRepositoryImplTest.java index 2da40436..a9b4b67a 100644 --- a/src/test/java/repositories/impl/ProjectUserRepositoryImplTest.java +++ b/src/test/java/repositories/impl/ProjectUserRepositoryImplTest.java @@ -66,7 +66,7 @@ public void setUp() { when(pagedListFactoryProvider.get()).thenReturn(pagedListFactory); target = new ProjectUserRepositoryImpl( - persistence, validator, authProvider, activityActor, notificationActor, pagedListFactoryProvider); + persistence, validator, notificationActor, pagedListFactoryProvider); } @Test @@ -77,6 +77,7 @@ public void findByNonExisting() { when(pagedListFactory.createPagedList(any(Query.class))).thenReturn(pagedList); when(pagedList.getList()).thenReturn(Collections.emptyList()); + when(query.setDisableLazyLoading(eq(true))).thenReturn(query); // when PagedList actual = target.findBy(criteria); diff --git a/src/test/java/services/AccessTokenServiceTest.java b/src/test/java/services/AccessTokenServiceTest.java index 67c33846..7d984c87 100644 --- a/src/test/java/services/AccessTokenServiceTest.java +++ b/src/test/java/services/AccessTokenServiceTest.java @@ -1,7 +1,10 @@ package services; +import actors.ActivityActorRef; import criterias.AccessTokenCriteria; +import criterias.GetCriteria; import criterias.PagedListFactory; +import io.ebean.PagedList; import models.AccessToken; import models.User; import org.junit.Before; @@ -9,10 +12,11 @@ import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.AccessTokenRepository; import services.impl.AccessTokenServiceImpl; import services.impl.CacheServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import utils.UserRepositoryMock; import javax.validation.Validator; @@ -34,83 +38,54 @@ public class AccessTokenServiceTest { private CacheService cacheService; private User johnSmith; - @Test - public void testById() { - // mock accessToken - AccessToken accessToken = createAccessToken(ThreadLocalRandom.current().nextLong(), johnSmith, - "de"); - accessTokenRepository.create(accessToken); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("accessToken:id:" + accessToken.id); - assertThat(target.byId(accessToken.id)).nameIsEqualTo("de"); - verify(accessTokenRepository, times(1)).byId(eq(accessToken.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("accessToken:id:" + accessToken.id); - assertThat(target.byId(accessToken.id)).nameIsEqualTo("de"); - verify(accessTokenRepository, times(1)).byId(eq(accessToken.id)); - - // This should trigger cache invalidation - target.update(createAccessToken(accessToken, "de-AT")); - - assertThat(cacheService.keys().keySet()).contains("accessToken:id:" + accessToken.id); - assertThat(target.byId(accessToken.id)).nameIsEqualTo("de-AT"); - verify(accessTokenRepository, times(1)).byId(eq(accessToken.id)); - } - - @Test - public void testFindBy() { - // mock accessToken - AccessToken accessToken = createAccessToken(ThreadLocalRandom.current().nextLong(), johnSmith, - "de"); - accessTokenRepository.create(accessToken); - - // This invocation should feed the cache - AccessTokenCriteria criteria = new AccessTokenCriteria().withUserId(accessToken.user.id); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .nameIsEqualTo("de"); - verify(accessTokenRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .nameIsEqualTo("de"); - verify(accessTokenRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target.update(createAccessToken(accessToken, "de-AT")); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .nameIsEqualTo("de-AT"); - verify(accessTokenRepository, times(2)).findBy(eq(criteria)); - } - @Before public void before() { accessTokenRepository = mock(AccessTokenRepository.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); + cacheService = new NoCacheServiceImpl(); target = new AccessTokenServiceImpl( mock(Validator.class), cacheService, mock(AuthProvider.class), accessTokenRepository, mock(LogEntryService.class), - mock(MetricService.class) + mock(MetricService.class), + mock(ActivityActorRef.class) ); johnSmith = UserRepositoryMock.byUsername("johnsmith"); + } + + @Test + public void byId() { + // given + AccessToken accessToken = createAccessToken(ThreadLocalRandom.current().nextLong(), johnSmith, + "de"); + Http.Request request = mock(Http.Request.class); + + when(accessTokenRepository.byId(any(GetCriteria.class))).thenReturn(accessToken); + + // when + AccessToken actual = target.byId(accessToken.id, request); - when(accessTokenRepository.create(any())).then(this::persist); - when(accessTokenRepository.update(any())).then(this::persist); + // then + assertThat(actual).nameIsEqualTo("de"); } - private AccessToken persist(InvocationOnMock a) { - AccessToken t = a.getArgument(0); - when(accessTokenRepository.byId(eq(t.id), any())).thenReturn(t); - when(accessTokenRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + @Test + public void findBy() { + // given + AccessToken accessToken = createAccessToken(ThreadLocalRandom.current().nextLong(), johnSmith, + "de"); + Http.Request request = mock(Http.Request.class); + AccessTokenCriteria criteria = new AccessTokenCriteria().withUserId(accessToken.user.id); + + when(accessTokenRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(accessToken)); + + // when + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).nameIsEqualTo("de"); } } diff --git a/src/test/java/services/KeyServiceTest.java b/src/test/java/services/KeyServiceTest.java index 4b2cc432..57853e21 100644 --- a/src/test/java/services/KeyServiceTest.java +++ b/src/test/java/services/KeyServiceTest.java @@ -1,26 +1,28 @@ package services; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.KeyCriteria; import criterias.PagedListFactory; +import io.ebean.PagedList; +import mappers.KeyMapper; import models.Key; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import play.libs.Json; +import play.mvc.Http; import repositories.KeyRepository; +import repositories.MessageRepository; import repositories.Persistence; -import services.impl.CacheServiceImpl; import services.impl.KeyServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import utils.ProjectRepositoryMock; import javax.validation.Validator; import java.util.UUID; import static assertions.KeyAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -32,123 +34,70 @@ public class KeyServiceTest { private KeyRepository keyRepository; private KeyService target; - private CacheService cacheService; - @Test - public void testById() { - // mock key - Key key = createKey(UUID.randomUUID(), UUID.randomUUID(), "a"); - keyRepository.create(key); + @Before + public void before() { + keyRepository = mock(KeyRepository.class, + withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); + target = new KeyServiceImpl( + mock(Validator.class), + new NoCacheServiceImpl(), + keyRepository, + mock(LogEntryService.class), + mock(Persistence.class), + mock(AuthProvider.class), + mock(MetricService.class), + mock(ActivityActorRef.class), + mock(KeyMapper.class), + mock(PermissionService.class), + mock(MessageRepository.class) + ); + } - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("key:id:" + key.id); - assertThat(target.byId(key.id)).nameIsEqualTo("a"); - verify(keyRepository, times(1)).byId(eq(key.id)); + @Test + public void byId() { + // given + Key key = createKey(UUID.randomUUID(), UUID.randomUUID(), "ab"); + Http.Request request = mock(Http.Request.class); - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("key:id:" + key.id); - assertThat(target.byId(key.id)).nameIsEqualTo("a"); - verify(keyRepository, times(1)).byId(eq(key.id)); + when(keyRepository.byId(any(GetCriteria.class))).thenReturn(key); - // This should trigger cache invalidation - target.update(createKey(key, "ab")); + // when + Key actual = target.byId(key.id, request); - assertThat(cacheService.keys().keySet()).contains("key:id:" + key.id); - assertThat(target.byId(key.id)).nameIsEqualTo("ab"); - verify(keyRepository, times(1)).byId(eq(key.id)); + // then + assertThat(actual).nameIsEqualTo("ab"); } @Test - public void testFindBy() { - // mock key + public void findBy() { + // given Key key = createKey(UUID.randomUUID(), UUID.randomUUID(), "a"); - keyRepository.create(key); - - // This invocation should feed the cache - KeyCriteria criteria = new KeyCriteria().withProjectId(key.project.id); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .nameIsEqualTo("a"); - verify(keyRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .nameIsEqualTo("a"); - verify(keyRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target.update(createKey(key, "ab")); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .nameIsEqualTo("ab"); - verify(keyRepository, times(2)).findBy(eq(criteria)); - - LOGGER.debug("Cache keys before key creation: {}", cacheService.keys().keySet()); - LOGGER.debug("Project ID: {}", key.project.id); - // This should trigger cache invalidation - target.create(createKey(UUID.randomUUID(), key.project.id, "c")); - LOGGER.debug("Cache keys after key creation: {}", cacheService.keys().keySet()); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated after creation)") - .nameIsEqualTo("c"); - verify(keyRepository, times(3)).findBy(eq(criteria)); - } + Http.Request request = mock(Http.Request.class); + KeyCriteria criteria = KeyCriteria.from(request).withProjectId(key.project.id); - @Test - public void testByProjectAndName() { - // mock key - Key key = createKey(UUID.randomUUID(), - ProjectRepositoryMock.byOwnerAndName("johnsmith", "project1"), "a"); - keyRepository.create(key); + when(keyRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(key)); - // This invocation should feed the cache - assertThat(target.byProjectAndName(key.project, key.name, request)).nameIsEqualTo("a"); - verify(keyRepository, times(1)).byProjectAndName(eq(key.project), eq(key.name)); + // when + PagedList actual = target.findBy(criteria); - // This invocation should use the cache, not the repository - assertThat(target.byProjectAndName(key.project, key.name, request)).nameIsEqualTo("a"); - verify(keyRepository, times(1)).byProjectAndName(eq(key.project), eq(key.name)); - - // This should trigger cache invalidation - key = createKey(key, "ab"); - target.update(key); - - assertThat(cacheService.keys().keySet()) - .doesNotContain("key:project:" + key.project.id + ":name:a"); - assertThat(target.byProjectAndName(key.project, key.name, request)).nameIsEqualTo("ab"); - verify(keyRepository, times(1)).byProjectAndName(eq(key.project), eq(key.name)); + // then + assertThat(actual.getList().get(0)).nameIsEqualTo("a"); } - @Before - public void before() { - keyRepository = mock(KeyRepository.class, - withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); - target = new KeyServiceImpl( - mock(Validator.class), - cacheService, - keyRepository, - mock(LogEntryService.class), - mock(Persistence.class), - mock(AuthProvider.class), - mock(MetricService.class) - ); + @Test + public void byProjectAndName() { + // given + Key key = createKey(UUID.randomUUID(), + ProjectRepositoryMock.byOwnerAndName("johnsmith", "project1"), "a"); + Http.Request request = mock(Http.Request.class); - when(keyRepository.save((Key) any())).then(this::persist); - when(keyRepository.create(any())).then(this::persist); - when(keyRepository.update(any())).then(this::persist); - } + when(keyRepository.byProjectAndName(eq(key.project), eq(key.name))).thenReturn(key); + + // when + Key actual = target.byProjectAndName(key.project, key.name, request); - private Key persist(InvocationOnMock a) { - Key t = a.getArgument(0); - if(t.id == null) - t.id = UUID.randomUUID(); - LOGGER.debug("mock: persisting {} - {}", t.getClass(), Json.toJson(t)); - when(keyRepository.byId(eq(t.id), any())).thenReturn(t); - when(keyRepository.byProjectAndName(eq(t.project), eq(t.name))).thenReturn(t); - when(keyRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + // then + assertThat(actual).nameIsEqualTo("a"); } } diff --git a/src/test/java/services/LinkedAccountServiceTest.java b/src/test/java/services/LinkedAccountServiceTest.java index 74c566b9..cf8ecb55 100644 --- a/src/test/java/services/LinkedAccountServiceTest.java +++ b/src/test/java/services/LinkedAccountServiceTest.java @@ -1,18 +1,20 @@ package services; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.LinkedAccountCriteria; import criterias.PagedListFactory; +import io.ebean.PagedList; import models.LinkedAccount; import models.User; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.LinkedAccountRepository; -import services.impl.CacheServiceImpl; import services.impl.LinkedAccountServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import utils.UserRepositoryMock; import javax.validation.Validator; @@ -20,7 +22,6 @@ import java.util.concurrent.ThreadLocalRandom; import static assertions.LinkedAccountAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -35,87 +36,54 @@ public class LinkedAccountServiceTest { private CacheService cacheService; private User johnSmith; - @Test - public void testById() { - // mock linkedAccount - LinkedAccount linkedAccount = createLinkedAccount(ThreadLocalRandom.current().nextLong(), - johnSmith, "google", UUID.randomUUID().toString()); - linkedAccountRepository.create(linkedAccount); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("linkedAccount:id:" + linkedAccount.id); - assertThat(target.byId(linkedAccount.id)) - .providerKeyIsEqualTo(linkedAccount.providerKey); - verify(linkedAccountRepository, times(1)).byId(eq(linkedAccount.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("linkedAccount:id:" + linkedAccount.id); - assertThat(target.byId(linkedAccount.id)) - .providerKeyIsEqualTo(linkedAccount.providerKey); - verify(linkedAccountRepository, times(1)).byId(eq(linkedAccount.id)); - - // This should trigger cache invalidation - target - .update(createLinkedAccount(linkedAccount, "facebook", UUID.randomUUID().toString())); - - assertThat(cacheService.keys().keySet()).contains("linkedAccount:id:" + linkedAccount.id); - assertThat(target.byId(linkedAccount.id)).providerKeyIsEqualTo("facebook"); - verify(linkedAccountRepository, times(1)).byId(eq(linkedAccount.id)); - } - - @Test - public void testFindBy() { - // mock linkedAccount - LinkedAccount linkedAccount = createLinkedAccount(ThreadLocalRandom.current().nextLong(), - johnSmith, "google", UUID.randomUUID().toString()); - linkedAccountRepository.create(linkedAccount); - - // This invocation should feed the cache - LinkedAccountCriteria criteria = new LinkedAccountCriteria().withUserId(linkedAccount.user.id); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .providerKeyIsEqualTo(linkedAccount.providerKey); - verify(linkedAccountRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .providerKeyIsEqualTo(linkedAccount.providerKey); - verify(linkedAccountRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target - .update(createLinkedAccount(linkedAccount, "facebook", UUID.randomUUID().toString())); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .providerKeyIsEqualTo("facebook"); - verify(linkedAccountRepository, times(2)).findBy(eq(criteria)); - } - @Before public void before() { linkedAccountRepository = mock(LinkedAccountRepository.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); + cacheService = new NoCacheServiceImpl(); target = new LinkedAccountServiceImpl( mock(Validator.class), cacheService, linkedAccountRepository, mock(LogEntryService.class), mock(AuthProvider.class), - mock(MetricService.class) + mock(MetricService.class), + mock(ActivityActorRef.class) ); johnSmith = UserRepositoryMock.byUsername("johnsmith"); + } - when(linkedAccountRepository.create(any())).then(this::persist); - when(linkedAccountRepository.update(any())).then(this::persist); + @Test + public void byId() { + // mock linkedAccount + LinkedAccount linkedAccount = createLinkedAccount(ThreadLocalRandom.current().nextLong(), + johnSmith, "google", UUID.randomUUID().toString()); + Http.Request request = mock(Http.Request.class); + + when(linkedAccountRepository.byId(any(GetCriteria.class))).thenReturn(linkedAccount); + + // when + LinkedAccount actual = target.byId(linkedAccount.id, request); + + // then + assertThat(actual).providerKeyIsEqualTo(linkedAccount.providerKey); } - private LinkedAccount persist(InvocationOnMock a) { - LinkedAccount t = a.getArgument(0); - when(linkedAccountRepository.byId(eq(t.id), any())).thenReturn(t); - when(linkedAccountRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + @Test + public void findBy() { + // given + LinkedAccount linkedAccount = createLinkedAccount(ThreadLocalRandom.current().nextLong(), + johnSmith, "google", UUID.randomUUID().toString()); + Http.Request request = mock(Http.Request.class); + LinkedAccountCriteria criteria = new LinkedAccountCriteria().withUserId(linkedAccount.user.id); + + when(linkedAccountRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(linkedAccount)); + + // when + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).providerKeyIsEqualTo(linkedAccount.providerKey); } } diff --git a/src/test/java/services/LocaleServiceTest.java b/src/test/java/services/LocaleServiceTest.java index 8ed97803..168f21a9 100644 --- a/src/test/java/services/LocaleServiceTest.java +++ b/src/test/java/services/LocaleServiceTest.java @@ -1,18 +1,22 @@ package services; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.LocaleCriteria; import criterias.PagedListFactory; +import io.ebean.PagedList; +import mappers.LocaleMapper; import models.Locale; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.LocaleRepository; +import repositories.MessageRepository; import repositories.Persistence; -import services.impl.CacheServiceImpl; import services.impl.LocaleServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import javax.validation.Validator; import java.util.UUID; @@ -29,82 +33,54 @@ public class LocaleServiceTest { private LocaleRepository localeRepository; private LocaleService target; - private CacheService cacheService; - - @Test - public void testById() { - // mock locale - Locale locale = createLocale(UUID.randomUUID(), UUID.randomUUID(), "de"); - localeRepository.create(locale); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("locale:id:" + locale.id); - assertThat(target.byId(locale.id)).nameIsEqualTo("de"); - verify(localeRepository, times(1)).byId(eq(locale.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("locale:id:" + locale.id); - assertThat(target.byId(locale.id)).nameIsEqualTo("de"); - verify(localeRepository, times(1)).byId(eq(locale.id)); - - // This should trigger cache invalidation - target.update(createLocale(locale, "de-AT")); - - assertThat(cacheService.keys().keySet()).contains("locale:id:" + locale.id); - assertThat(target.byId(locale.id)).nameIsEqualTo("de-AT"); - verify(localeRepository, times(1)).byId(eq(locale.id)); - } - - @Test - public void testFindBy() { - // mock locale - Locale locale = createLocale(UUID.randomUUID(), UUID.randomUUID(), "de"); - localeRepository.create(locale); - - // This invocation should feed the cache - LocaleCriteria criteria = new LocaleCriteria().withProjectId(locale.project.id); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .nameIsEqualTo("de"); - verify(localeRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .nameIsEqualTo("de"); - verify(localeRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target.update(createLocale(locale, "de-AT")); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .nameIsEqualTo("de-AT"); - verify(localeRepository, times(2)).findBy(eq(criteria)); - } @Before public void before() { localeRepository = mock(LocaleRepository.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); target = new LocaleServiceImpl( mock(Validator.class), - cacheService, + new NoCacheServiceImpl(), localeRepository, mock(LogEntryService.class), mock(Persistence.class), mock(AuthProvider.class), - mock(MetricService.class) + mock(MetricService.class), + mock(ActivityActorRef.class), + mock(LocaleMapper.class), + mock(PermissionService.class), + mock(MessageRepository.class) ); + } + + @Test + public void byId() { + // given + Locale locale = createLocale(UUID.randomUUID(), UUID.randomUUID(), "de"); + Http.Request request = mock(Http.Request.class); + + when(localeRepository.byId(any(GetCriteria.class))).thenReturn(locale); - when(localeRepository.create(any())).then(this::persist); - when(localeRepository.update(any())).then(this::persist); + // when + Locale actual = target.byId(locale.id, request); + + // then + assertThat(actual).nameIsEqualTo("de"); } - private Locale persist(InvocationOnMock a) { - Locale t = a.getArgument(0); - when(localeRepository.byId(eq(t.id), any())).thenReturn(t); - when(localeRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + @Test + public void findBy() { + // given + Locale locale = createLocale(UUID.randomUUID(), UUID.randomUUID(), "de"); + Http.Request request = mock(Http.Request.class); + LocaleCriteria criteria = LocaleCriteria.from(request).withProjectId(locale.project.id); + + when(localeRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(locale)); + + // then + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).nameIsEqualTo("de"); } } diff --git a/src/test/java/services/LogEntryServiceTest.java b/src/test/java/services/LogEntryServiceTest.java index 9d556e6a..e6db88b7 100644 --- a/src/test/java/services/LogEntryServiceTest.java +++ b/src/test/java/services/LogEntryServiceTest.java @@ -1,21 +1,23 @@ package services; -import io.ebean.Ebean; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.LogEntryCriteria; import criterias.PagedListFactory; +import io.ebean.Ebean; +import io.ebean.PagedList; import models.LogEntry; import models.Project; import models.User; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.LogEntryRepository; import repositories.Persistence; -import services.impl.CacheServiceImpl; import services.impl.LogEntryServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import utils.ProjectRepositoryMock; import utils.UserRepositoryMock; @@ -23,7 +25,6 @@ import java.util.UUID; import static assertions.LogEntryAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; @@ -34,91 +35,63 @@ public class LogEntryServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(LogEntryServiceTest.class); private LogEntryRepository logEntryRepository; - private LogEntryService logEntryService; + private LogEntryService target; private CacheService cacheService; private Persistence persistence; private User johnSmith; private User janeDoe; private Project project1; - @Test - public void testById() { - // mock logEntry - LogEntry logEntry = createLogEntry(UUID.randomUUID(), johnSmith, project1); - logEntryRepository.create(logEntry); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("activity:id:" + logEntry.id); - assertThat(logEntryService.byId(logEntry.id)).userIsEqualTo(johnSmith); - verify(logEntryRepository, times(1)).byId(eq(logEntry.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("activity:id:" + logEntry.id); - assertThat(logEntryService.byId(logEntry.id)).userIsEqualTo(johnSmith); - verify(logEntryRepository, times(1)).byId(eq(logEntry.id)); - - // This should trigger cache invalidation - logEntryService.update(createLogEntry(logEntry.id, janeDoe, project1)); - - assertThat(cacheService.keys().keySet()).contains("activity:id:" + logEntry.id); - assertThat(logEntryService.byId(logEntry.id)).userIsEqualTo(janeDoe); - verify(logEntryRepository, times(1)).byId(eq(logEntry.id)); - } - - @Test - public void testFindBy() { - // mock logEntry - LogEntry logEntry = createLogEntry(UUID.randomUUID(), johnSmith, project1); - logEntryRepository.create(logEntry); - - // This invocation should feed the cache - LogEntryCriteria criteria = new LogEntryCriteria().withUserId(logEntry.user.id); - assertThat(logEntryService.findBy(criteria).getList().get(0)) - .as("uncached") - .userIsEqualTo(johnSmith); - verify(logEntryRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(logEntryService.findBy(criteria).getList().get(0)) - .as("cached") - .userIsEqualTo(johnSmith); - verify(logEntryRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - logEntryService.update(createLogEntry(logEntry.id, janeDoe, project1)); - - assertThat(logEntryService.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .userIsEqualTo(janeDoe); - verify(logEntryRepository, times(2)).findBy(eq(criteria)); - } - @Before public void before() { logEntryRepository = mock(LogEntryRepository.class, - withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); + withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); + cacheService = new NoCacheServiceImpl(); persistence = mock(Persistence.class); - logEntryService = new LogEntryServiceImpl( - mock(Validator.class), - cacheService, - logEntryRepository, - persistence, - mock(AuthProvider.class) + target = new LogEntryServiceImpl( + mock(Validator.class), + cacheService, + logEntryRepository, + persistence, + mock(AuthProvider.class), + mock(ActivityActorRef.class) ); johnSmith = UserRepositoryMock.byUsername("johnsmith"); janeDoe = UserRepositoryMock.byUsername("janedoe"); project1 = ProjectRepositoryMock.byOwnerAndName(johnSmith.username, "project1"); - when(logEntryRepository.create(any())).then(this::persist); - when(logEntryRepository.update(any())).then(this::persist); when(persistence.createQuery(any())).then(invocation -> Ebean.createQuery(invocation.getArgument(0))); } - private LogEntry persist(InvocationOnMock a) { - LogEntry t = a.getArgument(0); - when(logEntryRepository.byId(eq(t.id), any())).thenReturn(t); - when(logEntryRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + @Test + public void byId() { + // given + LogEntry logEntry = createLogEntry(UUID.randomUUID(), johnSmith, project1); + Http.Request request = mock(Http.Request.class); + + when(logEntryRepository.byId(any(GetCriteria.class))).thenReturn(logEntry); + + // when + LogEntry actual = target.byId(logEntry.id, request); + + // then + assertThat(actual).userIsEqualTo(johnSmith); + } + + @Test + public void findBy() { + // given + LogEntry logEntry = createLogEntry(UUID.randomUUID(), johnSmith, project1); + Http.Request request = mock(Http.Request.class); + LogEntryCriteria criteria = LogEntryCriteria.from(request).withUserId(logEntry.user.id); + + when(logEntryRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(logEntry)); + + // when + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).userIsEqualTo(johnSmith); } } diff --git a/src/test/java/services/MessageServiceTest.java b/src/test/java/services/MessageServiceTest.java index 8d4177b3..5c3ff5d9 100644 --- a/src/test/java/services/MessageServiceTest.java +++ b/src/test/java/services/MessageServiceTest.java @@ -1,24 +1,27 @@ package services; +import actors.ActivityActorRef; +import actors.MessageWordCountActorRef; +import criterias.GetCriteria; import criterias.MessageCriteria; import criterias.PagedListFactory; +import io.ebean.PagedList; +import mappers.MessageMapper; import models.Message; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.MessageRepository; -import services.impl.CacheServiceImpl; +import repositories.Persistence; import services.impl.MessageServiceImpl; -import utils.CacheApiMock; +import services.impl.NoCacheServiceImpl; import javax.validation.Validator; import java.util.UUID; import static assertions.MessageAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static utils.MessageRepositoryMock.createMessage; @@ -29,81 +32,53 @@ public class MessageServiceTest { private MessageRepository messageRepository; private MessageService target; - private CacheService cacheService; - - @Test - public void testById() { - // mock message - Message message = createMessage(UUID.randomUUID(), UUID.randomUUID(), "value"); - messageRepository.create(message); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("message:id:" + message.id); - assertThat(target.byId(message.id)).valueIsEqualTo("value"); - verify(messageRepository, times(1)).byId(eq(message.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("message:id:" + message.id); - assertThat(target.byId(message.id)).valueIsEqualTo("value"); - verify(messageRepository, times(1)).byId(eq(message.id)); - - // This should trigger cache invalidation - target.update(createMessage(message, "value2")); - - assertThat(cacheService.keys().keySet()).contains("message:id:" + message.id); - assertThat(target.byId(message.id)).valueIsEqualTo("value2"); - verify(messageRepository, times(1)).byId(eq(message.id)); - } - - @Test - public void testFindBy() { - // mock message - Message message = createMessage(UUID.randomUUID(), UUID.randomUUID(), "value"); - messageRepository.create(message); - - // This invocation should feed the cache - MessageCriteria criteria = new MessageCriteria().withProjectId(message.key.project.id); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .valueIsEqualTo("value"); - verify(messageRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .valueIsEqualTo("value"); - verify(messageRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target.update(createMessage(message, "value3")); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .valueIsEqualTo("value3"); - verify(messageRepository, times(2)).findBy(eq(criteria)); - } @Before public void before() { messageRepository = mock(MessageRepository.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); target = new MessageServiceImpl( mock(Validator.class), - cacheService, + new NoCacheServiceImpl(), messageRepository, mock(LogEntryService.class), mock(AuthProvider.class), - mock(MetricService.class) + mock(MetricService.class), + mock(ActivityActorRef.class), + mock(MessageWordCountActorRef.class), + mock(Persistence.class), + mock(MessageMapper.class) ); + } + + @Test + public void byId() { + // given + Message message = createMessage(UUID.randomUUID(), UUID.randomUUID(), "value"); + Http.Request request = mock(Http.Request.class); + + when(messageRepository.byId(any(GetCriteria.class))).thenReturn(message); - when(messageRepository.create(any())).then(this::persist); - when(messageRepository.update(any())).then(this::persist); + // when + Message actual = target.byId(message.id, request); + + // then + assertThat(actual).valueIsEqualTo("value"); } - private Message persist(InvocationOnMock a) { - Message t = a.getArgument(0); - when(messageRepository.byId(eq(t.id), any())).thenReturn(t); - when(messageRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + @Test + public void findBy() { + // given + Message message = createMessage(UUID.randomUUID(), UUID.randomUUID(), "value"); + Http.Request request = mock(Http.Request.class); + MessageCriteria criteria = MessageCriteria.from(request).withProjectId(message.key.project.id); + + when(messageRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(message)); + + // when + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).valueIsEqualTo("value"); } } diff --git a/src/test/java/services/ProjectServiceTest.java b/src/test/java/services/ProjectServiceTest.java index 7bf4d377..ccc2ac2c 100644 --- a/src/test/java/services/ProjectServiceTest.java +++ b/src/test/java/services/ProjectServiceTest.java @@ -1,29 +1,34 @@ package services; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.PagedListFactory; import criterias.ProjectCriteria; +import io.ebean.PagedList; +import mappers.ProjectMapper; import models.Project; import models.User; import org.hibernate.validator.internal.engine.ConstraintViolationImpl; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.MessageRepository; import repositories.ProjectRepository; import services.impl.CacheServiceImpl; +import services.impl.NoCacheServiceImpl; import services.impl.ProjectServiceImpl; -import utils.CacheApiMock; import utils.UserRepositoryMock; import validators.ProjectNameUniqueChecker; -import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; import javax.validation.Validator; import java.util.Collections; import java.util.HashSet; -import java.util.Set; import java.util.UUID; import static assertions.CustomAssertions.assertThat; @@ -33,228 +38,144 @@ import static org.mockito.Mockito.*; import static utils.ProjectRepositoryMock.createProject; +@RunWith(MockitoJUnitRunner.class) public class ProjectServiceTest { private static final Logger LOGGER = LoggerFactory.getLogger(ProjectServiceTest.class); + @Mock private ProjectRepository projectRepository; + @Mock + private Validator validator; + @Mock + private ProjectMapper projectMapper; + private ProjectService target; - private CacheService cacheService; + private User johnSmith; private User janeDoe; - private Validator validator; + + @Before + public void before() { + projectRepository = mock( + ProjectRepository.class, + withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation())) + ); + target = new ProjectServiceImpl( + validator, + new NoCacheServiceImpl(), + projectRepository, + mock(LocaleService.class), + mock(KeyService.class), + mock(MessageService.class), + mock(MessageRepository.class), + mock(ProjectUserService.class), + mock(LogEntryService.class), + mock(AuthProvider.class), + mock(MetricService.class), + mock(ActivityActorRef.class), + projectMapper, + mock(PermissionService.class) + ); + + johnSmith = UserRepositoryMock.byUsername("johnsmith"); + janeDoe = UserRepositoryMock.byUsername("janedoe"); + } @Test - public void testById() { - // mock project + public void byId() { + // given Project project = createProject(UUID.randomUUID(), "name", "johnsmith"); - projectRepository.create(project); + Http.Request request = mock(Http.Request.class); - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("project:id:" + project.id); - assertThat(target.byId(project.id)).nameIsEqualTo("name"); - verify(projectRepository, times(1)).byId(eq(project.id)); + when(projectRepository.byId(any(GetCriteria.class))).thenReturn(project); - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("project:id:" + project.id); - assertThat(target.byId(project.id)).nameIsEqualTo("name"); - verify(projectRepository, times(1)).byId(eq(project.id)); + // when + Project actual = target.byId(project.id, request); - // This should trigger cache invalidation - project = createProject(project, "name2"); - target.update(project); - - assertThat(cacheService.keys().keySet()).contains("project:id:" + project.id); - assertThat(target.byId(project.id)).nameIsEqualTo("name2"); - verify(projectRepository, times(1)).byId(eq(project.id)); + // then + assertThat(actual).nameIsEqualTo("name"); } @Test - public void testFindBy() { - // mock project + public void findBy() { + // given Project project = createProject(UUID.randomUUID(), "name", johnSmith.username); - projectRepository.create(project); - - // This invocation should feed the cache + Http.Request request = mock(Http.Request.class); ProjectCriteria criteria = new ProjectCriteria().withSearch("name"); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .nameIsEqualTo("name"); - verify(projectRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .nameIsEqualTo("name"); - verify(projectRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - project = createProject(project, "name3"); - target.update(project); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .nameIsEqualTo("name3"); - verify(projectRepository, times(2)).findBy(eq(criteria)); + + when(projectRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(project)); + + // when + PagedList actual = target.findBy(criteria); + + // then + assertThat(actual.getList().get(0)).nameIsEqualTo("name"); } @Test - public void testByOwnerAndName() { - // mock project + public void byOwnerAndName() { + // given Project project = createProject(UUID.randomUUID(), "name", johnSmith.username); - projectRepository.create(project); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()) - .doesNotContain("project:owner:" + project.owner.username + ":" + project.name); - assertThat(target.byOwnerAndName(project.owner.username, project.name)) - .nameIsEqualTo("name"); - verify(projectRepository, times(1)) - .byOwnerAndName(eq(project.owner.username), eq(project.name)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()) - .contains("project:owner:" + project.owner.username + ":" + project.name); - assertThat(target.byOwnerAndName(project.owner.username, project.name)) - .nameIsEqualTo("name"); - verify(projectRepository, times(1)) - .byOwnerAndName(eq(project.owner.username), eq(project.name)); - - // This should trigger cache invalidation - project = createProject(project, "name2"); - target.update(project); - - assertThat(cacheService.keys().keySet()) - .doesNotContain("project:owner:" + project.owner.username + ":" + project.name); - assertThat(target.byOwnerAndName(project.owner.username, project.name)) - .nameIsEqualTo("name2"); - verify(projectRepository, times(1)) - .byOwnerAndName(eq(project.owner.username), eq(project.name)); + Http.Request request = mock(Http.Request.class); + + when(projectRepository.byOwnerAndName(johnSmith.username, project.name, null)).thenReturn(project); + + // when + Project actual = target.byOwnerAndName(project.owner.username, project.name, request); + + // then + assertThat(actual).nameIsEqualTo("name"); } @Test - public void testIncreaseWordCountBy() { - // mock project + public void increaseWordCountBy() { + // given Project project = createProject(UUID.randomUUID(), "name", johnSmith.username); - projectRepository.create(project); + Http.Request request = mock(Http.Request.class); - assertThat(target.byId(project.id)).wordCountIsNull(); + when(projectRepository.byId(any(GetCriteria.class))).thenReturn(project); - // This should trigger cache invalidation - target.increaseWordCountBy(project.id, 1); + // when + Project actual = target.increaseWordCountBy(project.id, 1, request); - assertThat(target.byId(project.id)).wordCountIsEqualTo(1); - verify(projectRepository, times(1)).byId(eq(project.id)); + // then + assertThat(actual).nameIsEqualTo("name").wordCountIsEqualTo(1); } @Test - public void testResetWordCount() { - // mock project + public void resetWordCount() { + // given Project project = createProject(UUID.randomUUID(), "name", johnSmith.username); project.wordCount = 100; - projectRepository.create(project); + Http.Request request = mock(Http.Request.class); - assertThat(target.byId(project.id)).wordCountIsEqualTo(100); + when(projectRepository.byId(any(GetCriteria.class))).thenReturn(project); - // This should trigger cache invalidation - target.resetWordCount(project.id); + // when + Project actual = target.resetWordCount(project.id, request); - assertThat(target.byId(project.id)).wordCountIsNull(); - verify(projectRepository, times(1)).byId(eq(project.id)); + // then + assertThat(actual).wordCountIsNull(); } @Test - public void testChangeOwner() { - // mock projects + public void changeOwner() { + // given Project projectJohn = createProject(UUID.randomUUID(), "name", johnSmith.username); - projectRepository.create(projectJohn); Project projectJane = createProject(UUID.randomUUID(), "name", janeDoe.username); - projectRepository.create(projectJane); - - assertThat(target.byId(projectJohn.id)) - .ownerIsEqualTo(johnSmith) - .nameIsEqualTo(projectJohn.name); - assertThat(target.byId(projectJane.id)) - .ownerIsEqualTo(janeDoe) - .nameIsEqualTo(projectJane.name); - - when(validator.validate(any())).thenAnswer(a -> { - Project p = a.getArgument(0); - - if (!new ProjectNameUniqueChecker(target).isValid(p)) { - return new HashSet<>(Collections.singletonList( - ConstraintViolationImpl.forBeanValidation( - "", - Collections.emptyMap(), - Collections.emptyMap(), - "", - Project.class, - p, - null, - null, - null, - null, - null, - null) - )); - } - - return new HashSet<>(); - }); - - try { - target.changeOwner(projectJohn, janeDoe); - failBecauseExceptionWasNotThrown(ConstraintViolationException.class); - } catch (Exception e) { - assertThat(e).isInstanceOf(ConstraintViolationException.class); - } - - // This should trigger cache invalidation - target.changeOwner(projectJohn.withName("name2"), janeDoe); - - assertThat(target.byId(projectJohn.id)).ownerIsEqualTo(janeDoe).nameIsEqualTo("name2"); - verify(projectRepository, times(1)).byId(eq(projectJohn.id)); - } - - - @Before - public void before() { - validator = mock(Validator.class); - projectRepository = mock( - ProjectRepository.class, - withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation())) - ); - cacheService = new CacheServiceImpl(new CacheApiMock()); - target = new ProjectServiceImpl( - validator, - cacheService, - projectRepository, - mock(LocaleService.class), - mock(KeyService.class), - mock(MessageService.class), - mock(MessageRepository.class), - mock(ProjectUserService.class), - mock(LogEntryService.class), - mock(AuthProvider.class), - mock(MetricService.class) - ); - johnSmith = UserRepositoryMock.byUsername("johnsmith"); - janeDoe = UserRepositoryMock.byUsername("janedoe"); - - when(projectRepository.create(any())).then(this::persist); - when(projectRepository.update(any())).then(this::persist); - } + dto.Project projectJohnDto = new dto.Project(); + projectJohnDto.name = projectJohn.name; + Http.Request request = mock(Http.Request.class); - private Project persist(InvocationOnMock a) { - Project p = a.getArgument(0); + when(projectRepository.byId(any(GetCriteria.class))).thenReturn(projectJohn); + when(projectMapper.toDto(eq(projectJohn), eq(request))).thenReturn(projectJohnDto); + when(projectRepository.update(eq(projectJohn))).thenReturn(projectJohn); - Set> violations = validator.validate(p); - if (violations != null && !violations.isEmpty()) { - throw new ConstraintViolationException("Violations found", violations); - } + // when + Project actual = target.changeOwner(projectJohn, janeDoe, request); - when(projectRepository.byId(eq(p.id), any())).thenReturn(p); - when(projectRepository.byOwnerAndName(eq(p.owner.username), eq(p.name))).thenReturn(p); - when(projectRepository.findBy(any())).thenReturn(PagedListFactory.create(p)); - return p; + // then + assertThat(actual).ownerNameIsEqualTo(janeDoe.name); } } diff --git a/src/test/java/services/UserServiceTest.java b/src/test/java/services/UserServiceTest.java index b6a28a34..c4c24ed5 100644 --- a/src/test/java/services/UserServiceTest.java +++ b/src/test/java/services/UserServiceTest.java @@ -1,26 +1,26 @@ package services; +import actors.ActivityActorRef; +import criterias.GetCriteria; import criterias.PagedListFactory; import criterias.UserCriteria; +import io.ebean.PagedList; import models.User; import org.junit.Before; import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import play.mvc.Http; import repositories.UserRepository; -import services.impl.CacheServiceImpl; +import services.impl.NoCacheServiceImpl; import services.impl.UserServiceImpl; -import utils.CacheApiMock; import javax.validation.Validator; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; import static assertions.UserAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; import static utils.UserRepositoryMock.createUser; @@ -30,93 +30,17 @@ public class UserServiceTest { private UserRepository userRepository; private UserService target; - private CacheService cacheService; - private LinkedAccountService linkedAccountService; - private AccessTokenService accessTokenService; - private LogEntryService logEntryService; - private ProjectService projectService; - private ProjectUserService projecUserService; - - @Test - public void testById() { - // mock user - User user = createUser(UUID.randomUUID(), "a", "b", "a@b.com"); - userRepository.create(user); - - // This invocation should feed the cache - assertThat(cacheService.keys().keySet()).doesNotContain("user:id:" + user.id); - assertThat(target.byId(user.id)).nameIsEqualTo("a"); - verify(userRepository, times(1)).byId(eq(user.id)); - - // This invocation should use the cache, not the repository - assertThat(cacheService.keys().keySet()).contains("user:id:" + user.id); - assertThat(target.byId(user.id)).nameIsEqualTo("a"); - verify(userRepository, times(1)).byId(eq(user.id)); - - // This should trigger cache invalidation - target.update(createUser(user, "ab", "b", "a@b.com")); - - assertThat(cacheService.keys().keySet()).contains("user:id:" + user.id); - assertThat(target.byId(user.id)).nameIsEqualTo("ab"); - verify(userRepository, times(1)).byId(eq(user.id)); - } - - @Test - public void testFindBy() { - // mock user - User user = createUser(UUID.randomUUID(), "a", "b", "a@b.com"); - userRepository.create(user); - - // This invocation should feed the cache - UserCriteria criteria = new UserCriteria().withSearch(user.name); - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached") - .nameIsEqualTo("a"); - verify(userRepository, times(1)).findBy(eq(criteria)); - // This invocation should use the cache, not the repository - assertThat(target.findBy(criteria).getList().get(0)) - .as("cached") - .nameIsEqualTo("a"); - verify(userRepository, times(1)).findBy(eq(criteria)); - - // This should trigger cache invalidation - target.update(createUser(user, "ab", "b", "a@b.com")); - - assertThat(target.findBy(criteria).getList().get(0)) - .as("uncached (invalidated)") - .nameIsEqualTo("ab"); - verify(userRepository, times(2)).findBy(eq(criteria)); - } - - @Test - public void testMerge() { - User user = createUser(UUID.randomUUID(), "a", "a", "a@a.com"); - User otherUser = createUser(UUID.randomUUID(), "b", "b", "b@b.com"); - userRepository.create(user); - userRepository.create(otherUser); - - target.merge(user, otherUser, request); - - assertThat(userRepository.byId(user.id)).nameIsEqualTo("a").activeIsTrue(); - assertThat(userRepository.byId(otherUser.id)).nameIsEqualTo("b").activeIsFalse(); - - verify(linkedAccountService, times(1)).save(anyList()); - verify(accessTokenService, times(1)).save(anyList()); - verify(logEntryService, times(1)).save(anyList()); - verify(projectService, times(1)).save(anyList()); - verify(projecUserService, times(1)).save(anyList()); - } @Before public void before() { userRepository = mock(UserRepository.class, withSettings().invocationListeners(i -> LOGGER.debug("{}", i.getInvocation()))); - cacheService = new CacheServiceImpl(new CacheApiMock()); - linkedAccountService = mock(LinkedAccountService.class); - accessTokenService = mock(AccessTokenService.class); - logEntryService = mock(LogEntryService.class); - projectService = mock(ProjectService.class); - projecUserService = mock(ProjectUserService.class); + CacheService cacheService = new NoCacheServiceImpl(); + LinkedAccountService linkedAccountService = mock(LinkedAccountService.class); + AccessTokenService accessTokenService = mock(AccessTokenService.class); + LogEntryService logEntryService = mock(LogEntryService.class); + ProjectService projectService = mock(ProjectService.class); + ProjectUserService projecUserService = mock(ProjectUserService.class); target = new UserServiceImpl( mock(Validator.class), cacheService, @@ -127,13 +51,10 @@ public void before() { projecUserService, logEntryService, mock(AuthProvider.class), - mock(MetricService.class) + mock(MetricService.class), + mock(ActivityActorRef.class) ); - when(userRepository.create(any())).then(this::persist); - when(userRepository.update(any())).then(this::persist); - when(userRepository.save((User) any())).then(this::persist); - when(userRepository.save(anyList())).then(this::persistList); when(linkedAccountService.findBy(any())).thenReturn(PagedListFactory.create()); when(accessTokenService.findBy(any())).thenReturn(PagedListFactory.create()); when(projectService.findBy(any())).thenReturn(PagedListFactory.create()); @@ -141,29 +62,49 @@ public void before() { when(logEntryService.findBy(any())).thenReturn(PagedListFactory.create()); } - private User updateMocks(User t) { - LOGGER.debug("updateMocks({})", t); + @Test + public void byId() { + // given + User user = createUser(UUID.randomUUID(), "a", "b", "a@b.com"); + Http.Request request = mock(Http.Request.class); - if (t.id == null) { - t.id = UUID.randomUUID(); - } - t.linkedAccounts = new ArrayList<>(t.linkedAccounts); + when(userRepository.byId(any(GetCriteria.class))).thenReturn(user); - when(userRepository.byId(eq(t.id), any())).thenReturn(t); - when(userRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); + // when + User actual = target.byId(user.id, request); - return t; + // then + assertThat(actual).nameIsEqualTo("a"); } - private User persist(InvocationOnMock a) { - return updateMocks(a.getArgument(0)); - } + @Test + public void findBy() { + // given + User user = createUser(UUID.randomUUID(), "a", "b", "a@b.com"); + userRepository.create(user); + Http.Request request = mock(Http.Request.class); + UserCriteria criteria = UserCriteria.from(request).withSearch(user.name); + + when(userRepository.findBy(eq(criteria))).thenReturn(PagedListFactory.create(user)); + + // when + PagedList actual = target.findBy(criteria); - private List persistList(InvocationOnMock a) { - List t = a.getArgument(0); - t.forEach(this::updateMocks); - when(userRepository.findBy(any())).thenReturn(PagedListFactory.create(t)); - return t; + // then + assertThat(actual.getList().get(0)).nameIsEqualTo("a"); } + @Test + public void testMerge() { + // given + Http.Request request = mock(Http.Request.class); + User user = createUser(UUID.randomUUID(), "a", "a", "a@a.com"); + User otherUser = createUser(UUID.randomUUID(), "b", "b", "b@b.com"); + + // when + User actual = target.merge(user, otherUser, request); + + // then + assertThat(actual).nameIsEqualTo("a").activeIsTrue(); + } } diff --git a/src/test/java/services/impl/ProjectUserServiceImplTest.java b/src/test/java/services/impl/ProjectUserServiceImplTest.java index 8e966a24..6bcbd890 100644 --- a/src/test/java/services/impl/ProjectUserServiceImplTest.java +++ b/src/test/java/services/impl/ProjectUserServiceImplTest.java @@ -1,5 +1,6 @@ package services.impl; +import actors.ActivityActorRef; import models.Project; import models.ProjectRole; import models.ProjectUser; @@ -10,16 +11,20 @@ import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import play.mvc.Http; import repositories.ProjectUserRepository; import services.AuthProvider; import services.CacheService; import services.LogEntryService; import services.MetricService; +import validators.ProjectUserModifyAllowedValidator; import javax.validation.Validator; import java.util.UUID; import static assertions.CustomAssertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ProjectUserServiceImplTest { @@ -36,13 +41,18 @@ public class ProjectUserServiceImplTest { private AuthProvider authProvider; @Mock private MetricService metricService; + @Mock + private ActivityActorRef activityActor; + @Mock + private ProjectUserModifyAllowedValidator projectUserModifyAllowedValidator; private ProjectUserServiceImpl target; @Before public void setUp() { target = new ProjectUserServiceImpl( - validator, cache, projectUserRepository, logEntryService, authProvider, metricService); + validator, cache, projectUserRepository, logEntryService, authProvider, metricService, activityActor, + projectUserModifyAllowedValidator); } @Test @@ -53,8 +63,11 @@ public void createAsAdmin() { .withProject(new Project().withId(UUID.randomUUID())) .withRole(ProjectRole.Owner); Long id = 1L; + Http.Request request = Mockito.mock(Http.Request.class); + + when(projectUserModifyAllowedValidator.isValid(eq(model), eq(request))).thenReturn(true); - Mockito.when(projectUserRepository.save(model)) + when(projectUserRepository.create(model)) .thenReturn(new ProjectUser() .withId(id) .withUser(model.user) @@ -62,7 +75,7 @@ public void createAsAdmin() { .withRole(model.role)); // when - ProjectUser actual = target.create(model); + ProjectUser actual = target.create(model, request); // then assertThat(actual) diff --git a/src/test/java/tests/AbstractTest.java b/src/test/java/tests/AbstractTest.java index 3fd80af6..fe062f80 100644 --- a/src/test/java/tests/AbstractTest.java +++ b/src/test/java/tests/AbstractTest.java @@ -28,7 +28,7 @@ public class AbstractTest extends WithApplication { protected AuthProvider authProvider; protected User createUser(String name, String email) { - return userService.create(new User().withName(name).withEmail(email).withActive(true)); + return userService.create(new User().withName(name).withEmail(email).withActive(true), null); } private GuiceApplicationBuilder builder() { diff --git a/src/test/java/utils/AccessTokenRepositoryMock.java b/src/test/java/utils/AccessTokenRepositoryMock.java index 2e75970e..49fb2b1e 100644 --- a/src/test/java/utils/AccessTokenRepositoryMock.java +++ b/src/test/java/utils/AccessTokenRepositoryMock.java @@ -1,11 +1,12 @@ package utils; -import java.util.Arrays; -import java.util.stream.Collectors; import models.AccessToken; import models.Scope; import models.User; -import repositories.impl.AccessTokenRepositoryImpl; +import services.impl.AccessTokenServiceImpl; + +import java.util.Arrays; +import java.util.stream.Collectors; public class AccessTokenRepositoryMock { @@ -19,7 +20,7 @@ public static AccessToken createAccessToken(Long id, User user, String name) { t.id = id; t.name = name; t.user = user; - t.key = AccessTokenRepositoryImpl.generateKey(AccessToken.KEY_LENGTH); + t.key = AccessTokenServiceImpl.generateKey(AccessToken.KEY_LENGTH); t.scope = Arrays.stream(Scope.values()).map(Scope::scope).collect(Collectors.joining(",")); return t; diff --git a/src/test/java/utils/CacheApiMock.java b/src/test/java/utils/CacheApiMock.java deleted file mode 100644 index cc9969be..00000000 --- a/src/test/java/utils/CacheApiMock.java +++ /dev/null @@ -1,76 +0,0 @@ -package utils; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import play.cache.SyncCacheApi; - -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.Callable; - -public class CacheApiMock implements SyncCacheApi { - - private static final Logger LOGGER = LoggerFactory.getLogger(CacheApiMock.class); - - private final Map map = new HashMap<>(); - - @Override - public T get(String key) { - LOGGER.debug("get(key={})", key); - - T value = (T) map.get(key); - - LOGGER.debug("returning {}", value); - - return value; - } - - @Override - public Optional getOptional(String key) { - return Optional.ofNullable(get(key)); - } - - @Override - public T getOrElseUpdate(String key, Callable block, int expiration) { - LOGGER.debug("getOrElseUpdate(key={}, block=..., expiration={})", key, expiration); - - if (!map.containsKey(key)) { - try { - map.put(key, block.call()); - } catch (Exception e) { - // do nothing - LOGGER.warn("Error while calling block", e); - } - } - return get(key); - } - - @Override - public T getOrElseUpdate(String key, Callable block) { - LOGGER.debug("getOrElseUpdate(key={}, block=...)", key); - - return getOrElseUpdate(key, block, -1); - } - - @Override - public void set(String key, Object value, int expiration) { - LOGGER.debug("set(key={}, value={}, expiration={})", key, value, expiration); - - map.put(key, value); - } - - @Override - public void set(String key, Object value) { - LOGGER.debug("set(key={}, value={})", key, value); - - set(key, value, -1); - } - - @Override - public void remove(String key) { - LOGGER.debug("remove(key={})", key); - - map.remove(key); - } -} diff --git a/src/test/java/utils/FormatUtilsTest.java b/src/test/java/utils/FormatUtilsTest.java index 43f29802..584cbb60 100644 --- a/src/test/java/utils/FormatUtilsTest.java +++ b/src/test/java/utils/FormatUtilsTest.java @@ -3,47 +3,123 @@ import models.Locale; import org.junit.Before; import org.junit.Test; -import org.mockito.Mockito; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import play.i18n.Lang; +import play.i18n.Messages; import play.i18n.MessagesApi; import play.mvc.Http; import tests.AbstractLocaleTest; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +@RunWith(MockitoJUnitRunner.class) public class FormatUtilsTest extends AbstractLocaleTest { - private FormatUtils formatUtils; + @Mock private MessagesApi messagesApi; + @Mock + private Messages messages; + @Mock + private Lang lang; + @Mock + private Http.Request request; + + private FormatUtils target; @Before public void setUp() { - messagesApi = Mockito.mock(MessagesApi.class); - formatUtils = new FormatUtils(messagesApi); + target = new FormatUtils(messagesApi); + + when(messagesApi.preferred(eq(request))).thenReturn(messages); + when(messages.lang()).thenReturn(lang); + } + + @Test + public void formatDisplayNamesWithEnglishLocaleAndNameNull() { + // given, when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(actual).isNull(); + } + + @Test + public void formatDisplayNamesWithEnglishLocaleAndNameEmpty() { + // given, when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(target.formatDisplayName(createLocale(""), request)).isNull(); + } + + @Test + public void formatDisplayNamesWithEnglishLocaleAndNameDe() { + // given + when(lang.locale()).thenReturn(java.util.Locale.forLanguageTag("en")); + + // when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(target.formatDisplayName(createLocale("en"), request)).isEqualTo("English"); } @Test - public void testFormatEnglishDisplayNames() { + public void formatDisplayNamesWithEnglishLocaleAndNameEn() { // given - Http.Request request = Mockito.mock(Http.Request.class); + when(lang.locale()).thenReturn(java.util.Locale.forLanguageTag("en")); - java.util.Locale.setDefault(java.util.Locale.forLanguageTag("en")); + // when + String actual = target.formatDisplayName(createLocale(null), request); - assertThat(formatUtils.formatDisplayName(createLocale(null), request)).isNull(); - assertThat(formatUtils.formatDisplayName(createLocale(""), request)).isNull(); - assertThat(formatUtils.formatDisplayName(createLocale("de"), request)).isEqualTo("German"); - assertThat(formatUtils.formatDisplayName(createLocale("en"), request)).isEqualTo("English"); + // then + assertThat(target.formatDisplayName(createLocale("en"), request)).isEqualTo("English"); } @Test - public void testFormatGermanDisplayNames() { + public void formatDisplayNamesWithGermanLocaleAndNameNull() { + // given, when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(actual).isNull(); + } + + @Test + public void formatDisplayNamesWithGermanLocaleAndNameEmpty() { + // given, when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(target.formatDisplayName(createLocale(""), request)).isNull(); + } + + @Test + public void formatDisplayNamesWithGermanLocaleAndNameDe() { // given - Http.Request request = Mockito.mock(Http.Request.class); - java.util.Locale.setDefault(java.util.Locale.forLanguageTag("de")); - - // when, then - assertThat(formatUtils.formatDisplayName(createLocale(null), request)).isNull(); - assertThat(formatUtils.formatDisplayName(createLocale(""), request)).isNull(); - assertThat(formatUtils.formatDisplayName(createLocale("de"), request)).isEqualTo("Deutsch"); - assertThat(formatUtils.formatDisplayName(createLocale("en"), request)).isEqualTo("Englisch"); + when(lang.locale()).thenReturn(java.util.Locale.forLanguageTag("de")); + + // when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(target.formatDisplayName(createLocale("de"), request)).isEqualTo("Deutsch"); + } + + @Test + public void formatDisplayNamesWithGermanLocaleAndNameEn() { + // given + when(lang.locale()).thenReturn(java.util.Locale.forLanguageTag("de")); + + // when + String actual = target.formatDisplayName(createLocale(null), request); + + // then + assertThat(target.formatDisplayName(createLocale("en"), request)).isEqualTo("Englisch"); } private Locale createLocale(String name) {