diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/NeeoBrainApi.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/NeeoBrainApi.java index 0fae1fc43aa..91081898ab1 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/NeeoBrainApi.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/NeeoBrainApi.java @@ -20,9 +20,8 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.neeo.internal.models.ExecuteResult; import org.openhab.binding.neeo.internal.models.NeeoBrain; @@ -52,8 +51,7 @@ public class NeeoBrainApi implements AutoCloseable { /** The {@link HttpRequest} used for making requests */ private final AtomicReference request; - /** The {@link ClientBuilder} to use */ - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** The IP address of the neeo brain */ private final NeeoUrlBuilder urlBuilder; @@ -63,14 +61,14 @@ public class NeeoBrainApi implements AutoCloseable { * * @param ipAddress the non-empty ip address */ - public NeeoBrainApi(String ipAddress, ClientBuilder clientBuilder) { + public NeeoBrainApi(String ipAddress, HttpClient httpClient) { NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty"); this.urlBuilder = new NeeoUrlBuilder( NeeoConstants.PROTOCOL + ipAddress + ":" + NeeoConstants.DEFAULT_BRAIN_PORT); - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; - request = new AtomicReference<>(new HttpRequest(clientBuilder)); + request = new AtomicReference<>(new HttpRequest(httpClient)); } /** @@ -246,7 +244,7 @@ public class NeeoBrainApi implements AutoCloseable { @Override public void close() throws Exception { - NeeoUtil.close(request.getAndSet(new HttpRequest(clientBuilder))); + NeeoUtil.close(request.getAndSet(new HttpRequest(httpClient))); } /** diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoBrainHandler.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoBrainHandler.java index 880236caf31..e94013dece1 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoBrainHandler.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoBrainHandler.java @@ -29,10 +29,10 @@ import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.servlet.ServletException; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.neeo.internal.NeeoBrainApi; import org.openhab.binding.neeo.internal.NeeoBrainConfig; import org.openhab.binding.neeo.internal.NeeoConstants; @@ -72,8 +72,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler { /** The {@link NetworkAddressService} to use */ private final NetworkAddressService networkAddressService; - /** The {@link ClientBuilder} to use */ - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** GSON implementation - only used to deserialize {@link NeeoAction} */ private final Gson gson = new Gson(); @@ -114,7 +113,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler { * @param networkAddressService the non-null {@link NetworkAddressService} */ NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService, - NetworkAddressService networkAddressService, ClientBuilder clientBuilder) { + NetworkAddressService networkAddressService, HttpClient httpClient) { super(bridge); Objects.requireNonNull(bridge, "bridge cannot be null"); @@ -124,7 +123,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler { this.servicePort = servicePort; this.httpService = httpService; this.networkAddressService = networkAddressService; - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; } /** @@ -169,7 +168,7 @@ public class NeeoBrainHandler extends BaseBridgeHandler { "Brain IP Address must be specified"); return; } - final NeeoBrainApi api = new NeeoBrainApi(ipAddress, clientBuilder); + final NeeoBrainApi api = new NeeoBrainApi(ipAddress, httpClient); final NeeoBrain brain = api.getBrain(); final String brainId = getNeeoBrainId(); @@ -195,13 +194,17 @@ public class NeeoBrainHandler extends BaseBridgeHandler { final NeeoAction action = Objects.requireNonNull(gson.fromJson(json, NeeoAction.class)); getThing().getThings().stream().map(Thing::getHandler).filter(NeeoRoomHandler.class::isInstance) .forEach(h -> ((NeeoRoomHandler) h).processAction(action)); - }, config.getForwardChain(), clientBuilder); + }, config.getForwardChain(), httpClient); NeeoUtil.checkInterrupt(); try { - servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId); + String servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId); + this.servletPath = servletPath; - httpService.registerServlet(servletPath, forwardActionServlet, new Hashtable<>(), + Hashtable initParams = new Hashtable<>(); + initParams.put("servlet-name", servletPath); + + httpService.registerServlet(servletPath, forwardActionServlet, initParams, httpService.createDefaultHttpContext()); final URL callbackURL = createCallbackUrl(brainId, config); diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoForwardActionsServlet.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoForwardActionsServlet.java index f00795831ec..a18cfb26d49 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoForwardActionsServlet.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoForwardActionsServlet.java @@ -20,10 +20,10 @@ import java.util.stream.Collectors; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpStatus; import org.openhab.binding.neeo.internal.net.HttpRequest; import org.openhab.binding.neeo.internal.net.HttpResponse; @@ -50,8 +50,7 @@ public class NeeoForwardActionsServlet extends HttpServlet { /** The forwarding chain */ private final @Nullable String forwardChain; - /** The {@link ClientBuilder} to use */ - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** The scheduler to use to schedule recipe execution */ private final ScheduledExecutorService scheduler; @@ -64,13 +63,13 @@ public class NeeoForwardActionsServlet extends HttpServlet { * @param forwardChain a possibly null, possibly empty forwarding chain */ NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Consumer callback, - @Nullable String forwardChain, ClientBuilder clientBuilder) { + @Nullable String forwardChain, HttpClient httpClient) { super(); this.scheduler = scheduler; this.callback = callback; this.forwardChain = forwardChain; - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; } /** @@ -92,9 +91,10 @@ public class NeeoForwardActionsServlet extends HttpServlet { callback.accept(json); + String forwardChain = this.forwardChain; if (forwardChain != null && !forwardChain.isEmpty()) { scheduler.execute(() -> { - try (final HttpRequest request = new HttpRequest(clientBuilder)) { + try (final HttpRequest request = new HttpRequest(httpClient)) { for (final String forwardUrl : forwardChain.split(",")) { if (!forwardUrl.isEmpty()) { final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json); diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoHandlerFactory.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoHandlerFactory.java index b3bedba0c2e..7418de70c32 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoHandlerFactory.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/handler/NeeoHandlerFactory.java @@ -17,14 +17,14 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.neeo.internal.NeeoConstants; import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService; import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService; import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.net.HttpServiceUtil; import org.openhab.core.net.NetworkAddressService; import org.openhab.core.thing.Bridge; @@ -56,18 +56,17 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory { /** The {@link NetworkAddressService} used for ip lookup */ private final NetworkAddressService networkAddressService; - /** The {@link ClientBuilder} used in HttpRequest */ - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** The discovery services created by this class (one per room and one for each device) */ private final ConcurrentMap> discoveryServiceRegs = new ConcurrentHashMap<>(); @Activate public NeeoHandlerFactory(@Reference HttpService httpService, - @Reference NetworkAddressService networkAddressService, @Reference ClientBuilder clientBuilder) { + @Reference NetworkAddressService networkAddressService, @Reference HttpClientFactory httpClientFactory) { this.httpService = httpService; this.networkAddressService = networkAddressService; - this.clientBuilder = clientBuilder; + this.httpClient = httpClientFactory.getCommonHttpClient(); } @Override @@ -87,7 +86,7 @@ public class NeeoHandlerFactory extends BaseThingHandlerFactory { final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext); final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing, port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, httpService, networkAddressService, - clientBuilder); + httpClient); registerRoomDiscoveryService(handler); return handler; } else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) { diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpRequest.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpRequest.java index 9049741d12a..9e6e50d9583 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpRequest.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpRequest.java @@ -16,18 +16,19 @@ import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation.Builder; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.glassfish.jersey.filter.LoggingFilter; import org.openhab.binding.neeo.internal.NeeoUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -44,17 +45,13 @@ public class HttpRequest implements AutoCloseable { private final Logger logger = LoggerFactory.getLogger(HttpRequest.class); /** The client to use */ - private final Client client; + private final HttpClient httpClient; /** * Instantiates a new request */ - public HttpRequest(ClientBuilder clientBuilder) { - client = clientBuilder.build(); - - if (logger.isDebugEnabled()) { - client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true)); - } + public HttpRequest(HttpClient httpClient) { + this.httpClient = httpClient; } /** @@ -66,17 +63,17 @@ public class HttpRequest implements AutoCloseable { public HttpResponse sendGetCommand(String uri) { NeeoUtil.requireNotEmpty(uri, "uri cannot be empty"); try { - final Builder request = client.target(uri).request(); - - final Response content = request.get(); - - try { - return new HttpResponse(content); - } finally { - content.close(); - } + final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri); + request.method(HttpMethod.GET); + request.timeout(10, TimeUnit.SECONDS); + ContentResponse refreshResponse = request.send(); + return new HttpResponse(refreshResponse); } catch (IOException | IllegalStateException e) { return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage()); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage()); + String message = e.getMessage(); + return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); } } @@ -99,26 +96,27 @@ public class HttpRequest implements AutoCloseable { logger.warn("Absolute URI required but provided URI '{}' is non-absolute. ", uriString); return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, "Absolute URI required"); } - final Builder request = client.target(targetUri).request(MediaType.APPLICATION_JSON); - - final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON)); - - try { - return new HttpResponse(content); - } finally { - content.close(); - } + final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(targetUri); + request.content(new StringContentProvider(body)); + request.header(HttpHeader.CONTENT_TYPE, "application/json"); + request.method(HttpMethod.POST); + request.timeout(10, TimeUnit.SECONDS); + ContentResponse refreshResponse = request.send(); + return new HttpResponse(refreshResponse); // IllegalArgumentException/ProcessingException catches issues with the URI being invalid // as well } catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) { return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage()); } catch (URISyntaxException e) { return new HttpResponse(HttpStatus.NOT_ACCEPTABLE_406, e.getMessage()); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage()); + String message = e.getMessage(); + return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); } } @Override public void close() { - client.close(); } } diff --git a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpResponse.java b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpResponse.java index 681a6169b40..8c4aa97c24a 100644 --- a/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpResponse.java +++ b/bundles/org.openhab.binding.neeo/src/main/java/org/openhab/binding/neeo/internal/net/HttpResponse.java @@ -13,7 +13,6 @@ package org.openhab.binding.neeo.internal.net; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -23,6 +22,7 @@ import javax.ws.rs.core.Response; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.api.ContentResponse; /** * This class represents an {@link HttpRequest} response @@ -47,24 +47,18 @@ public class HttpResponse { /** * Instantiates a new http response from the {@link Response}. * - * @param response the non-null response + * @param refreshResponse the non-null response * @throws IOException Signals that an I/O exception has occurred. */ - HttpResponse(Response response) throws IOException { - Objects.requireNonNull(response, "response cannot be null"); + HttpResponse(ContentResponse refreshResponse) throws IOException { + Objects.requireNonNull(refreshResponse, "response cannot be null"); - httpStatus = response.getStatus(); - httpReason = response.getStatusInfo().getReasonPhrase(); + httpStatus = refreshResponse.getStatus(); + httpReason = refreshResponse.getReason(); + contents = refreshResponse.getContent(); - if (response.hasEntity()) { - InputStream is = response.readEntity(InputStream.class); - contents = is.readAllBytes(); - } else { - contents = null; - } - - for (String key : response.getHeaders().keySet()) { - headers.put(key, response.getHeaderString(key)); + for (String key : refreshResponse.getHeaders().getFieldNamesCollection()) { + headers.put(key, refreshResponse.getHeaders().getField(key).toString()); } } diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/NeeoService.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/NeeoService.java index d50e03e1ccd..c36496370d4 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/NeeoService.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/NeeoService.java @@ -23,16 +23,17 @@ import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.servlet.ServletException; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.core.addon.AddonInfoRegistry; import org.openhab.core.config.core.ConfigurableService; import org.openhab.core.events.Event; import org.openhab.core.events.EventFilter; import org.openhab.core.events.EventPublisher; import org.openhab.core.events.EventSubscriber; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.core.items.ItemRegistry; import org.openhab.core.items.events.ItemStateChangedEvent; @@ -95,7 +96,8 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene private final MDNSClient mdnsClient; private final EventPublisher eventPublisher; private final NetworkAddressService networkAddressService; - private final ClientBuilder clientBuilder; + private final HttpClientFactory httpClientFactory; + private final HttpClient httpClient; /** The main dashboard servlet. Only created in the activate method (and disposed of in the deactivate method) */ private @Nullable NeeoDashboardServlet dashboardServlet; @@ -143,7 +145,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene @Reference AddonInfoRegistry addonInfoRegistry, @Reference ChannelTypeRegistry channelTypeRegistry, @Reference ThingTypeRegistry thingTypeRegistry, @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, @Reference MDNSClient mdnsClient, @Reference EventPublisher eventPublisher, - @Reference NetworkAddressService networkAddressService, @Reference ClientBuilder clientBuilder) { + @Reference NetworkAddressService networkAddressService, @Reference HttpClientFactory httpClientFactory) { this.httpService = httpService; this.itemRegistry = itemRegistry; this.bindingInfoRegistry = addonInfoRegistry; @@ -154,7 +156,8 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene this.mdnsClient = mdnsClient; this.eventPublisher = eventPublisher; this.networkAddressService = networkAddressService; - this.clientBuilder = clientBuilder; + this.httpClientFactory = httpClientFactory; + this.httpClient = httpClientFactory.getCommonHttpClient(); logger.debug("Neeo Service activated"); final ServiceContext localContext = new ServiceContext(componentContext, validate(httpService, "httpService"), @@ -165,7 +168,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene validate(eventPublisher, "eventPublisher"), validate(networkAddressService, "networkAddressService")); context = localContext; - discovery = new MdnsBrainDiscovery(localContext, clientBuilder); + discovery = new MdnsBrainDiscovery(localContext, httpClient); discovery.addListener(discoveryListener); try { @@ -279,7 +282,7 @@ public class NeeoService implements EventSubscriber, NetworkAddressChangeListene servletUrl); try { final NeeoBrainServlet newServlet = NeeoBrainServlet.create(localContext, servletUrl, - sysInfo.getHostname(), ipAddress, clientBuilder); + sysInfo.getHostname(), ipAddress, httpClient, httpClientFactory); servlets.add(newServlet); Hashtable initParams = new Hashtable<>(); diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoApi.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoApi.java index cbe38b3e4d4..4bc0c1b57eb 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoApi.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoApi.java @@ -20,6 +20,7 @@ import java.net.Socket; import java.net.URL; import java.util.Map; import java.util.Objects; +import java.util.Stack; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -27,13 +28,13 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpStatus; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.id.InstanceUUID; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.net.HttpServiceUtil; import org.openhab.io.neeo.internal.models.NeeoAdapterRegistration; import org.openhab.io.neeo.internal.models.NeeoRecipe; @@ -69,7 +70,13 @@ public class NeeoApi implements AutoCloseable { /** The brain's IP address */ private final String brainIpAddress; - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; + + private final HttpClientFactory httpClientFactory; + + private Stack httpClientStack = new Stack(); + + private int httpClientId = 0; /** The URL of the brain */ private final String brainUrl; @@ -122,25 +129,26 @@ public class NeeoApi implements AutoCloseable { * @param context the non-null {@link ServiceContext} * @throws IOException if an exception occurs connecting to the brain */ - public NeeoApi(String ipAddress, String brainId, ServiceContext context, ClientBuilder clientBuilder) - throws IOException { + public NeeoApi(String ipAddress, String brainId, ServiceContext context, HttpClient httpClient, + HttpClientFactory httpClientFactory) throws IOException { NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty"); NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty"); Objects.requireNonNull(context, "context cannot be null"); this.brainIpAddress = ipAddress; this.brainId = brainId; - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; + this.httpClientFactory = httpClientFactory; this.brainUrl = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress) + ":" + NeeoConstants.DEFAULT_BRAIN_PORT; - deviceKeys = new NeeoDeviceKeys(brainUrl, clientBuilder); + deviceKeys = new NeeoDeviceKeys(brainUrl, httpClient); - request = new AtomicReference<>(new HttpRequest(clientBuilder)); + request = new AtomicReference<>(new HttpRequest(httpClient)); - this.systemInfo = getSystemInfo(ipAddress, clientBuilder); + this.systemInfo = getSystemInfo(ipAddress, httpClient); String name = brainId; - try (HttpRequest request = new HttpRequest(clientBuilder)) { + try (HttpRequest request = new HttpRequest(httpClient)) { logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME); final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME); if (resp.getHttpCode() != HttpStatus.OK_200) { @@ -201,12 +209,12 @@ public class NeeoApi implements AutoCloseable { * @return the non-null {@link NeeoSystemInfo} for the address * @throws IOException Signals that an I/O exception has occurred or the URL is not a brain */ - public static NeeoSystemInfo getSystemInfo(String ipAddress, ClientBuilder clientBuilder) throws IOException { + public static NeeoSystemInfo getSystemInfo(String ipAddress, HttpClient httpClient) throws IOException { NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty"); final String sysInfo = NeeoConstants.PROTOCOL + (ipAddress.startsWith("/") ? ipAddress.substring(1) : ipAddress) + ":" + NeeoConstants.DEFAULT_BRAIN_PORT + NeeoConstants.SYSTEMINFO; - try (HttpRequest req = new HttpRequest(clientBuilder)) { + try (HttpRequest req = new HttpRequest(httpClient)) { final HttpResponse res = req.sendGetCommand(sysInfo); if (res.getHttpCode() == HttpStatus.OK_200) { return Objects.requireNonNull(GSON.fromJson(res.getContent(), NeeoSystemInfo.class)); @@ -392,7 +400,7 @@ public class NeeoApi implements AutoCloseable { try { setConnected(false); - NeeoUtil.close(request.getAndSet(new HttpRequest(clientBuilder))); + NeeoUtil.close(request.getAndSet(new HttpRequest(httpClient))); NeeoUtil.checkInterrupt(); registerApi(); @@ -461,6 +469,41 @@ public class NeeoApi implements AutoCloseable { return deviceKeys; } + private synchronized HttpClient getHttpClient() { + int stackSize = httpClientStack.size(); + if (stackSize == 0) { + int httpClientId = this.httpClientId + 1; + this.httpClientId = httpClientId; + String httpClientIdString = "neeo-" + brainId + "-" + httpClientId; + logger.debug("getHttpClient created new client {} for brain {}", httpClientIdString, brainId); + HttpClient httpClient = httpClientFactory.createHttpClient(httpClientIdString); + try { + httpClient.start(); + } catch (Exception e) { + logger.debug("Exception while starting HttpClient: {}", e.getMessage(), e); + } + return httpClient; + } else { + logger.debug("getHttpClient popped a client from the stack for brain {} depth {}", brainId, stackSize); + return (HttpClient) httpClientStack.pop(); + } + } + + private synchronized void returnHttpClient(HttpClient httpClient) { + int stackSize = httpClientStack.size(); + if (stackSize <= NeeoConstants.HTTPCLIENT_POOL_SIZE) { + logger.debug("getHttpClient returned a client for brain {} depth {}", brainId, stackSize); + httpClientStack.push(httpClient); + } else { + try { + logger.debug("getHttpClient destroyed a client for brain {}", brainId); + httpClient.stop(); + } catch (Exception e) { + logger.debug("Exception while stopping HttpClient: {}", e.getMessage(), e); + } + } + } + /** * Send a notification to the brain * @@ -469,12 +512,17 @@ public class NeeoApi implements AutoCloseable { */ public void notify(String msg) throws IOException { if (isConnected()) { - final HttpRequest rqst = request.get(); + HttpClient httpClient = getHttpClient(); + final HttpRequest rqst = new HttpRequest(httpClient); logger.debug("Sending Notification to brain ({}): {}", brainId, msg); final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl + NeeoConstants.NOTIFICATION, msg); if (resp.getHttpCode() != HttpStatus.OK_200) { + returnHttpClient(httpClient); throw resp.createException(); + } else { + logger.debug("Response from brain ({}): {} - {}", brainId, resp.getHttpCode(), resp.getContent()); } + returnHttpClient(httpClient); } else { logger.debug("Notification ignored - brain not connected"); } @@ -553,8 +601,15 @@ public class NeeoApi implements AutoCloseable { NeeoUtil.cancel(connect.getAndSet(null)); try { + int stackSize = httpClientStack.size(); + while (stackSize > 0) { + HttpClient httpClient = (HttpClient) httpClientStack.pop(); + httpClient.stop(); + httpClient = null; + stackSize--; + } deregisterApi(); - } catch (IOException e) { + } catch (Exception e) { logger.debug("Exception while deregistring api during close - ignoring: {}", e.getMessage(), e); } finally { // Do this regardless if a runtime exception was thrown diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoBrainServlet.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoBrainServlet.java index 877913e00eb..3af2bced2f9 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoBrainServlet.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoBrainServlet.java @@ -16,9 +16,9 @@ import java.io.IOException; import java.net.InetAddress; import java.util.Objects; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.io.neeo.internal.models.BrainStatus; import org.openhab.io.neeo.internal.servletservices.NeeoBrainSearchService; import org.openhab.io.neeo.internal.servletservices.NeeoBrainService; @@ -46,9 +46,9 @@ public class NeeoBrainServlet extends AbstractServlet { * @param servletUrl the non-null, non-empty servlet URL * @param api the non-null API */ - private NeeoBrainServlet(ServiceContext context, String servletUrl, NeeoApi api, ClientBuilder clientBuilder) { - super(context, servletUrl, new NeeoBrainSearchService(context), - new NeeoBrainService(api, context, clientBuilder)); + private NeeoBrainServlet(ServiceContext context, String servletUrl, NeeoApi api, HttpClient httpClient, + HttpClientFactory httpClientFactory) { + super(context, servletUrl, new NeeoBrainSearchService(context), new NeeoBrainService(api, context, httpClient)); Objects.requireNonNull(context, "context cannot be null"); NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty"); @@ -68,16 +68,16 @@ public class NeeoBrainServlet extends AbstractServlet { * @throws IOException when an exception occurs contacting the brain */ public static NeeoBrainServlet create(ServiceContext context, String servletUrl, String brainId, - InetAddress ipAddress, ClientBuilder clientBuilder) throws IOException { + InetAddress ipAddress, HttpClient httpClient, HttpClientFactory httpClientFactory) throws IOException { Objects.requireNonNull(context, "context cannot be null"); NeeoUtil.requireNotEmpty(servletUrl, "servletUrl cannot be empty"); NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty"); Objects.requireNonNull(ipAddress, "ipAddress cannot be null"); - final NeeoApi api = new NeeoApi(ipAddress.getHostAddress(), brainId, context, clientBuilder); + final NeeoApi api = new NeeoApi(ipAddress.getHostAddress(), brainId, context, httpClient, httpClientFactory); api.start(); - return new NeeoBrainServlet(context, servletUrl, api, clientBuilder); + return new NeeoBrainServlet(context, servletUrl, api, httpClient, httpClientFactory); } /** diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoConstants.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoConstants.java index 159882318ef..66c6dbe3364 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoConstants.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoConstants.java @@ -49,6 +49,7 @@ public class NeeoConstants { /** Constant used to identify thread pool name */ public static final String THREAD_POOL_NAME = "neeoio"; + public static final int HTTPCLIENT_POOL_SIZE = 5; /** Constants used for the Web APP */ public static final String WEBAPP_PREFIX = "/neeo"; diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoDeviceKeys.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoDeviceKeys.java index 91d507fcef9..c2322366a4b 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoDeviceKeys.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/NeeoDeviceKeys.java @@ -22,9 +22,8 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import javax.ws.rs.client.ClientBuilder; - import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.http.HttpStatus; import org.openhab.core.thing.ThingUID; import org.openhab.io.neeo.internal.models.NeeoThingUID; @@ -54,18 +53,18 @@ public class NeeoDeviceKeys { /** The brain's url */ private final String brainUrl; - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** * Creates the object from the context and brainUrl * * @param brainUrl the non-empty brain url */ - NeeoDeviceKeys(String brainUrl, ClientBuilder clientBuilder) { + NeeoDeviceKeys(String brainUrl, HttpClient httpClient) { NeeoUtil.requireNotEmpty(brainUrl, "brainUrl cannot be empty"); this.brainUrl = brainUrl; - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; } /** @@ -74,7 +73,7 @@ public class NeeoDeviceKeys { * @throws IOException Signals that an I/O exception has occurred. */ void refresh() throws IOException { - try (HttpRequest request = new HttpRequest(clientBuilder)) { + try (HttpRequest request = new HttpRequest(httpClient)) { logger.debug("Getting existing device mappings from {}{}", brainUrl, NeeoConstants.PROJECTS_HOME); final HttpResponse resp = request.sendGetCommand(brainUrl + NeeoConstants.PROJECTS_HOME); if (resp.getHttpCode() != HttpStatus.OK_200) { diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/discovery/MdnsBrainDiscovery.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/discovery/MdnsBrainDiscovery.java index 7cb8f9752b2..6d21c8d36e9 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/discovery/MdnsBrainDiscovery.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/discovery/MdnsBrainDiscovery.java @@ -34,10 +34,10 @@ import java.util.stream.Collectors; import javax.jmdns.ServiceEvent; import javax.jmdns.ServiceInfo; import javax.jmdns.ServiceListener; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.io.transport.mdns.MDNSClient; import org.openhab.io.neeo.internal.NeeoApi; @@ -106,17 +106,17 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery { /** The file we store definitions in */ private final File file = new File(NeeoConstants.FILENAME_DISCOVEREDBRAINS); - private final ClientBuilder clientBuilder; + private final HttpClient httpClient; /** * Creates the MDNS brain discovery from the given {@link ServiceContext} * * @param context the non-null service context */ - public MdnsBrainDiscovery(ServiceContext context, ClientBuilder clientBuilder) { + public MdnsBrainDiscovery(ServiceContext context, HttpClient httpClient) { Objects.requireNonNull(context, "context cannot be null"); this.context = context; - this.clientBuilder = clientBuilder; + this.httpClient = httpClient; } /** @@ -253,7 +253,7 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery { NeeoSystemInfo sysInfo; try { - sysInfo = NeeoApi.getSystemInfo(brainInfo.getValue().toString(), clientBuilder); + sysInfo = NeeoApi.getSystemInfo(brainInfo.getValue().toString(), httpClient); } catch (IOException e) { // We can get an MDNS notification BEFORE the brain is ready to process. // if that happens, we'll get an IOException (usually bad gateway message), schedule another attempt to get @@ -302,7 +302,7 @@ public class MdnsBrainDiscovery extends AbstractBrainDiscovery { try { final InetAddress addr = InetAddress.getByName(ipAddress); - final NeeoSystemInfo sysInfo = NeeoApi.getSystemInfo(ipAddress, clientBuilder); + final NeeoSystemInfo sysInfo = NeeoApi.getSystemInfo(ipAddress, httpClient); logger.debug("Manually adding brain ({}) with system information: {}", ipAddress, sysInfo); systemsLock.lock(); diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpRequest.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpRequest.java index 60adcd1953e..9903170b272 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpRequest.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpRequest.java @@ -14,24 +14,25 @@ package org.openhab.io.neeo.internal.net; import java.io.IOException; import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.ws.rs.ProcessingException; -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.Entity; -import javax.ws.rs.client.Invocation.Builder; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpStatus; -import org.glassfish.jersey.filter.LoggingFilter; import org.openhab.io.neeo.internal.NeeoUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This class represents an HTTP session with a client + * This class represents an HTTP session with a httpClient * * @author Tim Roberts - Initial Contribution */ @@ -41,18 +42,14 @@ public class HttpRequest implements AutoCloseable { /** the logger */ private final Logger logger = LoggerFactory.getLogger(HttpRequest.class); - /** The client to use */ - private final Client client; + /** The httpClient to use */ + private final HttpClient httpClient; /** * Instantiates a new request */ - public HttpRequest(ClientBuilder clientBuilder) { - client = clientBuilder.build(); - - if (logger.isDebugEnabled()) { - client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true)); - } + public HttpRequest(HttpClient httpClient) { + this.httpClient = httpClient; } /** @@ -64,18 +61,18 @@ public class HttpRequest implements AutoCloseable { public HttpResponse sendGetCommand(String uri) { NeeoUtil.requireNotEmpty(uri, "uri cannot be empty"); try { - final Builder request = client.target(uri).request(); - - final Response content = request.get(); - - try { - return new HttpResponse(content); - } finally { - content.close(); - } + final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri); + request.method(HttpMethod.GET); + request.timeout(10, TimeUnit.SECONDS); + ContentResponse refreshResponse = request.send(); + return new HttpResponse(refreshResponse); } catch (IOException | IllegalStateException | ProcessingException e) { String message = e.getMessage(); return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage()); + String message = e.getMessage(); + return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); } } @@ -91,23 +88,24 @@ public class HttpRequest implements AutoCloseable { Objects.requireNonNull(body, "body cannot be null"); try { - final Builder request = client.target(uri).request(MediaType.APPLICATION_JSON); - - final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON)); - - try { - return new HttpResponse(content); - } finally { - content.close(); - } + final org.eclipse.jetty.client.api.Request request = httpClient.newRequest(uri); + request.content(new StringContentProvider(body)); + request.header(HttpHeader.CONTENT_TYPE, "application/json"); + request.method(HttpMethod.POST); + request.timeout(10, TimeUnit.SECONDS); + ContentResponse refreshResponse = request.send(); + return new HttpResponse(refreshResponse); } catch (IOException | IllegalStateException | ProcessingException e) { String message = e.getMessage(); return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.debug("An exception occurred while invoking a HTTP request: '{}'", e.getMessage()); + String message = e.getMessage(); + return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, message != null ? message : ""); } } @Override public void close() { - client.close(); } } diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpResponse.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpResponse.java index cf5371a1246..02ee5675d6f 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpResponse.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/net/HttpResponse.java @@ -13,7 +13,6 @@ package org.openhab.io.neeo.internal.net; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @@ -23,6 +22,7 @@ import javax.ws.rs.core.Response; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.api.ContentResponse; /** * This class represents an {@link HttpRequest} response @@ -50,20 +50,15 @@ public class HttpResponse { * @param response the non-null response * @throws IOException Signals that an I/O exception has occurred. */ - HttpResponse(Response response) throws IOException { + HttpResponse(ContentResponse response) throws IOException { Objects.requireNonNull(response, "response cannot be null"); httpStatus = response.getStatus(); - httpReason = response.getStatusInfo().getReasonPhrase(); + httpReason = response.getReason(); + contents = response.getContent(); - if (response.hasEntity()) { - contents = response.readEntity(InputStream.class).readAllBytes(); - } else { - contents = null; - } - - for (String key : response.getHeaders().keySet()) { - headers.put(key, response.getHeaderString(key)); + for (String key : response.getHeaders().getFieldNamesCollection()) { + headers.put(key, response.getHeaders().getField(key).toString()); } } diff --git a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/servletservices/NeeoBrainService.java b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/servletservices/NeeoBrainService.java index ca4bc608859..7fbee45f6cd 100644 --- a/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/servletservices/NeeoBrainService.java +++ b/bundles/org.openhab.io.neeo/src/main/java/org/openhab/io/neeo/internal/servletservices/NeeoBrainService.java @@ -21,11 +21,11 @@ import java.util.concurrent.ScheduledExecutorService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.client.ClientBuilder; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.events.Event; import org.openhab.core.events.EventFilter; @@ -114,7 +114,7 @@ public class NeeoBrainService extends DefaultServletService { * @param api the non-null api * @param context the non-null context */ - public NeeoBrainService(NeeoApi api, ServiceContext context, ClientBuilder clientBuilder) { + public NeeoBrainService(NeeoApi api, ServiceContext context, HttpClient httpClient) { Objects.requireNonNull(api, "api cannot be null"); Objects.requireNonNull(context, "context cannot be null"); @@ -125,7 +125,7 @@ public class NeeoBrainService extends DefaultServletService { scheduler.execute(() -> { resendState(); }); - request = new HttpRequest(clientBuilder); + request = new HttpRequest(httpClient); } /**