/* | |
* TestSecondClassValues.java | |
* | |
* Created on October 13, 2006, 5:29 PM | |
* | |
* To change this template, choose Tools | Template Manager | |
* and open the template in the editor. | |
*/ | |
/* | |
* Licensed to the Apache Software Foundation (ASF) under one | |
* or more contributor license agreements. See the NOTICE file | |
* distributed with this work for additional information | |
* regarding copyright ownership. The ASF licenses this file | |
* to you under the Apache License, Version 2.0 (the | |
* "License"); you may not use this file except in compliance | |
* with the License. You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, | |
* software distributed under the License is distributed on an | |
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
* KIND, either express or implied. See the License for the | |
* specific language governing permissions and limitations | |
* under the License. | |
*/ | |
package org.apache.openjpa.persistence.kernel; | |
import java.math.BigDecimal; | |
import java.math.BigInteger; | |
import java.text.Collator; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.Iterator; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import org.apache.openjpa.persistence.kernel.common.apps.SCOTest; | |
import org.apache.openjpa.persistence.common.utils.AbstractTestCase; | |
import junit.framework.AssertionFailedError; | |
import org.apache.openjpa.persistence.OpenJPAEntityManager; | |
public class TestSecondClassValues extends BaseKernelTest { | |
public static double DOUBLE_PRECISION = 0.0001D; | |
public static float FLOAT_PRECISION = 0.0001F; | |
// mprudhom: use optimistic so we don't hang on some databases | |
private OpenJPAEntityManager pm; | |
private String newline = System.getProperty("line.separator"); | |
/** | |
* Creates a new instance of TestSecondClassValues | |
*/ | |
public TestSecondClassValues() { | |
} | |
public TestSecondClassValues(String name) { | |
super(name); | |
} | |
public void setUp() { | |
pm = getPM(true, false); | |
} | |
private int rnd() { | |
return ((int) (Math.random() * 20)) + 5; | |
} | |
public void testMapDeletion() { | |
OpenJPAEntityManager pm; | |
pm = getPM(); | |
startTx(pm); | |
SCOTest test = new SCOTest(); | |
pm.persist(test); | |
Map map = new HashMap(); | |
map.put("foo", new Integer(1)); | |
map.put("bar", new Integer(2)); | |
for (int i = 0; i < 10; i++) | |
map.put("baz#" + i, new Integer(i)); | |
test.setStrIntMap(map); | |
Object id = pm.getObjectId(test); | |
endTx(pm); | |
startTx(pm); | |
test = (SCOTest) pm.find(SCOTest.class, id); | |
assertNotNull(test); | |
map = test.getStrIntMap(); | |
assertEquals(12, map.size()); | |
assertEquals(new Integer(1), map.get("foo")); | |
assertEquals(new Integer(2), map.get("bar")); | |
map.remove("bar"); | |
endTx(pm); | |
startTx(pm); | |
test = (SCOTest) pm.find(SCOTest.class, id); | |
assertNotNull(test); | |
map = test.getStrIntMap(); | |
assertEquals(11, map.size()); | |
assertEquals(new Integer(1), map.get("foo")); | |
assertTrue(map.get("bar") == null); | |
map.clear(); | |
endTx(pm); | |
startTx(pm); | |
test = (SCOTest) pm.find(SCOTest.class, id); | |
assertNotNull(test); | |
map = test.getStrIntMap(); | |
assertEquals(0, map.size()); | |
endTx(pm); | |
} | |
public void testStringCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomString()); | |
saveSecondClassCollection(list); | |
} | |
public void testLongCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomLong()); | |
try { | |
saveSecondClassCollection(list); | |
} catch (AssertionFailedError afe) { | |
bug(AbstractTestCase.Platform.EMPRESS, 889, afe, | |
"Empress cannot store large long values"); | |
} | |
} | |
public void testShortCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomShort()); | |
saveSecondClassCollection(list); | |
} | |
public void testBigIntegerCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomBigInteger()); | |
saveSecondClassCollection(list); | |
} | |
public void testBigDecimalCollection() | |
throws Exception { | |
try { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomBigDecimal()); | |
saveSecondClassCollection(list); | |
} catch (AssertionFailedError e) { | |
bug(3, e, "Precision loss for BigDecimals"); | |
} | |
} | |
public void testIntegerCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomInt()); | |
saveSecondClassCollection(list); | |
} | |
public void testByteCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomByte()); | |
saveSecondClassCollection(list); | |
} | |
public void testBooleanCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomBoolean()); | |
saveSecondClassCollection(list, true); | |
} | |
public void testFloatCollection() | |
throws Exception { | |
try { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomFloat()); | |
saveSecondClassCollection(list); | |
} catch (AssertionFailedError afe) { | |
bug(3, afe, "Loss of BigDecimal precision"); | |
} | |
} | |
public void testDoubleCollection() | |
throws Exception { | |
try { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomDouble()); | |
saveSecondClassCollection(list); | |
} catch (AssertionFailedError afe) { | |
bug(3, afe, "Loss of BigDecimal precision"); | |
} | |
} | |
public void testDateCollection() | |
throws Exception { | |
ArrayList list = new ArrayList(); | |
for (int i = 0; i < rnd(); i++) | |
list.add(randomDate()); | |
list.add(new Date(472246800000L)); | |
saveSecondClassCollection(list); | |
} | |
public void testBigDecimalBigIntegerMap() | |
throws Exception { | |
try { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomBigDecimal(), randomBigInteger()); | |
saveSecondClassMap(map); | |
} catch (AssertionFailedError e) { | |
bug(3, e, "Precision loss for BigDecimals"); | |
} | |
} | |
public void testStrIntMap() | |
throws Exception { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomString(), randomInt()); | |
saveSecondClassMap(map); | |
} | |
public void testIntLongMap() throws Exception { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomInt(), randomLong()); | |
try { | |
saveSecondClassMap(map); | |
} | |
catch (AssertionFailedError afe) { | |
bug(AbstractTestCase.Platform.EMPRESS, 889, afe, | |
"Empress cannot store large long values"); | |
} | |
} | |
public void testFloatByteMap() | |
throws Exception { | |
try { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomFloat(), randomByte()); | |
saveSecondClassMap(map); | |
} catch (AssertionFailedError afe) { | |
bug(3, afe, "Loss of BigDecimal precision"); | |
} | |
} | |
public void testByteDoubleMap() | |
throws Exception { | |
try { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomByte(), randomDouble()); | |
saveSecondClassMap(map); | |
} catch (AssertionFailedError afe) { | |
bug(3, afe, "Loss of BigDecimal precision"); | |
} | |
} | |
public void testDoubleCharMap() | |
throws Exception { | |
try { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomDouble(), randomChar()); | |
saveSecondClassMap(map); | |
} catch (AssertionFailedError afe) { | |
bug(3, afe, "Loss of BigDecimal precision"); | |
} | |
} | |
public void testCharBooleanMap() | |
throws Exception { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomChar(), randomBoolean()); | |
saveSecondClassMap(map); | |
} | |
public void testDateStrMap() throws Exception { | |
HashMap map = new HashMap(); | |
for (int i = 0; i < rnd(); i++) | |
map.put(randomDate(), randomString()); | |
map.put(new Date(472246800000L), | |
"PostgreSQL ain't gonna like this date"); | |
assertNotNull("map is null testDateStrMap", map); | |
saveSecondClassMap(map); | |
} | |
private void saveSecondClassMap(HashMap map) | |
throws Exception { | |
try { | |
saveSecondClassMapInternal(map); | |
} finally { | |
commit(); | |
} | |
} | |
private void commit() { | |
try { | |
assertNotNull(pm); | |
// EntityTransaction trans = pm.getTransaction(); | |
// if (trans != null && trans.isActive()) { | |
// trans.commit(); | |
endTx(pm); | |
} | |
catch (Exception e) { | |
e.printStackTrace(); | |
} | |
} | |
private void begin() { | |
commit(); // make sure we are clean | |
// get a fresh PM | |
pm = getPM(true, false); | |
startTx(pm); | |
} | |
/** | |
* Save the specified map as a second-class object and validate | |
* its contents by reading back in the object and comparing | |
* all the values to the original. Furthermore, this method | |
* deletes each element of both maps in turn and re-validates | |
* each time to make sure updating of the map is working correctly. | |
*/ | |
private void saveSecondClassMapInternal(HashMap map) throws Exception { | |
begin(); | |
SCOTest test = new SCOTest(); | |
pm.persist(test); | |
int testID = test.getId(); | |
assertNotNull("Passed Map is null", map); | |
Map smap = setGetMap(test, map, true); | |
assertNotNull("Map is null in setGetMap", smap); | |
commit(); | |
for (Iterator mapKey = ((HashMap) map.clone()).keySet().iterator(); | |
mapKey.hasNext();) { | |
Object keyToDelete = mapKey.next(); | |
begin(); | |
SCOTest retrievedObject = | |
(SCOTest) pm.find(SCOTest.class, testID); | |
assertNotNull( | |
"retrievedObject Obj is null - saveSecondClassMapInternal", | |
retrievedObject); | |
Map retrievedMap = setGetMap(retrievedObject, map, false); | |
assertNotNull( | |
"retrievedMap Obj is null - saveSecondClassMapInternal", | |
retrievedMap); | |
assertTrue(map.size() != 0); | |
assertEquals(map.size(), retrievedMap.size()); | |
assertTrue("Incompatible types", map.keySet().iterator().next(). | |
getClass().isAssignableFrom(retrievedMap.keySet(). | |
iterator().next().getClass())); | |
// check to make sure all the keys match up to the appropriate | |
// values. | |
for (Iterator i = map.keySet().iterator(); i.hasNext();) { | |
Object key = i.next(); | |
assertTrue(key != null); | |
assertTrue(map.get(key) != null); | |
if (key.getClass() == Date.class | |
&& retrievedMap.get(key) == null) { | |
getLog().trace("Time: " | |
+ (((Date) key).getTime())); | |
getLog().trace("List: " | |
+ dumpDates(retrievedMap.keySet())); | |
/* | |
bug (6, "Dates lose precision in some data stores " | |
+ "(" + (((Date)key).getTime ()) + "," | |
+ "[" + dumpDates (retrievedMap.keySet ()) + "])"); | |
*/ | |
} | |
if ((key.getClass() == Double.class || | |
key.getClass() == Float.class || | |
key.getClass() == BigDecimal.class) | |
&& retrievedMap.get(key) == null) { | |
/* | |
bug (3, "Doubles and Floats " | |
+ " lose precision in some data stores"); | |
*/ | |
} | |
assertTrue("The original map contained the object (class=" | |
+ key.getClass().getName() + ", value=" | |
+ key.toString() + "), but that object was null " | |
+ "in the map that was retrieved " | |
+ dump(retrievedMap.keySet()) + ".", | |
retrievedMap.get(key) != null); | |
assertClassAndValueEquals( | |
map.get(key), retrievedMap.get(key)); | |
} | |
// now delete the first key in both maps, and make sure | |
// thinks are still OK. | |
map.remove(keyToDelete); | |
retrievedMap.remove(keyToDelete); | |
} | |
} | |
private void saveSecondClassCollection(ArrayList collection) | |
throws Exception { | |
saveSecondClassCollection(collection, false); | |
} | |
private void saveSecondClassCollection(ArrayList collection, | |
boolean useCustomCollator) | |
throws Exception { | |
try { | |
saveSecondClassCollectionInternal(collection, useCustomCollator); | |
} finally { | |
commit(); | |
} | |
} | |
private void saveSecondClassCollectionInternal(ArrayList collection, | |
boolean useCustomCollator) | |
throws Exception { | |
Object elementToDelete = null; | |
if (useCustomCollator) | |
Collections.sort(collection, | |
new CollectionSorter()); | |
else | |
Collections.sort(collection); | |
OpenJPAEntityManager pm1 = getPM(); | |
startTx(pm1); | |
SCOTest test = new SCOTest(); | |
pm1.persist(test); | |
int testID = test.getId(); | |
Collection storedCollection = setGetCollection(test, | |
(Collection) ((ArrayList) collection).clone(), true); | |
assertNotNull("retrieved storedCollection is null", storedCollection); | |
// make sure the pre-commit collections are identical! | |
assertEquals("Pre-commit collections were not equal: " + newline | |
+ dump(collection) + newline + "!=" + newline | |
+ dump(storedCollection), | |
collection.size(), storedCollection.size()); | |
endTx(pm1); | |
int deletionIndex = 0; | |
OpenJPAEntityManager pm2 = getPM(); | |
while (collection.size() > 0) { | |
deletionIndex++; | |
startTx(pm2); | |
SCOTest retrievedObject = | |
(SCOTest) pm2.find(SCOTest.class, testID); | |
assertNotNull( | |
"retrieved obj is null saveSecondClassCollectionInternal", | |
retrievedObject); | |
Collection identityCollection = new LinkedList(collection); | |
assertNotNull( | |
"identityCollection is null saveSecondClassCollectionInternal", | |
identityCollection); | |
Collection retrievedCollection = setGetCollection( | |
retrievedObject, identityCollection, false); | |
assertNotNull( | |
"retrievedCollection is null saveSecondClassCollectionInternal", | |
retrievedCollection); | |
validateCollection(retrievedCollection); | |
assertNotNull(retrievedCollection); | |
assertTrue(collection.size() != 0); | |
assertEquals("Retreived collection does not match original " | |
+ "after the " + deletionIndex + "th deletion (" | |
+ elementToDelete + "): " | |
+ newline | |
+ dump(collection) + newline + "!=" + newline | |
+ dump(retrievedCollection) + newline, | |
collection.size(), retrievedCollection.size()); | |
/* | |
try | |
{ | |
assertEquals (collection.size retrievedCollection.size ()); | |
} catch (AssertionFailedError afe) { | |
bug (AbstractTestCase.Platform.SQLSERVER, 2, afe, "Second-class collections" | |
+ " are not being retrieved correctly"); | |
} | |
*/ | |
// make sure the classes of the keys are the same. | |
Iterator ci = collection.iterator(); | |
Object co = collection.iterator().next(); | |
Iterator rci = retrievedCollection.iterator(); | |
Object rco = retrievedCollection.iterator().next(); | |
assertNotNull(co); | |
assertNotNull(rco); | |
assertEquals(co.getClass(), rco.getClass()); | |
List sortedRetreivedCollection = | |
new ArrayList(retrievedCollection); | |
if (useCustomCollator) | |
Collections.sort(sortedRetreivedCollection, | |
new CollectionSorter()); | |
else | |
Collections.sort(sortedRetreivedCollection); | |
// make sure the collection is OK | |
for (Iterator i = collection.iterator(), | |
j = sortedRetreivedCollection.iterator(); | |
i.hasNext() && j.hasNext();) { | |
assertClassAndValueEquals(i.next(), j.next()); | |
} | |
elementToDelete = collection.iterator().next(); | |
if (!(collection.remove(elementToDelete))) | |
fail("Could not delete element " | |
+ "(<" + elementToDelete.getClass().getName() + ">" | |
+ elementToDelete + ") " | |
+ "from " + dump(collection)); | |
if (!(retrievedCollection.remove(elementToDelete))) | |
fail("Could not delete element (" + elementToDelete + ") " | |
+ "from " + dump(retrievedCollection)); | |
endTx(pm2); | |
} | |
} | |
private void assertClassAndValueEquals(Object o1, Object o2) { | |
assertTrue("First object was null", o1 != null); | |
assertTrue("Second object was null", o2 != null); | |
assertTrue("Types did not match (class1=" | |
+ o1.getClass().getName() + ", class2=" | |
+ o2.getClass().getName() + ")", | |
o1.getClass().isAssignableFrom(o2.getClass())); | |
// floats and doubles are a little special: we only | |
// compare them to a certain precision, after which | |
// we give up. | |
/* | |
if (o1 instanceof Double) | |
assertEquals (((Double)o1).doubleValue (), | |
((Double)o2).doubleValue (), | |
DOUBLE_PRECISION); | |
else if (o1 instanceof Float) | |
assertEquals (((Float)o1).floatValue (), | |
((Float)o2).floatValue (), | |
FLOAT_PRECISION); | |
else if (o1 instanceof BigDecimal) | |
// BigDecimal equalist is a little special: see | |
// JDORuntimeTestCase.assertEquals(BigDecimal,BigDecimal) | |
assertEquals ("BigDecimal did not match", | |
(BigDecimal)o1, (BigDecimal)o2); | |
else | |
*/ | |
assertEquals("Object did not match (class1=" | |
+ o1.getClass().getName() + ", class2=" | |
+ o2.getClass().getName() + ")", | |
o1, o2); | |
} | |
private String dump(Collection coll) { | |
List list = new LinkedList(coll); | |
try { | |
Collections.sort(list); | |
} catch (RuntimeException e) { | |
} | |
StringBuffer buf = new StringBuffer().append("[") | |
.append("(size=").append(list.size()).append(")"); | |
Iterator it = list.iterator(); | |
if (it.hasNext()) | |
buf.append("<class=" + it.next().getClass().getName() + ">"); | |
for (Iterator i = list.iterator(); i.hasNext();) | |
buf.append(i.next()).append(i.hasNext() ? "," : ""); | |
return buf.append("]").toString(); | |
} | |
private String dumpDates(Collection coll) { | |
StringBuffer buf = new StringBuffer(); | |
for (Iterator i = coll.iterator(); i.hasNext();) | |
buf.append(((Date) i.next()).getTime()).append( | |
i.hasNext() ? "," : ""); | |
return buf.toString(); | |
} | |
/** | |
* Generic setter/getter for setting the maps purposes. | |
*/ | |
private Map setGetMap(SCOTest test, HashMap map, boolean doSet) { | |
if (map == null) | |
return null; | |
Object key = map.keySet().iterator().next(); | |
Object val = map.get(key); | |
if (key instanceof Date && val instanceof String) { | |
if (doSet) | |
test.setDateStrMap(map); | |
return test.getDateStrMap(); | |
} else if (key instanceof Character && val instanceof Boolean) { | |
if (doSet) | |
test.setCharBooleanMap(map); | |
return test.getCharBooleanMap(); | |
} else if (key instanceof Double && val instanceof Character) { | |
if (doSet) | |
test.setDoubleCharMap(map); | |
return test.getDoubleCharMap(); | |
} else if (key instanceof Byte && val instanceof Double) { | |
if (doSet) | |
test.setByteDoubleMap(map); | |
return test.getByteDoubleMap(); | |
} else if (key instanceof Float && val instanceof Byte) { | |
if (doSet) | |
test.setFloatByteMap(map); | |
return test.getFloatByteMap(); | |
} else if (key instanceof Long && val instanceof Float) { | |
if (doSet) | |
test.setLongFloatMap(map); | |
return test.getLongFloatMap(); | |
} else if (key instanceof Integer && val instanceof Long) { | |
if (doSet) | |
test.setIntLongMap(map); | |
return test.getIntLongMap(); | |
} else if (key instanceof String && val instanceof Integer) { | |
if (doSet) | |
test.setStrIntMap(map); | |
return test.getStrIntMap(); | |
} else if (key instanceof BigDecimal && val instanceof BigInteger) { | |
if (doSet) | |
test.setBigDecimalBigIntegerMap(map); | |
return test.getBigDecimalBigIntegerMap(); | |
} | |
fail("Unknown map type"); | |
return null; | |
} | |
/** | |
* Generic setter/getter for setting the collections purposes. | |
*/ | |
private Collection setGetCollection(SCOTest test, | |
Collection collection, boolean doSet) { | |
if (collection == null) | |
return null; | |
Object first = collection.iterator().next(); | |
if (first instanceof BigInteger) { | |
if (doSet) | |
test.setCBigInteger(collection); | |
return test.getCBigInteger(); | |
} else if (first instanceof BigDecimal) { | |
if (doSet) | |
test.setCBigDecimal(collection); | |
return test.getCBigDecimal(); | |
} else if (first instanceof Date) { | |
if (doSet) | |
test.setCDate(collection); | |
return test.getCDate(); | |
} else if (first instanceof Character) { | |
if (doSet) | |
test.setCCharacter(collection); | |
return test.getCCharacter(); | |
} else if (first instanceof Double) { | |
if (doSet) | |
test.setCDouble(collection); | |
return test.getCDouble(); | |
} else if (first instanceof Byte) { | |
if (doSet) | |
test.setCByte(collection); | |
return test.getCByte(); | |
} else if (first instanceof Float) { | |
if (doSet) | |
test.setCFloat(collection); | |
return test.getCFloat(); | |
} else if (first instanceof Long) { | |
if (doSet) | |
test.setCLong(collection); | |
return test.getCLong(); | |
} else if (first instanceof Integer) { | |
if (doSet) | |
test.setCInteger(collection); | |
return test.getCInteger(); | |
} else if (first instanceof String) { | |
if (doSet) | |
test.setCString(collection); | |
return test.getCString(); | |
} else if (first instanceof Short) { | |
if (doSet) | |
test.setCShort(collection); | |
return test.getCShort(); | |
} else if (first instanceof Boolean) { | |
if (doSet) | |
test.setCBoolean(collection); | |
return test.getCBoolean(); | |
} | |
fail("Unknown collection type"); | |
return null; | |
} | |
/** | |
* A simple sorter that should always return the same sort order. | |
* The only reason we need ti use this, instead of relying on the | |
* natural order in Collections.sort is that there seems to be | |
* a bug somewhere that prevents sorting on collections of Boolean | |
* objects. | |
*/ | |
public static class CollectionSorter | |
implements Comparator { | |
private Collator collator = Collator.getInstance(); | |
public CollectionSorter() { | |
} | |
public int compare(Object o1, Object o2) { | |
if (o1 != null && !(o1 instanceof Boolean)) | |
return collator.compare(o1, o2); | |
return collator.compare(o1.toString(), o2.toString()); | |
} | |
} | |
} |