/* *------------------------------------------------------------------------------ * Copyright (C) 2015-2016 University of Dundee. All rights reserved. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * *------------------------------------------------------------------------------ */ package omero.gateway; import java.beans.PropertyChangeListener; import java.beans.PropertyChangeSupport; import java.net.InetAddress; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.StringUtils; import ome.formats.OMEROMetadataStoreClient; import omero.RType; import omero.ServerError; import omero.client; import omero.api.ExporterPrx; import omero.api.IAdminPrx; import omero.api.IConfigPrx; import omero.api.IContainerPrx; import omero.api.IMetadataPrx; import omero.api.IPixelsPrx; import omero.api.IProjectionPrx; import omero.api.IQueryPrx; import omero.api.IRenderingSettingsPrx; import omero.api.IRepositoryInfoPrx; import omero.api.IRoiPrx; import omero.api.IScriptPrx; import omero.api.IUpdatePrx; import omero.api.RawFileStorePrx; import omero.api.RawPixelsStorePrx; import omero.api.RenderingEnginePrx; import omero.api.SearchPrx; import omero.api.ServiceFactoryPrx; import omero.api.StatefulServiceInterfacePrx; import omero.api.ThumbnailStorePrx; import omero.cmd.CmdCallbackI; import omero.cmd.HandlePrx; import omero.cmd.Request; import omero.gateway.cache.CacheService; import omero.gateway.exception.ConnectionStatus; import omero.gateway.exception.DSOutOfServiceException; import omero.gateway.facility.Facility; import omero.gateway.util.NetworkChecker; import omero.grid.ProcessCallbackI; import omero.grid.ScriptProcessPrx; import omero.grid.SharedResourcesPrx; import omero.log.LogMessage; import omero.log.Logger; import omero.model.ExperimenterGroupI; import omero.gateway.model.ExperimenterData; import omero.gateway.model.GroupData; import omero.gateway.util.PojoMapper; import com.google.common.collect.LinkedListMultimap; import com.google.common.collect.ListMultimap; import com.google.common.collect.Multimaps; /** * A Gateway for simplifying access to an OMERO server * * @author Dominik Lindner      <a * href="mailto:d.lindner@dundee.ac.uk">d.lindner@dundee.ac.uk</a> * @since 5.1 */ public class Gateway { /** Property to indicate that a {@link Connector} has been created */ public static final String PROP_CONNECTOR_CREATED = "PROP_CONNECTOR_CREATED"; /** Property to indicate that a {@link Connector} has been closed */ public static final String PROP_CONNECTOR_CLOSED = "PROP_CONNECTOR_CLOSED"; /** Property to indicate that a session has been created */ public static final String PROP_SESSION_CREATED = "PROP_SESSION_CREATED"; /** Property to indicate that a session has been closed */ public static final String PROP_SESSION_CLOSED = "PROP_SESSION_CLOSED"; /** Property to indicate that a {@link Facility} has been created */ public static final String PROP_FACILITY_CREATED = "PROP_FACILITY_CREATED"; /** Property to indicate that an import store has been created */ public static final String PROP_IMPORTSTORE_CREATED = "PROP_IMPORTSTORE_CREATED"; /** Property to indicate that an import store has been closed */ public static final String PROP_IMPORTSTORE_CLOSED = "PROP_IMPORTSTORE_CLOSED"; /** Property to indicate that a rendering engine has been created */ public static final String PROP_RENDERINGENGINE_CREATED = "PROP_RENDERINGENGINE_CREATED"; /** Property to indicate that a rendering engine has been closed */ public static final String PROP_RENDERINGENGINE_CLOSED = "PROP_RENDERINGENGINE_CLOSED"; /** Property to indicate that a stateful service has been created */ public static final String PROP_STATEFUL_SERVICE_CREATED = "PROP_SERVICE_CREATED"; /** Property to indicate that a stateful service has been closed */ public static final String PROP_STATEFUL_SERVICE_CLOSED = "PROP_SERVICE_CLOSED"; /** Property to indicate that a stateless service has been created */ public static final String PROP_STATELESS_SERVICE_CREATED = "PROP_STATELESS_SERVICE_CREATED"; /** Reference to a {@link Logger} */ private Logger log; /** The version of the server the Gateway is connected to */ private String serverVersion; /** Checks status of the network interfaces */ private NetworkChecker networkChecker; /** Flag indicating if the Gateway is connected to a server */ private boolean connected = false; /** Keeps the session alive */ private ScheduledThreadPoolExecutor keepAliveExecutor; /** The login credentials used for connecting to the server */ private LoginCredentials login; /** The logged in user */ private ExperimenterData loggedInUser; /** Holds all {@link Connector}s for different {@link SecurityContext}s */ private ListMultimap<Long, Connector> groupConnectorMap = Multimaps .<Long, Connector> synchronizedListMultimap(LinkedListMultimap .<Long, Connector> create()); /** Optional reference to a {@link CacheService} */ private CacheService cacheService; /** The PropertyChangeSupport */ private final PropertyChangeSupport pcs = new PropertyChangeSupport(this); /** Thread pool for asynchronous method calls */ private ExecutorService executorService; /** Flag to indicate that executor threads should be shutdown on disconnect */ private boolean executorShutdownOnDisconnect = false; /** * Creates a new Gateway instance * @param log A {@link Logger} */ public Gateway(Logger log) { this(log, null, null, false); } /** * Creates a new Gateway instance * * @param log * A {@link Logger} * @param cacheService * A {@link CacheService}, can be <code>null</code> */ public Gateway(Logger log, CacheService cacheService) { this(log, cacheService, null, true); } /** * Creates a new Gateway instance * * @param log * A {@link Logger} * @param cacheService * A {@link CacheService}, can be <code>null</code> * @param executorService * A {@link ExecutorService} for handling asynchronous tasks, can * be <code>null</code> (in which case the Java built-in cached * thread pool will be used) * @param executorShutdownOnDisconnect * Flag to indicate that executor threads should be shutdown on * disconnect (only taken into account if an * {@link ExecutorService} was provided; the default cached * thread pool will be shut down by default) */ public Gateway(Logger log, CacheService cacheService, ExecutorService executorService, boolean executorShutdownOnDisconnect) { this.log = log; this.cacheService = cacheService; this.executorService = executorService == null ? Executors .newCachedThreadPool() : executorService; this.executorShutdownOnDisconnect = executorService == null ? true : executorShutdownOnDisconnect; } /** * Submits an async task * * @param task * The task * @return The callback reference */ public <T> Future<T> submit(Callable<T> task) { return executorService.submit(task); } // Public connection handling methods /** * Connect to the server * * @param c * The {@link LoginCredentials} * @return The {@link ExperimenterData} who is logged in * @throws DSOutOfServiceException * If the connection can't be established */ public ExperimenterData connect(LoginCredentials c) throws DSOutOfServiceException { client client = createSession(c); loggedInUser = login(client, c); connected = true; return loggedInUser; } /** * Get the currently logged in user * * @return See above. */ public ExperimenterData getLoggedInUser() { return loggedInUser; } /** * Disconnects from the server */ public void disconnect() { if (executorShutdownOnDisconnect) { // shutdown still running asynchronous tasks executorService.shutdown(); try { if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) { executorService.shutdownNow(); if (!executorService.awaitTermination(30, TimeUnit.SECONDS)) getLogger().warn(this, "Could not terminate all asynchronous tasks"); } } catch (InterruptedException ie) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } boolean online = isNetworkUp(false); List<Connector> connectors = getAllConnectors(); Iterator<Connector> i = connectors.iterator(); while (i.hasNext()) i.next().shutDownServices(true); i = connectors.iterator(); while (i.hasNext()) { try { i.next().close(online); } catch (Throwable e) { if (log != null) { log.warn(this, new LogMessage("Cannot close connector", e)); } } } Facility.clear(); groupConnectorMap.clear(); keepAliveExecutor.shutdown(); connected = false; if (cacheService != null) cacheService.shutDown(); } /** * Tries to rejoin the session. * * @return See above. */ public boolean joinSession() { try { isNetworkUp(false); // Force re-check to prevent hang } catch (Exception e) { // no need to handle the exception. } boolean networkup = isNetworkUp(false); connected = false; if (!networkup) { if (log != null) { log.warn(this, "Network is down"); } return false; } List<Connector> connectors = removeAllConnectors(); Iterator<Connector> i = connectors.iterator(); Connector c; int index = 0; while (i.hasNext()) { c = i.next(); try { if (log != null) log.debug(this, "joining the session "); c.joinSession(); groupConnectorMap.put(c.getGroupID(), c); } catch (Throwable t) { if (log != null) log.error(this, new LogMessage("Failed to join the session ", t)); // failed to join so we create a new one, first we shut down try { c.shutDownServices(true); c.close(networkup); } catch (Throwable e) { if (log != null) log.error(this, new LogMessage( "Failed to close the session ", t)); } if (!groupConnectorMap.containsKey(c.getGroupID())) { try { createConnector(new SecurityContext(c.getGroupID()), false); } catch (Exception e) { if (log != null) log.error(this, new LogMessage( "Failed to create connector ", e)); index++; } } } } connected = index == 0; return connected; } /** * Check if the Gateway is still connected to the server * * @return See above. */ public boolean isConnected() { return connected; } /** * Get the ID of the current session * * @param user * The user to get the session ID for * @return See above */ public String getSessionId(ExperimenterData user) { try { Connector c = getConnector(new SecurityContext(user.getGroupId()), false, false); if (c != null) { return c.getClient().getSessionId(); } } catch (DSOutOfServiceException e) { } return null; } /** * Get the version of the server the Gateway is connected to * * @return See above * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public String getServerVersion() throws DSOutOfServiceException { if (serverVersion == null) { throw new DSOutOfServiceException("Not logged in."); } return serverVersion; } /** * Get a {@link Facility} to perform further operations with the server * * @param type * The kind of {@link Facility} to request * @return See above * @throws ExecutionException * If the {@link Facility} can't be retrieved or instantiated */ public <T extends Facility> T getFacility(Class<T> type) throws ExecutionException { return Facility.getFacility(type, this); } // General public methods /** * Adds a {@link PropertyChangeListener} * @param listener The listener */ public void addPropertyChangeListener(PropertyChangeListener listener) { this.pcs.addPropertyChangeListener(listener); } /** * Removes a {@link PropertyChangeListener} * @param listener The listener */ public void removePropertyChangeListener(PropertyChangeListener listener) { this.pcs.removePropertyChangeListener(listener); } /** * Get the {@link PropertyChangeListener}s * @return See above */ public PropertyChangeListener[] getPropertyChangeListeners() { return this.pcs.getPropertyChangeListeners(); } /** * Executes the commands. * * @param ctx * The {@link SecurityContext} * @param commands * The commands to execute. * @param target * The target context is any. * @return See above. * @throws Throwable If an error occurred */ public CmdCallbackI submit(SecurityContext ctx, List<Request> commands, SecurityContext target) throws Throwable { Connector c = getConnector(ctx, true, false); if (c != null) return c.submit(commands, target); return null; } /** * Directly submit a {@link Request} to the server * * @param ctx * The {@link SecurityContext} * @param cmd * The {@link Request} to submit * @return A callback reference, {@link CmdCallbackI} * @throws Throwable If an error occurred */ public CmdCallbackI submit(SecurityContext ctx, Request cmd) throws Throwable { Connector c = getConnector(ctx, true, false); if (c != null) { client client = getConnector(ctx, true, false).getClient(); HandlePrx handle = client.getSession().submit(cmd); return new CmdCallbackI(client, handle); } return null; } /** * Close Import for a certain user * * @param ctx * The {@link SecurityContext} * @param userName * The name of the user which import should be closed */ public void closeImport(SecurityContext ctx, String userName) { try { Connector c = getConnector(ctx, false, true); if (c != null) { if (StringUtils.isNotEmpty(userName)) c = c.getConnector(userName); c.closeImport(); } } catch (Throwable e) { if (log != null) log.warn(this, "Failed to close import: " + e); } } /** * Run a script on the server * * @param ctx * The {@link SecurityContext} * @param scriptID * The ID of the script * @param parameters * Parameters for the script * @return A callback reference, {@link ProcessCallbackI} * @throws DSOutOfServiceException * If the connection is broken, or not logged in * @throws ServerError If an error in the script execution occurred */ public ProcessCallbackI runScript(SecurityContext ctx, long scriptID, Map<String, RType> parameters) throws DSOutOfServiceException, ServerError { Connector c = getConnector(ctx); if (c == null) return null; IScriptPrx svc = c.getScriptService(); ScriptProcessPrx prx = svc.runScript(scriptID, parameters, null); return new ProcessCallbackI(c.getClient(), prx); } /** * Provides access to the {@link Logger} * * @return See above */ public Logger getLogger() { return log; } /** * Provides access to the {@link CacheService} * * @return See above */ public CacheService getCacheService() { return cacheService; } // Public service access methods /** * Returns the {@link SharedResourcesPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public SharedResourcesPrx getSharedResources(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getSharedResources(); return null; } /** * Returns the {@link IRenderingSettingsPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IRenderingSettingsPrx getRenderingSettingsService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getRenderingSettingsService(); return null; } /** * Returns the {@link IRepositoryInfoPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IRepositoryInfoPrx getRepositoryService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getRepositoryService(); return null; } /** * Returns the {@link IScriptPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IScriptPrx getScriptService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getScriptService(); return null; } /** * Returns the {@link IContainerPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IContainerPrx getPojosService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getPojosService(); return null; } /** * Returns the {@link IQueryPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IQueryPrx getQueryService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getQueryService(); return null; } /** * Returns the {@link IUpdatePrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IUpdatePrx getUpdateService(SecurityContext ctx) throws DSOutOfServiceException { return getUpdateService(ctx, null); } /** * Returns the {@link IUpdatePrx} service. * * @param ctx * The {@link SecurityContext} * @param userName The username * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IUpdatePrx getUpdateService(SecurityContext ctx, String userName) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (StringUtils.isNotEmpty(userName)) { try { c = c.getConnector(userName); } catch (Throwable e) { throw new DSOutOfServiceException( "Can't get derived connector.", e); } } if (c != null) return c.getUpdateService(); return null; } /** * Returns the {@link IMetadataPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IMetadataPrx getMetadataService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getMetadataService(); return null; } /** * Returns the {@link IRoiPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IRoiPrx getROIService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getROIService(); return null; } /** * Returns the {@link IConfigPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IConfigPrx getConfigService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getConfigService(); return null; } /** * Returns the {@link ThumbnailStorePrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public ThumbnailStorePrx getThumbnailService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getThumbnailService(); return null; } /** * Returns the {@link ExporterPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public ExporterPrx getExporterService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getExporterService(); return null; } /** * Returns the {@link RawFileStorePrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException Thrown if the service cannot be initialized. */ public RawFileStorePrx getRawFileService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getRawFileService(); return null; } /** * Returns the {@link RawPixelsStorePrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public RawPixelsStorePrx getPixelsStore(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getPixelsStore(); return null; } /** * Returns the {@link IPixelsPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IPixelsPrx getPixelsService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getPixelsService(); return null; } /** * Returns the {@link SearchPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public SearchPrx getSearchService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getSearchService(); return null; } /** * Returns the {@link IProjectionPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IProjectionPrx getProjectionService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getProjectionService(); return null; } /** * Returns the {@link IAdminPrx} service. * * @param ctx * The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IAdminPrx getAdminService(SecurityContext ctx) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getAdminService(); return null; } /** * Returns the {@link IAdminPrx} service. * * @param ctx * The {@link SecurityContext} * @param secure * Pass <code>true</code> to have a secure admin service, * <code>false</code> otherwise. * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public IAdminPrx getAdminService(SecurityContext ctx, boolean secure) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (c != null) return c.getAdminService(); return null; } /** * Creates or recycles the import store. * @param ctx The {@link SecurityContext} * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public OMEROMetadataStoreClient getImportStore(SecurityContext ctx) throws DSOutOfServiceException { return getImportStore(ctx, null); } /** * Creates or recycles the import store. * @param ctx The {@link SecurityContext} * @param userName The username * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. */ public OMEROMetadataStoreClient getImportStore(SecurityContext ctx, String userName) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); if (StringUtils.isNotEmpty(userName)) { try { c = c.getConnector(userName); } catch (Throwable e) { throw new DSOutOfServiceException( "Can't get derived connector.", e); } } if (c != null) return c.getImportStore(); return null; } /** * Returns the {@link RenderingEnginePrx Rendering service}. * @param ctx The {@link SecurityContext} * @param pixelsID The pixels ID * @return See above. * @throws DSOutOfServiceException * Thrown if the service cannot be initialized. * @throws ServerError * Thrown if the service cannot be initialized. */ public RenderingEnginePrx getRenderingService(SecurityContext ctx, long pixelsID) throws DSOutOfServiceException, ServerError { Connector c = getConnector(ctx, true, false); if (c != null) return c.getRenderingService(pixelsID, ctx.getCompression()); return null; } // Internal helper methods /** * Clears the groupConnector Map * * @return The connectors the map held previously */ private List<Connector> removeAllConnectors() { synchronized (groupConnectorMap) { // This should be the only location which calls values(). List<Connector> rv = new ArrayList<Connector>( groupConnectorMap.values()); groupConnectorMap.clear(); return rv; } } /** * Initiates a session * * @param c * The login credentials * @return The client * @throws DSOutOfServiceException */ private client createSession(LoginCredentials c) throws DSOutOfServiceException { client secureClient = null; try { // client must be cleaned up by caller. List<String> args = c.getArguments(); String username; if (args != null) { secureClient = new client(args.toArray(new String[args.size()])); username = secureClient.getProperty("omero.user"); } else { username = c.getUser().getUsername(); if (c.getServer().getPort() > 0) secureClient = new client(c.getServer().getHostname(), c .getServer().getPort()); else secureClient = new client(c.getServer().getHostname()); } secureClient.setAgent(c.getApplicationName()); ServiceFactoryPrx entryEncrypted; boolean session = true; ServiceFactoryPrx guestSession = null; try { // Check if it is a session first guestSession = secureClient.createSession( "guest", "guest"); this.pcs.firePropertyChange(PROP_SESSION_CREATED, null, secureClient.getSessionId()); guestSession.getSessionService().getSession(username); } catch (Exception e) { // thrown if it is not a session or session has expired. session = false; } finally { String id = secureClient.getSessionId(); secureClient.closeSession(); this.pcs.firePropertyChange(PROP_SESSION_CLOSED, null, id); } if (session) { entryEncrypted = secureClient.joinSession(username); } else { if (args != null) { entryEncrypted = secureClient.createSession(); } else { entryEncrypted = secureClient.createSession(c.getUser() .getUsername(), c.getUser().getPassword()); } } this.pcs.firePropertyChange(PROP_SESSION_CREATED, null, secureClient.getSessionId()); serverVersion = entryEncrypted.getConfigService().getVersion(); if (c.isCheckNetwork()) { try { String ip = InetAddress.getByName( c.getServer().getHostname()).getHostAddress(); networkChecker = new NetworkChecker(ip, log); } catch (Exception e) { if (log != null) log.warn(this, new LogMessage( "Failed to get inet address: " + c.getServer().getHostname(), e)); } } Runnable r = new Runnable() { public void run() { try { keepSessionAlive(); } catch (Throwable t) { if (log != null) log.warn( this, new LogMessage( "Exception while keeping the services alive", t)); } } }; keepAliveExecutor = new ScheduledThreadPoolExecutor(1); keepAliveExecutor.scheduleWithFixedDelay(r, 60, 60, TimeUnit.SECONDS); } catch (Throwable e) { if (secureClient != null) { secureClient.__del__(); } throw new DSOutOfServiceException( "Can't connect to OMERO. OMERO info not valid", e); } return secureClient; } /** * Logs in a certain user * * @param client * The client * @param cred * The login credentials * @return The user which is logged in * @throws DSOutOfServiceException */ private ExperimenterData login(client client, LoginCredentials cred) throws DSOutOfServiceException { this.login = cred; Connector connector = null; SecurityContext ctx = null; try { ServiceFactoryPrx entryEncrypted = client.getSession(); IAdminPrx prx = entryEncrypted.getAdminService(); String userName = prx.getEventContext().userName; ExperimenterData exp = (ExperimenterData) PojoMapper .asDataObject(prx.lookupExperimenter(userName)); if (cred.getGroupID() >= 0) { long defaultID = -1; try { defaultID = exp.getDefaultGroup().getId(); } catch (Exception e) { } ctx = new SecurityContext(defaultID); ctx.setServerInformation(cred.getServer()); connector = new Connector(ctx, client, entryEncrypted, cred.isEncryption(), log); for (PropertyChangeListener l : this.pcs .getPropertyChangeListeners()) connector.addPropertyChangeListener(l); this.pcs.firePropertyChange(Gateway.PROP_CONNECTOR_CREATED, null, client.getSessionId()); groupConnectorMap.put(ctx.getGroupID(), connector); if (defaultID == cred.getGroupID()) return exp; try { changeCurrentGroup(ctx, exp, cred.getGroupID()); ctx = new SecurityContext(cred.getGroupID()); ctx.setServerInformation(cred.getServer()); ctx.setCompression(cred.getCompression()); connector = new Connector(ctx, client, entryEncrypted, cred.isEncryption(), log); for (PropertyChangeListener l : this.pcs .getPropertyChangeListeners()) connector.addPropertyChangeListener(l); exp = getUserDetails(ctx, userName); groupConnectorMap.put(ctx.getGroupID(), connector); } catch (Exception e) { LogMessage msg = new LogMessage(); msg.print("Error while changing group."); msg.print(e); if (log != null) log.debug(this, msg); } } // Connector now controls the secureClient for closing. String host = client.getProperty("omero.host"); cred.getServer().setHostname(host); String port = client.getProperty("omero.port"); cred.getServer().setPort(Integer.parseInt(port)); ctx = new SecurityContext(exp.getDefaultGroup().getId()); ctx.setServerInformation(cred.getServer()); ctx.setCompression(cred.getCompression()); connector = new Connector(ctx, client, entryEncrypted, cred.isEncryption(), log); for(PropertyChangeListener l : this.pcs.getPropertyChangeListeners()) connector.addPropertyChangeListener(l); this.pcs.firePropertyChange(Gateway.PROP_CONNECTOR_CREATED, null, client.getSessionId()); groupConnectorMap.put(ctx.getGroupID(), connector); return exp; } catch (Throwable e) { throw new DSOutOfServiceException( "Cannot log in. User credentials not valid", e); } } /** * Keeps the session active, prevents premature automatic closing of the * session. * * @throws DSOutOfServiceException */ private void keepSessionAlive() throws DSOutOfServiceException { // Check if network is up before keeping service otherwise // we block until timeout. try { isNetworkUp(false); } catch (Exception e) { throw new DSOutOfServiceException("Network not available"); } Iterator<Connector> i = getAllConnectors().iterator(); Connector c; while (i.hasNext()) { c = i.next(); if (c.needsKeepAlive()) { if (!c.keepSessionAlive()) { throw new DSOutOfServiceException("Network not available"); } } } } /** * Change the current group of an user * * @param ctx * The {@link SecurityContext} * @param exp * The user which group should be changed * @param groupID * The new group of the user * @throws DSOutOfServiceException */ private void changeCurrentGroup(SecurityContext ctx, ExperimenterData exp, long groupID) throws DSOutOfServiceException { List<GroupData> groups = exp.getGroups(); Iterator<GroupData> i = groups.iterator(); GroupData group = null; boolean in = false; while (i.hasNext()) { group = i.next(); if (group.getId() == groupID) { in = true; break; } } if (in) { Connector c = getConnector(ctx, true, false); try { IAdminPrx svc = c.getAdminService(); svc.setDefaultGroup(exp.asExperimenter(), new ExperimenterGroupI(groupID, false)); } catch (Exception e) { throw new DSOutOfServiceException( "Can't modify the current group for user:" + exp.getId(), e); } } String s = "Can't modify the current group.\n\n"; if (!in) { throw new DSOutOfServiceException(s); } } /** * Get the user details of a certain user * * @param ctx * The {@link SecurityContext} * @param name * The name of the user * @return See above * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public ExperimenterData getUserDetails(SecurityContext ctx, String name) throws DSOutOfServiceException { Connector c = getConnector(ctx, true, false); try { IAdminPrx service = c.getAdminService(); return (ExperimenterData) PojoMapper.asDataObject(service .lookupExperimenter(name)); } catch (Exception e) { throw new DSOutOfServiceException("Cannot retrieve user's data ", e); } } /** * Checks if the network interface is up. * * @param useCachedValue * Uses the result of the last check instead of really performing * the test if the last check is not older than 5 sec * @return See above */ public boolean isNetworkUp(boolean useCachedValue) { try { if (networkChecker != null) return networkChecker.isNetworkup(useCachedValue); return true; } catch (Throwable t) { if (log != null) log.warn(this, new LogMessage("Error on isNetworkUp check", t)); } return false; } /** * Get all connectors * * @return See above */ private List<Connector> getAllConnectors() { synchronized (groupConnectorMap) { // This should be the only location which calls values(). return new ArrayList<Connector>(groupConnectorMap.values()); } } /** * Get a connector for a certain {@link SecurityContext} * * @param ctx * The {@link SecurityContext} * @return See above * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public Connector getConnector(SecurityContext ctx) throws DSOutOfServiceException { return getConnector(ctx, false, false); } /** * Close a connector for a certain {@link SecurityContext} * * @param ctx * The {@link SecurityContext} */ public void closeConnector(SecurityContext ctx) { List<Connector> clist = groupConnectorMap.removeAll(ctx.getGroupID()); if (CollectionUtils.isEmpty(clist)) return; for (Connector c : clist) { try { c.close(isNetworkUp(true)); } catch (Throwable e) { new Exception("Cannot close the connector", e); } } } /** * Close a service * * @param ctx * The {@link SecurityContext} * @param svc * The service to close */ public void closeService(SecurityContext ctx, StatefulServiceInterfacePrx svc) { try { Connector c = getConnector(ctx, false, true); if (c != null) { c.close(svc); } else { svc.close(); // Last ditch effort to close. } } catch (Exception e) { if (log != null) log.warn(this, String.format("Failed to close %s: %s", svc, e)); } } /** * Create a {@link RawPixelsStorePrx} * * @param ctx * The {@link SecurityContext} * @return See above * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public RawPixelsStorePrx createPixelsStore(SecurityContext ctx) throws DSOutOfServiceException { if (ctx == null) return null; Connector c = getConnector(ctx, true, false); return c.getPixelsStore(); } /** * Create a {@link ThumbnailStorePrx} * * @param ctx * The {@link SecurityContext} * @return See above * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public ThumbnailStorePrx createThumbnailStore(SecurityContext ctx) throws DSOutOfServiceException { if (ctx == null) return null; // check import as Connector c = getConnector(ctx, true, false); ExperimenterData exp = ctx.getExperimenterData(); if (exp != null && ctx.isSudo()) { try { c = c.getConnector(exp.getUserName()); } catch (Throwable e) { throw new DSOutOfServiceException( "Cannot create ThumbnailStore", e); } } // Pass close responsibility off to the caller. return c.getThumbnailService(); } /** * Checks if there is a {@link Connector} for a particular * {@link SecurityContext} * * @param ctx * The {@link SecurityContext} * @return <code>true</code> if there is one, <code>false</code> otherwise * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public boolean isAlive(SecurityContext ctx) throws DSOutOfServiceException { return null != getConnector(ctx, true, true); } /** * Returns the connector corresponding to the passed context. * * @param ctx * The security context. * @param recreate * whether or not to allow the recreation of the * {@link Connector}. A {@link DSOutOfServiceException} is thrown * if this is set to false and no {@link Connector} is available. * @param permitNull * whether or not to throw a {@link DSOutOfServiceException} if * no {@link Connector} is available by the end of the execution. * @return See above. * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ public Connector getConnector(SecurityContext ctx, boolean recreate, boolean permitNull) throws DSOutOfServiceException { try { isNetworkUp(true); // Need safe version? } catch (Exception e1) { if (permitNull) { if (log != null) log.warn( this, new LogMessage( "Failed to check network. Returning null connector", e1)); return null; } throw new DSOutOfServiceException("Network not available", e1, ConnectionStatus.NETWORK); } if (!isNetworkUp(true)) { if (permitNull) { if (log != null) log.warn(this, "Network down. Returning null connector"); return null; } throw new DSOutOfServiceException( "Network down. Returning null connector", ConnectionStatus.NETWORK); } if (ctx == null) { if (permitNull) { if (log != null) log.warn(this, "Null SecurityContext"); return null; } throw new DSOutOfServiceException("Null SecurityContext"); } Connector c = null; List<Connector> clist = groupConnectorMap.get(ctx.getGroupID()); if (clist.size() > 0) { c = clist.get(0); if (c.needsKeepAlive()) { // Check if network is up before keeping service otherwise // we block until timeout. try { isNetworkUp(true); } catch (Exception e) { throw new DSOutOfServiceException("Network down.", e, ConnectionStatus.NETWORK); } if (!c.keepSessionAlive()) { throw new DSOutOfServiceException( "Network down. Session not alive", ConnectionStatus.LOST_CONNECTION); } } } // We are going to create a connector and activate a session. if (c == null) { if (recreate) c = createConnector(ctx, permitNull); else { if (permitNull) { if (log != null) log.warn(this, "Cannot re-create. Returning null connector"); return null; } throw new DSOutOfServiceException("Not allowed to recreate"); } } ExperimenterData exp = ctx.getExperimenterData(); if (exp != null && ctx.isSudo()) { try { c = c.getConnector(exp.getUserName()); } catch (Throwable e) { throw new DSOutOfServiceException("Could not derive connector", e); } } return c; } /** * Shuts down the connectors created while creating/importing data for other * users. * * @param ctx The {@link SecurityContext} * @throws Exception * Thrown if the connector cannot be closed. */ public void shutDownDerivedConnector(SecurityContext ctx) throws Exception { Connector c = getConnector(ctx, true, true); if (c == null) return; try { c.closeDerived(isNetworkUp(true)); } catch (Throwable e) { new Exception("Cannot close the derived connectors", e); } } /** * Returns the rendering engines to re-activate. * * @return See above. */ public Map<SecurityContext, Set<Long>> getRenderingEngines() { Map<SecurityContext, Set<Long>> l = new HashMap<SecurityContext, Set<Long>>(); Iterator<Connector> i = getAllConnectors().iterator(); while (i.hasNext()) { l.putAll(i.next().getRenderingEngines()); } return l; } /** * Shuts down the rendering engine * * @param ctx * The {@link SecurityContext} * @param pixelsID The pixels id */ public void shutdownRenderingEngine(SecurityContext ctx, long pixelsID) { List<Connector> clist = groupConnectorMap.get(ctx.getGroupID()); for (Connector c : clist) { c.shutDownRenderingEngine(pixelsID); } } /** * Create a {@link Connector} for a particular {@link SecurityContext} * * @param ctx * The {@link SecurityContext} * @param permitNull * If not set throws an {@link DSOutOfServiceException} if the * creation failed * @return The {@link Connector} * @throws DSOutOfServiceException * If the connection is broken, or not logged in */ private Connector createConnector(SecurityContext ctx, boolean permitNull) throws DSOutOfServiceException { Connector c = null; try { ctx.setServerInformation(login.getServer()); // client will be cleaned up by connector client client = new client(login.getServer().getHostname(), login .getServer().getPort()); ServiceFactoryPrx prx = client.createSession(login.getUser() .getUsername(), login.getUser().getPassword()); if (ctx.getGroupID() >= 0) prx.setSecurityContext(new ExperimenterGroupI(ctx.getGroupID(), false)); c = new Connector(ctx, client, prx, login.isEncryption(), log); for (PropertyChangeListener l : this.pcs .getPropertyChangeListeners()) c.addPropertyChangeListener(l); this.pcs.firePropertyChange(Gateway.PROP_CONNECTOR_CREATED, null, client.getSessionId()); groupConnectorMap.put(ctx.getGroupID(), c); } catch (Throwable e) { if (!permitNull) { throw new DSOutOfServiceException("Failed to create connector", e); } } return c; } }