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

com.bronto.api.BrontoClient Maven / Gradle / Ivy

The newest version!
package com.bronto.api;

import java.util.Map;

import javax.xml.ws.BindingProvider;

import com.bronto.api.model.ApiException_Exception;
import com.bronto.api.model.BrontoSoapApiImplService;
import com.bronto.api.model.BrontoSoapPortType;
import com.bronto.api.model.SessionHeader;
import com.bronto.api.operation.AbstractObjectOperations;
import com.bronto.api.reflect.ApiReflection;
import com.bronto.api.request.BrontoClientRequest;

public class BrontoClient implements BrontoApi {
    private static final String[] SOAP_REQUEST_TIMEOUT = new String[] {
        "javax.xml.ws.client.receiveTimeout",
        "com.sun.xml.ws.request.timeout",
        "com.sun.xml.internal.ws.request.timeout"
    };
    private static final String[] SOAP_CONNECT_TIMEOUT = new String[] {
        "javax.xml.ws.client.connectionTimeout",
        "com.sun.xml.ws.connect.timeout",
        "com.sun.xml.internal.ws.connect.timeout"
    };

    private final int[] triesToBackOff;
    private final String apiToken;
    private final BrontoSoapApiImplService apiService;
    private final BrontoClientOptions options;
    private SessionHeader header;

	/**
	 * Initializes a Bronto API client using the given API token, client
	 * options, and service implementation. The API implementation service
	 * passed here will be used, regardless of any client options provided.
	 * 
	 * @param apiToken
	 *            Your Bronto API token.
	 * @param options
	 *            Any client options.
	 * @param apiService
	 *            The API implementation service to use. This is the service
	 *            that will be used regardless of any client options passed in.
	 */
	public BrontoClient(String apiToken, BrontoClientOptions options,
	        BrontoSoapApiImplService apiService) {
		this.apiToken = apiToken;
		this.options = options;
		this.apiService = apiService;

		this.triesToBackOff = new int[options.getRetryLimit()];
        header = new SessionHeader();
        for (int i = 0; i < options.getRetryLimit(); i++) {
            this.triesToBackOff[i] = options.getRetryStep() * i;
        }
    }

	/**
	 * Initializes the Bronto API client using the given API token and options.
	 * This is the constructor that should be called to use a custom WSDL URL.
	 * 
	 * @param apiToken
	 *            Your Bronto API token.
	 * @param options
	 *            Any custom client options.
	 */
	public BrontoClient(String apiToken, BrontoClientOptions options) {

		/*
		 * This is ugly, but it ensures that the API service passed to the
		 * constructor uses a non-null WSDL location. It also allows potential
		 * future unit tests create a client with a custom-mocked API service as
		 * well.
		 */
		this(apiToken, options, (options.getWsdlURL() == null)
		        ? new BrontoSoapApiImplService()
		        : new BrontoSoapApiImplService(options.getWsdlURL()));
	}

    public BrontoClient(String apiToken) {
        this(apiToken, new BrontoClientOptions());
    }
    
    protected void backOff(int retry) {
        try {
            Thread.sleep(triesToBackOff[retry]);
        } catch (InterruptedException ie) {
            throw new BrontoClientException(ie);
        }
    }

    protected void setRequestTimeout(BrontoSoapPortType port, int adjust) {
        Map requestContext = ((BindingProvider) port).getRequestContext();
        for (String requestKey : SOAP_REQUEST_TIMEOUT) {
            requestContext.put(requestKey, options.getReadTimeout() + adjust);
        }
    }

    protected void setConnectTimeout(BrontoSoapPortType port, int adjust) {
        Map requestContext = ((BindingProvider) port).getRequestContext();
        for (String connectKey : SOAP_CONNECT_TIMEOUT) {
            requestContext.put(connectKey, options.getConnectionTimeout() + adjust);
        }
    }

    protected void setTimeouts(BrontoSoapPortType port, int adjust, BrontoClientException.Recoverable timeout) {
        if (timeout == null || timeout == BrontoClientException.Recoverable.READ_TIMEOUT) {
            setRequestTimeout(port, adjust);
        } else if (timeout == null || timeout == BrontoClientException.Recoverable.CONNECTION_TIMEOUT) {
            setConnectTimeout(port, adjust);
        }
    }

    @Override
    public BrontoSoapPortType getService() {
        BrontoSoapPortType port = apiService.getBrontoSoapApiImplPort();
        setTimeouts(port, 0, null);
        return port;
    }

    @Override
    public SessionHeader getSessionHeader() {
        return header;
    }

    @Override
    public boolean isAuthenticated() {
        return header.getSessionId() != null;
    }

    @Override
    public String getToken() {
        return apiToken;
    }

    @Override
    public BrontoClientOptions getOptions() {
        return options;
    }

    @Override
    public String login() {
        return invoke(new BrontoClientRequest() {
            @Override
            public String invoke(BrontoSoapPortType service, SessionHeader header) throws Exception {
                return header.getSessionId();
            }
        });
    }

    private String internalAuth(BrontoSoapPortType service) {
        // TODO: place injectable session from observer
        try {
            String sessionId = service.login(apiToken);
            if (options.getObserver() != null) {
                options.getObserver().onSessionRefresh(this, sessionId);
            }
            header.setSessionId(sessionId);
            return sessionId;
        } catch (ApiException_Exception ex) {
            throw new BrontoClientException(ex);
        }
    }

    @Override
    public  T invoke(final BrontoClientRequest request) {
        int retry = 0;
        boolean reAuthed = false;
        BrontoClientException brontoEx = null;
        BrontoSoapPortType port = getService();
        do {
            try {
                if (!isAuthenticated()) {
                    internalAuth(port);
                }
                return request.invoke(port, getSessionHeader());
            } catch (Exception e) {
                // TODO: fix this... error handler logic should be an implemented strategy
                BrontoWriteException writeEx = null;
                if (e instanceof BrontoWriteException) {
                    writeEx = (BrontoWriteException) e;
                    brontoEx = writeEx;
                } else {
                    brontoEx = new BrontoClientException(e);
                }
                if (brontoEx.isInvalidSession()) {
                    if (reAuthed) {
                        retry++;
                    } else {
                        reAuthed = true;
                        internalAuth(port);
                    }
                } else if (brontoEx.isRecoverable()) {
                    // Received a timeout on a write, bail
                    if (brontoEx.isTimeout() && writeEx != null) {
                        throw brontoEx;
                    }
                    retry++;
                    if (retry < options.getRetryLimit()) {
                        if (brontoEx.isTimeout()) {
                            // Extend wait on a read timeout
                            setTimeouts(port, triesToBackOff[retry], brontoEx.getRecoverable());
                        } else {
                            // Service is unresponsive, delay and try again
                            backOff(retry);
                        }
                    } else if (options.getRetryer() != null && writeEx != null) {
                        options.getRetryer().storeAttempt(writeEx.getWriteContext());
                    }
                } else {
                    throw brontoEx;
                }
            }
        } while (retry < options.getRetryLimit());
        throw new RetryLimitExceededException(brontoEx);
    }

    @Override
    public  ObjectOperations transport(final Class clazz) {
        return new AbstractObjectOperations(clazz, this) {
            @Override
            public ApiReflection getSupportedWriteOperations() {
                return new ApiReflection(clazz, "add", "update", "delete");
            }
        };
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy