blob: db9eb68186c592c7420bb38c18cc1dee31bd5d18 [file] [log] [blame]
/*
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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.hadoop.ozone.recon;
import com.google.inject.Injector;
import com.google.inject.Scopes;
import com.google.inject.servlet.ServletModule;
import org.apache.hadoop.hdds.conf.ConfigurationSource;
import org.apache.hadoop.ozone.OzoneSecurityUtil;
import org.apache.hadoop.ozone.recon.api.AdminOnly;
import org.apache.hadoop.ozone.recon.api.filters.ReconAdminFilter;
import org.apache.hadoop.ozone.recon.api.filters.ReconAuthFilter;
import org.glassfish.hk2.api.ServiceLocator;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.server.spi.Container;
import org.glassfish.jersey.server.spi.ContainerLifecycleListener;
import org.glassfish.jersey.servlet.ServletContainer;
import org.jvnet.hk2.guice.bridge.api.GuiceBridge;
import org.jvnet.hk2.guice.bridge.api.GuiceIntoHK2Bridge;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.core.UriBuilder;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED;
import static org.apache.hadoop.ozone.OzoneConfigKeys.OZONE_ACL_ENABLED_DEFAULT;
/**
* Class to scan API Service classes and bind them to the injector.
*/
public class ReconRestServletModule extends ServletModule {
public static final String BASE_API_PATH = UriBuilder.fromPath("/api").path(
"v1").build().toString();
public static final String API_PACKAGE = "org.apache.hadoop.ozone.recon.api";
private static final Logger LOG =
LoggerFactory.getLogger(ReconRestServletModule.class);
private final ConfigurationSource conf;
public ReconRestServletModule(ConfigurationSource conf) {
this.conf = conf;
}
@Override
protected void configureServlets() {
configureApi(BASE_API_PATH, API_PACKAGE);
}
private void configureApi(String baseApiPath, String... packages) {
StringBuilder sb = new StringBuilder();
Set<String> adminEndpoints = new HashSet<>();
for (String pkg : packages) {
if (sb.length() > 0) {
sb.append(',');
}
checkIfPackageExistsAndLog(pkg, baseApiPath);
sb.append(pkg);
// Check for classes marked as admin only that will need an extra
// filter applied to their path.
Reflections reflections = new Reflections(pkg,
new TypeAnnotationsScanner(), new SubTypesScanner());
Set<Class<?>> adminEndpointClasses =
reflections.getTypesAnnotatedWith(AdminOnly.class);
adminEndpointClasses.stream()
.map(clss -> UriBuilder.fromResource(clss).build().toString())
.forEachOrdered(adminEndpoints::add);
if (LOG.isDebugEnabled()) {
LOG.debug("Registered the following endpoint classes as admin only: {}",
adminEndpointClasses);
}
}
Map<String, String> params = new HashMap<>();
params.put("javax.ws.rs.Application",
GuiceResourceConfig.class.getCanonicalName());
if (sb.length() > 0) {
params.put("jersey.config.server.provider.packages", sb.toString());
}
bind(ServletContainer.class).in(Scopes.SINGLETON);
String allApiPath =
UriBuilder.fromPath(baseApiPath).path("*").build().toString();
serve(allApiPath).with(ServletContainer.class, params);
addFilters(baseApiPath, adminEndpoints);
}
private void addFilters(String basePath, Set<String> adminSubPaths) {
if (OzoneSecurityUtil.isHttpSecurityEnabled(conf)) {
String authPath =
UriBuilder.fromPath(basePath).path("*").build().toString();
filter(authPath).through(ReconAuthFilter.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Added authentication filter to path {}", authPath);
}
boolean aclEnabled = conf.getBoolean(OZONE_ACL_ENABLED,
OZONE_ACL_ENABLED_DEFAULT);
if (aclEnabled) {
for (String path: adminSubPaths) {
String adminPath =
UriBuilder.fromPath(basePath).path(path + "*").build().toString();
filter(adminPath).through(ReconAdminFilter.class);
if (LOG.isDebugEnabled()) {
LOG.debug("Added admin filter to path {}", adminPath);
}
}
}
}
}
private void checkIfPackageExistsAndLog(String pkg, String path) {
String resourcePath = pkg.replace(".", "/");
URL resource = getClass().getClassLoader().getResource(resourcePath);
if (resource != null) {
if (LOG.isDebugEnabled()) {
LOG.debug("Using API endpoints from package {} for paths under {}.",
pkg, path);
}
} else {
LOG.warn("No Beans in '{}' found. Requests {} will fail.", pkg, path);
}
}
}
/**
* Class to bridge Guice bindings to Jersey hk2 bindings.
*/
class GuiceResourceConfig extends ResourceConfig {
GuiceResourceConfig() {
register(new ContainerLifecycleListener() {
@Override
public void onStartup(Container container) {
ServletContainer servletContainer = (ServletContainer) container;
InjectionManager injectionManager = container.getApplicationHandler()
.getInjectionManager();
ServiceLocator serviceLocator = injectionManager
.getInstance(ServiceLocator.class);
GuiceBridge.getGuiceBridge().initializeGuiceBridge(serviceLocator);
GuiceIntoHK2Bridge guiceBridge = serviceLocator
.getService(GuiceIntoHK2Bridge.class);
Injector injector = (Injector) servletContainer.getServletContext()
.getAttribute(Injector.class.getName());
guiceBridge.bridgeGuiceInjector(injector);
}
@Override
public void onReload(Container container) {
}
@Override
public void onShutdown(Container container) {
}
});
}
}