/** * Create the remote endpoint that communicates with the local services. */ protected RemoteEndpoint createRemoteEndpoint(MessageJsonHandler jsonHandler) { MessageConsumer outgoingMessageStream = new StreamMessageConsumer(output, jsonHandler); outgoingMessageStream = wrapMessageConsumer(outgoingMessageStream); RemoteEndpoint remoteEndpoint = new RemoteEndpoint(outgoingMessageStream, ServiceEndpoints.toEndpoint(localServices)); jsonHandler.setMethodProvider(remoteEndpoint); return remoteEndpoint; } }
protected void sendCancelNotification(Either<String, Number> id) { CancelParams cancelParams = new CancelParams(); cancelParams.setRawId(id); notify(MessageJsonHandler.CANCEL_METHOD.getMethodName(), cancelParams); }
errorObject = fallbackResponseError("Internal error. Exception handler provided no error object", throwable); out.consume(createErrorResponseMessage(requestMessage, errorObject)); if (throwable instanceof Error) throw (Error) throwable; out.consume(createResultResponseMessage(requestMessage, result)); }).exceptionally((Throwable t) -> { if (isCancellation(t)) { String message = "The request (id: " + messageId + ", method: '" + requestMessage.getMethod() + "') has been cancelled"; ResponseError errorObject = new ResponseError(ResponseErrorCode.RequestCancelled, message, null); responseMessage = createErrorResponseMessage(requestMessage, errorObject); } else { ResponseError errorObject = exceptionHandler.apply(t); if (errorObject == null) { errorObject = fallbackResponseError("Internal error. Exception handler provided no error object", t); responseMessage = createErrorResponseMessage(requestMessage, errorObject);
@Test public void testNullResponse() throws InterruptedException, ExecutionException { LogMessageAccumulator logMessages = new LogMessageAccumulator(); try { logMessages.registerTo(GenericEndpoint.class); Endpoint endpoint = ServiceEndpoints.toEndpoint(this); Map<String, JsonRpcMethod> methods = ServiceEndpoints.getSupportedMethods(LanguageServer.class); MessageJsonHandler handler = new MessageJsonHandler(methods); List<Message> msgs = new ArrayList<>(); MessageConsumer consumer = (message) -> { msgs.add(message); }; RemoteEndpoint re = new RemoteEndpoint(consumer, endpoint); RequestMessage request = new RequestMessage(); request.setId("1"); request.setMethod("shutdown"); re.consume(request); Assert.assertEquals("{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"result\":null}", handler.serialize(msgs.get(0))); msgs.clear(); shutdownReturn = new Object(); re.consume(request); Assert.assertEquals("{\"jsonrpc\":\"2.0\",\"id\":\"1\",\"result\":{}}", handler.serialize(msgs.get(0))); } finally { logMessages.unregister(); } }
@Test public void testExceptionInCompletableFuture() throws Exception { TestEndpoint endp = new TestEndpoint(); TestMessageConsumer consumer = new TestMessageConsumer(); RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); CompletableFuture<Object> future = endpoint.request("foo", "myparam"); CompletableFuture<Void> chained = future.thenAccept(result -> { throw new RuntimeException("BAAZ"); }); endpoint.consume(init(new ResponseMessage(), it -> { it.setId("1"); it.setResult("Bar"); })); try { chained.get(TIMEOUT, TimeUnit.MILLISECONDS); Assert.fail("Expected an ExecutionException."); } catch (ExecutionException exception) { assertEquals("java.lang.RuntimeException: BAAZ", exception.getMessage()); } }
@Test public void testOutputStreamClosed() throws Exception { LogMessageAccumulator logMessages = new LogMessageAccumulator(); try { logMessages.registerTo(RemoteEndpoint.class); TestEndpoint endp = new TestEndpoint(); MessageConsumer consumer = new MessageConsumer() { @Override public void consume(Message message) throws JsonRpcException { throw new JsonRpcException(new SocketException("Socket closed")); } }; RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); endpoint.notify("foo", null); logMessages.await(Level.INFO, "Failed to send notification message."); } finally { logMessages.unregister(); } }
@Test public void testExceptionInConsumer() throws Exception { TestEndpoint endp = new TestEndpoint(); MessageConsumer consumer = message -> { throw new RuntimeException("BAAZ"); }; RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); CompletableFuture<Object> future = endpoint.request("foo", "myparam"); future.whenComplete((result, exception) -> { assertNull(result); assertNotNull(exception); assertEquals("BAAZ", exception.getMessage()); }); try { future.get(TIMEOUT, TimeUnit.MILLISECONDS); Assert.fail("Expected an ExecutionException."); } catch (ExecutionException exception) { assertEquals("java.lang.RuntimeException: BAAZ", exception.getMessage()); } }
@Test public void testHandleRequestIssues() { TestEndpoint endp = new TestEndpoint(); TestMessageConsumer consumer = new TestMessageConsumer(); RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); endpoint.handle(init(new RequestMessage(), it -> { it.setId("1"); it.setMethod("foo"); it.setParams("myparam"); }), Collections.singletonList(new MessageIssue("bar"))); ResponseMessage responseMessage = (ResponseMessage) consumer.messages.get(0); assertNotNull(responseMessage.getError()); assertEquals("bar", responseMessage.getError().getMessage()); }
@Test public void testRequest() { TestEndpoint endp = new TestEndpoint(); TestMessageConsumer consumer = new TestMessageConsumer(); RemoteEndpoint endpoint = new DebugRemoteEndpoint(consumer, endp); endpoint.consume(new RequestMessage() {{ setId("1"); setMethod("foo"); setParams("myparam"); }}); Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next(); entry.getValue().complete("success"); assertEquals("foo", entry.getKey().getMethod()); assertEquals("myparam", entry.getKey().getParams()); assertEquals("success", ((ResponseMessage)consumer.messages.get(0)).getResult()); }
protected ResponseMessage createErrorResponseMessage(RequestMessage requestMessage, ResponseError errorObject) { ResponseMessage responseMessage = createResponseMessage(requestMessage); responseMessage.setError(errorObject); return responseMessage; }
/** * Send a notification to the remote endpoint. */ @Override public void notify(String method, Object parameter) { NotificationMessage notificationMessage = createNotificationMessage(method, parameter); try { out.consume(notificationMessage); } catch (Exception exception) { Level logLevel = JsonRpcException.indicatesStreamClosed(exception) ? Level.INFO : Level.WARNING; LOG.log(logLevel, "Failed to send notification message.", exception); } }
protected void handleNotification(NotificationMessage notificationMessage) { if (!handleCancellation(notificationMessage)) { // Forward the notification to the local endpoint try { localEndpoint.notify(notificationMessage.getMethod(), notificationMessage.getParams()); } catch (Exception exception) { LOG.log(Level.WARNING, "Notification threw an exception: " + notificationMessage, exception); } } }
protected void handleRequestIssues(RequestMessage requestMessage, List<MessageIssue> issues) { ResponseError errorObject = new ResponseError(); if (issues.size() == 1) { MessageIssue issue = issues.get(0); errorObject.setMessage(issue.getText()); errorObject.setCode(issue.getIssueCode()); errorObject.setData(issue.getCause()); } else { if (requestMessage.getMethod() != null) errorObject.setMessage("Multiple issues were found in '" + requestMessage.getMethod() + "' request."); else errorObject.setMessage("Multiple issues were found in request."); errorObject.setCode(ResponseErrorCode.InvalidRequest); errorObject.setData(issues); } out.consume(createErrorResponseMessage(requestMessage, errorObject)); }
/** * Send a request to the remote endpoint. */ @Override public CompletableFuture<Object> request(String method, Object parameter) { final RequestMessage requestMessage = createRequestMessage(method, parameter); final CompletableFuture<Object> result = new CompletableFuture<Object>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { sendCancelNotification(requestMessage.getRawId()); return super.cancel(mayInterruptIfRunning); } }; synchronized(sentRequestMap) { // Store request information so it can be handled when the response is received sentRequestMap.put(requestMessage.getId(), new PendingRequestInfo(requestMessage, result)); } try { // Send the request to the remote service out.consume(requestMessage); } catch (Exception exception) { // The message could not be sent, e.g. because the communication channel was closed result.completeExceptionally(exception); } return result; }
@Test public void testRequest1() { TestEndpoint endp = new TestEndpoint(); TestMessageConsumer consumer = new TestMessageConsumer(); RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); endpoint.consume(init(new RequestMessage(), it -> { it.setId("1"); it.setMethod("foo"); it.setParams("myparam"); })); Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next(); entry.getValue().complete("success"); assertEquals("foo", entry.getKey().getMethod()); assertEquals("myparam", entry.getKey().getParams()); ResponseMessage responseMessage = (ResponseMessage) consumer.messages.get(0); assertEquals("success", responseMessage.getResult()); assertEquals(Either.forLeft("1"), responseMessage.getRawId()); }
@Test public void testExceptionInOutputStream() throws Exception { LogMessageAccumulator logMessages = new LogMessageAccumulator(); try { logMessages.registerTo(RemoteEndpoint.class); TestEndpoint endp = new TestEndpoint(); MessageConsumer consumer = new MessageConsumer() { @Override public void consume(Message message) throws JsonRpcException { throw new JsonRpcException(new SocketException("Permission denied: connect")); } }; RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp); endpoint.notify("foo", null); logMessages.await(Level.WARNING, "Failed to send notification message."); } finally { logMessages.unregister(); } }
@Test public void testCancellation() { TestEndpoint endp = new TestEndpoint(); TestMessageConsumer consumer = new TestMessageConsumer(); RemoteEndpoint endpoint = new DebugRemoteEndpoint(consumer, endp); endpoint.consume(new RequestMessage() {{ setId("1"); setMethod("foo"); setParams("myparam"); }}); Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next(); entry.getValue().cancel(true); ResponseMessage message = (ResponseMessage) consumer.messages.get(0); assertNotNull(message); ResponseError error = message.getError(); assertNotNull(error); assertEquals(error.getCode(), ResponseErrorCode.RequestCancelled.getValue()); assertEquals(error.getMessage(), "The request (id: 1, method: 'foo') has been cancelled"); }
protected ResponseMessage createResultResponseMessage(RequestMessage requestMessage, Object result) { ResponseMessage responseMessage = createResponseMessage(requestMessage); responseMessage.setResult(result); return responseMessage; }
/** * Send a notification to the remote endpoint. */ @Override public void notify(String method, Object parameter) { NotificationMessage notificationMessage = createNotificationMessage(method, parameter); try { out.consume(notificationMessage); } catch (Exception exception) { Level logLevel = JsonRpcException.indicatesStreamClosed(exception) ? Level.INFO : Level.WARNING; LOG.log(logLevel, "Failed to send notification message.", exception); } }
protected void handleNotification(NotificationMessage notificationMessage) { if (!handleCancellation(notificationMessage)) { // Forward the notification to the local endpoint try { localEndpoint.notify(notificationMessage.getMethod(), notificationMessage.getParams()); } catch (Exception exception) { LOG.log(Level.WARNING, "Notification threw an exception: " + notificationMessage, exception); } } }