Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Colin DAMON
pouet
Commits
a5653ecd
Commit
a5653ecd
authored
Jul 14, 2020
by
Colin DAMON
Browse files
Merge branch '2-errors-itnernationalisation' into 'master'
Resolve "Errors internationalisation" Closes
#2
See merge request
!2
parents
eead5197
f14e88f2
Pipeline
#30068
canceled with stage
in 4 minutes and 30 seconds
Changes
55
Pipelines
3
Hide whitespace changes
Inline
Side-by-side
pom.xml
View file @
a5653ecd
...
...
@@ -244,10 +244,6 @@
<artifactId>
spring-security-test
</artifactId>
<scope>
test
</scope>
</dependency>
<dependency>
<groupId>
org.zalando
</groupId>
<artifactId>
problem-spring-web
</artifactId>
</dependency>
<dependency>
<groupId>
io.jsonwebtoken
</groupId>
<artifactId>
jjwt-api
</artifactId>
...
...
@@ -279,6 +275,11 @@
<groupId>
io.dropwizard.metrics
</groupId>
<artifactId>
metrics-core
</artifactId>
</dependency>
<dependency>
<groupId>
org.reflections
</groupId>
<artifactId>
reflections
</artifactId>
<scope>
test
</scope>
</dependency>
<!-- jhipster-needle-maven-add-dependency -->
</dependencies>
...
...
@@ -498,7 +499,7 @@
<artifactId>
jacoco-maven-plugin
</artifactId>
<version>
${jacoco-maven-plugin.version}
</version>
<configuration>
<excludes>
<excludes>
<exclude>
com/ippon/pouet/domain/*
</exclude>
</excludes>
</configuration>
...
...
src/main/java/com/ippon/pouet/common/domain/error/Assert.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
public
final
class
Assert
{
private
Assert
()
{}
public
static
void
notNull
(
String
field
,
Object
input
)
{
if
(
input
==
null
)
{
throw
MissingMandatoryValueException
.
forNullValue
(
field
);
}
}
public
static
void
notBlank
(
String
field
,
String
input
)
{
notNull
(
field
,
input
);
if
(
input
.
isBlank
())
{
throw
MissingMandatoryValueException
.
forBlankValue
(
field
);
}
}
}
src/main/java/com/ippon/pouet/common/domain/error/ErrorStatus.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
public
enum
ErrorStatus
{
BAD_REQUEST
,
UNAUTHORIZED
,
FORBIDDEN
,
NOT_FOUND
,
INTERNAL_SERVER_ERROR
,
}
src/main/java/com/ippon/pouet/common/domain/error/MissingMandatoryValueException.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
public
class
MissingMandatoryValueException
extends
PouetException
{
protected
MissingMandatoryValueException
(
PouetExceptionBuilder
builder
)
{
super
(
builder
);
}
public
static
MissingMandatoryValueException
forNullValue
(
String
fieldName
)
{
return
new
MissingMandatoryValueException
(
builder
(
StandardMessage
.
SERVER_MANDATORY_NULL
,
fieldName
,
defaultMessage
(
fieldName
)
+
" (null)"
)
);
}
public
static
MissingMandatoryValueException
forBlankValue
(
String
fieldName
)
{
return
new
MissingMandatoryValueException
(
builder
(
StandardMessage
.
SERVER_MANDATORY_BLANK
,
fieldName
,
defaultMessage
(
fieldName
)
+
" (blank)"
)
);
}
private
static
PouetExceptionBuilder
builder
(
PouetMessage
pouetMessage
,
String
fieldName
,
String
message
)
{
return
PouetException
.
builder
(
pouetMessage
).
status
(
ErrorStatus
.
INTERNAL_SERVER_ERROR
).
argument
(
"field"
,
fieldName
).
message
(
message
);
}
private
static
String
defaultMessage
(
String
fieldName
)
{
return
"The field \""
+
fieldName
+
"\" is mandatory and wasn't set"
;
}
}
src/main/java/com/ippon/pouet/common/domain/error/PouetException.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Map
;
/**
* Parent exception used in Pouet application. Those exceptions will be resolved as human readable errors.
*
* <p>
* You can use this implementation directly:
* </p>
*
* <p>
* <code>
* <pre>
* PouetException.builder(StandardMessages.USER_MANDATORY)
* .argument("key", "value")
* .argument("other", "test")
* .status(ErrorsHttpStatus.BAD_REQUEST)
* .message("Error message")
* .cause(new RuntimeException())
* .build();
* </pre>
* </code>
* </p>
*
* <p>
* Or make extension exceptions:
* </p>
*
* <p>
* <code>
* <pre>
* public class MissingMandatoryValueException extends PouetException {
*
* public MissingMandatoryValueException(PouetMessage pouetMessage, String fieldName) {
* this(builder(pouetMessage, fieldName, defaultMessage(fieldName)));
* }
*
* protected MissingMandatoryValueException(PouetExceptionBuilder builder) {
* super(builder);
* }
*
* private static PouetExceptionBuilder builder(PouetMessage pouetMessage, String fieldName, String message) {
* return PouetException.builder(pouetMessage)
* .status(ErrorsStatus.INTERNAL_SERVER_ERROR)
* .argument("field", fieldName)
* .message(message);
* }
*
* private static String defaultMessage(String fieldName) {
* return "The field \"" + fieldName + "\" is mandatory and wasn't set";
* }
* }
* </pre>
* </code>
* </p>
*/
public
class
PouetException
extends
RuntimeException
{
private
final
Map
<
String
,
String
>
arguments
;
private
final
ErrorStatus
status
;
private
final
PouetMessage
pouetMessage
;
protected
PouetException
(
PouetExceptionBuilder
builder
)
{
super
(
getMessage
(
builder
),
getCause
(
builder
));
arguments
=
getArguments
(
builder
);
status
=
getStatus
(
builder
);
pouetMessage
=
getPouetMessage
(
builder
);
}
private
static
String
getMessage
(
PouetExceptionBuilder
builder
)
{
if
(
builder
==
null
)
{
return
null
;
}
return
builder
.
message
;
}
private
static
Throwable
getCause
(
PouetExceptionBuilder
builder
)
{
if
(
builder
==
null
)
{
return
null
;
}
return
builder
.
cause
;
}
private
static
Map
<
String
,
String
>
getArguments
(
PouetExceptionBuilder
builder
)
{
if
(
builder
==
null
)
{
return
null
;
}
return
Collections
.
unmodifiableMap
(
builder
.
arguments
);
}
private
static
ErrorStatus
getStatus
(
PouetExceptionBuilder
builder
)
{
if
(
builder
==
null
)
{
return
null
;
}
return
builder
.
status
;
}
private
static
PouetMessage
getPouetMessage
(
PouetExceptionBuilder
builder
)
{
if
(
builder
==
null
)
{
return
null
;
}
return
builder
.
pouetMessage
;
}
public
static
PouetExceptionBuilder
builder
(
PouetMessage
message
)
{
return
new
PouetExceptionBuilder
(
message
);
}
public
Map
<
String
,
String
>
getArguments
()
{
return
arguments
;
}
public
ErrorStatus
getStatus
()
{
return
status
;
}
public
PouetMessage
getPouetMessage
()
{
return
pouetMessage
;
}
public
static
class
PouetExceptionBuilder
{
private
final
Map
<
String
,
String
>
arguments
=
new
HashMap
<>();
private
String
message
;
private
ErrorStatus
status
;
private
PouetMessage
pouetMessage
;
private
Throwable
cause
;
public
PouetExceptionBuilder
(
PouetMessage
pouetMessage
)
{
this
.
pouetMessage
=
pouetMessage
;
}
public
PouetExceptionBuilder
argument
(
String
key
,
Object
value
)
{
arguments
.
put
(
key
,
getStringValue
(
value
));
return
this
;
}
private
String
getStringValue
(
Object
value
)
{
if
(
value
==
null
)
{
return
"null"
;
}
return
value
.
toString
();
}
public
PouetExceptionBuilder
status
(
ErrorStatus
status
)
{
this
.
status
=
status
;
return
this
;
}
public
PouetExceptionBuilder
message
(
String
message
)
{
this
.
message
=
message
;
return
this
;
}
public
PouetExceptionBuilder
cause
(
Throwable
cause
)
{
this
.
cause
=
cause
;
return
this
;
}
public
PouetException
build
()
{
return
new
PouetException
(
this
);
}
}
}
src/main/java/com/ippon/pouet/common/domain/error/PouetMessage.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
import
java.io.Serializable
;
@FunctionalInterface
public
interface
PouetMessage
extends
Serializable
{
String
getMessageKey
();
}
src/main/java/com/ippon/pouet/common/domain/error/StandardMessage.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.domain.error
;
/**
* User messages for standard (common) use cases
*/
public
enum
StandardMessage
implements
PouetMessage
{
USER_MANDATORY
(
"user.mandatory"
),
SERVER_MANDATORY_NULL
(
"server.mandatory-null"
),
SERVER_MANDATORY_BLANK
(
"server.mandatory-blank"
),
BAD_REQUEST
(
"user.bad-request"
),
INTERNAL_SERVER_ERROR
(
"server.internal-server-error"
);
private
final
String
messageKey
;
private
StandardMessage
(
String
code
)
{
this
.
messageKey
=
code
;
}
@Override
public
String
getMessageKey
()
{
return
messageKey
;
}
}
src/main/java/com/ippon/pouet/common/infrastructure/primary/ArgumentsReplacer.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.infrastructure.primary
;
import
java.util.Map
;
final
class
ArgumentsReplacer
{
private
static
final
String
OPEN_MUSTACHE
=
"\\{\\{\\s*"
;
private
static
final
String
CLOSE_MUSTACHE
=
"\\s*\\}\\}"
;
private
ArgumentsReplacer
()
{}
public
static
String
replaceArguments
(
String
message
,
Map
<
String
,
String
>
arguments
)
{
if
(
message
==
null
||
arguments
==
null
)
{
return
message
;
}
String
result
=
message
;
for
(
Map
.
Entry
<
String
,
String
>
argument
:
arguments
.
entrySet
())
{
String
argumentRegex
=
OPEN_MUSTACHE
+
argument
.
getKey
()
+
CLOSE_MUSTACHE
;
result
=
result
.
replaceAll
(
argumentRegex
,
argument
.
getValue
());
}
return
result
;
}
}
src/main/java/com/ippon/pouet/common/infrastructure/primary/AuthenticationMessage.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.infrastructure.primary
;
import
com.ippon.pouet.common.domain.error.PouetMessage
;
enum
AuthenticationMessage
implements
PouetMessage
{
NOT_AUTHENTICATED
(
"user.authentication-not-authenticated"
);
private
final
String
messageKey
;
AuthenticationMessage
(
String
messageKey
)
{
this
.
messageKey
=
messageKey
;
}
@Override
public
String
getMessageKey
()
{
return
messageKey
;
}
}
src/main/java/com/ippon/pouet/common/infrastructure/primary/PouetError.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.infrastructure.primary
;
import
com.fasterxml.jackson.annotation.JsonProperty
;
import
io.swagger.annotations.ApiModel
;
import
io.swagger.annotations.ApiModelProperty
;
import
java.util.Collection
;
import
java.util.List
;
@ApiModel
(
description
=
"Error result for a WebService call"
)
class
PouetError
{
@ApiModelProperty
(
value
=
"Technical type of this error"
,
example
=
"user.mandatory"
,
required
=
true
)
private
final
String
errorType
;
@ApiModelProperty
(
value
=
"Human readable error message"
,
example
=
"Une erreur technique est survenue lors du traitement de votre demande"
,
required
=
true
)
private
final
String
message
;
@ApiModelProperty
(
value
=
"Invalid fields"
,
required
=
false
)
private
final
List
<
PouetFieldError
>
fieldsErrors
;
public
PouetError
(
String
errorType
,
String
message
)
{
this
(
errorType
,
message
,
null
);
}
public
PouetError
(
@JsonProperty
(
"errorType"
)
String
errorType
,
@JsonProperty
(
"message"
)
String
message
,
@JsonProperty
(
"fieldsErrors"
)
List
<
PouetFieldError
>
fieldsErrors
)
{
this
.
errorType
=
errorType
;
this
.
message
=
message
;
this
.
fieldsErrors
=
fieldsErrors
;
}
public
String
getErrorType
()
{
return
errorType
;
}
public
String
getMessage
()
{
return
message
;
}
public
Collection
<
PouetFieldError
>
getFieldsErrors
()
{
return
fieldsErrors
;
}
}
src/main/java/com/ippon/pouet/common/infrastructure/primary/PouetErrorHandler.java
0 → 100644
View file @
a5653ecd
package
com.ippon.pouet.common.infrastructure.primary
;
import
com.ippon.pouet.common.domain.error.ErrorStatus
;
import
com.ippon.pouet.common.domain.error.PouetException
;
import
com.ippon.pouet.common.domain.error.PouetMessage
;
import
com.ippon.pouet.common.domain.error.StandardMessage
;
import
java.util.List
;
import
java.util.Locale
;
import
java.util.Map
;
import
java.util.function.Function
;
import
java.util.stream.Collectors
;
import
javax.validation.ConstraintViolation
;
import
javax.validation.ConstraintViolationException
;
import
org.apache.commons.lang3.StringUtils
;
import
org.apache.commons.lang3.exception.ExceptionUtils
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.context.MessageSource
;
import
org.springframework.context.NoSuchMessageException
;
import
org.springframework.context.i18n.LocaleContextHolder
;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.ResponseEntity
;
import
org.springframework.http.converter.HttpMessageNotReadableException
;
import
org.springframework.security.access.AccessDeniedException
;
import
org.springframework.security.core.AuthenticationException
;
import
org.springframework.validation.FieldError
;
import
org.springframework.web.bind.MethodArgumentNotValidException
;
import
org.springframework.web.bind.annotation.ControllerAdvice
;
import
org.springframework.web.bind.annotation.ExceptionHandler
;
import
org.springframework.web.client.RestClientResponseException
;
import
org.springframework.web.context.request.WebRequest
;
import
org.springframework.web.method.annotation.MethodArgumentTypeMismatchException
;
import
org.springframework.web.multipart.MaxUploadSizeExceededException
;
import
org.springframework.web.server.ResponseStatusException
;
import
org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
;
@ControllerAdvice
public
class
PouetErrorHandler
extends
ResponseEntityExceptionHandler
{
private
static
final
String
MESSAGE_PREFIX
=
"pouet.error."
;
private
static
final
String
DEFAULT_KEY
=
StandardMessage
.
INTERNAL_SERVER_ERROR
.
getMessageKey
();
private
static
final
String
BAD_REQUEST_KEY
=
StandardMessage
.
BAD_REQUEST
.
getMessageKey
();
private
static
final
String
STATUS_EXCEPTION_KEY
=
"status-exception"
;
private
static
final
Logger
logger
=
LoggerFactory
.
getLogger
(
PouetErrorHandler
.
class
);
private
final
MessageSource
messages
;
public
PouetErrorHandler
(
MessageSource
messages
)
{
Locale
.
setDefault
(
Locale
.
FRANCE
);
this
.
messages
=
messages
;
}
@ExceptionHandler
public
ResponseEntity
<
PouetError
>
handlePouetException
(
PouetException
exception
)
{
HttpStatus
status
=
getStatus
(
exception
);
logError
(
exception
,
status
);
String
messageKey
=
getMessageKey
(
status
,
exception
);
PouetError
error
=
new
PouetError
(
messageKey
,
getMessage
(
messageKey
,
exception
.
getArguments
()));
return
new
ResponseEntity
<>(
error
,
status
);
}
@ExceptionHandler
public
ResponseEntity
<
PouetError
>
handleResponseStatusException
(
ResponseStatusException
exception
)
{
HttpStatus
status
=
exception
.
getStatus
();
logError
(
exception
,
status
);
PouetError
error
=
new
PouetError
(
STATUS_EXCEPTION_KEY
,
buildErrorStatusMessage
(
exception
));
return
new
ResponseEntity
<>(
error
,
status
);
}
private
String
buildErrorStatusMessage
(
ResponseStatusException
exception
)
{
String
reason
=
exception
.
getReason
();
if
(
StringUtils
.
isBlank
(
reason
))
{
Map
<
String
,
String
>
statusArgument
=
Map
.
of
(
"status"
,
String
.
valueOf
(
exception
.
getStatus
().
value
()));
return
getMessage
(
STATUS_EXCEPTION_KEY
,
statusArgument
);
}
return
reason
;
}
@ExceptionHandler
(
MaxUploadSizeExceededException
.
class
)
public
ResponseEntity
<
PouetError
>
handleFileSizeException
(
MaxUploadSizeExceededException
maxUploadSizeExceededException
)
{
logger
.
warn
(
"File size limit exceeded: {}"
,
maxUploadSizeExceededException
.
getMessage
(),
maxUploadSizeExceededException
);
PouetError
error
=
new
PouetError
(
"server.upload-too-big"
,
getMessage
(
"server.upload-too-big"
,
null
));
return
new
ResponseEntity
<>(
error
,
HttpStatus
.
BAD_REQUEST
);
}
@ExceptionHandler
(
AccessDeniedException
.
class
)
public
ResponseEntity
<
PouetError
>
handleAccessDeniedException
(
AccessDeniedException
accessDeniedException
)
{
PouetError
error
=
new
PouetError
(
"user.access-denied"
,
getMessage
(
"user.access-denied"
,
null
));
return
new
ResponseEntity
<>(
error
,
HttpStatus
.
FORBIDDEN
);
}
@ExceptionHandler
(
MethodArgumentTypeMismatchException
.
class
)
public
ResponseEntity
<
PouetError
>
handleMethodArgumentTypeMismatchException
(
MethodArgumentTypeMismatchException
exception
)
{
Throwable
rootCause
=
ExceptionUtils
.
getRootCause
(
exception
);
if
(
rootCause
instanceof
PouetException
)
{
return
handlePouetException
((
PouetException
)
rootCause
);
}
PouetError
error
=
new
PouetError
(
BAD_REQUEST_KEY
,
getMessage
(
BAD_REQUEST_KEY
,
null
));
return
new
ResponseEntity
<>(
error
,
HttpStatus
.
BAD_REQUEST
);
}
private
HttpStatus
getStatus
(
PouetException
exception
)
{
ErrorStatus
status
=
exception
.
getStatus
();
if
(
status
==
null
)
{
return
HttpStatus
.
INTERNAL_SERVER_ERROR
;
}
switch
(
status
)
{
case
BAD_REQUEST:
return
HttpStatus
.
BAD_REQUEST
;
case
UNAUTHORIZED:
return
HttpStatus
.
UNAUTHORIZED
;
case
FORBIDDEN:
return
HttpStatus
.
FORBIDDEN
;
case
NOT_FOUND:
return
HttpStatus
.
NOT_FOUND
;
default
:
return
HttpStatus
.
INTERNAL_SERVER_ERROR
;
}
}
private
void
logError
(
Exception
exception
,
HttpStatus
status
)
{
if
(
status
.
is5xxServerError
())
{
logger
.
error
(
"A server error was sent to a user: {}"
,
exception
.
getMessage
(),
exception
);
logErrorBody
(
exception
);
}
else
{
logger
.
warn
(
"An error was sent to a user: {}"
,
exception
.
getMessage
(),
exception
);
}
}
private
void
logErrorBody
(
Exception
exception
)
{
Throwable
cause
=
exception
.
getCause
();
if
(
cause
instanceof
RestClientResponseException
)
{
RestClientResponseException
restCause
=
(
RestClientResponseException
)
cause
;
logger
.
error
(
"Cause body: {}"
,
restCause
.
getResponseBodyAsString
());
}
}
private
String
getMessageKey
(
HttpStatus
status
,
PouetException
exception
)
{
PouetMessage
message
=
exception
.
getPouetMessage
();
if
(
message
==
null
)
{
return
getDefaultMessage
(
status
);
}
return
message
.
getMessageKey
();
}
private
String
getDefaultMessage
(
HttpStatus
status
)
{
if
(
status
.
is5xxServerError
())
{
return
DEFAULT_KEY
;
}
return
BAD_REQUEST_KEY
;
}
@Override
protected
ResponseEntity
<
Object
>
handleMethodArgumentNotValid
(
MethodArgumentNotValidException
exception
,
HttpHeaders
headers
,
HttpStatus
status
,
WebRequest
request
)
{
logger
.
debug
(
"Bean validation error {}"
,
exception
.
getMessage
(),
exception
);
PouetError
error
=
new
PouetError
(
BAD_REQUEST_KEY
,
getMessage
(
BAD_REQUEST_KEY
,
null
),
getFieldsError
(
exception
));
return
new
ResponseEntity
<>(
error
,
HttpStatus
.
BAD_REQUEST
);
}