Question:
I do processing and processing of documents. Several processors (zip, image processing) are hidden inside the processing, which do not have a common clear structure of arguments. An incoming request might look like this:
{
"sourceDocumentId": "b4e8d214-9473-4d41-83c9-06e5491c18d3",
"pipeline": [
{
"processor": "image",
"arguments": {
"pipeline": [
{
"processor": "resize",
"arguments": {
"width": 200,
"height": 150
}
},
{
"processor": "crop",
"arguments": {
"width": 100,
"height": 100,
"snap": "center"
}
}
]
}
},
{
"processor": "zip",
"arguments": {
"compression": "HIGH"
}
}
]
}
The whole thing needs to be validated (the existence of a processor with the specified name, support for mime-type throughout the entire pipeline – that the next processor accepts the output mime-type, the correctness of the arguments) in order to refuse the client before processing starts in case of providing incorrect options, and it is assumed that adding a new processor is simply adding a new interface implementation to the container. How can you organize such a validation based on the hibernate validator?
Answer:
As a result, the problem was solved in a roundabout (but, in my opinion, correct) way.
The above payload is parsed with jackson and I wrote a simple functionality to convert the above structure into concrete classes. In the application container, there are implementations of the DocumentProcessor
and ImageProcessor
(the former accept InputStream
and OutputStream
, the latter – BufferedImage
), which explicitly indicate their name and the object type of the received arguments. When parsing, the custom deserializer first looks through the unloading of these implementations available in the container, takes a specific processor from the unload, pulls up the required class of arguments from the processor, looks for a custom deserializer of this class, if it finds it, deserializes it with it, and if it doesn't find it, it uses ObjectMapper
for conversion arguments to the desired type. Thus, when deserializing the above payload, a processor named "image"
, the type of its arguments is obtained, the required deserializer is found, called, the deserializer receives the original Map as input, deserializes it into an intermediate List<{processor:string, arguments:map}>
object, for each processor
looks for the class of the argument container (if not, it uses the default map-based container), for each container it looks for a deserializer, and then finally starts the deserializer or uses the ObjectMapper
to deserialize.
The output system is very dumb and inflexible, but the result is a strict structure that is easy to validate with simple annotations. Jackson itself offers polymorphic deserialization , which greatly simplifies the work, but in this project it was decided to abandon it for some reason.
The question itself regarding map validation remains open.