Hi there,
we are trying to switch to camunda 7.8 but we are facing a NPE while trying to complete a UserTask via REST-API. The reason why this happens is rather curious:
We registered a taskNotificationListener which sends notifications via websockets when a task gets completed (via taskService.getIdentityLinksForTask(delegateTask.getId())). This method uses the GetIdentityLinksForTaskCmd which adds programmatically a new IdentityLinkObject to the IdentityList. Thankfully the comment in this method explains the NPE. Camunda then tries to complete and delete this task with all associated Identitylinks (TaskManager:L84). Since the TaskEntity is already cached in dbEntityCache AND the (dirty) List with IdentityLinkEntities is already loaded, a IdentityLinkEntity with ID [NULL] gets passed to the DbOperationsManager and the underlying DbEntityOperationComparator where it goes Boom. I’ve added the relevant code and the stacktrace below…
A solution for this issue would be returning a copy of List in the GetIdentityForTaskCmd.
the execute method in GetIdentityLinksForTaskCmd loads the IdentityLinks and adds a new Entity with ID [NULL] to the List.
List<IdentityLink> identityLinks = (List) task.getIdentityLinks();
// assignee is not part of identity links in the db.
// so if there is one, we add it here.
// @Tom: we discussed this long on skype and you agreed ;-)
// an assignee *is* an identityLink, and so must it be reflected in the API
//
// Note: we cant move this code to the TaskEntity (which would be cleaner),
// since the task.delete cascased to all associated identityLinks
// and of course this leads to exception while trying to delete a non-existing identityLink
if (task.getAssignee() != null) {
IdentityLinkEntity identityLink = new IdentityLinkEntity();
identityLink.setUserId(task.getAssignee());
identityLink.setTask(task);
identityLink.setType(IdentityLinkType.ASSIGNEE);
identityLinks.add(identityLink);
}
if (task.getOwner() != null) {
IdentityLinkEntity identityLink = new IdentityLinkEntity();
identityLink.setUserId(task.getOwner());
identityLink.setTask(task);
identityLink.setType(IdentityLinkType.OWNER);
identityLinks.add(identityLink);
}
return (List) task.getIdentityLinks();
org.camunda.bpm.engine.impl.persistence.entity.TaskEntity,delete reuses this cached List
public void deleteIdentityLinks(boolean withHistory) {
List<IdentityLinkEntity> identityLinkEntities = getIdentityLinks();
for (IdentityLinkEntity identityLinkEntity : identityLinkEntities) {
fireDeleteIdentityLinkAuthorizationProvider(identityLinkEntity.getType(),
identityLinkEntity.getUserId(), identityLinkEntity.getGroupId());
identityLinkEntity.delete(withHistory);
}
isIdentityLinksInitialized = false;
}
org.camunda.bpm.engine.impl.db.entitymanager.operation.DbOperationManager tries to sort the delete operations
protected SortedSet<DbEntityOperation> getDeletesByType(Class<? extends DbEntity> type, boolean create) {
SortedSet<DbEntityOperation> deletesByType = deletes.get(type);
if(deletesByType == null && create) {
deletesByType = new TreeSet<DbEntityOperation>(MODIFICATION_OPERATION_COMPARATOR);
deletes.put(type, deletesByType);
}
return deletesByType;
}
org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator
public int compare(DbEntityOperation firstOperation, DbEntityOperation secondOperation) {
if(firstOperation.equals(secondOperation)) {
return 0;
}
DbEntity firstEntity = firstOperation.getEntity();
DbEntity secondEntity = secondOperation.getEntity();
return firstEntity.getId().compareTo(secondEntity.getId());
}
The NPE happens because of an IndentityLinkEntity with ID [NULL]:
Caused by: java.lang.NullPointerException: null
at java.lang.String.compareTo(String.java:1155) ~[na:1.8.0_74]
at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:35) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.db.entitymanager.operation.comparator.DbEntityOperationComparator.compare(DbEntityOperationComparator.java:24) ~[camunda-engine-7.8.0.jar:7.8.0]
at java.util.TreeMap.put(TreeMap.java:552) ~[na:1.8.0_74]
at java.util.TreeSet.add(TreeSet.java:255) ~[na:1.8.0_74]
at org.camunda.bpm.engine.impl.db.entitymanager.operation.DbOperationManager.addOperation(DbOperationManager.java:75) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.performEntityOperation(DbEntityManager.java:572) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushCachedEntity(DbEntityManager.java:451) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flushEntityCache(DbEntityManager.java:417) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.db.entitymanager.DbEntityManager.flush(DbEntityManager.java:283) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.interceptor.CommandContext.flushSessions(CommandContext.java:203) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.interceptor.CommandContext.close(CommandContext.java:132) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.interceptor.CommandContextInterceptor.execute(CommandContextInterceptor.java:113) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor$1.doInTransaction(SpringTransactionInterceptor.java:42) ~[camunda-engine-spring-7.8.0.jar:7.8.0]
at org.springframework.transaction.support.TransactionTemplate.execute(TransactionTemplate.java:133) ~[spring-tx-4.3.12.RELEASE.jar:4.3.12.RELEASE]
at org.camunda.bpm.engine.spring.SpringTransactionInterceptor.execute(SpringTransactionInterceptor.java:40) ~[camunda-engine-spring-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.interceptor.ProcessApplicationContextInterceptor.execute(ProcessApplicationContextInterceptor.java:66) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.interceptor.LogInterceptor.execute(LogInterceptor.java:30) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.impl.TaskServiceImpl.complete(TaskServiceImpl.java:173) ~[camunda-engine-7.8.0.jar:7.8.0]
at org.camunda.bpm.engine.rest.sub.task.impl.TaskResourceImpl.complete(TaskResourceImpl.java:96) ~[camunda-engine-rest-core-7.8.0.jar:7.8.0]
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_74]
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_74]
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_74]
at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_74]
Well, that was a fun debugging session
Cheers,
Jürgen