aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java1
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java27
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/Container.java11
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/Osgi.java5
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/componentgraph/core/JerseyNode.java92
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/config/RestApiContext.java98
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/osgi/BundleClasses.java27
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/osgi/OsgiUtil.java168
-rw-r--r--container-core/src/main/java/com/yahoo/container/di/osgi/package-info.java8
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApi.java59
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java341
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java172
-rw-r--r--container-core/src/main/java/com/yahoo/restapi/RestApiRequestHandler.java2
-rw-r--r--container-core/src/main/resources/configdefinitions/container.di.config.jersey-bundles.def8
-rw-r--r--container-core/src/main/resources/configdefinitions/container.di.config.jersey-injection.def5
-rw-r--r--container-core/src/test/java/com/yahoo/container/di/ContainerTest.java61
-rw-r--r--container-core/src/test/java/com/yahoo/container/di/ContainerTestBase.java6
-rw-r--r--container-core/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java23
-rw-r--r--container-core/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java68
-rw-r--r--container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java35
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java21
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java7
-rw-r--r--vespajlib/src/main/java/com/yahoo/osgi/maven/ProjectBundleClassPaths.java128
-rw-r--r--vespajlib/src/test/java/com/yahoo/osgi/maven/ProjectBundleClassPathsTest.java36
24 files changed, 427 insertions, 982 deletions
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index 4b0e30e04da..944a989d79f 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -89,7 +89,6 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"baldersheim"}) default boolean dryRunOnnxOnSetup() { throw new UnsupportedOperationException("TODO specify default value"); }
@ModelFeatureFlag(owners = {"geirst"}) default boolean enableFeedBlockInDistributor() { return true; }
@ModelFeatureFlag(owners = {"hmusum"}, removeAfter = "7.428") default int clusterControllerMaxHeapSizeInMb() { return 128; }
- @ModelFeatureFlag(owners = {"hmusum"}, removeAfter = "7.422") default int metricsProxyMaxHeapSizeInMb(ClusterSpec.Type type) { return 256; }
@ModelFeatureFlag(owners = {"bjorncs", "tokle"}) default List<String> allowedAthenzProxyIdentities() { return List.of(); }
@ModelFeatureFlag(owners = {"tokle"}) default boolean tenantIamRole() { return false; }
@ModelFeatureFlag(owners = {"vekterli"}) default int maxActivationInhibitedOutOfSyncGroups() { return 0; }
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
index 08a468a3031..1d63fb2312a 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/HandlersConfigurerDi.java
@@ -5,7 +5,6 @@ import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.yahoo.component.AbstractComponent;
-import com.yahoo.component.ComponentSpecification;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.concurrent.ThreadFactoryFactory;
import com.yahoo.config.FileReference;
@@ -13,8 +12,6 @@ import com.yahoo.container.di.ComponentDeconstructor;
import com.yahoo.container.di.Container;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
import com.yahoo.container.di.config.SubscriberFactory;
-import com.yahoo.container.di.osgi.BundleClasses;
-import com.yahoo.container.di.osgi.OsgiUtil;
import com.yahoo.container.logging.AccessLog;
import com.yahoo.filedistribution.fileacquirer.FileAcquirer;
import com.yahoo.jdisc.application.OsgiFramework;
@@ -25,7 +22,6 @@ import com.yahoo.osgi.OsgiImpl;
import com.yahoo.osgi.OsgiWrapper;
import com.yahoo.statistics.Statistics;
import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleWiring;
import java.util.ArrayList;
import java.util.Collection;
@@ -35,8 +31,6 @@ import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
-import static com.yahoo.collections.CollectionUtil.first;
-
/**
* For internal use only.
*
@@ -94,27 +88,6 @@ public class HandlersConfigurerDi {
platformBundleLoader = new PlatformBundleLoader(this);
}
-
- // TODO Vespa 8: Remove, only used for Jersey
- @Override
- public BundleClasses getBundleClasses(ComponentSpecification bundleSpec, Set<String> packagesToScan) {
- //Temporary hack: Using class name since ClassLoaderOsgiFramework is not available at compile time in this bundle.
- if (osgiFramework.getClass().getName().equals("com.yahoo.application.container.impl.ClassLoaderOsgiFramework")) {
- Bundle syntheticClassPathBundle = first(osgiFramework.bundles());
- ClassLoader classLoader = syntheticClassPathBundle.adapt(BundleWiring.class).getClassLoader();
-
- return new BundleClasses(
- syntheticClassPathBundle,
- OsgiUtil.getClassEntriesForBundleUsingProjectClassPathMappings(classLoader, bundleSpec, packagesToScan));
- } else {
- Bundle bundle = getBundle(bundleSpec);
- if (bundle == null)
- throw new RuntimeException("No bundle matching '" + bundleSpec + "'");
-
- return new BundleClasses(bundle, OsgiUtil.getClassEntriesInBundleClassPath(bundle, packagesToScan));
- }
- }
-
@Override
public void installPlatformBundles(Collection<String> bundlePaths) {
// Don't install physical bundles for test frameworks, where all platform bundles are on the classpath.
diff --git a/container-core/src/main/java/com/yahoo/container/di/Container.java b/container-core/src/main/java/com/yahoo/container/di/Container.java
index a91aa6eb588..2bd8b2a90ec 100644
--- a/container-core/src/main/java/com/yahoo/container/di/Container.java
+++ b/container-core/src/main/java/com/yahoo/container/di/Container.java
@@ -12,11 +12,9 @@ import com.yahoo.container.di.ConfigRetriever.ComponentsConfigs;
import com.yahoo.container.di.ConfigRetriever.ConfigSnapshot;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
import com.yahoo.container.di.componentgraph.core.ComponentNode;
-import com.yahoo.container.di.componentgraph.core.JerseyNode;
import com.yahoo.container.di.componentgraph.core.Node;
import com.yahoo.container.di.config.ApplicationBundlesConfig;
import com.yahoo.container.di.config.PlatformBundlesConfig;
-import com.yahoo.container.di.config.RestApiContext;
import com.yahoo.container.di.config.SubscriberFactory;
import com.yahoo.vespa.config.ConfigKey;
import org.osgi.framework.Bundle;
@@ -200,14 +198,7 @@ public class Container {
for (ComponentsConfig.Components config : componentsConfig.components()) {
BundleInstantiationSpecification specification = bundleInstantiationSpecification(config);
Class<?> componentClass = osgi.resolveClass(specification);
- Node componentNode;
-
- if (RestApiContext.class.isAssignableFrom(componentClass)) {
- Class<? extends RestApiContext> nodeClass = componentClass.asSubclass(RestApiContext.class);
- componentNode = new JerseyNode(specification.id, config.configId(), nodeClass, osgi);
- } else {
- componentNode = new ComponentNode(specification.id, config.configId(), componentClass, null);
- }
+ Node componentNode = new ComponentNode(specification.id, config.configId(), componentClass, null);
graph.add(componentNode);
}
}
diff --git a/container-core/src/main/java/com/yahoo/container/di/Osgi.java b/container-core/src/main/java/com/yahoo/container/di/Osgi.java
index 940986e2f38..2ba93171081 100644
--- a/container-core/src/main/java/com/yahoo/container/di/Osgi.java
+++ b/container-core/src/main/java/com/yahoo/container/di/Osgi.java
@@ -5,11 +5,9 @@ import com.yahoo.component.ComponentSpecification;
import com.yahoo.config.FileReference;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.bundle.MockBundle;
-import com.yahoo.container.di.osgi.BundleClasses;
import org.osgi.framework.Bundle;
import java.util.Collection;
-import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
@@ -21,9 +19,6 @@ import static java.util.Collections.emptySet;
* @author ollivir
*/
public interface Osgi {
- default BundleClasses getBundleClasses(ComponentSpecification bundle, Set<String> packagesToScan) {
- return new BundleClasses(new MockBundle(), Collections.emptySet());
- }
default void installPlatformBundles(Collection<String> bundlePaths) {
System.out.println("installPlatformBundles " + bundlePaths);
diff --git a/container-core/src/main/java/com/yahoo/container/di/componentgraph/core/JerseyNode.java b/container-core/src/main/java/com/yahoo/container/di/componentgraph/core/JerseyNode.java
deleted file mode 100644
index 0f8aa678934..00000000000
--- a/container-core/src/main/java/com/yahoo/container/di/componentgraph/core/JerseyNode.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.di.componentgraph.core;
-
-import com.yahoo.component.ComponentId;
-import com.yahoo.component.ComponentSpecification;
-import com.yahoo.container.di.Osgi;
-import com.yahoo.container.di.config.JerseyBundlesConfig;
-import com.yahoo.container.di.config.RestApiContext;
-import com.yahoo.container.di.config.RestApiContext.BundleInfo;
-import com.yahoo.container.di.osgi.BundleClasses;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleWiring;
-
-import java.net.URL;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.List;
-
-/**
- * Represents an instance of RestApiContext
- *
- * @author gjoranv
- * @author Tony Vaagenes
- * @author ollivir
- */
-public class JerseyNode extends ComponentNode {
- private static final String WEB_INF_URL = "WebInfUrl";
-
- private final Osgi osgi;
-
- public JerseyNode(ComponentId componentId, String configId, Class<?> clazz, Osgi osgi) {
- super(componentId, configId, clazz, null);
- this.osgi = osgi;
- }
-
- @Override
- protected RestApiContext newInstance() {
- Object instance = super.newInstance();
- RestApiContext restApiContext = (RestApiContext) instance;
-
- List<JerseyBundlesConfig.Bundles> bundles = restApiContext.bundlesConfig.bundles();
- for (JerseyBundlesConfig.Bundles bundleConfig : bundles) {
- BundleClasses bundleClasses = osgi.getBundleClasses(ComponentSpecification.fromString(bundleConfig.spec()),
- new HashSet<>(bundleConfig.packages()));
-
- restApiContext.addBundle(createBundleInfo(bundleClasses.bundle(), bundleClasses.classEntries()));
- }
-
- componentsToInject.forEach(component -> restApiContext.addInjectableComponent(component.instanceKey(), component.componentId(),
- component.component()));
-
- return restApiContext;
- }
-
- @Override
- public int hashCode() {
- return super.hashCode();
- }
-
- @Override
- public boolean equals(Object other) {
- return super.equals(other)
- && (other instanceof JerseyNode && this.componentsToInject.equals(((JerseyNode) other).componentsToInject));
- }
-
- public static BundleInfo createBundleInfo(Bundle bundle, Collection<String> classEntries) {
- BundleInfo bundleInfo = new BundleInfo(bundle.getSymbolicName(), bundle.getVersion(), bundle.getLocation(), webInfUrl(bundle),
- bundle.adapt(BundleWiring.class).getClassLoader());
-
- bundleInfo.setClassEntries(classEntries);
- return bundleInfo;
- }
-
- public static Bundle getBundle(Osgi osgi, String bundleSpec) {
- Bundle bundle = osgi.getBundle(ComponentSpecification.fromString(bundleSpec));
- if (bundle == null) {
- throw new IllegalArgumentException("Bundle not found: " + bundleSpec);
- }
- return bundle;
- }
-
- private static URL webInfUrl(Bundle bundle) {
- String webInfUrlHeader = bundle.getHeaders().get(WEB_INF_URL);
-
- if (webInfUrlHeader == null) {
- return null;
- } else {
- return bundle.getEntry(webInfUrlHeader);
- }
- }
-
-}
diff --git a/container-core/src/main/java/com/yahoo/container/di/config/RestApiContext.java b/container-core/src/main/java/com/yahoo/container/di/config/RestApiContext.java
deleted file mode 100644
index bfb9a8f9160..00000000000
--- a/container-core/src/main/java/com/yahoo/container/di/config/RestApiContext.java
+++ /dev/null
@@ -1,98 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.di.config;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.inject.Inject;
-import com.google.inject.Key;
-import com.yahoo.component.ComponentId;
-import org.osgi.framework.Version;
-
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.Set;
-
-/**
- * Only for internal JDisc use.
- *
- * @author gjoranv
- */
-public class RestApiContext {
-
- private final List<BundleInfo> bundles = new ArrayList<>();
- private final List<Injectable> injectableComponents = new ArrayList<>();
-
- public final JerseyBundlesConfig bundlesConfig;
- public final JerseyInjectionConfig injectionConfig;
-
- @Inject
- public RestApiContext(JerseyBundlesConfig bundlesConfig, JerseyInjectionConfig injectionConfig) {
- this.bundlesConfig = bundlesConfig;
- this.injectionConfig = injectionConfig;
- }
-
- public List<BundleInfo> getBundles() {
- return Collections.unmodifiableList(bundles);
- }
-
- public void addBundle(BundleInfo bundle) {
- bundles.add(bundle);
- }
-
- public List<Injectable> getInjectableComponents() {
- return Collections.unmodifiableList(injectableComponents);
- }
-
- public void addInjectableComponent(Key<?> key, ComponentId id, Object component) {
- injectableComponents.add(new Injectable(key, id, component));
- }
-
- public static class Injectable {
- public final Key<?> key;
- public final ComponentId id;
- public final Object instance;
-
- public Injectable(Key<?> key, ComponentId id, Object instance) {
- this.key = key;
- this.id = id;
- this.instance = instance;
- }
- @Override
- public String toString() {
- return id.toString();
- }
- }
-
- public static class BundleInfo {
- public final String symbolicName;
- public final Version version;
- public final String fileLocation;
- public final URL webInfUrl;
- public final ClassLoader classLoader;
-
- private Set<String> classEntries;
-
- public BundleInfo(String symbolicName, Version version, String fileLocation, URL webInfUrl, ClassLoader classLoader) {
- this.symbolicName = symbolicName;
- this.version = version;
- this.fileLocation = fileLocation;
- this.webInfUrl = webInfUrl;
- this.classLoader = classLoader;
- }
-
- @Override
- public String toString() {
- return symbolicName + ":" + version;
- }
-
- public void setClassEntries(Collection<String> entries) {
- this.classEntries = ImmutableSet.copyOf(entries);
- }
-
- public Set<String> getClassEntries() {
- return classEntries;
- }
- }
-}
diff --git a/container-core/src/main/java/com/yahoo/container/di/osgi/BundleClasses.java b/container-core/src/main/java/com/yahoo/container/di/osgi/BundleClasses.java
deleted file mode 100644
index bca3ed73d0b..00000000000
--- a/container-core/src/main/java/com/yahoo/container/di/osgi/BundleClasses.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.di.osgi;
-
-import org.osgi.framework.Bundle;
-
-import java.util.Collection;
-
-/**
- * @author ollivir
- */
-public class BundleClasses {
- private final Bundle bundle;
- private final Collection<String> classEntries;
-
- public BundleClasses(Bundle bundle, Collection<String> classEntries) {
- this.bundle = bundle;
- this.classEntries = classEntries;
- }
-
- public Bundle bundle() {
- return bundle;
- }
-
- public Collection<String> classEntries() {
- return classEntries;
- }
-}
diff --git a/container-core/src/main/java/com/yahoo/container/di/osgi/OsgiUtil.java b/container-core/src/main/java/com/yahoo/container/di/osgi/OsgiUtil.java
deleted file mode 100644
index e1854155e5b..00000000000
--- a/container-core/src/main/java/com/yahoo/container/di/osgi/OsgiUtil.java
+++ /dev/null
@@ -1,168 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.di.osgi;
-
-import com.yahoo.component.ComponentSpecification;
-import com.yahoo.osgi.maven.ProjectBundleClassPaths;
-import com.yahoo.osgi.maven.ProjectBundleClassPaths.BundleClasspathMapping;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.wiring.BundleWiring;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Set;
-import java.util.function.Predicate;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-import java.util.stream.Collectors;
-
-import static com.google.common.io.Files.fileTreeTraverser;
-
-/**
- * Tested by com.yahoo.application.container.jersey.JerseyTest
- *
- * @author Tony Vaagenes
- * @author ollivir
- */
-public class OsgiUtil {
- private static final Logger log = Logger.getLogger(OsgiUtil.class.getName());
- private static final String CLASS_FILE_TYPE_SUFFIX = ".class";
-
- public static Collection<String> getClassEntriesInBundleClassPath(Bundle bundle, Set<String> packagesToScan) {
- BundleWiring bundleWiring = bundle.adapt(BundleWiring.class);
-
- if (packagesToScan.isEmpty()) {
- return bundleWiring.listResources("/", "*" + CLASS_FILE_TYPE_SUFFIX,
- BundleWiring.LISTRESOURCES_LOCAL | BundleWiring.LISTRESOURCES_RECURSE);
- } else {
- List<String> ret = new ArrayList<>();
- for (String pkg : packagesToScan) {
- ret.addAll(bundleWiring.listResources(packageToPath(pkg), "*" + CLASS_FILE_TYPE_SUFFIX, BundleWiring.LISTRESOURCES_LOCAL));
- }
- return ret;
- }
- }
-
- public static Collection<String> getClassEntriesForBundleUsingProjectClassPathMappings(ClassLoader classLoader,
- ComponentSpecification bundleSpec, Set<String> packagesToScan) {
- return classEntriesFrom(bundleClassPathMapping(bundleSpec, classLoader).classPathElements, packagesToScan);
- }
-
- private static BundleClasspathMapping bundleClassPathMapping(ComponentSpecification bundleSpec, ClassLoader classLoader) {
- ProjectBundleClassPaths projectBundleClassPaths = loadProjectBundleClassPaths(classLoader);
-
- if (projectBundleClassPaths.mainBundle.bundleSymbolicName.equals(bundleSpec.getName())) {
- return projectBundleClassPaths.mainBundle;
- } else {
- log.log(Level.WARNING,
- "Dependencies of the bundle " + bundleSpec + " will not be scanned. Please file a feature request if you need this");
- return matchingBundleClassPathMapping(bundleSpec, projectBundleClassPaths.providedDependencies);
- }
- }
-
- public static BundleClasspathMapping matchingBundleClassPathMapping(ComponentSpecification bundleSpec,
- Collection<BundleClasspathMapping> providedBundlesClassPathMappings) {
- for (BundleClasspathMapping mapping : providedBundlesClassPathMappings) {
- if (mapping.bundleSymbolicName.equals(bundleSpec.getName())) {
- return mapping;
- }
- }
- throw new RuntimeException("No such bundle: " + bundleSpec);
- }
-
- private static ProjectBundleClassPaths loadProjectBundleClassPaths(ClassLoader classLoader) {
- URL classPathMappingsFileLocation = classLoader.getResource(ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME);
- if (classPathMappingsFileLocation == null) {
- throw new RuntimeException("Couldn't find " + ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME + " in the class path.");
- }
-
- try {
- return ProjectBundleClassPaths.load(Paths.get(classPathMappingsFileLocation.toURI()));
- } catch (IOException | URISyntaxException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static Collection<String> classEntriesFrom(List<String> classPathEntries, Set<String> packagesToScan) {
- Set<String> packagePathsToScan = packagesToScan.stream().map(OsgiUtil::packageToPath).collect(Collectors.toSet());
- List<String> ret = new ArrayList<>();
-
- for (String entry : classPathEntries) {
- Path path = Paths.get(entry);
- if (Files.isDirectory(path)) {
- ret.addAll(classEntriesInPath(path, packagePathsToScan));
- } else if (Files.isRegularFile(path) && path.getFileName().toString().endsWith(".jar")) {
- ret.addAll(classEntriesInJar(path, packagePathsToScan));
- } else {
- throw new RuntimeException("Unsupported path " + path + " in the class path");
- }
- }
- return ret;
- }
-
- private static String relativePathToClass(Path rootPath, Path pathToClass) {
- Path relativePath = rootPath.relativize(pathToClass);
- return relativePath.toString();
- }
-
- private static Collection<String> classEntriesInPath(Path rootPath, Collection<String> packagePathsToScan) {
- Iterable<File> fileIterator;
- if (packagePathsToScan.isEmpty()) {
- fileIterator = fileTreeTraverser().preOrderTraversal(rootPath.toFile());
- } else {
- List<File> files = new ArrayList<>();
- for (String packagePath : packagePathsToScan) {
- for (File file : fileTreeTraverser().children(rootPath.resolve(packagePath).toFile())) {
- files.add(file);
- }
- }
- fileIterator = files;
- }
-
- List<String> ret = new ArrayList<>();
- for (File file : fileIterator) {
- if (file.isFile() && file.getName().endsWith(CLASS_FILE_TYPE_SUFFIX)) {
- ret.add(relativePathToClass(rootPath, file.toPath()));
- }
- }
- return ret;
- }
-
- private static String packagePath(String name) {
- int index = name.lastIndexOf('/');
- if (index < 0) {
- return name;
- } else {
- return name.substring(0, index);
- }
- }
-
- private static Collection<String> classEntriesInJar(Path jarPath, Set<String> packagePathsToScan) {
- Predicate<String> acceptedPackage;
- if (packagePathsToScan.isEmpty()) {
- acceptedPackage = ign -> true;
- } else {
- acceptedPackage = name -> packagePathsToScan.contains(packagePath(name));
- }
-
- try (JarFile jarFile = new JarFile(jarPath.toFile())) {
- return jarFile.stream().map(JarEntry::getName).filter(name -> name.endsWith(CLASS_FILE_TYPE_SUFFIX)).filter(acceptedPackage)
- .collect(Collectors.toList());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
- }
-
- private static String packageToPath(String packageName) {
- return packageName.replace('.', '/');
- }
-}
diff --git a/container-core/src/main/java/com/yahoo/container/di/osgi/package-info.java b/container-core/src/main/java/com/yahoo/container/di/osgi/package-info.java
deleted file mode 100644
index 9685cf571bd..00000000000
--- a/container-core/src/main/java/com/yahoo/container/di/osgi/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-/**
- * @author Tony Vaagenes
- */
-@ExportPackage
-package com.yahoo.container.di.osgi;
-
-import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApi.java b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
index 6f5bf298de3..2ef14679553 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApi.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApi.java
@@ -2,8 +2,10 @@
package com.yahoo.restapi;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.container.jdisc.AclMapping;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.RequestHandlerSpec;
import java.io.InputStream;
import java.util.List;
@@ -20,38 +22,78 @@ public interface RestApi {
static Builder builder() { return new RestApiImpl.BuilderImpl(); }
static RouteBuilder route(String pathPattern) { return new RestApiImpl.RouteBuilderImpl(pathPattern); }
+ static HandlerConfigBuilder handlerConfig() { return new RestApiImpl.HandlerConfigBuilderImpl(); }
HttpResponse handleRequest(HttpRequest request);
ObjectMapper jacksonJsonMapper();
+ /** @see com.yahoo.container.jdisc.HttpRequestHandler#requestHandlerSpec() */
+ RequestHandlerSpec requestHandlerSpec();
+
interface Builder {
Builder setObjectMapper(ObjectMapper mapper);
Builder setDefaultRoute(RouteBuilder route);
Builder addRoute(RouteBuilder route);
Builder addFilter(Filter filter);
+ /** see {@link RestApiMappers#DEFAULT_EXCEPTION_MAPPERS} for default mappers */
<EXCEPTION extends RuntimeException> Builder addExceptionMapper(Class<EXCEPTION> type, ExceptionMapper<EXCEPTION> mapper);
+ /** see {@link RestApiMappers#DEFAULT_RESPONSE_MAPPERS} for default mappers */
<RESPONSE_ENTITY> Builder addResponseMapper(Class<RESPONSE_ENTITY> type, ResponseMapper<RESPONSE_ENTITY> mapper);
+ /** see {@link RestApiMappers#DEFAULT_REQUEST_MAPPERS} for default mappers */
<REQUEST_ENTITY> Builder addRequestMapper(Class<REQUEST_ENTITY> type, RequestMapper<REQUEST_ENTITY> mapper);
<RESPONSE_ENTITY> Builder registerJacksonResponseEntity(Class<RESPONSE_ENTITY> type);
<REQUEST_ENTITY> Builder registerJacksonRequestEntity(Class<REQUEST_ENTITY> type);
+ /** Disables mappers listed in {@link RestApiMappers#DEFAULT_EXCEPTION_MAPPERS} */
Builder disableDefaultExceptionMappers();
+ /** Disables mappers listed in {@link RestApiMappers#DEFAULT_RESPONSE_MAPPERS} */
Builder disableDefaultResponseMappers();
+ Builder disableDefaultAclMapping();
RestApi build();
}
interface RouteBuilder {
RouteBuilder name(String name);
+ RouteBuilder addFilter(Filter filter);
+
+ // GET
RouteBuilder get(Handler<?> handler);
+ RouteBuilder get(Handler<?> handler, HandlerConfigBuilder config);
+
+ // POST
RouteBuilder post(Handler<?> handler);
- <REQUEST_ENTITY> RouteBuilder post(Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ <REQUEST_ENTITY> RouteBuilder post(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ RouteBuilder post(Handler<?> handler, HandlerConfigBuilder config);
+ <REQUEST_ENTITY> RouteBuilder post(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config);
+
+ // PUT
RouteBuilder put(Handler<?> handler);
- <REQUEST_ENTITY> RouteBuilder put(Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ <REQUEST_ENTITY> RouteBuilder put(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ RouteBuilder put(Handler<?> handler, HandlerConfigBuilder config);
+ <REQUEST_ENTITY> RouteBuilder put(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config);
+
+ // DELETE
RouteBuilder delete(Handler<?> handler);
+ RouteBuilder delete(Handler<?> handler, HandlerConfigBuilder config);
+
+ // PATCH
RouteBuilder patch(Handler<?> handler);
- <REQUEST_ENTITY> RouteBuilder patch(Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ <REQUEST_ENTITY> RouteBuilder patch(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ RouteBuilder patch(Handler<?> handler, HandlerConfigBuilder config);
+ <REQUEST_ENTITY> RouteBuilder patch(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config);
+
+ // Default
RouteBuilder defaultHandler(Handler<?> handler);
- <REQUEST_ENTITY> RouteBuilder defaultHandler(Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
- RouteBuilder addFilter(Filter filter);
+ RouteBuilder defaultHandler(Handler<?> handler, HandlerConfigBuilder config);
+ <REQUEST_ENTITY> RouteBuilder defaultHandler(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler);
+ <REQUEST_ENTITY> RouteBuilder defaultHandler(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config);
}
@FunctionalInterface interface Handler<RESPONSE_ENTITY> {
@@ -70,6 +112,12 @@ public interface RestApi {
@FunctionalInterface interface Filter { HttpResponse filterRequest(FilterContext context); }
+ interface HandlerConfigBuilder {
+ HandlerConfigBuilder withReadAclAction();
+ HandlerConfigBuilder withWriteAclAction();
+ HandlerConfigBuilder withCustomAclAction(AclMapping.Action action);
+ }
+
interface RequestContext {
HttpRequest request();
PathParameters pathParameters();
@@ -80,6 +128,7 @@ public interface RestApi {
RequestContent requestContentOrThrow();
ObjectMapper jacksonJsonMapper();
UriBuilder uriBuilder();
+ AclMapping.Action aclAction();
interface Parameters {
Optional<String> getString(String name);
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
index d63add5ed1d..646177e60db 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiImpl.java
@@ -1,20 +1,20 @@
// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.restapi;
-import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.container.jdisc.AclMapping;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.RequestHandlerSpec;
+import com.yahoo.container.jdisc.RequestView;
import com.yahoo.jdisc.http.HttpRequest.Method;
-import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
-import com.yahoo.yolean.Exceptions;
+import com.yahoo.restapi.RestApiMappers.ExceptionMapperHolder;
+import com.yahoo.restapi.RestApiMappers.RequestMapperHolder;
+import com.yahoo.restapi.RestApiMappers.ResponseMapperHolder;
-import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
-import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
@@ -23,8 +23,6 @@ import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
-import static java.nio.charset.StandardCharsets.UTF_8;
-
/**
* @author bjorncs
*/
@@ -39,6 +37,7 @@ class RestApiImpl implements RestApi {
private final List<RequestMapperHolder<?>> requestMappers;
private final List<Filter> filters;
private final ObjectMapper jacksonJsonMapper;
+ private final boolean disableDefaultAclMapping;
private RestApiImpl(RestApi.Builder builder) {
BuilderImpl builderImpl = (BuilderImpl) builder;
@@ -48,18 +47,19 @@ class RestApiImpl implements RestApi {
this.exceptionMappers = combineWithDefaultExceptionMappers(
builderImpl.exceptionMappers, Boolean.TRUE.equals(builderImpl.disableDefaultExceptionMappers));
this.responseMappers = combineWithDefaultResponseMappers(
- builderImpl.responseMappers, jacksonJsonMapper, Boolean.TRUE.equals(builderImpl.disableDefaultResponseMappers));
- this.requestMappers = combineWithDefaultRequestMappers(
- builderImpl.requestMappers, jacksonJsonMapper);
+ builderImpl.responseMappers, Boolean.TRUE.equals(builderImpl.disableDefaultResponseMappers));
+ this.requestMappers = combineWithDefaultRequestMappers(builderImpl.requestMappers);
this.filters = List.copyOf(builderImpl.filters);
this.jacksonJsonMapper = jacksonJsonMapper;
+ this.disableDefaultAclMapping = Boolean.TRUE.equals(builderImpl.disableDefaultAclMapping);
}
@Override
public HttpResponse handleRequest(HttpRequest request) {
Path pathMatcher = new Path(request.getUri());
Route resolvedRoute = resolveRoute(pathMatcher);
- RequestContextImpl requestContext = new RequestContextImpl(request, pathMatcher, jacksonJsonMapper);
+ AclMapping.Action aclAction = getAclMapping(request.getMethod(), request.getUri());
+ RequestContextImpl requestContext = new RequestContextImpl(request, pathMatcher, aclAction, jacksonJsonMapper);
FilterContextImpl filterContext =
createFilterContextRecursive(
resolvedRoute, requestContext, filters,
@@ -73,8 +73,33 @@ class RestApiImpl implements RestApi {
@Override public ObjectMapper jacksonJsonMapper() { return jacksonJsonMapper; }
+ @Override
+ public RequestHandlerSpec requestHandlerSpec() {
+ return RequestHandlerSpec.builder()
+ .withAclMapping(requestView -> getAclMapping(requestView.method(), requestView.uri()))
+ .build();
+ }
+
+ private AclMapping.Action getAclMapping(Method method, URI uri) {
+ Path pathMatcher = new Path(uri);
+ Route route = resolveRoute(pathMatcher);
+ HandlerHolder<?> handler = resolveHandler(method, route);
+ AclMapping.Action aclAction = handler.config.aclAction;
+ if (aclAction != null) return aclAction;
+ if (!disableDefaultAclMapping) {
+ // Fallback to default request handler spec which is used by the default implementation of
+ // HttpRequestHandler.requestHandlerSpec().
+ return RequestHandlerSpec.DEFAULT_INSTANCE.aclMapping().get(
+ new RequestView() {
+ @Override public Method method() { return method; }
+ @Override public URI uri() { return uri; }
+ });
+ }
+ throw new IllegalStateException(String.format("No ACL mapping configured for '%s' to '%s'", method, route.name));
+ }
+
private HttpResponse dispatchToRoute(Route route, RequestContextImpl context) {
- HandlerHolder<?> resolvedHandler = resolveHandler(context, route);
+ HandlerHolder<?> resolvedHandler = resolveHandler(context.request.getMethod(), route);
RequestMapperHolder<?> resolvedRequestMapper = resolveRequestMapper(resolvedHandler);
Object requestEntity;
try {
@@ -97,8 +122,8 @@ class RestApiImpl implements RestApi {
}
}
- private HandlerHolder<?> resolveHandler(RequestContextImpl context, Route route) {
- HandlerHolder<?> resolvedHandler = route.handlerPerMethod.get(context.request().getMethod());
+ private HandlerHolder<?> resolveHandler(Method method, Route route) {
+ HandlerHolder<?> resolvedHandler = route.handlerPerMethod.get(method);
return resolvedHandler == null ? route.defaultHandler : resolvedHandler;
}
@@ -154,7 +179,7 @@ class RestApiImpl implements RestApi {
List<ExceptionMapperHolder<?>> configuredExceptionMappers, boolean disableDefaultMappers) {
List<ExceptionMapperHolder<?>> exceptionMappers = new ArrayList<>(configuredExceptionMappers);
if (!disableDefaultMappers){
- exceptionMappers.add(new ExceptionMapperHolder<>(RestApiException.class, (context, exception) -> exception.response()));
+ exceptionMappers.addAll(RestApiMappers.DEFAULT_EXCEPTION_MAPPERS);
}
// Topologically sort children before superclasses, so most the specific match is found by iterating through mappers in order.
exceptionMappers.sort((a, b) -> (a.type.isAssignableFrom(b.type) ? 1 : 0) + (b.type.isAssignableFrom(a.type) ? -1 : 0));
@@ -162,71 +187,21 @@ class RestApiImpl implements RestApi {
}
private static List<ResponseMapperHolder<?>> combineWithDefaultResponseMappers(
- List<ResponseMapperHolder<?>> configuredResponseMappers, ObjectMapper jacksonJsonMapper, boolean disableDefaultMappers) {
+ List<ResponseMapperHolder<?>> configuredResponseMappers, boolean disableDefaultMappers) {
List<ResponseMapperHolder<?>> responseMappers = new ArrayList<>(configuredResponseMappers);
if (!disableDefaultMappers) {
- responseMappers.add(new ResponseMapperHolder<>(HttpResponse.class, (context, entity) -> entity));
- responseMappers.add(new ResponseMapperHolder<>(String.class, (context, entity) -> new MessageResponse(entity)));
- responseMappers.add(new ResponseMapperHolder<>(Slime.class, (context, entity) -> new SlimeJsonResponse(entity)));
- responseMappers.add(new ResponseMapperHolder<>(JsonNode.class, (context, entity) -> new JacksonJsonResponse<>(200, entity, jacksonJsonMapper, true)));
+ responseMappers.addAll(RestApiMappers.DEFAULT_RESPONSE_MAPPERS);
}
return responseMappers;
}
private static List<RequestMapperHolder<?>> combineWithDefaultRequestMappers(
- List<RequestMapperHolder<?>> configuredRequestMappers, ObjectMapper jacksonJsonMapper) {
+ List<RequestMapperHolder<?>> configuredRequestMappers) {
List<RequestMapperHolder<?>> requestMappers = new ArrayList<>(configuredRequestMappers);
- requestMappers.add(new RequestMapperHolder<>(Slime.class, RestApiImpl::toSlime));
- requestMappers.add(new RequestMapperHolder<>(JsonNode.class, ctx -> toJsonNode(ctx, jacksonJsonMapper)));
- requestMappers.add(new RequestMapperHolder<>(String.class, RestApiImpl::toString));
- requestMappers.add(new RequestMapperHolder<>(byte[].class, RestApiImpl::toByteArray));
- requestMappers.add(new RequestMapperHolder<>(InputStream.class, RestApiImpl::toInputStream));
- requestMappers.add(new RequestMapperHolder<>(Void.class, ctx -> Optional.empty()));
+ requestMappers.addAll(RestApiMappers.DEFAULT_REQUEST_MAPPERS);
return requestMappers;
}
- private static Optional<InputStream> toInputStream(RequestContext context) {
- return context.requestContent().map(RequestContext.RequestContent::content);
- }
-
- private static Optional<byte[]> toByteArray(RequestContext context) {
- InputStream in = toInputStream(context).orElse(null);
- if (in == null) return Optional.empty();
- return convertIoException(() -> Optional.of(in.readAllBytes()));
- }
-
- private static Optional<String> toString(RequestContext context) {
- try {
- return toByteArray(context).map(bytes -> new String(bytes, UTF_8));
- } catch (RuntimeException e) {
- throw new RestApiException.BadRequest("Failed parse request content as UTF-8: " + Exceptions.toMessageString(e), e);
- }
- }
-
- private static Optional<JsonNode> toJsonNode(RequestContext context, ObjectMapper jacksonJsonMapper) {
- if (log.isLoggable(Level.FINE)) {
- return toString(context).map(string -> {
- log.fine(() -> "Request content: " + string);
- return convertIoException("Failed to parse JSON", () -> jacksonJsonMapper.readTree(string));
- });
- } else {
- return toInputStream(context)
- .map(in -> convertIoException("Invalid JSON", () -> jacksonJsonMapper.readTree(in)));
- }
- }
-
- private static Optional<Slime> toSlime(RequestContext context) {
- try {
- return toString(context).map(string -> {
- log.fine(() -> "Request content: " + string);
- return SlimeUtils.jsonToSlimeOrThrow(string);
- });
- } catch (com.yahoo.slime.JsonParseException e) {
- log.log(Level.FINE, e.getMessage(), e);
- throw new RestApiException.BadRequest("Invalid JSON: " + Exceptions.toMessageString(e), e);
- }
- }
-
static class BuilderImpl implements RestApi.Builder {
private final List<Route> routes = new ArrayList<>();
private final List<ExceptionMapperHolder<?>> exceptionMappers = new ArrayList<>();
@@ -237,6 +212,7 @@ class RestApiImpl implements RestApi {
private ObjectMapper jacksonJsonMapper;
private Boolean disableDefaultExceptionMappers;
private Boolean disableDefaultResponseMappers;
+ private Boolean disableDefaultAclMapping;
@Override public RestApi.Builder setObjectMapper(ObjectMapper mapper) { this.jacksonJsonMapper = mapper; return this; }
@Override public RestApi.Builder setDefaultRoute(RestApi.RouteBuilder route) { this.defaultRoute = ((RouteBuilderImpl)route).build(); return this; }
@@ -256,20 +232,21 @@ class RestApiImpl implements RestApi {
}
@Override public <ENTITY> Builder registerJacksonResponseEntity(Class<ENTITY> type) {
- addResponseMapper(type, new JacksonResponseMapper<>()); return this;
+ addResponseMapper(type, new RestApiMappers.JacksonResponseMapper<>()); return this;
}
@Override public <ENTITY> Builder registerJacksonRequestEntity(Class<ENTITY> type) {
- addRequestMapper(type, new JacksonRequestMapper<>(type)); return this;
+ addRequestMapper(type, new RestApiMappers.JacksonRequestMapper<>(type)); return this;
}
@Override public Builder disableDefaultExceptionMappers() { this.disableDefaultExceptionMappers = true; return this; }
@Override public Builder disableDefaultResponseMappers() { this.disableDefaultResponseMappers = true; return this; }
+ @Override public Builder disableDefaultAclMapping() { this.disableDefaultAclMapping = true; return this; }
@Override public RestApi build() { return new RestApiImpl(this); }
}
- public static class RouteBuilderImpl implements RestApi.RouteBuilder {
+ static class RouteBuilderImpl implements RestApi.RouteBuilder {
private final String pathPattern;
private String name;
private final Map<Method, HandlerHolder<?>> handlerPerMethod = new HashMap<>();
@@ -279,50 +256,118 @@ class RestApiImpl implements RestApi {
RouteBuilderImpl(String pathPattern) { this.pathPattern = pathPattern; }
@Override public RestApi.RouteBuilder name(String name) { this.name = name; return this; }
- @Override public RestApi.RouteBuilder get(Handler<?> handler) {
- return addHandler(Method.GET, handler);
+ @Override public RestApi.RouteBuilder addFilter(RestApi.Filter filter) { filters.add(filter); return this; }
+
+ // GET
+ @Override public RouteBuilder get(Handler<?> handler) { return get(handler, null); }
+ @Override public RouteBuilder get(Handler<?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.GET, handler, config);
}
- @Override public RestApi.RouteBuilder post(Handler<?> handler) {
- return addHandler(Method.POST, handler);
+
+ // POST
+ @Override public RouteBuilder post(Handler<?> handler) { return post(handler, null); }
+ @Override public <REQUEST_ENTITY> RouteBuilder post(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler) {
+ return post(type, handler, null);
}
- @Override public <ENTITY> RouteBuilder post(Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler) {
- return addHandler(Method.POST, type, handler);
+ @Override public RouteBuilder post(Handler<?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.POST, handler, config);
}
- @Override public RestApi.RouteBuilder put(Handler<?> handler) {
- return addHandler(Method.PUT, handler);
+ @Override public <REQUEST_ENTITY> RouteBuilder post(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.POST, type, handler, config);
}
- @Override public <ENTITY> RouteBuilder put(Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler) {
- return addHandler(Method.PUT, type, handler);
+
+ // PUT
+ @Override public RouteBuilder put(Handler<?> handler) { return put(handler, null); }
+ @Override public <REQUEST_ENTITY> RouteBuilder put(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler) {
+ return put(type, handler, null);
}
- @Override public RestApi.RouteBuilder delete(Handler<?> handler) {
- return addHandler(Method.DELETE, handler);
+ @Override public RouteBuilder put(Handler<?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.PUT, handler, null);
}
- @Override public RestApi.RouteBuilder patch(Handler<?> handler) {
- return addHandler(Method.PATCH, handler);
+ @Override public <REQUEST_ENTITY> RouteBuilder put(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.PUT, type, handler, config);
}
- @Override public <ENTITY> RouteBuilder patch(Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler) {
- return addHandler(Method.PATCH, type, handler);
+
+ // DELETE
+ @Override public RouteBuilder delete(Handler<?> handler) { return delete(handler, null); }
+ @Override public RouteBuilder delete(Handler<?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.DELETE, handler, config);
}
- @Override public RestApi.RouteBuilder defaultHandler(Handler<?> handler) {
- defaultHandler = HandlerHolder.of(handler); return this;
+
+ // PATCH
+ @Override public RouteBuilder patch(Handler<?> handler) { return patch(handler, null); }
+ @Override public <REQUEST_ENTITY> RouteBuilder patch(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler) {
+ return patch(type, handler, null);
}
- @Override public <ENTITY> RouteBuilder defaultHandler(Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler) {
- defaultHandler = HandlerHolder.of(type, handler); return this;
+ @Override public RouteBuilder patch(Handler<?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.PATCH, handler, config);
+ }
+ @Override public <REQUEST_ENTITY> RouteBuilder patch(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config) {
+ return addHandler(Method.PATCH, type, handler, config);
}
- @Override public RestApi.RouteBuilder addFilter(RestApi.Filter filter) { filters.add(filter); return this; }
- private RestApi.RouteBuilder addHandler(Method method, Handler<?> handler) {
- handlerPerMethod.put(method, HandlerHolder.of(handler)); return this;
+ // Default
+ @Override public RouteBuilder defaultHandler(Handler<?> handler) {
+ return defaultHandler(handler, null);
+ }
+ @Override public RouteBuilder defaultHandler(Handler<?> handler, HandlerConfigBuilder config) {
+ defaultHandler = HandlerHolder.of(handler, build(config)); return this;
+ }
+ @Override public <REQUEST_ENTITY> RouteBuilder defaultHandler(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler) {
+ return defaultHandler(type, handler, null);
+ }
+ @Override
+ public <REQUEST_ENTITY> RouteBuilder defaultHandler(
+ Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler, HandlerConfigBuilder config) {
+ defaultHandler = HandlerHolder.of(type, handler, build(config)); return this;
+ }
+
+ private RestApi.RouteBuilder addHandler(Method method, Handler<?> handler, HandlerConfigBuilder config) {
+ handlerPerMethod.put(method, HandlerHolder.of(handler, build(config))); return this;
}
private <ENTITY> RestApi.RouteBuilder addHandler(
- Method method, Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler) {
- handlerPerMethod.put(method, HandlerHolder.of(type, handler)); return this;
+ Method method, Class<ENTITY> type, HandlerWithRequestEntity<ENTITY, ?> handler, HandlerConfigBuilder config) {
+ handlerPerMethod.put(method, HandlerHolder.of(type, handler, build(config))); return this;
+ }
+
+ private static HandlerConfig build(HandlerConfigBuilder builder) {
+ if (builder == null) return HandlerConfig.empty();
+ return ((HandlerConfigBuilderImpl)builder).build();
}
private Route build() { return new Route(this); }
}
+ static class HandlerConfigBuilderImpl implements HandlerConfigBuilder {
+ private AclMapping.Action aclAction;
+
+ @Override public HandlerConfigBuilder withReadAclAction() { return withCustomAclAction(AclMapping.Action.READ); }
+ @Override public HandlerConfigBuilder withWriteAclAction() { return withCustomAclAction(AclMapping.Action.WRITE); }
+ @Override public HandlerConfigBuilder withCustomAclAction(AclMapping.Action action) {
+ this.aclAction = action; return this;
+ }
+
+ HandlerConfig build() { return new HandlerConfig(this); }
+ }
+
+ private static class HandlerConfig {
+ final AclMapping.Action aclAction;
+
+ HandlerConfig(HandlerConfigBuilderImpl builder) {
+ this.aclAction = builder.aclAction;
+ }
+
+ static HandlerConfig empty() { return new HandlerConfigBuilderImpl().build(); }
+ }
+
private static class RequestContextImpl implements RestApi.RequestContext {
final HttpRequest request;
final Path pathMatcher;
@@ -332,12 +377,14 @@ class RestApiImpl implements RestApi {
final Headers headers = new HeadersImpl();
final Attributes attributes = new AttributesImpl();
final RequestContent requestContent;
+ final AclMapping.Action aclAction;
- RequestContextImpl(HttpRequest request, Path pathMatcher, ObjectMapper jacksonJsonMapper) {
+ RequestContextImpl(HttpRequest request, Path pathMatcher, AclMapping.Action aclAction, ObjectMapper jacksonJsonMapper) {
this.request = request;
this.pathMatcher = pathMatcher;
this.jacksonJsonMapper = jacksonJsonMapper;
this.requestContent = request.getData() != null ? new RequestContentImpl() : null;
+ this.aclAction = aclAction;
}
@Override public HttpRequest request() { return request; }
@@ -357,6 +404,7 @@ class RestApiImpl implements RestApi {
? new UriBuilder(uri.getScheme() + "://" + uri.getHost() + ':' + uriPort)
: new UriBuilder(uri.getScheme() + "://" + uri.getHost());
}
+ @Override public AclMapping.Action aclAction() { return aclAction; }
private class PathParametersImpl implements RestApi.RequestContext.PathParameters {
@Override
@@ -433,63 +481,37 @@ class RestApiImpl implements RestApi {
}
}
- private static class ExceptionMapperHolder<EXCEPTION extends RuntimeException> {
- final Class<EXCEPTION> type;
- final RestApi.ExceptionMapper<EXCEPTION> mapper;
-
- ExceptionMapperHolder(Class<EXCEPTION> type, RestApi.ExceptionMapper<EXCEPTION> mapper) {
- this.type = type;
- this.mapper = mapper;
- }
-
- HttpResponse toResponse(RestApi.RequestContext context, RuntimeException e) { return mapper.toResponse(context, type.cast(e)); }
- }
-
- private static class ResponseMapperHolder<ENTITY> {
- final Class<ENTITY> type;
- final RestApi.ResponseMapper<ENTITY> mapper;
-
- ResponseMapperHolder(Class<ENTITY> type, RestApi.ResponseMapper<ENTITY> mapper) {
- this.type = type;
- this.mapper = mapper;
- }
-
- HttpResponse toHttpResponse(RestApi.RequestContext context, Object entity) { return mapper.toHttpResponse(context, type.cast(entity)); }
- }
-
private static class HandlerHolder<REQUEST_ENTITY> {
final Class<REQUEST_ENTITY> type;
final HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler;
+ final HandlerConfig config;
- HandlerHolder(Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler) {
+ private HandlerHolder(
+ Class<REQUEST_ENTITY> type,
+ HandlerWithRequestEntity<REQUEST_ENTITY, ?> handler,
+ HandlerConfig config) {
this.type = type;
this.handler = handler;
+ this.config = config;
}
static <RESPONSE_ENTITY, REQUEST_ENTITY> HandlerHolder<REQUEST_ENTITY> of(
- Class<REQUEST_ENTITY> type, HandlerWithRequestEntity<REQUEST_ENTITY, RESPONSE_ENTITY> handler) {
- return new HandlerHolder<>(type, handler);
+ Class<REQUEST_ENTITY> type,
+ HandlerWithRequestEntity<REQUEST_ENTITY, RESPONSE_ENTITY> handler,
+ HandlerConfig config) {
+ return new HandlerHolder<>(type, handler, config);
}
- static <RESPONSE_ENTITY> HandlerHolder<Void> of(Handler<RESPONSE_ENTITY> handler) {
+ static <RESPONSE_ENTITY> HandlerHolder<Void> of(Handler<RESPONSE_ENTITY> handler, HandlerConfig config) {
return new HandlerHolder<>(
Void.class,
- (HandlerWithRequestEntity<Void, RESPONSE_ENTITY>) (context, nullEntity) -> handler.handleRequest(context));
+ (HandlerWithRequestEntity<Void, RESPONSE_ENTITY>) (context, nullEntity) -> handler.handleRequest(context),
+ config);
}
Object executeHandler(RestApi.RequestContext context, Object entity) { return handler.handleRequest(context, type.cast(entity)); }
}
- private static class RequestMapperHolder<ENTITY> {
- final Class<ENTITY> type;
- final RestApi.RequestMapper<ENTITY> mapper;
-
- RequestMapperHolder(Class<ENTITY> type, RequestMapper<ENTITY> mapper) {
- this.type = type;
- this.mapper = mapper;
- }
- }
-
static class Route {
private final String pathPattern;
private final String name;
@@ -507,47 +529,10 @@ class RestApiImpl implements RestApi {
}
private HandlerHolder<?> createDefaultMethodHandler() {
- return HandlerHolder.of(context -> { throw new RestApiException.MethodNotAllowed(context.request()); });
+ return HandlerHolder.of(
+ context -> { throw new RestApiException.MethodNotAllowed(context.request()); },
+ HandlerConfig.empty());
}
}
- private static class JacksonRequestMapper<ENTITY> implements RequestMapper<ENTITY> {
- private final Class<ENTITY> type;
-
- JacksonRequestMapper(Class<ENTITY> type) { this.type = type; }
-
- @Override
- public Optional<ENTITY> toRequestEntity(RequestContext context) throws RestApiException {
- if (log.isLoggable(Level.FINE)) {
- return RestApiImpl.toString(context).map(string -> {
- log.fine(() -> "Request content: " + string);
- return convertIoException("Failed to parse JSON", () -> context.jacksonJsonMapper().readValue(string, type));
- });
- } else {
- return RestApiImpl.toInputStream(context)
- .map(in -> convertIoException("Invalid JSON", () -> context.jacksonJsonMapper().readValue(in, type)));
- }
- }
- }
-
- private static class JacksonResponseMapper<ENTITY> implements ResponseMapper<ENTITY> {
- @Override
- public HttpResponse toHttpResponse(RequestContext context, ENTITY responseEntity) throws RestApiException {
- return new JacksonJsonResponse<>(200, responseEntity, context.jacksonJsonMapper(), true);
- }
- }
-
- @FunctionalInterface private interface SupplierThrowingIoException<T> { T get() throws IOException; }
- private static <T> T convertIoException(String messagePrefix, SupplierThrowingIoException<T> supplier) {
- try {
- return supplier.get();
- } catch (IOException e) {
- log.log(Level.FINE, e.getMessage(), e);
- throw new RestApiException.InternalServerError(messagePrefix + ": " + Exceptions.toMessageString(e), e);
- }
- }
-
- private static <T> T convertIoException(SupplierThrowingIoException<T> supplier) {
- return convertIoException("Failed to read request content", supplier);
- }
}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
new file mode 100644
index 00000000000..36d98421e6a
--- /dev/null
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiMappers.java
@@ -0,0 +1,172 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.restapi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.RestApi.ExceptionMapper;
+import com.yahoo.restapi.RestApi.RequestMapper;
+import com.yahoo.restapi.RestApi.ResponseMapper;
+import com.yahoo.slime.Slime;
+import com.yahoo.slime.SlimeUtils;
+import com.yahoo.yolean.Exceptions;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Implementations of {@link ExceptionMapper}, {@link RequestMapper} and {@link ResponseMapper}.
+ *
+ * @author bjorncs
+ */
+public class RestApiMappers {
+
+ private static final Logger log = Logger.getLogger(RestApiMappers.class.getName());
+
+ public static List<RequestMapperHolder<?>> DEFAULT_REQUEST_MAPPERS = List.of(
+ new RequestMapperHolder<>(Slime.class, RestApiMappers::toSlime),
+ new RequestMapperHolder<>(JsonNode.class, ctx -> toJsonNode(ctx, ctx.jacksonJsonMapper())),
+ new RequestMapperHolder<>(String.class, RestApiMappers::toString),
+ new RequestMapperHolder<>(byte[].class, RestApiMappers::toByteArray),
+ new RequestMapperHolder<>(InputStream .class, RestApiMappers::toInputStream),
+ new RequestMapperHolder<>(Void.class, ctx -> Optional.empty()));
+
+ public static List<ResponseMapperHolder<?>> DEFAULT_RESPONSE_MAPPERS = List.of(
+ new ResponseMapperHolder<>(HttpResponse.class, (context, entity) -> entity),
+ new ResponseMapperHolder<>(String.class, (context, entity) -> new MessageResponse(entity)),
+ new ResponseMapperHolder<>(Slime.class, (context, entity) -> new SlimeJsonResponse(entity)),
+ new ResponseMapperHolder<>(JsonNode.class,
+ (context, entity) -> new JacksonJsonResponse<>(200, entity, context.jacksonJsonMapper(), true)));
+
+ public static List<ExceptionMapperHolder<?>> DEFAULT_EXCEPTION_MAPPERS = List.of(
+ new ExceptionMapperHolder<>(RestApiException.class, (context, exception) -> exception.response()));
+
+ private RestApiMappers() {}
+
+ public static class JacksonRequestMapper<ENTITY> implements RequestMapper<ENTITY> {
+ private final Class<ENTITY> type;
+
+ JacksonRequestMapper(Class<ENTITY> type) { this.type = type; }
+
+ @Override
+ public Optional<ENTITY> toRequestEntity(RestApi.RequestContext context) throws RestApiException {
+ if (log.isLoggable(Level.FINE)) {
+ return RestApiMappers.toString(context).map(string -> {
+ log.fine(() -> "Request content: " + string);
+ return convertIoException("Failed to parse JSON", () -> context.jacksonJsonMapper().readValue(string, type));
+ });
+ } else {
+ return toInputStream(context)
+ .map(in -> convertIoException("Invalid JSON", () -> context.jacksonJsonMapper().readValue(in, type)));
+ }
+ }
+ }
+
+ public static class JacksonResponseMapper<ENTITY> implements ResponseMapper<ENTITY> {
+ @Override
+ public HttpResponse toHttpResponse(RestApi.RequestContext context, ENTITY responseEntity) throws RestApiException {
+ return new JacksonJsonResponse<>(200, responseEntity, context.jacksonJsonMapper(), true);
+ }
+ }
+
+ public static class RequestMapperHolder<ENTITY> {
+ final Class<ENTITY> type;
+ final RestApi.RequestMapper<ENTITY> mapper;
+
+ RequestMapperHolder(Class<ENTITY> type, RequestMapper<ENTITY> mapper) {
+ this.type = type;
+ this.mapper = mapper;
+ }
+ }
+
+ public static class ResponseMapperHolder<ENTITY> {
+ final Class<ENTITY> type;
+ final RestApi.ResponseMapper<ENTITY> mapper;
+
+ ResponseMapperHolder(Class<ENTITY> type, RestApi.ResponseMapper<ENTITY> mapper) {
+ this.type = type;
+ this.mapper = mapper;
+ }
+
+ HttpResponse toHttpResponse(RestApi.RequestContext ctx, Object entity) {
+ return mapper.toHttpResponse(ctx, type.cast(entity));
+ }
+ }
+
+ public static class ExceptionMapperHolder<EXCEPTION extends RuntimeException> {
+ final Class<EXCEPTION> type;
+ final RestApi.ExceptionMapper<EXCEPTION> mapper;
+
+ ExceptionMapperHolder(Class<EXCEPTION> type, RestApi.ExceptionMapper<EXCEPTION> mapper) {
+ this.type = type;
+ this.mapper = mapper;
+ }
+
+ HttpResponse toResponse(RestApi.RequestContext ctx, RuntimeException e) {
+ return mapper.toResponse(ctx, type.cast(e));
+ }
+ }
+
+ private static Optional<InputStream> toInputStream(RestApi.RequestContext context) {
+ return context.requestContent().map(RestApi.RequestContext.RequestContent::content);
+ }
+
+ private static Optional<byte[]> toByteArray(RestApi.RequestContext context) {
+ InputStream in = toInputStream(context).orElse(null);
+ if (in == null) return Optional.empty();
+ return convertIoException(() -> Optional.of(in.readAllBytes()));
+ }
+
+ private static Optional<String> toString(RestApi.RequestContext context) {
+ try {
+ return toByteArray(context).map(bytes -> new String(bytes, UTF_8));
+ } catch (RuntimeException e) {
+ throw new RestApiException.BadRequest("Failed parse request content as UTF-8: " + Exceptions.toMessageString(e), e);
+ }
+ }
+
+ private static Optional<JsonNode> toJsonNode(RestApi.RequestContext context, ObjectMapper jacksonJsonMapper) {
+ if (log.isLoggable(Level.FINE)) {
+ return toString(context).map(string -> {
+ log.fine(() -> "Request content: " + string);
+ return convertIoException("Failed to parse JSON", () -> jacksonJsonMapper.readTree(string));
+ });
+ } else {
+ return toInputStream(context)
+ .map(in -> convertIoException("Invalid JSON", () -> jacksonJsonMapper.readTree(in)));
+ }
+ }
+
+ @FunctionalInterface private interface SupplierThrowingIoException<T> { T get() throws IOException; }
+ private static <T> T convertIoException(String messagePrefix, SupplierThrowingIoException<T> supplier) {
+ try {
+ return supplier.get();
+ } catch (IOException e) {
+ log.log(Level.FINE, e.getMessage(), e);
+ throw new RestApiException.InternalServerError(messagePrefix + ": " + Exceptions.toMessageString(e), e);
+ }
+ }
+
+ private static <T> T convertIoException(SupplierThrowingIoException<T> supplier) {
+ return convertIoException("Failed to read request content", supplier);
+ }
+
+ private static Optional<Slime> toSlime(RestApi.RequestContext context) {
+ try {
+ return toString(context).map(string -> {
+ log.fine(() -> "Request content: " + string);
+ return SlimeUtils.jsonToSlimeOrThrow(string);
+ });
+ } catch (com.yahoo.slime.JsonParseException e) {
+ log.log(Level.FINE, e.getMessage(), e);
+ throw new RestApiException.BadRequest("Invalid JSON: " + Exceptions.toMessageString(e), e);
+ }
+ }
+
+}
diff --git a/container-core/src/main/java/com/yahoo/restapi/RestApiRequestHandler.java b/container-core/src/main/java/com/yahoo/restapi/RestApiRequestHandler.java
index c501ad8c804..4f8adfe9bef 100644
--- a/container-core/src/main/java/com/yahoo/restapi/RestApiRequestHandler.java
+++ b/container-core/src/main/java/com/yahoo/restapi/RestApiRequestHandler.java
@@ -4,6 +4,7 @@ package com.yahoo.restapi;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
+import com.yahoo.container.jdisc.RequestHandlerSpec;
import com.yahoo.jdisc.Metric;
import java.util.concurrent.Executor;
@@ -48,6 +49,7 @@ public abstract class RestApiRequestHandler<T extends RestApiRequestHandler<T>>
}
@Override public final HttpResponse handle(HttpRequest request) { return restApi.handleRequest(request); }
+ @Override public RequestHandlerSpec requestHandlerSpec() { return restApi.requestHandlerSpec(); }
public RestApi restApi() { return restApi; }
}
diff --git a/container-core/src/main/resources/configdefinitions/container.di.config.jersey-bundles.def b/container-core/src/main/resources/configdefinitions/container.di.config.jersey-bundles.def
deleted file mode 100644
index a226420274d..00000000000
--- a/container-core/src/main/resources/configdefinitions/container.di.config.jersey-bundles.def
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-namespace=container.di.config
-
-# The SymbolicName[:Version] of the Jersey bundles
-bundles[].spec string
-
-# The packages to scan for Jersey resources
-bundles[].packages[] string
diff --git a/container-core/src/main/resources/configdefinitions/container.di.config.jersey-injection.def b/container-core/src/main/resources/configdefinitions/container.di.config.jersey-injection.def
deleted file mode 100644
index 9f5be59abbd..00000000000
--- a/container-core/src/main/resources/configdefinitions/container.di.config.jersey-injection.def
+++ /dev/null
@@ -1,5 +0,0 @@
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-namespace=container.di.config
-
-inject[].instance string
-inject[].forClass string
diff --git a/container-core/src/test/java/com/yahoo/container/di/ContainerTest.java b/container-core/src/test/java/com/yahoo/container/di/ContainerTest.java
index b596246a43d..47ac3018fde 100644
--- a/container-core/src/test/java/com/yahoo/container/di/ContainerTest.java
+++ b/container-core/src/test/java/com/yahoo/container/di/ContainerTest.java
@@ -5,13 +5,10 @@ import com.google.inject.Guice;
import com.yahoo.component.AbstractComponent;
import com.yahoo.config.di.IntConfig;
import com.yahoo.config.test.TestConfig;
-import com.yahoo.container.bundle.MockBundle;
import com.yahoo.container.di.componentgraph.Provider;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent;
-import com.yahoo.container.di.componentgraph.core.ComponentGraphTest.SimpleComponent2;
import com.yahoo.container.di.componentgraph.core.ComponentNode.ComponentConstructorException;
-import com.yahoo.container.di.config.RestApiContext;
import org.junit.Ignore;
import org.junit.Test;
import org.osgi.framework.Bundle;
@@ -218,64 +215,6 @@ public class ContainerTest extends ContainerTestBase {
assertNotNull(newGraph.get(5, TimeUnit.MINUTES));
}
-
- @Test
- public void bundle_info_is_set_on_rest_api_context() {
- Class<RestApiContext> clazz = RestApiContext.class;
-
- writeBootstrapConfigs("restApiContext", clazz);
- dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
- dirConfigSource.writeConfig("jersey-injection", "inject[0]");
-
- Container container = newContainer(dirConfigSource);
- ComponentGraph componentGraph = getNewComponentGraph(container);
-
- RestApiContext restApiContext = componentGraph.getInstance(clazz);
- assertNotNull(restApiContext);
-
- assertEquals(1, restApiContext.getBundles().size());
- assertEquals(MockBundle.SymbolicName, restApiContext.getBundles().get(0).symbolicName);
- assertEquals(MockBundle.BundleVersion, restApiContext.getBundles().get(0).version);
-
- container.shutdownConfigurer();
- }
-
- @Test
- public void restApiContext_has_all_components_injected() {
- Class<RestApiContext> restApiClass = RestApiContext.class;
- Class<SimpleComponent> injectedClass = SimpleComponent.class;
- String injectedComponentId = "injectedComponent";
- Class<SimpleComponent2> anotherComponentClass = SimpleComponent2.class;
- String anotherComponentId = "anotherComponent";
-
- String componentsConfig =
- new ComponentEntry(injectedComponentId, injectedClass).asConfig(0) + "\n" +
- new ComponentEntry(anotherComponentId, anotherComponentClass).asConfig(1) + "\n" +
- new ComponentEntry("restApiContext", restApiClass).asConfig(2) + "\n" +
- "components[2].inject[0].id " + injectedComponentId + "\n" +
- "components[2].inject[1].id " + anotherComponentId + "\n";
-
- String injectionConfig = "inject[1]\n" +//
- "inject[0].instance " + injectedComponentId + "\n" +//
- "inject[0].forClass \"" + injectedClass.getName() + "\"\n";
-
- dirConfigSource.writeConfig("components", componentsConfig);
- dirConfigSource.writeConfig("platform-bundles", "");
- dirConfigSource.writeConfig("application-bundles", "");
- dirConfigSource.writeConfig("jersey-bundles", "bundles[0].spec \"mock-entry-to-enforce-a-MockBundle\"");
- dirConfigSource.writeConfig("jersey-injection", injectionConfig);
-
- Container container = newContainer(dirConfigSource);
- ComponentGraph componentGraph = getNewComponentGraph(container);
-
- RestApiContext restApiContext = componentGraph.getInstance(restApiClass);
-
- assertFalse(restApiContext.getInjectableComponents().isEmpty());
- assertEquals(2, restApiContext.getInjectableComponents().size());
-
- container.shutdownConfigurer();
- }
-
@Test
public void providers_are_destructed() {
writeBootstrapConfigs("id1", DestructableProvider.class);
diff --git a/container-core/src/test/java/com/yahoo/container/di/ContainerTestBase.java b/container-core/src/test/java/com/yahoo/container/di/ContainerTestBase.java
index 2106a1f3671..0d1a308f182 100644
--- a/container-core/src/test/java/com/yahoo/container/di/ContainerTestBase.java
+++ b/container-core/src/test/java/com/yahoo/container/di/ContainerTestBase.java
@@ -7,7 +7,6 @@ import com.yahoo.config.FileReference;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.di.ContainerTest.ComponentTakingConfig;
import com.yahoo.container.di.componentgraph.core.ComponentGraph;
-import com.yahoo.container.di.osgi.BundleClasses;
import org.junit.After;
import org.junit.Before;
import org.osgi.framework.Bundle;
@@ -57,11 +56,6 @@ public class ContainerTestBase {
}
@Override
- public BundleClasses getBundleClasses(ComponentSpecification bundle, Set<String> packagesToScan) {
- throw new UnsupportedOperationException("getBundleClasses not supported");
- }
-
- @Override
public Set<Bundle> useApplicationBundles(Collection<FileReference> bundles) {
return emptySet();
}
diff --git a/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java b/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java
index 70dc4c8665c..43e36781101 100644
--- a/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java
+++ b/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/ComponentGraphTest.java
@@ -16,11 +16,7 @@ import com.yahoo.config.ConfigInstance;
import com.yahoo.config.subscription.ConfigGetter;
import com.yahoo.config.test.Test2Config;
import com.yahoo.config.test.TestConfig;
-import com.yahoo.container.di.Osgi;
import com.yahoo.container.di.componentgraph.Provider;
-import com.yahoo.container.di.config.JerseyBundlesConfig;
-import com.yahoo.container.di.config.JerseyInjectionConfig;
-import com.yahoo.container.di.config.RestApiContext;
import com.yahoo.vespa.config.ConfigKey;
import org.junit.Test;
@@ -485,25 +481,6 @@ public class ComponentGraphTest {
assertThat(componentGraph.getInstance(ComponentTakingComponentId.class).componentId, is(ComponentId.fromString(componentId)));
}
- @Test
- public void rest_api_context_can_be_instantiated() {
- String configId = "raw:\"\"";
-
- Class<RestApiContext> clazz = RestApiContext.class;
- JerseyNode jerseyNode = new JerseyNode(uniqueComponentId(clazz.getName()), configId, clazz, new Osgi() {
- });
-
- ComponentGraph componentGraph = new ComponentGraph();
- componentGraph.add(jerseyNode);
- componentGraph.complete();
-
- componentGraph
- .setAvailableConfigs(ConfigMap.newMap(JerseyBundlesConfig.class, configId).add(JerseyInjectionConfig.class, configId));
-
- RestApiContext restApiContext = componentGraph.getInstance(clazz);
- assertNotNull(restApiContext);
- assertThat(restApiContext.getBundles().size(), is(0));
- }
//Note that all Components must be defined in a static context,
//otherwise their constructor will take the outer class as the first parameter.
diff --git a/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java b/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java
deleted file mode 100644
index f30f9260830..00000000000
--- a/container-core/src/test/java/com/yahoo/container/di/componentgraph/core/JerseyNodeTest.java
+++ /dev/null
@@ -1,68 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.container.di.componentgraph.core;
-
-import com.yahoo.container.bundle.MockBundle;
-import com.yahoo.container.di.config.RestApiContext;
-import com.yahoo.container.di.osgi.OsgiUtil;
-import org.junit.Test;
-import org.osgi.framework.wiring.BundleWiring;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.junit.Assert.assertThat;
-
-/**
- * @author gjoranv
- * @author ollivir
- */
-
-public class JerseyNodeTest {
- private MockBundle bundle;
- private List<String> bundleClasses;
- private final Map<String, String> resources;
-
- public JerseyNodeTest() {
- resources = new HashMap<>();
- resources.put("com/foo", "com/foo/Foo.class");
- resources.put("com/bar", "com/bar/Bar.class");
- bundle = new MockBundle() {
- @Override
- public Collection<String> listResources(String path, String ignored, int options) {
- if ((options & BundleWiring.LISTRESOURCES_RECURSE) != 0 && path.equals("/")) {
- return resources.values();
- } else {
- return Collections.singleton(resources.get(path));
- }
- }
- };
- bundleClasses = new ArrayList<>(resources.values());
- }
-
- @Test
- public void all_bundle_entries_are_returned_when_no_packages_are_given() {
- Collection<String> entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Collections.emptySet());
- assertThat(entries, containsInAnyOrder(bundleClasses.toArray()));
- }
-
- @Test
- public void only_bundle_entries_from_the_given_packages_are_returned() {
- Collection<String> entries = OsgiUtil.getClassEntriesInBundleClassPath(bundle, Collections.singleton("com.foo"));
- assertThat(entries, contains(resources.get("com/foo")));
- }
-
- @Test
- public void bundle_info_is_initialized() {
- RestApiContext.BundleInfo bundleInfo = JerseyNode.createBundleInfo(bundle, Collections.emptyList());
- assertThat(bundleInfo.symbolicName, is(bundle.getSymbolicName()));
- assertThat(bundleInfo.version, is(bundle.getVersion()));
- assertThat(bundleInfo.fileLocation, is(bundle.getLocation()));
- }
-}
diff --git a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
index 06fc6d80741..44dd61836d6 100644
--- a/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
+++ b/container-core/src/test/java/com/yahoo/restapi/RestApiImplTest.java
@@ -1,14 +1,18 @@
package com.yahoo.restapi;// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
import com.fasterxml.jackson.annotation.JsonProperty;
+import com.yahoo.container.jdisc.AclMapping;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.container.jdisc.RequestHandlerSpec;
+import com.yahoo.container.jdisc.RequestView;
import com.yahoo.test.json.JsonTestHelper;
import com.yahoo.yolean.Exceptions;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.ArrayList;
@@ -16,6 +20,7 @@ import java.util.List;
import java.util.Map;
import static com.yahoo.jdisc.http.HttpRequest.Method;
+import static com.yahoo.restapi.RestApi.handlerConfig;
import static com.yahoo.restapi.RestApi.route;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -103,13 +108,32 @@ class RestApiImplTest {
}
@Test
- public void uri_builder_creates_valid_uri_prefix() {
+ void uri_builder_creates_valid_uri_prefix() {
RestApi restApi = RestApi.builder()
.addRoute(route("/test").get(ctx -> new MessageResponse(ctx.uriBuilder().toString())))
.build();
verifyJsonResponse(restApi, Method.GET, "/test", null, 200, "{\"message\":\"http://localhost\"}");
}
+ @Test
+ void resolves_correct_acl_action() {
+ AclMapping.Action customAclAction = AclMapping.Action.custom("custom-action");
+ RestApi restApi = RestApi.builder()
+ .addRoute(route("/api1")
+ .get(ctx -> new MessageResponse(ctx.aclAction().name()),
+ handlerConfig().withCustomAclAction(customAclAction)))
+ .addRoute(route("/api2")
+ .post(ctx -> new MessageResponse(ctx.aclAction().name())))
+ .build();
+
+ verifyJsonResponse(restApi, Method.GET, "/api1", null, 200, "{\"message\":\"custom-action\"}");
+ verifyJsonResponse(restApi, Method.POST, "/api2", "ignored", 200, "{\"message\":\"write\"}");
+
+ RequestHandlerSpec spec = restApi.requestHandlerSpec();
+ assertRequestHandlerSpecAclMapping(spec, customAclAction, Method.GET, "/api1");
+ assertRequestHandlerSpecAclMapping(spec, AclMapping.Action.WRITE, Method.POST, "/api2");
+ }
+
private static void verifyJsonResponse(RestApi restApi, Method method, String path, String requestContent, int expectedStatusCode, String expectedJson) {
HttpRequest testRequest;
String uri = "http://localhost" + path;
@@ -132,6 +156,15 @@ class RestApiImplTest {
}
}
+ private static void assertRequestHandlerSpecAclMapping(
+ RequestHandlerSpec spec, AclMapping.Action expectedAction, Method method, String uriPath) {
+ RequestView requestView = new RequestView() {
+ @Override public Method method() { return method; }
+ @Override public URI uri() { return URI.create("http://localhost" + uriPath); }
+ };
+ assertEquals(expectedAction, spec.aclMapping().get(requestView));
+ }
+
public static class TestEntity {
@JsonProperty("mystring") public String stringValue;
@JsonProperty("myinstant") public Instant instantValue;
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
index bfcefecba0c..58afed4143f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandlerV2.java
@@ -11,7 +11,6 @@ import com.yahoo.restapi.SlimeJsonResponse;
import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
import com.yahoo.slime.Type;
import com.yahoo.vespa.hosted.controller.ApplicationController;
import com.yahoo.vespa.hosted.controller.Controller;
@@ -26,7 +25,6 @@ import com.yahoo.vespa.hosted.controller.tenant.CloudTenant;
import com.yahoo.vespa.hosted.controller.tenant.Tenant;
import javax.ws.rs.BadRequestException;
-import java.io.IOException;
import java.math.BigDecimal;
import java.time.Clock;
import java.time.Instant;
@@ -34,8 +32,8 @@ import java.time.LocalDate;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Comparator;
-import java.util.Optional;
import java.util.List;
+import java.util.Optional;
/**
* @author ogronnesby
@@ -78,11 +76,6 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
.addRoute(RestApi.route("/billing/v2/accountant/preview/tenant/{tenant}")
.get(self::previewBill)
.post(Slime.class, self::createBill))
- /*
- * Utility - map Slime.class => SlimeJsonResponse
- */
- .addRequestMapper(Slime.class, BillingApiHandlerV2::slimeRequestMapper)
- .addResponseMapper(Slime.class, BillingApiHandlerV2::slimeResponseMapper)
.build();
}
@@ -337,16 +330,4 @@ public class BillingApiHandlerV2 extends RestApiRequestHandler<BillingApiHandler
return inspector.field(field).asString();
}
- private static Optional<Slime> slimeRequestMapper(RestApi.RequestContext requestContext) {
- try {
- return Optional.of(SlimeUtils.jsonToSlime(requestContext.requestContentOrThrow().content().readAllBytes()));
- } catch (IOException e) {
- throw new IllegalArgumentException("Could not parse JSON input");
- }
- }
-
- private static HttpResponse slimeResponseMapper(RestApi.RequestContext ctx, Slime slime) {
- return new SlimeJsonResponse(slime);
- }
-
}
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index 4a104deaf55..d57b15b50b4 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -150,13 +150,6 @@ public class Flags {
"Takes effect at redeployment",
ZONE_ID, APPLICATION_ID);
- public static final UnboundIntFlag METRICS_PROXY_MAX_HEAP_SIZE_IN_MB = defineIntFlag(
- "metrics-proxy-max-heap-size-in-mb", 256,
- List.of("hmusum"), "2021-03-01", "2021-07-15",
- "JVM max heap size for metrics proxy in Mb",
- "Takes effect when restarting metrics proxy",
- CLUSTER_TYPE);
-
public static final UnboundStringFlag DEDICATED_CLUSTER_CONTROLLER_FLAVOR = defineStringFlag(
"dedicated-cluster-controller-flavor", "", List.of("jonmv"), "2021-02-25", "2021-08-25",
"Flavor as <vpu>-<memgb>-<diskgb> to use for dedicated cluster controller nodes",
diff --git a/vespajlib/src/main/java/com/yahoo/osgi/maven/ProjectBundleClassPaths.java b/vespajlib/src/main/java/com/yahoo/osgi/maven/ProjectBundleClassPaths.java
deleted file mode 100644
index 5d64548e4b9..00000000000
--- a/vespajlib/src/main/java/com/yahoo/osgi/maven/ProjectBundleClassPaths.java
+++ /dev/null
@@ -1,128 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.osgi.maven;
-
-import com.yahoo.slime.Cursor;
-import com.yahoo.slime.Inspector;
-import com.yahoo.slime.JsonFormat;
-import com.yahoo.slime.Slime;
-import com.yahoo.slime.SlimeUtils;
-
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Represents the bundles in a maven project and the classpath elements
- * corresponding to code that would end up in the bundle.
- *
- * @author Tony Vaagenes
- * @author bjorncs
- */
-
-public class ProjectBundleClassPaths {
- public static final String CLASSPATH_MAPPINGS_FILENAME = "bundle-plugin.bundle-classpath-mappings.json";
-
- public final BundleClasspathMapping mainBundle;
- public final List<BundleClasspathMapping> providedDependencies;
-
- public ProjectBundleClassPaths(BundleClasspathMapping mainBundle,
- List<BundleClasspathMapping> providedDependencies) {
- this.mainBundle = mainBundle;
- this.providedDependencies = providedDependencies;
- }
-
- public static void save(Path path, ProjectBundleClassPaths mappings) throws IOException {
- Files.createDirectories(path.getParent());
- try (OutputStream out = new BufferedOutputStream(Files.newOutputStream(path))) {
- save(out, mappings);
- }
- }
-
- static void save(OutputStream out, ProjectBundleClassPaths mappings) throws IOException {
- Slime slime = new Slime();
- Cursor rootCursor = slime.setObject();
- Cursor mainBundleCursor = rootCursor.setObject("mainBundle");
- BundleClasspathMapping.save(mainBundleCursor, mappings.mainBundle);
- Cursor dependenciesCursor = rootCursor.setArray("providedDependencies");
- mappings.providedDependencies
- .forEach(d -> BundleClasspathMapping.save(dependenciesCursor.addObject(), d));
- new JsonFormat(false).encode(out, slime);
- }
-
- public static ProjectBundleClassPaths load(Path path) throws IOException {
- byte[] bytes = Files.readAllBytes(path);
- return load(bytes);
- }
-
- static ProjectBundleClassPaths load(byte[] bytes) {
- Inspector inspector = SlimeUtils.jsonToSlime(bytes).get();
- BundleClasspathMapping mainBundle = BundleClasspathMapping.load(inspector.field("mainBundle"));
- Inspector dependenciesInspector = inspector.field("providedDependencies");
- List<BundleClasspathMapping> providedDependencies = new ArrayList<>();
- for (int i = 0; i < dependenciesInspector.entries(); i++) {
- providedDependencies.add(BundleClasspathMapping.load(dependenciesInspector.entry(i)));
- }
- return new ProjectBundleClassPaths(mainBundle, providedDependencies);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- ProjectBundleClassPaths that = (ProjectBundleClassPaths) o;
- return Objects.equals(mainBundle, that.mainBundle) &&
- Objects.equals(providedDependencies, that.providedDependencies);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mainBundle, providedDependencies);
- }
-
- public static class BundleClasspathMapping {
- public final String bundleSymbolicName;
- public final List<String> classPathElements;
-
- public BundleClasspathMapping(String bundleSymbolicName,
- List<String> classPathElements) {
- this.bundleSymbolicName = bundleSymbolicName;
- this.classPathElements = classPathElements;
- }
-
- static void save(Cursor rootCursor, BundleClasspathMapping mapping) {
- rootCursor.setString("bundleSymbolicName", mapping.bundleSymbolicName);
- Cursor arrayCursor = rootCursor.setArray("classPathElements");
- mapping.classPathElements.forEach(arrayCursor::addString);
- }
-
- static BundleClasspathMapping load(Inspector inspector) {
- String bundleSymoblicName = inspector.field("bundleSymbolicName").asString();
- Inspector elementsInspector = inspector.field("classPathElements");
- List<String> classPathElements = new ArrayList<>();
- for (int i = 0; i < elementsInspector.entries(); i++) {
- classPathElements.add(elementsInspector.entry(i).asString());
- }
- return new BundleClasspathMapping(bundleSymoblicName, classPathElements);
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- BundleClasspathMapping that = (BundleClasspathMapping) o;
- return Objects.equals(bundleSymbolicName, that.bundleSymbolicName) &&
- Objects.equals(classPathElements, that.classPathElements);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(bundleSymbolicName, classPathElements);
- }
- }
-
-}
diff --git a/vespajlib/src/test/java/com/yahoo/osgi/maven/ProjectBundleClassPathsTest.java b/vespajlib/src/test/java/com/yahoo/osgi/maven/ProjectBundleClassPathsTest.java
deleted file mode 100644
index 44a6a2eee8d..00000000000
--- a/vespajlib/src/test/java/com/yahoo/osgi/maven/ProjectBundleClassPathsTest.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.osgi.maven;
-
-import com.yahoo.osgi.maven.ProjectBundleClassPaths.BundleClasspathMapping;
-import org.junit.Test;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-
-import static java.util.Arrays.asList;
-import static org.junit.Assert.assertEquals;
-
-/**
- * @author bjorncs
- */
-public class ProjectBundleClassPathsTest {
-
- @Test
- public void bundle_classpaths_serializes_correctly_to_json() throws IOException {
- ProjectBundleClassPaths projectBundleClassPaths =
- new ProjectBundleClassPaths(
- new BundleClasspathMapping("main-bundle-name", asList("classpath-elem-0-1", "classpath-elem-0-2")),
- asList(
- new BundleClasspathMapping(
- "main-bundle-dep1",
- asList("classpath-elem-1-1", "classpath-elem-1-2")),
- new BundleClasspathMapping(
- "main-bundle-dep2",
- asList("classpath-elem-2-1", "classpath-elem-2-2"))));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- ProjectBundleClassPaths.save(out, projectBundleClassPaths);
- ProjectBundleClassPaths deserialized = ProjectBundleClassPaths.load(out.toByteArray());
- assertEquals(projectBundleClassPaths, deserialized);
- }
-
-}