ARIES-2030 implement extensions
Signed-off-by: Raymond Auge <rotty3000@apache.org>
diff --git a/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/ExtensionsTest.java b/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/ExtensionsTest.java
new file mode 100644
index 0000000..a475cdf
--- /dev/null
+++ b/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/ExtensionsTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.aries.jax.rs.rest.management.test;
+
+import static org.apache.aries.jax.rs.rest.management.RestManagementConstants.APPLICATION_EXTENSIONS_XML_TYPE;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import javax.ws.rs.client.WebTarget;
+import javax.ws.rs.core.Response;
+
+import org.apache.aries.jax.rs.rest.management.schema.ExtensionsSchema;
+import org.junit.jupiter.api.Test;
+import org.osgi.service.rest.RestApiExtension;
+import org.osgi.test.common.dictionary.Dictionaries;
+import org.osgi.test.common.stream.MapStream;
+import org.xmlunit.assertj3.XmlAssert;
+
+import net.javacrumbs.jsonunit.assertj.JsonAssertions;
+
+public class ExtensionsTest extends TestUtil {
+
+ @Test
+ public void getExtensionsJSON() {
+ WebTarget target = createDefaultTarget().path("extensions");
+
+ Response response = target.request().get();
+
+ String result = response.readEntity(String.class);
+
+ JsonAssertions.assertThatJson(
+ result
+ ).and(
+ j -> j.isObject()
+ );
+ }
+
+ @Test
+ public void getExtensionsJSON_WithService() {
+ WebTarget target = createDefaultTarget().path("extensions");
+
+ bundleContext.registerService(
+ RestApiExtension.class, new RestApiExtension() {},
+ Dictionaries.dictionaryOf(
+ RestApiExtension.NAME, "foo",
+ RestApiExtension.URI_PATH, "/foo"));
+
+ Response response = target.request().get();
+
+ String result = response.readEntity(String.class);
+
+ JsonAssertions.assertThatJson(
+ result
+ ).and(
+ j -> j.isObject(),
+ j -> j.node("extensions").isArray(),
+ j -> j.node("extensions[0]").and(
+ j1 -> j1.isObject(),
+ j1 -> j1.node("name").isEqualTo("foo"),
+ j1 -> j1.node("path").isEqualTo("/foo")
+ )
+ );
+ }
+
+ @Test
+ public void getExtensions_DOT_JSON() {
+ WebTarget target = createDefaultTarget().path("extensions.json");
+
+ Response response = target.request().get();
+
+ String result = response.readEntity(String.class);
+
+ JsonAssertions.assertThatJson(
+ result
+ ).and(
+ j -> j.isObject()
+ );
+ }
+
+ @Test
+ public void getExtensionsXML() {
+ WebTarget target = createDefaultTarget().path("extensions");
+
+ Response response = target.request(APPLICATION_EXTENSIONS_XML_TYPE).get();
+
+ String result = response.readEntity(String.class);
+
+ XmlAssert.assertThat(
+ result
+ ).isInvalid().nodesByXPath(
+ "//extensions/*"
+ ).isEmpty();
+ }
+
+ @Test
+ public void getExtensionsXML_DOT_XML() {
+ WebTarget target = createDefaultTarget().path("extensions.xml");
+
+ Response response = target.request().get();
+
+ String result = response.readEntity(String.class);
+
+ XmlAssert.assertThat(
+ result
+ ).isInvalid().nodesByXPath(
+ "//extensions/*"
+ ).isEmpty();
+ }
+
+ @Test
+ public void getExtensionsSchemaJSON() {
+ WebTarget target = createDefaultTarget().path("extensions");
+
+ Response response = target.request().get();
+
+ ExtensionsSchema extensionsSchema = response.readEntity(ExtensionsSchema.class);
+
+ assertThat(extensionsSchema.extensions.size()).isEqualTo(0);
+ }
+
+ @Test
+ public void getExtensionsSchemaXML() {
+ WebTarget target = createDefaultTarget().path("extensions");
+
+ Response response = target.request(APPLICATION_EXTENSIONS_XML_TYPE).get();
+
+ ExtensionsSchema extensionsSchema = response.readEntity(ExtensionsSchema.class);
+
+ assertThat(extensionsSchema.extensions.size()).isEqualTo(0);
+ }
+
+}
diff --git a/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/TestUtil.java b/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/TestUtil.java
index c2ebef6..e543a9f 100644
--- a/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/TestUtil.java
+++ b/integrations/rest-management/rest-management-itest/src/main/java/org/apache/aries/jax/rs/rest/management/test/TestUtil.java
@@ -56,7 +56,11 @@
@InjectService
public ClientBuilder clientBuilder;
- @InjectService(filter = "(%s=*)", filterArguments = JAX_RS_SERVICE_ENDPOINT)
+ @InjectService(
+ filter = "(%s=*)",
+ filterArguments = JAX_RS_SERVICE_ENDPOINT,
+ timeout = 400l
+ )
public ServiceAware<JaxrsServiceRuntime> jaxrsServiceRuntimeAware;
@InjectBundleContext
diff --git a/integrations/rest-management/rest-management/README.md b/integrations/rest-management/rest-management/README.md
index 877d229..b7f4b8e 100644
--- a/integrations/rest-management/rest-management/README.md
+++ b/integrations/rest-management/rest-management/README.md
@@ -8,6 +8,24 @@
Since there should only be one management API per framework the integration creates a separate application rooted at `${osgi.jaxrs.endpoint}/rms`
+Some of the available paths are:
+
+```
+/rms/framework/bundle/{bundleid}/header
+/rms/framework/bundle/{bundleid}
+/rms/framework/bundle/{bundleid}/startlevel
+/rms/framework/bundle/{bundleid}/state
+/rms/framework/bundles/representations
+/rms/framework/bundles
+/rms/framework
+/rms/framework/service/{serviceid}
+/rms/framework/services/representations
+/rms/framework/services
+/rms/framework/startlevel
+/rms/framework/state
+/rms/extensions
+```
+
### Open API
To simplify developer experience there is an Open API endpoint mounted at `${osgi.jaxrs.endpoint}/rms/openapi.(json|yaml)`.
diff --git a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/RestManagementConstants.java b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/RestManagementConstants.java
index 7dfd335..5161dee 100644
--- a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/RestManagementConstants.java
+++ b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/RestManagementConstants.java
@@ -109,6 +109,18 @@
public static final MediaType APPLICATION_BUNDLESTATE_XML_TYPE =
new MediaType("application", "org.osgi.bundlestate+xml");
+ public static final String APPLICATION_EXTENSIONS_JSON =
+ "application/org.osgi.extensions+json";
+
+ public static final MediaType APPLICATION_EXTENSIONS_JSON_TYPE =
+ new MediaType("application", "org.osgi.extensions+json");
+
+ public static final String APPLICATION_EXTENSIONS_XML =
+ "application/org.osgi.extensions+xml";
+
+ public static final MediaType APPLICATION_EXTENSIONS_XML_TYPE =
+ new MediaType("application", "org.osgi.extensions+xml");
+
public static final String APPLICATION_FRAMEWORKSTARTLEVEL_JSON =
"application/org.osgi.frameworkstartlevel+json";
diff --git a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/ExtensionResource.java b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/ExtensionResource.java
new file mode 100644
index 0000000..e82f318
--- /dev/null
+++ b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/ExtensionResource.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.aries.jax.rs.rest.management.internal;
+
+import static org.apache.aries.jax.rs.rest.management.RestManagementConstants.APPLICATION_EXTENSIONS_JSON;
+import static org.apache.aries.jax.rs.rest.management.RestManagementConstants.APPLICATION_EXTENSIONS_XML;
+
+import java.util.Optional;
+import java.util.Set;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.Response.ResponseBuilder;
+
+import org.apache.aries.component.dsl.CachingServiceReference;
+import org.apache.aries.jax.rs.rest.management.schema.ExtensionsSchema;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.rest.RestApiExtension;
+
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.media.Content;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+
+public class ExtensionResource extends BaseResource {
+
+ private final Set<CachingServiceReference<RestApiExtension>> extensions;
+
+ public ExtensionResource(
+ BundleContext bundleContext,
+ Set<CachingServiceReference<RestApiExtension>> extensions) {
+
+ super(bundleContext);
+ this.extensions = extensions;
+ }
+
+ @GET
+ @Path("extensions{ext: (\\.json|\\.xml)*}")
+ @Produces({APPLICATION_EXTENSIONS_JSON , APPLICATION_EXTENSIONS_XML})
+ @Operation(
+ summary = "Retrieves a Extensions Representation ",
+ responses = {
+ @ApiResponse(
+ responseCode = "200",
+ description = "The framework bundle",
+ content = @Content(schema = @Schema(implementation = ExtensionsSchema.class))
+ ),
+ @ApiResponse(
+ responseCode = "406",
+ description = "The REST management service does not support any of the requested representations"
+ )
+ }
+ )
+ public Response extensions(
+ @Parameter(allowEmptyValue = true, schema = @Schema(allowableValues = {".json", ".xml"}))
+ @PathParam("ext") String ext) {
+
+ ResponseBuilder builder = Response.status(
+ Response.Status.OK
+ ).entity(
+ ExtensionsSchema.build(extensions)
+ );
+
+ return Optional.ofNullable(
+ ext
+ ).map(
+ String::trim
+ ).map(
+ t -> ".json".equals(t) ? APPLICATION_EXTENSIONS_JSON : APPLICATION_EXTENSIONS_XML
+ ).map(t -> builder.type(t)).orElse(
+ builder
+ ).build();
+ }
+
+}
diff --git a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementActivator.java b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementActivator.java
index e742c83..4c6f143 100644
--- a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementActivator.java
+++ b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementActivator.java
@@ -19,6 +19,7 @@
import static org.apache.aries.component.dsl.OSGi.all;
import static org.apache.aries.component.dsl.OSGi.ignore;
+import static org.apache.aries.component.dsl.OSGi.just;
import static org.apache.aries.component.dsl.OSGi.register;
import static org.apache.aries.component.dsl.OSGi.service;
import static org.apache.aries.component.dsl.OSGi.serviceReferences;
@@ -29,10 +30,12 @@
import java.util.HashMap;
import java.util.function.BiFunction;
+import java.util.function.Consumer;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.core.Application;
+import org.apache.aries.component.dsl.OSGi;
import org.apache.aries.component.dsl.OSGiResult;
import org.apache.aries.jax.rs.rest.management.feature.RestManagementFeature;
import org.apache.aries.jax.rs.rest.management.internal.client.RestClientFactoryImpl;
@@ -44,6 +47,7 @@
import org.osgi.framework.PrototypeServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
+import org.osgi.service.rest.RestApiExtension;
import org.osgi.service.rest.client.RestClientFactory;
import io.swagger.v3.oas.models.ExternalDocumentation;
@@ -63,18 +67,31 @@
public void start(BundleContext bundleContext) throws Exception {
result = all(
ignore(
- register(
- Application.class,
- () -> new RestManagementApplication(bundleContext),
- () -> {
- HashMap<String, Object> map = new HashMap<>();
+ just(
+ new RestManagementApplication(bundleContext)
+ ).flatMap(application ->
+ register(
+ Application.class,
+ () -> application,
+ () -> {
+ HashMap<String, Object> map = new HashMap<>();
- map.put(JAX_RS_NAME, RestManagementApplication.class.getSimpleName());
- map.put(
- JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE, RMS_BASE);
+ map.put(JAX_RS_NAME, RestManagementApplication.class.getSimpleName());
+ map.put(
+ JaxrsWhiteboardConstants.JAX_RS_APPLICATION_BASE, RMS_BASE);
- return map;
- }
+ return map;
+ }
+ ).then(
+ dynamic(
+ serviceReferences(
+ RestApiExtension.class,
+ "(&(org.osgi.rest.name=*)(org.osgi.rest.uri.path=*))"
+ ),
+ application::addExtension,
+ application::removeExtension
+ )
+ )
)
),
ignore(
@@ -164,6 +181,12 @@
result.close();
}
+ public static <T> OSGi<Void> dynamic(
+ OSGi<T> program, Consumer<T> bind, Consumer<T> unbind) {
+
+ return program.foreach(bind, unbind);
+ }
+
class PrototypeWrapper<S> implements PrototypeServiceFactory<S> {
private final BiFunction<Bundle, ServiceRegistration<S>, S> function;
diff --git a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementApplication.java b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementApplication.java
index d7d5ab3..5bec98c 100644
--- a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementApplication.java
+++ b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/internal/RestManagementApplication.java
@@ -17,17 +17,23 @@
package org.apache.aries.jax.rs.rest.management.internal;
+import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
import javax.ws.rs.core.Application;
+import org.apache.aries.component.dsl.CachingServiceReference;
import org.apache.aries.jax.rs.rest.management.internal.jaxb.ServiceSchemaContextResolver;
import org.osgi.framework.BundleContext;
+import org.osgi.service.rest.RestApiExtension;
public class RestManagementApplication extends Application {
private final BundleContext bundleContext;
+ private final Set<CachingServiceReference<RestApiExtension>> extensions =
+ new ConcurrentSkipListSet<>(Comparator.naturalOrder());
public RestManagementApplication(BundleContext bundleContext) {
this.bundleContext = bundleContext;
@@ -39,6 +45,7 @@
singletons.add(new ServiceSchemaContextResolver());
+ singletons.add(new ExtensionResource(bundleContext, extensions));
singletons.add(new FrameworkBundleHeaderResource(bundleContext));
singletons.add(new FrameworkBundleResource(bundleContext));
singletons.add(new FrameworkBundlesRepresentationsResource(bundleContext));
@@ -54,4 +61,12 @@
return singletons;
}
+ public void addExtension(CachingServiceReference<RestApiExtension> extension) {
+ extensions.add(extension);
+ }
+
+ public void removeExtension(CachingServiceReference<RestApiExtension> extension) {
+ extensions.remove(extension);
+ }
+
}
diff --git a/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/schema/ExtensionsSchema.java b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/schema/ExtensionsSchema.java
new file mode 100644
index 0000000..a2129ce
--- /dev/null
+++ b/integrations/rest-management/rest-management/src/main/java/org/apache/aries/jax/rs/rest/management/schema/ExtensionsSchema.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.aries.jax.rs.rest.management.schema;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.apache.aries.component.dsl.CachingServiceReference;
+import org.osgi.service.rest.RestApiExtension;
+
+@XmlRootElement(name = "extensions")
+public class ExtensionsSchema {
+
+ @XmlElement(name = "extension")
+ public List<Extension> extensions = new ArrayList<>();
+
+ public static class Extension {
+ public String name;
+ public String path;
+ public long service;
+ }
+
+ public static ExtensionsSchema build(
+ Set<CachingServiceReference<RestApiExtension>> extensions) {
+
+ ExtensionsSchema extensionsSchema = new ExtensionsSchema();
+
+ extensions.stream().map(
+ ext -> {
+ Extension extension = new Extension();
+ extension.name = (String)ext.getProperty("org.osgi.rest.name");
+ extension.path = (String)ext.getProperty("org.osgi.rest.uri.path");
+ Optional.ofNullable(
+ ext.getProperty("org.osgi.rest.service")
+ ).map(
+ Long.class::cast
+ ).ifPresent(
+ service -> extension.service = service
+ );
+ return extension;
+ }
+ ).forEach(extensionsSchema.extensions::add);
+
+ return extensionsSchema;
+ }
+
+}