Skip to content

Commit

Permalink
Fix the ServletConfig loading issue with swagger. (apache#13122)
Browse files Browse the repository at this point in the history
  • Loading branch information
abhioncbr authored May 19, 2024
1 parent e71d1c6 commit 777040b
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,8 @@
package org.apache.pinot.broker.broker;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.Executor;
Expand All @@ -35,7 +33,8 @@
import org.apache.pinot.broker.requesthandler.BrokerRequestHandler;
import org.apache.pinot.broker.routing.BrokerRoutingManager;
import org.apache.pinot.common.metrics.BrokerMetrics;
import org.apache.pinot.common.utils.PinotStaticHttpHandler;
import org.apache.pinot.common.swagger.SwaggerApiListingResource;
import org.apache.pinot.common.swagger.SwaggerSetupUtils;
import org.apache.pinot.common.utils.log.DummyLogFileServer;
import org.apache.pinot.common.utils.log.LocalLogFileServer;
import org.apache.pinot.common.utils.log.LogFileServer;
Expand All @@ -47,8 +46,6 @@
import org.apache.pinot.spi.env.PinotConfiguration;
import org.apache.pinot.spi.utils.CommonConstants;
import org.apache.pinot.spi.utils.PinotReflectionUtils;
import org.glassfish.grizzly.http.server.CLStaticHttpHandler;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.jackson.JacksonFeature;
Expand Down Expand Up @@ -122,8 +119,8 @@ protected void configure() {
register(buildBrokerManagedAsyncExecutorProvider(brokerConf, brokerMetrics));
}
register(JacksonFeature.class);
registerClasses(io.swagger.jaxrs.listing.ApiListingResource.class);
registerClasses(io.swagger.jaxrs.listing.SwaggerSerializers.class);
register(SwaggerApiListingResource.class);
register(SwaggerSerializers.class);
register(AuthenticationFilter.class);
}

Expand All @@ -137,38 +134,13 @@ public void start(List<ListenerConfig> listenerConfigs) {
}

if (_swaggerBrokerEnabled) {
PinotReflectionUtils.runWithLock(this::setupSwagger);
PinotReflectionUtils.runWithLock(() ->
SwaggerSetupUtils.setupSwagger("Broker", _brokerResourcePackages, _useHttps, "/", _httpServer));
} else {
LOGGER.info("Hiding Swagger UI for Broker, by {}", CommonConstants.Broker.CONFIG_OF_SWAGGER_BROKER_ENABLED);
}
}

private void setupSwagger() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle("Pinot Broker API");
beanConfig.setDescription("APIs for accessing Pinot broker information");
beanConfig.setContact("https://github.com/apache/pinot");
beanConfig.setVersion("1.0");
beanConfig.setExpandSuperTypes(false);
if (_useHttps) {
beanConfig.setSchemes(new String[]{CommonConstants.HTTPS_PROTOCOL});
} else {
beanConfig.setSchemes(new String[]{CommonConstants.HTTP_PROTOCOL, CommonConstants.HTTPS_PROTOCOL});
}
beanConfig.setBasePath("/");
beanConfig.setResourcePackage(_brokerResourcePackages);
beanConfig.setScan(true);

HttpHandler httpHandler = new CLStaticHttpHandler(BrokerAdminApiApplication.class.getClassLoader(), "/api/");
// map both /api and /help to swagger docs. /api because it looks nice. /help for backward compatibility
_httpServer.getServerConfiguration().addHttpHandler(httpHandler, "/api/", "/help/");

URL swaggerDistLocation =
BrokerAdminApiApplication.class.getClassLoader().getResource(CommonConstants.CONFIG_OF_SWAGGER_RESOURCES_PATH);
CLStaticHttpHandler swaggerDist = new PinotStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation}));
_httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/");
}

private BrokerManagedAsyncExecutorProvider buildBrokerManagedAsyncExecutorProvider(PinotConfiguration brokerConf,
BrokerMetrics brokerMetrics) {
int corePoolSize =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* 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.apache.pinot.common.swagger;

import io.swagger.annotations.ApiOperation;
import io.swagger.jaxrs.listing.BaseApiListingResource;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.StringUtils;


/*
This class is required to avoid the jersey2 warning messages regarding servlet config constructor missing while
injecting the config. Please refer to Pinot issue 13047 & 5306 for more context.
In this implementation, we added the ServletConfig as the class level member instead of injecting it.
*/
@Path("/swagger.{type:json|yaml}")
public class SwaggerApiListingResource extends BaseApiListingResource {
@Context
ServletContext _context;

@Context
ServletConfig _servletConfig;

@GET
@Produces({MediaType.APPLICATION_JSON, "application/yaml"})
@ApiOperation(value = "The swagger definition in either JSON or YAML", hidden = true)
public Response getListing(@Context Application app, @Context HttpHeaders headers, @Context UriInfo uriInfo,
@PathParam("type") String type) {
if (StringUtils.isNotBlank(type) && type.trim().equalsIgnoreCase("yaml")) {
return getListingYamlResponse(app, _context, _servletConfig, headers, uriInfo);
} else {
return getListingJsonResponse(app, _context, _servletConfig, headers, uriInfo);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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
*
* 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.apache.pinot.common.swagger;

import io.swagger.jaxrs.config.BeanConfig;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.UnknownHostException;
import org.apache.pinot.common.utils.PinotStaticHttpHandler;
import org.apache.pinot.spi.utils.CommonConstants;
import org.glassfish.grizzly.http.server.CLStaticHttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;


public class SwaggerSetupUtils {
private SwaggerSetupUtils() {
}

public static void setupSwagger(String componentType, String resourcePackage, boolean useHttps, String basePath,
HttpServer httpServer) {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle(String.format("Pinot %s API", componentType));
beanConfig.setDescription(String.format("APIs for accessing Pinot %s information", componentType));
beanConfig.setContact("https://github.com/apache/pinot");
beanConfig.setVersion("1.0");
beanConfig.setExpandSuperTypes(false);
if (useHttps) {
beanConfig.setSchemes(new String[]{CommonConstants.HTTPS_PROTOCOL});
} else {
beanConfig.setSchemes(new String[]{CommonConstants.HTTP_PROTOCOL, CommonConstants.HTTPS_PROTOCOL});
}
beanConfig.setBasePath(basePath);
beanConfig.setResourcePackage(resourcePackage);
beanConfig.setScan(true);

try {
beanConfig.setHost(InetAddress.getLocalHost().getHostName());
} catch (UnknownHostException e) {
throw new RuntimeException("Cannot get localhost name");
}

ClassLoader classLoader = SwaggerSetupUtils.class.getClassLoader();
CLStaticHttpHandler staticHttpHandler = new CLStaticHttpHandler(classLoader, "/api/");
// map both /api and /help to swagger docs. /api because it looks nice. /help for backward compatibility
httpServer.getServerConfiguration().addHttpHandler(staticHttpHandler, "/api/", "/help/");

URL swaggerDistLocation = classLoader.getResource(CommonConstants.CONFIG_OF_SWAGGER_RESOURCES_PATH);
CLStaticHttpHandler swaggerDist = new PinotStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation}));
httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@
*/
package org.apache.pinot.controller.api;

import io.swagger.jaxrs.config.BeanConfig;
import io.swagger.jaxrs.listing.SwaggerSerializers;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import org.apache.pinot.common.utils.PinotStaticHttpHandler;
import org.apache.pinot.common.swagger.SwaggerApiListingResource;
import org.apache.pinot.common.swagger.SwaggerSetupUtils;
import org.apache.pinot.controller.ControllerConf;
import org.apache.pinot.controller.api.access.AuthenticationFilter;
import org.apache.pinot.core.api.ServiceAutoDiscoveryFeature;
Expand Down Expand Up @@ -66,8 +65,8 @@ public ControllerAdminApiApplication(ControllerConf conf) {
}
register(JacksonFeature.class);
register(MultiPartFeature.class);
registerClasses(io.swagger.jaxrs.listing.ApiListingResource.class);
registerClasses(io.swagger.jaxrs.listing.SwaggerSerializers.class);
register(SwaggerApiListingResource.class);
register(SwaggerSerializers.class);
register(new CorsFilter());
register(AuthenticationFilter.class);
// property("jersey.config.server.tracing.type", "ALL");
Expand All @@ -86,9 +85,9 @@ public void start(List<ListenerConfig> listenerConfigs) {
} catch (IOException e) {
throw new RuntimeException("Failed to start http server", e);
}
PinotReflectionUtils.runWithLock(this::setupSwagger);

ClassLoader classLoader = ControllerAdminApiApplication.class.getClassLoader();
PinotReflectionUtils.runWithLock(() ->
SwaggerSetupUtils.setupSwagger("Controller", _controllerResourcePackages, _useHttps, "/", _httpServer));

// This is ugly from typical patterns to setup static resources but all our APIs are
// at path "/". So, configuring static handler for path "/" does not work well.
Expand All @@ -103,33 +102,6 @@ public void start(List<ListenerConfig> listenerConfigs) {
_httpServer.getServerConfiguration().addHttpHandler(new CLStaticHttpHandler(classLoader, "/webapp/js/"), "/js/");
}

private void setupSwagger() {
BeanConfig beanConfig = new BeanConfig();
beanConfig.setTitle("Pinot Controller API");
beanConfig.setDescription("APIs for accessing Pinot Controller information");
beanConfig.setContact("https://github.com/apache/pinot");
beanConfig.setVersion("1.0");
beanConfig.setExpandSuperTypes(false);
if (_useHttps) {
beanConfig.setSchemes(new String[]{CommonConstants.HTTPS_PROTOCOL});
} else {
beanConfig.setSchemes(new String[]{CommonConstants.HTTP_PROTOCOL, CommonConstants.HTTPS_PROTOCOL});
}
beanConfig.setBasePath("/");
beanConfig.setResourcePackage(_controllerResourcePackages);
beanConfig.setScan(true);

ClassLoader loader = this.getClass().getClassLoader();
CLStaticHttpHandler apiStaticHttpHandler = new CLStaticHttpHandler(loader, "/api/");
// map both /api and /help to swagger docs. /api because it looks nice. /help for backward compatibility
_httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, "/api/");
_httpServer.getServerConfiguration().addHttpHandler(apiStaticHttpHandler, "/help/");

URL swaggerDistLocation = loader.getResource(CommonConstants.CONFIG_OF_SWAGGER_RESOURCES_PATH);
CLStaticHttpHandler swaggerDist = new PinotStaticHttpHandler(new URLClassLoader(new URL[]{swaggerDistLocation}));
_httpServer.getServerConfiguration().addHttpHandler(swaggerDist, "/swaggerui-dist/");
}

public void stop() {
if (!_httpServer.isStarted()) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ public abstract class ClusterTest extends ControllerTest {
protected BaseMinionStarter _minionStarter;

private String _brokerBaseApiUrl;
private String _minionBaseApiUrl;

private boolean _useMultiStageQueryEngine = false;

Expand All @@ -129,6 +130,10 @@ protected String getBrokerBaseApiUrl() {
return _brokerBaseApiUrl;
}

public String getMinionBaseApiUrl() {
return _minionBaseApiUrl;
}

protected boolean useMultiStageQueryEngine() {
return _useMultiStageQueryEngine;
}
Expand Down Expand Up @@ -321,9 +326,10 @@ protected void startMinion()
PinotConfiguration minionConf = getDefaultMinionConfiguration();
minionConf.setProperty(Helix.CONFIG_OF_CLUSTER_NAME, getHelixClusterName());
minionConf.setProperty(Helix.CONFIG_OF_ZOOKEEPR_SERVER, getZkUrl());
minionConf.setProperty(CommonConstants.Helix.KEY_OF_MINION_PORT,
NetUtils.findOpenPort(CommonConstants.Minion.DEFAULT_HELIX_PORT));
int minionPort = NetUtils.findOpenPort(CommonConstants.Minion.DEFAULT_HELIX_PORT);
minionConf.setProperty(CommonConstants.Helix.KEY_OF_MINION_PORT, minionPort);
minionConf.setProperty(CommonConstants.CONFIG_OF_TIMEZONE, "UTC");
_minionBaseApiUrl = "http://localhost:" + minionPort;
_minionStarter = new MinionStarter();
_minionStarter.init(minionConf);
_minionStarter.start();
Expand Down
Loading

0 comments on commit 777040b

Please sign in to comment.