Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion connectors/http-connector/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<parent>
<groupId>com.github.openstack4j.core.connectors</groupId>
<artifactId>openstack4j-connectors</artifactId>
<version>3.12</version>
<version>co-3.12.3</version>
</parent>
<name>OpenStack4j HttpURL Connector</name>
<artifactId>openstack4j-http-connector</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion connectors/httpclient/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<parent>
<groupId>com.github.openstack4j.core.connectors</groupId>
<artifactId>openstack4j-connectors</artifactId>
<version>3.12</version>
<version>co-3.12.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openstack4j-httpclient</artifactId>
Expand Down
74 changes: 74 additions & 0 deletions connectors/jersey2-jakarta/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.github.openstack4j.core.connectors</groupId>
<artifactId>openstack4j-connectors</artifactId>
<version>co-3.12.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>openstack4j-jersey2-jakarta</artifactId>
<name>OpenStack4j Jersey2 Jakarta Connector</name>
<url>https://github.com/openstack4j/openstack4j/</url>
<packaging>jar</packaging>

<properties>
<jersey-version>3.1.10</jersey-version>
</properties>

<dependencies>
<dependency>
<groupId>org.glassfish.jersey.core</groupId>
<artifactId>jersey-client</artifactId>
<version>${jersey-version}</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey-version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
<version>4.0.0</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Name>${project.name}</Bundle-Name>
<Export-Package>
org.openstack4j.connectors.jersey2
</Export-Package>
<Import-Package>
!org.openstack4j.connectors.jersey2,
*
</Import-Package>
<!--
Required for SPI in OSGi:
http://aries.apache.org/modules/spi-fly.html#specconf

This bundle defines an implementation for following services in META-INF-services:
org.openstack4j.core.transport.HttpExecutorService
-->
<Require-Capability>
osgi.extender; filter:="(osgi.extender=osgi.serviceloader.registrar)"
</Require-Capability>
<Provide-Capability>
osgi.serviceloader; osgi.serviceloader=org.openstack4j.core.transport.HttpExecutorService
</Provide-Capability>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package org.openstack4j.connectors.jersey2;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.URL;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.ClientRequestContext;
import jakarta.ws.rs.client.ClientRequestFilter;
import jakarta.ws.rs.ext.ContextResolver;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.client.HttpUrlConnectorProvider.ConnectionFactory;
import org.glassfish.jersey.jackson.JacksonFeature;
import org.openstack4j.api.exceptions.ConnectionException;
import org.openstack4j.core.transport.ClientConstants;
import org.openstack4j.core.transport.Config;
import org.openstack4j.core.transport.ObjectMapperSingleton;
import org.openstack4j.core.transport.UntrustedSSL;

/**
* A factory for creating a rest Client which is mapped to Jackson for JSON processing.
*
* @author Jeremy Unruh
*/
class ClientFactory {

private static final CustomContextResolver RESOLVER = new CustomContextResolver();
private static LoadingCache<Config, Client> CACHE = CacheBuilder.newBuilder()
.expireAfterAccess(20, TimeUnit.MINUTES)
.build(new CacheLoader<Config, Client>() {
@Override
public Client load(Config config) throws Exception {
return buildClientFromConfig(config);
}
});

/**
* Creates or Returns a Client
*
* @param config the configuration to use for the given client
* @return the client
*/
static Client create(Config config) {
try {
return CACHE.get(config);
} catch (ExecutionException e) {
throw new ConnectionException("Issue creating Jersey 2 Client: " + e.getMessage(), 0, e);
}
}

private static Client buildClientFromConfig(Config config) {
ClientConfig clientConfig = new ClientConfig();

if (config.getProxy() != null) {
addProxy(clientConfig, config);
}

ClientBuilder cb = ClientBuilder.newBuilder()
.withConfig(clientConfig)
.property(ClientProperties.SUPPRESS_HTTP_COMPLIANCE_VALIDATION, "true")
.register(JacksonFeature.class)
.register(RESOLVER)
.register(new RequestFilter());

if (config.getSslContext() != null) {
cb.sslContext(config.getSslContext());
} else if (config.isIgnoreSSLVerification()) {
cb.sslContext(UntrustedSSL.getSSLContext());
}

if (config.getHostNameVerifier() != null) {
cb.hostnameVerifier(config.getHostNameVerifier());
} else if (config.isIgnoreSSLVerification()) {
cb.hostnameVerifier(UntrustedSSL.getHostnameVerifier());
}

if (config.getReadTimeout() > 0) {
cb.property(ClientProperties.READ_TIMEOUT, config.getReadTimeout());
}

if (config.getConnectTimeout() > 0) {
cb.property(ClientProperties.CONNECT_TIMEOUT, config.getConnectTimeout());
}

return cb.build();
}

private static void addProxy(ClientConfig cc, Config config) {
if (config.getProxy() != null) {
HttpUrlConnectorProvider cp = new HttpUrlConnectorProvider();
cc.connectorProvider(cp);
final Proxy proxy = new Proxy(Type.HTTP,
new InetSocketAddress(config.getProxy().getRawHost(), config.getProxy().getPort()));

cp.connectionFactory(new ConnectionFactory() {

@Override
public HttpURLConnection getConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection(proxy);
}
});
}
}

private static final class RequestFilter implements ClientRequestFilter {

/**
* {@inheritDoc}
*/
@Override
public void filter(ClientRequestContext requestContext) throws IOException {
requestContext.getHeaders().remove(ClientConstants.HEADER_CONTENT_LANGUAGE);
requestContext.getHeaders().remove(ClientConstants.HEADER_CONTENT_ENCODING);
}
}

private static final class CustomContextResolver implements ContextResolver<ObjectMapper> {

/**
* {@inheritDoc}
*/
@Override
public ObjectMapper getContext(Class<?> type) {
return ObjectMapperSingleton.getContext(type);
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package org.openstack4j.connectors.jersey2;

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.MediaType;
import jakarta.ws.rs.core.Response;

import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import org.glassfish.jersey.client.ClientProperties;
import org.glassfish.jersey.client.HttpUrlConnectorProvider;
import org.glassfish.jersey.client.RequestEntityProcessing;
import org.glassfish.jersey.logging.LoggingFeature;
import org.openstack4j.core.transport.ClientConstants;
import org.openstack4j.core.transport.HttpRequest;
import org.openstack4j.core.transport.internal.HttpLoggingFilter;

/**
* HttpCommand is responsible for executing the actual request driven by the HttpExecutor.
*/
public final class HttpCommand<R> {

private HttpRequest<R> request;
private Entity<?> entity;
private Invocation.Builder invocation;
private int retries;

private HttpCommand(HttpRequest<R> request) {
this.request = request;
}

/**
* Creates a new HttpCommand from the given request
*
* @param request the request
* @return the command
*/
public static <R> HttpCommand<R> create(HttpRequest<R> request) {
HttpCommand<R> command = new HttpCommand<R>(request);
command.initialize();
return command;
}

private void initialize() {
Client client = ClientFactory.create(request.getConfig());
//try to set unsupported HTTP method. In our case used for PATCH.
if ("PATCH".equals(request.getMethod().name())) {
client.property(HttpUrlConnectorProvider.SET_METHOD_WORKAROUND, true);
}

WebTarget target = client.target(request.getEndpoint()).path(request.getPath());

if (HttpLoggingFilter.isLoggingEnabled()) {
target.register(new LoggingFeature(Logger.getLogger("os"), 10000));
}

target = populateQueryParams(target, request);
invocation = target.request(MediaType.APPLICATION_JSON);

populateHeaders(invocation, request);

entity = (request.getEntity() == null) ? null: Entity.entity(request.getEntity(), request.getContentType());
}

/**
* Executes the command and returns the Response
*
* @return the response
*/
public Response execute() {
Response response = null;

if (hasEntity()) {
if (isInputStreamEntity()) {
// Issue #20 - Out of Memory in Jersey for large streams
invocation.property(ClientProperties.CHUNKED_ENCODING_SIZE, 1024);
invocation.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.CHUNKED);
}
response = invocation.method(request.getMethod().name(), getEntity());
} else if (request.hasJson()) {
response = invocation.method(request.getMethod().name(), Entity.entity(request.getJson(), ClientConstants.CONTENT_TYPE_JSON));
} else {
response = invocation.method(request.getMethod().name());
}

return response;
}

private boolean isInputStreamEntity() {
return (hasEntity() && InputStream.class.isAssignableFrom(entity.getEntity().getClass()));
}

/**
* @return the request entity or null
*/
public Entity<?> getEntity() {
return entity;
}

/**
* @return true if a request entity has been set
*/
public boolean hasEntity() {
return entity != null;
}

/**
* @return current retry execution count for this command
*/
public int getRetries() {
return retries;
}

/**
* @return incremement's the retry count and returns self
*/
public HttpCommand<R> incrementRetriesAndReturn() {
initialize();
retries++;
return this;
}

public HttpRequest<R> getRequest() {
return request;
}

private WebTarget populateQueryParams(WebTarget target, HttpRequest<R> request) {

if (!request.hasQueryParams()) {
return target;
}

for (Map.Entry<String, List<Object>> entry : request.getQueryParams().entrySet()) {
for (Object o : entry.getValue()) {
target = target.queryParam(entry.getKey(), o);
}
}
return target;
}

private void populateHeaders(Invocation.Builder invocation, HttpRequest<R> request) {

if (!request.hasHeaders()) {
return;
}

for (Map.Entry<String, Object> h : request.getHeaders().entrySet()) {
invocation.header(h.getKey(), h.getValue());
}
}
}
Loading