@CompileStatic class MessageSourceResolvableHelper extends Object
Contains utility methods for creating message codes and resolving them via Spring's MessageSource.
The most significant part are highly specialized static utility methods for creating message codes. At this moment, methods in this class and properties fromorg.klokwrk.lib.hi.spring.context.MessageSourceResolvableSpecification
, are highly specialized to localize parts of JSON responses
whose structure is defined by ResponseFormatting*
interceptors from org.klokwrk.cargotracking.lib.web.spring.mvc
package. Currently, only JSON responses representing failures have
parts that need to be localized.
To better understand each utility method's purpose, it is useful to observe the complete message format of JSON responses representing failures. Here is an example for response roughly
corresponding to the failed validation:
{ "metaData": { "general": { "severity": "warning", "locale": "en_GB", "timestamp": "2020-04-26T09:41:04.917666Z" }, "http": { // Created only for JSON responses sent over HTTP channels "status": "400", "message": "Bad Request" }, "violation": { "code": "400", "message": "Request is not valid.", "logUuid": "123", // Created only for 'unknown' violations "type": "validation|domain|infrastructure_web|unknown", "validationReport": { // Created only for validation violations root: { type: "myRequest" message: "..." } constraintViolations: [ { type: "notNull|notBlank|...", scope: "property|object", path: "...", message: "...", invalidPropertyValue: "..." }, { type: "notNull|notBlank|...", scope: "property|object", path: "...", message: "...", invalidPropertyValue: "..." }, ... ] } } }, "payload": {} // In case of failures, payload is empty. }There are several parts that need localization. For all failure types we have to localize
metaData.violation.message
. For validation failure types, localization is also needed for
metaData.violation.validationReport.root.message
and metaData.violation.validationReport.constraintViolations[].message
.
Therefore, in case of validation failure, message codes for metaData.violation.message
are created via makeMessageCodeListForViolationMessageOfValidationFailure()
method.
Similarly, if we have a domain failure, we will use makeMessageCodeListForViolationMessageOfDomainFailure()
method.
Following the same naming principles, in case of validation failures, message codes for metaData.violation.validationReport.root.message
will be created with
makeMessageCodeListForRootBeanMessageOfValidationFailure()
, while message codes for metaData.violation.validationReport.constraintViolations[].message
will be created with
makeMessageCodeListForConstraintViolationMessageOfValidationFailure()
.
Do note that the order of created message codes is significant and should go from the most specific message code first, then ending with more general elements. Spring's MessageSource
machinery will resolve messages trying codes in the given order, from first to last (the first match wins).
In the lack of a better place, here we are presenting an example of the successful message. Its parts do not require any localization.
{ "metaData": { "general": { "severity": "info", "locale": "en_GB", "timestamp": "2020-04-27T06:13:09.225Z", }, "http": { "status": "200", "message": "OK" } }, "payload": { ... } }
Constructor and description |
---|
MessageSourceResolvableHelper() |
Type Params | Return Type | Name and description |
---|---|---|
|
static List<String> |
makeMessageCodeListForConstraintViolationMessageOfValidationFailure(MessageSourceResolvableSpecification specification, String overridingResolvedDefaultMessage) For validation category of failures, creates a list of message codes for resolving metaData.violation.validationReport.constraintViolations[].message parts of JSON response.
|
|
static List<String> |
makeMessageCodeListForViolationMessageOfDomainFailure(MessageSourceResolvableSpecification specification) For domain category of failures, creates a list of message codes for resolving metaData.violation.message part of JSON response.
|
|
static List<String> |
makeMessageCodeListForViolationMessageOfInfrastructureWebFailure(MessageSourceResolvableSpecification specification) For infrastructure_web type of failures, creates a list of message codes for resolving metaData.violation.message part of JSON response.
|
|
static List<String> |
makeMessageCodeListForViolationMessageOfUnknownFailure(MessageSourceResolvableSpecification specification) For unknown category of failures, creates a list of message codes for resolving metaData.violation.message part of JSON response.
|
|
static List<String> |
makeMessageCodeListForViolationMessageOfValidationFailure(MessageSourceResolvableSpecification specification) For validation category of failures, creates a list of message codes for resolving metaData.violation.message part of JSON response.
|
|
static String |
resolveMessageCodeList(Locale locale, MessageSource messageSource, List<String> messageCodeList, List<String> messageParameters, String defaultMessage) Resolves a message for given messageCodeList against Spring's MessageSource. |
For validation
category of failures, creates a list of message codes for resolving metaData.violation.validationReport.constraintViolations[].message
parts of JSON response.
messageCategory
to failure
, messageType
to validation
, and
severity
to warning
. Significant and distinguishing MessageSourceResolvableSpecification properties are messageSubType
, constraintViolationPropertyPath
and constraintType
. This means that values of those properties (beside controllerSimpleName
and controllerMethodName
) are used for creating message code permutations
(ordered combinations).
When using a competent validation library, constraint violation messages on a property path level are usually localized already, based on validation constraint type. We need replacement of these
messages only if we have to have a more specific message than only constraint type can provide. For example, the validation library can provide a standard localized message for notBlank
constraint type. If we need a more specific message for somePropertyOfMine.notBlank
we have to override the originally provided message. For this purpose, we can use some of the message
codes generated by this method.
On the other hand, if we have originally provided message specific for a particular constraint type, we do not want to override it with a more general message, just saying that validation
failed, for example. Therefore, this method has overridingResolvedDefaultMessage
parameter allowing the caller to provide an already resolved message. The presence of this value will
prevent the usage of more general message codes.
To summarize, in the presence of overridingResolvedDefaultMessage
(which is a common case), we will generate only message codes that can be resolved to the more detailed message.
Example of message codes for messageSubType = requestDao, constraintViolationPropertyPath = somePath.message, constraintType = notNull
and non-empty
overridingResolvedDefaultMessage
(the common case):
"testController.testControllerMethod.failure.validation.requestDao.somePath.message.notNull", "testController.testControllerMethod.failure.validation.requestDao.notNull", "testController.testControllerMethod.failure.validation.notNull", "testControllerMethod.failure.validation.requestDao.somePath.message.notNull", "testControllerMethod.failure.validation.requestDao.notNull", "testControllerMethod.failure.validation.notNull" "default.failure.validation.requestDao.somePath.message.notNull", "default.failure.validation.requestDao.notNull"Example of message codes for
messageSubType = requestDao, constraintViolationPropertyPath = somePath.message, constraintType = notNull
and empty overridingResolvedDefaultMessage
(rare exceptional case):
"testController.testControllerMethod.failure.validation.requestDao.somePath.message.notNull", "testController.testControllerMethod.failure.validation.requestDao.notNull", "testController.testControllerMethod.failure.validation.notNull", "testControllerMethod.failure.validation.requestDao.somePath.message.notNull", "testControllerMethod.failure.validation.requestDao.notNull", "testControllerMethod.failure.validation.notNull" "default.failure.validation.requestDao.somePath.message.notNull", "default.failure.validation.requestDao.notNull" "default.failure.validation.notNull", "default.failure.validation", "default.failure.warning", "default.warning",
For domain
category of failures, creates a list of message codes for resolving metaData.violation.message
part of JSON response.
messageCategory
to failure
and messageType
to domain
. Significant and
distinguishing MessageSourceResolvableSpecification properties are severity
, messageSubType
and messageSubTypeDetails
. This means that values of those properties
(beside controllerSimpleName
and controllerMethodName
) are used for creating message code permutations (ordered combinations).
For domain failures, severity
might be error
or warning
, where warning
severity should be used in majority of cases. Domain warnings can indicate an invalid input
that clashes with business rules, although the request did pass semantic validation of input parameters. The clash can have various causes like inappropriate aggregate state or missing data in
related external registries. Since domain failures are always anticipated and controlled, error severity should be used rarely, if ever.
The messageSubType
categorizes domain failure, while messageSubTypeDetails
provides more details if needed. The messageSubTypeDetails
should be used only when
messageSubType
is pretty general and not specific enough by itself.
Say we have a request triggering command for accepting some kind of cargo. All request parameters are syntactically valid, but there is no route between provided locations, and consequently,
we cannot carry the cargo. In such a case, the command can raise a domain exception with destinationLocationCannotAcceptCargo
code. When it comes to resolving of message codes for
metaData.violation.message
, following codes will be generated.
Example of message codes for messageSubType = destinationLocationCannotAcceptCargo
.
"testController.testControllerMethod.failure.domain.warning.destinationLocationCannotAcceptCargo" "testController.testControllerMethod.failure.domain.destinationLocationCannotAcceptCargo" "testController.testControllerMethod.failure.domain.warning" "testControllerMethod.failure.domain.warning.destinationLocationCannotAcceptCargo" "testControllerMethod.failure.domain.destinationLocationCannotAcceptCargo" "testControllerMethod.failure.domain.warning" "default.failure.domain.warning.destinationLocationCannotAcceptCargo" "default.failure.domain.destinationLocationCannotAcceptCargo" "default.failure.domain.warning" "default.failure.domain" "default.failure.warning" "default.warning"In another example, we are issuing a query for the details about some cargo. Again, the given request contains all syntactically valid parameters, but our system cannot find the requested cargo. Our query reacts by raising a domain exception containing
notFound
code. If we need to be more specific, the query can provide additional details about what exactly cannot be found. In
our case, this will be bookingOfferSummary
.
Example of message codes for messageSubType = notFound, messageSubTypeDetails = bookingOfferSummary
:
"testController.testControllerMethod.failure.domain.warning.notFound.bookingOfferSummary" "testController.testControllerMethod.failure.domain.warning.notFound" "testController.testControllerMethod.failure.domain.notFound.bookingOfferSummary" "testController.testControllerMethod.failure.domain.notFound" "testController.testControllerMethod.failure.domain.warning" "testControllerMethod.failure.domain.warning.notFound.bookingOfferSummary" "testControllerMethod.failure.domain.warning.notFound" "testControllerMethod.failure.domain.notFound.bookingOfferSummary" "testControllerMethod.failure.domain.notFound" "testControllerMethod.failure.domain.warning" "default.failure.domain.warning.notFound.bookingOfferSummary" "default.failure.domain.warning.notFound" "default.failure.domain.notFound.bookingOfferSummary" "default.failure.domain.notFound" "default.failure.domain.warning" "default.failure.domain" "default.failure.warning" "default.warning"
For infrastructure_web
type of failures, creates a list of message codes for resolving metaData.violation.message
part of JSON response.
messageCategory
to failure
and messageType
to infrastructure_web
.
Significant and distinguishing MessageSourceResolvableSpecification properties are severity
and messageSubType
. This means that values of those properties (beside
controllerSimpleName
and controllerMethodName
) are used for creating message code permutations (ordered combinations).
Failure type infrastructure_web
represents failures handled by springMvc framework rather than by our infrastructure. In the future we might have additional infrastructure_*
types as more infrastructure is added (i.e., messaging).
Example of message codes for severity = error, messageSubType = missingPathVariableException
.
"testController.testControllerMethod.failure.infrastructure_web.error.missingPathVariableException" "testController.testControllerMethod.failure.infrastructure_web.missingPathVariableException" "testController.testControllerMethod.failure.infrastructure_web.error" "testControllerMethod.failure.infrastructure_web.error.missingPathVariableException" "testControllerMethod.failure.infrastructure_web.missingPathVariableException" "testControllerMethod.failure.infrastructure_web.error" "default.failure.infrastructure_web.error.missingPathVariableException" "default.failure.infrastructure_web.missingPathVariableException" "default.failure.infrastructure_web.error" "default.failure.infrastructure_web" "default.failure.error" "default.error"
For unknown
category of failures, creates a list of message codes for resolving metaData.violation.message
part of JSON response.
messageCategory
to failure
, messageType
to unknown
, and
severity
to error
. Significant and distinguishing MessageSourceResolvableSpecification property is messageSubType
. This means that
the value of that property (beside controllerSimpleName
and controllerMethodName
) is used for creating message code permutations (ordered combinations).
As unknown failures represent problems that are not anticipated, severity is always error
. Therefore, implementation fixes severity value to error
. Further,
messageSubType
usually corresponds to the uncapitalized simple class name of the corresponding exception.
Example of message codes for messageSubType = runtimeException
.
"testController.testControllerMethod.failure.unknown.runtimeException" "testController.testControllerMethod.failure.unknown" "testControllerMethod.failure.unknown.runtimeException" "testControllerMethod.failure.unknown" "default.failure.unknown.runtimeException" "default.failure.unknown" "default.failure.error" "default.error"
For validation
category of failures, creates a list of message codes for resolving metaData.violation.message
part of JSON response.
messageCategory
to failure
, messageType
to validation
, and
severity
to warning
. Significant and distinguishing MessageSourceResolvableSpecification property is only messageSubType
. This means that the value of that
property (beside controllerSimpleName
and controllerMethodName
) is used for creating message code permutations (ordered combinations).
Further details about validation failure are given in other parts of JSON response, namely metaData.violation.validationReport.constraintViolations[].message
. These parts are localized
with the help of other methods of this class.
Example of message codes for messageSubType = requestDao
:
"testController.testControllerMethod.failure.validation.requestDao", "testControllerMethod.failure.validation.requestDao", "default.failure.validation.requestDao", "default.failure.validation", "default.failure.warning", "default.warning"
Resolves a message for given messageCodeList against Spring's MessageSource.