/* * $Id$ * * Copyright 2009-2014 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.formats.importer; import static omero.rtypes.rstring; import java.awt.Rectangle; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.concurrent.atomic.AtomicReference; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import loci.formats.FormatTools; import ome.formats.OMEROMetadataStoreClient; import ome.formats.importer.targets.ImportTarget; import ome.formats.importer.targets.TargetBuilder; import ome.formats.importer.util.IniFileLoader; import ome.system.PreferenceContext; import ome.system.UpgradeCheck; import omero.model.Annotation; import omero.model.NamedValue; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.qos.logback.classic.Level; /** * Utility class which configures the Import. * * @since Beta4.1 */ public class ImportConfig { private final static Logger log = LoggerFactory.getLogger(ImportConfig.class); /** The class used to identify the dataset target.*/ private static final String DATASET_CLASS = "omero.model.Dataset"; /** The class used to identify the screen target.*/ private static final String SCREEN_CLASS = "omero.model.Screen"; /** * Delimiter used to encode multiple servers in one preferences value. */ public final static String SERVER_NAME_SEPARATOR = ","; /** * Lookup key for {@link System#getProperty(String)}. Should be the path of * a readers.txt file. */ public final static String READERS_KEY = "omero.import.readers"; // // CONFIGURATION SOURCES: several configuration sources are defined below. // Each may or may not be used for a given {@link value}. // /** * Preferences node which will be used for all Preferences in the * ome.formats package. This must work in tandem with other sources such as * {@link IniFileLoader} */ private final Preferences prefs; /** * Ini-file based configuration source which loads both a static * configuration file and a user-defined configuration file. */ private final IniFileLoader ini; /** * {@link Properties} instance which will also be used for lookups. In the * default case, this is from {@link System#getProperties()} */ private final Properties props; /** * Stores the omeroVersion from omero.properties */ private String omeroVersion = "Unknown"; // // MUTABLE STATE : To prevent every class from having it's own // username/password/port/etc field, all are available here. On save, these // are committed to disk. // public final StrValue agent; public final StrValue hostname; public final StrValue username; public final StrValue password; public final IntValue port; public final LongValue savedProject; public final LongValue savedDataset; public final LongValue savedScreen; public final StrValue sessionKey; public final LongValue group; public final BoolValue doThumbnails; public final BoolValue noStatsInfo; public final StrValue email; public final StrValue userSpecifiedName; public final StrValue userSpecifiedDescription; @Deprecated public final StrValue targetClass; @Deprecated public final LongValue targetId; public final StrValue target; public final BoolValue debug; public final BoolValue contOnError; public final BoolValue sendReport; public final BoolValue sendFiles; public final BoolValue sendLogFile; public final StrValue qaBaseURL; public final BoolValue checkUpgrade; public final BoolValue useCustomImageNaming; public final BoolValue useFullPath; public final IntValue numOfDirectories; public final FileValue savedDirectory; public final StrValue readersPath; public final StrValue checksumAlgorithm; public final BoolValue encryptedConnection; public final BoolValue autoClose; public final AnnotationListValue annotations; public final DoubleArrayValue userPixels; public final static String DEFAULT_QABASEURL = "http://qa.openmicroscopy.org.uk/qa"; /** * Keys for fileset version information entries. * @author m.t.b.carroll@dundee.ac.uk * @since 5.1 */ public static enum VersionInfo { BIO_FORMATS_READER("bioformats.reader"), BIO_FORMATS_VERSION("bioformats.version"), CLIENT_LANGUAGE_NAME("client.language.name"), CLIENT_LANGUAGE_VENDOR("client.language.vendor"), CLIENT_LANGUAGE_COMPILER("client.language.compiler"), CLIENT_LANGUAGE_VERSION("client.language.version"), OMERO_VERSION("omero.version"), OS_NAME("os.name"), OS_VERSION("os.version"), OS_ARCHITECTURE("os.architecture"), LOCALE("locale"); /** the map key corresponding to this instance */ public final String key; private VersionInfo(String key) { this.key = key; } } /** * Static method for creating {@link Preferences} during construction if * necessary. */ private static Preferences prefs() { Preferences prefs = Preferences.userNodeForPackage(ImportConfig.class); try { prefs.flush(); } catch (Exception e) { log.error("Error flushing preferences"); } return prefs; } /** * Simplest constructor which use calls * {@link ImportConfig#ImportConfig(File)} with null. */ public ImportConfig() { this(null); } /** * Calls * {@link ImportConfig#ImportConfig(Preferences, IniFileLoader, Properties)} * with user preferences, a local {@link PreferenceContext}, an * {@link IniFileLoader} initialized with the given argument, and * {@link System#getProperties()}. * * @param configFile * Can be null. */ public ImportConfig(final File configFile) { this(prefs(), new IniFileLoader(configFile), System.getProperties()); } /** * Complete constructor. All values can be null. * * @param prefs user and system preference and configuration data * @param ini loads {@code importer.ini} or other specified file * @param props a Java properties object overriding other sources of preferences and configuration data */ public ImportConfig(final Preferences prefs, IniFileLoader ini, Properties props) { this.prefs = prefs; this.props = props; this.ini = ini; // Various startup requirements ResourceBundle bundle = ResourceBundle.getBundle("omero"); omeroVersion = bundle.getString("omero.version"); log.info("OMERO Version: " + omeroVersion); if (ini != null) { ini.updateFlexReaderServerMaps(); } log.info("Bioformats " + getBioFormatsVersion()); agent = new StrValue("agent", this, "importer"); hostname = new StrValue("hostname", this, "omero.host"); username = new StrValue("username", this, "omero.name"); password = new StrValue("password", this, "omero.pass"); port = new IntValue("port", this, 4064, "omero.port") { @Override public synchronized void load() { super.load(); // Handle previous versions in which a null/"" got stored // to preferences. if (_current.compareAndSet(null, _default)) { log.debug("Replacing port load value with default"); } } }; sessionKey = new StrValue("session", this); group = new LongValue("group", this, null); doThumbnails = new BoolValue("doThumbnails", this, true); noStatsInfo = new BoolValue("noStatsInfo", this, false); email = new StrValue("email", this); qaBaseURL = new StrValue("qaBaseURL", this, DEFAULT_QABASEURL); checkUpgrade = new BoolValue("checkUpgrade", this, true); userSpecifiedName = new StrValue("userSpecifiedName", this); userSpecifiedDescription = new StrValue("userSpecifiedDescription", this); targetClass = new StrValue("targetClass", this); targetId = new LongValue("targetId", this, 0L); target = new StrValue("target", this); savedProject = new LongValue("savedProject", this, 0L); savedDataset = new LongValue("savedDataset", this, 0L); savedScreen = new LongValue("savedScreen", this, 0L); debug = new BoolValue("debug", this, false); contOnError = new BoolValue("contOnError", this, false); sendReport = new BoolValue("sendReport", this, false); sendFiles = new BoolValue("sendFiles", this, true); sendLogFile = new BoolValue("sendLogFile", this, true); useFullPath = new BoolValue("useFullPath", this, true); useCustomImageNaming = new BoolValue("overrideImageName", this, true); numOfDirectories = new IntValue("numOfDirectories", this, 0); savedDirectory = new FileValue("savedDirectory", this); encryptedConnection = new BoolValue("ecryptedConnection", this, true); autoClose = new BoolValue("autoClose", this, false); annotations = new AnnotationListValue( "annotations", this, new ArrayList<Annotation>()); userPixels = new DoubleArrayValue( "userPixels", this, null); readersPath = new StrValue("readersPath", this); checksumAlgorithm = new StrValue("checksumAlgorithm", this); } public String getBioFormatsVersion() { return String.format("version: %s revision: %s date: %s", FormatTools.VERSION, FormatTools.VCS_REVISION, FormatTools.DATE); } public String getOmeroVersion() { return omeroVersion; } /** * Note useful version information that can be extracted from this system, * as provenance that is useful for debugging. * @param versionInfo the map into which version information is to be added */ public void fillVersionInfo(List<NamedValue> versionInfo) { final Map<VersionInfo, String> properties = new HashMap<VersionInfo, String>(); properties.put(VersionInfo.BIO_FORMATS_VERSION, getBioFormatsVersion()); properties.put(VersionInfo.CLIENT_LANGUAGE_NAME, "Java"); properties.put(VersionInfo.CLIENT_LANGUAGE_VENDOR, System.getProperty("java.vendor")); properties.put(VersionInfo.CLIENT_LANGUAGE_COMPILER, System.getProperty("java.compiler")); properties.put(VersionInfo.CLIENT_LANGUAGE_VERSION, System.getProperty("java.version")); properties.put(VersionInfo.LOCALE, Locale.getDefault().toString()); properties.put(VersionInfo.OMERO_VERSION, getOmeroVersion()); properties.put(VersionInfo.OS_NAME, System.getProperty("os.name")); properties.put(VersionInfo.OS_VERSION, System.getProperty("os.version")); properties.put(VersionInfo.OS_ARCHITECTURE, System.getProperty("os.arch")); /* fill any useful information for Ice to serialize */ for (final Map.Entry<VersionInfo, String> property : properties.entrySet()) { if (StringUtils.isNotEmpty(property.getValue())) { versionInfo.add(new NamedValue(property.getKey().key, property.getValue())); } } } /** * Modifies the logging level of everything under the * <code>ome.formats</code>, <code>ome.services.blitz</code>, * <code>ome.system</code> and <code>loci</code> packages hierarchically. * @param levelString the level to set or, if {@code null}, then the * initial debug level is used as before having been changed. */ public void configureDebug(String levelString) { Level level; if (levelString == null) { level = Level.toLevel(ini.getDebugLevel()); } else { level = Level.toLevel(levelString); } setLevel("ome.formats", level); setLevel("ome.services.blitz", level); setLevel("ome.system", level); setLevel("loci", level); } private void setLevel(String loggerName, Level level) { Logger logger = LoggerFactory.getLogger(loggerName); if (!(logger instanceof ch.qos.logback.classic.Logger)) return; ((ch.qos.logback.classic.Logger) logger).setLevel(level); } // // Login methods // /** * Create and return a new OMEROMetadataStoreClient * @return a new OMEROMetadataStoreClient * @throws Exception if the creation failed */ public OMEROMetadataStoreClient createStore() throws Exception { if (!canLogin()) { throw new RuntimeException("Can't create store. See canLogin()"); } OMEROMetadataStoreClient client = new OMEROMetadataStoreClient(); if (sessionKey.empty()) { client.initialize(username.get(), password.get(), hostname.get(), port.get(), group.get(), encryptedConnection.get()); } else { client.initialize(hostname.get(), port.get(), sessionKey.get(), encryptedConnection.get()); } return client; } /** * Check online to see if this is the current version * @return if it is <em>not</em> the current version */ public boolean isUpgradeNeeded() { ResourceBundle bundle = ResourceBundle.getBundle("omero"); String url = bundle.getString("omero.upgrades.url"); UpgradeCheck check = new UpgradeCheck(url, getVersionNumber(), agent.get()); check.run(); return check.isUpgradeNeeded(); } /** * Confirm all information for login is supplied * * @return true if all is ok */ public boolean canLogin() { if (((username.empty() || password.empty()) && sessionKey.empty()) || hostname.empty()) { return false; } return true; } // // GUI related. Delegates to IniFileLoader // /** * @return ini log file */ public String getLogFile() { return ini.getLogFile(); } /** * @return ini home URL */ public String getHomeUrl() { return ini.getHomeUrl(); } /** * @return ini forum URL */ public String getForumUrl() { return ini.getForumUrl(); } /** * @return ini application title */ public String getAppTitle() { return ini.getAppTitle(); } /** * @return ini getForceFileArchiveOn */ public boolean getForceFileArchiveOn() { return ini.getForceFileArchiveOn(); } /** * @return ini getStaticDisableHistory */ public boolean getStaticDisableHistory() { return ini.getStaticDisableHistory(); } /** * @return ini getUserDisableHistory */ public boolean getUserDisableHistory() { return ini.getUserDisableHistory(); } /** * @param b - true if Quaqua should be used */ public void setUserDisableHistory(boolean b) { ini.setUserDisableHistory(b); } /** * @return ini version note */ public String getVersionNumber() { return this.omeroVersion; // + " " + ini.getVersionNote(); } public void setVersionNumber(String s) { this.omeroVersion = s; } /** * @return ini version number */ public String getIniVersionNumber() { return ini.getVersionNumber(); } /** * @return ini user settings directory */ public String getUserSettingsDirectory() { return ini.getUserSettingsDirectory(); } /** * @return ini option for if Qquaqua should be use for Macs */ public boolean getUseQuaqua() { return ini.getUseQuaqua(); } /** * @param b - true if Quaqua should be used */ public void setUseQuaqua(boolean b) { ini.setUseQuaqua(b); } /** * @param level - default debug level */ public void setDebugLevel(int level) { ini.setDebugLevel(level); } /** * @return current debug level */ public int getDebugLevel() { return ini.getDebugLevel(); } /** * @return UI bounds for application window */ public Rectangle getUIBounds() { return ini.getUIBounds(); } /** * @param bounds - set UI bounds for application window */ public void setUIBounds(Rectangle bounds) { ini.setUIBounds(bounds); } /** * @return feedback URL for QA system */ public String getFeedbackUrl() { return qaBaseURL + "/upload_processing/"; } /** * @return token URL for QA system */ public String getTokenUrl() { return qaBaseURL + "/initial/"; } /** * @return upload URL for QA system */ public String getUploaderUrl() { return qaBaseURL + "/upload_processing/"; } /** * @return ini user full path */ public boolean getUserFullPath() { return ini.getUserFullPath(); } /** * @param b ini user full path */ public void setUserFullPath(boolean b) { ini.setUserFullPath(b); } /** * @return custom image naming */ public boolean getCustomImageNaming() { return ini.getCustomImageNaming(); } /** * @param b custom image naming */ public void setCustomImageNaming(boolean b) { ini.setCustomImageNaming(b); } /** * @return number of directories */ public int getNumOfDirectories() { return ini.getNumOfDirectories(); } /** * @param i number of directories */ public void setNumOfDirectories(int i) { ini.setNumOfDirectories(i); } // // HELPERS // /** * Build prompt * * @param value the value * @param prompt the prompt * @param hide use {@code *}s for characters * @param <T> the kind of value contained by the "value" argument */ protected <T> void prompt(Value<T> value, String prompt, boolean hide) { String v = value.toString(); if (hide) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < v.length(); i++) { sb.append("*"); } v = sb.toString(); } System.out.print(String.format("%s[%s]:", prompt, v)); String input; BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (true) { try { input = br.readLine(); if (input == null || input.trim().equals("")) { continue; } value.set(value.fromString(input)); } catch (IOException e) { log.error("IGNORING: ", e); continue; } } } /** * if can't log in request needed information */ public void requestFromUser() { if (!canLogin()) { loadAll(); prompt(hostname, " Enter server name: ", false); prompt(username, " Enter user name: ", false); prompt(password, " Enter password: ", true); } } protected List<Value<?>> values() { List<Value<?>> rv = new ArrayList<Value<?>>(); for (Field f : getClass().getFields()) { try { Object o = f.get(this); if (o instanceof Value) { Value<?> cv = (Value<?>) o; rv.add(cv); } } catch (Exception e) { log.debug("Error during field lookup: " + e); } } return rv; } public Map<String, String> map() { Map<String, String> rv = new HashMap<String, String>(); for (Value<?> cv : values()) { rv.put(cv.key, cv.toString()); } return rv; } /** * Loads gui specific values for which it makes sense to have a preferences values. * * @see #saveAll() */ public void loadGui() { email.load(); } /** * Saves gui specific values for which it makes sense to have a preferences values. * * @see #saveAll() */ public void saveGui() { email.store(); } /** * Loads all the values for which it makes sense to have a preferences values. * * @see #saveAll() */ public void loadAll() { // Moving to expliti calls. // for (Value<?> cv : values()) { // cv.load(); // } savedProject.load(); savedDataset.load(); savedScreen.load(); useCustomImageNaming.load(); useFullPath.load(); numOfDirectories.load(); savedDirectory.load(); sendLogFile.load(); sendFiles.load(); sendReport.load(); port.load(); } /** * @see #loadAll() */ public void saveAll() { // Moving to explicit calls // for (Value<?> cv : values()) { // cv.store(); // } savedProject.store(); savedDataset.store(); savedScreen.store(); useCustomImageNaming.store(); useFullPath.store(); numOfDirectories.store(); savedDirectory.store(); sendLogFile.store(); sendFiles.store(); sendReport.store(); try { prefs.flush(); ini.flushPreferences(); } catch (BackingStoreException e) { log.error(e.toString()); // slf4j migration: toString() throw new RuntimeException(e); } } /** * Container which thread-safely makes a generic configuration value * available, without requiring getters and setters. * * @param <T> the kind of value to associate with the key */ public static abstract class Value<T> { final AtomicReference<T> _current = new AtomicReference<T>(); final String key, omeroKey; final Preferences prefs; final IniFileLoader ini; final Properties props; final T _default; /** * Records the load location */ Object which = null; /** * Ctor taking an {@link ImportConfig} instance meaning that all the * context values are used. */ Value(String key, ImportConfig config) { this(key, config, null, null); } Value(String key, ImportConfig config, T defValue) { this(key, config, defValue, null); } Value(String key, ImportConfig config, T defValue, String omeroKey) { this.key = key; this.omeroKey = omeroKey; this.ini = config.ini; this.prefs = config.prefs; this.props = config.props; _default = defValue; _current.set(null); } /** * Returns the generic type contained by this holder. This does not * touch the persistent stores, but only accesses the value in-memory. * @return the held value */ public T get() { if (_current.get() == null) return _default; else return _current.get(); } /** * Sets the in-memory value, which will get persisted on * {@link #store()} when {@link ImportConfig#saveAll()} is called. * @param t the value to hold */ public void set(T t) { _current.set(t); } @Override public String toString() { T t = get(); if (t == null) { return ""; } else { return t.toString(); } } /** * Stores the current value back to some medium. In each case, * the type-matching source is used <em>except</em> when the * {@link Properties} are used, since this is most likely not a * persistent store. */ public synchronized void store() { if (which instanceof Properties || which instanceof Preferences) { prefs.put(key, toString()); log.debug("Saved " + key + " to " + prefs); } else if (which instanceof IniFileLoader) { // FIXME ((IniFileLoader)which).set log.debug("Saved " + key + " to " + ini); } else if (which == null && prefs != null) { // Loaded from defaults prefs.put(key, toString()); log.debug("Freshly saved " + key + " to " + prefs); } else { log.debug("WHICH:" + which); // Unknown state } } /** * Loads properties from various locations. In order, the * {@link Properties} argument, the {@link PreferenceContext}, the * {@link Preferences}, the {@link IniFileLoader}, and finally the * default value. */ public synchronized void load() { if (empty() && props != null) { set(fromString(props.getProperty(key))); if (!empty()) { which = props; log.debug("Loaded " + key + " from " + props); return; } } if (empty() && prefs != null) { set(fromString(prefs.get(key, ""))); if (!empty()) { which = prefs; log.debug("Loaded " + key + " from " + prefs); return; } } if (empty() && ini != null) { // set(fromString((ini.getProperty(key)); log.debug("Loaded " + key + " from " + ini); // break; FIXME } if (empty()) { set(_default); log.debug("Loaded " + key + " from default"); which = null; } } public boolean empty() { return get() == null; } protected abstract T fromString(String string); } public static class StrValue extends Value<String> { public StrValue(String key, ImportConfig config) { super(key, config); } public StrValue(String key, ImportConfig config, String defValue) { super(key, config, defValue); } public StrValue(String key, ImportConfig config, String defValue, String omeroKey) { super(key, config, defValue, omeroKey); } @Override protected String fromString(String arg0) { return arg0; } public boolean empty() { String s = get(); return s == null || s.length() == 0; } } public static class AnnotationListValue extends Value<List<Annotation>> { public AnnotationListValue(String key, ImportConfig config, List<Annotation> defValue) { super(key, config, defValue); } @Override protected List<Annotation> fromString(String string) { throw new RuntimeException("Not implemented."); } } public static class DoubleArrayValue extends Value<Double[]> { public DoubleArrayValue(String key, ImportConfig config, Double[] defValue) { super(key, config, defValue); } @Override protected Double[] fromString(String string) { throw new RuntimeException("Not implemented."); } } public static class PassValue extends StrValue { public PassValue(String key, ImportConfig config) { super(key, config); } @Override public synchronized void store() { log.trace("Skipping password storage"); } } public static class BoolValue extends Value<Boolean> { public BoolValue(String key, ImportConfig config, boolean defValue) { super(key, config, defValue); } @Override protected Boolean fromString(String arg0) { if (arg0 == null) { return null; } return Boolean.parseBoolean(arg0); } } public static class IntValue extends Value<Integer> { public IntValue(String key, ImportConfig config, int defValue) { super(key, config, Integer.valueOf(defValue)); } public IntValue(String key, ImportConfig config, int defValue, String omeroKey) { super(key, config, Integer.valueOf(defValue), omeroKey); } @Override protected Integer fromString(String arg0) { try { return Integer.valueOf(arg0); } catch (NumberFormatException nfe) { return null; } } } public static class LongValue extends Value<Long> { public LongValue(String key, ImportConfig config, Long defValue) { super(key, config, defValue); } @Override protected Long fromString(String arg0) { try { return Long.valueOf(arg0); } catch (NumberFormatException nfe) { return null; } } } public static class FileValue extends Value<File> { public FileValue(String key, ImportConfig config) { super(key, config); } @Override protected File fromString(String arg0) { if (arg0 == null) { return null; } return new File(arg0); } @Override public File get() { File f = super.get(); if (f != null && f.exists()) { return f; } else { set(null); return null; } } } public ImportTarget getTarget() { TargetBuilder builder = new TargetBuilder(); if (target.get() != null) { ImportTarget t = builder.parse(target.get()).build(); log.info("Using import target: {}", target); return t; } if (DATASET_CLASS.equals(targetClass.get())) { return builder.parse( String.format("%s:%s", DATASET_CLASS, targetId.get())) .build(); } else if (SCREEN_CLASS.equals(targetClass.get())) { return builder.parse( String.format("%s:%s", SCREEN_CLASS, targetId.get())) .build(); } return null; } }