
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