| /* |
| * 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.OpenJPAEntityManager; |
| import org.apache.openjpa.persistence.common.utils.AbstractTestCase; |
| import org.apache.openjpa.persistence.kernel.common.apps.SCOTest; |
| |
| import junit.framework.AssertionFailedError; |
| |
| 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); |
| } |
| |
| @Override |
| 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", 1); |
| map.put("bar", 2); |
| for (int i = 0; i < 10; i++) |
| map.put("baz#" + i, 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(1, map.get("foo")); |
| assertEquals(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(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 (Object keyToDelete : ((HashMap) map.clone()).keySet()) { |
| 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 (Object key : map.keySet()) { |
| 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) { |
| |
| } |
| |
| StringBuilder buf = new StringBuilder().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) { |
| StringBuilder buf = new StringBuilder(); |
| 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() { |
| |
| } |
| |
| @Override |
| 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()); |
| } |
| } |
| } |