Package com.betfair.cougar.client

Source Code of com.betfair.cougar.client.AbstractHttpExecutable$CougarHttpResponse

/*
* Copyright 2013, The Sporting Exchange Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.betfair.cougar.client;

import com.betfair.cougar.api.ExecutionContext;
import com.betfair.cougar.api.security.IdentityToken;
import com.betfair.cougar.api.security.IdentityTokenResolver;
import com.betfair.cougar.client.exception.ExceptionTransformer;
import com.betfair.cougar.client.query.QueryStringGeneratorFactory;
import com.betfair.cougar.core.api.OperationBindingDescriptor;
import com.betfair.cougar.core.api.client.AbstractClientTransport;
import com.betfair.cougar.core.api.client.ExceptionFactory;
import com.betfair.cougar.core.api.client.TransportMetrics;
import com.betfair.cougar.core.api.ev.*;
import com.betfair.cougar.core.api.exception.*;
import com.betfair.cougar.core.api.transcription.EnumDerialisationException;
import com.betfair.cougar.core.api.transcription.EnumUtils;
import com.betfair.cougar.core.api.transcription.Parameter;
import com.betfair.cougar.marshalling.api.databinding.DataBindingFactory;
import com.betfair.cougar.transport.api.protocol.http.HttpServiceBindingDescriptor;
import com.betfair.cougar.transport.api.protocol.http.rescript.RescriptOperationBindingDescriptor;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;

import java.io.IOException;
import java.io.InputStream;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPInputStream;

/**
* Base executable which holds the logic of interacting with Execution Venue.
* Note this marshals the request and un-marshals the response using the provided Transformable
*/
@ManagedResource
public abstract class AbstractHttpExecutable<HR> extends AbstractClientTransport {
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractHttpExecutable.class);

    private static final String REMOTE_ADDRESS_DESCRIPTION = "Remote Service Address";
    private static final String SERVICE_CONTEXT_PATH_DESCRIPTION = "Service Context Path";

    protected static final int MAX_TOTAL_CONNECTIONS = 128;
    protected static final String CONTENT_TYPE = "application/json";
    private static final String UNMARSHALL_ENCODING = "utf-8";
    protected static final String DEFAULT_REQUEST_UUID_HEADER = "X-UUID";
    protected static final int DEFAULT_HTTPS_PORT = 443;
    protected static final int DEFAULT_HTTP_PORT = 80;

    private final Map<OperationKey, OperationBindingDescriptor> descriptorMap =
            new HashMap<OperationKey, OperationBindingDescriptor>();
    private final HttpServiceBindingDescriptor serviceBindingDescriptor;
    private final MessageBuilder messageBuilder = new MessageBuilder();

    private AtomicReference<String> remoteAddressRef = new AtomicReference<String>("NOT ASSIGNED");
    private DataBindingFactory dataBindingFactory;
    private IdentityTokenResolver<HR, HR, X509Certificate[]> identityTokenResolver;
    private QueryStringGeneratorFactory queryStringGeneratorFactory;
    private ExceptionTransformer exceptionTransformer;
    private ExceptionFactory exceptionFactory;
    protected CougarRequestFactory<HR> requestFactory;

    protected boolean transportSSLEnabled;
    protected boolean hostnameVerificationDisabled;
    private boolean gzipCompressionEnabled = true;
    protected boolean hardFailEnumDeserialisation;

    //Path to the keystore
    protected Resource httpsKeystore;
    //Password for that keystore
    protected String httpsKeyPassword;
    protected String httpsKeystoreType = KeyStore.getDefaultType();


    //The trust store fields are only necessary for 2 way SSL
    //Path to the trusted certificate store
    protected Resource httpsTruststore;
    //Trust store's password
    protected String httpsTrustPassword;
    protected String httpsTruststoreType = KeyStore.getDefaultType();

    // http timeout, -1 = not set
    protected int connectTimeout = -1;
    protected int idleTimeout = -1;


    public AbstractHttpExecutable(final HttpServiceBindingDescriptor bindingDescriptor,
                                  CougarRequestFactory<HR> requestFactory) {
        this.serviceBindingDescriptor = bindingDescriptor;
        this.requestFactory = requestFactory;
    }

    protected int extractPortFromAddress() throws IOException {
        String remoteAddress = remoteAddressRef.get();
        if (remoteAddress == null || remoteAddress.equals("NOT ASSIGNED")) {
            throw new IllegalArgumentException("Remote address not set!");
        }
        URL url = new URL(remoteAddress);
        return url.getPort();
    }


    public void init() throws Exception {
        final OperationBindingDescriptor[] operationBindings = serviceBindingDescriptor.getOperationBindings();

        for (OperationBindingDescriptor binding : operationBindings) {
            descriptorMap.put(binding.getOperationKey(), binding);
        }

        requestFactory.setGzipCompressionEnabled(gzipCompressionEnabled);
    }


    @Override
    public void execute(final ExecutionContext ctx, final OperationKey key, final Object[] args,
                        final ExecutionObserver obs, final ExecutionVenue executionVenue, final TimeConstraints timeConstraints) {

        final OperationDefinition operationDefinition = executionVenue.getOperationDefinition(key);

        final Parameter[] parameters = operationDefinition.getParameters();
        final RescriptOperationBindingDescriptor operationBinding =
                (RescriptOperationBindingDescriptor) descriptorMap.get(key.getLocalKey());

        final Message message = messageBuilder.build(args, parameters, operationBinding);
        String contextPath = serviceBindingDescriptor.getServiceContextPath();
        String remoteAddress = remoteAddressRef.get();
        if (remoteAddress.endsWith("/") && contextPath.startsWith("/")) {
            contextPath = contextPath.substring(1);
        }
        final String uri = remoteAddress + contextPath +"v"+ serviceBindingDescriptor.getServiceVersion().getMajor()
                + operationBinding.getURI();
        final String queryString = queryStringGeneratorFactory.getQueryStringGenerator()
                .generate(message.getQueryParmMap());
        final String httpMethod = operationBinding.getHttpMethod();

        // create http request

        HR request = requestFactory.create(uri + queryString, httpMethod, message,
                dataBindingFactory.getMarshaller(), CONTENT_TYPE, ctx, timeConstraints);

        Exception exception = null;
        Object result = null;
        InputStream inputStream = null;

        if (identityTokenResolver != null && getIdentityResolver() != null &&
                identityTokenResolver.isRewriteSupported()) {
            final List<IdentityToken> identityTokens = getIdentityResolver().tokenise(ctx.getIdentity());
            identityTokenResolver.rewrite(identityTokens, request);
            if (LOGGER.isDebugEnabled()) {
                StringBuilder sb = new StringBuilder();
                for (IdentityToken it: identityTokens) {
                    if (sb.length() > 0) {
                        sb.append(",");
                    }
                    sb.append(it.getName()).append("=").append(it.getValue());
                }
                LOGGER.info("Rewrote tokens " + sb + " to http request");
            }
        }

        // Send Request

        sendRequest(request, obs, operationDefinition);

    }

    protected interface CougarHttpResponse {

        InputStream getEntity() throws IOException;

        List<String> getContentEncoding();

        int getResponseStatus();

        String getServerIdentity();

        long getResponseSize();
    }

    protected void processResponse(final CougarHttpResponse response,
                                   final ExecutionObserver obs,
                                   final OperationDefinition definition) {
        Exception exception = null;
        Object result = null;
        InputStream inputStream = null;
        final RescriptOperationBindingDescriptor operationBinding = (RescriptOperationBindingDescriptor)
                descriptorMap.get(definition.getOperationKey().getLocalKey());
        try {
            if (response.getEntity() == null) {
                exception = new CougarClientException(ServerFaultCode.RemoteCougarCommunicationFailure,
                        "No response returned by server");
            } else {
                inputStream = response.getEntity();
                if (gzipCompressionEnabled && !gzipHandledByTransport()) {
                    List<String> codecs = response.getContentEncoding();
                    for (String codecName : codecs) {
                        if ("gzip".equals(codecName) || "x-gzip".equals(codecName)) {
                            inputStream = new GZIPInputStream(inputStream);
                        }
                    }
                }
                EnumUtils.setHardFailureForThisThread(hardFailEnumDeserialisation);
                if (response.getResponseStatus() == HttpStatus.SC_OK) {
                    if (!operationBinding.voidReturnType()) {
                        try {
                            result = dataBindingFactory.getUnMarshaller().unmarshall(inputStream,
                                    definition.getReturnType(), UNMARSHALL_ENCODING, true);
                        }
                        catch (Exception e2) {
                            throw clientException(response, e2);
                        }
                    }
                } else if (response.getResponseStatus() == HttpStatus.SC_NOT_FOUND) {
                    // IN this case, we know there will be no exception body, so just stick on a new Exception
                    exception = new CougarClientException(ServerFaultCode.NoSuchService,
                            "The server did not recognise the URL.");
                } else {
                    try {
                        exception = exceptionTransformer.convert(inputStream, exceptionFactory,
                                response.getResponseStatus());
                    } catch (Exception e2) {
                        throw clientException(response, e2);
                    }
                }
            }
        } catch (final Exception e) {
            if (e instanceof CougarClientException) {
                exception = e;
            }
            else if (e instanceof EnumDerialisationException) {
                exception = new CougarClientException(CougarMarshallingException.unmarshallingException("json","Enum failure",e,true));
            }
            else if (e instanceof CougarException) {
                exception = new CougarClientException((CougarException)e);
            }
            else {
                exception = new CougarClientException(ServerFaultCode.RemoteCougarCommunicationFailure,
                        "Exception occurred in Client: " + e.getMessage(), e);
            }
        } finally {
            IOUtils.closeQuietly(inputStream);
        }

        if (exception == null) {
            obs.onResult(new ClientExecutionResult(result, response.getResponseSize()));
        } else {
            obs.onResult(new ClientExecutionResult(exception, response.getResponseSize()));
        }
        // end request callback
    }

    private CougarClientException clientException(CougarHttpResponse response, Exception e) {
        if (e instanceof CougarClientException) {
            return (CougarClientException)e;
        }
        if (e instanceof CougarException) {
            return new CougarClientException(((CougarException)e).getServerFaultCode(),e.getMessage(),e, !isDefinitelyCougarResponse(response));
        }
        ServerFaultCode serverFaultCode = ServerFaultCode.RemoteCougarCommunicationFailure;
        String message = "Unknown error communicating with remote Cougar service";
        switch (response.getResponseStatus()) {
            case HttpStatus.SC_NOT_FOUND:
                serverFaultCode = ServerFaultCode.NoSuchOperation;
                message = "Service not found";
        }
        return new CougarClientException(serverFaultCode, message, e, !isDefinitelyCougarResponse(response));
    }

    protected void processException(ExecutionObserver obs, Throwable t, String url) {
        ServerFaultCode serverFaultCode = ServerFaultCode.RemoteCougarCommunicationFailure;
        if (t instanceof SocketTimeoutException) {
            serverFaultCode = ServerFaultCode.Timeout;
        }
        processException(obs, t, url, serverFaultCode);
    }

    protected void processException(ExecutionObserver obs, Throwable t, String url, ServerFaultCode serverFaultCode) {
        Exception exception = new CougarClientException(serverFaultCode,
                "Exception occurred in Client: " + t.getMessage()+": "+url, t);
        obs.onResult(new ClientExecutionResult(exception, 0));
    }

    protected boolean gzipHandledByTransport() {
        return false;
    }

    protected abstract void sendRequest(HR request, ExecutionObserver obs,
                                        OperationDefinition operationDefinition);


    /**
     * has the responding server identified itself as Cougar
     *
     * Note that due to legacy servers/network infrastructure a 'false negative' is possible.
     * in other words although a 'true' response confirms that the server has identified itself Cougar,
     * a 'false' does not confirm that a server IS NOT Cougar. The cougar header could have been stripped out
     * by network infrastructure, or the server could still be a legacy Cougar which does not provide this header.
     *
     * @param response http response
     * @return boolean has the responding server identified itself as Cougar
     */
    private boolean isDefinitelyCougarResponse(CougarHttpResponse response) {
        String ident = response.getServerIdentity();
        if (ident != null && ident.contains("Cougar 2")) {
            return true;
        }
        return false;
    }

    @ManagedAttribute(description = REMOTE_ADDRESS_DESCRIPTION)
    public void setRemoteAddress(final String host) {
        this.remoteAddressRef.set(host);
    }

    @ManagedAttribute(description = REMOTE_ADDRESS_DESCRIPTION)
    public String getRemoteAddress() {
        return remoteAddressRef.get();
    }

    @ManagedAttribute(description = SERVICE_CONTEXT_PATH_DESCRIPTION)
    public String getServiceContextPath() {
        if (serviceBindingDescriptor != null) {
            return serviceBindingDescriptor.getServiceContextPath();
        } else {
            return "";
        }
    }

    /**
     * Return some information about the current transport.
     *
     * @return the transport metrics
     */
    public abstract TransportMetrics getTransportMetrics();

    public void setDataBindingFactory(DataBindingFactory dataBindingFactory) {
        this.dataBindingFactory = dataBindingFactory;
    }

    public void setQueryStringGeneratorFactory(QueryStringGeneratorFactory queryStringGeneratorFactory) {
        this.queryStringGeneratorFactory = queryStringGeneratorFactory;
    }

    public void setIdentityTokenResolver(IdentityTokenResolver<HR, HR, X509Certificate[]> identityTokenResolver) {
        this.identityTokenResolver = identityTokenResolver;
    }

    public void setExceptionTransformer(ExceptionTransformer exceptionTransformer) {
        this.exceptionTransformer = exceptionTransformer;
    }

    public void setExceptionFactory(ExceptionFactory exceptionFactory) {
        this.exceptionFactory = exceptionFactory;
    }

    public void setTransportSSLEnabled(boolean transportSSLEnabled) {
        this.transportSSLEnabled = transportSSLEnabled;
    }

    public void setHttpsKeyPassword(String httpsKeyPassword) {
        this.httpsKeyPassword = httpsKeyPassword;
    }

    public void setHttpsKeystore(Resource httpsKeystore) {
        this.httpsKeystore = httpsKeystore;
    }

    public void setHttpsKeystoreType(String httpsKeystoreType) {
        this.httpsKeystoreType = httpsKeystoreType;
    }

    public void setHttpsTrustPassword(String httpsTrustPassword) {
        this.httpsTrustPassword = httpsTrustPassword;
    }

    public void setHttpsTruststore(Resource httpsTruststore) {
        this.httpsTruststore = httpsTruststore;
    }

    public void setHttpsTruststoreType(String httpsTruststoreType) {
        this.httpsTruststoreType = httpsTruststoreType;
    }

    public void setConnectTimeout(int timeout) {
        this.connectTimeout = timeout;
    }

    public void setIdleTimeout(int idleTimeout) {
        this.idleTimeout = idleTimeout;
    }

    public void setHostnameVerificationDisabled(final boolean hostnameVerificationDisabled) {
        this.hostnameVerificationDisabled = hostnameVerificationDisabled;
    }

    public void setRequestFactory(CougarRequestFactory<HR> requestFactory) {
        this.requestFactory = requestFactory;
    }

    @ManagedAttribute
    public int getIdleTimeout() {
        return idleTimeout;
    }

    @ManagedAttribute
    public int getConnectTimeout() {
        return connectTimeout;
    }

    @ManagedAttribute
    public String getHttpsTruststoreType() {
        return httpsTruststoreType;
    }

    @ManagedAttribute
    public Resource getHttpsTruststore() {
        return httpsTruststore;
    }

    @ManagedAttribute
    public String getHttpsKeystoreType() {
        return httpsKeystoreType;
    }

    @ManagedAttribute
    public Resource getHttpsKeystore() {
        return httpsKeystore;
    }

    @ManagedAttribute
    public boolean isHostnameVerificationDisabled() {
        return hostnameVerificationDisabled;
    }

    @ManagedAttribute
    public boolean isTransportSSLEnabled() {
        return transportSSLEnabled;
    }

    @ManagedAttribute
    public boolean isHardFailEnumDeserialisation() {
        return hardFailEnumDeserialisation;
    }

    public void setHardFailEnumDeserialisation(boolean hardFailEnumDeserialisation) {
        this.hardFailEnumDeserialisation = hardFailEnumDeserialisation;
    }

    @ManagedAttribute
    public boolean isGzipCompressionEnabled() {
        return gzipCompressionEnabled;
    }

    @ManagedAttribute
    public void setGzipCompressionEnabled(boolean gzipCompressionEnabled) {
        this.gzipCompressionEnabled = gzipCompressionEnabled;
    }
}
TOP

Related Classes of com.betfair.cougar.client.AbstractHttpExecutable$CougarHttpResponse

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.