All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kie.services.client.api.command.RemoteConfiguration Maven / Gradle / Ivy

package org.kie.services.client.api.command;

import java.io.IOException;
import java.net.Inet6Address;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;

import javax.jms.ConnectionFactory;
import javax.jms.Queue;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;

import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientRequestFactory;
import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.client.core.executors.ApacheHttpClient4Executor;
import org.jboss.resteasy.spi.interception.ClientExecutionContext;
import org.jboss.resteasy.spi.interception.ClientExecutionInterceptor;
import org.kie.api.runtime.manager.Context;
import org.kie.services.client.api.builder.exception.InsufficientInfoToBuildException;
import org.kie.services.client.api.command.exception.RemoteCommunicationException;
import org.kie.services.client.serialization.JaxbSerializationProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * In order to protect the Remote (Java) API, this class may not be extended nor may its constructor be made public.
 */
public final class RemoteConfiguration {

    public static final String SSL_CONNECTION_FACTORY_NAME = "jms/SslRemoteConnectionFactory";
    public static final String CONNECTION_FACTORY_NAME = "jms/RemoteConnectionFactory";
    public static final String SESSION_QUEUE_NAME = "jms/queue/KIE.SESSION";
    public static final String TASK_QUEUE_NAME = "jms/queue/KIE.TASK";
    public static final String RESPONSE_QUEUE_NAME = "jms/queue/KIE.RESPONSE";

    public static final int DEFAULT_TIMEOUT = 5;
    private long timeout = DEFAULT_TIMEOUT; // in seconds
    
    // REST or JMS
    private final Type type;

    // General
    private String deploymentId;
    private Long processInstanceId;
    private String userName;
    private String password;
    private Set> extraJaxbClasses = new HashSet>();

    // REST
    private ClientRequestFactory requestFactory;
    private boolean useFormBasedAuth = false;

    // JMS
    private boolean useSsl = false;
    private ConnectionFactory connectionFactory;
    private Queue ksessionQueue;
    private Queue taskQueue;
    private Queue responseQueue;
    private int jmsSerializationType = JaxbSerializationProvider.JMS_SERIALIZATION_TYPE;

    private static final AtomicInteger idGen = new AtomicInteger(0);
    
    /**
     * Public constructors and setters
     */

    @SuppressWarnings("unused")
    private RemoteConfiguration() {
        // no public constructor!
        this.type = Type.CONSTRUCTOR;
    }

    public RemoteConfiguration(Type type) { 
        this.type = type;
    }
    
    // REST ----------------------------------------------------------------------------------------------------------------------

    public RemoteConfiguration(String deploymentId, URL url, String username, String password) {
        this(deploymentId, url, username, password, DEFAULT_TIMEOUT);
    }

    public RemoteConfiguration(String deploymentId, URL url, String username, String password, int timeout) {
        this(deploymentId, url, username, password, timeout, false);
    }

    public RemoteConfiguration(String deploymentId, URL url, String username, String password, int timeout, boolean formBasedAuth) {
        this.type = Type.REST;
        this.deploymentId = deploymentId;
        this.timeout = timeout;
        this.useFormBasedAuth = formBasedAuth;
        createRequestFactory(url, username, password);
    }

    public void createRequestFactory(URL url, String username, String password) { 
        URL serverPlusRestUrl = initializeRestServicesUrl(url);
        if (username == null || username.trim().isEmpty()) {
            throw new IllegalArgumentException("The user name may not be empty or null.");
        }
        if (password == null) {
            throw new IllegalArgumentException("The password may not be null.");
        }
        if( useFormBasedAuth ) { 
            this.requestFactory = createFormBasedAuthenticatingRequestFactory(serverPlusRestUrl, username, password, (int) timeout);
        } else { 
            this.requestFactory = createAuthenticatingRequestFactory(serverPlusRestUrl, username, password, (int) timeout);
        }
    }
    
    /**
     * Initializes the URL that will be used for the request factory
     * 
     * @param deploymentId Deployment ID
     * @param url URL of the server instance
     * @return An URL that can be used for the REST request factory
     */
    private URL initializeRestServicesUrl(URL url) {
        if (url == null) {
            throw new IllegalArgumentException("The url may not be empty or null.");
        }
        try {
            url.toURI();
        } catch (URISyntaxException urise) {
            throw new IllegalArgumentException(
                    "URL (" + url.toExternalForm() + ") is incorrectly formatted: " + urise.getMessage(), urise);
        }

        String urlString = url.toExternalForm();
        if (!urlString.endsWith("/")) {
            urlString += "/";
        }
        urlString += "rest";

        URL serverPlusRestUrl;
        try {
            serverPlusRestUrl = new URL(urlString);
        } catch (MalformedURLException murle) {
            throw new IllegalArgumentException(
                    "URL (" + url.toExternalForm() + ") is incorrectly formatted: " + murle.getMessage(), murle);
        }

        return serverPlusRestUrl;
    }

    /**
     * Creates an request factory that authenticates using the given username and password
     * 
     * @param url
     * @param username
     * @param password
     * @param timeout
     * 
     * @return A request factory that can be used to send (authenticating) requests to REST services
     */
    public static ClientRequestFactory createAuthenticatingRequestFactory(URL url, String username, String password, int timeout) {
        BasicHttpContext localContext = new BasicHttpContext();
        HttpClient preemptiveAuthClient = createPreemptiveAuthHttpClient(username, password, timeout, localContext);
        ClientExecutor clientExecutor = new ApacheHttpClient4Executor(preemptiveAuthClient, localContext);
        try {
            return new ClientRequestFactory(clientExecutor, url.toURI());
        } catch (URISyntaxException urise) {
            throw new IllegalArgumentException("URL (" + url.toExternalForm() + ") is not formatted correctly.", urise);
        }
    }

    /**
     * Creates an request factory that authenticates using the given username and password
     * 
     * @param url
     * @param username
     * @param password
     * @param timeout
     * 
     * @return A request factory that can be used to send (authenticating) requests to REST services
     */
    public static ClientRequestFactory createFormBasedAuthenticatingRequestFactory(URL url, 
            final String username, final String password, 
            int timeout) {
        try {
            return new FormBasedAuthenticatingClientRequestFactory(url.toURI(), username, password, timeout);
        } catch (URISyntaxException urise) {
            throw new RemoteCommunicationException("Invalid URL: " + url.toExternalForm(), urise);
        }
    }

    static class FormBasedAuthenticatingClientRequestFactory extends ClientRequestFactory { 
     
        private final String username;
        private final String password;
        private final ClientExecutor executor;
        
        public FormBasedAuthenticatingClientRequestFactory(URI uri, String username, String password, int timeout) { 
            super(uri);
            this.username = username;
            this.password = password;
           
            DefaultHttpClient httpClient = new DefaultHttpClient();
            HttpParams params = httpClient.getParams();
            HttpConnectionParams.setConnectionTimeout(params, timeout*1000);
            HttpConnectionParams.setSoTimeout(params, timeout*1000);
            
            executor = new ApacheHttpClient4Executor(httpClient);
        }
        
        public ClientRequest createRelativeRequest(String uriTemplate) {
           ClientRequest request =  executor.createRequest(getBase().toString() + uriTemplate);
           request.registerInterceptor(new FormBasedAuthenticatingInterceptor(username, password));
           return request;
        }

        public ClientRequest createRequest(String uriTemplate) {
           ClientRequest request =  executor.createRequest(uriTemplate);
           request.registerInterceptor(new FormBasedAuthenticatingInterceptor(username, password));
           return request;
        }
    }
    
    static class FormBasedAuthenticatingInterceptor implements ClientExecutionInterceptor {

        private static final Logger logger = LoggerFactory.getLogger(FormBasedAuthenticatingInterceptor.class);
       
        private static final String LOGIN_FORM = "/uf_security_check";
        private static final String FORM_BASED_AUTH_PROPERTY = "org.kie.remote.form.based.auth";

        private final String username;
        private final String password;
        private String sessionCookie = null;

        public FormBasedAuthenticatingInterceptor(String username, String password) {
            this.username = username;
            this.password = password;
        }

        /**
         * This method is called every time a {@link ClientRequest} is executed.
         * 

* This interceptor method is thus triggered from {@link ClientRequest} calls within the method itself, * making it a recursively called method [*]. */ @Override public ClientResponse execute(ClientExecutionContext ctx) throws Exception { // Setup ClientRequest origRequest = ctx.getRequest(); if( sessionCookie != null ) { // Try with session cookie if it already exists origRequest.header(HttpHeaders.COOKIE, sessionCookie); } URL restUrl = new URL(origRequest.getUri()); String restUrlString = restUrl.toExternalForm(); String origRequestMethod = origRequest.getHttpMethod(); debug("Processing request: [" + origRequestMethod + "] " + restUrlString + (sessionCookie == null ? "" : " (session: " + sessionCookie + ")")); // Do request (whichever request it may be!) ClientResponse response = ctx.proceed(); int status = response.getStatus(); debug("Response received [" + status + "]" ); // If // 1. this is the form-based auth request, or // 2. if the form-based auth has completed, // then we're done.. if( restUrlString.endsWith(LOGIN_FORM) || Boolean.parseBoolean((String) origRequest.getAttributes().get(FORM_BASED_AUTH_PROPERTY)) ) { return response; } // Check response to see if form-based auth is necessary String requestCookie = (String) response.getHeaders().getFirst(HttpHeaders.SET_COOKIE); Object contentTypeObj = response.getHeaders().getFirst(HttpHeaders.CONTENT_TYPE); boolean doFormBasedAuth = false; if (contentTypeObj != null && (contentTypeObj instanceof String)) { if (((String) contentTypeObj).startsWith(MediaType.TEXT_HTML) && requestCookie != null && !requestCookie.equals(sessionCookie) ) { debug( "New session cookie: " + requestCookie ); doFormBasedAuth = true; sessionCookie = requestCookie; } } // If form-based auth is required, do it if (doFormBasedAuth) { response.releaseConnection(); // Create form-based auth URL String appBase = "/" + restUrl.getPath().substring(1).replaceAll("/.*", ""); URL appBaseUrl = new URL(restUrl.getProtocol(), restUrl.getHost(), restUrl.getPort(), appBase); ClientRequestFactory requestFactory = new ClientRequestFactory(appBaseUrl.toURI()); ClientRequest formRequest = requestFactory.createRelativeRequest(LOGIN_FORM); formRequest = formRequest.formParameter("uf_username", username).formParameter("uf_password", password); if( sessionCookie != null ) { formRequest.header(HttpHeaders.COOKIE, sessionCookie); } // Do form-based auth try { debug("Trying form-based authentication for session '" + sessionCookie + "'"); // [*] triggers recursive call of this method response = formRequest.post(); int formRequestStatus = response.getStatus(); if (formRequestStatus != 302) { String errMsg = "Unable to complete form-based authentication in via " + formRequest.getUri(); System.err.println(errMsg + "\n [" + formRequestStatus + "] " + response.getEntity(String.class)); throw new RemoteCommunicationException(errMsg + " (see output)"); } debug("Form-based authentication succeeded."); } catch (RemoteCommunicationException rce) { throw rce; } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { String errMsg = "Unable to complete form-based authentication in via " + formRequest.getUri(); throw new RemoteCommunicationException(errMsg, e); } } finally { try { response.releaseConnection(); } catch (Exception e) { // do nothing.. } } // Somehow, query parameters are being added a second time here.. // As long we use UriInfo in the service-side resources to get the query parameters // instead of the HttpServletRequest, then things work.. try { if( sessionCookie == null ) { throw new IllegalStateException("A cookie for a authenticated session should be available at this point!" ); } debug("Retrying original request (proceed): [" + origRequestMethod + "] " + restUrlString ); // [*] triggers recursive call of this method response = ctx.proceed(); } catch (Exception e) { if (e instanceof RuntimeException) { throw (RuntimeException) e; } else { throw new RemoteCommunicationException("Unable to " + origRequestMethod + " to " + restUrlString, e); } } } return response; } private void debug(String msg) { logger.debug(msg); } } /** * This method is used in order to create the authenticating REST client factory. * * @param userName * @param password * @param timeout * @param localContext * * @return A {@link DefaultHttpClient} instance that will authenticate using the given username and password. */ private static DefaultHttpClient createPreemptiveAuthHttpClient(String userName, String password, int timeout, BasicHttpContext localContext) { BasicHttpParams params = new BasicHttpParams(); int timeoutMilliSeconds = timeout * 1000; HttpConnectionParams.setConnectionTimeout(params, timeoutMilliSeconds); HttpConnectionParams.setSoTimeout(params, timeoutMilliSeconds); DefaultHttpClient client = new DefaultHttpClient(params); if (userName != null && !"".equals(userName)) { client.getCredentialsProvider().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials(userName, password)); // Generate BASIC scheme object and stick it to the local execution context BasicScheme basicAuth = new BasicScheme(); String contextId = UUID.randomUUID().toString(); localContext.setAttribute(contextId, basicAuth); // Add as the first request interceptor client.addRequestInterceptor(new PreemptiveAuth(contextId), 0); } String hostname = "localhost"; try { hostname = Inet6Address.getLocalHost().toString(); } catch (Exception e) { // do nothing } // set the following user agent with each request String userAgent = "org.kie.services.client (" + idGen.incrementAndGet() + " / " + hostname + ")"; HttpProtocolParams.setUserAgent(client.getParams(), userAgent); return client; } /** * This class is used in order to effect preemptive authentication in the REST request factory. */ static class PreemptiveAuth implements HttpRequestInterceptor { private final String contextId; public PreemptiveAuth(String contextId) { this.contextId = contextId; } public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE); // If no auth scheme available yet, try to initialize it preemptively if (authState.getAuthScheme() == null) { AuthScheme authScheme = (AuthScheme) context.getAttribute(contextId); CredentialsProvider credsProvider = (CredentialsProvider) context.getAttribute(ClientContext.CREDS_PROVIDER); HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST); if (authScheme != null) { Credentials creds = credsProvider.getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort())); if (creds == null) { throw new HttpException("No credentials for preemptive authentication"); } authState.update(authScheme, creds); } } } } // JMS ---------------------------------------------------------------------------------------------------------------------- public RemoteConfiguration(String deploymentId, ConnectionFactory connectionFactory, Queue ksessionQueue, Queue taskQueue, Queue responseQueue) { this.deploymentId = deploymentId; this.type = Type.JMS; setQueuesAndConnectionFactory(connectionFactory, ksessionQueue, taskQueue, responseQueue); } public void setQueuesAndConnectionFactory(ConnectionFactory connectionFactory, Queue ksessionQueue, Queue taskQueue, Queue responseQueue) { this.connectionFactory = connectionFactory; this.ksessionQueue = ksessionQueue; this.taskQueue = taskQueue; this.responseQueue = responseQueue; checkValidValues(this.connectionFactory, this.ksessionQueue, this.taskQueue, this.responseQueue); } public void checkValidJmsValues() { checkValidValues(connectionFactory, ksessionQueue, taskQueue, responseQueue); } private static void checkValidValues(ConnectionFactory connectionFactory, Queue ksessionQueue, Queue taskQueue, Queue responseQueue) throws InsufficientInfoToBuildException { if (connectionFactory == null) { throw new InsufficientInfoToBuildException("The connection factory argument may not be null."); } if (ksessionQueue == null && taskQueue == null) { throw new InsufficientInfoToBuildException("At least a ksession queue or task queue is required."); } if (responseQueue == null) { throw new InsufficientInfoToBuildException("The response queue argument may not be null."); } } public RemoteConfiguration(String deploymentId, ConnectionFactory connectionFactory, Queue ksessionQueue, Queue taskQueue, Queue responseQueue, String username, String password) { this(deploymentId, connectionFactory, ksessionQueue, taskQueue, responseQueue); setAndCheckUserNameAndPassword(username, password); } public RemoteConfiguration(String deploymentId, InitialContext context, String username, String password) { this.deploymentId = deploymentId; this.type = Type.JMS; setAndCheckUserNameAndPassword(username, password); setRemoteInitialContext(context); } public void setRemoteInitialContext(InitialContext context) { String prop = CONNECTION_FACTORY_NAME; try { if( this.connectionFactory == null ) { this.connectionFactory = (ConnectionFactory) context.lookup(prop); } prop = SESSION_QUEUE_NAME; this.ksessionQueue = (Queue) context.lookup(prop); prop = TASK_QUEUE_NAME; this.taskQueue = (Queue) context.lookup(prop); prop = RESPONSE_QUEUE_NAME; this.responseQueue = (Queue) context.lookup(prop); } catch (NamingException ne) { throw new RemoteCommunicationException("Unable to retrieve object for " + prop, ne); } checkValidValues(connectionFactory, ksessionQueue, taskQueue, responseQueue); } private void setAndCheckUserNameAndPassword(String username, String password) { if (username == null || username.trim().isEmpty()) { throw new IllegalArgumentException("The user name may not be empty or null."); } this.userName = username; if (password == null) { throw new IllegalArgumentException("The password may not be null."); } this.password = password; } /** * (Package-scoped) Getters */ String getDeploymentId() { return deploymentId; } int getSerializationType() { return jmsSerializationType; } boolean isJms() { assert type != null : "type is null!"; return (this.type == Type.JMS); } boolean isRest() { assert type != null : "type is null!"; return (this.type == Type.REST); } public enum Type { REST, JMS, CONSTRUCTOR; } // ---- // REST // ---- ClientRequestFactory getRequestFactory() { return this.requestFactory; } // ---- // JMS // ---- public String getUserName() { // assert userName != null : "username value should not be null!"; // disabled for tests return userName; } public String getPassword() { // assert password != null : "password value should not be null!"; // disabled for tests return password; } ConnectionFactory getConnectionFactory() { assert connectionFactory != null : "connectionFactory value should not be null!"; return connectionFactory; } Queue getKsessionQueue() { // assert ksessionQueue != null : "ksessionQueue value should not be null!"; // disabled for testing return ksessionQueue; } Queue getTaskQueue() { // assert taskQueue != null : "taskQueue value should not be null!"; // disabled for testing return taskQueue; } Queue getResponseQueue() { assert responseQueue != null : "responseQueue value should not be null!"; return responseQueue; } public void addJaxbClasses(Set> extraJaxbClassList) { this.extraJaxbClasses.addAll(extraJaxbClassList); } public void clearJaxbClasses() { this.extraJaxbClasses.clear(); } Set> getExtraJaxbClasses() { return this.extraJaxbClasses; } JaxbSerializationProvider getJaxbSerializationProvider() { JaxbSerializationProvider provider = new JaxbSerializationProvider(extraJaxbClasses); return provider; } public Type getType() { return this.type; } public long getTimeout() { return timeout; } public boolean getUseUssl() { return useSsl; } Long getProcessInstanceId() { return processInstanceId; } // Setters ------------------------------------------------------------------------------------------------------------------- public void setTimeout(long timeout) { this.timeout = timeout; } public void setDeploymentId(String deploymentId) { this.deploymentId = deploymentId; } public void setProcessInstanceId(long processInstanceId) { this.processInstanceId = processInstanceId; } public void setUserName(String userName) { this.userName = userName; } public void setPassword(String password) { this.password = password; } public void setExtraJaxbClasses(Set> extraJaxbClasses) { this.extraJaxbClasses = extraJaxbClasses; } public void setConnectionFactory(ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } public void setKsessionQueue(Queue ksessionQueue) { this.ksessionQueue = ksessionQueue; } public void setTaskQueue(Queue taskQueue) { this.taskQueue = taskQueue; } public void setResponseQueue(Queue responseQueue) { this.responseQueue = responseQueue; } public void setUseFormBasedAuth(boolean useFormBasedAuth) { this.useFormBasedAuth = useFormBasedAuth; } public void setUseSsl(boolean useSsl) { this.useSsl = useSsl; } // Clone --- private RemoteConfiguration(RemoteConfiguration config) { this.connectionFactory = config.connectionFactory; this.deploymentId = config.deploymentId; this.extraJaxbClasses = config.extraJaxbClasses; this.jmsSerializationType = config.jmsSerializationType; this.ksessionQueue = config.ksessionQueue; this.password = config.password; this.processInstanceId = config.processInstanceId; this.responseQueue = config.responseQueue; this.requestFactory = config.requestFactory; this.taskQueue = config.taskQueue; this.timeout = config.timeout; this.type = config.type; this.useFormBasedAuth = config.useFormBasedAuth; this.userName = config.userName; this.useSsl = config.useSsl; } public RemoteConfiguration clone() { return new RemoteConfiguration(this); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy