How to define failed predicate in composite Guava predicate - java

I have list of "User" validations to be performed, each validation implements Predicate (from Guava). I have following method which fills the List of validations to be performed and creates composed Predicate of all validations.
List<Validation> validations = new ArrayList();
Predicate<User> composedUserValidations;
private void setupValidations() {
validations.add(userEmailIsValid());
validations.add(userPhoneIsValid());
validations.add(userLoginIsValid());
.....
//creating composite
composedUserValidations = and(validations);
}
Question: when I will trigger apply to composedUserValidations and one of them will fail, is there a way to find which one exactly is failing.
Now I have:
public boolean isUserValid(User user) {
if (!composedUserValidations.apply(user)) {
return false;
}
}
and I want to have something like:
public void validateUser(User user) {
if (!composedUserValidations.apply(ticket)) {
throw new ValidationExcepction("Predicate which failed");
}
}
What will be the best way to do it?

You could design a builder for your User class, let's name it UserBuilder.
For each of the User's properties (login, email, phone) in the builder class, you could not only provide the value you would like to set for the User instance, but also the Predicate, which you can immediately apply. If any of the predicates fail, you could easily throw an Exception and/or output a message, etc.
For all the instances you create, you could optionally pass different validation rules, or you could additionally design your builder to skip the validation for any of the User fields.
Example:
public class UserBuilder {
private String phone;
private String email;
//add more properties here;
public void phone(String phone, Predicate<String> phoneValidator) {
if (!phoneValidator.test(phone)) {
throw new UserBuilderException("Invalid phone provided");
}
this.phone = phone;
return this;
}
public void email(String email, Predicate<String> emailValidator) {
if (!emailValidator.test(email)) {
throw new UserBuilderException("Invalid email provided");
}
this.email = email;
return this;
}
//add more builder methods here
public User build() {
return new User(phone, email);
}
}
You could use that with:
Predicate<String> phoneValidator = ....
Predicate<String> emailValidator = ....
User user = new UserBuilder()
.phone("123567", phoneValidator)
.email("something#example.com", emailValidator)
.build();
What you get with that is:
Flexibility to provide custom validation rules (or omit some of them) for each User instance you have.
Better visibility about which properties fails their corresponding validation rules.
No User instances created with invalid content. A User instance will be created only for the property tuples, which pass all the Predicates provided in their builders.

Related

Mapping multiple DTO to entities - nested exception

I'm trying to map DTOs to entities. I created a service that only takes care of mapping objects - ObjectMapper. DTO objects have relationships with each other. When I map a single object, for example when I create User, Group, Note, everything works. But when I want to use a method that returns a Note with a specific ID - /notes/{id}, I get the following error.
Handler dispatch failed; nested exception is java.langStackOverflowError] with root cause
To get specific Note, I need to use this mapping method that also cause this error. As u can see, I have to also convert Group and Tags.
//Note
public NoteDTO NoteEntityToDtoGet(Note note) {
NoteDTO noteDTO = new NoteDTO();
noteDTO.setId(note.getId());
noteDTO.setTitle(note.getTitle());
noteDTO.setDescription(note.getDescription());
noteDTO.setGroup(GroupEntityToDtoGet(note.getGroup()));
noteDTO.setTags(TagConvertSet(note.getTags()));
return noteDTO;
}
When I don't have relationships defined as another DTO in the DTO class, but as an entity, everything works, since I don't have to convert the DTO to an entity.
Do you know where I'm making a mistake when mapping? Am I making a mistake in mapping multiple objects at once?
ObjectMapper
#Service
public class ObjectMapper {
//User
public UserDTO UserEntityToDtoGet(User user) {
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setName(user.getName());
userDTO.setEmail(user.getEmail());
userDTO.setGroup(user.getGroups());
return userDTO;
}
private UserCreationDTO UserEntityToDtoCreate(User user) {
UserCreationDTO userCreationDTO = new UserCreationDTO();
userCreationDTO.setName(user.getName());
userCreationDTO.setEmail(user.getEmail());
return userCreationDTO;
}
private User UserDtoToEntityCreate(UserCreationDTO userCreationDTO) {
User user = new User();
user.setName(userCreationDTO.getName());
user.setEmail(userCreationDTO.getEmail());
return user;
}
//Group
public GroupDTO GroupEntityToDtoGet(Group group) {
GroupDTO groupDTO = new GroupDTO();
groupDTO.setId(group.getId());
groupDTO.setName(group.getName());
groupDTO.setUser(UserEntityToDtoGet(group.getUser()));
groupDTO.setNotes(NoteConvertList(group.getNotes()));
groupDTO.setTags(TagConvertSet(group.getTags()));
return groupDTO;
}
public GroupCreationDTO GroupEntityToDtoCreate(Group group) {
GroupCreationDTO groupCreationDTO = new GroupCreationDTO();
groupCreationDTO.setName(group.getName());
groupCreationDTO.setUser(UserEntityToDtoGet(group.getUser()));
groupCreationDTO.setTags(TagConvertSet(group.getTags()));
return groupCreationDTO;
}
public Group GroupDtoToEntityCreate(GroupCreationDTO groupCreationDTO) {
Group group = new Group();
group.setName(groupCreationDTO.getName());
return group;
}
//Note
public NoteDTO NoteEntityToDtoGet(Note note) {
NoteDTO noteDTO = new NoteDTO();
noteDTO.setId(note.getId());
noteDTO.setTitle(note.getTitle());
noteDTO.setDescription(note.getDescription());
noteDTO.setGroup(GroupEntityToDtoGet(note.getGroup()));
noteDTO.setTags(TagConvertSet(note.getTags()));
return noteDTO;
}
public Note NoteDtoToEntityCreate(NoteCreationDTO noteCreationDTO) {
Note note = new Note();
note.setTitle(noteCreationDTO.getTitle());
note.setDescription(noteCreationDTO.getDescription());
return note;
}
public NoteCreationDTO NoteEntityToDtoCreate(Note note) {
NoteCreationDTO noteCreationDTO = new NoteCreationDTO();
noteCreationDTO.setTitle(note.getTitle());
noteCreationDTO.setDescription(note.getDescription());
return noteCreationDTO;
}
public List<NoteDTO> NoteConvertList(List<Note> note) {
return note.stream()
.map(this::NoteEntityToDtoGet)
.collect(Collectors.toList());
}
//Tag
public TagDTO TagEntityToDtoGet(Tag tag) {
TagDTO tagDTO = new TagDTO();
tagDTO.setId(tag.getId());
tagDTO.setName(tag.getName());
tagDTO.setNotes(tag.getNotes());
tagDTO.setGroups(tag.getGroups());
return tagDTO;
}
public TagCreationDTO TagEntityToDtoCreate(Tag tag) {
TagCreationDTO tagCreationDTO = new TagCreationDTO();
tagCreationDTO.setId(tag.getId());
tagCreationDTO.setName(tag.getName());
tagCreationDTO.setNotes(tag.getNotes());
return tagCreationDTO;
}
public Set<TagDTO> TagConvertSet(Set<Tag> groups) {
return groups.stream()
.map(this::TagEntityToDtoGet)
.collect(Collectors.toSet());
}
}
You get StackOverFlowError because you end up with infinite recursive methods call and your application creates infinite amount of objects, so you just run out of memory:
1) your NoteEntityToDtoGet method gets Note's group and calls GroupEntityToDtoGet method on the Group object;
2) in GroupEntityToDtoGet method you get all Group's notes and call NoteConvertList method on them, which calls NoteEntityToDtoGet on each of the 'Note'
3) step 1 again...
... the same cycle goes over and over without a stop until your stack memory, you know, overflows :)
So you should decide do your DTO classes really need to hold references to other entity collections.

How to dynamic search with Criteria API in Java?

I want to dynamic search with Criteria API in Java.
In the code I wrote, we need to write each entity in the url bar in JSON. I don't want to write "plaka".
The URL : <localhost:8080/api/city/query?city=Ankara&plaka=> I want to only "city" or "plaka"
Here we need to write each entity, even if we are going to search with only 1 entity. Type Entity and it should be empty.
My code is as below. Suppose there is more than one entity, what I want to do is to search using a single entity it wants to search. As you can see in the photo, I don't want to write an entity that I don't need. can you help me what should I do?
My code in Repository
public interface CityRepository extends JpaRepository<City, Integer> , JpaSpecificationExecutor<City> {
}
My code in Service
#Service
public class CityServiceImp implements CityService{
private static final String CITY = "city";
private static final String PLAKA = "plaka";
#Override
public List<City> findCityByNameAndPlaka(String cityName, int plaka) {
GenericSpecification genericSpecification = new GenericSpecification<City>();
if (!cityName.equals("_"))
genericSpecification.add(new SearchCriteria(CITY,cityName, SearchOperation.EQUAL));
if (plaka != -1)
genericSpecification.add(new SearchCriteria(PLAKA,plaka, SearchOperation.EQUAL));
return cityDao.findAll(genericSpecification);
}
#Autowired
CityRepository cityDao;
My code in Controller
#RestController
#RequestMapping("api/city")
public class CityController {
#Autowired
private final CityService cityService;
public CityController(CityService cityService) {
this.cityService = cityService;
#GetMapping("/query")
public List<City> query(#RequestParam String city, #RequestParam String plaka){
String c = city;
int p;
if (city.length() == 0)
c = "_";
if (plaka.length() == 0) {
p = -1;
}
else
p = Integer.parseInt(plaka);
return cityService.findCityByNameAndPlaka(c,p);
}
My code in SearchCriteria
public class SearchCriteria {
private String key;
private Object value;
private SearchOperation operation;
public SearchCriteria(String key, Object value, SearchOperation operation) {
this.key = key;
this.value = value;
this.operation = operation;
}
public String getKey() {
return key;
}
public Object getValue() {
return value;
}
public SearchOperation getOperation() {
return operation;
}
My code in GenericSpecification
public class GenericSpecification<T> implements Specification<T> {
private List<SearchCriteria> list;
public GenericSpecification() {
this.list = new ArrayList<>();
}
public void add(SearchCriteria criteria){
list.add(criteria);
}
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
for (SearchCriteria criteria : list) {
if (criteria.getOperation().equals(SearchOperation.GREATER_THAN)) {
predicates.add(builder.greaterThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN)) {
predicates.add(builder.lessThan(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.GREATER_THAN_EQUAL)) {
predicates.add(builder.greaterThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.LESS_THAN_EQUAL)) {
predicates.add(builder.lessThanOrEqualTo(
root.get(criteria.getKey()), criteria.getValue().toString()));
} else if (criteria.getOperation().equals(SearchOperation.NOT_EQUAL)) {
predicates.add(builder.notEqual(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.EQUAL)) {
predicates.add(builder.equal(
root.get(criteria.getKey()), criteria.getValue()));
} else if (criteria.getOperation().equals(SearchOperation.MATCH)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
"%" + criteria.getValue().toString().toLowerCase() + "%"));
} else if (criteria.getOperation().equals(SearchOperation.MATCH_END)) {
predicates.add(builder.like(
builder.lower(root.get(criteria.getKey())),
criteria.getValue().toString().toLowerCase() + "%"));
}
}
return builder.and(predicates.toArray(new Predicate[0]));
}
My code in SearchOperation
public enum SearchOperation {
GREATER_THAN,
LESS_THAN,
GREATER_THAN_EQUAL,
LESS_THAN_EQUAL,
NOT_EQUAL,
EQUAL,
MATCH,
MATCH_END,
}
The good thing about the Criteria API is that you can use the CriteriaBuilder to build complex SQL statements based on the fields that you have. You can combine multiple criteria fields using and and or statements with ease.
How I approached something similar int he past is using a GenericDao class that takes a Filter that has builders for the most common operations (equals, qualsIgnoreCase, lessThan, greaterThan and so on). I actually have something similar in an open-source project I started: https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/dao/GenericDao.java
https://gitlab.com/pazvanti/logaritmical/-/blob/master/app/data/filter/JPAFilter.java
Next, the implicit DAO class extends this GenericDao and when I want to do an operation (ex: find a user with the provided username) and there I create a Filter.
Now, the magic is in the filter. This is the one that creates the Predicate.
In your request, you will receive something like this: field1=something&field2=somethingElse and so on. The value can be preceded by the '<' or '>' if you want smaller or greater and you initialize your filter with the values. If you can retrieve the parameters as a Map<String, String>, even better.
Now, for each field in the request, you create a predicate using the helper methods from the JPAFilter class and return he resulted Predicate. In the example below I assume that you don't have it as a Map, but as individual fields (it is easy to adapt the code for a Map):
public class SearchFilter extends JPAFilter {
private Optional<String> field1 = Optional.empty();
private Optional<String> field2 = Optional.empty();
#Override
public Predicate getPredicate(CriteriaBuilder criteriaBuilder, Root root) {
Predicate predicateField1 = field1.map(f -> equals(criteriaBuilder, root, "field1", f)).orElse(null);
Predicate predicateField2 = field2.map(f -> equals(criteriaBuilder, root, "field2", f)).orElse(null);
return andPredicateBuilder(criteriaBuilder, predicateField1, predicateField2);
}
}
Now, I have the fields as Optional since in this case I assumed that you have them as Optional in your request mapping (Spring has this) and I know it is a bit controversial to have Optional as input params, but in this case I think it is acceptable (more on this here: https://petrepopescu.tech/2021/10/an-argument-for-using-optional-as-input-parameters/)
The way the andPredicateBuilder() is made is that it works properly even if one of the supplied predicates is null. Also, I made s simple mapping function, adjust to include for < and >.
Now, in your DAO class, just supply the appropriate filter:
public class SearchDao extends GenericDAO {
public List<MyEntity> search(Filter filter) {
return get(filter);
}
}
Some adjustments need to be made (this is just starter code), like an easier way to initialize the filter (and doing this inside the DAO) and making sure that that the filter can only by applied for the specified entity (probably using generics, JPAFIlter<T> and having SearchFilter extends JPAFilter<MyEntity>). Also, some error handling can be added.
One disadvantage is that the fields have to match the variable names in your entity class.

Restrict acces in DTO class for fields and return different value

I'm implementing authorization in my Spring Boot application. And I want to apply authorization to dto's.
See the class below
public class ExampleDTO{
private String phoneNumber; // want to restrict acces on this field.
}
E.g.
User A with permission A can see phoneNumber 123456789
User B with permission B can see phoneNumber 123456***
A solutions can be something like this:
public void setPhoneNumber(String iban) {
if (Service.checkPermission("A")) {
this.phoneNumber= 123456789;
} else if (Service.checkPermission("B")) {
this.phoneNumber= "123456***";
} else {
this.phoneNumber= "*********";
}
}
But this is against the OOP principle.
Also I can do this in a service:
public ExampleDTO getExampleDto(String iban) {
if (checkPermission("A")) {
return ExampleDtoA;
} else if (checkPermission("B")) {
return ExampleDtoB;
} else {
return ExampleDtoC;
}
}
But this will allow code duplication;
So does one of you have a better solution?
One way to go is to use a builder to build ExampleDto. Something like this:
public class ExampleDTOBuilder {
public enum PhonePermission { A, B, NONE };
private String phoneNumber;
private PhonePermission phonePermission = PhonePermission.NONE;
public ExampleDTOBuilder setPhoneNumber( String phoneNumber ) {
this.phoneNumber = phoneNumber;
return this;
}
public ExampleDTOBuilder setPermission(PhonePermission phonePermission) {
this.phonePermission = phonePermission;
return this;
}
public ExampleDTO build() {
ExampleDTO dto = new ExampleDTO();
switch (this.phonePermission) {
case A:
dto.setPhoneNumber(this.phoneNumber);
break;
case B:
dto.setPhoneNumber(getMaskedPhoneNumber(this.phoneNumber));
default:
dto.setPhoneNumber("********");
break;
}
return dto;
}
private String getMaskedPhoneNumber(String phoneNumber) {
return "123***"; // do your masking
}
}
Use:
ExampleDTO dto = new ExampleDTOBuilder()
.setPermission(PhonePermission.A)
.setPhoneNumber("123456")
.build();
More details about the Builder pattern can be found here: https://en.wikipedia.org/wiki/Builder_pattern
I am assuming that you are saving these phone numbers in a database and that you want to persist the real phone numbers, but return different values based on permissions.
If this is the case, then you should not alter the setPhoneNumber method of the DTO as this could put the following phone number values into your DB: 123456***, *********.
You can encapsulate the logic in a converter between your entity and dto layer
public class ExampleService {
ExampleDto getExample(String iban) {
// Get entity from your DB
ExampleEntity entity = entityRepository.getEntity(iban);
return converter.convert(entity);
}
}
public class Converter {
public ExampleDto convert(ExampleEntity entity) {
ExampleDto dto = new ExampleDto();
if (checkPermission("A"))
dto.setPhoneNumber(entity.getPhoneNumber());
else if (checkPermission("B"))
dto.setPhoneNumber(entity.getPhoneNumberPartialMasked();
else
dto.setPhoneNumber(entity.getPhoneNumberMasked());
return dto;
}
}
If you are using the spring framework, then you can see the following classes: org.springframework.core.convert.converter.Converter and org.springframework.core.convert.ConversionService. There are lots of examples out there on how to use these.

Hibernate Validator path returns null for `NodeImpl.getValue()` for a custom constraint

I have the following:
#ValidAccount
class Account {
List<User> users;
}
class User {
String name;
}
And my validator has
public boolean isValid (final Account account, final ConstraintValidatorContext context) {
// For the sake of simplicy, I am not looping through the array and just fetching the first entry
User user = account.getUsers().get(0);
if (user.getName().equals("SOME_DISALLOWED_NAME")) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("name is invalid")
.addPropertyNode("users")
.addPropertyNode("name").inIterable().atIndex(0)
.addConstraintViolation();
return false;
}
return true;
}
Now I expect to be able to do
// Logging path shows users[0].name
Try.of(() -> (NodeImpl) Iterables.getLast(violation.getPropertyPath()))
.andThen(node -> {
final User user = (User) node.getParent().getValue();
});
But this casting fails, as getValue() returns null. What am I doing wrong?
Edit
Ultimately, my custom validator is intended to do a unique check that there are no two users with the same name

MBeanOperationInfo and MBeanAttributeInfo meta data?

I have written a JMX interface for one of our applications. Another application then connects and allows the user to see various state related attributes / invoke operations remotely via this management tool. I stumbled across a small bug where our database connection settings are being exposed over JMX, with the password unencrypted. I would like to tag the attributes / operations that should be obfuscated with some flag, but it doesnt appear as though the MBeanAttributeInfo or MBeanOperationInfo objects support adding any user defined values exception for name and description. I suppose I could delimit the description field like
String desc = getAttrDesc() + ";" + getIsObfuscated();
But I dont like this approach very much. The question then is, is there a better way to provide arbitrary key value pairs to an attribute / operation info object, or the Dynamic MBean itself? It doesnt have to be on the info objects themselves, just as long as I can match them up on the management tool side. Any insight would be appreciated.
Just for clarification, when I construct the MBeanOperationInfo (leaving out the attributes for the sake of example) I do so like this:
LinkedList<MBeanOperationInfo> opperInfos = new LinkedList<MBeanOperationInfo>();
for (Method m : m_InstObj.getMethods()) {
InstrumentedOperation anno = m.getAnnotation(InstrumentedOperation.class);
String desc = anno.description();
opperInfos.add(new MBeanOperationInfo(desc, m));
}
m_Operations = new MBeanOperationInfo[opperInfos.size()];
int I = 0;
for (MBeanOperationInfo info : opperInfos) {
m_Operations[I] = info;
I++;
}
I would like the InstrumentedOperation annotation to have a field for obfuscated that I can use like this:
anno.obfuscated(); // <- retreives a boolean set as a compile time constant on the annotation
and be able to include this value in the Info object.
Then on the receiving side I do this:
MBeanOperationInfo[] operInfos = conn.getMBeanInfo(name).getOperations();
for (MBeanOperationInfo info : operInfos) {
String propName = getPropNameFromInfo(info.getName());
if (!uniqueSettings.contains(propName)) {
// this setting hasn't been handled, get the getters and setters and make the method map
String getter = getGetterForSetting(operInfos, info.getName());
String setter = getSetterForSetting(operInfos, info.getName());
Object value = conn.invoke(name, getter, new Object[] {}, new String[] {});
if (getter != null && setter != null) {
SettingMethodMap map = new SettingMethodMap(name.getKeyProperty("type"), propName, info.getName(), setter, getter, value);
uniqueSettings.add(propName);
m_Settings.add(map);
}
}
}
Here I would like to be able to retreive the key value pair through some mechanism, so I would know that I need to handle this field different and obfuscate it in the editor.
This can be achieved using the javax.management.DescriptorKey.
For example, using a code sample that I adapted for this, using a standard mbean:
"Obfuscated" annotation:
import java.lang.annotation.*;
import javax.management.DescriptorKey;
#Documented
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Obfuscated {
#DescriptorKey("obfuscated")
boolean value() default true;
}
MBean interface:
public interface LoginMBean {
String getName();
#Obfuscated
String getPassword();
}
MBean implementation:
public class Login implements LoginMBean {
private final String user;
private final String password;
public Login(String user, String password) {
this.user = user;
this.password = password;
}
#Override public String getName() { return user; }
#Override public String getPassword() { return password; }
}
Some code to register the MBean and browse its information:
import java.lang.management.ManagementFactory;
import javax.management.*;
public class Main {
public static void main(String[] args) {
try {
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
ObjectName mbeanName = new ObjectName("com.mydomain", "type", "login");
server.registerMBean(
new StandardMBean(new Login("John Doe", "password"), LoginMBean.class), mbeanName);
MBeanInfo mbeanInfo = server.getMBeanInfo(mbeanName);
MBeanAttributeInfo[] attrs = mbeanInfo.getAttributes();
for (MBeanAttributeInfo attr: attrs) {
Descriptor desc = attr.getDescriptor();
boolean obfuscated = false;
if (desc.getFieldValue("obfuscated") != null) {
obfuscated = (Boolean) desc.getFieldValue("obfuscated");
}
if (obfuscated) System.out.printf("field '%s' is obfuscated%n", attr.getName());
else {
Object value = server.getAttribute(mbeanName, attr.getName());
System.out.printf("value of field '%s' is '%s'%n",
attr.getName(), value == null ? "null" : value.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Finally, the output after running Main:
value of field 'Name' is 'John Doe'
field 'Password' is obfuscated

Categories

Resources