Java tutorial
/* * Copyright 2013 Matt Sicker and Contributors * * Licensed 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 atg.tools.dynunit.test; import atg.nucleus.Nucleus; import atg.tools.dynunit.naming.LoggingNameResolver; import atg.tools.dynunit.test.configuration.BasicConfiguration; import atg.tools.dynunit.test.configuration.RepositoryConfiguration; import atg.tools.dynunit.test.util.FileUtil; import atg.tools.dynunit.test.util.RepositoryManager; import atg.tools.dynunit.util.ComponentUtil; import junit.framework.TestCase; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.FileFilter; import java.io.IOException; import java.lang.reflect.Method; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import static atg.tools.dynunit.util.PropertiesUtil.setSystemProperty; import static atg.tools.dynunit.util.PropertiesUtil.setSystemPropertyIfEmpty; // TODO: this should be converted to some sort of runner /** * Replacement base class for AtgDustTestCase. Extend this class and use * the following 'pattern' whenever you want to junit test some atg components: * <ul> * <li><b>Copy</b> all needed configuration and repository mapping files to a * staging location outside of your source tree using<b> * {@link AtgDustCase#copyConfigurationFiles(String[], String, String...)}</b>. * The staging directory will automatically be used as the configuration * directory. Copying all needed priorities to a location outside of the source * tree is the preferred method, because this frameworks creates properties on * the fly and that could pollute your current source tree.</li> * <!-- * <li><b> * <p/> * <i>Or: </i></b>tell {@link AtgDustCase} class where the configuration * location is by using <b>{@link AtgDustCase#setConfigurationLocation(String)} * </b>, but be aware that the location will also be used for properties file * generation.</li> * --> * </ul> * <p/> * <!-- p> <b>Rule of thumb:</b> When running repository tests, copy everything * outside of your source tree (or when you use maven, use the target directory * ). If you run basic component/formhandler tests, pointing it to your existing * configuration directory might be sufficient. * <p/> * </p--> * <p/> * Repository based tests are depended on one of the two steps previously * described plus: * <ul> * <li><b>{@link AtgDustCase#prepareRepository(String, String...)}</b> for * testing against an default in-memory hsql database or <b> * {@link AtgDustCase#prepareRepository(String, java.util.Properties, boolean, boolean, String...)} * </b> for testing against an existing database.</li> * </ul> * <p/> * If you need to generate some components "on the fly": * <ul> * <li><b>{@link AtgDustCase#createPropertyFile(String, String, Class)}</b></li> * </ul> * <p/> * <p> * Example usage can be found in test.SongsRepositoryTest. * </p> * <p/> * <p> * This class overrides Junit 3 and not Junit 4 because currently Junit 4 has * some test runner/eclipse related bugs which makes it impossible for me to use * it. * </p> * * @author robert */ public class AtgDustCase extends TestCase { private static final Logger logger = LogManager.getLogger(); private final RepositoryManager repositoryManager = new RepositoryManager(); private final BasicConfiguration basicConfiguration = new BasicConfiguration(); private File configurationLocation; private Nucleus nucleus; private LoggingNameResolver loggingNameResolver; private boolean debug; private String atgConfigPath; private String environment; private String localConfig; private List<String> configDstsDir; @Nullable private static final Map<String, Long> CONFIG_FILES_TIMESTAMPS; private static Map<String, Long> CONFIG_FILES_GLOBAL_FORCE = null; private static Class<?> perflib; private static final File TIMESTAMP_SER = new File(FileUtils.getTempDirectory(), "dynunit-timestamp.ser"); private static final File GLOBAL_FORCE_SER = new File(FileUtils.getTempDirectory(), "dynunit-global-force.ser"); private static long SERIAL_TTL = 43200000L; /** * Every *.properties file copied using this method will have it's scope (if one is available) set to global. * * @param sourceDirectories * One or more directories containing needed configuration files. * @param destinationDirectory * where to copy the above files to. This will also be the * configuration location. * @param excludedDirectories * One or more directories not to include during the copy * process. Use this one to speeds up the test cycle * considerably. You can also call it with an empty * {@link String[]} or <code>null</code> if nothing should be * excluded * * @throws IOException * Whenever some file related error's occur. */ protected final void copyConfigurationFiles(@NotNull final String[] sourceDirectories, @NotNull final String destinationDirectory, @Nullable final String... excludedDirectories) throws IOException { logger.entry(sourceDirectories, destinationDirectory, excludedDirectories); setConfigurationLocation(destinationDirectory); logger.info("Copying configurating files and forcing global scope on all configs."); preCopyingOfConfigurationFiles(sourceDirectories, excludedDirectories); for (final String sourceDirectory : sourceDirectories) { FileUtils.copyDirectory(new File(sourceDirectory), new File(destinationDirectory), new FileFilter() { @Override public boolean accept(final File file) { return ArrayUtils.contains(excludedDirectories, file.getName()); } }); } forceGlobalScopeOnAllConfigs(destinationDirectory); if (FileUtil.isDirty()) { FileUtil.serialize(GLOBAL_FORCE_SER, FileUtil.getConfigFilesTimestamps()); } logger.exit(); } /** * Donated by Remi Dupuis * * @param properties * * @throws IOException */ protected final void manageConfigurationFiles(Properties properties) throws IOException { logger.entry(properties); String atgConfigPath = properties.getProperty("atgConfigsJars").replace("/", File.separator); String[] configs = properties.getProperty("configs").split(","); String environment = properties.getProperty("environment"); String localConfig = properties.getProperty("localConfig"); String[] excludes = properties.getProperty("excludes").split(","); String rootConfigDir = properties.getProperty("rootConfigDir").replace("/", File.separator); int i = 0; for (String conf : configs) { String src = conf.split(" to ")[0]; String dst = conf.split(" to ")[1]; configs[i] = (rootConfigDir + "/" + src.trim() + " to " + rootConfigDir + "/" + dst.trim()).replace("/", File.separator); i++; } i = 0; for (String dir : excludes) { excludes[i] = dir.trim(); i++; } final List<String> srcsAsList = new ArrayList<String>(configs.length); final List<String> distsAsList = new ArrayList<String>(configs.length); for (String config : configs) { srcsAsList.add(config.split(" to ")[0]); distsAsList.add(config.split(" to ")[1]); } this.atgConfigPath = atgConfigPath; this.environment = environment; this.localConfig = localConfig; // The Last dstdir is used for Configuration location setConfigurationLocation(distsAsList.get(distsAsList.size() - 1)); logger.debug("Copying configuration files and forcing global scope on all configs."); preCopyingOfConfigurationFiles(srcsAsList.toArray(new String[srcsAsList.size()]), excludes); logger.info("Copying configuration files and forcing global scope on all configs"); // copy all files to it's destination for (String config : configs) { FileUtil.copyDirectory(config.split(" to ")[0], config.split(" to ")[1], Arrays.asList(excludes)); logger.debug(config); logger.debug(config.split(" to ")[0]); logger.debug(config.split(" to ")[1]); } // forcing global scope on all configurations for (String config : configs) { String dstDir = config.split(" to ")[1]; // forcing global scope on all property files forceGlobalScopeOnAllConfigs(dstDir); } this.configDstsDir = distsAsList; } /** * @param configurationStagingLocation * The location where the property file should be created. This will also set the * {@link AtgDustCase#configurationLocation}. * @param nucleusComponentPath * Nucleus component path (e.g /Some/Service/Impl). * @param klass * The {@link Class} implementing the nucleus component specified in previous argument. * * @throws IOException * If we have some File related errors */ protected final void createPropertyFile(final String configurationStagingLocation, final String nucleusComponentPath, final Class<?> klass) throws IOException { configurationLocation = new File(configurationStagingLocation); final String componentFileName = nucleusComponentPath.replaceAll("/", File.separator) + ".properties"; final File componentFile = new File(configurationLocation, componentFileName); ComponentUtil.newComponentForFile(componentFile, klass); } /** * Prepares a test against an in-memory hsql database. * * @param repoPath * the nucleus component path of the repository to be tested. * @param definitionFiles * one or more repository definition files. * * @throws IOException * The moment we have some properties/configuration related * error * @throws SQLException * Whenever there is a database related error */ protected final void prepareRepository(final String repoPath, final String... definitionFiles) throws SQLException, IOException { final Properties properties = new Properties(); properties.put("driver", "org.hsqldb.jdbcDriver"); properties.put("url", "jdbc:hsqldb:mem:testDb"); properties.put("user", "sa"); properties.put("password", ""); prepareRepository(repoPath, properties, true, true, definitionFiles); } /** * Prepares a test against an existing database. * * @param repositoryPath * The the repository to be tested, specified as nucleus * component path. * @param connectionProperties * A {@link Properties} instance with the following values (in * this example the properties are geared towards an mysql * database): * <p/> * <pre> * final * Properties properties = new * Properties(); * * properties.put("driver", * "com.mysql.jdbc.Driver"); * * properties.put("url", * "jdbc:mysql://localhost:3306/someDb"); * * properties.put("user", * "someUserName"); * * properties.put("password", * "somePassword"); * </pre> * @param dropTables * If <code>true</code> then existing tables will be dropped and * re-created, if set to <code>false</code> the existing tables * will be used. * @param createTables * if set to <code>true</code> all non existing tables needed for * the current test run will be created, if set to * <code>false</code> this class expects all needed tables for * this test run to be already created * @param definitionFiles * One or more needed repository definition files. * * @throws IOException * The moment we have some properties/configuration related * error * @throws SQLException * Whenever there is a database related error */ protected final void prepareRepository(final String repositoryPath, final Properties connectionProperties, final boolean dropTables, final boolean createTables, final String... definitionFiles) throws SQLException, IOException { final Map<String, String> connectionSettings = new HashMap<String, String>(); for (final Entry<Object, Object> entry : connectionProperties.entrySet()) { connectionSettings.put((String) entry.getKey(), (String) entry.getValue()); } final RepositoryConfiguration repositoryConfiguration = new RepositoryConfiguration(); repositoryConfiguration.setDebug(debug); repositoryConfiguration.setRoot(configurationLocation); repositoryConfiguration.createPropertiesByConfigurationLocation(); repositoryConfiguration.createFakeXADataSource(connectionProperties); repositoryConfiguration.createRepository(repositoryPath, dropTables, createTables, definitionFiles); repositoryManager.initializeMinimalRepositoryConfiguration(configurationLocation, repositoryPath, connectionSettings, dropTables, debug, definitionFiles); } /** * Method for retrieving a fully injected ATG component. * * @param nucleusComponentPath * Path to a nucleus component (e.g. /Some/Service/Impl). * * @return Fully injected instance of the component registered under previous argument or {@code null} if there * is an error. * * @throws IOException */ protected Object resolveNucleusComponent(final String nucleusComponentPath) throws IOException { logger.entry(nucleusComponentPath); startNucleus(configurationLocation); final Object component = loggingNameResolver.resolveName(nucleusComponentPath); return logger.exit(component); } /** * Call this method to set the configuration location. * * @param configurationLocation * The configuration location to set. Most of the time this * location is a directory containing all repository definition * files and component property files which are needed for the * test. */ protected final void setConfigurationLocation(final String configurationLocation) { logger.entry(configurationLocation); this.configurationLocation = new File(configurationLocation); logger.debug("Using configuration location: {}", this.configurationLocation.getPath()); logger.exit(); } /** * Always make sure to call this because it will do necessary clean up * actions (shutting down in-memory database (if it was used) and the * nucleus) so he next test can run safely. */ @Override protected void tearDown() throws Exception { super.tearDown(); repositoryManager.shutdownInMemoryDbAndCloseConnections(); if (nucleus != null) { nucleus.doStopService(); nucleus.stopService(); nucleus.destroy(); } } /** * Enables or disables the debug level of nucleus components. * * @param debug * Setting this to <code>true</code> will enable debug on all * (currently only on repository related) components, setting it * to <code>false</code> turns the debug off again. */ protected void setDebug(final boolean debug) { this.debug = debug; } private void startNucleus(final File configPath) throws IOException { if (nucleus == null || !nucleus.isRunning()) { basicConfiguration.setDebug(debug); basicConfiguration.setRoot(configPath); basicConfiguration.createPropertiesByConfigurationLocation(); setSystemPropertyIfEmpty("atg.dynamo.license.read", "true"); setSystemPropertyIfEmpty("atg.license.read", "true"); // TODO: Can I safely keep this one disabled? // NucleusServlet.addNamingFactoriesAndProtocolHandlers(); if (StringUtils.isNotEmpty(environment)) { for (final String property : StringUtils.split(environment, ';')) { final String[] strings = StringUtils.split(property, '='); final String key = strings[0]; final String value = strings[1]; setSystemProperty(key, value); logger.debug("{} = {}", key, value); } } String fullConfigPath = ""; if (atgConfigPath != null && !atgConfigPath.equals("")) { fullConfigPath = atgConfigPath + ";" + fullConfigPath; } if (configDstsDir != null && configDstsDir.size() > 0) { for (String dst : configDstsDir) { fullConfigPath = fullConfigPath + dst + ";"; } } else { fullConfigPath = configPath.getAbsolutePath(); } if (localConfig != null && !localConfig.equals("")) { fullConfigPath = fullConfigPath + localConfig.replace("/", File.separator); } logger.info("The full config path used to start nucleus: {}", fullConfigPath); System.setProperty("atg.configpath", new File(fullConfigPath).getAbsolutePath()); nucleus = Nucleus.startNucleus(new String[] { fullConfigPath }); loggingNameResolver = new LoggingNameResolver(nucleus); } } private void preCopyingOfConfigurationFiles(final String[] srcDirs, final String excludes[]) throws IOException { boolean isDirty = false; final FileFilter filter = new FileFilter() { @Override public boolean accept(final File file) { return ArrayUtils.contains(excludes, file.getName()); } }; // TODO: use FileUtils.copyDirectory, etc. for (final String src : srcDirs) { final Collection<File> srcFiles = FileUtils.listFiles(new File(src), null, true); for (final File file : srcFiles) { if (!Arrays.asList(excludes == null ? new String[] {} : excludes).contains(file.getName()) && !file.getPath().contains(".svn") && file.isFile()) { if (CONFIG_FILES_TIMESTAMPS.get(file.getPath()) != null && file.lastModified() == CONFIG_FILES_TIMESTAMPS.get(file.getPath())) { } else { CONFIG_FILES_TIMESTAMPS.put(file.getPath(), file.lastModified()); isDirty = true; } } } } if (isDirty) { logger.debug("Config files timestamps map is dirty an will be re serialized"); FileUtil.serialize(TIMESTAMP_SER, CONFIG_FILES_TIMESTAMPS); } FileUtil.setConfigFilesTimestamps(CONFIG_FILES_TIMESTAMPS); FileUtil.setConfigFilesGlobalForce(CONFIG_FILES_GLOBAL_FORCE); } private void forceGlobalScopeOnAllConfigs(final String dstDir) throws IOException { // TODO: convert perflib usage to standard JDK if (perflib == null) { for (final File file : FileUtils.listFiles(new File(dstDir), new String[] { "properties" }, true)) { FileUtil.forceGlobalScope(file); } } else { try { List<File> payload = (List<File>) FileUtils.listFiles(new File(dstDir), new String[] { "properties" }, true); Method schedule = perflib.getMethod("schedule", new Class[] { int.class, List.class, Class.class, String.class, Class[].class, List.class }); List<Object> list = new ArrayList<Object>(); list.add("$scope="); list.add("$scope=global\n"); schedule.invoke(perflib.newInstance(), 4, payload, FileUtil.class, "searchAndReplace", new Class[] { String.class, String.class, File.class }, list); } catch (Exception e) { logger.catching(e); } } } static { final String s = System.getProperty("SERIAL_TTL"); if (s == null) { logger.debug( "SERIAL_TTL has not been set. Using default value of {} ms, or start VM with -DSERIAL_TTL=n", SERIAL_TTL); } else { logger.debug("SERIAL_TTL set to {}", s); } try { SERIAL_TTL = s != null ? Long.parseLong(s) * 1000 : SERIAL_TTL; } catch (NumberFormatException e) { logger.catching(e); logger.warn("The value given by SERIAL_TTL, {}, could not be parsed.", s); } CONFIG_FILES_TIMESTAMPS = FileUtil.deserialize(TIMESTAMP_SER, SERIAL_TTL); CONFIG_FILES_GLOBAL_FORCE = FileUtil.deserialize(GLOBAL_FORCE_SER, SERIAL_TTL); try { perflib = Class.forName("com.bsdroot.util.concurrent.SchedulerService"); } catch (ClassNotFoundException e) { logger.catching(e); logger.debug( "com.bsdroot.util.concurrent experimental performance library not found, continuing normally"); } } }