Début transition vers exceptions + DTO

This commit is contained in:
FyloZ 2020-02-17 17:15:04 -05:00
parent d4ea4c3874
commit 5fde4078f7
13 changed files with 259 additions and 170 deletions

View File

@ -0,0 +1,23 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
public class EntityAlreadyExistsException extends ModelException {
private IdentifierType identifierType;
private Object requestedId;
public EntityAlreadyExistsException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
super(type);
this.identifierType = identifierType;
this.requestedId = requestedId;
}
public IdentifierType getIdentifierType() {
return identifierType;
}
public Object getRequestedId() {
return requestedId;
}
}

View File

@ -0,0 +1,35 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
import org.hibernate.type.IdentifierType;
/**
* Représente une exception qui sera lancée lorsqu'un objet du modèle n'est pas trouvé.
*/
public class EntityNotFoundException extends ModelException {
/**
* Le type d'identifiant utilisé
*/
private IdentifierType identifierType;
/**
* La valeur de l'identifiant
*/
private Object requestedId;
public EntityNotFoundException(Class<? extends IModel> type, IdentifierType identifierType, Object requestedId) {
super(type);
this.identifierType = identifierType;
this.requestedId = requestedId;
}
public IdentifierType getIdentifierType() {
return identifierType;
}
public Object getRequestedId() {
return requestedId;
}
}

View File

@ -0,0 +1,27 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
/**
* Représente une exception qui sera déclenchée lors des opérations sur le modèle.
*/
public class ModelException extends RuntimeException {
/**
* Le type de modèle qui est sujette à l'exception
*/
protected Class<? extends IModel> type;
public ModelException(Class<? extends IModel> type) {
this.type = type;
}
public Class<? extends IModel> getType() {
return type;
}
public enum IdentifierType {
ID,
NAME
}
}

View File

@ -0,0 +1,15 @@
package dev.fyloz.trial.colorrecipesexplorer.core.exception.model;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
public class NullIdentifierException extends ModelException {
public NullIdentifierException(Class<? extends IModel> type) {
super(type);
}
@Override
public String getMessage() {
return String.format("Un modèle de type '%s' avait un identifiant nécessaire null", type.getSimpleName());
}
}

View File

@ -0,0 +1,14 @@
package dev.fyloz.trial.colorrecipesexplorer.core.model.dto;
import lombok.Data;
import java.util.Map;
@Data
public class RecipeExplorerFormDto {
private Long recipeId;
private Map<Long, String> locations;
private String note;
}

View File

@ -3,27 +3,40 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dev.fyloz.trial.colorrecipesexplorer.ColorRecipesExplorerApplication;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityAlreadyExistsException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.ModelException;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.NullIdentifierException;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
import org.slf4j.Logger;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.lang.NonNull;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public abstract class GenericService<T extends IModel, R extends JpaRepository<T, Long>> implements IGenericService<T> {
public class GenericService<T extends IModel, R extends JpaRepository<T, Long>> implements IGenericService<T> {
protected Logger logger = ColorRecipesExplorerApplication.LOGGER;
protected R dao;
public GenericService(R dao) {
private Class<T> type;
public GenericService(R dao, Class<T> type) {
this.dao = dao;
this.type = type;
}
@Override
public Optional<T> getById(Long id) {
return dao.findById(id);
public T getById(Long id) {
Optional<T> found = dao.findById(id);
if (found.isEmpty()) throw new EntityNotFoundException(type, EntityNotFoundException.IdentifierType.ID, id);
return found.get();
}
@Override
@ -32,60 +45,45 @@ public abstract class GenericService<T extends IModel, R extends JpaRepository<T
}
@Override
public Optional<T> save(T entity) {
if (isValidForCreation(entity)) {
return Optional.of(dao.save(entity));
}
public T save(@NotNull T entity) {
if (entity.getId() != null && existsById(entity.getId()))
throw new EntityAlreadyExistsException(type, ModelException.IdentifierType.ID, entity.getId());
return Optional.empty();
return dao.save(entity);
}
@Override
public boolean saveAll(Iterable<T> entities) {
if (entities == null) return false;
for (T e : entities) {
if (!save(e).isPresent()) {
return false;
}
}
dao.saveAll(entities);
return true;
@Transactional
public Collection<T> saveAll(@NotNull Collection<T> entities) {
return entities
.stream()
.map(this::save)
.collect(Collectors.toList());
}
@Override
public Optional<T> update(@NonNull T entity) {
if (isValidForUpdate(entity)) {
return Optional.of(dao.save(entity));
}
public T update(@NonNull T entity) {
if (entity.getId() == null) throw new NullIdentifierException(type);
if (!existsById(entity.getId()))
throw new EntityNotFoundException(type, ModelException.IdentifierType.ID, entity.getId());
return Optional.empty();
return dao.save(entity);
}
@Override
public boolean delete(T entity) {
if (entity == null) return false;
public void delete(@NonNull T entity) {
if (entity.getId() == null) throw new NullIdentifierException(type);
if (!existsById(entity.getId()))
throw new EntityNotFoundException(type, ModelException.IdentifierType.ID, entity.getId());
if (exists(entity)) {
dao.delete(entity);
return true;
}
return false;
dao.delete(entity);
}
@Override
public boolean deleteAll(List<T> entities) {
if (entities == null) return false;
for (T entity : entities) {
if (!exists(entity)) return false;
}
dao.deleteAll(entities);
return true;
@Transactional
public void deleteAll(@NonNull List<T> entities) {
entities
.forEach(this::delete);
}
/**
@ -94,8 +92,9 @@ public abstract class GenericService<T extends IModel, R extends JpaRepository<T
* @param entity L'entité à vérifier.
* @return Si l'entité est valide pour la création.
*/
@Deprecated(since = "1.3.0", forRemoval = true)
public boolean isValidForCreation(T entity) {
return entity != null && !exists(entity);
return entity != null && !existsById(entity.getId());
}
/**
@ -104,23 +103,19 @@ public abstract class GenericService<T extends IModel, R extends JpaRepository<T
* @param entity L'entité à vérifier.
* @return Si l'entité est valide pour l'édition.
*/
@Deprecated(since = "1.3.0", forRemoval = true)
public boolean isValidForUpdate(@NonNull T entity) {
return entity.getId() != null && exists(entity);
return entity.getId() != null && existsById(entity.getId());
}
/**
* Vérifie si une entité existe dans la base de données.
*
* @param entity L'entité à vérifier
* @return Si l'entité existe.
*/
@Override
@Deprecated(since = "v.1.3.0", forRemoval = true)
public boolean exists(T entity) {
return entity != null && entity.getId() != null && existsById(entity.getId());
}
@Override
public boolean existsById(Long id) {
public final boolean existsById(Long id) {
return dao.existsById(id);
}

View File

@ -1,81 +1,73 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services;
import dev.fyloz.trial.colorrecipesexplorer.core.model.IModel;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
public interface IGenericService<T extends IModel> {
/**
* Récupère toutes les entités du même type dans la base de données.
* Récupère toutes les entités de type T.
*
* @return Une collection contenant toutes les entités du même type.
* @return Toutes les entités de type T
*/
List<T> getAll();
/**
* Récupère l'entité correspondant à l'identifiant spécifié dans la base de données.
* Récupère l'entité de type T correspondant à un identifiant.
*
* @param id L'identifiant de l'entité
* @return L'entité correspondant à l'identifiant.
* @return L'entité correspondant à l'identifiant
*/
Optional<T> getById(Long id);
T getById(Long id);
/**
* Sauvegarde une entité dans la base de données.
* <p>
* La sauvegarde sera exécutée si l'entité est valide pour la création.
* Crée une entité.
*
* @param entity L'entité à sauvegarder
* @return Si la sauvegarde a fonctionné.
* @param entity L'entité à créer
* @return L'entité créée
*/
Optional<T> save(T entity);
T save(T entity);
/**
* Sauvegarde des entités dans la base de données.
* <p>
* Les sauvegardes seront exécutée si toutes les entités sont valides pour la création.
* Crée plusieurs entités.
*
* @param entities La liste d'entités à sauvegarder
* @return Si la sauvegarde a fonctionné.
* @param entities Les entités à créer
* @return Les entités créées
*/
@Transactional
boolean saveAll(Iterable<T> entities);
Collection<T> saveAll(Collection<T> entities);
/**
* Met à jour une entité dans la base de données.
* <p>
* La mise à jour sera exécutée si l'entité existe dans la BDD.
* Met à jour une entité dans le stockage.
*
* @param entity L'entité à mettre à jour
* @return Si la mise à jour a fonctionné.
* @return L'entité mise à jour
*/
Optional<T> update(T entity);
T update(T entity);
/**
* Supprime une entité dans la base de données.
* <p>
* La suppression sera exécutée si l'entité existe dans la BDD.
* Supprime une entité.
*
* @param entity L'entité à supprimer
* @return Si la suppression a fonctionné.
*/
boolean delete(T entity);
void delete(T entity);
/**
* Supprime des entités dans la base de données.
* <p>
* Les suppressions seront exécutée si toutes les entités existes dans la BDD.
* Supprime plusieurs entités.
*
* @param entities La liste d'entités à supprimer
* @return Si la suppression a fonctionné.
* @param entities Les entités à supprimer
*/
@Transactional
boolean deleteAll(List<T> entities);
void deleteAll(List<T> entities);
@Deprecated(since = "1.3.0", forRemoval = true)
boolean exists(T entity);
/**
* Vérifie si une entité correspondant à un identifiant existe.
*
* @param id L'identifiant de l'entité
* @return Si un entité correspondant à l'identifiant existe
*/
boolean existsById(Long id);
}

View File

@ -1,4 +1,4 @@
package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
package dev.fyloz.trial.colorrecipesexplorer.core.services;
import dev.fyloz.trial.colorrecipesexplorer.core.utils.PdfBuilder;
import org.springframework.beans.factory.annotation.Autowired;

View File

@ -24,7 +24,7 @@ public class MixService extends GenericService<Mix, MixDao> {
@Autowired
public MixService(MixDao mixDao, MaterialService materialService, MixQuantityService mixQuantityService, MixTypeService mixTypeService, RecipeService recipeService) {
super(mixDao);
super(mixDao, Mix.class);
this.materialService = materialService;
this.mixQuantityService = mixQuantityService;
this.mixTypeService = mixTypeService;

View File

@ -3,6 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.core.services.model;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.FileHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.io.file.ImageHandler;
import dev.fyloz.trial.colorrecipesexplorer.core.model.*;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.GenericService;
import dev.fyloz.trial.colorrecipesexplorer.dao.RecipeDao;
import org.springframework.beans.factory.annotation.Autowired;
@ -18,12 +19,14 @@ import java.util.stream.Collectors;
public class RecipeService extends GenericService<Recipe, RecipeDao> {
private CompanyService companyService;
private MixService mixService;
private StepService stepService;
@Autowired
public RecipeService(RecipeDao recipeDao, CompanyService companyService, StepService stepService) {
super(recipeDao);
public RecipeService(RecipeDao recipeDao, CompanyService companyService, MixService mixService, StepService stepService) {
super(recipeDao, Recipe.class);
this.companyService = companyService;
this.mixService = mixService;
this.stepService = stepService;
}
@ -58,7 +61,7 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
return mappedByCompany(getAll());
}
public Optional<Recipe> updateRecipe(Recipe newRecipe, Recipe storedRecipe, MultiValueMap<String, Object> form) {
public Recipe updateRecipe(Recipe newRecipe, Recipe storedRecipe, MultiValueMap<String, Object> form) {
storedRecipe.setName(newRecipe.getName());
storedRecipe.setCompany(newRecipe.getCompany());
storedRecipe.setDescription(newRecipe.getDescription());
@ -67,18 +70,32 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
storedRecipe.setRemark(newRecipe.getRemark());
storedRecipe.setNote(newRecipe.getNote());
Optional<Recipe> optionalRecipe = convertAndCreateSteps(storedRecipe, form);
if (optionalRecipe.isEmpty()) {
return Optional.empty();
}
return update(optionalRecipe.get());
return convertAndCreateSteps(storedRecipe, form);
}
@Transactional
public boolean deleteRecipe(Recipe recipe) {
if (!delete(recipe)) return false;
return removeAllFiles(recipe);
public void updateRecipeExplorerInfos(RecipeExplorerFormDto form) {
long recipeId = form.getRecipeId();
Map<Long, String> locations = form.getLocations();
String note = form.getNote();
Recipe recipe = getById(recipeId);
// Note
recipe.setNote(note);
save(recipe);
// Casiers
for (Map.Entry<Long, String> location : locations.entrySet()) {
Mix mix = mixService.getById(location.getKey());
mix.setLocation(location.getValue());
mixService.update(mix);
}
}
@Override
public void delete(Recipe recipe) {
super.delete(recipe);
removeAllFiles(recipe);
}
/**
@ -126,13 +143,6 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
.contains(mixType);
}
@Deprecated(since = "1.2.0")
public Map<Company, List<Recipe>> getRecipesForSearchString(String searchString) {
List<Recipe> recipes = dao.findAllByRecipeDescriptionContainsOrRecipeCodeContains(searchString.toUpperCase());
return mappedByCompany(recipes);
}
private Map<Company, List<Recipe>> mappedByCompany(List<Recipe> recipes) {
List<Company> companies = companyService.getAll();
Map<Company, List<Recipe>> mappedRecipes = new HashMap<>();
@ -156,9 +166,7 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
* @return La recette avec les étapes.
*/
@Transactional
public Optional<Recipe> convertAndCreateSteps(Recipe recipe, MultiValueMap<String, Object> input) {
if (recipe == null || input == null) return Optional.empty();
public Recipe convertAndCreateSteps(Recipe recipe, MultiValueMap<String, Object> input) {
// Convertit les étapes en RecipeSteps
List<RecipeStep> steps = new ArrayList<>();
if (input.containsKey("step")) {
@ -174,14 +182,11 @@ public class RecipeService extends GenericService<Recipe, RecipeDao> {
return setSteps(recipe, steps);
}
private Optional<Recipe> setSteps(Recipe recipe, List<RecipeStep> steps) {
if (!stepService.deleteAll(recipe.getRecipeSteps())) return Optional.empty();
recipe.setRecipeSteps(null);
update(recipe);
private Recipe setSteps(Recipe recipe, List<RecipeStep> steps) {
stepService.deleteAll(recipe.getRecipeSteps());
recipe.setRecipeSteps(steps);
return Optional.of(recipe);
return update(recipe);
}
private boolean removeAllFiles(Recipe recipe) {

View File

@ -1,11 +1,13 @@
package dev.fyloz.trial.colorrecipesexplorer.web.controller;
import dev.fyloz.trial.colorrecipesexplorer.core.exception.model.EntityNotFoundException;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.JSONResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseCode;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Mix;
import dev.fyloz.trial.colorrecipesexplorer.core.model.Recipe;
import dev.fyloz.trial.colorrecipesexplorer.core.model.dto.RecipeExplorerFormDto;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.MixService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import org.springframework.beans.factory.annotation.Autowired;
@ -47,66 +49,47 @@ public class RecipeExplorerController {
public ModelAndView getPage(@PathVariable Long id) {
ModelResponseBuilder modelResponseBuilder = new ModelResponseBuilder(EXPLORER_RECIPE);
Optional<Recipe> optionalRecipe = recipeService.getById(id);
if (optionalRecipe.isEmpty()) {
try {
Recipe recipe = recipeService.getById(id);
modelResponseBuilder.addResponseData(ResponseDataType.RECIPE, recipe);
List<Mix> mixes = new ArrayList<>(recipe.getMixes()); // Convertit le PersistentBag en ArrayList
mixes.sort(Comparator.comparing(Mix::getId));
return modelResponseBuilder
.addResponseData(ResponseDataType.RECIPE, recipe)
.addResponseData(ResponseDataType.MIXES, mixes)
.addResponseData(ResponseDataType.IMAGES, recipeService.getImageFiles(recipe))
.build();
} catch (EntityNotFoundException e) {
return modelResponseBuilder
.withView(INDEX)
.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, id)
.build();
}
Recipe recipe = optionalRecipe.get();
List<Mix> mixes = new ArrayList<>(recipe.getMixes()); // Convertit le PersistentBag en ArrayList
mixes.sort(Comparator.comparing(Mix::getId));
return modelResponseBuilder
.addResponseData(ResponseDataType.RECIPE, recipe)
.addResponseData(ResponseDataType.MIXES, mixes)
.addResponseData(ResponseDataType.IMAGES, recipeService.getImageFiles(recipe))
.build();
}
// TODO convertir form en DTO
@PostMapping(value = EXPLORER_RECIPE, consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Map<String, Object> saveRecipeInformations(@RequestBody Map<String, Object> form) {
public Map<String, Object> saveRecipeInformations(RecipeExplorerFormDto form) {
JSONResponseBuilder responseBuilder = new JSONResponseBuilder();
try {
long recipeId = Long.parseLong(form.get("recipeId").toString());
Map<String, String> location = (Map<String, String>) form.get("locations");
String note = form.get("note").toString();
Optional<Recipe> optionalRecipe = recipeService.getById(recipeId);
if (optionalRecipe.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.RECIPE_NOT_FOUND, recipeId);
} else {
Recipe recipe = optionalRecipe.get();
recipe.setNote(note);
recipeService.save(recipe);
}
for (String mixIdStr : location.keySet()) {
long mixId = Long.parseLong(mixIdStr);
Optional<Mix> optionalMix = mixService.getById(mixId);
if (optionalMix.isEmpty()) {
responseBuilder.addResponseCode(ResponseCode.MIX_NOT_FOUND, mixId);
} else {
Mix mix = optionalMix.get();
if (mix.getRecipe().getId() != recipeId) {
responseBuilder.addResponseCode(ResponseCode.MIX_NOT_ASSOCIATED_WITH_RECIPE, mixId, recipeId);
} else {
mix.setLocation(location.get(mixIdStr));
mixService.update(mix);
}
}
}
if (!responseBuilder.containsErrors()) {
responseBuilder.addResponseCode(ResponseCode.SUCCESS_SAVING_RECIPE_INFORMATIONS);
} catch (EntityNotFoundException e) {
ResponseCode responseCode = null;
switch (e.getType().getSimpleName()) {
case "Recipe":
responseCode = ResponseCode.RECIPE_NOT_FOUND;
break;
case "Mix":
responseCode = ResponseCode.MIX_NOT_FOUND;
break;
}
responseBuilder.addResponseCode(responseCode, e.getRequestedId());
}
return responseBuilder.build();

View File

@ -3,7 +3,7 @@ package dev.fyloz.trial.colorrecipesexplorer.web.controller.files;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ModelResponseBuilder;
import dev.fyloz.trial.colorrecipesexplorer.core.io.response.ResponseDataType;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.RecipeService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.model.TouchUpKitService;
import dev.fyloz.trial.colorrecipesexplorer.core.services.TouchUpKitService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;

View File

@ -1,7 +1,7 @@
# v1.3.0 (Optimisations back-end)
### Note: Cette mise à jour n'est pas compatible avec les anciennes versions.
### Corrections
* Reusinage des modèles. (Empêche la compatibilité avec les anciennes versions)
* Réusinage des modèles. (Empêche la compatibilité avec les anciennes versions)
# v1.2.0 (Imprimante P-touch)
### Corrections