org.kawanfw.file.api.client.RemoteSession Maven / Gradle / Ivy
Show all versions of awake-file-server Show documentation
/*
* This file is part of Awake FILE.
* Awake file: Easy file upload & download over HTTP with Java.
* Copyright (C) 2015, KawanSoft SAS
* (http://www.kawansoft.com). All rights reserved.
*
* Awake FILE is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Awake FILE 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*
* Any modifications to this file must keep this entire header
* intact.
*/
package org.kawanfw.file.api.client;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.SocketException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.List;
import java.util.Vector;
import java.util.logging.Level;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.kawanfw.commons.api.client.InvalidLoginException;
import org.kawanfw.commons.api.client.RemoteException;
import org.kawanfw.commons.api.client.SessionParameters;
import org.kawanfw.commons.client.http.HttpTransfer;
import org.kawanfw.commons.client.http.HttpTransferUtil;
import org.kawanfw.commons.client.http.SimpleNameValuePair;
import org.kawanfw.commons.json.ListOfStringTransport;
import org.kawanfw.commons.util.ClientLogger;
import org.kawanfw.commons.util.FrameworkDebug;
import org.kawanfw.commons.util.HtmlConverter;
import org.kawanfw.commons.util.StringUtil;
import org.kawanfw.commons.util.Tag;
import org.kawanfw.file.util.parms.Action;
import org.kawanfw.file.util.parms.Parameter;
import org.kawanfw.file.util.parms.ReturnCode;
import org.kawanfw.file.version.FileVersion;
/**
* Main class for establishing an http session with a remote host.
*
* Also allows the execution of some basic operations:
*
* - Get the Java version of the servlet container on the remote server.
* - Call remote Java methods.
* - Upload files by wrapping bytes copy from a {@code FileInputStream} to a
* {@link RemoteOutputStream}.
* - Download files by wrapping bytes copy from a {@link RemoteInputStream} to
* a {@code FileOutputStream}.
* - Returns with one call the length of a list of files located on the remote
* host.
*
*
* Note that main operations on remote files are done using {@link RemoteFile}
* class whose method names, signatures and roles are equivalent to those of
* {@code File} class.
*
* Example:
*
*
* // Define URL of the path to the {@code ServerFileManager} servlet
* String url = "https://www.acme.org/ServerFileManager";
*
* // The login info for strong authentication on server side:
* String username = "myUsername";
* char[] password = { 'm', 'y', 'P', 'a', 's', 's', 'w', 'o', 'r', 'd' };
*
* // Establish a session with the remote server
* RemoteSession remoteSession = new RemoteSession(url, username, password);
*
* // OK: upload a file
* remoteSession.upload(new File("c:\\myFile.txt"), "/home/mylogin/myFile.txt");
*
*
*
*
* Communication via a proxy server is done using
* {@code java.net.Proxy} and {@code java.net.PasswordAuthentication} for
* authentication.
*
*
*
*
String url = "http://www.acme.org/ServerFileManager";
String username = "myUsername";
char[] password = "myPassword".toCharArray();
Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(
"proxyHostname", 8080));
PasswordAuthentication passwordAuthentication = null;
// If proxy requires authentication:
passwordAuthentication = new PasswordAuthentication("proxyUsername", "proxyPassword".toCharArray());
RemoteSession remoteSession = new RemoteSession(url, username,
password, proxy, passwordAuthentication);
// Etc.
*
*
*
*
*
* NTLM authentication is done using {@code PasswordAuthentication}:
*
*
*
*
String url = "http://www.acme.org/ServerFileManager";
String username = "myUsername";
char[] password = "myPassword".toCharArray();
Proxy proxy = Proxy.NO_PROXY;
// DOMAIN is passed along username:
PasswordAuthentication passwordAuthentication = new PasswordAuthentication("DOMAIN\\username", "password".toCharArray());
RemoteSession remoteSession = new RemoteSession(url, username,
password, proxy, passwordAuthentication);
// Etc.
*
*
*
*
* @see org.kawanfw.file.api.client.RemoteFile
* @see org.kawanfw.file.api.client.RemoteInputStream
* @see org.kawanfw.file.api.client.RemoteOutputStream
*
* @author Nicolas de Pomereu
* @since 1.0
*/
public class RemoteSession implements Cloneable {
static final String REMOTE_SESSION_IS_CLOSED = "RemoteSession is closed.";
/** For debug info */
private static boolean DEBUG = FrameworkDebug.isSet(RemoteSession.class);
/** Defines 1 kilobyte */
public static final int KB = 1024;
/** Defines 1 megabyte */
public static final int MB = 1024 * KB;
/** Defines if we must use base64 or html encode when using call() */
private static boolean USE_HTML_ENCODING = true;
/** The url to use to connectet to the Awake FILE Server */
private String url = null;
/**
* The username is stored in static memory to be passed to upload file
* servlet
*/
private String username = null;
/**
* Token is stored in static to be available during all session and contains
* SHA-1(userId + ServerClientLogin.SECRET_FOR_LOGIN) computed by server.
* Token is re-send and checked at each send or recv command to be sure user
* is authenticated.
*/
private String authenticationToken = null;
/** Proxy to use with HttpUrlConnection */
private Proxy proxy = null;
/** For authenticated proxy */
private PasswordAuthentication passwordAuthentication = null;
/** The Http Parameters instance */
private SessionParameters sessionParameters = null;
/** The http transfer instance */
private HttpTransfer httpTransfer = null;
/** The remote Java version */
private String remoteJavaVersion = null;
/**
* Says if we want to use base64 encoding for parameters passed to call() -
* This is a method for legacy applications prior to v1.0.
*/
public static void setUseBase64EncodingForCall() {
USE_HTML_ENCODING = false;
}
/**
* Private constructor for clone().
*
* @param url
* the URL of the path to the {@code ServerFileManager} Servlet
* @param username
* the username for authentication on the Awake Server (may be
* null for call() or downloadUrl())
* @param authenticationToken
* the actual token of the Awake FILE session to clone
* @param proxy
* the proxy to use, may be null for direct access
* @param passwordAuthentication
* the proxy credentials, null if no proxy or if the proxy does
* not require authentication
* @param sessionParameters
* the http parameters to use
* @param remoteJavaVersion
* the Java version on remote server
*/
private RemoteSession(String url, String username,
String authenticationToken, Proxy proxy,
PasswordAuthentication passwordAuthentication,
SessionParameters sessionParameters, String remoteJavaVersion) {
this.url = url;
this.username = username;
this.authenticationToken = authenticationToken;
this.proxy = proxy;
this.passwordAuthentication = passwordAuthentication;
this.sessionParameters = sessionParameters;
this.remoteJavaVersion = remoteJavaVersion;
httpTransfer = HttpTransferUtil.HttpTransferFactory(url, proxy,
passwordAuthentication, sessionParameters);
}
/**
* Creates an Awake FILE session with a proxy and protocol parameters.
*
* @param url
* the URL of the path to the {@code ServerFileManager} Servlet
* @param username
* the username for authentication on the Awake Server (may be
* null for call()
* @param password
* the user password for authentication on the Awake Server (may
* be null)
* @param proxy
* the proxy to use, may be null for direct access
* @param passwordAuthentication
* the proxy credentials, null if no proxy or if the proxy does
* not require authentication
* @param sessionParameters
* the session parameters to use (may be null)
*
* @throws MalformedURLException
* if the url is malformed
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws InvalidLoginException
* the username or password is invalid
* @throws SecurityException
* Scheme is required to be https (SSL/TLS)
* @throws RemoteException
* an exception has been thrown on the server side. This traps
* an Awake product failure and should not happen.
* @throws IOException
* for all other IO / Network / System Error
*/
public RemoteSession(String url, String username, char[] password,
Proxy proxy, PasswordAuthentication passwordAuthentication,
SessionParameters sessionParameters) throws MalformedURLException,
UnknownHostException, ConnectException, SocketException,
InvalidLoginException, RemoteException, SecurityException,
IOException {
if (url == null) {
throw new MalformedURLException("url is null!");
}
@SuppressWarnings("unused")
URL asUrl = new URL(url); // Try to raise a MalformedURLException;
this.username = username;
this.url = url;
this.proxy = proxy;
this.passwordAuthentication = passwordAuthentication;
this.sessionParameters = sessionParameters;
// username & password may be null: for call()
if (username == null) {
return;
}
// Launch the Servlet
httpTransfer = HttpTransferUtil.HttpTransferFactory(url, proxy,
passwordAuthentication, sessionParameters);
// TestReload if SSL required by host
if (this.url.toLowerCase().startsWith("http://") && isForceHttps()) {
throw new SecurityException(
Tag.PRODUCT_SECURITY
+ " Remote Host requires a SSL url that starts with \"https\" scheme");
}
String passwordStr = new String(password);
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.TEST_CRYPTO,
Parameter.TEST_CRYPTO));
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.LOGIN_ACTION));
requestParams
.add(new SimpleNameValuePair(Parameter.USERNAME, username));
requestParams.add(new SimpleNameValuePair(Parameter.PASSWORD,
passwordStr));
httpTransfer.send(requestParams);
// If everything is OK, we have in our protocol a response that
// 1) starts with "OK". 2) Is followed by the Authentication Token
// else: response starts with "INVALID_LOGIN_OR_PASSWORD".
String receive = httpTransfer.recv();
debug("receive: " + receive);
if (receive.startsWith(ReturnCode.INVALID_LOGIN_OR_PASSWORD)) {
throw new InvalidLoginException("Invalid username or password.");
} else if (receive.startsWith(ReturnCode.OK)) {
// OK! We are logged in & and correctly authenticated
// Keep in static memory the Authentication Token for next api
// commands (First 20 chars)
String theToken = receive.substring(ReturnCode.OK.length() + 1);
authenticationToken = StringUtils.left(theToken,
Parameter.TOKEN_LEFT_SIZE);
} else {
this.username = null;
// Should never happen
throw new InvalidLoginException(Tag.PRODUCT_PRODUCT_FAIL
+ " Please contact support.");
}
}
/**
* Creates an Awake FILE session with a proxy.
*
* @param url
* the URL of the path to the {@code ServerFileManager} Servlet
* @param username
* the username for authentication on the Awake Server (may be
* null for call()
* @param password
* the user password for authentication on the Awake Server (may
* be null)
* @param proxy
* the proxy to use, may be null for direct access
* @param passwordAuthentication
* the proxy credentials, null if no proxy or if the proxy does
* not require authentication
*
* @throws MalformedURLException
* if the url is malformed
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws InvalidLoginException
* the username or password is invalid
* @throws SecurityException
* scheme is required to be https (SSL/TLS)
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*/
public RemoteSession(String url, String username, char[] password,
Proxy proxy, PasswordAuthentication passwordAuthentication)
throws MalformedURLException, UnknownHostException,
ConnectException, SocketException, InvalidLoginException,
RemoteException, SecurityException, IOException {
this(url, username, password, proxy, passwordAuthentication, null);
}
/**
* Creates an Awake FILE session.
*
* @param url
* the URL of the path to the {@code ServerFileManager} Servlet
* @param username
* the username for authentication on the Awake Server (may be
* null for call()
* @param password
* the user password for authentication on the Awake Server (may
* be null)
*
* @throws MalformedURLException
* if the url is malformed
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws InvalidLoginException
* the username or password is invalid
* @throws SecurityException
* scheme is required to be https (SSL/TLS)
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*/
public RemoteSession(String url, String username, char[] password)
throws MalformedURLException, UnknownHostException,
ConnectException, SocketException, InvalidLoginException,
RemoteException, IOException, SecurityException {
this(url, username, password, null, null);
}
/**
* Returns the username of this Awake FILE session
*
* @return the username of this Awake FILE session
*/
public String getUsername() {
return this.username;
}
/**
* Returns the {@code SessionParameters} instance in use for the Awake FILE
* session.
*
* @return the {@code SessionParameters} instance in use for the Awake FILE
* session
*/
public SessionParameters getSessionParameters() {
return this.sessionParameters;
}
/**
* Returns the URL of the path to the ServerFileManager
Servlet
* (or ServerSqlManager
Servlet if session has been initiated
* by a RemoteConnection
).
*
* @return the URL of the path to the ServerFileManager
Servlet
*/
public String getUrl() {
return url;
}
/**
* Returns the {@code Proxy} instance in use for this File Session.
*
* @return the {@code Proxy} instance in use for this File Session
*/
public Proxy getProxy() {
return this.proxy;
}
/**
* Returns the proxy credentials
*
* @return the proxy credentials
*/
public PasswordAuthentication getPasswordAuthentication() {
return passwordAuthentication;
}
/**
* Returns the http status code of the last executed verb
*
* @return the http status code of the last executed verb
*/
public int getHttpStatusCode() {
if (httpTransfer != null) {
return httpTransfer.getHttpStatusCode();
} else {
return 0;
}
}
/**
* Returns the Authentication Token. This method is used by the Kawansoft
* frameworks.
*
* @return the Authentication Token
*/
public String getAuthenticationToken() {
return this.authenticationToken;
}
/**
* Calls a remote Java method and (eventually) pass some parameters to it.
*
* @param methodName
* the full method name to call in the format
* org.acme.config.package.MyClass.myMethod
* @param params
* the array of parameters passed to the method
*
* @return the result of the Java call as {@code String}
*
* @throws IllegalArgumentException
* if methodName is null
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
*
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*
*/
public String call(String methodName, Object... params)
throws IllegalArgumentException, InvalidLoginException,
UnknownHostException, ConnectException, SocketException,
RemoteException, IOException {
// For legacy methods
if (!USE_HTML_ENCODING) {
return callBase64Encoded(methodName, params);
}
// Class and method name can not be null
if (methodName == null) {
throw new IllegalArgumentException("methodName can not be null!");
}
// username & Authentication Token may be null
// because some methods can be called freely
if (username == null) {
username = "null";
}
if (authenticationToken == null) {
authenticationToken = "null";
}
// Build the params types
List paramsTypes = new Vector();
// Build the params values
List paramsValues = new Vector();
debug("");
for (int i = 0; i < params.length; i++) {
if (params[i] == null) {
throw new IllegalArgumentException(
Tag.PRODUCT
+ " null values are not supported. Please provide a value for all parameters.");
} else {
String classType = params[i].getClass().getName();
// NO! can alter class name if value is obsfucated
// classType = StringUtils.substringAfterLast(classType, ".");
paramsTypes.add(classType);
String value = params[i].toString();
debug("");
debug("classType: " + classType);
debug("value : " + value);
paramsValues.add(value);
}
}
// ListHolder listHolderTypes = new ListHolder();
// listHolderTypes.setList(paramsTypes);
String jsonParamTypes = ListOfStringTransport.toJson(paramsTypes);
// ListHolder listHolderValues = new ListHolder();
// listHolderValues.setList(paramsValues);
String jsonParamValues = ListOfStringTransport.toJson(paramsValues);
debug("methodName : " + methodName);
debug("jsonParamTypes : " + jsonParamTypes);
debug("jsonParamValues: " + jsonParamValues);
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.CALL_ACTION_HTML_ENCODED));
requestParams
.add(new SimpleNameValuePair(Parameter.USERNAME, username));
requestParams.add(new SimpleNameValuePair(Parameter.TOKEN,
authenticationToken));
requestParams.add(new SimpleNameValuePair(Parameter.METHOD_NAME,
methodName));
requestParams.add(new SimpleNameValuePair(Parameter.PARAMS_TYPES,
jsonParamTypes));
requestParams.add(new SimpleNameValuePair(Parameter.PARAMS_VALUES,
jsonParamValues));
httpTransfer.send(requestParams);
// Return the answer
String response = httpTransfer.recv();
debug("response: " + response);
// Content is OK
if (response.startsWith(ReturnCode.INVALID_LOGIN_OR_PASSWORD)) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
}
// The response is in Html encode:
if (!response.isEmpty()) {
response = HtmlConverter.fromHtml(response);
}
return response;
}
/**
* Returns with one call the length of a list of files located on the remote
* host.
*
* This convenient methods is provided for fast compute of the total length
* of a list of files to download, without contacting the server for each
* file result. (Case using a progress monitor).
*
* The real paths of the remote files depend on the Awake FILE configuration
* on the server. See User Documentation.
*
* @param pathnames
* the list of pathnames on host with "/" as file separator. Must
* be absolute.
*
* @return the total length in bytes of the files located on the remote
* host.
*
* @throws IllegalArgumentException
* if pathnames is null
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
*
* @throws UnknownHostException
* if the host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*/
public long length(List pathnames) throws IllegalArgumentException,
InvalidLoginException, UnknownHostException, ConnectException,
SocketException, RemoteException, IOException {
if (pathnames == null) {
throw new IllegalArgumentException("pathnames can not be null!");
}
if (username == null || authenticationToken == null) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
}
pathnames = HtmlConverter.toHtml(pathnames);
String jsonString = ListOfStringTransport.toJson(pathnames);
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.GET_FILE_LENGTH_ACTION));
requestParams
.add(new SimpleNameValuePair(Parameter.USERNAME, username));
requestParams.add(new SimpleNameValuePair(Parameter.TOKEN,
authenticationToken));
requestParams.add(new SimpleNameValuePair(Parameter.FILENAME,
jsonString));
httpTransfer.send(requestParams);
// If everything is OK, we have in our protocol a response that
// 1) starts with "OK". 2) Is followed by the Authentication Token
// else: response starts with "INVALID_LOGIN_OR_PASSWORD".
String response = httpTransfer.recv();
if (response.startsWith(ReturnCode.INVALID_LOGIN_OR_PASSWORD)) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
} else {
try {
long fileLength = Long.parseLong(response);
return fileLength;
} catch (NumberFormatException nfe) {
// Build an Awake Exception with the content of the recv stream
throw new IOException(Tag.PRODUCT_PRODUCT_FAIL + " "
+ nfe.getMessage(), nfe);
}
}
}
/**
* Downloads a file from the remote server.
* This method simply wraps bytes copy from a {@link RemoteInputStream} to a
* {@code FileOutputStream}.
*
* The real path of the remote file depends on the Awake FILE configuration
* on the server. See User Documentation.
*
* Large files are split in chunks that are downloaded in sequence. The
* default chunk length is 10Mb. You can change the default value with
* {@link SessionParameters#setDownloadChunkLength(long)} before passing
* {@code SessionParameters} to this class constructor.
*
* Note that file chunking requires that all chunks be downloaded from to
* the same web server. Thus, file chunking does not support true stateless
* architecture with multiple identical web servers. If you want to set a
* full stateless architecture with multiple identical web servers, you must
* disable file chunking. This is done by setting a 0 download chunk length
* value using {@link SessionParameters#setDownloadChunkLength(long)}.
*
* A recovery mechanism allows - in case of failure - to start again in the
* same JVM run the file download from the last non-downloaded chunk. See
* User Guide for more information.
*
* Note that this method can not be used with a progress indicator/monitor
* and so does not implement any increment mechanism. The reason is dual:
*
* - Implementing an increment mechanism would require to add cumbersome
* API.
* - Wrapped classes {@link RemoteInputStream} and
* {@code FileOutputStream} allow easy implementation of progress
* indicators. See Tutorial and included examples.
*
*
* @param pathname
* the pathname on host with "/" as file separator. Must be
* absolute.
* @param file
* the file to create on the client side
* @throws IllegalArgumentException
* if file or pathname is null
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
* @throws FileNotFoundException
* if the remote file is not found on server
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*/
public void download(String pathname, File file)
throws IllegalArgumentException, InvalidLoginException,
FileNotFoundException, UnknownHostException, ConnectException,
SocketException, RemoteException, IOException {
if (pathname == null) {
throw new IllegalArgumentException("pathname can not be null!");
}
if (file == null) {
throw new IllegalArgumentException("file can not be null!");
}
if (getUsername() == null || getAuthenticationToken() == null) {
throw new InvalidLoginException(
RemoteSession.REMOTE_SESSION_IS_CLOSED);
}
InputStream in = null;
OutputStream out = null;
// (IOUtils is a general IO stream manipulation utilities
// provided by Apache Commons IO)
try {
in = new RemoteInputStream(this, pathname);
out = new BufferedOutputStream(new FileOutputStream(file));
IOUtils.copy(in, out);
// Cleaner to close in here so that no Exception is thrown in
// finally clause
in.close();
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
/**
* Uploads a file on the server.
* This method simply wraps bytes copy from a {@code FileInputStream} to a
* {@link RemoteOutputStream}.
*
* The real path of the remote file depends on the Awake FILE configuration
* on the server. See User Documentation.
*
* Large files are split in chunks that are uploaded in sequence. The
* default chunk length is 10Mb. You can change the default value with
* {@link SessionParameters#setUploadChunkLength(long)} before passing
* {@code SessionParameters} to this class constructor.
*
* Note that file chunking requires all chunks to be sent to the same web
* server that will aggregate the chunks after the last send. Thus, file
* chunking does not support true stateless architecture with multiple
* identical web servers. If you want to set a full stateless architecture
* with multiple identical web servers, you must disable file chunking. This
* is done by setting a 0 upload chunk length value using
* {@link SessionParameters#setUploadChunkLength(long)}.
*
* A recovery mechanism allows - in case of failure - to start again in the
* same JVM run the file upload from the last non-uploaded chunk. See User
* Guide for more information.
*
* Note that this method can not be used with a progress indicator/monitor
* and so does not implement any increment mechanism. The reason is dual:
*
* - Implementing an increment mechanism would require to add cumbersome
* API.
* - Wrapped classes {@code FileInputStream} and
* {@link RemoteOutputStream} allow easy implementation of progress
* indicators. See Tutorial and included examples.
*
*
* @param file
* the file to upload
* @param pathname
* the pathname on host with "/" as file separator. Must be
* absolute.
* @throws IllegalArgumentException
* if file or pathname is null
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
* @throws FileNotFoundException
* if the file to upload is not found
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*
*/
public void upload(File file, String pathname)
throws IllegalArgumentException, InvalidLoginException,
FileNotFoundException, UnknownHostException, ConnectException,
SocketException, RemoteException, IOException {
if (pathname == null) {
throw new IllegalArgumentException("pathname can not be null!");
}
if (file == null) {
throw new IllegalArgumentException("file can not be null!");
}
if (!file.exists()) {
throw new FileNotFoundException("File does not exists: " + file);
}
if (getUsername() == null || getAuthenticationToken() == null) {
throw new InvalidLoginException(
RemoteSession.REMOTE_SESSION_IS_CLOSED);
}
InputStream in = null;
OutputStream out = null;
// (IOUtils is a general IO stream manipulation utilities
// provided by Apache Commons IO)
try {
in = new BufferedInputStream(new FileInputStream(file));
out = new RemoteOutputStream(this, pathname, file.length());
IOUtils.copy(in, out);
// Cleaner to close out here so that no Exception is thrown in
// finally clause
out.close();
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
/**
* Returns the Java version of the the servlet container on the remote
* server
* (The value of {@code System.getProperty("java.version")}.
*
* @return the Java version of the the servlet container on the remote
* server
*
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
*
* @throws UnknownHostException
* if the host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*/
public String getRemoteJavaVersion() throws InvalidLoginException,
UnknownHostException, ConnectException, SocketException,
RemoteException, IOException {
if (username == null || authenticationToken == null) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
}
// Remote Java verdion is cached
if (remoteJavaVersion != null) {
return remoteJavaVersion;
}
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.GET_JAVA_VERSION));
requestParams
.add(new SimpleNameValuePair(Parameter.USERNAME, username));
requestParams.add(new SimpleNameValuePair(Parameter.TOKEN,
authenticationToken));
httpTransfer.send(requestParams);
// If everything is OK, we have in our protocol a response that
// 1) starts with "OK". 2) Is followed by the Authentication Token
// else: response starts with "INVALID_LOGIN_OR_PASSWORD".
String response = httpTransfer.recv();
if (response.startsWith(ReturnCode.INVALID_LOGIN_OR_PASSWORD)) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
} else {
remoteJavaVersion = response;
return remoteJavaVersion;
}
}
/**
* Allows to get a copy of the current RemoteSession
: use it to
* do some simultaneous operations in a different thread (in order to avoid
* conflicts).
*/
@Override
public RemoteSession clone() {
RemoteSession remoteSession = new RemoteSession(this.url,
this.username, this.authenticationToken, this.proxy,
this.passwordAuthentication, this.sessionParameters,
this.remoteJavaVersion);
return remoteSession;
}
/**
* Returns the Awake FILE Version.
*
* @return the Awake FILE Version
*/
public String getVersion() {
return FileVersion.getVersion();
}
/**
* Logs off from the remote host. This will purge the authentication
* values necessary for method calls.
*
* Method should be called at the closure of the Client application.
*/
public void logoff() {
username = null;
authenticationToken = null;
proxy = null;
passwordAuthentication = null;
sessionParameters = null;
remoteJavaVersion = null;
if (httpTransfer != null) {
httpTransfer.close();
httpTransfer = null;
}
}
/**
* Returns the HttpTransfer instance in use
*
* @return the httpTransfer instance
*/
HttpTransfer getHttpTransfer() {
return httpTransfer;
}
/**
*
* Tests if remote host requires our URL to be SSL
*
* @return true if the host requires SSL
*
* @throws UnknownHostException
* @throws ConnectException
* @throws RemoteException
* @throws SecurityException
* @throws IOException
*/
private boolean isForceHttps() throws UnknownHostException,
ConnectException, RemoteException, SecurityException, IOException {
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.BEFORE_LOGIN_ACTION));
httpTransfer.send(requestParams);
// If everything is OK, we have in our protocol a response that
// 1) starts with "OK". 2) Is followed by the Authentication Token
// else: response starts with "INVALID_LOGIN_OR_PASSWORD".
String receive = httpTransfer.recv();
Boolean isForceHttps = new Boolean(receive);
return isForceHttps.booleanValue();
}
/**
* Calls a remote Java {@code class.method} and (eventually) pass some
* parameters to it. This method transforms the values in Base64. It's a
* legacy method not to be used anymore: use {@code call} instead.
*
* @param methodName
* the full method name to call in the format
* org.acme.config.package.MyClass.myMethod
* @param params
* the array of parameters passed to the method
* @return the result of the Java call as string
*
* @throws IllegalArgumentException
* if methodName is null
* @throws InvalidLoginException
* the session has been closed by a {@code logoff()}
*
* @throws UnknownHostException
* if host URL (http://www.acme.org) does not exists or no
* Internet Connection.
* @throws ConnectException
* if the Host is correct but the {@code ServerFileManager}
* Servlet is not reachable
* (http://www.acme.org/ServerFileManager) and access failed
* with a status != OK (200). (If the host is incorrect, or is
* impossible to connect to - Tomcat down - the
* {@code ConnectException} will be the sub exception
* {@code HttpHostConnectException}.)
* @throws SocketException
* if network failure during transmission
* @throws RemoteException
* an exception has been thrown on the server side
* @throws IOException
* for all other IO / Network / System Error
*
*/
private String callBase64Encoded(String methodName, Object... params)
throws IllegalArgumentException, InvalidLoginException,
UnknownHostException, ConnectException, SocketException,
RemoteException, IOException {
// Class and method name can not be null
if (methodName == null) {
throw new IllegalArgumentException("methodName can not be null!");
}
// username & Authentication Token may be null
// because some methods can be called freely
if (username == null) {
username = "null";
}
if (authenticationToken == null) {
authenticationToken = "null";
}
// Build the params types
List paramsTypes = new Vector();
// Build the params values
List paramsValues = new Vector();
debug("");
for (int i = 0; i < params.length; i++) {
if (params[i] == null) {
throw new IllegalArgumentException(
Tag.PRODUCT
+ " null values are not supported. Please provide a value for all parameters.");
} else {
String classType = params[i].getClass().getName();
// NO! can alter class name if value is obsfucated
// classType = StringUtils.substringAfterLast(classType, ".");
paramsTypes.add(classType);
String value = params[i].toString();
debug("");
debug("classType: " + classType);
debug("value : " + value);
paramsValues.add(value);
}
}
// ListHolder listHolderTypes = new ListHolder();
// listHolderTypes.setList(paramsTypes);
String jsonParamTypes = ListOfStringTransport.toJson(paramsTypes);
// ListHolder listHolderValues = new ListHolder();
// listHolderValues.setList(paramsValues);
String jsonParamValues = ListOfStringTransport.toJson(paramsValues);
debug("methodName : " + methodName);
debug("jsonParamTypes : " + jsonParamTypes);
debug("jsonParamValues: " + jsonParamValues);
// Prepare the request parameters
List requestParams = new Vector();
requestParams.add(new SimpleNameValuePair(Parameter.ACTION,
Action.CALL_ACTION));
// requestParams.add(new SimpleNameValuePair(Parameter.LOGIN,
// StringUtil.toBase64(username)));
requestParams
.add(new SimpleNameValuePair(Parameter.USERNAME, username));
requestParams.add(new SimpleNameValuePair(Parameter.TOKEN,
authenticationToken));
requestParams.add(new SimpleNameValuePair(Parameter.METHOD_NAME,
methodName));
requestParams.add(new SimpleNameValuePair(Parameter.PARAMS_TYPES,
StringUtil.toBase64(jsonParamTypes)));
requestParams.add(new SimpleNameValuePair(Parameter.PARAMS_VALUES,
StringUtil.toBase64(jsonParamValues)));
httpTransfer.send(requestParams);
// Return the answer
String response = httpTransfer.recv();
debug("response: " + response);
// Content is OK
if (response.startsWith(ReturnCode.INVALID_LOGIN_OR_PASSWORD)) {
throw new InvalidLoginException(REMOTE_SESSION_IS_CLOSED);
}
// The response is in Base 64
try {
if (!response.isEmpty()) {
response = StringUtil.fromBase64(response);
}
} catch (Exception e) {
debug(response);
// May happen till new Awake FILE is not deployed on Server
throw new IOException(Tag.PRODUCT_PRODUCT_FAIL
+ " Response must be and is not base64!", e);
}
return response;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((authenticationToken == null) ? 0 : authenticationToken
.hashCode());
result = prime * result + ((url == null) ? 0 : url.hashCode());
result = prime * result
+ ((username == null) ? 0 : username.hashCode());
return result;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RemoteSession other = (RemoteSession) obj;
if (authenticationToken == null) {
if (other.authenticationToken != null)
return false;
} else if (!authenticationToken.equals(other.authenticationToken))
return false;
if (url == null) {
if (other.url != null)
return false;
} else if (!url.equals(other.url))
return false;
if (username == null) {
if (other.username != null)
return false;
} else if (!username.equals(other.username))
return false;
return true;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "RemoteSession [url=" + url + ", username=" + username
+ ", proxy=" + proxy + ", passwordAuthentication="
+ passwordAuthentication + ", SessionParameters="
+ sessionParameters + "]";
}
/**
* debug tool
*/
private void debug(String s) {
if (DEBUG) {
ClientLogger.getLogger().log(Level.WARNING, s);
}
}
}
// End