Issue
I am facing an issue in a springboot project, I am trying to retrieve statistics of "Tickets" that are handled ontime using jpa specifications. Ticket are given a number of days to handle based on the purpose.
java.lang.NullPointerException: null
at com.ticketcorp.ticket.repository.TicketSpecification.lambda$isOntime$c9c337fb$1(TicketSpecification.java:208) ~[classes/:na]
Which i Believe is to be expected since i got this warning on the same line:
'Map<String, Integer>' may not contain keys of type 'Path<String>'
Here is my Ticket Entity:
@Table(name = "tickets")
public class Ticket {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String code;
private String purpose;
@Lob
@Column(columnDefinition = "TEXT")
private String content;
@Lob
@Column(columnDefinition = "TEXT")
private String solution;
@Lob
@Column(columnDefinition = "TEXT")
private String comment;
private int status;
private LocalDateTime createdAt= LocalDateTime.now();
private LocalDateTime handledAt= LocalDateTime.now();
}
Here is my Ticket Specification:
public class TicketSpecification {
public static Specification<Ticket> isOntime(ArrayList<Purpose> purposes) {
return (root, query, builder) -> {
/*Example of content for nameAndDurationMap: {Suggestion=25, Bug report=1}*/
Map<String, Integer> nameAndDurationMap = PurposeUtils.PurposeArrayToNameDurationMap(purposes);
return builder.le(
builder.diff(
builder.function("unix_timestamp", Long.class, root.get(Ticket_.handledAt)),
builder.function("unix_timestamp", Long.class, root.get(Ticket_.createdAt))
)
, nameAndDurationMap.get(root.get(Ticket_.purpose)) * 86400);/*Line 208*/
};
}
}
Here is my Ticket Service:
@Service
public class TicketService {
@Autowired
private TicketRepository ticketRepository;
public String countTicketsHandledOnTime(){
int handledStatus=2;
Specification<Ticket> allTicketHandledOnTimeQuery =where(TicketSpecification.isHandled(handledStatus)).and(TicketSpecification.isOntime(purposes));
return String.valueOf(ticketRepository.count(allTicketHandledOntimeQuery));
}
}
Here is Purpose POJO Model:
public class Purpose{
private String id;
private String name;
private String description;
private int level;
private int duration;
}
Here is PurposeUtils : It takes a list of purposes and generate a hashmap of purpose and number of days it should take to handle a ticket of that purpose.
public class PurposeUtils {
public static Map<String, Integer> PurposeArrayToNameDurationMap(ArrayList<Purpose> purposes) {
Map<String, Integer> purposeMap = new HashMap<String, Integer>();
for(Purpose purpose: purposes) {
purposeMap.put(purpose.getName(), purpose.getDuration());
}
return purposeMap;
}
}
Solution
I assume you use javax.persistence.criteria.Root
in this line:
nameAndDurationMap.get(root.get(Ticket_.purpose)) * 86400);/*Line 208*/
note documentation of Root:
Path< Y > get(String attributeName) Create a path corresponding to the referenced attribute.
so you ask the map to get the value that indeed is not there
Note that root is not a value holder, it is a for the prdicat creation, so in your predict you will say I want value X(root) to met Y condition
this will become SQL query, so it has to be values that SQL can handle, your code will not be called on every ticket... if you want to do it either makes it iterate every purpose
(
if purpose_x == Ticket_.purpose and le(...)
or purpose_y == Ticket_.purpose and le(...)
)
or move the logic to DB function you can call
code that will give the idea but probably will not run since it dry:
public class TicketSpecification {
public static Specification<Ticket> isOntime(ArrayList<Purpose> purposes){
return (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
for(Purpose purpose: purposes) {
predicates.add(isOntime(purpose.getName(), purpose.getDuration(),root, query, builder);
}
return builder.or(predicates.toArray(new Predicate[0]));
}
}
public static Predicate isOntime(String purposes_name,int purposes_time,Root<Ticket> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
return builder.and(
builder.equal(root.get(Ticket_.purpose),purposes_name)
,
builder.le(
builder.diff(
builder.function("unix_timestamp", Long.class, root.get(Ticket_.handledAt)),
builder.function("unix_timestamp", Long.class, root.get(Ticket_.createdAt))
)
,(purposes_time * 86400);/*Line 208*/
)
);
}
}
Answered By - Sarel Foyerlicht
Answer Checked By - Cary Denson (JavaFixing Admin)