Java tutorial
/* * Copyright (c) 2008, SQL Power Group Inc. * * This file is part of Wabit. * * Wabit is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * Wabit is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.wabit; import java.awt.Image; import java.beans.PropertyDescriptor; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import junit.framework.TestCase; import org.apache.commons.beanutils.BeanUtils; import org.apache.commons.beanutils.PropertyUtils; import org.apache.log4j.Logger; import ca.sqlpower.dao.PersistedSPOProperty; import ca.sqlpower.dao.PersistedSPObject; import ca.sqlpower.dao.PersisterUtils; import ca.sqlpower.dao.SPPersistenceException; import ca.sqlpower.dao.SPPersister; import ca.sqlpower.dao.StubSPPersister; import ca.sqlpower.dao.SPPersister.DataType; import ca.sqlpower.object.SPChildEvent; import ca.sqlpower.object.SPObject; import ca.sqlpower.object.SPChildEvent.EventType; import ca.sqlpower.sql.DataSourceCollection; import ca.sqlpower.sql.JDBCDataSource; import ca.sqlpower.sql.Olap4jDataSource; import ca.sqlpower.sql.PlDotIni; import ca.sqlpower.sql.SPDataSource; import ca.sqlpower.sqlobject.SQLDatabase; import ca.sqlpower.sqlobject.SQLDatabaseMapping; import ca.sqlpower.testutil.NewValueMaker; import ca.sqlpower.util.SQLPowerUtils; import ca.sqlpower.wabit.dao.CountingWabitPersister; import ca.sqlpower.wabit.dao.WabitSessionPersister; import ca.sqlpower.wabit.dao.session.WabitSessionPersisterSuperConverter; import ca.sqlpower.wabit.dao.session.WorkspacePersisterListener; import ca.sqlpower.wabit.rs.olap.OlapConnectionPool; import ca.sqlpower.wabit.rs.olap.OlapQuery; /** * A baseline test that all tests for WabitObject implementations should pass. * The intention is that those test classes will extend this class, thereby * inheriting the baseline tests. While this test can be run with any SPObject, * it is only intended to be run by those that can be handled by the * {@link WabitSessionPersister}. */ public abstract class AbstractWabitObjectTest extends TestCase { /** * Small implementation of the WabitPersister that will throw an exception on commit * when its error state is set to true. */ public static class ErrorWabitPersister extends StubSPPersister { private int transactionCount = 0; private boolean throwError = false; @Override public void begin() throws SPPersistenceException { transactionCount++; } public void commit() throws SPPersistenceException { transactionCount--; if (transactionCount == 0 && throwError) { throw new SPPersistenceException(null, "Cause everything to rollback"); } } public void setThrowError(boolean willThrowError) { throwError = willThrowError; } }; private static final Logger logger = Logger.getLogger(AbstractWabitObjectTest.class); /** * Returns the object being tested. This will typically have been * created by the subclass's setUp method. The object returned * by this method must be in the workspace returned by {@link #getWorkspace()}. */ public abstract SPObject getObjectUnderTest(); /** * A session that is hooked up to a pl.ini that is used for regression testing. */ private WabitSession session; private NewValueMaker valueMaker; /** * A converter for use in tests involving persisting objects. */ private WabitSessionPersisterSuperConverter converterFactory; private WabitWorkspace workspace; private StubWabitSessionContext context; public WabitWorkspace getWorkspace() { return this.workspace; } public StubWabitSessionContext getContext() { return context; } public WabitSession getSession() { return session; } /** * Returns a list of JavaBeans property names that should be ignored when * testing for proper events. */ public Set<String> getPropertiesToIgnoreForEvents() { Set<String> ignore = new HashSet<String>(); ignore.add("class"); ignore.add("session"); ignore.add("workspaceContainer"); ignore.add("runnableDispatcher"); ignore.add("magicEnabled"); return ignore; } /** * These properties, on top of the properties ignored for events, will be * ignored when checking the properties of a specific {@link WabitObject} * are persisted. */ public Set<String> getPropertiesToIgnoreForPersisting() { return new HashSet<String>(); } /** * Returns a list of JavaBeans property names that should be ignored when * testing that all of the properties of an object are persisted when the * object itself is being persisted. */ public Set<String> getPropertiesToNotPersistOnObjectPersist() { Set<String> ignore = new HashSet<String>(); ignore.add("class"); ignore.add("children"); ignore.add("parent"); ignore.add("dependencies"); ignore.add("UUID"); ignore.add("session"); ignore.add("workspaceContainer"); ignore.add("runnableDispatcher"); ignore.add("allowedChildTypes"); ignore.add("magicEnabled"); ignore.add("permsString"); return ignore; } @Override protected void setUp() throws Exception { super.setUp(); final PlDotIni plIni = new PlDotIni(); plIni.read(new File("src/test/resources/pl.regression.ini")); final Olap4jDataSource olapDS = plIni.getDataSource("World Facts OLAP Connection", Olap4jDataSource.class); if (olapDS == null) throw new IllegalStateException("Cannot find 'World Facts OLAP Connection'"); final OlapConnectionPool connectionPool = new OlapConnectionPool(olapDS, new SQLDatabaseMapping() { private final SQLDatabase sqlDB = new SQLDatabase(olapDS.getDataSource()); public SQLDatabase getDatabase(JDBCDataSource ds) { return sqlDB; } }); this.context = new StubWabitSessionContext() { public org.olap4j.OlapConnection createConnection(Olap4jDataSource dataSource) throws java.sql.SQLException, ClassNotFoundException, javax.naming.NamingException { return connectionPool.getConnection(); }; public DataSourceCollection<SPDataSource> getDataSources() { return plIni; } }; this.session = new StubWabitSession(context) { @Override public DataSourceCollection<SPDataSource> getDataSources() { return getContext().getDataSources(); } @Override public WabitWorkspace getWorkspace() { return workspace; } }; this.workspace = new WabitWorkspace(); this.workspace.setSession(session); valueMaker = new WabitNewValueMaker(getWorkspace(), plIni); converterFactory = new WabitSessionPersisterSuperConverter(session, getWorkspace(), true); } /** * For the persister tests to work the object under test must be in the * workspace in this session. */ public void testObjectUnderTestInWorkspace() throws Exception { assertNotNull(getWorkspace().findByUuid(getObjectUnderTest().getUUID(), getObjectUnderTest().getClass())); } /** * Uses reflection to find all the settable properties of the object under test, * and fails if any of them can be set without an event happening. */ public void testSettingPropertiesFiresEvents() throws Exception { CountingWabitListener listener = new CountingWabitListener(); SPObject wo = getObjectUnderTest(); wo.addSPListener(listener); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass())); Set<String> propertiesToIgnoreForEvents = getPropertiesToIgnoreForEvents(); for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForEvents.contains(property.getName())) continue; try { oldVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } int oldChangeCount = listener.getPropertyChangeCount(); Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName()); try { logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' (" + newVal.getClass().getName() + ")"); BeanUtils.copyProperty(wo, property.getName(), newVal); // some setters fire multiple events (they change more than one property) assertTrue( "Event for set " + property.getName() + " on " + wo.getClass().getName() + " didn't fire!", listener.getPropertyChangeCount() > oldChangeCount); if (listener.getPropertyChangeCount() == oldChangeCount + 1) { assertEquals("Property name mismatch for " + property.getName() + " in " + wo.getClass(), property.getName(), listener.getLastPropertyEvent().getPropertyName()); assertEquals("New value for " + property.getName() + " was wrong", newVal, listener.getLastPropertyEvent().getNewValue()); } } catch (InvocationTargetException e) { logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type " + wo.getClass().getName()); } } } /** * This will reflectively iterate over all of the properties in the Wabit * object and set each value that has a setter and getter. When the property * is set it should cause the property to be persisted through the * {@link WorkspacePersisterListener}. */ public void testPropertiesArePersisted() throws Exception { CountingWabitPersister countingPersister = new CountingWabitPersister(); WorkspacePersisterListener listener = new WorkspacePersisterListener( new StubWabitSession(new StubWabitSessionContext()), countingPersister, true); SPObject wo = getObjectUnderTest(); wo.addSPListener(listener); WabitSessionPersisterSuperConverter converterFactory = new WabitSessionPersisterSuperConverter( new StubWabitSession(new StubWabitSessionContext()), new WabitWorkspace(), true); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass())); //Ignore properties that are not in events because we won't have an event //to respond to. Set<String> propertiesToIgnoreForEvents = getPropertiesToIgnoreForEvents(); Set<String> propertiesToIgnoreForPersisting = getPropertiesToIgnoreForPersisting(); for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForEvents.contains(property.getName())) continue; if (propertiesToIgnoreForPersisting.contains(property.getName())) continue; countingPersister.clearAllPropertyChanges(); try { oldVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName()); int oldChangeCount = countingPersister.getPersistPropertyCount(); try { logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' (" + newVal.getClass().getName() + ")"); BeanUtils.copyProperty(wo, property.getName(), newVal); assertTrue("Did not persist property " + property.getName(), oldChangeCount < countingPersister.getPersistPropertyCount()); //The first property change at current is always the property change we are //looking for, this may need to be changed in the future to find the correct //property. PersistedSPOProperty propertyChange = null; for (PersistedSPOProperty nextPropertyChange : countingPersister.getAllPropertyChanges()) { if (nextPropertyChange.getPropertyName().equals(property.getName())) { propertyChange = nextPropertyChange; break; } } assertNotNull("A property change event cannot be found for the property " + property.getName(), propertyChange); assertEquals(wo.getUUID(), propertyChange.getUUID()); assertEquals(property.getName(), propertyChange.getPropertyName()); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && property.getName().equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } Object oldConvertedType = converterFactory.convertToBasicType(oldVal, additionalVals.toArray()); assertEquals( "Old value of property " + property.getName() + " was wrong, value expected was " + oldConvertedType + " but is " + countingPersister.getLastOldValue(), oldConvertedType, propertyChange.getOldValue()); //Input streams from images are being compared by hash code not values if (Image.class.isAssignableFrom(property.getPropertyType())) { logger.debug(propertyChange.getNewValue().getClass()); assertTrue(Arrays.equals(PersisterUtils.convertImageToStreamAsPNG((Image) newVal).toByteArray(), PersisterUtils .convertImageToStreamAsPNG((Image) converterFactory .convertToComplexType(propertyChange.getNewValue(), Image.class)) .toByteArray())); } else { assertEquals(converterFactory.convertToBasicType(newVal, additionalVals.toArray()), propertyChange.getNewValue()); } Class<? extends Object> classType; if (oldVal != null) { classType = oldVal.getClass(); } else { classType = newVal.getClass(); } assertEquals(PersisterUtils.getDataType(classType), propertyChange.getDataType()); } catch (InvocationTargetException e) { logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type " + wo.getClass().getName()); } } } /** * Returns the specific class type that is the parent of this WabitObject. * This returns {@link WabitObject} which works for most cases but some specific * instances have a tighter parent type. */ public Class<? extends SPObject> getParentClass() { return WabitObject.class; } /** * This test uses the object under test to ensure that the * {@link WabitSessionPersister} updates each property appropriately on * persistence. */ public void testPersisterUpdatesProperties() throws Exception { SPObject wo = getObjectUnderTest(); WabitSessionPersister persister = new WabitSessionPersister("secondary test persister", session, getWorkspace(), true); WabitSessionPersisterSuperConverter converterFactory = new WabitSessionPersisterSuperConverter( new StubWabitSession(new StubWabitSessionContext()), new WabitWorkspace(), true); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass())); //Ignore properties that are not in events because we won't have an event //to respond to. Set<String> propertiesToIgnoreForEvents = getPropertiesToIgnoreForEvents(); Set<String> propertiesToIgnoreForPersisting = getPropertiesToIgnoreForPersisting(); for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForEvents.contains(property.getName())) continue; if (propertiesToIgnoreForPersisting.contains(property.getName())) continue; try { oldVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } //special case for parent types. If a specific wabit object has a tighter parent then //WabitObject the getParentClass should return the parent type. Class<?> propertyType = property.getPropertyType(); if (property.getName().equals("parent")) { propertyType = getParentClass(); } Object newVal = valueMaker.makeNewValue(propertyType, oldVal, property.getName()); logger.debug("Persisting property \"" + property.getName() + "\" from oldVal \"" + oldVal + "\" to newVal \"" + newVal + "\""); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && property.getName().equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } DataType type = PersisterUtils.getDataType(property.getPropertyType()); Object basicNewValue = converterFactory.convertToBasicType(newVal, additionalVals.toArray()); persister.begin(); persister.persistProperty(wo.getUUID(), property.getName(), type, converterFactory.convertToBasicType(oldVal, additionalVals.toArray()), basicNewValue); persister.commit(); Object newValAfterSet = PropertyUtils.getSimpleProperty(wo, property.getName()); Object basicExpectedValue = converterFactory.convertToBasicType(newValAfterSet, additionalVals.toArray()); assertPersistedValuesAreEqual(newVal, newValAfterSet, basicNewValue, basicExpectedValue, property.getPropertyType()); } } /** * Tests that the new value that was persisted is the same as an old value * that was to be persisted. This helper method for the persister tests will * compare the values by their converted type or some other means as not all * values that are persisted have implemented their equals method. * <p> * This will do the asserts to compare if the objects are equal. * * @param valueBeforePersist * the value that we are expecting the persisted value to contain * @param valueAfterPersist * the value that was persisted to the object. This will be * tested against the valueBeforePersist to ensure that they are * the same. * @param basicValueBeforePersist * The valueBeforePersist converted to a basic type by a * converter. * @param basicValueAfterPersist * The valueAfterPersist converted to a basic type by a * converter. * @param valueType * The type of object the before and after values should contain. */ private void assertPersistedValuesAreEqual(Object valueBeforePersist, Object valueAfterPersist, Object basicValueBeforePersist, Object basicValueAfterPersist, Class<? extends Object> valueType) { //Input streams from images are being compared by hash code not values if (Image.class.isAssignableFrom(valueType)) { assertTrue(Arrays.equals( PersisterUtils.convertImageToStreamAsPNG((Image) valueBeforePersist).toByteArray(), PersisterUtils.convertImageToStreamAsPNG((Image) valueAfterPersist).toByteArray())); } else { //Not all new values are equivalent to their old values so we are //comparing them by their basic type as that is at least comparable, in most cases, i hope. assertEquals("Persist failed for type " + valueType, basicValueBeforePersist, basicValueAfterPersist); } } /** * Tests that calling * {@link SPPersister#persistObject(String, String, String, int)} for a * session persister will create a new object and set all of the properties * on the object. */ public void testPersisterAddsNewObject() throws Exception { SPObject wo = getObjectUnderTest(); wo.setMagicEnabled(false); WabitSessionPersister persister = new WabitSessionPersister("test persister", session, session.getWorkspace(), true); WorkspacePersisterListener listener = new WorkspacePersisterListener(session, persister, true); WabitSessionPersisterSuperConverter converterFactory = new WabitSessionPersisterSuperConverter( new StubWabitSession(new StubWabitSessionContext()), new WabitWorkspace(), true); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass())); //Set all possible values to new values for testing. Set<String> propertiesToIgnoreForEvents = getPropertiesToIgnoreForEvents(); for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnoreForEvents.contains(property.getName())) continue; if (property.getName().equals("parent")) continue; //Changing the parent causes headaches. try { oldVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } Object newVal = valueMaker.makeNewValue(property.getPropertyType(), oldVal, property.getName()); try { logger.debug("Setting property '" + property.getName() + "' to '" + newVal + "' (" + newVal.getClass().getName() + ")"); BeanUtils.copyProperty(wo, property.getName(), newVal); } catch (InvocationTargetException e) { logger.debug("(non-fatal) Failed to write property '" + property.getName() + " to type " + wo.getClass().getName()); } } SPObject parent = wo.getParent(); int oldChildCount = parent.getChildren().size(); listener.transactionStarted(null); listener.childRemoved( new SPChildEvent(parent, wo.getClass(), wo, parent.getChildren().indexOf(wo), EventType.REMOVED)); listener.transactionEnded(null); //persist the object wo.setParent(parent); listener.transactionStarted(null); listener.childAdded( new SPChildEvent(parent, wo.getClass(), wo, parent.getChildren().size(), EventType.ADDED)); listener.transactionEnded(null); //the object must now be added to the super parent assertEquals(oldChildCount, parent.getChildren().size()); SPObject persistedObject = parent.getChildren().get(parent.childPositionOffset(wo.getClass())); persistedObject.setMagicEnabled(false); //check all the properties are what we expect on the new object Set<String> ignorableProperties = getPropertiesToNotPersistOnObjectPersist(); List<String> settablePropertyNames = new ArrayList<String>(); for (PropertyDescriptor pd : settableProperties) { settablePropertyNames.add(pd.getName()); } settablePropertyNames.removeAll(ignorableProperties); for (String persistedPropertyName : settablePropertyNames) { Class<?> classType = null; for (PropertyDescriptor propertyDescriptor : settableProperties) { if (propertyDescriptor.getName().equals(persistedPropertyName)) { classType = propertyDescriptor.getPropertyType(); break; } } logger.debug("Persisted object is of type " + persistedObject.getClass()); Object oldVal = PropertyUtils.getSimpleProperty(wo, persistedPropertyName); Object newVal = PropertyUtils.getSimpleProperty(persistedObject, persistedPropertyName); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && persistedPropertyName.equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } Object basicOldVal = converterFactory.convertToBasicType(oldVal, additionalVals.toArray()); Object basicNewVal = converterFactory.convertToBasicType(newVal, additionalVals.toArray()); logger.debug("Property " + persistedPropertyName + ". oldVal is \"" + basicOldVal + "\" but newVal is \"" + basicNewVal + "\""); assertPersistedValuesAreEqual(oldVal, newVal, basicOldVal, basicNewVal, classType); } } /** * Reflective test that the wabit object can be persisted as an object and all of * its properties are persisted with it. */ public void testPersistsObjectAsChild() throws Exception { //This may need to actually have the wabit object as a child to itself. WabitObject parent = new StubWabitObject(); CountingWabitPersister persister = new CountingWabitPersister(); WorkspacePersisterListener listener = new WorkspacePersisterListener( new StubWabitSession(new StubWabitSessionContext()), persister, true); SPObject wo = getObjectUnderTest(); if (wo.getParent() == null) { wo.setParent(parent); } listener.childAdded(new SPChildEvent(parent, wo.getClass(), wo, 0, EventType.ADDED)); assertTrue(persister.getPersistObjectCount() > 0); PersistedSPObject persistedWabitObject = persister.getAllPersistedObjects().get(0); assertEquals(wo.getClass().getSimpleName(), persistedWabitObject.getType()); assertEquals(wo.getUUID(), persistedWabitObject.getUUID()); //confirm we get one persist property for each getter/setter pair //confirm we get one persist property for each value in one of the constructors in the object. List<PropertyDescriptor> settableProperties = new ArrayList<PropertyDescriptor>( Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass()))); List<PersistedSPOProperty> allPropertyChanges = persister.getAllPropertyChanges(); Set<String> ignorableProperties = getPropertiesToNotPersistOnObjectPersist(); ignorableProperties.addAll(getPropertiesToIgnoreForEvents()); List<PersistedSPOProperty> changesOnObject = new ArrayList<PersistedSPOProperty>(); logger.debug("Looking through properties registered to persist..."); for (int i = allPropertyChanges.size() - 1; i >= 0; i--) { if (allPropertyChanges.get(i).getUUID().equals(wo.getUUID())) { changesOnObject.add(allPropertyChanges.get(i)); logger.debug( "The property " + allPropertyChanges.get(i).getPropertyName() + " is ready to persist!"); } else { logger.debug("The property " + allPropertyChanges.get(i).getPropertyName() + " has not been set in WabitSessionPersister and" + " WorkspacePersisterListener to persist properly!"); } } List<String> settablePropertyNames = new ArrayList<String>(); for (PropertyDescriptor pd : settableProperties) { settablePropertyNames.add(pd.getName()); } settablePropertyNames.removeAll(ignorableProperties); if (settablePropertyNames.size() != changesOnObject.size()) { for (String descriptor : settablePropertyNames) { PersistedSPOProperty foundChange = null; for (PersistedSPOProperty propertyChange : changesOnObject) { if (propertyChange.getPropertyName().equals(descriptor)) { foundChange = propertyChange; break; } } assertNotNull("The property " + descriptor + " was not persisted", foundChange); } } logger.debug("Property names" + settablePropertyNames); assertTrue(settablePropertyNames.size() <= changesOnObject.size()); WabitSessionPersisterSuperConverter factory = new WabitSessionPersisterSuperConverter( new StubWabitSession(new StubWabitSessionContext()), new WabitWorkspace(), true); for (String descriptor : settablePropertyNames) { PersistedSPOProperty foundChange = null; for (PersistedSPOProperty propertyChange : changesOnObject) { if (propertyChange.getPropertyName().equals(descriptor)) { foundChange = propertyChange; break; } } assertNotNull("The property " + descriptor + " was not persisted", foundChange); assertTrue(foundChange.isUnconditional()); assertEquals(wo.getUUID(), foundChange.getUUID()); Object value = PropertyUtils.getSimpleProperty(wo, descriptor); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && descriptor.equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } Object valueConvertedToBasic = factory.convertToBasicType(value, additionalVals.toArray()); logger.debug("Property \"" + descriptor + "\": expected \"" + valueConvertedToBasic + "\" but was \"" + foundChange.getNewValue() + "\" of type " + foundChange.getDataType()); assertEquals(valueConvertedToBasic, foundChange.getNewValue()); } } /** * Reflectively discovers all the addXXX() methods which take an argument of * type WabitObject, then calls them to add and remove children. The test * passes if each of these discovered methods fires a well-formed * WabitChildEvent when the child is added and removed. */ public void testAddChildren() throws Exception { SPObject wo = getObjectUnderTest(); CountingWabitListener listener = new CountingWabitListener(); wo.addSPListener(listener); Method[] allMethods = wo.getClass().getMethods(); for (Method method : allMethods) { Class<?>[] paramTypes = method.getParameterTypes(); //XXX Take this out once we have the wabit workspace an interface with a //normal and session implementation. if (wo instanceof WabitWorkspace && (method.getName().equals("addGroup") || method.getName().equals("addUser"))) continue; if (method.getName().matches("add.*") && paramTypes.length == 1 && WabitObject.class.isAssignableFrom(paramTypes[0])) { int oldAddCount = listener.getAddedCount(); int oldRemoveCount = listener.getRemovedCount(); Object newChild = valueMaker.makeNewValue(paramTypes[0], null, method.getName()); method.invoke(wo, newChild); assertEquals("Add child event for " + method.getName() + " didn't fire!", oldAddCount + 1, listener.getAddedCount()); assertEquals(oldRemoveCount, listener.getRemovedCount()); assertSame(wo, listener.getLastEvent().getSource()); assertSame(newChild, listener.getLastEvent().getChild()); assertSame(paramTypes[0], listener.getLastEvent().getChildType()); //TODO uncomment this when all objects are parented properly //assertSame(wo, ((WabitObject) newChild).getParent()); } else { logger.debug("Skipped " + method.getName()); } } } /** * This test will set all of the properties in a WabitObject in one transaction then * after committing the next persister after it will throw an exception causing the * persister to undo all of the changes it just made. */ public void testPersisterCommitCanRollbackProperties() throws Exception { SPObject wo = getObjectUnderTest(); WabitSessionPersister persister = new WabitSessionPersister("test persister", session, getWorkspace(), true); CountingWabitListener countingListener = new CountingWabitListener(); ErrorWabitPersister errorPersister = new ErrorWabitPersister(); WorkspacePersisterListener listener = new WorkspacePersisterListener(session, errorPersister, true); SQLPowerUtils.listenToHierarchy(getWorkspace(), listener); wo.addSPListener(countingListener); List<PropertyDescriptor> settableProperties; settableProperties = Arrays.asList(PropertyUtils.getPropertyDescriptors(wo.getClass())); Set<String> propertiesToIgnore = new HashSet<String>(getPropertiesToIgnoreForEvents()); propertiesToIgnore.addAll(getPropertiesToIgnoreForPersisting()); //Track old and new property values to test they are set properly Map<String, Object> propertyNameToOldVal = new HashMap<String, Object>(); //Set all of the properties of the object under test in one transaction. persister.begin(); int propertyChangeCount = 0; for (PropertyDescriptor property : settableProperties) { Object oldVal; if (propertiesToIgnore.contains(property.getName())) continue; try { oldVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } propertyNameToOldVal.put(property.getName(), oldVal); //special case for parent types. If a specific wabit object has a tighter parent then //WabitObject the getParentClass should return the parent type. Class<?> propertyType = property.getPropertyType(); if (property.getName().equals("parent")) { propertyType = getParentClass(); } Object newVal = valueMaker.makeNewValue(propertyType, oldVal, property.getName()); logger.debug("Persisting property \"" + property.getName() + "\" from oldVal \"" + oldVal + "\" to newVal \"" + newVal + "\""); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && property.getName().equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } DataType type = PersisterUtils.getDataType(property.getPropertyType()); Object basicNewValue = converterFactory.convertToBasicType(newVal, additionalVals.toArray()); persister.persistProperty(wo.getUUID(), property.getName(), type, converterFactory.convertToBasicType(oldVal, additionalVals.toArray()), basicNewValue); propertyChangeCount++; } //Commit the transaction causing the rollback to occur errorPersister.setThrowError(true); try { persister.commit(); fail("The commit method should have an error sent to it and it should rethrow the exception."); } catch (SPPersistenceException t) { //continue } for (PropertyDescriptor property : settableProperties) { Object currentVal; if (propertiesToIgnore.contains(property.getName())) continue; try { currentVal = PropertyUtils.getSimpleProperty(wo, property.getName()); // check for a setter if (property.getWriteMethod() == null) continue; } catch (NoSuchMethodException e) { logger.debug( "Skipping non-settable property " + property.getName() + " on " + wo.getClass().getName()); continue; } Object oldVal = propertyNameToOldVal.get(property.getName()); //XXX will replace this later List<Object> additionalVals = new ArrayList<Object>(); if (wo instanceof OlapQuery && property.getName().equals("currentCube")) { additionalVals.add(((OlapQuery) wo).getOlapDataSource()); } logger.debug("Checking property " + property.getName() + " was set to " + oldVal + ", actual value is " + currentVal); assertEquals(converterFactory.convertToBasicType(oldVal, additionalVals.toArray()), converterFactory.convertToBasicType(currentVal, additionalVals.toArray())); } logger.debug("Received " + countingListener.getPropertyChangeCount() + " change events."); assertTrue(propertyChangeCount * 2 <= countingListener.getPropertyChangeCount()); } /** * Tests that if an error occurs while adding a child to a parent the child * will be removed from the parent when rolled back. */ public void testPersisterCommitCanRollbackNewChild() throws Exception { SPObject wo = getObjectUnderTest(); SPObject parent = wo.getParent(); //Removing the object under test from the parent but setting the object's parent //back to have an object that we can add to the parent through the persister but //is not currently a child of the object as that would cause two objects with the //same UUID to exist under the parent causing exceptions. wo.getParent().removeChild(wo); wo.setParent(parent); WabitSessionPersister persister = new WabitSessionPersister("test persister", session, getWorkspace(), true); CountingWabitListener countingListener = new CountingWabitListener(); ErrorWabitPersister errorPersister = new ErrorWabitPersister(); WorkspacePersisterListener listener = new WorkspacePersisterListener(session, errorPersister, true); SQLPowerUtils.listenToHierarchy(getWorkspace(), listener); parent.addSPListener(countingListener); int childrenBefore = parent.getChildren().size(); persister.begin(); class PublicListener extends WorkspacePersisterListener { public PublicListener(WabitSession session, SPPersister persister) { super(session, persister, true); } @Override public void persistChild(SPObject parent, SPObject child, Class<? extends SPObject> childClassType, int indexOfChild) { super.persistChild(parent, child, childClassType, indexOfChild); } } ; PublicListener listenerToPeristObject = new PublicListener(session, persister); listenerToPeristObject.persistChild(parent, wo, wo.getClass(), 0); errorPersister.setThrowError(true); boolean exceptionThrown; try { persister.commit(); exceptionThrown = false; } catch (Throwable t) { //an error that made the commit failed was successfully passed on. exceptionThrown = true; } if (!exceptionThrown) fail("The exception from the errorPersister should be rethrown."); assertEquals("Incorrect number of children", childrenBefore, parent.getChildren().size()); assertFalse(parent.getChildren().contains(wo)); assertEquals("Child added event was not fired", 1, countingListener.getAddedCount()); assertEquals("Child removed event was not fired", 1, countingListener.getRemovedCount()); } /** * Tests that if an error occurs while removing a child on a parent the child * will be added to the parent when rolled back. */ public void testPersisterCommitCanRollbackRemovedChild() throws Exception { SPObject wo = getObjectUnderTest(); SPObject parent = wo.getParent(); WabitSessionPersister persister = new WabitSessionPersister("test persister", session, getWorkspace(), true); CountingWabitListener countingListener = new CountingWabitListener(); ErrorWabitPersister errorPersister = new ErrorWabitPersister(); WorkspacePersisterListener listener = new WorkspacePersisterListener(session, errorPersister, true); SQLPowerUtils.listenToHierarchy(getWorkspace(), listener); parent.addSPListener(countingListener); int childrenBefore = parent.getChildren().size(); persister.begin(); persister.removeObject(parent.getUUID(), wo.getUUID()); errorPersister.setThrowError(true); boolean exceptionThrown; try { persister.commit(); exceptionThrown = false; } catch (Throwable t) { //an error that made the commit failed was successfully passed on. exceptionThrown = true; } if (!exceptionThrown) fail("The exception from the errorPersister should be rethrown."); assertEquals("Incorrect number of children", childrenBefore, parent.getChildren().size()); assertTrue(parent.getChildren().contains(wo)); assertEquals("Child added event was not fired", 1, countingListener.getAddedCount()); assertEquals("Child removed event was not fired", 1, countingListener.getRemovedCount()); } /** * No WabitObject is allowed to return null from getChildren(). Objects that * don't allow children should return an empty list. */ public void testGetChildrenNotNull() throws Exception { assertNotNull(getObjectUnderTest().getChildren()); } /** * No WabitObject is allowed to return a null dependency. Objects with no dependencies * should return an empty list. * @throws Exception */ public void testDependenciesNotNull() throws Exception { assertNotNull(getObjectUnderTest().getDependencies()); assertFalse(getObjectUnderTest().getDependencies().contains(null)); } }