/**
* Copyright (C) 2014-2016 LinkedIn Corp. (pinot-core@linkedin.com)
*
* 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
*
* 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 com.linkedin.pinot.controller.api;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.Restlet;
import org.restlet.resource.Directory;
import org.restlet.resource.ServerResource;
import org.restlet.routing.Filter;
import org.restlet.routing.Redirector;
import org.restlet.routing.Router;
import org.restlet.routing.Template;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.linkedin.pinot.common.metrics.ControllerMetrics;
import com.linkedin.pinot.common.restlet.PinotRestletApplication;
import com.linkedin.pinot.common.restlet.swagger.SwaggerResource;
import com.linkedin.pinot.controller.api.restlet.resources.BasePinotControllerRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.LLCSegmentCommit;
import com.linkedin.pinot.controller.api.restlet.resources.LLCSegmentConsumed;
import com.linkedin.pinot.controller.api.restlet.resources.LLCSegmentStoppedConsuming;
import com.linkedin.pinot.controller.api.restlet.resources.PinotControllerHealthCheck;
import com.linkedin.pinot.controller.api.restlet.resources.PinotInstanceRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotSchemaRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotSegmentRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotSegmentUploadRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableIndexingConfigs;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableInstances;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableMetadataConfigs;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableSchema;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableSegmentConfigs;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTableTenantConfigs;
import com.linkedin.pinot.controller.api.restlet.resources.PinotTenantRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PinotVersionRestletResource;
import com.linkedin.pinot.controller.api.restlet.resources.PqlQueryResource;
import com.linkedin.pinot.controller.api.restlet.resources.TableSize;
import com.linkedin.pinot.controller.api.restlet.resources.TableViews;
public class ControllerRestApplication extends PinotRestletApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(ControllerRestApplication.class);
private static final String ONE_HOUR_IN_MILLIS = Long.toString(3600L * 1000L);
private static String CONSOLE_WEBAPP_ROOT_PATH;
private static Router router;
private static ControllerMetrics metrics;
public ControllerRestApplication(String queryConsolePath) {
super();
CONSOLE_WEBAPP_ROOT_PATH = queryConsolePath;
}
public static void setControllerMetrics(ControllerMetrics controllerMetrics) {
metrics = controllerMetrics;
}
public static ControllerMetrics getControllerMetrics() { return metrics; }
protected void configureRouter(Router router) {
// Remove server-side HTTP timeout (see http://stackoverflow.com/questions/12943447/restlet-server-socket-timeout)
getContext().getParameters().add("maxIoIdleTimeMs", ONE_HOUR_IN_MILLIS);
getContext().getParameters().add("ioMaxIdleTimeMs", ONE_HOUR_IN_MILLIS);
router.setDefaultMatchingMode(Template.MODE_EQUALS);
/**
* Start Routers 2.0
*/
attachRoutesForClass(router, PinotTenantRestletResource.class);
attachRoutesForClass(router, PinotSchemaRestletResource.class);
attachRoutesForClass(router, PinotTableRestletResource.class);
// GET
attachRoutesForClass(router, PinotTableInstances.class);
attachRoutesForClass(router, PinotTableSchema.class);
attachRoutesForClass(router, PinotSegmentRestletResource.class);
attachRoutesForClass(router, TableSize.class);
// PUT
attachRoutesForClass(router, PinotTableSegmentConfigs.class);
attachRoutesForClass(router, PinotTableIndexingConfigs.class);
attachRoutesForClass(router, PinotTableTenantConfigs.class);
attachRoutesForClass(router, PinotTableMetadataConfigs.class);
// Uploading Downloading segments
attachRoutesForClass(router, PinotSegmentUploadRestletResource.class);
attachRoutesForClass(router, PinotVersionRestletResource.class);
// SegmentCompletionProtocol implementation on the controller
attachRoutesForClass(router, LLCSegmentCommit.class);
attachRoutesForClass(router, LLCSegmentConsumed.class);
attachRoutesForClass(router, LLCSegmentStoppedConsuming.class);
// GET... add it here because it can block visibility of
// some of the existing paths (like indexingConfigs) added above
attachRoutesForClass(router, TableViews.class);
router.attach("/api", SwaggerResource.class);
/**
* End Routes 2.0
*/
attachRoutesForClass(router, PinotInstanceRestletResource.class);
router.attach("/pinot-controller/admin", PinotControllerHealthCheck.class);
router.attach("/pql", PqlQueryResource.class);
try {
final Directory webdir = new Directory(getContext(), CONSOLE_WEBAPP_ROOT_PATH);
webdir.setDeeplyAccessible(true);
webdir.setIndexName("index.html");
router.attach("/query", webdir);
} catch (Exception e) {
LOGGER.warn("Failed to initialize route for /query", e);
}
try {
final Directory swaggerIndexDir = new Directory(getContext(), getClass().getClassLoader().getResource("swagger-ui").toString());
swaggerIndexDir.setIndexName("index.html");
swaggerIndexDir.setDeeplyAccessible(true);
router.attach("/swagger-ui", swaggerIndexDir);
} catch (Exception e) {
LOGGER.warn("Failed to initialize route for /swagger-ui", e);
}
try {
final Directory swaggerUiDir = new Directory(getContext(), getClass().getClassLoader().getResource("META-INF/resources/webjars/swagger-ui/2.2.2").toString());
swaggerUiDir.setDeeplyAccessible(true);
router.attach("/swaggerui-dist", swaggerUiDir);
} catch (Exception e) {
LOGGER.warn("Failed to initialize route for /swaggerui-dist", e);
}
try {
final Redirector redirector = new Redirector(getContext(), "/swagger-ui/index.html?url=/api", Redirector.MODE_CLIENT_TEMPORARY);
router.attach("/help", redirector);
} catch (Exception e) {
LOGGER.warn("Failed to initialize route for /help", e);
}
try {
final Directory landing = new Directory(getContext(), getClass().getClassLoader().getResource("landing").toString());
landing.setDeeplyAccessible(false);
landing.setIndexName("index.html");
router.attach("/", landing);
} catch (Exception e) {
LOGGER.warn("Failed to initialize route for /", e);
}
}
@Override
protected void attachRoute(Router router, String routePath, Class<? extends ServerResource> clazz) {
router.attach(routePath, new AddHeaderFilter(getContext(), createFinder(clazz)));
}
private class AddHeaderFilter extends Filter {
public AddHeaderFilter(Context context, Restlet next) {
super(context, next);
}
@Override
protected int beforeHandle(Request request, Response response) {
BasePinotControllerRestletResource.addExtraHeaders(response);
return Filter.CONTINUE;
}
}
public static Router getRouter() {
return router;
}
}