package ome.formats.test.util; import gnu.getopt.Getopt; import gnu.getopt.LongOpt; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.text.Format; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import ome.formats.OMEROMetadataStoreClient; import ome.formats.importer.ImportConfig; import ome.formats.importer.ImportContainer; import ome.formats.importer.ImportLibrary; import ome.formats.importer.OMEROWrapper; import ome.formats.importer.util.HtmlMessenger; import ome.formats.test.util.TestEngineConfig.ErrorOn; import omero.ServerError; import omero.model.Dataset; import omero.model.IObject; import omero.model.Project; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.aop.framework.ProxyFactory; import Glacier2.CannotCreateSessionException; import Glacier2.PermissionDeniedException; public class TestEngine { // usage() name private static final String APP_NAME = "import-tester"; /** Logger for this class */ private static final Logger log = LoggerFactory.getLogger(TestEngine.class); // Immutable state /** Our configuration. */ private final TestEngineConfig config; private final OMEROMetadataStoreClient store; private final ImportLibrary importLibrary; private final OMEROWrapper wrapper; private final IniWritingInterceptor interceptor = new IniWritingInterceptor(); // Mutable state int errors = 0; private String login_url; private String login_username; private String login_password; private String message_url; private String comment_url; private Date start; public TestEngine(TestEngineConfig config) throws CannotCreateSessionException, PermissionDeniedException, ServerError { this.config = config; ProxyFactory pf = new ProxyFactory(new OMEROMetadataStoreClient()); pf.addAdvice(interceptor); pf.setProxyTargetClass(true); store = (OMEROMetadataStoreClient) pf.getProxy(); wrapper = new OMEROWrapper(new ImportConfig()); login_url = config.getFeedbackLoginUrl(); login_username = config.getFeedbackLoginUsername(); login_password = config.getFeedbackLoginPassword(); message_url = config.getFeedbackMessageUrl(); comment_url = config.getCommentUrl(); // Login if (config.getSessionKey() != null) { store.initialize(config.getHostname(), config.getPort(), config.getSessionKey()); } else { store.initialize(config.getUsername(), config.getPassword(), config.getHostname(), config.getPort()); } importLibrary = new ImportLibrary(store, wrapper); } public boolean run(String targetDirectory) throws Throwable { // Create a time stamp and use it for the project name String projectName = new Date().toString(); log.info("Storing project: " + projectName); Project project = store.addProject(projectName, ""); // Our root directory File projectDirectory = new File(targetDirectory); boolean status = false; if (!config.getRecurse()) { // Do not parse sub-directory - only import files in the target. String name = projectDirectory.getName(); log.info("Storing dataset: " + name); Dataset dataset = store.addDataset(name, "", project); status = processDirectory(config.getPopulate(), projectDirectory, dataset); } else { // Parse the sub-directories - these will become our datasets for (File datasetDirectory : projectDirectory.listFiles()) { if (datasetDirectory.exists() && datasetDirectory.isDirectory()) { String name = datasetDirectory.getName(); log.info("Storing dataset: " + name); Dataset dataset = store.addDataset(name, "", project); // In each sub-directory/dataset, import the images needed status = processDirectory(config.getPopulate(), datasetDirectory, dataset); } } } store.logout(); return status; } private boolean processDirectory(boolean populate, File directory, IObject target) throws Throwable { String iniFilePath = directory + File.separator + "test_setup.ini"; log.info("INI file path: " + iniFilePath); // Load up the main ini file TestEngineIniFile iniFile = new TestEngineIniFile(new File(iniFilePath)); interceptor.setIniFile(iniFile); String[] fileTypes = iniFile.getFileTypes(); if (populate = true && fileTypes != null) { // get all files in the directory File[] datasetFiles = directory.listFiles(); for (File datasetFile : datasetFiles) { for (String fileType : fileTypes) { if (datasetFile.isFile() && datasetFile.getName().endsWith("." + fileType) && !datasetFile.getName().startsWith(".")) { iniFile.addFile(datasetFile.getName()); } } } } else if (populate = true && fileTypes == null) { log.error("No filetypes for " + iniFilePath); } // The filtered list of files we're to attempt to import String[] fileList = iniFile.getFileList(); // Sanity check if (fileList.length < 1) { log.error("No files available to import."); } for (int j = 0; j < fileList.length; j++) { if (fileList[j].equals("populate_options")) continue; File file = new File(directory + File.separator + fileList[j]); // Import and return pixels list log.info("------Importing file: " + file + "------"); // Skip missing files if (!file.exists()) { log.warn("Image file " + file.getName() + " missing but referenced in test_setup.ini"); continue; } try { // Do import start = new Date(); interceptor.setSourceFile(file); ImportContainer ic = new ImportContainer(file, target, null, null, null, null); ic.setUserSpecifiedName(fileList[j]); importLibrary.importImage(ic, 0, 0, 1); iniFile.flush(); } catch (Throwable e) { // Flush our file log to disk try { iniFile.flush(); } catch (Throwable e1) { log.error("Failed on flushing ini file" + e1); } //store.logout(); log.error("Failed on file: " + file.getAbsolutePath(), e); errors += 1; sendRequest("", "TestEngine Error", e, file); //throw e; } } return true; } /** * Exits the JVM by calculating the proper exit code based on the number * (and eventually type) of errors and {@link TestEngineConfig#getErrorOn()} */ public void exit() { ErrorOn err = ErrorOn.valueOf(config.getErrorOn()); int returnCode = 0; switch (err) { case never: { break; } default: { returnCode = errors; } } System.err.println("Number of errors: " + errors); System.exit(returnCode); } /** * Prints usage to STDERR and exits with return code 1. */ public static void usage() { System.err.println(String.format( "Usage: %s [OPTION]... [TARGET DIRECTORY]\n" + "Imports one or more files into an OMERO instance and tests\n" + "metadata. More information about the test engine can be found at:\n" + "\n" + "http://trac.openmicroscopy.org.uk/wiki/ImporterTestEngine\n" + "\n" + "Optional arguments:\n" + " -s\tOMERO server hostname\n" + " -u\tOMERO username\n" + " -w\tOMERO password\n" + " -k\tOMERO session key (can be used in place of -u and -w)\n" + " -p\tOMERO server port [default: 4064]\n" + " -e\tRaise error on given situation: any, minimal, never [defaults to any]\n" + " -f\tOMERO feedback url [default: %s]\n" + " -c\tConfiguration file location (instead of any of the above arguments)\n" + " -x\tPopulate initiation files with metadata [defaults to False]\n" + " -h, --help\tDisplay this help and exit\n" + " --no-recurse\tSingle directory test run\n" + "\n" + "ex. %s -s localhost -u username -w password ShortRunImages\n" + "\n" + "Report bugs to qa@openmicroscopy.org.uk>", APP_NAME, TestEngineConfig.DEFAULT_FEEDBACK, APP_NAME)); System.exit(1); } /** * Sends error message to feedback system. */ private void sendRequest(String email, String comment, Throwable error, File file) { Map<String, String> postList = new HashMap<String, String>(); Date end = new Date(); Format formatter = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); StringWriter strw = new StringWriter(); error.printStackTrace(new PrintWriter(strw)); comment = comment + ", Started: " + formatter.format(start) + ", Ended: " + formatter.format(end); postList.put("java_version", System.getProperty("java.version")); postList.put("java_classpath", System.getProperty("java.class.path")); postList.put("os_name", System.getProperty("os.name")); postList.put("os_arch", System.getProperty("os.arch")); postList.put("os_version", System.getProperty("os.version")); postList.put("error", "File: " + file.getAbsolutePath() + "\n]n" + strw.toString()); postList.put("comment", comment); postList.put("email", email); postList.put("app_name", "5"); postList.put("import_session", "test"); postList.put("extra", ""); try { HtmlMessenger messenger = new HtmlMessenger(comment_url, postList); @SuppressWarnings("unused") String serverReply = messenger.executePost(); log.info("Feedback sent. Returned: " + serverReply); } catch( Exception e ) { log.error("Error while sending debug information.", e); //Get the full debug text StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); String debugText = sw.toString(); log.error("Feedback failed with: " + debugText); } } /** * Command line application entry point which parses CLI arguments and * passes them into the importer. Return codes are: * <ul> * <li>0 on success</li> * <li>1 on argument parsing failure</li> * <li>2 on exception during import</li> * </ul> * @param args Command line arguments. */ public static void main(String[] args) throws Throwable { LongOpt[] longOptions = new LongOpt[] { new LongOpt("error-on", LongOpt.REQUIRED_ARGUMENT, null, 'e'), new LongOpt("feedback", LongOpt.REQUIRED_ARGUMENT, null,'f'), new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'), new LongOpt("no-recurse", LongOpt.OPTIONAL_ARGUMENT, null, 'r') }; // First find our configuration file, if it has been provided. Getopt g = new Getopt(APP_NAME, args, "s:u:w:p:k:c:x", longOptions); int a; InputStream configFile = new ByteArrayInputStream(new byte[0]); while ((a = g.getopt()) != -1) { switch (a) { case 'c': { configFile = new FileInputStream(g.getOptarg()); break; } } } // Second check for the configuration file on the CLASSPATH if we // haven't been given a configuration file on the command line. if (configFile instanceof ByteArrayInputStream) { InputStream fromClasspath = TestEngine.class.getClassLoader(). getResourceAsStream("test_engine.ini"); configFile = fromClasspath == null? configFile : fromClasspath; } // Now parse our options. g = new Getopt(APP_NAME, args, "s:u:w:p:c:k:x", longOptions); TestEngineConfig config = new TestEngineConfig(configFile); while ((a = g.getopt()) != -1) { switch (a) { case 's': { config.setHostname(g.getOptarg()); break; } case 'u': { config.setUsername(g.getOptarg()); break; } case 'w': { config.setPassword(g.getOptarg()); break; } case 'k': { config.setSessionKey(g.getOptarg()); break; } case 'p': { config.setPort(Integer.parseInt(g.getOptarg())); break; } case 'x': { config.setPopulate(!config.getPopulate()); break; } case 'r': { config.setRecurse(!config.getRecurse()); break; } case 'e': { config.setErrorOn(g.getOptarg()); } case 'f': { config.setFeedbackUrl(g.getOptarg()); break; } case 'c': { // Ignore, we've dealt with this already. break; } default: { usage(); } } } // Ensure that we have all of our required login arguments if (!config.validateLogin()) { usage(); } // Ensure that we have a valid target path. String path = config.getTarget(); if (args.length - g.getOptind() == 1) { path = args[g.getOptind()]; } else if (path == null) { usage(); } TestEngine engine = new TestEngine(config); engine.run(path); engine.exit(); } }