diff --git a/modules/com.dbeaver.jdbc.api/src/com/dbeaver/jdbc/model/AbstractJdbcResultSet.java b/modules/com.dbeaver.jdbc.api/src/com/dbeaver/jdbc/model/AbstractJdbcResultSet.java index 77caacb..437c021 100644 --- a/modules/com.dbeaver.jdbc.api/src/com/dbeaver/jdbc/model/AbstractJdbcResultSet.java +++ b/modules/com.dbeaver.jdbc.api/src/com/dbeaver/jdbc/model/AbstractJdbcResultSet.java @@ -95,7 +95,7 @@ public int findColumn(String columnLabel) throws SQLException { @Override public String getString(int columnIndex) throws SQLException { - return CommonUtils.toString(getObject(columnIndex)); + return CommonUtils.toString(getObject(columnIndex), null); } @Override diff --git a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RequestHandlerFactory.java b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RequestHandlerFactory.java index 2e872f8..1c284ef 100644 --- a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RequestHandlerFactory.java +++ b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RequestHandlerFactory.java @@ -24,9 +24,9 @@ import java.util.function.Predicate; @FunctionalInterface -public interface RequestHandlerFactory { +public interface RequestHandlerFactory { @NotNull - RestServer.RequestHandler createHandler( + RestServer.RequestHandler createHandler( @NotNull Class cls, @NotNull T object, @NotNull Gson gson, diff --git a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RestServer.java b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RestServer.java index be4f76e..6eb84e4 100644 --- a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RestServer.java +++ b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RestServer.java @@ -37,38 +37,111 @@ import java.net.InetSocketAddress; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.*; import java.util.function.Predicate; import java.util.logging.Level; import java.util.logging.Logger; -public class RestServer { +public class RestServer { private static final Logger log = Logger.getLogger(RestServer.class.getName()); + private static final Object EMPTY_CONTROLLER = new Object(); + private static final RequestHandlerFactory DEFAULT_HANDLER_FACTORY = new RequestHandlerFactory() { + @NotNull + @Override + public RequestHandler createHandler( + @NotNull Class cls, + @NotNull T object, + @NotNull Gson gson, + @NotNull Predicate filter, + @Nullable String landingPage + ) { + return new RequestHandler<>(cls, object, gson, filter, landingPage); + } + }; + private HttpServer server; - private String landingPage; - public RestServer( + public RestServer( + @NotNull Class cls, + @NotNull T object, + @NotNull Gson gson, + @NotNull Predicate filter, + int port, + int backlog + ) throws IOException { + this(cls, object, gson, filter, port, backlog, DEFAULT_HANDLER_FACTORY); + } + + public RestServer( @NotNull Class cls, @NotNull T object, @NotNull Gson gson, @NotNull Predicate filter, int port, int backlog, - @NotNull RequestHandlerFactory handlerFactory + @NotNull RequestHandlerFactory handlerFactory + ) throws IOException { + this( + Collections.singletonList(new ControllerDef<>("/", cls, object)), + gson, + filter, + port, + backlog, + null, + handlerFactory + ); + } + + private RestServer( + @NotNull List> controllers, + @NotNull Gson gson, + @NotNull Predicate filter, + int port, + int backlog, + @Nullable String landingPage, + @NotNull RequestHandlerFactory handlerFactory ) throws IOException { InetSocketAddress listenAddr = new InetSocketAddress(InetAddress.getLoopbackAddress(), port); server = HttpServer.create(listenAddr, backlog); - server.createContext("/", handlerFactory.createHandler(cls, object, gson, filter, landingPage)); + boolean hasRootController = controllers.stream().anyMatch(ctrl -> "/".equals(ctrl.path)); + if (controllers.isEmpty()) { + createEmptyRootContext(gson, filter, landingPage, handlerFactory); + } else { + controllers.forEach(ctrl -> + server.createContext( + ctrl.path, + createHandler( + ctrl, + gson, + filter, + "/".equals(ctrl.path) ? landingPage : null, + handlerFactory + ) + ) + ); + if (!hasRootController && landingPage != null) { + createEmptyRootContext(gson, filter, landingPage, handlerFactory); + } + } server.setExecutor(createExecutor()); server.start(); } @NotNull - public static Builder builder(@NotNull Class cls, @NotNull T object) { - return new Builder<>(object, cls); + public static Builder builder(@NotNull Class cls, @NotNull T object) { + Builder builder = new Builder(); + builder.addController("/", cls, object); + return builder; + } + + @NotNull + public static Builder builder() { + return new Builder(); } public boolean isRunning() { @@ -97,15 +170,40 @@ public InetSocketAddress getAddress() { return server.getAddress(); } - void setLandingPage(@Nullable String landingPage) { - this.landingPage = landingPage; - } - @NotNull protected Executor createExecutor() { return new ThreadPoolExecutor(1, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); } + private void createEmptyRootContext( + @NotNull Gson gson, + @NotNull Predicate filter, + @Nullable String landingPage, + @NotNull RequestHandlerFactory handlerFactory + ) { + server.createContext( + "/", + createHandler( + new ControllerDef<>("/", Object.class, EMPTY_CONTROLLER), + gson, + filter, + landingPage, + handlerFactory + ) + ); + } + + @NotNull + private static RequestHandler createHandler( + @NotNull ControllerDef controller, + @NotNull Gson gson, + @NotNull Predicate filter, + @Nullable String landingPage, + @NotNull RequestHandlerFactory handlerFactory + ) { + return handlerFactory.createHandler(controller.cls, controller.instance, gson, filter, landingPage); + } + private static final Type REQUEST_TYPE = new TypeToken>() {}.getType(); public static class RequestHandler implements HttpHandler { @@ -155,7 +253,6 @@ public void handle(HttpExchange exchange) throws IOException { try { responseText = gson.toJson(response.object, response.type); } catch (Throwable e) { - // Serialization error StringWriter buf = new StringWriter(); new RpcException("JSON serialization error: " + e.getMessage(), e).printStackTrace(new PrintWriter(buf, true)); @@ -206,7 +303,12 @@ private Response executeRequest(@NotNull HttpExchange exchange) throws IOExce } final URI uri = exchange.getRequestURI(); - final String path = uri.getPath().replaceAll("^/+", ""); + final String context = exchange.getHttpContext().getPath(); + String path = uri.getPath(); + if (path.startsWith(context)) { + path = path.substring(context.length()); + } + path = path.replaceAll("^/+", ""); final Method method = mappings.get(path); if (method == null) { @@ -288,73 +390,78 @@ private static Response createResponseContent(Object result, Type type) return new Response<>(result, type, RpcConstants.SC_OK); } - public static final class Builder { + public static final class Builder { private static final Predicate DEFAULT_PREDICATE = address -> true; - private final T object; - private final Class cls; private Gson gson; private int port; private int backlog; private Predicate filter = DEFAULT_PREDICATE; private String landingPage; - private RequestHandlerFactory handlerFactory = RequestHandler::new; + private final List> controllers = new ArrayList<>(); + private RequestHandlerFactory handlerFactory = DEFAULT_HANDLER_FACTORY; - private Builder(@NotNull T object, @NotNull Class cls) { - this.object = object; - this.cls = cls; + private Builder() { this.gson = RpcConstants.DEFAULT_GSON; this.port = 0; this.backlog = 0; } @NotNull - public Builder setGson(@NotNull Gson gson) { + public Builder addController( + @NotNull String path, + @NotNull Class cls, + @NotNull T instance + ) { + String normalizedPath = path.startsWith("/") ? path : "/" + path; + controllers.add(new ControllerDef<>(normalizedPath, cls, instance)); + return this; + } + + @NotNull + public Builder setGson(@NotNull Gson gson) { this.gson = gson; return this; } @NotNull - public Builder setPort(int port) { + public Builder setPort(int port) { this.port = port; return this; } @NotNull - public Builder setBacklog(int backlog) { + public Builder setBacklog(int backlog) { this.backlog = backlog; return this; } @NotNull - public Builder setLandingPage(String landingPage) { + public Builder setLandingPage(String landingPage) { this.landingPage = landingPage; return this; } @NotNull - public Builder setFilter(@NotNull Predicate filter) { + public Builder setFilter(@NotNull Predicate filter) { this.filter = filter; return this; } - public Builder setHandlerFactory(@NotNull RequestHandlerFactory handlerFactory) { + public Builder setHandlerFactory(@NotNull RequestHandlerFactory handlerFactory) { this.handlerFactory = handlerFactory; return this; } @NotNull - public RestServer create() { + public RestServer create() { try { - RestServer restServer = new RestServer<>(cls, object, gson, filter, port, backlog, handlerFactory); - if (landingPage != null) { - restServer.setLandingPage(landingPage); - } - return restServer; + return new RestServer(controllers, gson, filter, port, backlog, landingPage, handlerFactory); } catch (IOException e) { throw new UncheckedIOException(e); } } + } private static class Response { @@ -368,4 +475,18 @@ public Response(@Nullable T object, @NotNull Type type, int code) { this.code = code; } } + + private static final class ControllerDef { + final String path; + final Class cls; + final T instance; + + ControllerDef(String path, Class cls, T instance) { + this.path = path; + this.cls = cls; + this.instance = instance; + } + } + + } diff --git a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcConstants.java b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcConstants.java index e64dae1..cbe7cdd 100644 --- a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcConstants.java +++ b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcConstants.java @@ -26,6 +26,7 @@ public class RpcConstants { public static final int SC_OK = 200; + public static final int SC_NO_CONTENT = 204; public static final int SC_FORBIDDEN = 403; public static final int SC_UNSUPPORTED = 405; public static final int SC_NOT_FOUND = 404; diff --git a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcInvocationHandler.java b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcInvocationHandler.java index 3747274..6835b56 100644 --- a/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcInvocationHandler.java +++ b/modules/org.jkiss.utils/src/org/jkiss/utils/rest/RpcInvocationHandler.java @@ -1,23 +1,24 @@ /* * DBeaver - Universal Database Manager - * Copyright (C) 2010-2024 DBeaver Corp + * Copyright (C) 2010-2026 DBeaver Corp and others * - * All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * NOTICE: All information contained herein is, and remains - * the property of DBeaver Corp and its suppliers, if any. - * The intellectual and technical concepts contained - * herein are proprietary to DBeaver Corp and its suppliers - * and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * Dissemination of this information or reproduction of this material - * is strictly forbidden unless prior written permission is obtained - * from DBeaver Corp. + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.jkiss.utils.rest; import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.utils.BeanUtils; @@ -27,9 +28,13 @@ import java.net.URI; import java.util.LinkedHashMap; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; public abstract class RpcInvocationHandler implements InvocationHandler, RestProxy { + private static final Logger log = Logger.getLogger(RpcInvocationHandler.class.getName()); + @NotNull private final Class clientClass; protected final URI uri; @@ -115,11 +120,14 @@ public synchronized Object invoke(Object proxy, Method method, Object[] args) th } try { +// System.out.println("CONTENTS: " + contents); return gson.fromJson(contents, returnType); } catch (Throwable e) { + log.log(Level.WARNING, "Failed to parse json response: \n" + contents, e); //just debug breakpoint, rethrow it throw e; } + } catch (RpcException e) { if (e.getErrorClass() != null) { Throwable error = null;