/* * Copyright 2015 Trento Rise (trentorise.eu) * * 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 eu.trentorise.opendata.jackan.test; import eu.trentorise.opendata.commons.TodConfig; import static eu.trentorise.opendata.commons.validation.Preconditions.checkNotEmpty; import eu.trentorise.opendata.jackan.CheckedCkanClient; import eu.trentorise.opendata.jackan.CkanClient; import eu.trentorise.opendata.jackan.CkanClient.Builder; import eu.trentorise.opendata.jackan.exceptions.JackanException; import eu.trentorise.opendata.jackan.exceptions.JackanNotFoundException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; /** * * @author David Leoni */ public class JackanTestConfig { private static final JackanTestConfig INSTANCE = new JackanTestConfig(); /** * Path to file containing jackan testing specific properties */ public static final String TEST_PROPERTIES_PATH = "jackan.test.properties"; private static final String OUTPUT_CKAN_PROPERTY = "jackan.test.ckan.output"; private static final String OUTPUT_CKAN_TOKEN_PROPERTY = "jackan.test.ckan.output-token"; private static final String CLIENT_CLASS_PROPERTY = "jackan.test.ckan.client-class"; private static final String PROXY_PROPERTY = "jackan.test.ckan.proxy"; private static final String TIMEOUT_PROPERTY = "jackan.test.ckan.timeout"; private Properties properties; private boolean initialized = false; /** * Ckan used for tests which require writing */ private String outputCkan; /** * Token for CKAN like "b7592183-53c4-57da-wq52-5b1cb84db9db" */ private String outputCkanToken; /** * By default tests will use {@link CheckedCkanClient} */ private String clientClass; private String proxy; private int timeout = CkanClient.DEFAULT_TIMEOUT; private TodConfig todConfig; private JackanTestConfig() { todConfig = TodConfig.of(JackanTestConfig.class); } /** * @throws IllegalStateException * if {@link #loadLogConfig()} didn't succeed. */ public String getOutputCkan() { if (initialized) { return outputCkan; } else { throw new IllegalStateException("Jackan testing system was not properly initialized!"); } } /** * @throws IllegalStateException * if {@link #loadLogConfig()} didn't succeed. */ public String getOutputCkanToken() { if (initialized) { return outputCkanToken; } else { throw new IllegalStateException("Jackan testing system was not properly initialized!"); } } public String getClientClass() { if (initialized) { return clientClass; } else { throw new IllegalStateException("Jackan testing system was not properly initialized!"); } } private static File confDir; private static File defaultConfDir; private static boolean isDefaultConfDir(File dir) { return (dir.isDirectory() && dir.listFiles().length == 1 && dir.listFiles()[0].getName() .toLowerCase() .endsWith("readme.txt")); } /** * Walks the parent directories until finds the conf folder. * * @return the conf folder * @throws JackanNotFoundException * if folder is not found. */ private static File findConfDir() { if (confDir == null) { File directory = new File(System.getProperty("user.dir")); boolean reachedFileSystemRoot = false; while (!reachedFileSystemRoot) { File candidateConf = new File(directory.getAbsolutePath() + File.separator + "conf"); System.out.println("Trying conf candidate " + candidateConf.getAbsolutePath()); if (directory.isDirectory() && directory.exists() && candidateConf.isDirectory() && candidateConf.exists() && !isDefaultConfDir(candidateConf)) { System.out.println("Found conf folder at " + candidateConf.getAbsolutePath()); confDir = candidateConf; return confDir; } else { if (isDefaultConfDir(candidateConf)) { defaultConfDir = candidateConf; } File parent = directory.getParentFile(); if (parent != null) { directory = parent; } else { reachedFileSystemRoot = true; } } } String msg = ""; if (defaultConfDir != null) { msg += " (but found default conf directory at " + defaultConfDir + " with only README.txt inside)"; } throw new JackanException("Couldn't find conf folder! " + msg); } else { return confDir; } } /** * First searches in conf/ directory, then continues search in conf * directories walking up to file system root. * * @param filepath * Relative filepath with file name and extension included. i.e. * abc/myfile.xml, which will be first searched in * conf/abc/myfile.xml * @throws JackanNotFoundException * if no file is found */ private static File findConfFile(String filepath) { checkNotEmpty(filepath, "Invalid filepath!"); File confDir = findConfDir(); File candFile = new File(confDir + File.separator + filepath); if (candFile.exists()) { return candFile; } else { /* * candFile = new File( confDir + File.separator + ".." + * File.separator + "conf-template" + File.separator + filepath); if * (candFile.exists()) { return candFile; } */ throw new JackanNotFoundException("Can't find file " + filepath + " in conf dir: " + confDir); } } /** * Loads file. * * @param filepath * relative or absolute path with complete file name, i.e. * /etc/bla.xml or ../../bla.xml * @throws JackanNotFoundException * if file is not found * @throws JackanException * on generic error */ private InputStream loadConfFile(File file) { if (!file.exists()) { throw new JackanException("Logback filepath " + file + " does not reference a file that exists"); } else { if (!file.isFile()) { throw new JackanException("Logback filepath " + file + " exists, but does not reference a file"); } else { if (!file.canRead()) { throw new JackanException( "Logback filepath " + file + " exists and is a file, but cannot be read."); } else { try { return new FileInputStream(file); } catch (FileNotFoundException e) { throw new JackanNotFoundException("Couldn't find file " + file); } } } } } /** * Loads logging config (see {@link TodConfig#loadLogConfig()}) and * configuration for writing tests at path {@link #TEST_PROPERTIES_PATH}. * NOTE: The latter config is searched in a smarter way by looking in the * first conf/ folder found walking along directory tree. */ public void loadConfig() { TodConfig.loadLogConfig(this.getClass()); Logger logger = Logger.getLogger(JackanTestConfig.class.getName()); // final InputStream inputStream = // JackanTestConfig.class.getResourceAsStream("/" + // TEST_PROPERTIES_PATH); FileInputStream inputStream = null; try { File jackanConfFile; try { jackanConfFile = findConfFile(TEST_PROPERTIES_PATH); inputStream = new FileInputStream(jackanConfFile); logger.log(Level.INFO, "Loaded test configuration file {0}", jackanConfFile); } catch (Exception ex) { throw new IOException( "Couldn't load Jackan test config file " + TEST_PROPERTIES_PATH + ", to enable writing tests please copy src/test/resources/jackan.test.properties to conf folder in the project root and edit as needed!", ex); } properties = new Properties(); properties.load(inputStream); @Nullable String timeoutString = properties.getProperty(TIMEOUT_PROPERTY); if (timeoutString != null) { timeout = Integer.parseInt(timeoutString); } proxy = properties.getProperty(PROXY_PROPERTY); outputCkan = properties.getProperty(OUTPUT_CKAN_PROPERTY); if (outputCkan == null || outputCkan.trim() .isEmpty()) { throw new IOException( "Couldn't find property " + OUTPUT_CKAN_PROPERTY + " in configuration file " + jackanConfFile); } else { logger.log(Level.INFO, "Will use {0} for CKAN write tests", outputCkan); } outputCkanToken = properties.getProperty(OUTPUT_CKAN_TOKEN_PROPERTY); if (outputCkanToken == null || outputCkanToken.trim() .isEmpty()) { throw new IOException("COULDN'T FIND PROPERTY " + OUTPUT_CKAN_TOKEN_PROPERTY + " IN CONFIGURATION FILE " + jackanConfFile); } else { logger.log(Level.INFO, "Will use token {0} for CKAN write tests", outputCkanToken); } clientClass = properties.getProperty(CLIENT_CLASS_PROPERTY); if (clientClass == null || clientClass.trim() .isEmpty()) { clientClass = CheckedCkanClient.class.getName(); logger.log(Level.INFO, "Will use default client class {0} for writing", clientClass); } else { clientClass = clientClass.trim(); logger.log(Level.INFO, "Will use client class {0} for writing", clientClass); } // let's see if it doesn't explode.. try { makeClientInstanceForWriting(clientClass); } catch (Exception ex) { throw new Exception("Could not make test instance for client class " + clientClass, ex); } initialized = true; } catch (Exception e) { logger.log(Level.SEVERE, "JACKAN ERROR - COULDN'T INITIALIZE TEST ENVIRONMENT PROPERLY! INTEGRATION TESTS (ESPECIALLY FOR WRITING) MIGHT FAIL BECAUSE OF THIS!!", e); } } /** * Returns a default client instance for writing. */ public CkanClient makeClientInstanceForWriting() { return makeClientInstanceForWriting(clientClass); } public CkanClient makeClientInstanceForWriting(String clientClass) { checkNotEmpty(clientClass, "Invalid class string!"); Class forName; try { forName = Class.forName(clientClass); } catch (Exception ex) { throw new RuntimeException("Couldn't find client class " + clientClass + "!", ex); } Method method; CkanClient.Builder builder; try { method = forName.getMethod("builder"); builder = (Builder) method.invoke(null); } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) { throw new RuntimeException("Could not find builder() static method in client class " + clientClass, ex); } return builder.setCatalogUrl(outputCkan) .setCkanToken(outputCkanToken) .setProxy(proxy) .setTimeout(timeout) .build(); } /** * Returns the singleton */ public static JackanTestConfig of() { return INSTANCE; } }