From 30eb0c9aff6e2fa81cb1a0bf41346db976b2b1b0 Mon Sep 17 00:00:00 2001 From: nscuro Date: Tue, 14 May 2024 20:44:23 +0200 Subject: [PATCH 1/2] Make `About` extensible Introduces an SPI that applications can use to provide additional data to be exposed via `/api/version`. Signed-off-by: nscuro --- .../java/alpine/common/AboutProvider.java | 38 +++++++++++++++++++ .../src/main/java/alpine/model/About.java | 31 +++++++++++++++ .../src/test/java/alpine/model/AboutTest.java | 24 ++++++++++++ .../services/alpine.common.AboutProvider | 1 + 4 files changed, 94 insertions(+) create mode 100644 alpine-common/src/main/java/alpine/common/AboutProvider.java create mode 100644 alpine-model/src/test/resources/META-INF/services/alpine.common.AboutProvider diff --git a/alpine-common/src/main/java/alpine/common/AboutProvider.java b/alpine-common/src/main/java/alpine/common/AboutProvider.java new file mode 100644 index 00000000..d882ae3f --- /dev/null +++ b/alpine-common/src/main/java/alpine/common/AboutProvider.java @@ -0,0 +1,38 @@ +/* + * This file is part of Alpine. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.common; + +import java.util.Map; + +/** + * @since 2.2.6 + */ +public interface AboutProvider { + + /** + * @return The name of the component this provider collects "about" information for + */ + String name(); + + /** + * @return Information about the described component + */ + Map collect(); + +} diff --git a/alpine-model/src/main/java/alpine/model/About.java b/alpine-model/src/main/java/alpine/model/About.java index a2421315..b313b866 100644 --- a/alpine-model/src/main/java/alpine/model/About.java +++ b/alpine-model/src/main/java/alpine/model/About.java @@ -19,9 +19,16 @@ package alpine.model; import alpine.Config; +import alpine.common.AboutProvider; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import javax.inject.Singleton; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; /** * A value object describing the name of the application, version, and the timestamp when it was built. @@ -91,4 +98,28 @@ public String getUuid() { public String getSystemUuid() { return SYSTEM_UUID; } + + /** + * @return Additional information provided by {@link AboutProvider}s + * @since 2.2.6 + */ + @JsonAnyGetter + @JsonInclude(Include.NON_EMPTY) + @SuppressWarnings("unused") // Called by JSON serializer. + public Map getProviderData() { + final ServiceLoader providers = ServiceLoader.load(AboutProvider.class); + + final var data = new HashMap(); + for (final AboutProvider provider : providers) { + final String providerName = provider.name(); + final Map providerData = provider.collect(); + + if (providerData != null && !providerData.isEmpty()) { + data.put(providerName, providerData); + } + } + + return data; + } + } diff --git a/alpine-model/src/test/java/alpine/model/AboutTest.java b/alpine-model/src/test/java/alpine/model/AboutTest.java index 9d605239..57bf5f84 100644 --- a/alpine-model/src/test/java/alpine/model/AboutTest.java +++ b/alpine-model/src/test/java/alpine/model/AboutTest.java @@ -18,10 +18,13 @@ */ package alpine.model; +import alpine.common.AboutProvider; import alpine.common.util.UuidUtil; import org.junit.Assert; import org.junit.Test; +import java.util.Map; + public class AboutTest { @Test @@ -36,5 +39,26 @@ public void getterTest() { Assert.assertTrue(about.getFramework().getVersion().startsWith("2.")); Assert.assertTrue(about.getFramework().getTimestamp().startsWith("20")); Assert.assertTrue(UuidUtil.isValidUUID(about.getFramework().getUuid())); + + final Map providerData = about.getProviderData(); + Assert.assertEquals(1, providerData.size()); + Assert.assertNotNull(providerData.get("test")); + Assert.assertTrue(providerData.get("test") instanceof Map); + Assert.assertEquals("bar", ((Map) providerData.get("test")).get("foo")); + } + + public static class TestProvider implements AboutProvider { + + @Override + public String name() { + return "test"; + } + + @Override + public Map collect() { + return Map.of("foo", "bar"); + } + } + } diff --git a/alpine-model/src/test/resources/META-INF/services/alpine.common.AboutProvider b/alpine-model/src/test/resources/META-INF/services/alpine.common.AboutProvider new file mode 100644 index 00000000..820a203c --- /dev/null +++ b/alpine-model/src/test/resources/META-INF/services/alpine.common.AboutProvider @@ -0,0 +1 @@ +alpine.model.AboutTest$TestProvider \ No newline at end of file From 72c916421de964e3c3c151725d2d1b00415528f1 Mon Sep 17 00:00:00 2001 From: nscuro Date: Tue, 14 May 2024 20:46:22 +0200 Subject: [PATCH 2/2] Add `AboutProvider` for database information Example response of `/api/version` for Dependency-Track: ```json { "version": "4.12.0-SNAPSHOT", "timestamp": "2024-05-14T18:33:19Z", "framework": { "version": "2.2.6-SNAPSHOT", "timestamp": "2024-05-14T18:32:59Z", "name": "Alpine", "uuid": "d7a6b868-13a3-4bbd-909d-a7780a9a6b8f" }, "application": "Dependency-Track", "systemUuid": "21e013b8-8a51-4314-ac38-bed8cdde21c9", "uuid": "d1bf53e9-3a1a-4cd5-b65d-e969b51e9840", "database": { "productVersion": "2.2.224 (2023-09-17)", "productName": "H2" } } ``` Signed-off-by: nscuro --- .../persistence/DatabaseAboutProvider.java | 69 +++++++++++++++++++ .../services/alpine.common.AboutProvider | 1 + 2 files changed, 70 insertions(+) create mode 100644 alpine-server/src/main/java/alpine/server/persistence/DatabaseAboutProvider.java create mode 100644 alpine-server/src/main/resources/META-INF/services/alpine.common.AboutProvider diff --git a/alpine-server/src/main/java/alpine/server/persistence/DatabaseAboutProvider.java b/alpine-server/src/main/java/alpine/server/persistence/DatabaseAboutProvider.java new file mode 100644 index 00000000..67a589ac --- /dev/null +++ b/alpine-server/src/main/java/alpine/server/persistence/DatabaseAboutProvider.java @@ -0,0 +1,69 @@ +/* + * This file is part of Alpine. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package alpine.server.persistence; + +import alpine.common.AboutProvider; +import alpine.common.logging.Logger; + +import javax.jdo.PersistenceManager; +import javax.jdo.datastore.JDOConnection; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; +import java.util.HashMap; +import java.util.Map; + +import static alpine.server.persistence.PersistenceManagerFactory.createPersistenceManager; + +/** + * An {@link AboutProvider} for database information. + * + * @since 2.2.6 + */ +public class DatabaseAboutProvider implements AboutProvider { + + private static final Logger LOGGER = Logger.getLogger(DatabaseAboutProvider.class); + + @Override + public String name() { + return "database"; + } + + @Override + public Map collect() { + final var data = new HashMap(); + + try (final PersistenceManager pm = createPersistenceManager()) { + final JDOConnection jdoConnection = pm.getDataStoreConnection(); + final var nativeConnection = (Connection) jdoConnection.getNativeConnection(); + try { + final DatabaseMetaData databaseMetaData = nativeConnection.getMetaData(); + data.put("productName", databaseMetaData.getDatabaseProductName()); + data.put("productVersion", databaseMetaData.getDatabaseProductVersion()); + } catch (SQLException e) { + LOGGER.error("Failed to retrieve database metadata", e); + } finally { + jdoConnection.close(); + } + } + + return data; + } + +} diff --git a/alpine-server/src/main/resources/META-INF/services/alpine.common.AboutProvider b/alpine-server/src/main/resources/META-INF/services/alpine.common.AboutProvider new file mode 100644 index 00000000..7143fd66 --- /dev/null +++ b/alpine-server/src/main/resources/META-INF/services/alpine.common.AboutProvider @@ -0,0 +1 @@ +alpine.server.persistence.DatabaseAboutProvider \ No newline at end of file