I need to perform a field validation (it can be one of values) if another field is present.
import javax.validation.*;
class Person {
#NotBlank
private String name;
private Long groupId;
#Valid // if group id is not null, select one from available.
private String specialization;
// getters, setters.
}
class PersonValidaionLogic {
#Autowired
private SpecializationService specializationService;
public void validatePerson(final Person person) {
Long groupId = person.getGroupId();
if (groupId != null) {
Set<String> availableSpecializations = specializationService.getByGroupId(groupId);
if (!availableSpecializations.contains(specialization)) {
addValidationError("specialization is not valid");
}
}
}
}
There is a nice answer on how to validate multiple fields in a class with conditions on each other.
How do I pass specializationService and groupId to the validator.
Feel free to share your solution or ideas! This is how I solved this problem.
I used the idea from the link in my question, but in much easier way.
First, I solved a problem how to pass a Spring component or service into validator. I used a component which holds a static reference to the service.
Second, I validated the whole object as described in the link.
Here is the code!
1) Create annotation #PersonConstraint and put in on Person class.
This may help https://www.baeldung.com/javax-validation-method-constraints
#Target({ TYPE })
#Retention(RUNTIME)
#Constraint(validatedBy = PersonValidator.class)
public #interface PersonConstraint {
String message() default "Specialization is not valid";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
CaseMode value();
}
2) Component which holds static reference to the service.
#Component // Spring component.
class ServiceHolderComponent {
private static SpecializationService SPECIALIZATION_SERVICE;
#Autowired
public ServiceHolderComponent(final SpecializationService specializationService) {
GROUP_SERVICE = Validate.notNull(groupService); //apache lib
}
public static SpecializationService getSpecializationService() {
return SPECIALIZATION_SERVICE;
}
}
3) And person validator
public class PersonValidator implements ConstraintValidator<PersonConstraint, Person> {
private final SpecializationService specializationService;
public UserDynamicEnumValidator() {
this(ServiceHolderComponent.getSpecializationService());
}
public UserDynamicEnumValidator(final SpecializationService specializationService) {
this.specializationService = specializationService;
}
#Override
public boolean isValid(final Person person, final ConstraintValidatorContext context) {
final Long groupId = person.getGroupId();
if (groupId == null) {
return true; // We consider it valid.
}
final String specialization = person.getSpecializat();
if (StringUtils.isEmpty(specialization)) {
return true; // We consider it valid.
}
// I changed the code of the service, so it returns a set of strings - projection query and collectors to set.
final Set<String> availableSpecializationValuesByGroup = specializationService.findByValue(groupId);
if (!availableSpecializationValuesByGroup.contains(specialization)) {
// To display custom message
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("Specialization is not valid").addConstraintViolation();
return false;
}
return true;
}
}
To display a custom message in validator check this
Related
I have:
Generic interface with method isValid (T obj).
Driver class with parameters such as age, experience, license etc. VaLidator for
Driver Class which checks if Driver is Valid.
So I need a validator for the Driver class.
public class Driver <T>{
private String name;
private String surname;
private String secondname;
private int dateofbirth;
private T pass;
private T dateofissue;
public int getDateofbirth() {
return dateofbirth;
}
public T getPass() {
return pass;
}
public T getDateofissue() {
return dateofissue;
}
}
public interface Validator <T> {
boolean isValid (T obj);
}
class DriverValidator<T> implements Validator {
I do not know how I should do it. For any explanation will be very grateful
As mentioned in comments above it's not easy to help in this case, because the kind of validation is not known, also it's a little bit odd when seeing generic type is used 2 times by different getters. Also the dateOfBirth variable should be a long, a Date or a LocalDate.
But anyway, here is an example of an implementation of a validator to explain the technical concept (of course you have to add more checks, but the idea should be clear) :
public class DriverValidator<T> implements Validator<Driver<T>> {
#Override
public boolean isValid(Driver<T> driver) {
if (driver==null) {
return false;
}
if (driver.getPass() == null) {
/* e.g. when this field may not be null...*/
return false;
}
// ... do more checks - e.g. check old enough etc.
return true;
}
}
I have some validations in my "command", parameter of the controller that are executed within the set of each attribute, the problem is that the attributes not informed, jackson does not invoke the set method to do the validation. Is it possible to force Jackson to invoke the Set method even when the attribute is missing?
For exemple Payload without agency field:
{
"bank": "237",
"account": "20772-1",
"taxId": "36456155800",
"paidAmount": 30.00
}
My Controller:
public Return confirmTransfer(#RequestBody RechargeTransferConfirmationCommand command) {
System.out.println(command);
}
Class Java:
public class RechargeTransferConfirmationCommand {
public static final String ERR_INVALID_AGENCY = "Agency number can not be null.";
private String bank;
private String account;
private String agency;
private String taxId;
public RechargeTransferConfirmationCommand(BigDecimal paidAmount, String bank, String account,
String agency, String taxId) {
setPaidAmount(paidAmount);
setBank(bank);
setAccount(account);
setAgency(agency);
setTaxId(taxId);
}
public void setRechargeId(RechargeId rechargeId) {
assertArgumentNotNull(rechargeId, Recharge.ERR_RECHARGE_ID_INVALID);
this.rechargeId = rechargeId;
}
private void setPaidAmount(BigDecimal paidAmount) {
if (paidAmount == null || paidAmount.compareTo(BigDecimal.ZERO) < 0)
throw new IllegalArgumentException(Recharge.ERR_INVALID_AMOUNT);
this.paidAmount = paidAmount;
}
private void setBank(String bank) {
assertArgumentNotEmpty(bank, TransferInformation.ERR_INVALID_BANK_NUMBER);
this.bank = bank;
}
private void setAccount(String account) {
assertArgumentNotEmpty(account, TransferInformation.ERR_INVALID_ACCOUNT);
this.account = account;
}
private void setAgency(String agency) {
assertArgumentNotEmpty(agency, ERR_INVALID_AGENCY);
this.agency = agency;
}
private void setTaxId(String taxId) {
assertArgumentNotEmpty(taxId, ERR_INVALID_TAX_ID);
this.taxId = taxId;
}
}
In this case, for each field the set method is invoked to do the validation, except the agency field that was not informed in the payload, it should soon throw the exception contained in the method assertArgumentNotEmpty.
yes jackson will not invoke the setter methods of fields that are not passed in payload, if you want to validate missing fields you need custom Deserializer
class RechargeTransferConfirmationCommandDeserializer extends JsonDeserializer<RechargeTransferConfirmationCommand> {
#Override
public RechargeTransferConfirmationCommand deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
RechargeTransferConfirmationCommand responseModel = jp.readValueAs(RechargeTransferConfirmationCommand.class);
RechargeTransferConfirmationCommand finalModel = new RechargeTransferConfirmationCommand();
finalModel.set agency(responseModel. agency == null || age.trim().isEmpty() ? "agency" : responseModel.agency);
return finalModel;
}
}
And you model should be
#JsonDeserialize(using = DefaultModelDeserializer.class)
public class DefaultModel {
...
}
use #JsonSetter in method && declare no args constructor also:
public RechargeTransferConfirmationCommand(){
}
#JsonSetter
public void setRechargeId(RechargeId rechargeId) {
assertArgumentNotNull(rechargeId, Recharge.ERR_RECHARGE_ID_INVALID);
this.rechargeId = rechargeId;
}
But better you use javax validation. Look at this to get details.
Another way:
You can write all validtion in one method and use #AssertTrue:
#AssertTrue
public boolean isValid(){
//if all field is valid then return true;
return false;
}
problem solved using #JsonCreator, thank you all for your help
#JsonCreator
public RechargeTransferConfirmationCommand(#JsonProperty("paidAmount") BigDecimal paidAmount,
#JsonProperty("bank") String bank, #JsonProperty("account") String account,
#JsonProperty("agency") String agency, #JsonProperty("taxId") String taxId) {
setPaidAmount(paidAmount);
setBank(bank);
setAccount(account);
setAgency(agency);
setTaxId(taxId);
}
We have a Stored Procedure in MS SQL that returns some result set which doesn't match any DB table. And we want to map the result to plain java POJO without using #Entity annotation. But if we call
entityManager.createStoredProcedureQuery("schema.myProcedure",
PlainPojo.class);
it will fail for org.hibernate.MappingException: Unknown entity exception
I know there is #SqlResultSetMapping annotation but it needs to be placed on Entity class ? and this doesn't work
#Repository
#SqlResultSetMapping(name = "Mapping")
public class MyRepositoryImpl implements MyRepository
only this
#Enity
#SqlResultSetMapping(name = "Mapping")
public class MyEntity
but we don't want entity class for this... so place it on another entity ?? (no really)
is it possible to place it somewhere else ?
or is it possible do it in code ?
another solution ?
Is it possible to make something like transient Entity ? -> use #Entity annotation without creating DB table etc.
I didn't find a complex solution on stackoverflow and reinvent the wheel maybe this will help for some one, who need to call stored procedures many times in project.
The main idea is write custom annotation similar #StoredProcedure, but independent of #Entity
#Target(TYPE)
#Retention(RUNTIME)
public #interface CustomNamedStoredProcedureQueries {
/**
* Array of CustomNamedStoredProcedureQuery annotations.
*/
CustomNamedStoredProcedureQuery[] value();
}
#Target(TYPE)
#Retention(RUNTIME)
public #interface CustomNamedStoredProcedureQuery {
/*
* The name of the stored procedure for call.
*/
String name();
/*
* The name of the stored procedure in the database.
*/
String procedureName();
/*
* The scheme name of the database.
*/
String schemeName();
/*
* The name of the package that contains stored procedure in the database.
*/
String packageName();
/*
* Information about all parameters of the stored procedure.
*/
CustomStoredProcedureParameter[] parameters() default {};
/*
* The names of one or more result set mappings, as defined in metadata, when you need custom mapping.
*/
ProcedureRowMapper[] resultSetRowMappers() default {};
/*
* The names of one or more result set mappings, as defined in metadata, when you need 1 to 1 mapping.
*/
ProcedureResultClass[] resultSetMappers() default {};
}
#Target({})
#Retention(RUNTIME)
public #interface CustomStoredProcedureParameter {
/** Name of strored procedure parameter.*/
String name() default "";
/** JDBC type of the parameter.*/
int type();
/** Parameter mode.*/
ParameterMode mode() default ParameterMode.IN;
}
#Target({})
#Retention(RUNTIME)
public #interface ProcedureResultClass {
/* Name of procedure parameter*/
String name();
/* Class of object using for mapping */
Class<?> resultClass();
}
#Target({})
#Retention(RUNTIME)
public #interface ProcedureRowMapper {
/* Name of cursor field for use mapper*/
String name();
/* Class of object using for mapping, must extend BeanPropertyRowMapper */
Class<? extends BeanPropertyRowMapper> mapper();
}
As mentioned before use ProcedureResultClass when you need to map resultSet 1 to 1, fields must have the same name.
Use ProcedureRowMapper when you need some custom mapping, you can #Override mapRow() method, and map it manually, or you can #Override initBeanWrapper() if you just need to convert some fields in specific way.
So, next we need a method that makes it work. Put it in init method of your application, like #PostConstruct of main method.
ProjectUtils {
private JdbcTemplate jdbcTemplate;
#Autowired
public ProjectUtils(DataSource dataSource) {
jdbcTemplate = new JdbcTemplate(DataSource);
}
private static Map<String, SimpleJdbcCall> storedProcedureContainer = new HashMap<>();
public void initStoredProcedure() {
Reflections reflections = new Reflections("my.package");
Set<Class<?>> classList = reflections.getTypesAnnotatedWith(CustomNamedStoredProcedureQueries.class);
for (Class<?> clazz : classList) {
CustomNamedStoredProcedureQueries queriesAnnotation = clazz.getAnnotation(CustomNamedStoredProcedureQueries.class);
for (CustomNamedStoredProcedureQuery queryAnnotation : queriesAnnotation.value()) {
SimpleJdbcCall jdbcCall = new SimpleJdbcCall(jdbcTemplate)
.withSchemaName(queryAnnotation.schemeName())
.withCatalogName(queryAnnotation.packageName())
.withProcedureName(queryAnnotation.procedureName());
Set<SqlParameter> parameters = this.getDeclaredParameters(queryAnnotation.parameters());
jdbcCall.declareParameters(parameters.toArray(new SqlParameter[0]));
Map<String, RowMapper<?>> rowMappers = getDeclaredRowMappers(queryAnnotation.resultSetRowMappers(), queryAnnotation.resultSetMappers());
rowMappers.forEach((parameterName, rowMapper) -> jdbcCall.addDeclaredRowMapper(parameterName, rowMapper));
jdbcCall.compile();
storedProcedureContainer.put(queryAnnotation.name(), jdbcCall);
}
}
}
private Set<SqlParameter> getDeclaredParameters(CustomStoredProcedureParameter[] procedureParameters) {
Set<SqlParameter> parameters = new HashSet<>();
for (CustomStoredProcedureParameter procedureParameter : procedureParameters) {
if (procedureParameter.mode().equals(ParameterMode.IN)) {
parameters.add(new SqlParameter(procedureParameter.name(), procedureParameter.type()));
}
if (procedureParameter.mode().equals(ParameterMode.OUT)) {
parameters.add(new SqlOutParameter(procedureParameter.name(), procedureParameter.type()));
}
}
return parameters;
}
private Map<String, RowMapper<?>> getDeclaredRowMappers(ProcedureRowMapper[] resultSetMappers, ProcedureResultClass[] simpleResultSetMappers) {
Map<String, RowMapper<?>> mappers = new HashMap<>();
if (!ArrayUtils.isEmpty(simpleResultSetMappers)) {
for (ProcedureResultClass procedureResultClass : simpleResultSetMappers) {
mappers.put(procedureResultClass.name(), BeanPropertyRowMapper.newInstance(procedureResultClass.resultClass()));
}
return mappers;
}
try {
for (ProcedureRowMapper resultSetMapper : resultSetMappers) {
mappers.put(resultSetMapper.name(), resultSetMapper.mapper().newInstance());
}
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
return mappers;
}
public SimpleJdbcCall getProcedure(String name) {
return storedProcedureContainer.get(name);
}
}
Supposed to that #CustomNamedStoredProcedureQueries must be declared, even when stored Procedure only one, you can change this yourself if want.
You can use either ProcedureResultClass or ProcedureRowMapper, not both, it can changed to.
Examples (I use oracledb, but with minor change it can be used for another db)
#CustomNamedStoredProcedureQueries({
#CustomNamedStoredProcedureQuery(
name = "getFoo",
schemeName = "FooScheme",
packageName = "FooPackage",
procedureName = "get_Foo",
resultSetMappers = #ProcedureResultClass(name = "foo_cursor", resultClass = Foo.class),
parameters = {
#CustomStoredProcedureParameter(name = "foo_id", type = OracleTypes.NUMBER, mode = ParameterMode.IN),
#CustomStoredProcedureParameter(name = "error_code", type = OracleTypes.NUMBER, mode = ParameterMode.OUT),
#CustomStoredProcedureParameter(name = "foo_cursor", type = OracleTypes.CURSOR, mode = ParameterMode.OUT),
}
),
#CustomNamedStoredProcedureQuery(
name = "deleteFoo",
schemeName = "FooScheme",
packageName = "FooPackage",
procedureName = "delete_Foo",
parameters = {
#CustomStoredProcedureParameter(name = "foo_id", type = OracleTypes.NUMBER, mode = ParameterMode.IN),
#CustomStoredProcedureParameter(name = "error_code", type = OracleTypes.NUMBER, mode = ParameterMode.OUT),
}
)
})
public class Foo {
long id;
String name;
int age;
public void setId(long id) {
this.id= id;
}
public long getId() {
return this.id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
}
Call it:
public Foo getFoo(Long fooId) {
SqlParameterSource sqlParams = new MapSqlParameterSource()
.addValue("foo_id", fooId);
SimpleJdbcCall procedure = projectUtils.getProcedure("getFoo");
Map<String, Object> result = procedure.execute(sqlParams);
return ((List<Foo>) result.get("foo_cursor")).get(0);
}
Tip - It can be used with custom type(types that stored in bd), like Array, for that you need create custom type extended of AbstractSqlTypeValue and override createTypeValue(), then pass it like others parameters (carefully tested only on oracle).
CustomType must be declared on scheme level and higher, on package level it not working, scheme and type must be call in upper case.
#Override
protected Object createTypeValue(Connection connection, int sqlType, String typeName) throws SQLException {
if (connection.isWrapperFor(OracleConnection.class)) {
OracleConnection oracleConnection = connection.unwrap(OracleConnection.class);
return oracleConnection.createARRAY("FOOSCHEME.NUMBERARRAY", this.array); // array pass when customType class init
}
return connection.createArrayOf("FOOSCHEME.NUMBERARRAY", this.array);
}
I'm building a Rest WS and to validate the request elements I'm using a JSR-303 BeanValidation, but there's a field type Enum.
EmploymentType.java
public enum EmploymentType {
EMPTY, FULL, PARTTIME, CONTRACT, CASUAL;
public static EmploymentType getDefaultEnum() {
return EMPTY;
}
}
and the class I'm using use to implement this:
Employment.java
public class Employment implements Serializable{
private static final long serialVersionUID = 1L;
#NotNull(message="employmentType does not accept null values")
private EmploymentType employmentType;
#Valid
#NotNull(message="orgData does not accept null values")
private OrgData orgData;
public Employment() {
employmentType = EmploymentType.getDefaultEnum();
orgData = new OrgData();
}
public EmploymentType getEmploymentType() {
return employmentType;
}
public void setEmploymentType(EmploymentType employmentType) {
this.employmentType = employmentType;
}
public OrgData getOrgData() {
return orgData;
}
public void setOrgData(OrgData orgData) {
this.orgData = orgData;
}
}
the implementation I developed only prevents the enum being a null object, is there a way to validate that the value of the enum is only within the range of declared values? (EMPTY, FULL, PARTTIME, CONTRACT, CASUAL)
I believe you have to do the validation of the valueOf OR the name of the enum
Here is the excerpt what will make the validation happen
public class Employment implements Serializable {
#NotNull(message = "employmentType does not accept null values")
#Valid
private EmploymentType employmentType;
public EmploymentType getEmploymentType() {
getEmploymentTypeOfEnum();
return employmentType;
}
#Pattern(regexp = "EMPTY|FULL")
private String getEmploymentTypeOfEnum(){ // you don't need it to be public
return employmentType.name();
}
}
I have a couple of objects from which selected members should be combined to create an output object. All these are POJOs. I am seeing that all object mappers work on a single POJO to another POJO level. Is there any mapper that supports what I am looking for? Of course, I understand that there is some mapping stuff that I need to specify.
Edit:
I know how to get this done by writings own Java class. I am just looking for a way to do it with one of the mapping libraries.
You aren't limited in what you require to be passed to your mapper. You can define it to accept several items and build the object based on the multiple inputs. Here is an example:
public class ClassOne {
private final String someProperty;
public ClassOne(String someProperty) {
this.someProperty = someProperty;
}
public String getSomeProperty() {
return someProperty;
}
}
public class ClassTwo {
private final String someOtherProperty;
public ClassTwo(String someOtherProperty) {
this.someOtherProperty = someOtherProperty;
}
public String getSomeOtherProperty() {
return someOtherProperty;
}
}
public class CombinedClass {
public static CombinedClass mapper(ClassOne one, ClassTwo two){
return new CombinedClass(one.getSomeProperty(), two.getSomeOtherProperty());
}
private final String someProperty;
private final String someOtherProperty;
private CombinedClass(String someProperty, String someOtherProperty) {
this.someProperty = someProperty;
this.someOtherProperty = someOtherProperty;
}
public String getSomeProperty() {
return someProperty;
}
public String getSomeOtherProperty() {
return someOtherProperty;
}
}