Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.symphony.bdk.http.jersey2.ApiClientJersey2 Maven / Gradle / Ivy
package com.symphony.bdk.http.jersey2;
import com.symphony.bdk.http.api.ApiClient;
import com.symphony.bdk.http.api.ApiClientBodyPart;
import com.symphony.bdk.http.api.ApiException;
import com.symphony.bdk.http.api.ApiResponse;
import com.symphony.bdk.http.api.Pair;
import com.symphony.bdk.http.api.auth.Authentication;
import com.symphony.bdk.http.api.tracing.DistributedTracingContext;
import com.symphony.bdk.http.api.util.TypeReference;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.ProcessingException;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.Invocation;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import org.apache.http.NoHttpResponseException;
import org.apache.http.conn.ConnectTimeoutException;
import org.apiguardian.api.API;
import org.glassfish.jersey.media.multipart.FormDataBodyPart;
import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartMediaTypes;
import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import static com.symphony.bdk.http.api.util.ApiUtils.isCollectionOfFiles;
/**
* Jersey2 implementation for the {@link ApiClient} interface called by generated code.
*/
@API(status = API.Status.STABLE)
public class ApiClientJersey2 implements ApiClient {
protected Client httpClient;
protected String basePath;
protected Map defaultHeaderMap;
protected String tempFolderPath;
protected Map authentications;
protected List enforcedAuthenticationSchemes;
public ApiClientJersey2(final Client httpClient, String basePath, Map defaultHeaders,
String temporaryFolderPath) {
this.httpClient = httpClient;
this.basePath = basePath;
this.defaultHeaderMap = new HashMap<>(defaultHeaders);
this.tempFolderPath = temporaryFolderPath;
this.authentications = new HashMap<>();
this.enforcedAuthenticationSchemes = new ArrayList<>();
}
/**
* {@inheritDoc}
*/
@Override
public ApiResponse invokeAPI(
final String path,
final String method,
final List queryParams,
final Object body,
final Map headerParams,
final Map cookieParams,
final Map formParams,
final String accept,
final String contentType,
final String[] authNames,
final TypeReference returnType
) throws ApiException {
// Not using `.target(this.basePath).path(path)` below,
// to support (constant) query string in `path`, e.g. "/posts?draft=1"
WebTarget target = httpClient.target(this.basePath + path);
this.updateParamsForAuth(authNames, headerParams);
if (queryParams != null) {
for (Pair queryParam : queryParams) {
if (queryParam.getValue() != null) {
target = target.queryParam(queryParam.getName(), escapeString(queryParam.getValue()));
}
}
}
Invocation.Builder invocationBuilder = target.request().accept(accept);
boolean clearTraceId = false;
if (!DistributedTracingContext.hasTraceId()) {
DistributedTracingContext.setTraceId();
clearTraceId = true;
}
invocationBuilder =
invocationBuilder.header(DistributedTracingContext.TRACE_ID, DistributedTracingContext.getTraceId());
if (headerParams != null) {
for (Entry entry : headerParams.entrySet()) {
String value = entry.getValue();
if (value != null) {
invocationBuilder = invocationBuilder.header(entry.getKey(), value);
}
}
}
if (cookieParams != null) {
for (Entry entry : cookieParams.entrySet()) {
String value = entry.getValue();
if (value != null) {
invocationBuilder = invocationBuilder.cookie(entry.getKey(), value);
}
}
}
// apply default headers, that can be set from config.yaml
for (Entry entry : defaultHeaderMap.entrySet()) {
String key = entry.getKey();
if (!headerParams.containsKey(key)) {
String value = entry.getValue();
if (value != null) {
invocationBuilder = invocationBuilder.header(key, value);
}
}
}
// https://eclipse-ee4j.github.io/jersey.github.io/documentation/latest/client.html#connectors.warning
// by setting this header now instead of org.glassfish.jersey.media.multipart.internal.MultiPartWriter
// we avoid the warning
if (contentType.startsWith(MediaType.MULTIPART_FORM_DATA)) {
invocationBuilder.header("MIME-Version", "1.0");
}
Entity> entity =
(body == null && formParams == null) ? Entity.json("") : this.serialize(body, formParams, contentType);
try (Response response = getResponse(invocationBuilder, method, entity)) {
int statusCode = response.getStatusInfo().getStatusCode();
Map> responseHeaders = buildResponseHeaders(response);
GenericType genericReturnType = null;
if (returnType != null) {
genericReturnType = new GenericType<>(returnType.getType());
}
if (response.getStatus() == Response.Status.NO_CONTENT.getStatusCode()) {
return new ApiResponse<>(statusCode, responseHeaders);
} else if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) {
if (genericReturnType == null) {
return new ApiResponse<>(statusCode, responseHeaders);
} else {
return new ApiResponse<>(statusCode, responseHeaders, deserialize(response, genericReturnType));
}
} else {
String message = "error";
String respBody = null;
if (response.hasEntity()) {
try {
respBody = String.valueOf(response.readEntity(String.class));
message = respBody;
} catch (RuntimeException e) {
// ignored if we cannot read the response body
}
}
throw new ApiException(
response.getStatus(),
message,
buildResponseHeaders(response),
respBody);
}
} finally {
if (clearTraceId) {
DistributedTracingContext.clear();
}
}
}
private Response getResponse(Invocation.Builder invocationBuilder, String method, Entity> entity)
throws ApiException {
try {
switch (method) {
case HttpMethod.GET:
return invocationBuilder.get();
case HttpMethod.POST:
return invocationBuilder.post(entity);
case HttpMethod.PUT:
return invocationBuilder.put(entity);
case HttpMethod.DELETE:
return invocationBuilder.method(HttpMethod.DELETE, entity);
case HttpMethod.PATCH:
return invocationBuilder.method(HttpMethod.PATCH, entity);
case HttpMethod.HEAD:
return invocationBuilder.head();
case HttpMethod.OPTIONS:
return invocationBuilder.options();
case "TRACE":
return invocationBuilder.trace();
default:
throw new ApiException(500, "unknown method type " + method);
}
} catch (ProcessingException e) {
if (e.getCause() instanceof ConnectTimeoutException) {
throw new ProcessingException(new SocketTimeoutException(e.getCause().getMessage()));
}
else if (e.getCause() instanceof NoHttpResponseException) {
// ensures that it will be caught later in the retry strategy
throw new ProcessingException(new SocketException(e.getCause().getMessage()));
}
else {
throw e;
}
}
}
@Override
public String getBasePath() {
return basePath;
}
/**
* {@inheritDoc}
*/
@Override
public String parameterToString(Object param) {
if (param == null) {
return "";
} else if (param instanceof Collection) {
StringBuilder b = new StringBuilder();
for (Object o : (Collection>) param) {
if (b.length() > 0) {
b.append(',');
}
b.append(o);
}
return b.toString();
} else {
return String.valueOf(param);
}
}
/**
* {@inheritDoc}
*/
@Override
public List parameterToPairs(String collectionFormat, String name, Object value) {
List params = new ArrayList<>();
// preconditions
if (name == null || name.isEmpty() || value == null) {
return params;
}
Collection> valueCollection;
if (value instanceof Collection) {
valueCollection = (Collection) value;
} else {
params.add(new Pair(name, parameterToString(value)));
return params;
}
if (valueCollection.isEmpty()) {
return params;
}
// get the collection format (default: csv)
String format = (collectionFormat == null || collectionFormat.isEmpty() ? "csv" : collectionFormat);
// create the params based on the collection format
if ("multi".equals(format)) {
for (Object item : valueCollection) {
params.add(new Pair(name, parameterToString(item)));
}
return params;
}
String delimiter = ",";
switch (format) {
case "csv":
delimiter = ",";
break;
case "ssv":
delimiter = " ";
break;
case "tsv":
delimiter = "\t";
break;
case "pipes":
delimiter = "|";
break;
}
StringBuilder sb = new StringBuilder();
for (Object item : valueCollection) {
sb.append(delimiter);
sb.append(parameterToString(item));
}
params.add(new Pair(name, sb.substring(1)));
return params;
}
/**
* {@inheritDoc}
*/
@Override
public String selectHeaderAccept(String[] accepts) {
if (accepts.length == 0) {
return null;
}
for (String accept : accepts) {
if (isJsonMime(accept)) {
return accept;
}
}
return String.join(",", accepts);
}
/**
* {@inheritDoc}
*/
@Override
public String selectHeaderContentType(String[] contentTypes) {
if (contentTypes.length == 0) {
return "application/json";
}
for (String contentType : contentTypes) {
if (isJsonMime(contentType)) {
return contentType;
}
}
return contentTypes[0];
}
/**
* {@inheritDoc}
*/
@Override
public String escapeString(String str) {
try {
return URLEncoder.encode(str, "utf8").replaceAll("\\+", "%20");
} catch (UnsupportedEncodingException e) {
return str;
}
}
/**
* {@inheritDoc}
*/
@Override
public Map getAuthentications() {
return this.authentications;
}
/**
* {@inheritDoc}
*/
@Override
public void addEnforcedAuthenticationScheme(String name) {
this.enforcedAuthenticationSchemes.add(name);
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* "* / *" is also default to JSON
*
* @param mime MIME
* @return True if the MIME type is JSON
*/
protected boolean isJsonMime(String mime) {
String jsonMime = "(?i)^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$";
return mime != null && (mime.matches(jsonMime) || mime.equals("*/*"));
}
/**
* Serialize the given Java object into string entity according the given
* Content-Type (only JSON is supported for now).
*
* @param obj Object
* @param formParams Form parameters
* @param contentType Context type
* @return Entity
*/
protected Entity> serialize(Object obj, Map formParams, String contentType) {
if (contentType.startsWith(MediaType.MULTIPART_FORM_DATA)) {
return this.serializeMultiPartFormDataEntity(formParams);
} else if (contentType.startsWith(MediaType.APPLICATION_FORM_URLENCODED)) {
final Form form = new Form();
formParams.forEach((key, value) -> form.param(key, parameterToString(value)));
return Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE);
} else {
// We let jersey handle the serialization
return Entity.entity(obj, contentType);
}
}
private Entity> serializeMultiPartFormDataEntity(Map formParams) {
FormDataMultiPart multiPart = new FormDataMultiPart();
for (final Entry param : formParams.entrySet()) {
// if part is a File
if (param.getValue() instanceof File) {
multiPart = buildFileBodyPart(multiPart, param.getKey(), (File) param.getValue());
} else if (isCollectionOfFiles(param.getValue())) {
Collection> paramValues = (Collection>) param.getValue();
for (Object paramValue : paramValues) {
multiPart = buildFileBodyPart(multiPart, param.getKey(), (File) paramValue);
}
}
// if part is a ApiClientBodyPart[]
else if (param.getValue() instanceof ApiClientBodyPart[]) {
for (ApiClientBodyPart attachment : (ApiClientBodyPart[]) param.getValue()) {
final StreamDataBodyPart streamPart =
new StreamDataBodyPart(param.getKey(), attachment.getContent(), attachment.getFilename());
multiPart = (FormDataMultiPart) multiPart.bodyPart(streamPart);
}
}
// if part is a single ApiClientBodyPart
else if (param.getValue() instanceof ApiClientBodyPart) {
final ApiClientBodyPart part = (ApiClientBodyPart) param.getValue();
final StreamDataBodyPart streamPart =
new StreamDataBodyPart(param.getKey(), part.getContent(), part.getFilename());
multiPart = (FormDataMultiPart) multiPart.bodyPart(streamPart);
} else {
multiPart = multiPart.field(param.getKey(), this.parameterToString(param.getValue()));
}
}
return Entity.entity(multiPart, MultiPartMediaTypes.createFormData());
}
private FormDataMultiPart buildFileBodyPart(FormDataMultiPart multiPart, String paramKey, File paramValue) {
final FormDataContentDisposition contentDisposition = FormDataContentDisposition
.name(paramKey)
.fileName(paramValue.getName())
.size(paramValue.length())
.build();
final FormDataBodyPart streamPart = new FormDataBodyPart(
contentDisposition,
paramValue,
MediaType.APPLICATION_OCTET_STREAM_TYPE
);
return (FormDataMultiPart) multiPart.bodyPart(streamPart);
}
/**
* Deserialize response body to Java object according to the Content-Type.
*
* @param Type
* @param response Response
* @param returnType Return type
* @return Deserialize object
* @throws ApiException API exception
*/
@SuppressWarnings("unchecked")
protected T deserialize(Response response, GenericType returnType) throws ApiException {
if (response == null || returnType == null) {
return null;
}
if ("byte[]".equals(returnType.toString())) {
// Handle binary response (byte array).
return (T) response.readEntity(byte[].class);
} else if (returnType.getRawType() == File.class) {
// Handle file downloading.
return (T) downloadFileFromResponse(response);
}
return response.readEntity(returnType);
}
/**
* Download file from the given response.
*
* @param response Response
* @return File
* @throws ApiException If fail to read file content from response and write to disk
*/
protected File downloadFileFromResponse(final Response response) throws ApiException {
try {
final File file = this.prepareDownloadFile(response);
Files.copy(response.readEntity(InputStream.class), file.toPath(), StandardCopyOption.REPLACE_EXISTING);
return file;
} catch (IOException e) {
throw new ApiException("Unable to download file from response", e);
}
}
protected File prepareDownloadFile(Response response) throws IOException {
String filename = null;
String contentDisposition = (String) response.getHeaders().getFirst("Content-Disposition");
if (contentDisposition != null && !"".equals(contentDisposition)) {
// Get filename from the Content-Disposition header.
Pattern pattern = Pattern.compile("filename=['\"]?([^'\"\\s]+)['\"]?");
Matcher matcher = pattern.matcher(contentDisposition);
if (matcher.find()) {
filename = matcher.group(1);
}
}
String prefix;
String suffix = null;
if (filename == null) {
prefix = "download-";
suffix = "";
} else {
int pos = filename.lastIndexOf('.');
if (pos == -1) {
prefix = filename + "-";
} else {
prefix = filename.substring(0, pos) + "-";
suffix = filename.substring(pos);
}
// File.createTempFile requires the prefix to be at least three characters long
if (prefix.length() < 3) {
prefix = "download-";
}
}
if (tempFolderPath == null) {
return File.createTempFile(prefix, suffix);
} else {
return File.createTempFile(prefix, suffix, new File(tempFolderPath));
}
}
protected Map> buildResponseHeaders(Response response) {
Map> responseHeaders = new HashMap<>();
for (Entry> entry : response.getHeaders().entrySet()) {
List values = entry.getValue();
List headers = new ArrayList<>();
for (Object o : values) {
headers.add(String.valueOf(o));
}
responseHeaders.put(entry.getKey(), headers);
}
return responseHeaders;
}
/**
* Update query and header parameters based on authentication settings.
*
* @param authNames The authentications to apply
*/
protected void updateParamsForAuth(String[] authNames, Map headerParams) throws ApiException {
if (authNames == null && this.enforcedAuthenticationSchemes.isEmpty()) {
return;
}
authNames = withEnforcedSecurityScheme(authNames);
for (String authName : authNames) {
Authentication auth = this.authentications.get(authName);
if (auth == null) {
throw new RuntimeException("Authentication undefined: " + authName);
}
auth.apply(headerParams);
}
}
private String[] withEnforcedSecurityScheme(String[] authNames) {
if (authNames == null) {
authNames = new String[0];
}
return Stream.concat(this.enforcedAuthenticationSchemes.stream(), Arrays.stream(authNames)).toArray(String[]::new);
}
}