[JBoss JIRA] Created: (JBPM-970) Orphaned records remain in the DB after process instance is deleted
by Tihomir Daskalov (JIRA)
Orphaned records remain in the DB after process instance is deleted
-------------------------------------------------------------------
Key: JBPM-970
URL: http://jira.jboss.com/jira/browse/JBPM-970
Project: JBoss jBPM
Issue Type: Bug
Components: Core Engine
Affects Versions: jBPM 3.1.2
Reporter: Tihomir Daskalov
Assigned To: Tom Baeyens
I have detected leak of orphaned records in the DB in the following cases:
1) deleting a process instance variable
2) changing the type of a process instance variable (for example from string to long)
3) updating a byte array (the leak appears only when the updates are performed in different JBPM sessions - i.e. when the process instance execution is asynchronous)
Seems that for the first two cases the orphaned records are left because of the cascade="save-update" in org.jbpm.context.log.VariableLog.hbm.xml. It causes the variable instances that are referenced only from the process instance log to be not deleted in the DB. As workaround I had to manually remove the orphaned records in the following way:
// The logs map contains the logs per token.
Map tokensLogs = SessionContext.getJbpmContext().getLoggingSession()
.findLogsByProcessInstance(_processInstance.getId());
if (tokensLogs.size() != 1) {
// For now are supported only logs with one token.
throw new IllegalArgumentException("Expected exactly one token but found: "
+ tokensLogs.size());
}
// The map is expected to have only one element mapping the root token
// to the list of its logs.
List logs = (List) tokensLogs.values().iterator().next();
Session session = SessionContext.getJbpmContext().getSession();
for (Iterator iter = logs.iterator(); iter.hasNext();) {
ProcessLog log = (ProcessLog) iter.next();
if (log instanceof VariableLog) {
VariableLog variableLog = ((VariableLog) log);
VariableInstance variableInstance = variableLog.getVariableInstance();
// Remove only the variable instances that are not associated with token.
if (variableInstance.getToken() == null) {
session.delete(variableInstance);
}
}
session.delete(log);
}
The third case seems to be caused from orphaned child entity (org.jbpm.bytes.ByteArray). The new ByteArray value replaces the old ByteArray value in the org.jbpm.context.exe.VariableInstance class, and thus the old ByteArray remains orphan. This is no problem if the old ByteArray is still transient - it will be GC-ed, but if it was already persisted then the persisted entity will remain orphan in the DB. This explains why the 3-rd case can be reproduced only when the byte array is persisted between the two states of the process instance.
I have used the following code to workaround the 3-rd case:
// Take reference to the old ByteArray entity.
ByteArray oldByteArray = null;
TokenVariableMap tokenVariableMap = pProcessInstance.getContextInstance().getTokenVariableMap(pProcessInstance.getRootToken());
if (tokenVariableMap != null) {
VariableInstance variableInstance = tokenVariableMap.getVariableInstance(pVariableName);
if (variableInstance != null) {
oldByteArray = (ByteArray) ((ByteArrayInstance) variableInstance).getObject();
}
}
// Set the new value of the byte array.
pProcessInstance.getContextInstance().setVariable(pVariableName, pBytes);
// Manually remove the old byte array.
if (oldByteArray != null) {
Session session = _jbpmContext().getSession();
if (session != null && session.contains(oldByteArray)) {
SessionContext.getJbpmContext().getSession().delete(oldByteArray);
}
}
Follows the test case that reproduces (via failing) the three operations described above:
public class JbpmTest extends TestCase {
private JbpmContext _jbpmContext;
protected void setUp() throws Exception {
JbpmConfiguration.getInstance().createSchema();
_jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
}
protected void tearDown() throws Exception {
_jbpmContext.close();
JbpmConfiguration.getInstance().dropSchema();
}
/**
* Tests that deleting a JBPM variable leaks orphaned records.
*/
public void testOrphanRecords_1() throws HibernateException, SQLException {
ProcessDefinition processDefinition = ProcessDefinition
.parseXmlString("<process-definition name='test'>"
+ " <start-state>"
+ " <transition to='s' />"
+ " </start-state>"
+ " <state name='s'>"
+ " <transition to='end' />"
+ " </state>"
+ " <end-state name='end' />"
+ "</process-definition>");
_jbpmContext.deployProcessDefinition(processDefinition);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
// Execute a process instance and create and delete a variable.
ProcessInstance processInstance = new ProcessInstance(processDefinition);
processInstance.getContextInstance().setVariable("test", "abc");
processInstance.signal();
assertFalse(processInstance.hasEnded());
processInstance.getContextInstance().deleteVariable("test");
processInstance.signal();
assertTrue(processInstance.hasEnded());
// Persist the process instance.
_jbpmContext.save(processInstance);
_jbpmContext.getSession().flush();
long processInstanceId = processInstance.getId();
// Assure that the log entries are really persisted.
assertTrue(0 < getRecordCount("JBPM_LOG"));
// Delete the process instance.
_jbpmContext.getGraphSession().deleteProcessInstance(processInstanceId);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
}
/**
* Tests that changing the type of a JBPM variable leaks orphaned records.
*/
public void testOrphanRecords_2() throws HibernateException, SQLException {
ProcessDefinition processDefinition = ProcessDefinition
.parseXmlString("<process-definition name='test'>"
+ " <start-state>"
+ " <transition to='s' />"
+ " </start-state>"
+ " <state name='s'>"
+ " <transition to='end' />"
+ " </state>"
+ " <end-state name='end' />"
+ "</process-definition>");
_jbpmContext.deployProcessDefinition(processDefinition);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
// Execute a process instance and create and then change the type of a variable.
ProcessInstance processInstance = new ProcessInstance(processDefinition);
processInstance.getContextInstance().setVariable("test", "abc");
processInstance.signal();
assertFalse(processInstance.hasEnded());
processInstance.getContextInstance().setVariable("test", new Long(10));
processInstance.signal();
assertTrue(processInstance.hasEnded());
// Persist the process instance.
_jbpmContext.save(processInstance);
_jbpmContext.getSession().flush();
long processInstanceId = processInstance.getId();
// Assure that the log entries are really persisted.
assertTrue(0 < getRecordCount("JBPM_LOG"));
// Delete the process instance.
_jbpmContext.getGraphSession().deleteProcessInstance(processInstanceId);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
}
/**
* Tests that a JBPM variable of byte array type leaks orphaned records.
* Note that the leak appears only when the JBPM session is reopened between the states.
*/
public void testOrphanRecords_3() throws HibernateException, SQLException {
ProcessDefinition processDefinition = ProcessDefinition
.parseXmlString("<process-definition name='test'>"
+ " <start-state>"
+ " <transition to='s' />"
+ " </start-state>"
+ " <state name='s'>"
+ " <transition to='end' />"
+ " </state>"
+ " <end-state name='end' />"
+ "</process-definition>");
_jbpmContext.deployProcessDefinition(processDefinition);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
// Execute a process instance and create a byte array variable.
// Note that the leak appears only when the JBPM session is reopened between the states.
ProcessInstance processInstance = new ProcessInstance(processDefinition);
processInstance.getContextInstance().setVariable("bytes", new byte[] {1, 2});
_jbpmContext.save(processInstance);
_jbpmContext.getSession().flush();
long processInstanceId = processInstance.getId();
_jbpmContext.close();
// Reopen the session and continue the execution.
_jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
processInstance = _jbpmContext.loadProcessInstance(processInstanceId);
processInstance.signal();
assertFalse(processInstance.hasEnded());
processInstance.getContextInstance().setVariable("bytes", new byte[] {1, 2, 3, 4});
processInstance.signal();
assertTrue(processInstance.hasEnded());
_jbpmContext.save(processInstance);
_jbpmContext.close();
_jbpmContext = JbpmConfiguration.getInstance().createJbpmContext();
// Assure that the log entries are really persisted.
assertTrue(0 < getRecordCount("JBPM_LOG"));
// Delete the process instance.
_jbpmContext.getGraphSession().deleteProcessInstance(processInstanceId);
// Assure that the watched tables are empty.
assertEquals(0, getRecordCount("JBPM_VARIABLEINSTANCE"));
assertEquals(0, getRecordCount("JBPM_LOG"));
assertEquals(0, getRecordCount("JBPM_BYTEBLOCK"));
assertEquals(0, getRecordCount("JBPM_BYTEARRAY"));
}
private int getRecordCount(String pTableName) throws HibernateException, SQLException {
Statement stmt = null;
ResultSet rs = null;
try {
stmt = _jbpmContext.getSession().connection().createStatement();
String sql = "SELECT count(*) FROM " + pTableName;
rs = stmt.executeQuery(sql);
rs.next();
return rs.getInt(1);
} finally {
if (rs != null) {
rs.close();
}
if (stmt != null) {
stmt.close();
}
}
}
}
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.jboss.com/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
16 years, 11 months
[JBoss JIRA] Created: (JBPM-967) Triggered timer on task does not end task, thus leaving task in users inbox.
by Dave Caruana (JIRA)
Triggered timer on task does not end task, thus leaving task in users inbox.
----------------------------------------------------------------------------
Key: JBPM-967
URL: http://jira.jboss.com/jira/browse/JBPM-967
Project: JBoss jBPM
Issue Type: Bug
Components: Core Engine
Affects Versions: jBPM jPDL 3.2
Environment: Windows, Mac OS. Tomcat / MySQL 5.
Reporter: Dave Caruana
Assigned To: Tom Baeyens
The "timer" task in the following process definition correctly initiates the timer. The timer is successfully is triggered after a minute (or so) and the "trigger" transition is correctly signalled. As a result, the "triggered" task node is entered and its task assigned.
However, the task assigned in "timer" remains assigned. It's not ended.
<?xml version="1.0" encoding="UTF-8"?>
<process-definition xmlns="urn:jbpm.org:jpdl-3.1" name="wf:testtimers">
<swimlane name="initiator" />
<start-state name="start">
<task swimlane="initiator" />
<transition name="" to="timer"/>
</start-state>
<task-node name="timer">
<task swimlane="initiator">
<timer duedate="1 minute" transition="trigger">
<script>
System.out.println("Trigger...");
</script>
</timer>
</task>
<transition name="trigger" to="triggered" />
</task-node>
<task-node name="triggered">
<event type="node-enter">
<script>
System.out.println("Triggered");
</script>
</event>
<task swimlane="initiator"/>
<transition name="" to="end" />
</task-node>
<end-state name="end" />
</process-definition>
I applied the following patch to Timer.java, but am unsure if it's the correct approach, although it fixes the above. It determines a task instance is associated, and if so, ends the task, rather than the token directly.
Line 84:
// then take a transition if one is specified
if ((transitionName != null) && (exception == null)) // and if no unhandled
// exception occurred
// during the action
{
if (taskInstance != null)
{
if (taskInstance.getToken().getNode().hasLeavingTransition(transitionName))
{
taskInstance.end(transitionName);
}
}
else if (token.getNode().hasLeavingTransition(transitionName))
{
token.signal(transitionName);
}
}
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.jboss.com/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
16 years, 11 months
[JBoss JIRA] Created: (JBPM-966) Modify Hibernate mappings to enable searching for TaskInstance's by VariableInstance
by Richard Tomlinson (JIRA)
Modify Hibernate mappings to enable searching for TaskInstance's by VariableInstance
------------------------------------------------------------------------------------
Key: JBPM-966
URL: http://jira.jboss.com/jira/browse/JBPM-966
Project: JBoss jBPM
Issue Type: Feature Request
Components: Core Engine
Affects Versions: jBPM jPDL 3.2
Environment: Any
Reporter: Richard Tomlinson
Assigned To: Tom Baeyens
We want to be able to *efficiently* search for task instances based on the values of one or more variable instance. For example:
TaskInstance myTasklnstance = ....
myTaskInstance.setVariableLocally("owner", "theOwner");
myTaskInstance.setVariable("anId", new Long(123456));
Using this example we would like to create a Hibernate Criteria based query that will allow us to search and retrieve the task instance using the two variables as criteria of the search. At present, this does not appear to be possible.
Querying for TaskInstances based on one or more VariableInstance's requires a convoluted HQL expression where each variableinstance (v) is obtained with the join to task instance (ti) with "v.processInstance.id = ti.taskMgmtInstance.processInstance.id"
Observation:
-----------------
TaskInstance hibernate mappings allows the retrieval of all variable instances for a given task instance. This mapping allows the resulting sql to join the JBPM_TASKINSTANCE table to JBPM_VARIABLEINSTANCE table. The JBPM_VARIABLEINSTANCE table has a FK back to JBPM_TASKINSTANCE but the Hibernate mappings do not contain the correponding reverse mapping.
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.jboss.com/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
16 years, 11 months
[JBoss JIRA] Created: (JBPM-956) jBPM 3.2.x: enhance MockConnection to allow for using JDK 1.6
by Arjan van Bentem (JIRA)
jBPM 3.2.x: enhance MockConnection to allow for using JDK 1.6
-------------------------------------------------------------
Key: JBPM-956
URL: http://jira.jboss.com/jira/browse/JBPM-956
Project: JBoss jBPM
Issue Type: Feature Request
Components: Core Engine
Affects Versions: jBPM jPDL 3.2
Environment: Win32, JDK 1.6
Reporter: Arjan van Bentem
Assigned To: Tom Baeyens
Priority: Optional
Since JDK 1.6, the interface java.sql.Connection includes numerous new methods, such as createArray and createBlob. Adding some dummy methods to org.jbpm.persistence.db.MockConnection allows for using JDK 1.6
API: http://java.sun.com/javase/6/docs/api/java/sql/Connection.html
FIX: add the following lines to http://fisheye.labs.jboss.com/browse/JBPM/jbpm.3/jpdl/jar/src/test/java/o...
public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
throw new UnsupportedOperationException();
}
public Blob createBlob() throws SQLException {
throw new UnsupportedOperationException();
}
public Clob createClob() throws SQLException {
throw new UnsupportedOperationException();
}
public NClob createNClob() throws SQLException {
throw new UnsupportedOperationException();
}
public SQLXML createSQLXML() throws SQLException {
throw new UnsupportedOperationException();
}
public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
throw new UnsupportedOperationException();
}
public Properties getClientInfo() throws SQLException {
throw new UnsupportedOperationException();
}
public String getClientInfo(String name) throws SQLException {
throw new UnsupportedOperationException();
}
public boolean isValid(int timeout) throws SQLException {
throw new UnsupportedOperationException();
}
public void setClientInfo(Properties properties) throws SQLClientInfoException {
throw new UnsupportedOperationException();
}
public void setClientInfo(String name, String value) throws SQLClientInfoException {
throw new UnsupportedOperationException();
}
public boolean isWrapperFor(Class arg0) throws SQLException {
throw new UnsupportedOperationException();
}
public Object unwrap(Class arg0) throws SQLException {
throw new UnsupportedOperationException();
}
Just for the JIRA search:
The type MockConnection must implement the inherited abstract method Connection.createArrayOf(String, Object[])
The type MockConnection must implement the inherited abstract method Connection.createBlob()
The type MockConnection must implement the inherited abstract method Connection.createClob()
The type MockConnection must implement the inherited abstract method Connection.createNClob()
The type MockConnection must implement the inherited abstract method Connection.createSQLXML()
The type MockConnection must implement the inherited abstract method Connection.createStruct(String, Object[])
The type MockConnection must implement the inherited abstract method Connection.getClientInfo()
The type MockConnection must implement the inherited abstract method Connection.getClientInfo(String)
The type MockConnection must implement the inherited abstract method Connection.isValid(int)
The type MockConnection must implement the inherited abstract method Connection.setClientInfo(Properties)
The type MockConnection must implement the inherited abstract method Connection.setClientInfo(String, String)
The type MockConnection must implement the inherited abstract method Wrapper.isWrapperFor(Class)
The type MockConnection must implement the inherited abstract method Wrapper.unwrap(Class)
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.jboss.com/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
16 years, 11 months
[JBoss JIRA] Created: (JBPM-964) NullPointerException in ContextInstance.setVariable(java.lang.String name, java.lang.Object value) method.
by Vt Ysh (JIRA)
NullPointerException in ContextInstance.setVariable(java.lang.String name, java.lang.Object value) method.
----------------------------------------------------------------------------------------------------------
Key: JBPM-964
URL: http://jira.jboss.com/jira/browse/JBPM-964
Project: JBoss jBPM
Issue Type: Bug
Components: Core Engine
Affects Versions: jBPM jPDL 3.2
Environment: Windows XP SP2, jbpm-jpdl-3.2.GA
Reporter: Vt Ysh
Assigned To: Tom Baeyens
Priority: Trivial
NullPointerException occurs when trying to set variable value of byte[] type in ContextInstance. Error happens only in one case, then value is not null and has zero length
Here is a junit TestCase which represents an error
import junit.framework.TestCase;
import org.jbpm.context.exe.ContextInstance;
import org.jbpm.graph.def.ProcessDefinition;
import org.jbpm.graph.exe.ProcessInstance;
public class ContextTest extends TestCase {
public void testContext() {
ProcessDefinition processDefinition = ProcessDefinition.parseXmlString(
"<process-definition>" +
" <start-state>" +
" <transition to='s' />" +
" </start-state>" +
" <state name='s'>" +
" <transition to='end' />" +
" </state>" +
" <end-state name='end' />" +
"</process-definition>"
);
ProcessInstance processInstance =
new ProcessInstance(processDefinition);
ContextInstance contextInstance =
processInstance.getContextInstance();
try
{
contextInstance.setVariable("test", new byte[0]);
fail("Should raise NullPointer exception");
}catch(NullPointerException e)
{
e.printStackTrace();
}
processInstance.signal();
}
}
Console output:
java.lang.NullPointerException
at java.util.ArrayList.<init>(ArrayList.java:133)
at org.jbpm.bytes.ByteArray.<init>(ByteArray.java:60)
at org.jbpm.context.log.variableinstance.ByteArrayUpdateLog.<init>(ByteArrayUpdateLog.java:41)
at org.jbpm.context.exe.variableinstance.ByteArrayInstance.setObject(ByteArrayInstance.java:44)
at org.jbpm.context.exe.VariableInstance.setValue(VariableInstance.java:137)
at org.jbpm.context.exe.VariableInstance.create(VariableInstance.java:74)
at org.jbpm.context.exe.VariableContainer.setVariableLocally(VariableContainer.java:157)
at org.jbpm.context.exe.VariableContainer.setVariable(VariableContainer.java:45)
at org.jbpm.context.exe.ContextInstance.setVariable(ContextInstance.java:166)
at org.jbpm.context.exe.ContextInstance.setVariable(ContextInstance.java:156)
...
--
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://jira.jboss.com/jira/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira
16 years, 11 months