Issue Details (XML | Word | Printable)

Key: NUCCORE-496
Type: Bug Bug
Status: Closed Closed
Resolution: Fixed
Priority: Major Major
Assignee: Andy Jefferson
Reporter: Sudipta
Votes: 0
Watchers: 1
Operations

If you were logged in you would be able to see more operations.
DataNucleus Core

NullPointer Exception from listIterator() when calling set() on the iterator

Created: 23/Mar/10 08:33 PM   Updated: 04/Oct/12 05:02 PM   Resolved: 31/Mar/10 08:12 PM
Component/s: Persistence
Affects Version/s: 2.0.0.release, 2.0.1, 2.0.2
Fix Version/s: 2.0.3, 2.1.0.m1

File Attachments: 1. Zip Archive test-rule.zip (10 kB)


Forum Thread URL: http://www.jpox.org/servlet/forum/viewthread_thread,6016
Datastore: PostgreSQL


 Description  « Hide
Hi,
I am getting an ArrayIndexOutOfBoundsException in org.datanucleus.sco.backed.LinkedList.

Following is the structure of my persistent classes:
public class ClassRuleSet implements Comparable {
private String name;
private Map<String, ClassRuleList> rulesMap;

public ClassRuleSet(String name) {
this.name = name;
rulesMap = new HashMap<String, ClassRuleList>();
}

public int compareTo(Object obj) {
if ((obj == null) || ! (obj instanceof ClassRuleSet)) {
return 1;
}
ClassRuleSet ruleset = (ClassRuleSet)obj;
return name.compareTo(ruleset.name);
}

void putRule(ClassRule rule) {
String deviceTypeGuid = rule.getDeviceTypeGuid();
if (deviceTypeGuid == null) {
String m = "no deviceTypeGuid for rule, cannot put rule in ruleset";
throw new IllegalArgumentException(m);
}
ClassRuleList rules = rulesMap.get(deviceTypeGuid);
if (rules == null) {
rules = new ClassRuleList();
rulesMap.put(deviceTypeGuid, rules);
}
rules.add(rule);
}
}

public class ClassRuleList {
private List<ClassRule> rules;

ClassRuleList() {
rules = new LinkedList<ClassRule>();
}

void add(ClassRule rule) {
rules.add(rule);
sort();
}

void sort() {

Collections.sort(rules); //This is the place where it is throwing ArrayIndexBound exception
}
}

public class ClassRule
implements Cloneable, Comparable {

private String deviceTypeGuid;
private String name;
private ClassRuleSet ruleset;

public ClassRule(String name) {
this.name = name;
}

public int compareTo(Object obj) {
if ((obj == null) || !(obj instanceof ClassRule)) {
return 1;
}
ClassRule rule = (ClassRule)obj;
return name.compareTo(rule.name);
}

public void setRuleSet(ClassRuleSet ruleset) {
if (this.ruleset != null) {
if (this.ruleset.equals(ruleset)) {
return;
}
if (deviceTypeGuid != null) {
this.ruleset.disconnectRule(this);
}
}
this.ruleset = ruleset;
if ((ruleset != null) && (deviceTypeGuid != null)) {
this.ruleset.putRule(this);
}
}
}

The main program:

public void addRule() throws Exception {
PersistenceManager pm = pmf.getPersistenceManager();
ClassRuleSet ruleSet = null;
Transaction tx = pm.currentTransaction();
tx.setOptimistic(true);
tx.begin();

//Create a RuleSet
Random random = new Random();
String ruleSetName = "RuleSet" + random.nextInt();
ruleSet = new ClassRuleSet(ruleSetName);
System.out.println(ruleSet);
pm.makePersistent(ruleSet);

//Create a first rule and set the rule set of that rule to the one created above
ClassRule rule = new ClassRule("Rule5");
rule.setDeviceTypeGuid("XXX");
rule.setSubstitutionValidation(true);
rule.setRuleSet(ruleSet);
tx.commit();

//Create 2nd rule and set the ruleset of that rule to the one created above
//The rule set in this case is retrieved using a JDO query

tx.begin();
Query query = pm.newQuery(ClassRuleSet.class);
query.setFilter("name == \"" + ruleSetName + "\"");
Collection results = (Collection)query.execute();
Iterator iter = results.iterator();
while (iter.hasNext()) {
ruleSet = (ClassRuleSet)iter.next();
}
query.closeAll();
rule = new ClassRule("Rule4");
rule.setDeviceTypeGuid("XXX");
rule.setSubstitutionValidation(true);
rule.setRuleSet(ruleSet); //This call produces the ArrayIndexOutOfBoundsException
tx.commit();

//Create 3rd rule and set the ruleset of that rule to the one created above
//The rule set in this case is retrieved using a JDO query
tx.begin();
query = pm.newQuery(ClassRuleSet.class);
query.setFilter("name == \"" + ruleSetName + "\"");
results = (Collection)query.execute();
iter = results.iterator();
while (iter.hasNext()) {
ruleSet = (ClassRuleSet)iter.next();
}
query.closeAll();
rule = new ClassRule("Rule6");
rule.setDeviceTypeGuid("XXX");
rule.setSubstitutionValidation(true);
rule.setRuleSet(ruleSet);

tx.commit();
pm.close();

The jdo mapping is as follows:

<package name="foo">
<class name="ClassRuleSet" table="class_rule_set">
<primary-key>
<column name="class_rule_set_id" jdbc-type="INTEGER"/>
</primary-key>
<version strategy="version-number" column="jdo_version"/>
<field name="name" column="nme">
<index name="idx_class_rule_set_name"/>
</field>
<field name="rulesMap" table="class_rule_set_rules_map">
<join column="class_rule_set_id" delete-action="none"/>
</field>
</class>

<class name="ClassRuleList" table="class_rule_list">
<primary-key>
<column name="class_rule_list_id" jdbc-type="INTEGER"/>
</primary-key>
<version strategy="version-number" column="jdo_version"/>
<field name="rules" table="class_rule_list_rle">
<join column="class_rule_list_id"/>
<element column="class_rle_id"/>
<order column="seq"/>
</field>
</class>

<class name="ClassRule" table="class_rle">
<primary-key>
<column name="class_rle_id" jdbc-type="INTEGER"/>
</primary-key>
<version strategy="version-number" column="jdo_version"/>
<field name="name" column="nme"/>
</class>
</package>

The jdo properties:
javax.jdo.PersistenceManagerFactoryClass=org.datanucleus.jdo.JDOPersistenceManagerFactory

javax.jdo.option.ConnectionDriverName=org.postgresql.Driver
javax.jdo.option.ConnectionURL=jdbc:postgresql://localhost:5434/bcan
javax.jdo.option.ConnectionUserName=bcan
javax.jdo.option.ConnectionPassword=

javax.jdo.option.IgnoreCache=false
javax.jdo.option.Multithreaded=false
javax.jdo.option.NontransactionalRead=true
javax.jdo.option.NontransactionalWrite=false
javax.jdo.option.Optimistic=true
javax.jdo.option.RetainValues=true
javax.jdo.option.RestoreValues=false
datanucleus.cache.level2.type=soft

datanucleus.autoCreateSchema=false
datanucleus.validateTables=false
datanucleus.validateConstraints=false
datanucleus.validateColumns=false

datanucleus.identifier.case=LowerCase
datanucleus.rdbms.constraintCreateMode=JDO2
datanucleus.query.JDOQL.implementation=JDOQL2
datanucleus.query.flushBeforeExecution=true


I am getting the following exception while trying to run the main program. The exception happens while trying to set the ruleset of the 2nd rule.
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
at java.util.LinkedList.entry(LinkedList.java:365)
at java.util.LinkedList.set(LinkedList.java:328)
at org.datanucleus.sco.backed.LinkedList.set(LinkedList.java:1069)
at org.datanucleus.sco.backed.LinkedList.set(LinkedList.java:1092)
at org.datanucleus.sco.SCOListIterator.set(SCOListIterator.java:157)
at java.util.Collections.sort(Collections.java:121)
at foo.ClassRuleList.sort(ClassRuleList.java:111)
at foo.ClassRuleList.add(ClassRuleList.java:40)
at foo.ClassRuleSet.putRule(ClassRuleSet.java:227)
at foo.ClassRule.setRuleSet(ClassRule.java:220)
at foo.Main.addRule(Main.java:53)
at foo.Main.main(Main.java:101)

One thing that I noticed is that it only happens for the 2nd rule. I put a try/catch in the main program around the code for creating and setting rule set of the 2nd rule, the exception was thrown for the 2nd rule, but the third rule was added successfully.


Sort Order: Ascending order - Click to sort in descending order
Sudipta added a comment - 23/Mar/10 08:36 PM
Test case

Andy Jefferson added a comment - 24/Mar/10 06:23 PM
Suggest that you review the testcase definition to save time in future
1. org.datanucleus.test package
2. no idea what all that code for opening a properties file is about
   pmf = JDOHelper.getPersistenceManagerFactory("datanucleus.properties");
3. datanucleus.properties file
4. "foo.jdo" is illegal. JDO1.0.1 and later all use "package.jdo", and only would be accepted since DN also allows that non-standard location.

No comments on the actual issue since no time to run it. the log would tell you what was being called (by Collections.sort, which we don't use in any of our tests, so who knows what sequence of operations it makes use of)

Andy Jefferson added a comment - 31/Mar/10 08:12 PM
SVN trunk and branches/2.0 fixes this incorrect handling of ListIterator.set()

Peter Dettman added a comment - 16/Apr/10 04:41 AM
I believe the set() method of SCOListIterator is still incorrect, and a few other methods too.

The javadocs for java.util.ListIterator explain:
"Note that the remove() and set(Object) methods are not defined in terms of the cursor position; they are defined to operate on the last element returned by a call to next() or previous()."

I am attaching a patch (against HEAD) including
1. A new test case in SCOListIteratorTest that focuses on add/remove/set in various combinations.
2. A re-implementation of several methods of SCOListIterator to pass this new test and the existing ones.

In the new version, the class relies on it's internal ListIterator ('iter') to enforce all the usage restrictions; all calls are forwarded to 'iter' before any side-effects are applied to the fields, or the SCO (to avoid exceptions leaving it in an unexpected state). Several methods rely on 'iter' to determine the relevant index for a call to the SCO.


Peter Dettman added a comment - 16/Apr/10 06:39 AM
Actually I'm unable to attach to this Closed issue. Will attach to forum post pending activation of my account there.

Andy Jefferson added a comment - 16/Apr/10 08:34 AM
If you have a problem with a release you don't attach something to a closed issue, cos its closed and released. Simply raise a new issue (far easier), and set the "Affects Versions" as the release(s) that it is a problem in.