Skip to content

Commit

Permalink
11_11_restrict_modification
Browse files Browse the repository at this point in the history
  • Loading branch information
GlyzinAI committed Sep 8, 2019
1 parent d97657c commit 5288908
Show file tree
Hide file tree
Showing 14 changed files with 164 additions and 23 deletions.
2 changes: 2 additions & 0 deletions config/messages/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ common.save=Save
common.cancel=Cancel
common.search=Search

exception.common.notFound=Not found entity with {0}
exception.user.duplicateEmail=User with this email already exists
exception.user.modificationRestriction=Admin/User modification is forbidden
exception.meal.duplicateDateTime=You already have meal with this date/time

error.appError=Application error
Expand Down
2 changes: 2 additions & 0 deletions config/messages/app_ru.properties
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ common.save=Сохранить
common.cancel=Отменить
common.search=Искать

exception.common.notFound=Не найдена запись с {0}
exception.user.duplicateEmail=Пользователь с такой почтой уже есть в приложении
exception.user.modificationRestriction=Модификация Admin/User запрещена
exception.meal.duplicateDateTime=У вас уже есть еда с такой датой/временем

error.appError=Ошибка приложения
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/ru/javawebinar/topjava/Profiles.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public class Profiles {

public static final String
POSTGRES_DB = "postgres",
HSQL_DB = "hsqldb";
HSQL_DB = "hsqldb",
HEROKU = "heroku";

// Get DB profile depending of DB driver in classpath
public static String getActiveDbProfile() {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/ru/javawebinar/topjava/util/ValidationUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ public static <T> T checkNotFound(T object, String msg) {
return object;
}

public static void checkNotFound(boolean found, String msg) {
public static void checkNotFound(boolean found, String arg) {
if (!found) {
throw new NotFoundException("Not found entity with " + msg);
throw new NotFoundException(arg);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package ru.javawebinar.topjava.util.exception;

import org.springframework.http.HttpStatus;

import java.util.Arrays;

public class ApplicationException extends RuntimeException {

private final ErrorType type;
private final String msgCode;
private final HttpStatus httpStatus;
private final String[] args;

public ApplicationException(String msgCode, HttpStatus httpStatus) {
this(ErrorType.APP_ERROR, msgCode, httpStatus);
}

public ApplicationException(ErrorType type, String msgCode, HttpStatus httpStatus, String... args) {
super(String.format("type=%s, msgCode=%s, args=%s", type, msgCode, Arrays.toString(args)));
this.type = type;
this.msgCode = msgCode;
this.httpStatus = httpStatus;
this.args = args;
}

public ErrorType getType() {
return type;
}

public String getMsgCode() {
return msgCode;
}

public HttpStatus getHttpStatus() {
return httpStatus;
}

public String[] getArgs() {
return args;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.javawebinar.topjava.util.exception;

import org.springframework.http.HttpStatus;

public class ModificationRestrictionException extends ApplicationException {
public static final String EXCEPTION_MODIFICATION_RESTRICTION = "exception.user.modificationRestriction";

public ModificationRestrictionException() {
super(EXCEPTION_MODIFICATION_RESTRICTION, HttpStatus.UNAVAILABLE_FOR_LEGAL_REASONS);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package ru.javawebinar.topjava.util.exception;
import org.springframework.http.HttpStatus;

public class NotFoundException extends RuntimeException {
public NotFoundException(String message) {
super(message);
public class NotFoundException extends ApplicationException {
public static final String NOT_FOUND_EXCEPTION = "exception.common.notFound";

// http://stackoverflow.com/a/22358422/548473
public NotFoundException(String arg) {
super(ErrorType.DATA_NOT_FOUND, NOT_FOUND_EXCEPTION, HttpStatus.UNPROCESSABLE_ENTITY, arg);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.core.annotation.Order;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
Expand All @@ -17,10 +18,10 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import ru.javawebinar.topjava.util.ValidationUtil;
import ru.javawebinar.topjava.util.exception.ApplicationException;
import ru.javawebinar.topjava.util.exception.ErrorInfo;
import ru.javawebinar.topjava.util.exception.ErrorType;
import ru.javawebinar.topjava.util.exception.IllegalRequestDataException;
import ru.javawebinar.topjava.util.exception.NotFoundException;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;
Expand All @@ -43,11 +44,10 @@ public class ExceptionInfoHandler {
@Autowired
private MessageUtil messageUtil;

// http://stackoverflow.com/a/22358422/548473
@ResponseStatus(value = HttpStatus.UNPROCESSABLE_ENTITY)
@ExceptionHandler(NotFoundException.class)
public ErrorInfo handleError(HttpServletRequest req, NotFoundException e) {
return logAndGetErrorInfo(req, e, false, DATA_NOT_FOUND);
@ExceptionHandler(ApplicationException.class)
public ResponseEntity<ErrorInfo> applicationError(HttpServletRequest req, ApplicationException appEx) {
ErrorInfo errorInfo = logAndGetErrorInfo(req, appEx, false, appEx.getType(), messageUtil.getMessage(appEx));
return ResponseEntity.status(appEx.getHttpStatus()).body(errorInfo);
}

@ResponseStatus(value = HttpStatus.CONFLICT) // 409
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
import ru.javawebinar.topjava.util.ValidationUtil;
import ru.javawebinar.topjava.util.exception.ApplicationException;
import ru.javawebinar.topjava.util.exception.ErrorType;

import javax.servlet.http.HttpServletRequest;
Expand All @@ -21,21 +22,26 @@ public class GlobalControllerExceptionHandler {

@ExceptionHandler(NoHandlerFoundException.class)
public ModelAndView wrongRequest(HttpServletRequest req, NoHandlerFoundException e) throws Exception {
return logAndGetExceptionView(req, e, false, ErrorType.WRONG_REQUEST);
return logAndGetExceptionView(req, e, false, ErrorType.WRONG_REQUEST, null);
}

@ExceptionHandler(ApplicationException.class)
public ModelAndView applicationErrorHandler(HttpServletRequest req, ApplicationException appEx) throws Exception {
return logAndGetExceptionView(req, appEx, true, appEx.getType(), messageUtil.getMessage(appEx));
}

@ExceptionHandler(Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
return logAndGetExceptionView(req, e, true, ErrorType.APP_ERROR);
return logAndGetExceptionView(req, e, true, ErrorType.APP_ERROR, null);
}

private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType) {
private ModelAndView logAndGetExceptionView(HttpServletRequest req, Exception e, boolean logException, ErrorType errorType, String msg) {
Throwable rootCause = ValidationUtil.logAndGetRootCause(log, req, e, logException, errorType);

ModelAndView mav = new ModelAndView("exception/exception");
mav.addObject("typeMessage", messageUtil.getMessage(errorType.getErrorCode()));
mav.addObject("exception", rootCause);
mav.addObject("message", ValidationUtil.getMessage(rootCause));
mav.addObject("message", msg != null ? msg : ValidationUtil.getMessage(rootCause));
return mav;
}
}
13 changes: 9 additions & 4 deletions src/main/java/ru/javawebinar/topjava/web/MessageUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.springframework.context.MessageSourceResolvable;
import org.springframework.context.i18n.LocaleContextHolder;
import org.springframework.stereotype.Component;
import ru.javawebinar.topjava.util.exception.ApplicationException;

import java.util.Locale;

Expand All @@ -19,12 +20,16 @@ public MessageUtil(MessageSource messageSource) {
this.messageSource = messageSource;
}

public String getMessage(String code, Locale locale) {
return messageSource.getMessage(code, null, locale);
public String getMessage(String code, Locale locale, String... args) {
return messageSource.getMessage(code, args, locale);
}

public String getMessage(String code) {
return getMessage(code, LocaleContextHolder.getLocale());
public String getMessage(String code, String... args) {
return getMessage(code, LocaleContextHolder.getLocale(), args);
}

public String getMessage(ApplicationException appEx) {
return getMessage(appEx.getMsgCode(), appEx.getArgs());
}

public String getMessage(MessageSourceResolvable resolvable) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import ru.javawebinar.topjava.Profiles;
import ru.javawebinar.topjava.model.AbstractBaseEntity;
import ru.javawebinar.topjava.model.User;
import ru.javawebinar.topjava.service.UserService;
import ru.javawebinar.topjava.to.UserTo;
import ru.javawebinar.topjava.util.UserUtil;
import ru.javawebinar.topjava.util.exception.ModificationRestrictionException;

import java.util.List;

Expand All @@ -24,6 +28,14 @@ public abstract class AbstractUserController {
@Autowired
private UniqueMailValidator emailValidator;

private boolean modificationRestriction;

@Autowired
@SuppressWarnings("deprecation")
public void setEnvironment(Environment environment) {
modificationRestriction = environment.acceptsProfiles(Profiles.HEROKU);
}

@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(emailValidator);
Expand Down Expand Up @@ -51,23 +63,33 @@ public User create(User user) {

public void delete(int id) {
log.info("delete {}", id);
checkModificationAllowed(id);
service.delete(id);
}

public void update(User user, int id) {
log.info("update {} with id={}", user, id);
assureIdConsistent(user, id);
checkModificationAllowed(id);
service.update(user);
}

public void update(UserTo userTo, int id) {
log.info("update {} with id={}", userTo, id);
assureIdConsistent(userTo, id);
checkModificationAllowed(id);
service.update(userTo);
}

public void enable(int id, boolean enabled) {
log.info(enabled ? "enable {}" : "disable {}", id);
checkModificationAllowed(id);
service.enable(id, enabled);
}

private void checkModificationAllowed(int id) {
if (modificationRestriction && id < AbstractBaseEntity.START_SEQ + 2) {
throw new ModificationRestrictionException();
}
}
}
2 changes: 1 addition & 1 deletion src/main/resources/spring/spring-db.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
p:password="${database.password}"/>
</beans>

<beans profile="postgres">
<beans profile="postgres, heroku">
<!--
For postgres driver logging
It uses java.util.logging and logged via jul-to-slf4j bridge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import ru.javawebinar.topjava.model.Meal;
import ru.javawebinar.topjava.util.exception.ErrorType;
import ru.javawebinar.topjava.util.exception.NotFoundException;

import javax.validation.ConstraintViolationException;
import java.time.LocalDate;
import java.time.Month;

import static java.time.LocalDateTime.of;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static ru.javawebinar.topjava.MealTestData.*;
import static ru.javawebinar.topjava.UserTestData.ADMIN_ID;
import static ru.javawebinar.topjava.UserTestData.USER_ID;
Expand Down Expand Up @@ -76,7 +77,10 @@ void update() throws Exception {
@Test
void updateNotFound() throws Exception {
NotFoundException e = assertThrows(NotFoundException.class, () -> service.update(MEAL1, ADMIN_ID));
assertEquals(e.getMessage(), "Not found entity with id=" + MEAL1_ID);
String msg = e.getMessage();
assertTrue(msg.contains(ErrorType.DATA_NOT_FOUND.name()));
assertTrue(msg.contains(NotFoundException.NOT_FOUND_EXCEPTION));
assertTrue(msg.contains(String.valueOf(MEAL1_ID)));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.javawebinar.topjava.web.user;

import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import ru.javawebinar.topjava.UserTestData;
import ru.javawebinar.topjava.util.exception.ErrorType;
import ru.javawebinar.topjava.web.AbstractControllerTest;

import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static ru.javawebinar.topjava.Profiles.HEROKU;
import static ru.javawebinar.topjava.TestUtil.userHttpBasic;
import static ru.javawebinar.topjava.UserTestData.*;
import static ru.javawebinar.topjava.util.exception.ModificationRestrictionException.EXCEPTION_MODIFICATION_RESTRICTION;

@ActiveProfiles({HEROKU})
public class HerokuRestControllerTest extends AbstractControllerTest {

private static final String REST_URL = AdminRestController.REST_URL + '/';

@Test
void delete() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.delete(REST_URL + USER_ID)
.with(userHttpBasic(ADMIN)))
.andDo(print())
.andExpect(errorType(ErrorType.APP_ERROR))
.andExpect(detailMessage(EXCEPTION_MODIFICATION_RESTRICTION))
.andExpect(status().isUnavailableForLegalReasons());
}

@Test
void update() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.put(REST_URL + USER_ID)
.contentType(MediaType.APPLICATION_JSON)
.with(userHttpBasic(ADMIN))
.content(UserTestData.jsonWithPassword(USER, "password")))
.andExpect(errorType(ErrorType.APP_ERROR))
.andExpect(detailMessage(EXCEPTION_MODIFICATION_RESTRICTION))
.andExpect(status().isUnavailableForLegalReasons());
}
}

0 comments on commit 5288908

Please sign in to comment.