How to get rid of ifs in this case? - java

I have the building process for Criteria SQL requests but this repeating if() extremely horrible. I want to get rid of theirs. May by Reflection API help me but my struggle is failing. Maybe some who help me with a small example.
#AllArgsConstructor
class PhoneSpecification implements Specification<Phone> {
private final #NonNull PhoneFilter filter;
#Override
public Predicate toPredicate(Root<Phone> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
if (nonNull(filter.getId())) {
predicate.getExpressions().add(cb.equal(root.get("id"), filter.getId()));
}
if (nonNull(filter.getNote())) {
predicate.getExpressions().add(cb.like(root.get("note"), toLike(filter.getNote(), ANY)));
}
if (nonNull(filter.getNumber())) {
predicate.getExpressions().add(cb.like(root.get("number"), toLike(filter.getNumber(), ANY)));
}
if (nonNull(filter.getStatus())) {
predicate.getExpressions().add(cb.like(root.get("status"), toLike(filter.getStatus(), ANY)));
}
if (nonNull(filter.getOpName())) {
predicate.getExpressions().add(cb.like(root.get("opName"), toLike(filter.getOpName(), ANY)));
}
if (nonNull(filter.getOpLogin())) {
predicate.getExpressions().add(cb.like(root.get("opAccLogin"), toLike(filter.getOpLogin(), ANY)));
}
if (nonNull(filter.getOpPassword())) {
predicate.getExpressions().add(cb.like(root.get("opPassword"), toLike(filter.getOpPassword(), ANY)));
}
if (nonNull(filter.getRegFrom()) && nonNull(filter.getRegTo())) {
predicate.getExpressions().add(cb.between(root.get("regDate"), filter.getRegFrom(), filter.getRegTo()));
}
return predicate;
}
}
I thying something like this:
Lists.newArrayList(Phone.class.getDeclaredFields()).forEach(field -> {
field.setAccessible(true);
try {
field.get(filter)
//...
} catch (IllegalAccessException e) {
e.printStackTrace();
}
});
But I confused can I get value and type, and name of field...
Maybe reflection isn't a good way and you know better? Design pattern or something else?
This filter DTO:
#Data
class PhoneFilter {
private Pageable pageable;
private Integer id;
private Timestamp regFrom;
private Timestamp regTo;
private String number;
private String opLogin;
private String opPassword;
private String opName;
private String status;
private String note;
}

Related

is it possible to convert statement result to List<Object> in java

I want to convert the below statement result to list of my object.
import com.amazonaws.services.dynamodbv2.model.ExecuteStatementResult;
ExecuteStatementResult executeStatementResult = dynamoDB.executeStatement(executeStatementRequest);
System.out.println( executeStatementResult.getItems() );
Output:
[{bookingClasses={S: A,B,C,}, suppliers={S: BA,1A,TF,}, adjustmentType={S: PERCENTAGE,}, departureStartDate={S: 2022-11-17,}}]
Please note .getItems() method is like below:
public java.util.List<java.util.Map<String, AttributeValue>> getItems() {
return items;
}
I want something like this to work for me:
List<java.util.Map<String, AttributeValue>> items = executeStatementResult.getItems();
List<PricingRule> pricingRules = ( List<PricingRule> ) items;
Any help suggestion or workaround appreciated.
Thank you
You can't cast directly so you will need to implement something like PricingRule::fromMap and do something like
class PricingRule {
// everything you already have
public static PricingRule fromMap(Map<String, AttributeValue> items) {
// create PricingRule
}
}
Then when you need your list you can use something like
List<java.util.Map<String, AttributeValue>> items = executeStatementResult.getItems();
// assumes java 17+, alternatively use .collect(Collectors.toList())
List<PricingRule> pricingRules = items.stream().map(PricingRule::fromMap).toList();
Glad you managed to solve your problem, but have you considered using the table resource?
var table = dynamoDbEnhancedClient.table("TableName", TableSchema.fromClass(PricingRule.class));
List<PricingRule> items = table.query(<condition>).items().stream().toList();
This code requires that PricingRule is defined as a dynamo bean, for example
#DynamoDbBean
public class PricingRule {
private String pk;
private String sk;
private String field1;
private String field2;
public PricingRule() {
}
#DynamoDbPartitionKey
public String getPk() {
return pk;
}
public void setPk(String pk) {
this.pk = pk;
}
#DynamoDbSortKey
public String getSk() {
return sk;
}
public void setSk(String sk) {
this.sk = sk;
}
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
}
I got solution and posting if someone may come up with this problem in future.
public List<PricingRule> statementToList() {
List<PricingRule> pricingRules = new ArrayList<>();
try {
// Create ExecuteStatementRequest
ExecuteStatementRequest executeStatementRequest = createExecuteStatementRequest();
ExecuteStatementResult executeStatementResult = dynamoDB.executeStatement(executeStatementRequest);
final ObjectMapper mapper = new ObjectMapper();
executeStatementResult.getItems().forEach(item -> pricingRules.add(mapper.convertValue(ItemUtils.toSimpleMapValue(item), PricingRule.class)));
} catch (Exception e) {
handleExecuteStatementErrors(e);
}
return pricingRules;
}

Store ENUM value into database

I wan to use ENUM to map values into database table rows:
BusinessCustomersSearchParams:
#Getter
#Setter
public class BusinessCustomersSearchParams {
private String title;
private List<String> status;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}
Specification:
#Override
public Page<BusinessCustomersFullDTO> findBusinessCustomers(BusinessCustomersSearchParams params, Pageable pageable)
{
Specification<BusinessCustomers> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (params.getTitle() != null) {
predicates.add(cb.like(cb.lower(root.get("description")), "%" + params.getTitle().toLowerCase() + "%"));
}
final List<String> statuses = Optional.ofNullable(params.getStatus()).orElse(Collections.emptyList());
if (statuses != null && !statuses.isEmpty()){
List<BusinessCustomersStatus> statusesAsEnum = statuses.stream()
.map(status -> BusinessCustomersStatus.fromStatus(status))
.collect(Collectors.toList())
;
predicates.add(root.get("status").in(statusesAsEnum));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
return businessCustomersService.findAll(spec, pageable).map(businessCustomersMapper::toFullDTO);
}
AttributeConverter:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter
public class BusinessCustomersStatusAttributeConverter
implements AttributeConverter<BusinessCustomersStatus, String> {
public String convertToDatabaseColumn( BusinessCustomersStatus value ) {
if ( value == null ) {
return null;
}
return value.getStatus();
}
public BusinessCustomersStatus convertToEntityAttribute( String value ) {
if ( value == null ) {
return null;
}
return BusinessCustomersStatus.fromStatus( value );
}
}
Enum:
package org.merchant.database.service.businesscustomers;
public enum BusinessCustomersStatus {
A("active"),
O("onboarding"),
N("not_verified"),
V("verified"),
S("suspended"),
I("inactive");
private String status;
BusinessCustomersStatus(String status)
{
this.status = status;
}
public String getStatus() {
return status;
}
public static BusinessCustomersStatus fromStatus(String status) {
switch (status) {
case "active": {
return A;
}
case "onboarding": {
return O;
}
case "not_verified": {
return NV;
}
case "verified": {
return V;
}
case "suspended": {
return S;
}
case "inactive": {
return I;
}
default: {
throw new UnsupportedOperationException(
String.format("Unkhown status: '%s'", status)
);
}
}
}
}
Entity:
#Entity
#Table(name = "business_customers")
public class BusinessCustomers implements Serializable {
..........
#Convert( converter = BusinessCustomersStatusAttributeConverter.class )
private BusinessCustomersStatus status;
......
}
Full code example: https://github.com/rcbandit111/Search_specification_POC
I send http query with params list?size=5&page=0&status=active,suspended and I get result with capital letters "status": "ACTIVE".
I wan to search and get status from FE for status using status=active but store into database row field only symbol A.
How I can store into database the ENUM key A?
In order to store the actual enumeration value in the database, you can do two things.
One, as suggested by #PetarBivolarski, modify the method convertToDatabaseColumn in AttributeConverter and return value.name() instead of value.getStatus(). But please, be aware that in addition you will need to update the convertToEntityAttribute as well to take into account that change:
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
#Converter
public class BusinessCustomersStatusAttributeConverter
implements AttributeConverter<BusinessCustomersStatus, String> {
public String convertToDatabaseColumn( BusinessCustomersStatus value ) {
if ( value == null ) {
return null;
}
return value.name();
}
public BusinessCustomersStatus convertToEntityAttribute( String value ) {
if ( value == null ) {
return null;
}
return BusinessCustomersStatus.valueOf( value );
}
}
If you think about it, a more straightforward solution will be just keep the status field as #Enumerated:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
#Entity
#Table(name = "business_customers")
public class BusinessCustomers {
//...
#Enumerated(EnumType.STRING)
#Column(name = "status", length = 20)
private BusinessCustomersStatus status;
//...
}
It is in addition more according to the rest of your code.
Regarding your second problem, the application is returning "status":"ACTIVE" because in BusinessCustomersFullDTO you are defining the status field as String and this field receives the result of the mapping process performed by #Mapstruct and BusinessCustomersMapper.
To solve that issue, as I suggested you previously, you can modify your Mapper to handle the desired custom conversion:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.merchant.config.BaseMapperConfig;
import org.merchant.database.entity.BusinessCustomers;
import org.merchant.database.service.businesscustomers.BusinessCustomersStatus;
import org.merchant.dto.businesscustomers.BusinessCustomersFullDTO;
#Mapper(config = BaseMapperConfig.class)
public interface BusinessCustomersMapper {
#Mapping(source = "status", target = "status", qualifiedByName = "businessCustomersToDTOStatus")
BusinessCustomersFullDTO toFullDTO(BusinessCustomers businessCustomers);
#Named("busineessCustomersToDTOStatus")
public static String businessCustomersToDTOStatus(final BusinessCustomersStatus status) {
if (status == null) {
return null;
}
return status.getStatus();
}
}
If you do not prefer this solution, perhaps you can take a different approach: it will consist in the following. The idea is modifying the Jackson serialization and deserialization behavior of BusinessCustomersFullDTO. In fact, in your use case only is necessary to modify the serialization logic.
First, define the status field in BusinessCustomersFullDTO in terms of BusinessCustomersStatus as well:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Builder(toBuilder = true)
public class BusinessCustomersFullDTO {
private long id;
private String name;
private String businessType;
private BusinessCustomersStatus status;
private String description;
private String country;
private String address1;
}
To complete the solution, please, perform the following changes in the BusinessCustomersStatus enum:
public enum BusinessCustomersStatus {
A("active"),
O("onboarding"),
NV("not_verified"),
V("verified"),
S("suspended"),
I("inactive");
private String status;
BusinessCustomersStatus(String status)
{
this.status = status;
}
// Define the status field as the enum representation by using #JsonValue
#JsonValue
public String getStatus() {
return status;
}
// Use the fromStatus method as #JsonCreator
#JsonCreator
public static BusinessCustomersStatus fromStatus(String status) {
if (StringUtils.isEmpty(status)) {
return null;
}
switch (status) {
case "active": {
return A;
}
case "onboarding": {
return O;
}
case "not_verified": {
return NV;
}
case "verified": {
return V;
}
case "suspended": {
return S;
}
case "inactive": {
return I;
}
default: {
throw new UnsupportedOperationException(
String.format("Unkhown status: '%s'", status)
);
}
}
}
}
Note the inclusion of the #JsonValue and #JsonCreator annotations: the later is using for deserialization which seems unnecessary to me in your application, but just in case.
Please, see the relevant documentation of the provided Jackson annotations.
Notice your convertToDatabaseColumn() method in BusinessCustomersStatusAttributeConverter.
It should return value.name() instead of value.getStatus().

Intercept request to create RequestBody

I have a base CreateRequest class which I inherit in a couple of children, each responsible for some custom validations:
Base:
public class CreateEventRequest {
#NotEmpty
private String name;
#NotNull
#JsonProperty
private Boolean isPrivate;
}
Child:
public class CreateRegularEventRequest extends CreateEventRequest {
#NotNull
#Future
private LocalDateTime startTime;
#NotNull
#Future
private LocalDateTime endTime;
public LocalDateTime getStartTime() {
return startTime;
}
public LocalDateTime getEndTime() {
return endTime;
}
}
In order to take advantage of the validations, I've tried this:
#PostMapping(value = "/", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CreateEventResponse> createEvent(HttpEntity<String> httpEntity,
#AuthenticationPrincipal SecuredUser user) {
try {
CreateEventRequest eventRequest = eventRequestFactory.getEventRequestString(httpEntity.getBody());
Set<ConstraintViolation<CreateEventRequest>> violations = validator.validate(eventRequest);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
return new ResponseEntity<>(this.service.createEvent(eventRequest, user), HttpStatus.CREATED);
} catch (ConstraintViolationException e) {
throw e;
} catch (Exception e) {
return ResponseEntity.badRequest().build();
}
}
My factory is:
public CreateEventRequest getEventRequestString(String json) {
try {
String eventTypeRaw = new ObjectMapper().readTree(json)
.get("event_type").asText();
if (!StringUtils.isEmpty(eventTypeRaw)) {
EventType eventType = EventType.valueOf(eventTypeRaw);
if (EventType.REGULAR.equals(eventType)) {
return objectMapper.readValue(json, CreateRegularEventRequest.class);
} else if (EventType.RECURRING.equals(eventType)) {
return objectMapper.readValue(json, CreateRecurringEventRequest.class);
}
}
return null;
} catch (JsonProcessingException e) {
return null;
}
}
This seems really hacky to me and do not scale for future Parent-Child relation, my question is, is there a way to intercept this request to create this child classes and pass it to the controller or some built-in validations to handle this scenario?
Thanks!
Did yo try:
public ResponseEntity<CreateEventResponse> createEvent( #Valid #RequestBody CreateRegularEventRequest reqBody ...) {
..
all the mapping and validation are done automatically

Unable to make restTemplate call with Generics for nested Json

I am trying to make a restTemplate call for API testing. The json returned is a nested one with multiple levels.
{
"code": 200,
"data": {
"result": {
"publicId": "xyz"
}
}
}
I have the following classes acting as wrapper :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public abstract class RestCallResponse<T> {
private int code;
protected RestCallResponse(int code) {
this.code = code;
}
protected RestCallResponse(){}
#JsonProperty("data")
public Map<?, ?> getRestCallResponse() {
return ImmutableMap.of("result", getResult());
}
#JsonIgnore
protected abstract T getResult();
public int getCode() {
return code;
}
}
And then a SuccessRestResponse class extending this abstract class :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class SuccessRestResponse<T> extends RestCallResponse<T> {
#JsonProperty("result")
private T result;
public SuccessRestResponse() {
}
public SuccessRestResponse(T result) {
super(HttpStatus.SC_OK);
this.result = result;
}
protected T getResult() {
return this.result;
}
}
Then finally I have the actual data POJO :
public final class CreatedResponse {
#JsonProperty
private final EntityId publicId;
public CreateCreativeResponse(EntityId publicId) {
this.publicId = publicId;
}
}
In the test case, I am making a call as such :
ResponseEntity<SuccessRestResponse<CreatedResponse>> newResponse =
restTemplate.exchange(requestEntity, new ParameterizedTypeReference<SuccessRestResponse<CreatedResponse>>() {});
But I am getting the following error :
nested exception is org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: null value in entry: result=null (through reference chain: com.inmobi.helix.test.api.dao.SuccessRestResponse["data"]);
Any suggestions? Where am I going wrong?
I solved the problem with a workaround. Still don't know what's wrong with the above piece of code though.
I got rid of the class RestCallResponse<T> and edited the field members in SuccessRestResponse<T> to look like this :
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.ANY)
public class SuccessRestResponse<T> {
private int code;
private Map<String, T> data;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public Map<String, T> getData() {
return data;
}
#JsonIgnore
public T getResult() {
return data.get("result");
}
public void setData(Map<String, T> data) {
this.data = data;
}
}
This corresponds to the nested json while deserialization.
P.S. - Would still like to know what went wrong in my above code
though. As in, why did class hierarchy not work for me.

JPA not updating column with Converter class

I'm using a Converter class to store a complex class as JSON text in mySQL. When I add a new entity, the Converter class works as intended. However, when I update the entity, the data in the complex class is not updated in the database but it's updated in memory. Other attributes such as Lat and Long are updated. The breakpoint I placed at the convertToDatabaseColumn method and it did not trigger on update.
Object Class
public class Project {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String city;
private String state;
private String country;
#Enumerated(EnumType.STRING)
private StatusType status;
private String street;
private double latitude;
private double longitude;
#Convert(converter=ProjectPropertyConverter.class)
private ProjectProperty property;
}
public class ProjectProperty {
private String description;
private List<String> projectImgs;
private Boolean hasImages;
}
Property Converter Class
#Converter (autoApply=true)
public class ProjectPropertyConverter implements AttributeConverter<ProjectProperty, String> {
#Override
public String convertToDatabaseColumn(ProjectProperty prop) {
try {
ObjectMapper mapper = new ObjectMapper();
String jsonString = mapper.writeValueAsString(prop);
return jsonString;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
#Override
public ProjectProperty convertToEntityAttribute(String jsonValue) {
try {
ObjectMapper mapper = new ObjectMapper();
ProjectProperty p = mapper.readValue(jsonValue, ProjectProperty.class);
if(p.getProjectImgs().isEmpty())
{
p.setHasImages(Boolean.FALSE);
}
else
{
p.setHasImages(Boolean.TRUE);
}
return p;
} catch (Exception e) {
System.out.print(e.toString());
return null;
}
}
}
Method to Update Database
public void modifyEntity(Object entity, String query, HashMap params) {
try {
tx.begin();
em.flush();
tx.commit();
} catch (Exception e) {
e.toString();
}
}
I came here looking for same answers. Turns out the problem is JPA doesn't know that your object is dirty. This was solved by implementing equals()/hashcode() methods on this complex objects. In your example, implement equals and hashcode for ProjectProperty
Once that is done, JPA is able to identify via these methods that the underlying object is dirty and converts and persists.

Categories