I have a question about the javax validation api, specifically, is it possible to generate a validation message of the following format.
For example, there are classes with a nested structure: Model, Attribute, and Value:
public class Model {
#NotBlank
private String id;
#NotBlank
private String name;
#Size(min = 1)
private List<Attribute> attributes;
}
public class Attribute {
#NotBlank
private String id;
#NotBlank
private String name;
#Size(min = 1)
private List<Value> values;
}
public class Value {
#NotBlank
private String id;
#NotBlank
private String name;
}
After calling the validation of the model object:
validator.validate(modelObject)
i want to generate validation errors that would contain the object IDs of each of the levels, for example:
M[model_id] A[attribute_id] V [value_id] Value error message
M[model_id] A[attribute_id] Open attribute error message
M[model_id] A[attribute_id] Private attribute error message
M[model_id] A[attribute_id] Attribute error message
M[model_id] Model error message
Is it possible to do something similar?
For business users, this is more readable compared to the default output of the path ' attributes[0].values[0].names'.
I will be grateful for any help!
Note: i found it in the org.hibernate class.validator.internal.engine.validationcontext. Abstract ValidationContext the processedPathsPerBean field that stores the Path and Bean mapping, but this context is not accessible from the outside ((
I found a solution using the "Extensions of the Path"
public String createErrorMessageFrom(ConstraintViolation<E> violation) {
Path path = violation.getPropertyPath();
StringBuilder targetErrorMessage = new StringBuilder();
String fieldWithInvalidValue = null;
if (isRoot(path)) {
fieldWithInvalidValue = path.toString();
} else {
for (Iterator<Path.Node> iterator = violation.getPropertyPath().iterator(); iterator.hasNext(); ) {
Path.Node nextPathNode = iterator.next();
fieldWithInvalidValue = nextPathNode.toString();
if (iterator.hasNext()) {
NamedEntityDto dto = (NamedEntityDto) nextPathNode.as(PropertyNode.class).getValue();
targetErrorMessage
.append(getDtoPrefixForMessage(dto));
}
}
}
targetErrorMessage.append(String.format("The field '%s' %s", fieldWithInvalidValue, violation.getMessage()));
return targetErrorMessage.toString();
}
I got a webservice that consumes the following structure:
#ApiModel(discriminator = "status", subTypes = {SuccessPolicyUpdate.class, FailurePolicyUpdate.class})
public abstract class AbstractPolicyUpdate {
private String clientId;
private PolicyStatus status;
}
this is being inherited by the followign twso subclasses
#ApiModel(parent = AbstractPolicyUpdate.class)
public class FailurePolicyUpdate extends AbstractPolicyUpdate {
private FailureCode code;
}
#ApiModel(parent = AbstractPolicyUpdate.class)
public class SuccessPolicyUpdate extends AbstractPolicyUpdate {
private String policyNumber;
private List<Inconsistency> inconsistencies = new ArrayList<>();
}
And inconsistency is just a pojo:
public class Inconsistency {
private InconsistencyCode code;
private String oldValue;
private String newValue;
}
this generates the following json:
{"swagger":"2.0","info":{"description":"Api Documentation","version":"1.0","title":"Api Documentation","termsOfService":"urn:tos","contact":{},"license":{"name":"Apache 2.0","url":"http://www.apache.org/licenses/LICENSE-2.0"}},"host":"localhost:8080","basePath":"/","tags":[{"name":"digital-vapz-controller","description":"Digital Vapz Controller"}],"paths":{"/api/policy":{"post":{"tags":["digital-vapz-controller"],"summary":"Update policy status","operationId":"updatePolicyStatusUsingPOST","consumes":["application/json"],"produces":["application/json"],"parameters":[{"name":"Authorization","in":"header","description":"Authorization","required":true,"type":"string"},{"in":"body","name":"policyUpdate","description":"policyUpdate","required":true,"schema":{"$ref":"#/definitions/AbstractPolicyUpdate"}}],"responses":{"200":{"description":"Request succesfully processed"},"201":{"description":"Created"},"400":{"description":"Invalid input parameters for this request","schema":{"$ref":"#/definitions/ErrorResponse"}},"401":{"description":"Invalid authorization for this request"},"403":{"description":"Forbidden"},"404":{"description":"Not Found"},"500":{"description":"Internal server error","schema":{"$ref":"#/definitions/ErrorResponse"}}},"deprecated":false}}},"definitions":{"AbstractPolicyUpdate":{"type":"object","discriminator":"status","properties":{"clientId":{"type":"string"},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"AbstractPolicyUpdate"},"ErrorResponse":{"type":"object","properties":{"code":{"type":"string"},"errors":{"type":"array","items":{"type":"string"}}},"title":"ErrorResponse"},"FailurePolicyUpdate":{"title":"FailurePolicyUpdate","allOf":[{"$ref":"#/definitions/AbstractPolicyUpdate"},{"type":"object","properties":{"clientId":{"type":"string"},"code":{"type":"string","enum":["CLIENT_ONBOARDING_KNOWN","CLIENT_RSB_CODE","CLIENT_VAPZ_EXISTS","RETRIEVING_PRODUCT","RETRIEVING_PTC","NEEDS_ANALYSIS","CREATING_POLICY","UPDATE_PTC"]},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"FailurePolicyUpdate"}]},"SuccessPolicyUpdate":{"title":"SuccessPolicyUpdate","allOf":[{"$ref":"#/definitions/AbstractPolicyUpdate"},{"type":"object","properties":{"clientId":{"type":"string"},"inconsistencies":{"type":"array","items":{"$ref":"#/definitions/Inconsistency"}},"policyNumber":{"type":"string"},"status":{"type":"string","enum":["SUCCESS","FAILURE"]}},"title":"SuccessPolicyUpdate"}]}}}
There is a ref to Inconsistency, but the model itself is not in the generated json. What am I forgetting?
I am using io.springfox libs, version 2.9.2
I tried to work with immutable objects in MongoDB and Lombok. I found a solution to my problem but it needs to write additional code from docs but I need to used Bson annotations and create a constructor where describes fields via annotations. But if I user #AllArgsConstructor catch exception: "Cannot find a public constructor for 'User'" because I can't use default constructor with final fields. I think i can customize CodecRegistry correctly and the example will work correctly but I couldn't find solution for it in docs and google and Stackoverflow.
Is there a way to solve this problem?
#Data
#Builder(builderClassName = "Builder")
#Value
#BsonDiscriminator
public class User {
private final ObjectId id;
private final String name;
private final String pass;
private final String login;
private final Role role;
#BsonCreator
public User(#BsonProperty("id") final ObjectId id,
#BsonProperty("name") final String name,
#BsonProperty("pass") final String pass,
#BsonProperty("login") final String login,
#BsonProperty("role") final Role role) {
this.id = id;
this.name = name;
this.pass = pass;
this.login = login;
this.role = role;
}
#AllArgsConstructor
public enum Role {
USER("USER"),
ADMIN("ADMIN"),
GUEST("GUEST");
#Getter
private String value;
}
public static class Builder {
}
}
Example for MongoDB where I create, save and then update users:
public class ExampleMongoDB {
public static void main(String[] args) {
final MongoClient mongoClient = MongoClients.create();
final MongoDatabase database = mongoClient.getDatabase("db");
database.drop();
final CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
final MongoCollection<User> users = database.getCollection("users", User.class).withCodecRegistry(pojoCodecRegistry);
users.insertMany(new ExampleMongoDB().getRandomUsers());
System.out.println("Before updating:");
users.find(new Document("role", "ADMIN")).iterator().forEachRemaining(
System.out::println
);
System.out.println("After updating:");
users.updateMany(eq("role", "ADMIN"), set("role", "GUEST"));
users.find(new Document("role", "GUEST")).iterator().forEachRemaining(
System.out::println
);
}
public List<User> getRandomUsers() {
final ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 15; i++) {
users.add(
User.builder()
.login("log" + i)
.name("name" + i)
.pass("pass" + i)
.role(
(i % 2 == 0) ? User.Role.ADMIN : User.Role.USER
).build()
);
}
return users;
}
}
This should work (it worked for me):
#Builder(builderClassName = "Builder")
#Value
#AllArgsConstructor(onConstructor = #__(#BsonCreator))
#BsonDiscriminator
public class User {
#BsonId
private final ObjectId _id;
#BsonProperty("name")
private final String name;
#BsonProperty("pass")
private final String pass;
#BsonProperty("login")
private final String login;
#BsonProperty("role")
private final Role role;
}
Then in lombok.config add these (in your module/project directory):
lombok.addLombokGeneratedAnnotation=true
lombok.anyConstructor.addConstructorProperties=true
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonProperty
lombok.copyableAnnotations += org.bson.codecs.pojo.annotations.BsonId
Also piece of advice, keep _id if you are going to use automatic conversion to POJOs using PojoCodec, which will save a lot of trouble.
I am trying to implement a simple service and use the HATEOAS resource from spring-boot in order to display a link. When the service run, it throws a WARN message in the console with the following:
javax.xml.bind.JAXBException: class com.in28minutes.rest.webservices.restfulwebservices.user.User nor any of its super class is known to this context
I am using JDK 11, which forced me to add the dependency, since I was getting a ClassNotFoundException:
"org.glassfish.jaxb:jaxb-runtime"
But after adding that dependency the spring Resource HATEOAS class is not able to be marshalled.
public class User {
private Integer id;
#Size(min=2, message="The name should have at least 2 characters")
private String name;
#Past
private LocalDate birthDate;
public User() {
}
public User(Integer id, String name, LocalDate birthDate) {
super();
this.id = id;
this.name = name;
this.birthDate = birthDate;
}
...
}
#GetMapping("/users/{id}")
public Resource<User> retrieveUser(#PathVariable("id") int theId) {
User aUserResult = service.findOne(theId);
if (aUserResult == null) {
throw new UserNotFoundException("id-" + theId);
}
Resource<User> aUserResource = new Resource<User>(aUserResult);
ControllerLinkBuilder aLinkTo = linkTo(methodOn(this.getClass()).retrieveAllUsers());
aUserResource.add(aLinkTo.withRel("all-users"));
return aUserResource;
}
strangely, this is related to browser. it should work if you call the endpoint using client like "curl" instead of browser.
workaround which helped for me - add:
, produces="application/json; charset=UTF-8"
to GetMapping()
more details at:
https://github.com/spring-guides/tut-rest/issues/64
I have a problem with resolving entities during entity manager startup.
Now it falls with following error:
Exception [EclipseLink-197] (Eclipse Persistence Services -
2.5.2.v20140319-9ad6abd): org.eclipse.persistence.exceptions.DescriptorException Exception
Description: The mapping [an_div] is not the appropriate type for this
descriptor Mapping:
org.eclipse.persistence.mappings.DirectToFieldMapping[an_div-->div]
Descriptor:
EISDescriptor(com.cloudyle.paasplus.api.fhir.model.dstu2.composite.NarrativeDt
--> [DatabaseTable(DATATYPE), DatabaseTable(NARRATIVEDT)])
The abstract class configuration:
#MappedSuperclass
#UuidGenerator(name = "UUID_GEN_SQL")
#NoSql(dataFormat = DataFormatType.MAPPED)
public abstract class AbstractBaseResource
{
private static final long serialVersionUID = -1212459053211153257L;
protected static final Logger logger = LoggerFactory.getLogger(AbstractBaseResource.class);
public static final String DELIMITER = ", ";
#Id
#GeneratedValue(generator = "UUID_GEN_SQL")
#Column(name = "_id")
private String id;
#Version
#Field(name = "object_version")
private Long objectVersion;
#Embedded
#Field(name = "text")
private NarrativeDt text;
// getter and setter
// ...
}
A simple entity class:
#Entity
#NoSql(dataFormat = DataFormatType.MAPPED)
public class Account extends AbstractBaseResource
{
#Field(name = "name")
private String an_name;
// other fields + getter and setter
// ...
}
The embbedable entity where the problem could be:
#Embeddable
#NoSql(dataFormat = DataFormatType.MAPPED)
#Customizer(DtChildCustomizer.class)
public class NarrativeDt extends Datatype
{
#Field(name = "status")
private String an_status;
#Field(name = "div")
private String an_div;
// getter and setter
// ...
}
The extended embbedable:
#Embeddable
#NoSql(dataFormat = DataFormatType.MAPPED)
#Customizer(DtParentCustomizer.class)
public abstract class Datatype implements IDatatype, Serializable
{
#Field(name = "element_specific_id")
private String an_elementSpecificId;
// getter and setter
// ...
}
The child customizer:
public class DtChildCustomizer implements Serializable, DescriptorCustomizer
{
#Override
public void customize(final ClassDescriptor descriptor)
{
descriptor.getInheritancePolicy().setParentClass(Datatype.class);
}
}
And the parent customizer:
public class DtParentCustomizer implements Serializable, DescriptorCustomizer
{
#Override
public void customize(final ClassDescriptor descriptor) throws Exception
{
descriptor.getInheritancePolicy().setSingleTableStrategy();
final DatabaseField indicatorField = new DatabaseField();
indicatorField.setName("classType");
indicatorField.setLength(255);
indicatorField.setType(java.lang.String.class);
descriptor.getInheritancePolicy().setClassIndicatorField(indicatorField);
descriptor.getInheritancePolicy().useClassNameAsIndicator();
}
}
I can not understand why eclispeLink have a problem to resolve simple mapping an_div->div.
All suggestion will be appreciated, I already spent too much time working on it. I can not see the mapping problem :(
Kind regards