com.microsoft.bingads.internal.restful.RestfulServiceClient Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of microsoft.bingads Show documentation
Show all versions of microsoft.bingads Show documentation
The Bing Ads Java SDK is a library improving developer experience when working with the Bing Ads services by providing high-level access to features such as Bulk API, OAuth Authorization and SOAP API.
package com.microsoft.bingads.internal.restful;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.BiFunction;
import java.util.function.Function;
import jakarta.ws.rs.client.AsyncInvoker;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.InvocationCallback;
import jakarta.ws.rs.client.WebTarget;
import jakarta.xml.ws.AsyncHandler;
import jakarta.xml.ws.Response;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.microsoft.bingads.ApiEnvironment;
import com.microsoft.bingads.AsyncCallback;
import com.microsoft.bingads.GlobalSettings;
import com.microsoft.bingads.InternalException;
import com.microsoft.bingads.internal.ResultFuture;
import com.microsoft.bingads.internal.ServiceFactoryImpl;
import com.microsoft.bingads.internal.ServiceUtils;
import com.microsoft.bingads.internal.restful.adaptor.AdaptorUtil;
class ResponseInfo {
private TResponse response;
public TResponse getResponse() {
return response;
}
public void setResponse(TResponse response) {
this.response = response;
}
private TFaultDetail faultDetail;
public TFaultDetail getFaultDetail() {
return faultDetail;
}
public void setFaultDetail(TFaultDetail faultDetail) {
this.faultDetail = faultDetail;
}
}
class RestfulServiceClient {
protected static final String HttpPost = "POST";
protected static final String HttpPut = "PUT";
protected static final String HttpDelete = "DELETE";
protected Map headers;
protected ApiEnvironment environment;
private Class> serviceInterface;
private static final ConcurrentHashMap, Calendar> retryAfter = new ConcurrentHashMap, Calendar>();
private static final ArrayList statusCodesForApplicationFault = new ArrayList(Arrays.asList(400, 401, 403, 429, 500));
private static final ArrayList statusCodesForSwitchToSoap = new ArrayList(Arrays.asList(404, 501));
private static final String userAgent = getUserAgent();
private boolean enableFallbackToSoap;
static {
com.microsoft.bingads.internal.restful.adaptor.generated.campaignmanagement.arrayOfTypes.AddMixInForArrayOfTypes.AddMixInForArrayOfTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.campaignmanagement.enums.AddMixInForEnumTypes.AddMixInForEnumTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.campaignmanagement.enums.AddMixInForComplexTypesWithEnumList.AddMixInForComplexTypesWithEnumList();
com.microsoft.bingads.internal.restful.adaptor.generated.campaignmanagement.polymorphicTypes.AddMixInForPolymorphicTypes.AddMixInForPolymorphicTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.bulk.arrayOfTypes.AddMixInForArrayOfTypes.AddMixInForArrayOfTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.bulk.enums.AddMixInForEnumTypes.AddMixInForEnumTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.bulk.enums.AddMixInForComplexTypesWithEnumList.AddMixInForComplexTypesWithEnumList();
com.microsoft.bingads.internal.restful.adaptor.generated.bulk.polymorphicTypes.AddMixInForPolymorphicTypes.AddMixInForPolymorphicTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.reporting.arrayOfTypes.AddMixInForArrayOfTypes.AddMixInForArrayOfTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.reporting.enums.AddMixInForEnumTypes.AddMixInForEnumTypes();
com.microsoft.bingads.internal.restful.adaptor.generated.reporting.enums.AddMixInForComplexTypesWithEnumList.AddMixInForComplexTypesWithEnumList();
com.microsoft.bingads.internal.restful.adaptor.generated.reporting.polymorphicTypes.AddMixInForPolymorphicTypes.AddMixInForPolymorphicTypes();
}
protected RestfulServiceClient(Map headers, ApiEnvironment env, Class> serviceInterface) {
this.headers = headers;
this.environment = env;
this.serviceInterface = serviceInterface;
this.enableFallbackToSoap = ServiceUtils.getFallbackFlag();
}
private static String getUserAgent() {
String actualUserAgent = "BingAdsSDKJava.RestApi";
String clientName = ServiceUtils.getClientName();
if (clientName != null) {
actualUserAgent += "." + clientName;
}
actualUserAgent += "/" + ServiceFactoryImpl.VERSION;
String javaVersion = System.getProperty("java.version");
if (javaVersion.matches("\\d+[\\d|\\.|\\_]*\\d+")) {
actualUserAgent += "_" + javaVersion;
}
return actualUserAgent;
}
private Invocation.Builder createRequestBuilder(String entityEndpointPath) {
WebTarget targetForService = GlobalSettings.getHttpClientProvider().get(serviceInterface, environment);
Invocation.Builder builder;
// some implementations, for example CXF, may not be thread safe
synchronized (targetForService) {
builder = targetForService.path(entityEndpointPath).request();
}
for (Map.Entry entry : headers.entrySet()) {
String name = entry.getKey();
String value = entry.getValue();
if ("AuthenticationToken".equals(name)) {
builder.header("Authorization", "Bearer " + value);
} else {
builder.header(name, value);
}
}
builder.header("User-Agent", userAgent);
return builder;
}
private void SetRetryAfterTime(String value)
{
int delta;
try {
delta = Integer.parseInt(value);
} catch (NumberFormatException e) {
delta = 3600;
}
Calendar cal = Calendar.getInstance();
cal.add(Calendar.SECOND, delta);
retryAfter.put(serviceInterface, cal);
}
protected ResponseInfo getResponseInfo(TRequest request, String entityEndpoint, String verb, Class responseClass, Class faultDetailClass) {
if (retryAfter.get(serviceInterface) != null && enableFallbackToSoap
&& Calendar.getInstance().before(retryAfter.get(serviceInterface))) {
return null;
}
String jsonRequest;
try {
jsonRequest = AdaptorUtil.mapper.writeValueAsString(request);
} catch (JsonProcessingException e) {
throw new InternalException(e);
}
Invocation.Builder builder = createRequestBuilder(entityEndpoint);
jakarta.ws.rs.core.Response httpResponse;
switch (verb) {
case HttpPost:
httpResponse = builder.post(Entity.json(jsonRequest));
break;
case HttpPut:
httpResponse = builder.put(Entity.json(jsonRequest));
break;
case HttpDelete:
httpResponse = builder.method("DELETE", Entity.json(jsonRequest));
break;
default:
throw new InternalException("Unknown HTTP verb: " + verb);
}
int statusCode = httpResponse.getStatus();
if (statusCodesForSwitchToSoap.contains(statusCode) && enableFallbackToSoap) {
SetRetryAfterTime(httpResponse.getHeaderString("RetryAfter"));
httpResponse.close();
return null;
}
ResponseInfo responseInfo = new ResponseInfo<>();
if (statusCodesForApplicationFault.contains(statusCode)) {
TFaultDetail faultDetail = parseStream(httpResponse.readEntity(InputStream.class), faultDetailClass);
responseInfo.setFaultDetail(faultDetail);
return responseInfo;
}
if (statusCode >= 200 && statusCode <= 299) {
TResponse response = parseStream(httpResponse.readEntity(InputStream.class), responseClass);
responseInfo.setResponse(response);
return responseInfo;
}
throw new InternalException("Got unexpected status code " + statusCode + " with response content: " + httpResponse.readEntity(String.class));
}
protected String getFaultMessage(String trackingId) {
return "An error occurred while executing the request. Please check the exception Detail property for more information. TrackingId: " + trackingId;
}
protected T parseStream(InputStream stream, Class resultClass) {
try {
return AdaptorUtil.mapper.readValue(AdaptorUtil.mapper.getFactory().createParser(stream), resultClass);
} catch (IOException e) {
throw new InternalException(e);
} finally {
try {
stream.close();
} catch (IOException e) {
throw new InternalException(e);
}
}
}
protected Response processRequestAsync(TRequest request, String entityEndpoint, String verb, Class respClass, Class faultDetailClass, Function getException, BiFunction, Future>> soapMethod, AsyncHandler handler) {
ResponseFuture responseFuture = new ResponseFuture();
if (handler != null) {
responseFuture.setHandler(handler);
}
Calendar retryAfterTime = retryAfter.get(serviceInterface);
if (retryAfterTime != null && Calendar.getInstance().before(retryAfterTime)) {
soapMethod.apply(request, new AsyncHandler() {
@Override
public void handleResponse(Response soapResponse) {
try {
responseFuture.setContext(soapResponse.getContext());
responseFuture.setResult(soapResponse.get());
} catch (Throwable e) {
responseFuture.setException(e);
}
}
});
return responseFuture;
}
String jsonRequest;
try {
jsonRequest = AdaptorUtil.mapper.writeValueAsString(request);
} catch (JsonProcessingException e) {
throw new InternalException(e);
}
InvocationCallback invocationCallback = new InvocationCallback() {
@Override
public void completed(jakarta.ws.rs.core.Response httpResponse) {
try {
int statusCode = httpResponse.getStatus();
if (statusCodesForSwitchToSoap.contains(statusCode) && enableFallbackToSoap) {
httpResponse.close();
SetRetryAfterTime(httpResponse.getHeaderString("RetryAfter"));
soapMethod.apply(request, new AsyncHandler() {
@Override
public void handleResponse(Response soapResponse) {
try {
responseFuture.setContext(soapResponse.getContext());
responseFuture.setResult(soapResponse.get());
} catch (Throwable e) {
responseFuture.setException(e);
}
}
});
return;
}
String trackingIdHeader = httpResponse.getHeaderString(ServiceUtils.TRACKING_HEADER_NAME);
if (statusCodesForApplicationFault.contains(statusCode)) {
TFaultDetail faultDetail = parseStream(httpResponse.readEntity(InputStream.class), faultDetailClass);
Exception exception = getException.apply(faultDetail);
responseFuture.setException(exception);
return;
}
Map context = new HashMap<>();
context.put(ServiceUtils.TRACKING_KEY, trackingIdHeader);
responseFuture.setContext(context);
if (statusCode >= 200 && statusCode <= 299) {
TResponse responseObj = parseStream(httpResponse.readEntity(InputStream.class), respClass);
responseFuture.setResult(responseObj);
return;
}
throw new InternalException("Got unexpected status code " + statusCode + " with response content: " + httpResponse.readEntity(String.class));
} catch (Throwable e) {
responseFuture.setException(e);
}
}
@Override
public void failed(Throwable throwable) {
responseFuture.setException(throwable);
}
};
AsyncInvoker asyncInvoker = createRequestBuilder(entityEndpoint).async();
switch (verb) {
case HttpPost:
asyncInvoker.post(Entity.json(jsonRequest), invocationCallback);
break;
case HttpPut:
asyncInvoker.put(Entity.json(jsonRequest), invocationCallback);
break;
case HttpDelete:
asyncInvoker.method("DELETE", Entity.json(jsonRequest), invocationCallback);
break;
default:
throw new InternalException("Unknown HTTP verb: " + verb);
}
return responseFuture;
}
static class ResponseFuture extends ResultFuture implements Response {
private Map context = null;
public ResponseFuture() {
super(null);
}
public void setHandler(AsyncHandler handler) {
super.handler = new AsyncCallback() {
@Override
public void onCompleted(Future result) {
handler.handleResponse(new Response() {
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return result.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return result.isCancelled();
}
@Override
public boolean isDone() {
return result.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return result.get();
}
@Override
public T get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return result.get(timeout, unit);
}
@Override
public Map getContext() {
return context;
}
});
}
};
}
@Override
public Map getContext() {
return context;
}
void setContext(Map context) {
this.context = context;
}
}
}