/* * RClientImpl.java * * Copyright (C) 2010-2016, Microsoft Corporation * * This program is licensed to you under the terms of Version 2.0 of the * Apache License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) for more details. * */ package com.revo.deployr.client.core.impl; import com.revo.deployr.client.RClient; import com.revo.deployr.client.RUser; import com.revo.deployr.client.core.REndpoints; import com.revo.deployr.client.call.RCall; import com.revo.deployr.client.call.AbstractCall; import com.revo.deployr.client.call.user.LoginCall; import com.revo.deployr.client.call.user.LogoutCall; import com.revo.deployr.client.call.user.AboutCall; import com.revo.deployr.client.call.user.AutosaveCall; import com.revo.deployr.client.call.project.ProjectCreateCall; import com.revo.deployr.client.call.repository.RepositoryScriptExecuteCall; import com.revo.deployr.client.call.repository.RepositoryScriptInterruptCall; import com.revo.deployr.client.RClientException; import com.revo.deployr.client.RDataException; import com.revo.deployr.client.RSecurityException; import com.revo.deployr.client.RGridException; import com.revo.deployr.client.auth.RAuthentication; import com.revo.deployr.client.auth.basic.RBasicAuthentication; import com.revo.deployr.client.core.RCoreResponse; import com.revo.deployr.client.core.RCoreResult; import com.revo.deployr.client.core.RClientExecutor; import com.revo.deployr.client.data.RData; import com.revo.deployr.client.util.RDataUtil; import com.revo.deployr.client.params.AnonymousProjectExecutionOptions; import com.revo.deployr.client.RProjectExecution; import com.revo.deployr.client.RScriptExecution; import com.revo.deployr.client.RRepositoryDirectory; import com.revo.deployr.client.about.RUserDetails; import com.revo.deployr.client.about.RUserLimitDetails; import com.revo.deployr.client.about.RProjectDetails; import com.revo.deployr.client.about.RProjectExecutionDetails; import com.revo.deployr.client.util.REntityUtil; import org.apache.http.Header; import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.client.CookieStore; import org.apache.http.params.HttpParams; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ClientConnectionManager; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.client.params.CookiePolicy; import org.apache.http.client.params.ClientPNames; import org.apache.http.client.utils.URIBuilder; import org.apache.http.cookie.Cookie; import javax.net.ssl.SSLContext; import org.apache.http.conn.ssl.*; import org.apache.http.config.*; import org.apache.http.conn.socket.*; import org.apache.http.impl.client.HttpClients; import java.security.cert.X509Certificate; import java.security.*; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import java.util.*; import java.net.*; import java.io.*; import java.util.concurrent.Executors; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import java.util.concurrent.ExecutorService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class RClientImpl implements RClient, RClientExecutor { private Log log = LogFactory.getLog(RClientImpl.class); private final static String XSRF_HEADER = "X-XSRF-TOKEN"; private DefaultHttpClient httpClient; private ExecutorService eService; private String serverurl; private String httpcookie; private String csrf; private SSLSocketFactory sslSocketFactory; private RLiveContext liveContext; private ArrayList<Integer> GRID_EXCEPTION_CODES = new ArrayList<Integer>( Arrays.asList(910, 911, 912, 913, 914, 915, 916, 917, 918, 919)); public RClientImpl(String serverurl) throws RClientException, RSecurityException { this(serverurl, 200, false); } public RClientImpl(String serverurl, int concurrentCallLimit) throws RClientException, RSecurityException { this(serverurl, concurrentCallLimit, false); } public RClientImpl(String serverurl, int concurrentCallLimit, boolean allowSelfSignedSSLCert) throws RClientException, RSecurityException { log.debug("Creating client connection: serverurl=" + serverurl + ", concurrentCallLimit=" + concurrentCallLimit + ", allowSelfSignedSSLCert=" + allowSelfSignedSSLCert); this.serverurl = serverurl; // Create and initialize HTTP parameters HttpParams httpParams = new BasicHttpParams(); // Set Infinite Connection and Socket Timeouts. HttpConnectionParams.setConnectionTimeout(httpParams, 0); HttpConnectionParams.setSoTimeout(httpParams, 0); ConnManagerParams.setMaxTotalConnections(httpParams, concurrentCallLimit); ConnManagerParams.setMaxConnectionsPerRoute(httpParams, new ConnPerRouteBean(concurrentCallLimit)); HttpProtocolParams.setVersion(httpParams, HttpVersion.HTTP_1_1); // Create and initialize scheme registry SchemeRegistry schemeRegistry = new SchemeRegistry(); schemeRegistry.register( new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); if(allowSelfSignedSSLCert) { /* * Register scheme for "https" that bypasses * SSL cert trusted-origin verification check * which makes it possible to connect to a * DeployR server using a self-signed certificate. * * Recommended for prototyping and testing only, * not recommended for production environments. */ TrustStrategy blindTrust = new TrustStrategy() { @Override public boolean isTrusted(X509Certificate[] certificate, String authType) { return true; } }; try { sslSocketFactory = new SSLSocketFactory(blindTrust, SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); schemeRegistry.register(new Scheme("https", 8443, sslSocketFactory)); } catch(GeneralSecurityException gsex) { String exMsg = "Self-signed SSL cert config failed, " + gsex.getMessage(); log.debug(exMsg); throw new RSecurityException(exMsg, 0); } } // Create a HttpClient with the ThreadSafeClientConnManager. // This connection manager must be used if more than one thread will // be using the HttpClient. ClientConnectionManager cm = new ThreadSafeClientConnManager(httpParams, schemeRegistry); httpClient = new DefaultHttpClient(cm, httpParams); // Enable cookie handling by setting cookie policy on HttpClient. httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY, CookiePolicy.BEST_MATCH); log.debug("Created client connection: httpClient=" + httpClient); eService = Executors.newCachedThreadPool(); } public RUser login(RAuthentication rAuthentication) throws RClientException, RSecurityException { return login(rAuthentication, false); } public RUser login(RAuthentication rAuthentication, boolean disableautosave) throws RClientException, RSecurityException { RCall rCall = null; if(rAuthentication instanceof RBasicAuthentication) { RBasicAuthentication basic = (RBasicAuthentication) rAuthentication; rCall = new LoginCall(basic.getUsername(), basic.getPassword(), disableautosave); } RCoreResult rResult = processCall(rCall); Map<String, String> identity = rResult.getIdentity(); Map<String, Integer> limits = rResult.getLimits(); // // Store cookie from response header, we no longer return this value in // the DeployR response markup as of `8.0.5` // CookieStore cookieStore = httpClient.getCookieStore(); Cookie cookie = cookieStore.getCookies().get(0); this.httpcookie = (cookie != null ? cookie.getValue() : null); // // - Store `X-XSRF-TOKEN` from `/r/uer/login` for future authenticated // requests // for (Header header : rResult.getHeaders()) { if (header.getName().equals(XSRF_HEADER)) { this.csrf = header.getValue(); } } RUserDetails userDetails = REntityUtil.getUserDetails(identity, limits, csrf); liveContext = new RLiveContext(this, serverurl, httpcookie); return new RUserImpl(userDetails, liveContext); } public void logout(RUser user) throws RClientException, RSecurityException { RUserDetails about = (user != null) ? ((RUserImpl) user).about : null; RCall rCall = new LogoutCall(about); processCall(rCall); } public RScriptExecution executeScript(String scriptName, String scriptAuthor, String scriptVersion) throws RClientException, RSecurityException, RDataException, RGridException { return executeScript(scriptName, RRepositoryDirectory.ROOT, scriptAuthor, scriptVersion, null); } public RScriptExecution executeScript(String scriptName, String scriptAuthor, String scriptVersion, AnonymousProjectExecutionOptions executionOptions) throws RClientException, RSecurityException, RDataException, RGridException { return executeScript(scriptName, RRepositoryDirectory.ROOT, scriptAuthor, scriptVersion, executionOptions); } public RScriptExecution executeScript(String scriptName, String scriptDirectory, String scriptAuthor, String scriptVersion) throws RClientException, RSecurityException, RDataException, RGridException { return executeScript(scriptName, scriptDirectory, scriptAuthor, scriptVersion, null); } public RScriptExecution executeScript(String scriptName, String scriptDirectory, String scriptAuthor, String scriptVersion, AnonymousProjectExecutionOptions executionOptions) throws RClientException, RSecurityException, RDataException, RGridException { RCall rCall = new RepositoryScriptExecuteCall(scriptName, scriptDirectory, scriptAuthor, scriptVersion, null, executionOptions); RCoreResult rResult = processCallOnGrid(rCall); Map projectMap = rResult.getProject(); RProjectDetails projectDetails = REntityUtil.getProjectDetails(projectMap); Map execution = (Map) rResult.getExecution(); List<RData> workspaceObjects = rResult.getRObjects(); List<Map> repofiles = rResult.getRepoFiles(); String error = rResult.getError(); int errorCode = rResult.getErrorCode(); liveContext = new RLiveContext(this, serverurl, httpcookie); RProjectExecutionDetails execDetails = REntityUtil.getProjectExecutionDetails(projectDetails, execution, workspaceObjects, repofiles, error, errorCode, liveContext); RScriptExecution rExecution = new RScriptExecutionImpl(projectDetails, execDetails, liveContext); boolean success = rResult.isSuccess(); log.debug("executeScript: success=" + success + " error=" + error + " errorCode=" + errorCode); return rExecution; } public RScriptExecution executeExternal(String externalSource, AnonymousProjectExecutionOptions executionOptions) throws RClientException, RSecurityException, RDataException, RGridException { RCall rCall = new RepositoryScriptExecuteCall(null, null, null, null, externalSource, executionOptions); RCoreResult rResult = processCallOnGrid(rCall); Map projectMap = rResult.getProject(); RProjectDetails projectDetails = REntityUtil.getProjectDetails(projectMap); Map execution = (Map) rResult.getExecution(); List<RData> workspaceObjects = rResult.getRObjects(); List<Map> repofiles = rResult.getRepoFiles(); String error = rResult.getError(); int errorCode = rResult.getErrorCode(); liveContext = new RLiveContext(this, serverurl, httpcookie); RProjectExecutionDetails execDetails = REntityUtil.getProjectExecutionDetails(projectDetails, execution, workspaceObjects, repofiles, error, errorCode, liveContext); RScriptExecution rExecution = new RScriptExecutionImpl(projectDetails, execDetails, liveContext); boolean success = rResult.isSuccess(); log.debug("executeExternal: success=" + success + " error=" + error + " errorCode=" + errorCode); return rExecution; } public URL renderScript(String scriptName, String scriptDirectory, String scriptAuthor, String scriptVersion, AnonymousProjectExecutionOptions options) throws RClientException, RDataException { URL renderURL; try { String urlPath = serverurl + REndpoints.RREPOSITORYSCRIPTRENDER; if(httpcookie != null) { urlPath += ";jsessionid=" + httpcookie; } URIBuilder builder = new URIBuilder(urlPath); builder.addParameter("filename", scriptName); builder.addParameter("directory", scriptDirectory); builder.addParameter("author", scriptAuthor); if(scriptVersion != null) { builder.addParameter("version", scriptVersion); } if(options != null) { builder.addParameter("blackbox", Boolean.toString(options.blackbox)); builder.addParameter("recycle", Boolean.toString(options.recycle)); if(options.rinputs != null) { String markup = RDataUtil.toJSON(options.rinputs); builder.addParameter("inputs", markup); } String objectNames = null; if(options.routputs != null) { for(String object : options.routputs) { if(objectNames != null) { objectNames = objectNames + "," + object; } else { objectNames = object; } } } builder.addParameter("robjects", objectNames); builder.addParameter("tag", options.tag); builder.addParameter("echooff", Boolean.toString(options.echooff)); builder.addParameter("consoleoff", Boolean.toString(options.consoleoff)); builder.addParameter("encodeDataFramePrimitiveAsVector", Boolean.toString(options.encodeDataFramePrimitiveAsVector)); if(options.preloadWorkspace != null) { builder.addParameter("preloadobjectname", options.preloadWorkspace.filename); builder.addParameter("preloadobjectauthor", options.preloadWorkspace.author); builder.addParameter("preloadobjectversion", options.preloadWorkspace.version); } if(options.preloadDirectory != null) { builder.addParameter("preloadfilename", options.preloadWorkspace.filename); builder.addParameter("preloadfileauthor", options.preloadWorkspace.author); builder.addParameter("preloadfileversion", options.preloadWorkspace.version); } if(options.adoptionOptions != null) { builder.addParameter("adoptworkspace", options.adoptionOptions.adoptWorkspace); builder.addParameter("adoptdirectory", options.adoptionOptions.adoptDirectory); builder.addParameter("adoptpackages", options.adoptionOptions.adoptPackages); } if(options.storageOptions != null) { builder.addParameter("storefile", options.storageOptions.files); builder.addParameter("storeobjct", options.storageOptions.objects); builder.addParameter("storeworkspace", options.storageOptions.workspace); builder.addParameter("storenewversion", Boolean.toString(options.storageOptions.newversion)); builder.addParameter("storepublic", Boolean.toString(options.storageOptions.published)); } builder.addParameter("nan", options.nan); builder.addParameter("infinity", options.infinity); builder.addParameter("graphics", options.graphicsDevice); builder.addParameter("graphicswidth", Integer.toString(options.graphicsWidth)); builder.addParameter("graphicsheight", Integer.toString(options.graphicsHeight)); } renderURL = builder.build().toURL(); } catch(Exception uex) { throw new RClientException("Render url: ex=" + uex.getMessage()); } return renderURL; } public void interruptScript() throws RClientException, RSecurityException { RCall rCall = new RepositoryScriptInterruptCall(); RCoreResult rResult = processCall(rCall); String error = rResult.getError(); int errorCode = rResult.getErrorCode(); boolean success = rResult.isSuccess(); log.debug("interruptScript: success=" + success + " error=" + error + " errorCode=" + errorCode); } public void release() { try { logout(null); } catch(Exception ex) { // Ignore any exceptions on forced logout operation. } try { eService.shutdown(); } catch (Exception ex) { log.debug("Exception shutting down the executor service.", ex); } log.debug("RClient eService.isShutdown=" + eService.isShutdown()); try { httpClient.getConnectionManager().shutdown(); } catch (Exception ex) { log.debug("Exception shutting down the HTTP connection manager.", ex); } log.debug("RClient shutdown completes."); } // RClientExecutor interface implementation. public RCoreResult processCall(RCall rCall) throws RClientException, RSecurityException { RCoreResponse pResponse = execute(rCall); RCoreResult rResult = null; try { rResult = pResponse.get(); log.debug("processCall: pCoreResult.statusMsg=" + rResult.getStatusMsg() + ", statusCode=" + rResult.getStatusCode()); if(rResult.getStatusCode() == 200) { if(!rResult.isSuccess()) throw new RClientException(rResult.getError()); } else if(rResult.getStatusCode() == 401) { throw new RSecurityException("Unauthenticated call.", 401); } else if(rResult.getStatusCode() == 403) { throw new RSecurityException("Unauthorized call.", 403); } else if(rResult.getStatusCode() == 409) { throw new RSecurityException("Conflict on call.", 409); } else { throw new RClientException(rResult.getStatusMsg(), rResult.getStatusCode()); } } catch(RSecurityException psex) { throw psex; } catch(RClientException pcex) { throw pcex; } catch(Exception ex) { throw new RClientException(ex.getMessage(), ex); } return rResult; } public RCoreResult processCallOnGrid(RCall rCall) throws RClientException, RSecurityException, RGridException { RCoreResponse pResponse = execute(rCall); RCoreResult rResult = null; try { rResult = pResponse.get(); log.debug("processCallOnGrid: pCoreResult.statusMsg=" + rResult.getStatusMsg() + ", statusCode=" + rResult.getStatusCode()); if(rResult.getStatusCode() == 200) { if(!rResult.isSuccess()) { if(GRID_EXCEPTION_CODES.contains(rResult.getErrorCode())) { throw new RGridException(rResult.getError(), rResult.getErrorCode()); } else { throw new RClientException(rResult.getError(), rResult.getErrorCode()); } } } else if(rResult.getStatusCode() == 401) { throw new RSecurityException("Unauthenticated call.", 401); } else if(rResult.getStatusCode() == 403) { throw new RSecurityException("Unauthorized call.", 403); } else if(rResult.getStatusCode() == 409) { throw new RSecurityException("Conflict on call.", 409); } else { throw new RClientException(rResult.getError(), rResult.getErrorCode()); } } catch(RSecurityException secex) { throw secex; } catch(RClientException cex) { if(GRID_EXCEPTION_CODES.contains(rResult.getErrorCode())) { throw new RGridException(rResult.getError(), rResult.getErrorCode()); } else { throw cex; } } catch(RGridException gex) { throw gex; } catch(Exception ex) { throw new RClientException(ex.getMessage(), ex); } return rResult; } public InputStream download(URIBuilder builder) throws RClientException { try { log.debug("download: uri builder=" + builder); String uriPath = builder.build().toString(); HttpGet request = new HttpGet(uriPath); HttpResponse response = httpClient.execute(request); return response.getEntity().getContent(); } catch(Exception ex){ String exMsg = builder + " download failed, " + ex.getMessage(); throw new RClientException(exMsg, ex); } } // Private method implementations. public RCoreResponse execute(RCall call) { AbstractCall abstractCall = (AbstractCall) call; abstractCall.addHeader(XSRF_HEADER, csrf); // Provide httpClient and DeployR server url context to RCall. abstractCall.setClient(httpClient, serverurl); Callable callable = (Callable) call; // Wrap Callable in FutureTask for execution by the Executor Service. FutureTask task = new FutureTask(callable); abstractCall.setFuture(task); eService.submit(task); return (RCoreResponse) call; } }