/*
* Copyright (C) 2008-2013 Glencoe Software, Inc. 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;
import static omero.rtypes.rlong;
import static omero.rtypes.rstring;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import ome.util.Utils;
import ome.util.checksum.ChecksumProviderFactory;
import ome.util.checksum.ChecksumProviderFactoryImpl;
import ome.util.checksum.ChecksumType;
import omero.api.ClientCallback;
import omero.api.ClientCallbackPrxHelper;
import omero.api.ISessionPrx;
import omero.api.IUpdatePrx;
import omero.api.RawFileStorePrx;
import omero.api.ServiceFactoryPrx;
import omero.api.ServiceFactoryPrxHelper;
import omero.api.ServiceInterfacePrx;
import omero.api.StatefulServiceInterface;
import omero.api.StatefulServiceInterfacePrx;
import omero.api._ClientCallbackDisp;
import omero.constants.AGENT;
import omero.constants.IP;
import omero.model.ChecksumAlgorithm;
import omero.model.ChecksumAlgorithmI;
import omero.model.OriginalFile;
import omero.model.OriginalFileI;
import omero.model.enums.ChecksumAlgorithmSHA1160;
import omero.util.ModelObjectFactoryRegistry;
import omero.util.Resources;
import omero.util.Resources.Entry;
import Glacier2.CannotCreateSessionException;
import Glacier2.PermissionDeniedException;
import Glacier2.SessionNotExistException;
import Ice.Current;
/**
* Central client-side blitz entry point. This class uses solely Ice
* functionality to provide access to blitz (as opposed to also using Spring)
* and should be in sync with the OmeroPy omero.client class as well as the
* OmeroCpp omero::client class.
*
* In order to more closely map the destructors in Python and C++, this class
* keeps a collection of {@link omero.client} instances, which
* are destroyed on program termination.
*
* Typical usage: <code>
* omero.client client = new omero.client(); // Uses ICE_CONFIG
* omero.client client = new omero.client(host); // Defines "omero.host"
* omero.client client = new omero.client(host, port); // Defines "omero.host" and "omero.port"
*</code>
*
* For more information, see <a
* href="http://trac.openmicroscopy.org.uk/ome/wiki/ClientDesign">
* http://trac.openmicroscopy.org.uk/ome/wiki/ClientDesign </a>
*
* @author Josh Moore, josh at glencoesoftware.com
* @since 3.0-Beta3
*/
public class client {
/**
* A {@link java.util.Collection} of all the {@link omero.client} instances
* created so that we can guarantee that we at least <i>attempt</i> to shut
* them down before exiting.
*/
private final static Set<client> CLIENTS = Collections
.synchronizedSet(new HashSet<client>());
static {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
Set<client> clients = new HashSet<client>(CLIENTS);
for (client client : clients) {
try {
client.__del__();
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
}
/**
* See {@link #setAgent(String)}
*/
private volatile String __agent = "OMERO.java";
/**
* See {@link #setIP(String)}
*/
private volatile String __ip;
/**
* Identifier for this client instance. Multiple client uuids may be
* attached to a single session uuid.
*/
private volatile String __uuid;
/**
* {@link Ice.InitializationData} from the last communicator used to create
* the {@link #__ic} if nulled after {@link #closeSession()}.
*/
private volatile Ice.InitializationData __previous;
/**
* {@link Ice.ObjectAdapter} containing the {@link ClientCallback} for this
* instance.
*/
private volatile Ice.ObjectAdapter __oa;
/**
* Single communicator for this {@link omero.client}. Nullness is used as a
* test of what state the client is in, therefore all access is sychronized
* by {@link #lock}
*/
private volatile Ice.Communicator __ic;
/**
* Single session for this {@link omero.client}. Nullness is used as a test
* of what state the client is in, like {@link #__ic}, therefore all access
* is synchronized by {@link #lock}
*
*/
private volatile ServiceFactoryPrx __sf;
/**
* Callback object which is linked to this router session.
*/
private volatile CallbackI __cb;
/**
* If non-null, then has access to this client instance and will
* periodically call
* {@link ServiceFactoryPrx#keepAlive(omero.api.ServiceInterfacePrx)} in
* order to keep any session alive. This can be enabled either via the
* omero.keep_alive configuration property, or by calling the
* {@link #enableKeepAlive(int)} method. Once enabled, the period cannot be
* adjusted during a single session.
*/
private volatile Resources __resources;
/**
* Whether or not remote calls are allowed during shutdown.
* If false (the default), then the instance will try to
* connect to the server and free any resources. Otherwise,
* a fastShutdown will take place. The most common reason
* to perform a fast shutdown is the loss of network
* connection. Calling "waitForShutdown" on the Ice stack
* without the proper connection will hang. (See #9673)
*/
private AtomicBoolean fastShutdown = new AtomicBoolean(false);
/**
* @see #isSecure()
*/
private final boolean insecure;
// Creation
// =========================================================================
private static Properties defaultRouter(String host, int port) {
Properties p = new Properties();
p.setProperty("omero.host", host);
p.setProperty("omero.port", Integer.toString(port));
return p;
}
/**
* Calls {@link #client(Ice.InitializationData)} with a new
* {@link Ice.InitializationData}
*/
public client() {
this(new Ice.InitializationData());
}
/**
* Creates an {@link Ice.Communicator} from a {@link Ice.InitializationData}
* instance. Cannot be null.
* @param id the Ice initialization data
*/
public client(Ice.InitializationData id) {
insecure = false;
init(id);
}
/**
* Creates an {@link Ice.Communicator} pointing at the given server using
* the {@link ome.system.Server#DEFAULT_PORT}.
* @param host the server host name
*/
public client(String host) {
this(defaultRouter(host, omero.constants.GLACIER2PORT.value));
}
/**
* Creates an {@link Ice.Communicator} pointing at the given server with the
* non-standard port.
* @param host the server host name
* @param port the server port number
*/
public client(String host, int port) {
this(defaultRouter(host, port));
}
/**
* Calls {@link #client(String[], Ice.InitializationData)} with a new
* {@link Ice.InitializationData}
* @param args command-line arguments
*/
public client(String[] args) {
this(args, new Ice.InitializationData());
}
/**
* Creates an {@link Ice.Communicator} from command-line arguments. These
* are parsed via Ice.Properties.parseIceCommandLineOptions(args) and
* Ice.Properties.parseCommandLineOptions("omero", args)
* @param args command-line arguments
* @param id the Ice initialization data
*/
public client(String[] args, Ice.InitializationData id) {
insecure = false;
if (id.properties == null) {
id.properties = Ice.Util.createProperties(args);
}
args = id.properties.parseIceCommandLineOptions(args);
args = id.properties.parseCommandLineOptions("omero", args);
init(id);
}
/**
* Creates an {@link Ice.Communicator} from multiple files.
* @param files files from which to load properties
*/
public client(File... files) {
insecure = false;
Ice.InitializationData id = new Ice.InitializationData();
id.properties = Ice.Util.createProperties(new String[] {});
for (File file : files) {
id.properties.load(file.getAbsolutePath());
}
init(id);
}
/**
* Creates an {@link Ice.Communicator} from a {@link Map} instance. The
* {@link String} representation of each member is added to the
* {@link Ice.Properties} under the {@link String} representation of the
* key.
* @param p properties
*/
public client(Map p) {
this(p, true);
}
private client(Map p, boolean secure) {
this.insecure = !secure;
Ice.InitializationData id = new Ice.InitializationData();
id.properties = Ice.Util.createProperties(new String[] {});
if (p != null) {
for (Object key : p.keySet()) {
id.properties
.setProperty(key.toString(), p.get(key).toString());
}
}
init(id);
}
private void optionallySetProperty(Ice.InitializationData id, String key, String def) {
String val = id.properties.getProperty(key);
if (val == null || val.length() == 0) {
val = def;
}
id.properties.setProperty(key, val);
}
/**
* Initializes the current client via an {@link Ice.InitializationData}
* instance. This is called by all of the constructors, but may also be
* called on {@link #createSession(String, String)} if a previous call to
* {@link #closeSession()} has nulled the {@link Ice.Communicator}
*/
private void init(Ice.InitializationData id) {
if (id == null) {
throw new ClientError("No initialization data provided.");
}
if (id.properties == null) {
id.properties = Ice.Util.createProperties(new String[] {});
}
// Strictly necessary for this class to work
optionallySetProperty(id, "Ice.ImplicitContext", "Shared");
if (Ice.Util.intVersion() >= 30600) {
optionallySetProperty(id, "Ice.ACM.Client.Timeout",
""+omero.constants.ACMCLIENTTIMEOUT.value);
optionallySetProperty(id, "Ice.ACM.Client.Heartbeat", ""+
omero.constants.ACMCLIENTHEARTBEAT.value);
} else {
optionallySetProperty(id, "Ice.ACM.Client", "0");
}
optionallySetProperty(id, "Ice.CacheMessageBuffers", "0");
optionallySetProperty(id, "Ice.RetryIntervals", "-1");
optionallySetProperty(id, "Ice.Default.EndpointSelection", "Ordered");
optionallySetProperty(id, "Ice.Default.PreferSecure", "1");
optionallySetProperty(id, "Ice.Plugin.IceSSL", "IceSSL.PluginFactory");
optionallySetProperty(id, "IceSSL.Protocols", "tls1");
optionallySetProperty(id, "IceSSL.Ciphers", "NONE (DH_anon.*AES)");
optionallySetProperty(id, "IceSSL.VerifyPeer", "0");
optionallySetProperty(id, "omero.block_size", Integer
.toString(omero.constants.DEFAULTBLOCKSIZE.value));
// Set the default encoding if this is Ice 3.5 or later
// and none is set.
if (Ice.Util.intVersion() >= 30500) {
optionallySetProperty(id, "Ice.Default.EncodingVersion", "1.0");
}
// Setting MessageSizeMax
optionallySetProperty(id, "Ice.MessageSizeMax", Integer
.toString(omero.constants.MESSAGESIZEMAX.value));
// Setting ConnectTimeout
parseAndSetInt(id, "Ice.Override.ConnectTimeout",
omero.constants.CONNECTTIMEOUT.value);
// Set large thread pool max values for all communicators
for (String x : Arrays.asList("Client", "Server")) {
String sizemax = id.properties.getProperty(String.format("Ice.ThreadPool.%s.SizeMax", x));
if (sizemax == null || sizemax.length() == 0) {
id.properties.setProperty(String.format("Ice.ThreadPool.%s.SizeMax", x), "50");
}
}
// Port, setting to default if not present
String port = parseAndSetInt(id, "omero.port",
omero.constants.GLACIER2PORT.value);
// Default Router, set a default and then replace
String router = id.properties.getProperty("Ice.Default.Router");
if (router == null || router.length() == 0) {
router = omero.constants.DEFAULTROUTER.value;
}
String host = id.properties.getPropertyWithDefault("omero.host",
"<\"omero.host\" not set>");
router = router.replaceAll("@omero.port@", port);
router = router.replaceAll("@omero.host@", host);
id.properties.setProperty("Ice.Default.Router", router);
// Dump properties
String dump = id.properties.getProperty("omero.dump");
if (dump != null && dump.length() > 0) {
Map<String, String> propertyMap = getPropertyMap(id.properties);
for (String key : propertyMap.keySet()) {
System.out.println(String.format("%s=%s", key,
propertyMap.get(key)));
}
}
if (__ic != null) {
throw new ClientError("Client already initialized.");
}
try {
__ic = Ice.Util.initialize(id);
} catch (Ice.EndpointParseException epe) {
throw new ClientError("No host specified. " +
"Use omero.client(HOSTNAME), ICE_CONFIG, or similar.");
}
if (__ic == null) {
throw new ClientError("Improper initialization");
}
// Register Object Factories
new ModelObjectFactoryRegistry().setIceCommunicator(__ic, this);
new rtypes.RTypeObjectFactoryRegistry().setIceCommunicator(__ic);
// Define our unique identifer (used during close/detach)
__uuid = UUID.randomUUID().toString();
Ice.ImplicitContext ctx = __ic.getImplicitContext();
if (ctx == null) {
throw new ClientError("Ice.ImplicitContext not set to Shared");
}
ctx.put(omero.constants.CLIENTUUID.value, __uuid);
// ticket:2951 - sending user group
String group = id.properties.getPropertyWithDefault("omero.group", "");
if (group.length() > 0) {
ctx.put("omero.group", group);
}
// Store this instance for cleanup on shutdown.
CLIENTS.add(this);
}
/**
* Sets the {@code fastShutdown} flag. By setting this
* to true, you will prevent proper clean up. This should
* only be used in the case of network loss (or similar).
* @param fastShutdown if shutdown should be fast
* @return the previous value of this property
*/
public boolean setFastShutdown(boolean fastShutdown) {
return this.fastShutdown.getAndSet(fastShutdown);
}
/**
* Sets the {@link omero.model.Session#getUserAgent() user agent} string for
* this client. Every session creation will be passed this argument. Finding
* open sessions with the same agent can be done via
* {@link omero.api.ISessionPrx#getMyOpenAgentSessions(String)}.
* @param agent the user agent for sessions
*/
public void setAgent(String agent) {
__agent = agent;
}
/**
* Sets the {@link omero.model.Session#getUserIP() user ip} string for
* this client. Every session creation will be passed this argument. Finding
* open sessions with the same agent can be done via
* {@link omero.api.ISessionPrx#getMyOpenAgentSessions(String)}.
* @param ip the user IP address
*/
public void setIP(String ip) {
__ip = ip;
}
/**
* Specifies whether or not this client was created via a call to
* {@link #createClient(boolean)} with a boolean of false. If insecure, then
* all remote calls will use the insecure connection defined by the server.
* @return if the client's remote calls are secure
*/
public boolean isSecure() {
return !insecure;
}
/**
* Creates a possibly insecure {@link omero.client} instance and calls
* {@link #joinSession(String)} using the current {@link #getSessionId()
* session id}. If secure is false, then first the "omero.router.insecure"
* configuration property is retrieved from the server and used as the value
* of "Ice.Default.Router" for the new client. Any exception thrown during
* creation is passed on to the caller.
*
* Note: detachOnDestroy has NOT been called on the session in the returned client.
* Clients are responsible for doing this immediately if such desired.
*/
public omero.client createClient(boolean secure) throws ServerError,
CannotCreateSessionException, PermissionDeniedException {
Map<String, String> props = getPropertyMap();
if (!secure) {
String insecure = getSession().getConfigService().getConfigValue(
"omero.router.insecure");
if (insecure != null && insecure.length() != 0) {
props.put("Ice.Default.Router", insecure);
} else {
getCommunicator().getLogger().warning("Could not retrieve \"omero.router.insecure\"");
}
}
omero.client nClient = new omero.client(props, secure);
nClient.setAgent(__agent + ";secure=" + secure);
nClient.joinSession(getSessionId());
return nClient;
}
// Destruction
// =========================================================================
/**
* Calls closeSession() and ignores any exceptions.
*
* Equivalent to OmeroPy's __del__ or OmeroCpp's omero::client::~client()
*/
public void __del__() {
try {
closeSession();
} catch (Exception e) {
System.out.println("Ignoring error in client.__del__()");
e.printStackTrace();
}
}
/**
* Returns the {@link Ice.Communicator} for this instance or throws an
* exception if null.
* @return this client's {@link Ice.Communicator}
*/
public Ice.Communicator getCommunicator() {
Ice.Communicator ic = __ic;
if (ic == null) {
throw new ClientError(
"No Ice.Communicator active; call createSession() or create a new client instance.");
}
return ic;
}
public Ice.ObjectAdapter getAdapter() {
Ice.ObjectAdapter oa = __oa;
if (oa == null) {
throw new ClientError("No ObjectAdapter; call createSession()");
}
return oa;
}
/**
* Returns the current active session or throws an exception if none has
* been {@link #createSession(String, String) created} since the last
* {@link #closeSession()}
* @return the current active session
*/
public ServiceFactoryPrx getSession() {
ServiceFactoryPrx sf = __sf;
if (__sf == null) {
throw new ClientError("Call createSession() to login.");
}
return sf;
}
/**
* Returns the UUID for the current session without making a remote call.
* Uses {@link #getSession()} internally and will throw an exception if
* no session is active.
* @return the current session's UUID
*/
public String getSessionId() {
return getSession().ice_getIdentity().name;
}
/**
* Returns the category which should be used for all callbacks
* passed to the server.
* @return the category for callbacks
*/
public String getCategory() {
return getRouter(getCommunicator()).getCategoryForClient();
}
/**
* @see #getSession()
* @return the current active session
* @deprecated use {@link #getSession()} instead, to be removed in 5.3
*/
@Deprecated
public ServiceFactoryPrx getServiceFactory() {
return getSession();
}
/**
* Returns the {@link Ice.ImplicitContext} which defines what properties
* will be sent on every method invocation.
* @return the {@link Ice.ImplicitContext}
*/
public Ice.ImplicitContext getImplicitContext() {
return getCommunicator().getImplicitContext();
}
/**
* Returns the {@link Ice.Properties active properties} for this instance.
* @return the active properties
*/
public Ice.Properties getProperties() {
return getCommunicator().getProperties();
}
/**
* Returns the property value for this key or the empty string if none.
* @param key a property key
* @return the key's value or the empty string if none
*/
public String getProperty(String key) {
return getProperties().getProperty(key);
}
/**
* Returns all properties which are prefixed with "omero." or "Ice."
* using the Properties from {@link #getProperties()}.
* @return the "omero." and "Ice." properties
*/
public Map<String, String> getPropertyMap() {
return getPropertyMap(getProperties());
}
/**
* Returns all properties which are prefixed with "omero." or "Ice."
* @param properties some Ice properties
* @return the "omero." and "Ice." properties
*/
public Map<String, String> getPropertyMap(Ice.Properties properties) {
Map<String, String> rv = new HashMap<String, String>();
for (String prefix : Arrays.asList("omero", "Ice")) {
Map<String, String> prefixed = properties
.getPropertiesForPrefix(prefix);
rv.putAll(prefixed);
}
return rv;
}
/**
* Returns the user-configured setting for "omero.block_size"
* or {@link omero.constants.DEFAULTBLOCKSIZE} if none is set.
* @return the block size (in bytes)
*/
public int getDefaultBlockSize() {
try {
return Integer.valueOf(getProperty("omero.block_size"));
} catch (Exception e) {
return omero.constants.DEFAULTBLOCKSIZE.value;
}
}
// Session management
// =========================================================================
/**
* Uses the given session uuid as name and password to rejoin a running
* session.
*/
public ServiceFactoryPrx joinSession(String session)
throws CannotCreateSessionException, PermissionDeniedException,
ServerError {
return createSession(session, session);
}
/**
* Calls {@link #createSession(String, String)} with null values
*/
public ServiceFactoryPrx createSession()
throws CannotCreateSessionException, PermissionDeniedException,
ServerError {
return createSession(null, null);
}
/**
* Performs the actual logic of logging in, which is done via the
* {@link #getRouter(Ice.Communicator)}. Disallows an extant {@link ServiceFactoryPrx}
* and tries to re-create a null {@link Ice.Communicator}. A null or empty
* username will throw an exception, but an empty password is allowed.
*/
public ServiceFactoryPrx createSession(String username, String password)
throws CannotCreateSessionException, PermissionDeniedException,
ServerError {
// Checking state
if (__sf != null) {
throw new ClientError(
"Session already active. Create a new omero.client or closeSession()");
}
if (__ic == null) {
if (__previous == null) {
throw new ClientError(
"No previous data to recreate communicator.");
}
init(__previous);
__previous = null;
}
// Check the required properties
if (username == null) {
username = getProperty("omero.user");
if (username == null || "".equals(username)) {
throw new ClientError("No username specified");
}
}
if (password == null) {
password = getProperty("omero.pass");
if (password == null) {
throw new ClientError("No password specified");
}
}
// Acquire router and get the proxy
Glacier2.SessionPrx prx = null;
int retries = 0;
while (retries < 3) {
String reason = null;
if (retries > 0) {
__ic.getLogger().warning(
reason + " - createSession retry: " + retries);
}
try {
Map<String, String> ctx = new HashMap<String, String>(getImplicitContext().getContext());
ctx.put(AGENT.value, __agent);
ctx.put(IP.value, __ip);
Glacier2.RouterPrx rtr = getRouter(__ic);
prx = rtr.createSession(username, password, ctx);
// Create the adapter.
__oa = __ic.createObjectAdapterWithRouter("omero.ClientCallback", rtr);
__oa.activate();
Ice.Identity id = new Ice.Identity();
id.name = __uuid;
id.category = rtr.getCategoryForClient();
__cb = new CallbackI(id, this.__ic, this.__oa);
__oa.add(__cb, id);
break;
} catch (omero.WrappedCreateSessionException wrapped) {
if (!wrapped.concurrency) {
throw wrapped; // We only retry concurrency issues.
}
reason = wrapped.type + ":" + wrapped.reason;
retries++;
} catch (Ice.ConnectTimeoutException cte) {
reason = "Ice.ConnectTimeoutException:" + cte.getMessage();
retries++;
}
}
if (null == prx) {
throw new ClientError("Obtained null object proxy");
}
// Check type
__sf = ServiceFactoryPrxHelper.uncheckedCast(prx);
if (__sf == null) {
throw new ClientError(
"Obtained object proxy is not a ServiceFactory");
}
// Configure keep alive
String keep_alive = __ic.getProperties().getPropertyWithDefault(
"omero.keep_alive", "-1");
try {
int i = Integer.valueOf(keep_alive);
enableKeepAlive(i);
} catch (NumberFormatException nfe) {
// ignore
}
// Set the client callback on the session
// and pass it to icestorm
try {
Ice.ObjectPrx raw = __oa.createProxy(__cb.id);
__sf.setCallback(ClientCallbackPrxHelper.uncheckedCast(raw));
} catch (RuntimeException e) {
__del__();
throw e;
} catch (Exception e) {
__del__();
throw new RuntimeException(e);
}
// Set the session uuid in the implicit context
getImplicitContext().put(omero.constants.SESSIONUUID.value, getSessionId());
return this.__sf;
}
/**
* Acquires the {@link Ice.Communicator#getDefaultRouter default router},
* and throws an exception if it is not of type {Glacier2.RouterPrx}. Also
* sets the {@link Ice.ImplicitContext} on the router proxy.
* @param comm the Ice communicator
* @return the communicator's router
*/
public static Glacier2.RouterPrx getRouter(Ice.Communicator comm) {
Ice.RouterPrx prx = comm.getDefaultRouter();
if (prx == null) {
throw new ClientError("No default router found.");
}
Glacier2.RouterPrx router = Glacier2.RouterPrxHelper.checkedCast(prx);
if (router == null) {
throw new ClientError("Error obtaining Glacier2 router");
}
// For whatever reason, we have to set the context
// on the router context here as well
router = Glacier2.RouterPrxHelper.uncheckedCast(router.ice_context(comm
.getImplicitContext().getContext()));
return router;
}
/**
* Resets the "omero.keep_alive" property on the current
* {@link Ice.Communicator} which is used on initialization to determine the
* time-period between {@link omero.util.Resources.Entry#check() checks}.
*/
public void enableKeepAlive(int seconds) {
// A communicator must be configured!
Ice.Communicator ic = getCommunicator();
// Setting this here guarantees that after closeSession(), the
// next createSession() will use the new value despite what was
// in the configuration file.
ic.getProperties().setProperty("omero.keep_alive", "" + seconds);
// If it's not null, then there's already an entry for keeping
// any existing session alive.
if (__resources == null && seconds > 0) {
__resources = new Resources(seconds);
__resources.add(new Entry() {
// Return true unless prx.keepAlive() throws an exception.
public boolean check() {
ServiceFactoryPrx prx = __sf;
Ice.Communicator ic = __ic;
if (prx != null) {
try {
prx.keepAlive(null);
} catch (Exception e) {
if (ic != null) {
ic.getLogger().warning(
"Proxy keep alive failed.");
}
}
}
return true;
}
public void cleanup() {
// Nothing to do.
}
});
}
}
/**
* Returns all active {@link StatefulServiceInterface} proxies. This can
* be used to call close before calling setSecurityContext.
*/
public List<StatefulServiceInterfacePrx> getStatefulServices() throws ServerError {
List<StatefulServiceInterfacePrx> rv = new ArrayList<StatefulServiceInterfacePrx>();
ServiceFactoryPrx sf = getSession();
List<String> services = sf.activeServices();
for (String srv : services) {
try {
ServiceInterfacePrx prx = sf.getByName(srv);
StatefulServiceInterfacePrx sPrx =
omero.api.StatefulServiceInterfacePrxHelper.checkedCast(prx);
if (sPrx != null) {
rv.add(sPrx);
}
} catch (Exception e) {
getCommunicator().getLogger().warning(
"Error looking up proxy: " + srv);
}
}
return rv;
}
/**
* Closes the session and nulls out the communicator. This is required by an
* Ice bug.
*
* @see <a
* href="http://www.zeroc.com/forums/help-center/2370-ice_ping-error-right-after-createsession-succeed.html">2370
* Ice Ping Error</a>
*/
public void closeSession() {
ServiceFactoryPrx oldSf = this.__sf;
this.__sf = null;
Ice.ObjectAdapter oldOa = this.__oa;
this.__oa = null;
Ice.Communicator oldIc = this.__ic;
this.__ic = null;
// Only possible if improperly configured
if (oldIc == null) {
return; // EARLY EXIT !
}
if (oldOa != null) {
try {
oldOa.deactivate();
} catch (Exception e) {
oldIc.getLogger().warning(
"While deactivating adapter: " + e.getMessage());
}
}
__previous = new Ice.InitializationData();
__previous.properties = oldIc.getProperties()._clone();
__previous.logger = oldIc.getLogger();
// ThreadHook is not support since not available from ic
// Shutdown keep alive
Resources oldR = __resources;
__resources = null;
if (oldR != null) {
try {
oldR.cleanup();
} catch (Exception e) {
oldIc.getLogger().warning(
"While cleaning up resources: " + e.getMessage());
}
}
final boolean fast = this.fastShutdown.get();
try {
if (oldSf != null && !fast) {
oldSf = ServiceFactoryPrxHelper.uncheckedCast(oldSf.ice_oneway());
if (oldIc != null) {
getRouter(oldIc).destroySession();
}
}
} catch (Ice.ConnectionLostException cle) {
// ok. Exception will always be thrown
} catch (Ice.ConnectionRefusedException cle) {
// ok. Server probably went down
} catch (Ice.ConnectTimeoutException cte) {
// ok. Server probably went down
} catch (Ice.DNSException dns) {
// ok. client is having network issues
} catch (Ice.SocketException se) {
// ok. client is having network issues
} catch (SessionNotExistException e) {
// ok. we don't want the session to exist
} finally {
try {
if (oldIc != null && !fast) {
oldIc.destroy();
}
} finally {
CLIENTS.remove(this);
}
}
}
/**
* Calls ISession.closeSession(omero.model.Session) until
* the returned reference count is greater than zero. The
* number of invocations is returned. If ISession.closeSession()
* cannot be called, -1 is returned.
*/
public int killSession() {
ServiceFactoryPrx sf = getSession();
Ice.Logger __logger = getCommunicator().getLogger();
omero.model.Session s = new omero.model.SessionI();
s.setUuid(omero.rtypes.rstring(getSessionId()));
omero.api.ISessionPrx prx = null;
try {
prx = sf.getSessionService();
} catch (Exception e) {
__logger.warning("Cannot get session service for killSession. Using closeSession: " + e);
closeSession();
return -1;
}
int count = 0;
try {
int r = 1;
while (r > 0) {
count++;
r = prx.closeSession(s);
}
} catch (omero.RemovedSessionException rse) {
// ignore
} catch (Exception e) {
__logger.warning("Unknown exception while closing all references:" + e);
}
// Now the server-side session is dead, call closeSession()
closeSession();
return count;
}
// File handling
// =========================================================================
/**
* Calculates the local sha1 for a file.
* @param file a local file
* @return the file's SHA-1 hexadecimal digest
*/
public String sha1(File file) {
ChecksumProviderFactory cpf = new ChecksumProviderFactoryImpl();
return cpf.getProvider(ChecksumType.SHA1).putFile(
file.getAbsolutePath()).checksumAsString();
}
public OriginalFile upload(File file) throws ServerError, IOException {
return upload(file, null);
}
public OriginalFile upload(File file, OriginalFile fileObject) throws ServerError, IOException {
return upload(file, fileObject, 262144);
}
/**
* Utility method to upload a file to the server
*
* @param file
* Cannot be null.
* @param fileObject
* Can be null.
* @param blockSize
* Can be null.
*/
public OriginalFile upload(File file, OriginalFile fileObject, Integer blockSize) throws ServerError, IOException {
ServiceFactoryPrx sf = getSession();
if (file == null) {
throw new ClientError("Non-null file must be provided");
}
if (!file.exists() || ! file.canRead()) {
throw new ClientError("File does not exist or is not readable: " + file.getAbsolutePath());
}
if (blockSize == null) {
blockSize = 262144;
}
long size = file.length();
if (blockSize > size) {
blockSize = (int) size;
}
if (fileObject == null) {
fileObject = new OriginalFileI();
}
fileObject.setSize(rlong(size));
final ChecksumAlgorithm hasher = new ChecksumAlgorithmI();
hasher.setValue(rstring(ChecksumAlgorithmSHA1160.value));
fileObject.setHasher(hasher);
fileObject.setHash(rstring(sha1(file)));
if (fileObject.getName() == null) {
fileObject.setName(rstring(file.getName()));
}
if (fileObject.getPath() == null) {
String path = file.getParent() == null ?
File.separator : (file.getParent() + File.separator);
fileObject.setPath(rstring(path));
}
if (fileObject.getMimetype() == null) {
String mimeType = null;
try {
mimeType = Files.probeContentType(file.toPath());
} catch (IOException | SecurityException e) {
/* can't guess */
}
if (mimeType == null) {
mimeType = "application/octet-stream";
}
fileObject.setMimetype(rstring(mimeType));
}
IUpdatePrx up = sf.getUpdateService();
fileObject = (OriginalFile) up.saveAndReturnObject(fileObject);
byte[] buf = new byte[blockSize];
RawFileStorePrx rfs = sf.createRawFileStore();
FileInputStream stream = null;
try {
rfs.setFileId(fileObject.getId().getValue());
stream = new FileInputStream(file);
long pos = 0;
int rlen;
ByteBuffer bbuf;
while ((rlen = stream.read(buf)) > 0) {
rfs.write(buf, pos, rlen);
pos += rlen;
bbuf = ByteBuffer.wrap(buf);
bbuf.limit(rlen);
}
return rfs.save();
} finally {
Utils.closeQuietly(stream);
if (rfs != null) {
rfs.close();
}
}
}
public void download(long fileId, File file) throws ServerError, IOException {
download(fileId, file, 262144);
}
public void download(long fileId, File file, int blockSize) throws ServerError, IOException {
final ServiceFactoryPrx sf = getSession();
final OriginalFile obj = (OriginalFile) sf.getQueryService().get("OriginalFile", fileId);
final RawFileStorePrx store = sf.createRawFileStore();
final FileOutputStream stream = new FileOutputStream(file);
final long size = obj.getSize().getValue();
long offset = 0;
store.setFileId(fileId);
try {
for (offset = 0; (offset+blockSize) < size;) {
stream.write(store.read(offset, blockSize));
offset += blockSize;
}
stream.write(store.read(offset, (int) (size-offset)));
} finally {
Utils.closeQuietly(stream);
store.close();
}
}
// Environment methods
// =========================================================================
/**
* Retrieves an item from the "input" shared (session) memory.
*/
public RType getInput(String key) throws ServerError {
return env().getInput(getSessionId(), key);
}
/**
* Retrieves an item from the "output" shared (session) memory.
*/
public RType getOutput(String key) throws ServerError {
return env().getOutput(getSessionId(), key);
}
/**
* Sets an item in the "input" shared (session) memory under the given name.
*/
public void setInput(String key, RType value) throws ServerError {
env().setInput(getSessionId(), key, value);
}
/**
* Sets an item in the "output" shared (session) memory under the given
* name.
*/
public void setOutput(String key, RType value) throws ServerError {
env().setOutput(getSessionId(), key, value);
}
/**
* Returns a list of keys for all items in the "input" shared (session)
* memory
*/
public List<String> getInputKeys() throws ServerError {
return env().getInputKeys(getSessionId());
}
/**
* Returns a list of keys for all items in the "output" shared (session)
* memory
*/
public List<String> getOutputKeys() throws ServerError {
return env().getOutputKeys(getSessionId());
}
// Helpers
// =========================================================================
protected String parseAndSetInt(Ice.InitializationData data, String key,
int newValue) {
String currentValue = data.properties.getProperty(key);
if (currentValue == null || currentValue.length() == 0) {
String newStr = Integer.toString(newValue);
data.properties.setProperty(key, newStr);
currentValue = newStr;
}
return currentValue;
}
protected static String filesToString(File... files) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < files.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(files[i].getAbsolutePath());
}
return sb.toString();
}
/**
* Helper method to access session environment
*/
protected ISessionPrx env() throws ServerError {
ISessionPrx s = getSession().getSessionService();
return s;
}
// Callback
// =========================================================================
public void onHeartbeat(Runnable runnable) {
__cb.onHeartbeat = runnable;
}
public void onSessionClosed(Runnable runnable) {
__cb.onSessionClosed = runnable;
}
public void onShutdown(Runnable runnable) {
__cb.onShutdown = runnable;
}
/**
* Implementation of {@link ClientCallback} which will be added to any
* {@link ServiceFactoryPrx} which this instance creates. Note: this client
* should avoid all interaction with the {@link client#lock} since it can
* lead to deadlocks during shutdown. See: ticket:1210
*/
private static class CallbackI extends _ClientCallbackDisp {
private final Ice.Identity id;
private final Ice.Communicator ic;
private final Ice.ObjectAdapter oa;
private Runnable _noop = new Runnable() {
public void run() {
// ok
}
};
private Runnable _closeSession = new Runnable() {
public void run() {
try {
oa.deactivate();
} catch (Exception e) {
System.err.println("On session closed: " + e.getMessage());
}
}
};
private Runnable onHeartbeat = _noop;
private Runnable onSessionClosed = _noop;
private Runnable onShutdown = _noop;
public CallbackI(Ice.Identity id, Ice.Communicator ic, Ice.ObjectAdapter oa) {
this.id = id;
this.ic = ic;
this.oa = oa;
}
public void requestHeartbeat(Current __current) {
execute(onHeartbeat, "heartbeat");
}
public void shutdownIn(long milliseconds, Current __current) {
execute(onShutdown, "shutdown");
}
public void sessionClosed(Current __current) {
execute(onSessionClosed, "sessionClosed");
}
protected void execute(Runnable runnable, String action) {
try {
runnable.run();
// ic.getLogger().trace("ClientCallback", action + " run");
} catch (Exception e) {
try {
ic.getLogger().error(
"Error performing " + action + ": "
+ e.getMessage());
} catch (Exception e2) {
// This could be a null pointer exception or any number
// of things. But it's important for us to know that a
// heartbeat could not be performed, for example.
System.err.println("Error performing " + action + " :"
+ e.getMessage());
System.err.println("(Stderr due to: " + e2.getMessage()
+ ")");
}
}
}
}
}