Want to reduce code from these validations, these validators' classes verify and return if inputs are valid or invalid, it's a reduction, I will validate some panels and almost 40 fields. Want to see if there is some pattern to simplify this, code is more than 300 lines which I believe to be a bad practice.
package Validation1;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MinimalReproducibleExampleValidation {
public static void main(String[] args) {
boolean saveToDatabase = true;
String name = "Richard";
String idCard = "123456789";
String address = "Main Street 454";
Entity entity = new Entity ();
/// Name Validation
if (saveToDatabase) {
ValidationEntity nameValidation = new
ValidationEntity(ValidationEntity.Regex.Alphabetic,
name, "ID Card", 0, 13);
saveToDatabase = nameValidation.isValid();
entity.setName(name);
}
/// ID Card Validation
if (saveToDatabase) {
ValidationEntity idCardValidator = new
ValidationEntity(ValidationEntity.Regex.Numerical,
idCard, "ID Card", 0, 13);
saveToDatabase = idCardValidator.isValid();
entity.setIdCard(idCard);
}
/// EMail Validation
if (saveToDatabase) {
ValidationEntity emailValidator = new
ValidationEntity(ValidationEntity.Regex.AlphaNumerical,
address, "Address", 1, 9);
saveToDatabase = emailValidator.isValid();
entity.setAddress(address);
}
// If every field is valid, save
if (saveToDatabase) {
new EntityDao().save(entity);
}
}
}
and:
class ValidationEntity {
private Regex regex;
private String input;
private String errorMessage;
private Integer minimum;
private Integer maximum;
public ValidationEntity(Regex regex, String input, String errorMessage, int minimum, int maximum) {
this.regex = regex;
this.input = input;
this.errorMessage = errorMessage;
this.minimum = minimum;
this.maximum = maximum;
}
public boolean isValid() {
Pattern pattern = Pattern.compile(getRegexFormat(), Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(input);
return matcher.matches();
}
public String getRegexFormat() {
return "^" + regex.getRegex() + "{" + minimum + "," + maximum + "}";
}
and:
public enum Regex {
LowercaseAlphabetic("[a-z]"), UppercaseAlphabetic("[A-Z]"), Numerical("[0-9]"), Alphabetic("[a-zA-Z]"),
AlphaNumerical("^[A-Za-z0-9_ ]*$");
public String regexValue;
Regex(String regexValue) {
this.regexValue = regexValue;
}
}
}
and:
class EntityDao {
public void save(Entity entity) {
System.out.println("Saving the model!");
}
}
and:
class Entity {
private String name;
private String idCard;
private String address;
public void setIdCard(String idCard) {
this.idCard = idCard;
}
public void setName(String name) {
this.name = name;
}
public void setAddress(String address) {
this.address = address;
}
public String getIdCard() {
return idCard;
}
public String getIdName() {
return name;
}
public String getAddress() {
return address;
}
}
We have a repetetive behaviour:
if (saveToDatabase) {
ValidationEntity nameValidation = new
ValidationEntity(ValidationEntity.Regex.Alphabetic,
name, "ID Card", 0, 13);
saveToDatabase = nameValidation.isValid();
entity.setName(name);
// create ValidationEntity object
// check whether entity is valid
// set some attribute value
}
I would suggest:
to collect all validation logic in one place
set some attribute values after validation is completed
and then save to database
By doing the above actions, we will separate our validation logic, variable assignments and saving database. So our code should comply with Single Responsibility principle of SOLID principles.
So let's put repetetive behaviour in some abstraction. I am sorry, I am not Java guy. Let me show via C#. But I've provided comments of how code could look in Java:
public abstract class FieldValidation
{
// I am not Java guy, but if I am not mistaken, in Java,
// if you do not want method to be overriden, you shoud use `final` keyword
public abstract bool IsValid();
}
And its concrete implementations:
public class IdCardFieldValidation : FieldValidation // extends in Java
{
public override bool IsValid() // #Override in Java
{
// your code here:
/*ValidationEntity nameValidation = new
ValidationEntity(ValidationEntity.Regex.Alphabetic,
name, "ID Card", 0, 13);
return nameValidation.isValid();*/
return true;
}
}
public class EmailFieldValidation : FieldValidation // extends in Java
{
public override bool IsValid() // #Override in Java
{
// your code here:
/*ValidationEntity emailValidator = new
ValidationEntity(ValidationEntity.Regex.AlphaNumerical,
address, "Address", 1, 9);
returnr emailValidator.isValid();*/
return true;
}
}
And then we can create a class which will have all validations. And this class will return whether all validations were okay:
public class AllFieldValidations
{
private List<FieldValidation> _fieldValidations = new List<FieldValidation>()
{
new IdCardFieldValidation(),
new EmailFieldValidation()
};
public bool IsValid()
{
foreach (var fieldValidation in _fieldValidations)
{
if (!fieldValidation.IsValid())
return false;
}
return true;
}
}
And then your method will look like this:
AllFieldValidations allFieldValidations = new AllFieldValidations();
bool isAllFieldsValid = allFieldValidations.IsValid();
if (!isAllFieldsValid)
return;
SetAttributes();
SaveToDatabase();
and implementations of other methods:
public void SetAttributes
{
entity.setName(name);
entity.setIdCard(idCard);
entity.setAddress(address);
}
public void SaveToDatabase()
{
new EntityDao().save(entity);
}
So here we've applied Single responsibility principle here of SOLID principles.
I mean we do not have almighty method that has all validations, setting attributes and saving to database.
Pros of this approach:
highly testable code
great separation of concerns
improved readability. We've created methods with self explanatory names
Related
I am using restTemplate to retrieve data from a url, and I get it as a List of Objects but I need a List of Strings to be able to filter it (I want to remove duplicates and change some attribute names).
This is my Template:
public static Provinces restTemplateProvince(RestTemplate restTemplate) {
String ProvinceCommunityURL = "https://www.el-tiempo.net/api/json/v2/provincias";
Provinces province = restTemplate.getForObject(ProvinceCommunityURL, Provinces.class);
return province;
}
Now I want to filter this data and show it in my own API. I'm able to show it with the following:
RestController
public class ShowcaseController {
#Autowired
ProvinceService provinceService;
#GetMapping("/provinces")
public Provinces getAllProvinces(){
return provinceService.getAllProvinces();
}
}
#Service
public class ProvinceService {
#Autowired
RestTemplate restTemplate;
public Provinces getAllProvinces(){
Provinces listOfProvinces = Templates.restTemplateProvince(searchList);
return listOfProvinces;
}
}
But I can't filter it in this list type.
How could I do it?
My Province class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class Provinces {
#JsonProperty("provincial")
private List<ProvincesData> provinces;
public Provinces(){}
public Provinces(List<ProvincesData> provinces) {
this.provinces = provinces;
}
#JsonProperty("provincial")
public List<ProvincesData> getprovinces() {
return provinces;
}
#JsonProperty("Test")
public void setprovinces(List<ProvincesData> provinces) {
this.provinces = provinces;
}
}
And ProvinceData class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class ProvincesData {
#JsonProperty("CODPROV")
private String codProv;
#JsonProperty("NOMBRE_PROVINCIA")
private String nomeProvincia;
#JsonProperty("CODAUTON")
private String codAuton;
#JsonProperty("COMUNIDAD_CIUDAD_AUTONOMA")
private String comunidadeCidadeAutonoma;
public ProvincesData(){
}
public ProvincesData(String codProv, String nomeProvincia, String codAuton, String comunidadeCidadeAutonoma){
this.codProv = codProv;
this.nomeProvincia = nomeProvincia;
this.codAuton = codAuton;
this.comunidadeCidadeAutonoma = comunidadeCidadeAutonoma;
}
#JsonProperty("CODPROV")
public String getCodProv() {
return codProv;
}
#JsonProperty("Test")
public void setCodProv(String codProv) {
this.codProv = codProv;
}
public String getNomeProvincia() {
return nomeProvincia;
}
public void setNomeProvincia(String nomeProvincia) {
this.nomeProvincia = nomeProvincia;
}
public String getCodAuton() {
return codAuton;
}
public void setCodAuton(String codAuton) {
this.codAuton = codAuton;
}
public String getComunidadeCidadeAutonoma() {
return comunidadeCidadeAutonoma;
}
public void setComunidadeCidadeAutonoma(String comunidadeCidadeAutonoma) {
this.comunidadeCidadeAutonoma = comunidadeCidadeAutonoma;
}
}
The filter to isolate the codAuton and comunidadeCidadeAutonoma columns without repeating. If possible, build a new list with only the data I want and change de variable name so that I can then show them in my API with different titles and such.
Regards.
Update your service to something like:
public static Provinces restTemplateProvince(RestTemplate restTemplate) {
String ProvinceCommunityURL = "https://www.el-tiempo.net/api/json/v2/provincias";
Provinces province = restTemplate.getForObject(ProvinceCommunityURL, Provinces.class);
List<String> included = new ArrayList<>();
List<ProvincesData> filtered = province.getprovinces()
.stream().filter(p -> {
if (included.contains(p.getCodAuton())) {
return false;
} else {
included.add(p.getCodAuton());
return true;
}
})
.collect(Collectors.toList());
province.setprovinces(filtered);
return province;
}
Could be done more efficiently but it is probably more readable like this.
I am using drools(kie-wb web interface) and I have a bpm process which takes a loanApplication and returns that loanApplication with updated data(is the goal). But when I try to do that I get returned.
"Unable to create response: [LoanApplicationReviewer.LoanApplicationReview:21 - Age Knockout:1] -- [LoanApplicationReviewer.LoanApplicationReview:21 - Age Knockout:1] -- null"
My data model:
public class LoanApplication implements java.io.Serializable {
static final long serialVersionUID = 1L;
private java.lang.Integer id;
private java.lang.Integer age;
private boolean accepted = true;
private java.util.List<java.lang.String> knockoutReasons = new java.util.ArrayList<java.lang.String>();
public LoanApplication() {
}
public java.lang.Integer getId() {
return this.id;
}
public void setId(java.lang.Integer id) {
this.id = id;
}
public java.lang.Integer getAge() {
return this.age;
}
public void setAge(java.lang.Integer age) {
this.age = age;
}
public boolean isAccepted() {
return this.accepted;
}
public void setAccepted(boolean accepted) {
this.accepted = accepted;
}
public java.util.List<java.lang.String> getKnockoutReasons() {
return this.knockoutReasons;
}
public void setKnockoutReasons(
java.util.List<java.lang.String> knockoutReasons) {
this.knockoutReasons = knockoutReasons;
}
public void addKnockoutReason(String knockoutReason) {
if (this.knockoutReasons == null) {
this.knockoutReasons = new java.util.ArrayList<java.lang.String>();
}
this.knockoutReasons.add(knockoutReason);
}
public String toString() {
return "loanApplicationResponse::[accepted=" + this.accepted
+ ",knockoutReasons="
+ this.knockoutReasons.toString() + "]";
}
public LoanApplication(java.lang.Integer id, java.lang.Integer age,
boolean accepted, java.util.List
knockoutReasons) {
this.id = id;
this.age = age;
this.accepted = accepted;
this.knockoutReasons = knockoutReasons;
}
}
and my rule is:
package com.xyz.loanapplicationreviewer;
import com.xyz.loanapplicationreviewer.LoanApplication;
import org.kie.api.runtime.process.WorkflowProcessInstance;
rule 'age less than 30 do not accept'
ruleflow-group 'ageKnockoutGroup'
dialect "mvel"
when
$process : WorkflowProcessInstance();
$loanApp : LoanApplication() from (LoanApplication)$process.getVariable("loanApplication");
eval ($loanApp.getAge() < 30);
then
$loanApp.setAccepted(false);
$loanApp.addKnockoutReason("age under 30");
((WorkflowProcessInstance)kcontext.getKnowledgeRuntime().getProcessInstance($process.getId())).setVariable("loanApplication", $loanApp);
System.out.println("Age less than 30 knockout");
end
I have added the an entry script on the business rule step to fill in the process instance like so:
kcontext.getKnowledgeRuntime().insert(kcontext.getProcessInstance());
I also have filled in what I expect to be my data assignments as well. It appears to get the data because when I create a new instance and run it from the form it has the data and executes the rule, just from the rest interface using swagger I get the above error.
After looking at this for much to long;
It seems I had to have modify my request slightly to become:
{
"loanApplication" : {
"com.xyz.abc.LoanApplication" : {
"id" : 1,
"age": 1,
"accepted" : true
}
}
}
Further I had to change the rule to look like:
rule 'age less than 30 do not accept'
ruleflow-group 'ageKnockoutGroup'
dialect "mvel"
when
loanApplication : LoanApplication(age < 30);
//process : WorkflowProcessInstance();
//loanApp : LoanApplication() from (LoanApplication)process.getVariable("loanApplication");
//eval (loanApp.getAge() < 30);
then
loanApplication.setAccepted(false);
loanApplication.addKnockoutReason("age under 30");
System.out.println("in then less than 30 with loanApp:" + loanApplication.toString());
String knockoutReasonFact = "age under 30";
boolean acceptedFact = false;
insert(knockoutReasonFact);
insert(acceptedFact);
update(loanApplication);
end
Using all the same endpoints I ultimately got the response back that I had expected. I just ended up tracing through how business central was calling into my drools/jbpm process and just mirrored that.
Before asking questions, I apologize for not being good at English.
I'm implementing a custom ConstraintValidator for cross-field validation as shown below.
Validation Target Class
public class ValidationTarget {
#Valid
private Inner inner;
#ValidDates(fromField = "from", toField = "to",
message = "{from} must not be later than {to}")
public class Inner {
private Date from;
private Date to;
}
// ...
}
CustomConstraintValidator
// imports ...
public class CustomConstraintValidator implements ConstraintValidator<ValidDates, Object> {
private String fromFieldName;
private String toFieldName;
private String message;
#Override
public void initialize(ValidDatesvalidationSpec) {
this.fromFieldName = validationSpec.fromField();
this.toFieldName = validationSpec.toField();
this.message = validationSpec.message();
}
#Override
public boolean isValid(Object target, ConstraintValidatorContext ctx) {
Date startDateObject = getFieldValue(target, fromFieldName);
Date endDateObject = getFieldValue(target, toFieldName);
if (start.after(end)) {
addConstraintViolation(toFieldName, message, ctx);
return false;
}
return true;
}
private void addConstraintViolation(String propertyName, String message, ConstraintValidatorContext ctx) {
ctx.buildConstraintViolationWithTemplate(message)
.addPropertyNode(propertyName)
.addConstraintViolation()
.disableDefaultConstraintViolation();
}
private Date getFieldValue(Object instance, String fieldName) {
Field field = ReflectionUtils.findField(instance.getClass(), fieldName);
ReflectionUtils.makeAccessible(field);
return (Date) ReflectionUtils.getField(field, instance);
}
}
In the above code, Inner class object is returned when calling getInvalidValue() of ConstraintViolation.
I want to change this value only to a specific field value rather than to the entire Inner class.
I have this constructor...
public ShiftLog(String companyName, boolean workedForAgent, String agentName,
Date shiftStart, Date shiftEnd,
boolean breakTaken, Date breakStart,
Date breakEnd, boolean isTransportJob,
String transportCompanyName, String vehicleRegistration) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.workedForAgent = workedForAgent;
this.agentName = agentName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
this.breakTaken = breakTaken;
this.breakStart = breakStart;
this.breakEnd = breakEnd;
this.isTransportJob = isTransportJob;
this.transportCompanyName = transportCompanyName;
this.vehicleRegistration = vehicleRegistration;
}
Now I want to add in a shift log (instantiate a shift log object for a user). The problem is that there are multiple combinations a shift log can have. For example, workedForAgent is false, there should be no need to pass in agentName. How can I do that without creating multiple constructors because there can be multiple possible combinations? For example, user can work for agent but not take a break, meaning break start time and end time shouldn't be needed to pass in. But that would require so many constructors for all possible combinations. Any alternative?
Also I am using the room database to append all this info. So if workedForAgent is false for example, automatically set agentName to null. How could that be done as well.
Take a look at Builder patterns.
Builder pattern is a creational design pattern it means its solves problem related to object creation.
It typically solve problem in object oriented programming i.e determining what constructor to use.
Adding to #Kodiak
You can replace your constructor with builder in few clicks
as mentioned here https://www.jetbrains.com/help/idea/replace-constructor-with-builder.html
Plus, the best part is,it will refactor all the occurrence of the constructor with builder automatically
Short Answer: Use Getters/Setters
Long Answer: The alternative method here is that you can instantiate the variables that you sure they must exist in the constructor and then the other conditional variables can be defined with setter methods and you can easily fetch with getters.
public class ShiftLog {
private Object userUid;
private String companyName;
private boolean workedForAgent;
private String agentName;
private Date shiftStart;
private Date shiftEnd;
private boolean breakTaken;
private Date breakStart;
private Date breakEnd;
private boolean isTransportJob;
private String transportCompanyName;
private String vehicleRegistration;
public ShiftLog(String companyName, Date shiftStart, Date shiftEnd) {
this.userUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
this.companyName = companyName;
this.shiftStart = shiftStart;
this.shiftEnd = shiftEnd;
}
public boolean isWorkedForAgent() {
return workedForAgent;
}
public void setWorkedForAgent(boolean workedForAgent) {
this.workedForAgent = workedForAgent;
}
public String getAgentName() {
return agentName;
}
public void setAgentName(String agentName) {
this.agentName = agentName;
}
public boolean isBreakTaken() {
return breakTaken;
}
public void setBreakTaken(boolean breakTaken) {
this.breakTaken = breakTaken;
}
public Date getBreakStart() {
return breakStart;
}
public void setBreakStart(Date breakStart) {
this.breakStart = breakStart;
}
public Date getBreakEnd() {
return breakEnd;
}
public void setBreakEnd(Date breakEnd) {
this.breakEnd = breakEnd;
}
public boolean isTransportJob() {
return isTransportJob;
}
public void setTransportJob(boolean isTransportJob) {
this.isTransportJob = isTransportJob;
}
public String getTransportCompanyName() {
return transportCompanyName;
}
public void setTransportCompanyName(String transportCompanyName) {
this.transportCompanyName = transportCompanyName;
}
public String getVehicleRegistration() {
return vehicleRegistration;
}
public void setVehicleRegistration(String vehicleRegistration) {
this.vehicleRegistration = vehicleRegistration;
}
}
Supposing the following class:
#Immutable
public final MyMessageClass {
private String message;
private Date dateLastChange;
private String identifier;
public MyClass(final String message){
this.message = message;
dataLastChange = new Date();
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public String messageValue(){
return message;
}
}
Once we have built an object of this class,
MyMessageClass myMessageObject = new MyMessageClass("Once upon a time ...");
and once we have finish doing some operations with it,
logger.info("The message is{}",myMessageObject.messageValue());
logger.info("The last change was done on {}",myMessageObject.lastChange());
it is expected to get an identifier from somewhere (a remote service, for instance) and attach it to the message. But, if I do something like this:
myMessageObject.setIdentifier(identifier);
I supposedly breaking the immutability capability of the object. If so, how is the best way to update the identifier field avoiding to do a new constructor call (so creating a new object)?
So the problem is just because you want to log some stuff first? Can't you do that after you've constructed the object?
Alternatively, you can use the builder pattern, with something like this. Note the final instance fields - instances of this class will be immutable even without the annotation.
#Immutable
public final MyMessageClass {
private final String message;
private final Date dateLastChange;
private final String identifier;
public MyClass(final MyMessageClass.Builder builder){
this.message = builder.message;
this.dataLastChange = builder.dataLastChange;
this.identifier = builder.identifier;
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public String messageValue(){
return message;
}
public String identifier(){
return identifier;
}
public static final class Builder {
private String message;
private final Date dateLastChange = new Date();
private String identifier;
public Builder message(final String message) {
this.message = message;
return this;
}
public String message() {
return message;
}
public Builder identifier(final String identifier) {
this.identifier = identifier;
return this;
}
public String identifier() {
return identifier;
}
public Date lastChange() {
return new Date(dateLastChange.getTime());
}
public MyMessageClass build() {
return new MyMessageClass(this);
}
}
}
You can then incrementally build the content of your object.
MyMessageClass.Builder builder = new MyMessageClass.Builder().message("Once upon a time ...");
logger.info("The message is{}", builder.message());
logger.info("The last change was done on {}",builder.lastChange());
String identifier = // get the identifier from somewhere
MyMessageClass message = builder.identifier(identifier).build();