Java tutorial
/** * This Source Code Form is subject to the terms of the Mozilla Public License, * v. 2.0. If a copy of the MPL was not distributed with this file, You can * obtain one at http://mozilla.org/MPL/2.0/. OpenMRS is also distributed under * the terms of the Healthcare Disclaimer located at http://openmrs.org/license. * * Copyright (C) OpenMRS Inc. OpenMRS is a registered trademark and the OpenMRS * graphic logo is a trademark of OpenMRS Inc. */ package org.openmrs.test; import java.awt.Font; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Timer; import java.util.TimerTask; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; import javax.swing.UIManager; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dbunit.DatabaseUnitException; import org.dbunit.database.DatabaseConfig; import org.dbunit.database.DatabaseConnection; import org.dbunit.database.IDatabaseConnection; import org.dbunit.dataset.DefaultDataSet; import org.dbunit.dataset.DefaultTable; import org.dbunit.dataset.IDataSet; import org.dbunit.dataset.ReplacementDataSet; import org.dbunit.dataset.stream.StreamingDataSet; import org.dbunit.dataset.xml.FlatXmlDataSet; import org.dbunit.dataset.xml.FlatXmlProducer; import org.dbunit.dataset.xml.XmlDataSet; import org.dbunit.ext.h2.H2DataTypeFactory; import org.dbunit.operation.DatabaseOperation; import org.hibernate.SessionFactory; import org.hibernate.cfg.Environment; import org.hibernate.dialect.H2Dialect; import org.hibernate.jdbc.ReturningWork; import org.junit.After; import org.junit.AfterClass; import org.junit.Assume; import org.junit.Before; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.openmrs.ConceptName; import org.openmrs.Drug; import org.openmrs.User; import org.openmrs.annotation.OpenmrsProfileExcludeFilter; import org.openmrs.api.context.Context; import org.openmrs.api.context.ContextAuthenticationException; import org.openmrs.api.context.ContextMockHelper; import org.openmrs.module.ModuleConstants; import org.openmrs.util.OpenmrsClassLoader; import org.openmrs.util.OpenmrsConstants; import org.openmrs.util.OpenmrsUtil; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; import org.springframework.test.context.transaction.TransactionConfiguration; import org.springframework.test.context.transaction.TransactionalTestExecutionListener; import org.springframework.transaction.annotation.Transactional; import org.xml.sax.InputSource; /** * This is the base for spring/context tests. Tests that NEED to use calls to the Context class and * use Services and/or the database should extend this class. NOTE: Tests that do not need access to * spring enabled services do not need this class and extending this will only slow those test cases * down. (because spring is started before test cases are run). Normal test cases do not need to * extend anything */ @ContextConfiguration(locations = { "classpath:applicationContext-service.xml", "classpath*:openmrs-servlet.xml", "classpath*:moduleApplicationContext.xml" }) @TestExecutionListeners({ TransactionalTestExecutionListener.class, SkipBaseSetupAnnotationExecutionListener.class, StartModuleExecutionListener.class }) @Transactional @TransactionConfiguration(defaultRollback = true) public abstract class BaseContextSensitiveTest extends AbstractJUnit4SpringContextTests { private static Log log = LogFactory.getLog(BaseContextSensitiveTest.class); /** * Only the classpath/package path and filename of the initial dataset */ protected static final String INITIAL_XML_DATASET_PACKAGE_PATH = "org/openmrs/include/initialInMemoryTestDataSet.xml"; protected static final String EXAMPLE_XML_DATASET_PACKAGE_PATH = "org/openmrs/include/standardTestDataset.xml"; /** * cached runtime properties */ protected static Properties runtimeProperties; /** * Used for username/password dialog */ private static final Font font = new Font("Arial", Font.BOLD, 16); /** * Our username field is outside of the getUsernameAndPassword() method so we can do our * force-focus-on-the-username-field trick -- i.e., refer to the field within an anonymous * TimerTask method. */ private static JTextField usernameField; /** * This frame contains the password dialog box. In order to bring the frame to the front in the * TimerTask method, we make it a private field */ private static Frame frame; /** * Static variable to keep track of the number of times this class has been loaded (aka, number * of tests already run) */ private static Integer loadCount = 0; /** * Allows to determine if the DB is initialized with standard data */ private static boolean isBaseSetup; /** * Stores a user authenticated for running tests which allows to discover a situation when some * test authenticates as a different user and we need to revert to the original one */ private User authenticatedUser; /** * Allows mocking services returned by Context. See {@link ContextMockHelper} * * @since 1.11, 1.10, 1.9.9 */ @InjectMocks protected ContextMockHelper contextMockHelper; private static volatile BaseContextSensitiveTest instance; /** * Basic constructor for the super class to all openmrs api unit tests. This constructor sets up * the classloader and the properties file so that by the type spring gets around to finally * starting, the openmrs runtime properties are already in place A static load count is kept to * count the number of times this class has been loaded. * * @see #getLoadCount() */ public BaseContextSensitiveTest() { Thread.currentThread().setContextClassLoader(OpenmrsClassLoader.getInstance()); Properties props = getRuntimeProperties(); if (log.isDebugEnabled()) log.debug("props: " + props); Context.setRuntimeProperties(props); loadCount++; instance = this; } /** * Initializes fields annotated with {@link Mock}. * * @since 1.11, 1.10, 1.9.9 */ @Before public void initMocks() { MockitoAnnotations.initMocks(this); } /** * @since 1.11, 1.10, 1.9.9 */ @After public void revertContextMocks() { contextMockHelper.revertMocks(); } /** * Modules should extend {@link BaseModuleContextSensitiveTest}, not this class. If they extend * this class, then they won't work right when run in batches. * * @throws Exception */ @Before public void checkNotModule() throws Exception { if (this.getClass().getPackage().toString().contains("org.openmrs.module.") && !(this instanceof BaseModuleContextSensitiveTest)) { throw new RuntimeException( "Module unit test classes should extend BaseModuleContextSensitiveTest, not just BaseContextSensitiveTest"); } } /** * Allows to ignore the test if the environment does not match the given parameters. * * @param openmrsPlatformVersion * @param modules * @since 1.11.3, 1.10.2, 1.9.9 */ public void assumeOpenmrsProfile(String openmrsPlatformVersion, String... modules) { OpenmrsProfileExcludeFilter filter = new OpenmrsProfileExcludeFilter(); Map<String, Object> profile = new HashMap<String, Object>(); profile.put("openmrsPlatformVersion", openmrsPlatformVersion); if (modules != null) { profile.put("modules", modules); } else { profile.put("modules", new String[0]); } String errorMessage = "Ignored. Expected profile: {openmrsPlatformVersion=" + openmrsPlatformVersion + ", modules=[" + StringUtils.join((String[]) profile.get("modules"), ", ") + "]}"; Assume.assumeTrue(errorMessage, filter.matchOpenmrsProfileAttributes(profile)); } /** * Allows to ignore the test if the given modules are not running. * * @param module in the format moduleId:version * @param modules additional list of modules in the format moduleId:version * @since 1.11.3, 1.10.2, 1.9.9 */ public void assumeOpenmrsModules(String module, String... modules) { String[] allModules = ArrayUtils.addAll(modules, module); assumeOpenmrsProfile(null, allModules); } /** * Allows to ignore the test if the environment does not match the given OpenMRS version. * * @param openmrsPlatformVersion * @since 1.11.3, 1.10.2, 1.9.9 */ public void assumeOpenmrsPlatformVersion(String openmrsPlatformVersion) { assumeOpenmrsProfile(openmrsPlatformVersion); } /** * Get the number of times this class has been loaded. This is a rough approx of how many tests * have been run so far. This can be used to determine if the test is being run in a standalone * context or if other tests have been run before. * * @return number of times this class has been loaded */ public Integer getLoadCount() { return loadCount; } /** * Used for runtime properties. The default is "openmrs" because most people will use that as * the default. If your webapp and runtime properties are under a different name, override this * method in your tests * * @return String webapp name to assume when looking up the runtime properties */ public String getWebappName() { return "openmrs"; } /** * Mimics org.openmrs.web.Listener.getRuntimeProperties() Overrides the database connection * properties if the user wants an in-memory database * * @return Properties runtime */ public Properties getRuntimeProperties() { // cache the properties for subsequent calls if (runtimeProperties == null) runtimeProperties = TestUtil.getRuntimeProperties(getWebappName()); // if we're using the in-memory hypersonic database, add those // connection properties here to override what is in the runtime // properties if (useInMemoryDatabase() == true) { runtimeProperties.setProperty(Environment.DIALECT, H2Dialect.class.getName()); String url = "jdbc:h2:mem:openmrs;DB_CLOSE_DELAY=30;LOCK_TIMEOUT=10000"; runtimeProperties.setProperty(Environment.URL, url); runtimeProperties.setProperty(Environment.DRIVER, "org.h2.Driver"); runtimeProperties.setProperty(Environment.USER, "sa"); runtimeProperties.setProperty(Environment.PASS, ""); // these properties need to be set in case the user has this exact // phrasing in their runtime file. runtimeProperties.setProperty("connection.username", "sa"); runtimeProperties.setProperty("connection.password", ""); runtimeProperties.setProperty("connection.url", url); // automatically create the tables defined in the hbm files runtimeProperties.setProperty(Environment.HBM2DDL_AUTO, "create-drop"); } else { String url = System.getProperty("databaseUrl"); String username = System.getProperty("databaseUsername"); String password = System.getProperty("databasePassword"); runtimeProperties.setProperty(Environment.URL, url); runtimeProperties.setProperty(Environment.DRIVER, System.getProperty("databaseDriver")); runtimeProperties.setProperty(Environment.USER, username); runtimeProperties.setProperty(Environment.PASS, password); runtimeProperties.setProperty(Environment.DIALECT, System.getProperty("databaseDialect")); // these properties need to be set in case the user has this exact // phrasing in their runtime file. runtimeProperties.setProperty("connection.username", username); runtimeProperties.setProperty("connection.password", password); runtimeProperties.setProperty("connection.url", url); //for the first time, automatically create the tables defined in the hbm files //after that, just update, if there are any changes. This is for performance reasons. runtimeProperties.setProperty(Environment.HBM2DDL_AUTO, "update"); } // we don't want to try to load core modules in tests runtimeProperties.setProperty(ModuleConstants.IGNORE_CORE_MODULES_PROPERTY, "true"); try { File tempappdir = File.createTempFile("appdir-for-unit-tests-", ""); tempappdir.delete(); // so we can make it into a directory tempappdir.mkdir(); // turn it into a directory tempappdir.deleteOnExit(); // clean up when we're done with tests runtimeProperties.setProperty(OpenmrsConstants.APPLICATION_DATA_DIRECTORY_RUNTIME_PROPERTY, tempappdir.getAbsolutePath()); OpenmrsUtil.setApplicationDataDirectory(tempappdir.getAbsolutePath()); } catch (IOException e) { log.error("Unable to create temp dir", e); } return runtimeProperties; } /** * Authenticate to the Context. A popup box will appear asking the current user to enter * credentials unless there is a junit.username and junit.password defined in the runtime * properties * * @throws Exception */ public void authenticate() throws Exception { if (Context.isAuthenticated() && Context.getAuthenticatedUser().equals(authenticatedUser)) { return; } try { Context.authenticate("admin", "test"); authenticatedUser = Context.getAuthenticatedUser(); return; } catch (ContextAuthenticationException wrongCredentialsError) { if (useInMemoryDatabase()) { // if we get here the user is using some database other than the standard // in-memory database, prompt the user for input log.error("For some reason we couldn't auth as admin:test ?!", wrongCredentialsError); } } Integer attempts = 0; // TODO: how to make this a locale specific message for the user to see? String message = null; // only need to authenticate once per session while (!Context.isAuthenticated() && attempts < 3) { // look in the runtime properties for a defined username and // password first String junitusername = null; String junitpassword = null; try { Properties props = this.getRuntimeProperties(); junitusername = props.getProperty("junit.username"); junitpassword = props.getProperty("junit.password"); } catch (Exception e) { // if anything happens just default to asking the user } String[] credentials = null; // ask the user for creds if no junit username/pass defined // in the runtime properties or if that username/pass failed already if (junitusername == null || junitpassword == null || attempts > 0) { credentials = askForUsernameAndPassword(message); // credentials are null if the user clicked "cancel" in popup if (credentials == null) return; } else credentials = new String[] { junitusername, junitpassword }; // try to authenticate to the Context with either the runtime // defined credentials or the user supplied credentials from the // popup try { Context.authenticate(credentials[0], credentials[1]); authenticatedUser = Context.getAuthenticatedUser(); } catch (ContextAuthenticationException e) { message = "Invalid username/password. Try again."; } attempts++; } } /** * Utility method for obtaining username and password through Swing interface for tests. Any * tests extending the org.openmrs.BaseTest class may simply invoke this method by name. * Username and password are returned in a two-member String array. If the user aborts, null is * returned. <b> <em>Do not call for non-interactive tests, since this method will try to * render an interactive dialog box for authentication!</em></b> * * @param message string to display above username field * @return Two-member String array containing username and password, respectively, or * <code>null</code> if user aborts dialog */ public static synchronized String[] askForUsernameAndPassword(String message) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } if (message == null || "".equals(message)) message = "Enter username/password to authenticate to OpenMRS..."; JPanel panel = new JPanel(new GridBagLayout()); JLabel usernameLabel = new JLabel("Username"); usernameLabel.setFont(font); usernameField = new JTextField(20); usernameField.setFont(font); JLabel passwordLabel = new JLabel("Password"); passwordLabel.setFont(font); JPasswordField passwordField = new JPasswordField(20); passwordField.setFont(font); panel.add(usernameLabel, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 5, 0)); panel.add(usernameField, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); panel.add(passwordLabel, new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.EAST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 5, 0)); panel.add(passwordField, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); frame = new JFrame(); Window window = new Window(frame); frame.setVisible(true); frame.setTitle("JUnit Test Credentials"); // We use a TimerTask to force focus on username, but still use // JOptionPane for model dialog TimerTask later = new TimerTask() { @Override public void run() { if (frame != null) { // bring the dialog's window to the front frame.toFront(); usernameField.grabFocus(); } } }; // try setting focus half a second from now new Timer().schedule(later, 500); // attention grabber for those people that aren't as observant TimerTask laterStill = new TimerTask() { @Override public void run() { if (frame != null) { frame.toFront(); // bring the dialog's window to the // front usernameField.grabFocus(); } } }; // if the user hasn't done anything in 10 seconds, tell them the window // is there new Timer().schedule(laterStill, 10000); // show the dialog box int response = JOptionPane.showConfirmDialog(window, panel, message, JOptionPane.OK_CANCEL_OPTION); // clear out the window so the timer doesn't screw up laterStill.cancel(); frame.setVisible(false); window.setVisible(false); frame = null; // response of 2 is the cancel button, response of -1 is the little red // X in the top right return (response == 2 || response == -1 ? null : new String[] { usernameField.getText(), String.valueOf(passwordField.getPassword()) }); } /** * Override this method to turn on/off the in-memory database. The default is to use the * in-memory database. When this method returns false, the database defined by the runtime * properties is used instead * * @return true/false whether or not to use an in memory database */ public Boolean useInMemoryDatabase() { return !"false".equals(System.getProperty("useInMemoryDatabase")); } /** * Get the database connection currently in use by the testing framework. * <p> * Note that if you commit a transaction, any changes done by a test will not be rolled back and * you will need to clean up yourself by calling for example {@link #deleteAllData()}. * * @return Connection jdbc connection to the database */ @SuppressWarnings("deprecation") public Connection getConnection() { SessionFactory sessionFactory = (SessionFactory) applicationContext.getBean("sessionFactory"); return sessionFactory.getCurrentSession().doReturningWork(new ReturningWork<Connection>() { @Override public Connection execute(Connection connection) throws SQLException { return connection; } }); } /** * This initializes the empty in-memory database with some rows in order to actually run some * tests */ public void initializeInMemoryDatabase() throws Exception { //Don't allow the user to overwrite data if (!useInMemoryDatabase()) throw new Exception( "You shouldn't be initializing a NON in-memory database. Consider unoverriding useInMemoryDatabase"); /* * Hbm2ddl used in tests creates primary key columns, which are not auto incremented, if * NativeIfNotAssignedIdentityGenerator is used. We need to alter those columns in tests. */ List<String> tables = Arrays.asList("concept"); for (String table : tables) { getConnection() .prepareStatement("ALTER TABLE " + table + " ALTER COLUMN " + table + "_id INT AUTO_INCREMENT") .execute(); } executeDataSet(INITIAL_XML_DATASET_PACKAGE_PATH); } /** * Note that with the H2 DB this operation always commits an open transaction. * * @param connection * @throws SQLException */ private void turnOnDBConstraints(Connection connection) throws SQLException { String constraintsOnSql; if (useInMemoryDatabase()) { constraintsOnSql = "SET REFERENTIAL_INTEGRITY TRUE"; } else { constraintsOnSql = "SET FOREIGN_KEY_CHECKS=1;"; } PreparedStatement ps = connection.prepareStatement(constraintsOnSql); ps.execute(); ps.close(); } private void turnOffDBConstraints(Connection connection) throws SQLException { String constraintsOffSql; if (useInMemoryDatabase()) { constraintsOffSql = "SET REFERENTIAL_INTEGRITY FALSE"; } else { constraintsOffSql = "SET FOREIGN_KEY_CHECKS=0;"; } PreparedStatement ps = connection.prepareStatement(constraintsOffSql); ps.execute(); ps.close(); } /** * Used by {@link #executeDataSet(String)} to cache the parsed xml files. This speeds up * subsequent runs of the dataset */ private static Map<String, IDataSet> cachedDatasets = new HashMap<String, IDataSet>(); /** * Runs the flat xml data file at the classpath location specified by * <code>datasetFilename</code> This is a convenience method. It simply creates an * {@link IDataSet} and calls {@link #executeDataSet(IDataSet)} * * @param datasetFilename String path/filename on the classpath of the xml data set to clean * insert into the current database * @see #getConnection() * @see #executeDataSet(IDataSet) */ public void executeDataSet(String datasetFilename) throws Exception { // try to get the given filename from the cache IDataSet xmlDataSetToRun = cachedDatasets.get(datasetFilename); // if we didn't find it in the cache, load it if (xmlDataSetToRun == null) { File file = new File(datasetFilename); InputStream fileInInputStreamFormat = null; Reader reader = null; try { // try to load the file if its a straight up path to the file or // if its a classpath path to the file if (file.exists()) { fileInInputStreamFormat = new FileInputStream(datasetFilename); } else { fileInInputStreamFormat = getClass().getClassLoader().getResourceAsStream(datasetFilename); if (fileInInputStreamFormat == null) throw new FileNotFoundException( "Unable to find '" + datasetFilename + "' in the classpath"); } reader = new InputStreamReader(fileInInputStreamFormat); ReplacementDataSet replacementDataSet = new ReplacementDataSet( new FlatXmlDataSet(reader, false, true, false)); replacementDataSet.addReplacementObject("[NULL]", null); xmlDataSetToRun = replacementDataSet; reader.close(); } finally { IOUtils.closeQuietly(fileInInputStreamFormat); IOUtils.closeQuietly(reader); } // cache the xmldataset for future runs of this file cachedDatasets.put(datasetFilename, xmlDataSetToRun); } executeDataSet(xmlDataSetToRun); } /** * Runs the large flat xml dataset. It does not cache the file as opposed to * {@link #executeDataSet(String)}. * * @param datasetFilename * @throws Exception * @since 1.10 */ public void executeLargeDataSet(String datasetFilename) throws Exception { InputStream inputStream = null; try { final File file = new File(datasetFilename); if (file.exists()) { inputStream = new FileInputStream(datasetFilename); } else { inputStream = getClass().getClassLoader().getResourceAsStream(datasetFilename); if (inputStream == null) throw new FileNotFoundException("Unable to find '" + datasetFilename + "' in the classpath"); } final FlatXmlProducer flatXmlProducer = new FlatXmlProducer(new InputSource(inputStream)); final StreamingDataSet streamingDataSet = new StreamingDataSet(flatXmlProducer); final ReplacementDataSet replacementDataSet = new ReplacementDataSet(streamingDataSet); replacementDataSet.addReplacementObject("[NULL]", null); executeDataSet(replacementDataSet); inputStream.close(); } finally { IOUtils.closeQuietly(inputStream); } } /** * Runs the xml data file at the classpath location specified by <code>datasetFilename</code> * using XmlDataSet. It simply creates an {@link IDataSet} and calls * {@link #executeDataSet(IDataSet)}. <br> * <br> * This method is different than {@link #executeDataSet(String)} in that this one does not * expect a flat file xml but instead a true XmlDataSet. <br> * <br> * In addition, there is no replacing of [NULL] values in strings. * * @param datasetFilename String path/filename on the classpath of the xml data set to clean * insert into the current database * @see #getConnection() * @see #executeDataSet(IDataSet) */ public void executeXmlDataSet(String datasetFilename) throws Exception { // try to get the given filename from the cache IDataSet xmlDataSetToRun = cachedDatasets.get(datasetFilename); // if we didn't find it in the cache, load it if (xmlDataSetToRun == null) { File file = new File(datasetFilename); InputStream fileInInputStreamFormat = null; try { // try to load the file if its a straight up path to the file or // if its a classpath path to the file if (file.exists()) fileInInputStreamFormat = new FileInputStream(datasetFilename); else { fileInInputStreamFormat = getClass().getClassLoader().getResourceAsStream(datasetFilename); if (fileInInputStreamFormat == null) throw new FileNotFoundException( "Unable to find '" + datasetFilename + "' in the classpath"); } XmlDataSet xmlDataSet = null; xmlDataSet = new XmlDataSet(fileInInputStreamFormat); xmlDataSetToRun = xmlDataSet; fileInInputStreamFormat.close(); } finally { IOUtils.closeQuietly(fileInInputStreamFormat); } // cache the xmldataset for future runs of this file cachedDatasets.put(datasetFilename, xmlDataSetToRun); } executeDataSet(xmlDataSetToRun); } /** * Run the given dataset specified by the <code>dataset</code> argument * * @param dataset IDataSet to run on the current database used by Spring * @see #getConnection() */ public void executeDataSet(IDataSet dataset) throws Exception { Connection connection = getConnection(); IDatabaseConnection dbUnitConn = setupDatabaseConnection(connection); //Do the actual update/insert: //insert new rows, update existing rows, and leave others alone DatabaseOperation.REFRESH.execute(dbUnitConn, dataset); } private IDatabaseConnection setupDatabaseConnection(Connection connection) throws DatabaseUnitException { IDatabaseConnection dbUnitConn = new DatabaseConnection(connection); if (useInMemoryDatabase()) { //Setup the db connection to use H2 config. DatabaseConfig config = dbUnitConn.getConfig(); config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory()); } return dbUnitConn; } /** * This is a convenience method to clear out all rows in all tables in the current connection. * <p> * This operation always results in a commit. * * @throws Exception */ public void deleteAllData() throws Exception { Context.clearSession(); Connection connection = getConnection(); turnOffDBConstraints(connection); IDatabaseConnection dbUnitConn = setupDatabaseConnection(connection); // find all the tables for this connection ResultSet resultSet = connection.getMetaData().getTables(null, "PUBLIC", "%", null); DefaultDataSet dataset = new DefaultDataSet(); while (resultSet.next()) { String tableName = resultSet.getString(3); dataset.addTable(new DefaultTable(tableName)); } // do the actual deleting/truncating DatabaseOperation.DELETE_ALL.execute(dbUnitConn, dataset); turnOnDBConstraints(connection); connection.commit(); updateSearchIndex(); isBaseSetup = false; } /** * Method to clear the hibernate cache */ @Before public void clearHibernateCache() { SessionFactory sf = (SessionFactory) applicationContext.getBean("sessionFactory"); sf.getCache().evictCollectionRegions(); sf.getCache().evictEntityRegions(); } /** * This method is run before all test methods that extend this {@link BaseContextSensitiveTest} * unless you annotate your method with the "@SkipBaseSetup" annotation After running this * method an in-memory database will be available that has the content of the rows from * {@link #INITIAL_XML_DATASET_PACKAGE_PATH} and {@link #EXAMPLE_XML_DATASET_PACKAGE_PATH} xml * files. This method will also ask to be authenticated against the current Context and * database. The {@link #initializeInMemoryDatabase()} method has a user of admin:test. * <p> * If you annotate a test with "@SkipBaseSetup", this method will call {@link #deleteAllData()}, * but only if you use the in memory DB. * * @see SkipBaseSetup * @see SkipBaseSetupAnnotationExecutionListener * @see #initializeInMemoryDatabase() * @see #authenticate() * @throws Exception */ @Before public void baseSetupWithStandardDataAndAuthentication() throws Exception { // Open a session if needed if (!Context.isSessionOpen()) { Context.openSession(); } // The skipBaseSetup flag is controlled by the @SkipBaseSetup annotation. if (useInMemoryDatabase()) { if (!skipBaseSetup) { if (!isBaseSetup) { deleteAllData(); if (useInMemoryDatabase()) { initializeInMemoryDatabase(); } else { executeDataSet(INITIAL_XML_DATASET_PACKAGE_PATH); } executeDataSet(EXAMPLE_XML_DATASET_PACKAGE_PATH); //Commit so that it is not rolled back after a test. getConnection().commit(); updateSearchIndex(); isBaseSetup = true; } authenticate(); } else { if (isBaseSetup) { deleteAllData(); } } Context.clearSession(); } public Class<?>[] getIndexedTypes() { return new Class<?>[] { ConceptName.class, Drug.class }; } /** * It needs to be call if you want to do a concept search after you modify a concept in a test. * It is because index is automatically updated only after transaction is committed, which * happens only at the end of a test in our transactional tests. */ public void updateSearchIndex() { for (Class<?> indexType : getIndexedTypes()) { Context.updateSearchIndexForType(indexType); } } @After public void clearSessionAfterEachTest() { // clear the session to make sure nothing is cached, etc Context.clearSession(); // needed because the authenticatedUser is the only object that sticks // around after tests and the clearSession call if (Context.isSessionOpen()) Context.logout(); } /** * Called after each test class. This is called once per test class that extends * {@link BaseContextSensitiveTest}. Needed so that "unit of work" that is the test class is * surrounded by a pair of open/close session calls. * * @throws Exception */ @AfterClass public static void closeSessionAfterEachClass() throws Exception { //Some tests add data via executeDataset() //We need to delete it in order not to interfere with others if (instance != null) { try { instance.deleteAllData(); } catch (Exception ex) { //No need to worry about this } instance = null; } // clean up the session so we don't leak memory if (Context.isSessionOpen()) { Context.closeSession(); } } /** * Instance variable used by the {@link #baseSetupWithStandardDataAndAuthentication()} method to * know whether the current "@Test" method has asked to be _not_ do the initialize/standard * data/authenticate * * @see SkipBaseSetup * @see SkipBaseSetupAnnotationExecutionListener * @see #baseSetupWithStandardDataAndAuthentication() */ private boolean skipBaseSetup = false; /** * Don't run the {@link #setupDatabaseWithStandardData()} method. This means that the associated * "@Test" must call one of these: * * <pre> * * initializeInMemoryDatabase() ; * * executeDataSet(EXAMPLE_DATA_SET); * * Authenticate * </pre> * * on its own if any of those results are needed. This method is called before all "@Test" * methods that have been annotated with the "@SkipBaseSetup" annotation. * * @throws Exception * @see SkipBaseSetup * @see SkipBaseSetupAnnotationExecutionListener * @see #baseSetupWithStandardDataAndAuthentication() */ public void skipBaseSetup() throws Exception { skipBaseSetup = true; } }