From cac4810575e8d4605949b8c31521b394c8547b20 Mon Sep 17 00:00:00 2001 From: Carles Arnal Date: Wed, 20 Nov 2024 15:48:14 +0100 Subject: [PATCH] Move common components to registry repo (#5530) * Move common components to registry repo * Remove auth component * Remove util component * Remove web component * Remove test component * Move config properties to app * Remove remaining components from common components * Add dynamic config source * Adapt register for reflection * Use maven plugin from the github repository --- .github/workflows/integration-tests.yaml | 2 +- .github/workflows/release-images.yaml | 2 +- .github/workflows/verify.yaml | 6 +- app/pom.xml | 31 +- .../auth/AppAuthenticationMechanism.java | 351 ++++++++++++++++++ .../registry/auth/CredentialsHelper.java | 63 ++++ .../apicurio/registry/auth/WrappedValue.java | 41 ++ .../rest/v7/impl/AbstractResource.java | 4 +- .../v7/impl/CompatibilityResourceImpl.java | 2 +- .../rest/v7/impl/ConfigResourceImpl.java | 6 +- .../rest/v7/impl/ContextResourceImpl.java | 2 +- .../rest/v7/impl/ExporterResourceImpl.java | 2 +- .../rest/v7/impl/ModeResourceImpl.java | 2 +- .../rest/v7/impl/SchemasResourceImpl.java | 2 +- .../v7/impl/SubjectVersionsResourceImpl.java | 8 +- .../rest/v7/impl/SubjectsResourceImpl.java | 6 +- .../config/impl/DynamicConfigProducer.java | 46 +++ .../impl/DynamicConfigPropertyIndexImpl.java | 136 +++++++ .../config/impl/DynamicConfigSource.java | 117 ++++++ .../config/impl/DynamicConfigStartup.java | 50 +++ .../DynamicConfigPropertyDtoMapper.java | 42 +++ .../DynamicConfigSqlStorageComponent.java | 140 +++++++ .../apicurio/registry/core/AppException.java | 44 +++ .../io/apicurio/registry/core/System.java | 81 ++++ .../registry/logging/DefaultLoggerClass.java | 24 ++ .../io/apicurio/registry/logging/Logged.java | 33 ++ .../registry/logging/LoggerProducer.java | 59 +++ .../registry/logging/LoggingInterceptor.java | 72 ++++ .../audit/AuditHttpRequestContext.java | 58 +++ .../logging/audit/AuditHttpRequestInfo.java | 31 ++ .../logging/audit/AuditLogService.java | 62 ++++ .../logging/audit/AuditMetaDataExtractor.java | 32 ++ .../registry/logging/audit/Audited.java | 61 +++ .../logging/audit/AuditedInterceptor.java | 145 ++++++++ .../logging/audit/AuditingConstants.java | 39 ++ .../audit/HttpRequestsAuditFilter.java | 81 ++++ .../registry/rest/v2/AdminResourceImpl.java | 18 +- .../rest/v2/DownloadsResourceImpl.java | 2 +- .../registry/rest/v2/GroupsResourceImpl.java | 22 +- .../registry/rest/v2/IdsResourceImpl.java | 2 +- .../registry/rest/v2/SearchResourceImpl.java | 2 +- .../registry/rest/v2/SystemResourceImpl.java | 4 +- .../registry/rest/v2/UsersResourceImpl.java | 2 +- .../registry/rest/v3/AdminResourceImpl.java | 20 +- .../rest/v3/DownloadsResourceImpl.java | 2 +- .../registry/rest/v3/GroupsResourceImpl.java | 20 +- .../registry/rest/v3/IdsResourceImpl.java | 2 +- .../registry/rest/v3/SearchResourceImpl.java | 2 +- .../registry/rest/v3/SystemResourceImpl.java | 4 +- .../registry/rest/v3/UsersResourceImpl.java | 2 +- .../CompatibilityRuleExecutor.java | 2 +- .../integrity/IntegrityRuleExecutor.java | 2 +- .../rules/validity/ValidityRuleExecutor.java | 2 +- .../impl/gitops/GitOpsRegistryStorage.java | 2 +- .../impl/gitops/sql/BlueSqlStorage.java | 2 +- .../impl/gitops/sql/GreenSqlStorage.java | 2 +- .../kafkasql/KafkaSqlRegistryStorage.java | 2 +- .../impl/kafkasql/KafkaSqlSubmitter.java | 2 +- .../impl/kafkasql/sql/KafkaSqlSink.java | 2 +- .../impl/sql/AbstractSqlRegistryStorage.java | 2 +- .../storage/impl/sql/SqlRegistryStorage.java | 2 +- ...lipse.microprofile.config.spi.ConfigSource | 1 + .../registry/rbac/MockAuditLogService.java | 4 +- common/pom.xml | 2 +- config-index/definitions/pom.xml | 32 ++ .../apicurio/common/apps/config/Dynamic.java | 47 +++ .../apps/config/DynamicConfigPropertyDef.java | 194 ++++++++++ .../apps/config/DynamicConfigPropertyDto.java | 106 ++++++ .../config/DynamicConfigPropertyIndex.java | 36 ++ .../config/DynamicConfigPropertyList.java | 40 ++ .../DynamicConfigSqlStorageStatements.java | 36 ++ .../apps/config/DynamicConfigStorage.java | 57 +++ .../config/DynamicConfigStorageAccessor.java | 26 ++ .../io/apicurio/common/apps/config/Info.java | 60 +++ .../src/main/resources/META-INF/beans.xml | 5 + config-index/deployment/pom.xml | 52 +++ .../deployment/ConfigIndexProcessor.java | 107 ++++++ config-index/runtime/pom.xml | 64 ++++ .../index/DynamicPropertiesInfoRecorder.java | 34 ++ .../resources/META-INF/quarkus-extension.yaml | 9 + docs/generateAllConfigPartial.java | 5 +- .../ref-registry-all-configs.adoc | 33 +- docs/pom.xml | 2 +- pom.xml | 43 ++- prod-verifier/pom.xml | 4 +- schema-util/json/pom.xml | 5 +- 86 files changed, 2871 insertions(+), 142 deletions(-) create mode 100644 app/src/main/java/io/apicurio/registry/auth/AppAuthenticationMechanism.java create mode 100644 app/src/main/java/io/apicurio/registry/auth/CredentialsHelper.java create mode 100644 app/src/main/java/io/apicurio/registry/auth/WrappedValue.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigProducer.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigPropertyIndexImpl.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigSource.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigStartup.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigPropertyDtoMapper.java create mode 100644 app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigSqlStorageComponent.java create mode 100644 app/src/main/java/io/apicurio/registry/core/AppException.java create mode 100644 app/src/main/java/io/apicurio/registry/core/System.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/DefaultLoggerClass.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/Logged.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/LoggerProducer.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/LoggingInterceptor.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestContext.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestInfo.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/AuditLogService.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/AuditMetaDataExtractor.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/Audited.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/AuditedInterceptor.java create mode 100644 app/src/main/java/io/apicurio/registry/logging/audit/HttpRequestsAuditFilter.java create mode 100644 config-index/definitions/pom.xml create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/Dynamic.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDef.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDto.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyIndex.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyList.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigSqlStorageStatements.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorage.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorageAccessor.java create mode 100644 config-index/definitions/src/main/java/io/apicurio/common/apps/config/Info.java create mode 100644 config-index/definitions/src/main/resources/META-INF/beans.xml create mode 100644 config-index/deployment/pom.xml create mode 100644 config-index/deployment/src/main/java/apicurio/common/app/components/config/index/deployment/ConfigIndexProcessor.java create mode 100644 config-index/runtime/pom.xml create mode 100644 config-index/runtime/src/main/java/apicurio/common/app/components/config/index/DynamicPropertiesInfoRecorder.java create mode 100644 config-index/runtime/src/main/resources/META-INF/quarkus-extension.yaml diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 641f24c1d9..5cdef9599f 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -59,7 +59,7 @@ jobs: run: mvn -N io.takari:maven:wrapper -Dmaven=3.8.2 - name: Build Application - run: ./mvnw clean package -pl app,distro/docker -am --no-transfer-progress -Pprod -DskipTests=true -Dmaven.javadoc.skip=true -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 + run: ./mvnw clean package -am --no-transfer-progress -Pprod -DskipTests=true -Dmaven.javadoc.skip=true -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 - name: Build and Push Application image run: | diff --git a/.github/workflows/release-images.yaml b/.github/workflows/release-images.yaml index 0fee8e49c5..4b08e37c87 100644 --- a/.github/workflows/release-images.yaml +++ b/.github/workflows/release-images.yaml @@ -101,7 +101,7 @@ jobs: - name: Build Registry run: | cd registry - ./mvnw clean package -pl app,distro/docker -am --no-transfer-progress -Pprod -DskipTests=true -DskipCommitIdPlugin=false -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 -Dspotless.check.skip=true + ./mvnw clean package -am --no-transfer-progress -Pprod -DskipTests=true -DskipCommitIdPlugin=false -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 -Dspotless.check.skip=true - name: Build Registry UI working-directory: registry/ui diff --git a/.github/workflows/verify.yaml b/.github/workflows/verify.yaml index cce37a3a02..9f9840834a 100644 --- a/.github/workflows/verify.yaml +++ b/.github/workflows/verify.yaml @@ -218,7 +218,7 @@ jobs: ( cd /tmp/coreutils-workaround && mvn dependency:get -DremoteRepositories=https://repo1.maven.org/maven2 -Dartifact=com.github.java-json-tools:jackson-coreutils:2.0 ) - name: Build Application - run: ./mvnw clean package -pl app,distro/docker -am -Pprod -DskipTests=true -DskipCommitIdPlugin=false -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 --no-transfer-progress + run: ./mvnw clean package -am -Pprod -DskipTests=true -DskipCommitIdPlugin=false -Dmaven.wagon.httpconnectionManager.maxTotal=30 -Dmaven.wagon.http.retryHandler.count=5 --no-transfer-progress - name: Build Native executables env: @@ -330,7 +330,7 @@ jobs: run: make lint-check - name: Build Registry - run: mvn clean package -pl app -am -Dskip.npm -DskipTests=true --no-transfer-progress + run: mvn clean package -am -Dskip.npm -DskipTests=true --no-transfer-progress - name: Run the tests working-directory: python-sdk @@ -357,7 +357,7 @@ jobs: go-version: '1.20' - name: Build Registry - run: mvn clean package -pl app -am -Dskip.npm -DskipTests=true --no-transfer-progress + run: mvn clean package -am -Dskip.npm -DskipTests=true --no-transfer-progress - name: Run the tests working-directory: go-sdk diff --git a/app/pom.xml b/app/pom.xml index f5820a5b06..4b75105c94 100644 --- a/app/pom.xml +++ b/app/pom.xml @@ -34,31 +34,16 @@ apicurio-registry-schema-util-provider - io.apicurio - apicurio-common-app-components-core + apicurio-registry-config-definitions io.apicurio - apicurio-common-app-components-logging + apicurio-registry-config-index - - io.apicurio - apicurio-common-app-components-config - - - - io.apicurio - apicurio-common-app-components-config-index - - - - io.apicurio - apicurio-common-app-components-auth - io.apicurio apicurio-common-rest-client-jdk @@ -209,6 +194,15 @@ snakeyaml ${snakeyaml.version} + + com.google.guava + guava + + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + @@ -408,8 +402,7 @@ io.apicurio - apicurio-common-app-components-maven-plugin - ${apicurio-common-app-components.version} + apicurio-registry-maven-plugin merge-test-properties diff --git a/app/src/main/java/io/apicurio/registry/auth/AppAuthenticationMechanism.java b/app/src/main/java/io/apicurio/registry/auth/AppAuthenticationMechanism.java new file mode 100644 index 0000000000..ef2e9ad532 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/auth/AppAuthenticationMechanism.java @@ -0,0 +1,351 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.auth; + +import io.apicurio.common.apps.config.Dynamic; +import io.apicurio.common.apps.config.Info; +import io.apicurio.registry.logging.audit.AuditHttpRequestContext; +import io.apicurio.registry.logging.audit.AuditHttpRequestInfo; +import io.apicurio.registry.logging.audit.AuditLogService; +import io.apicurio.rest.client.VertxHttpClientProvider; +import io.apicurio.rest.client.auth.OidcAuth; +import io.apicurio.rest.client.auth.exception.AuthErrorHandler; +import io.apicurio.rest.client.auth.exception.AuthException; +import io.apicurio.rest.client.auth.exception.ForbiddenException; +import io.apicurio.rest.client.auth.exception.NotAuthorizedException; +import io.apicurio.rest.client.error.ApicurioRestClientException; +import io.apicurio.rest.client.spi.ApicurioHttpClient; +import io.quarkus.arc.Unremovable; +import io.quarkus.oidc.runtime.OidcAuthenticationMechanism; +import io.quarkus.security.identity.IdentityProviderManager; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.quarkus.vertx.http.runtime.security.*; +import io.smallrye.jwt.auth.principal.DefaultJWTParser; +import io.smallrye.jwt.auth.principal.ParseException; +import io.smallrye.mutiny.Uni; +import io.vertx.core.Vertx; +import io.vertx.ext.web.RoutingContext; +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Alternative; +import jakarta.inject.Inject; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.slf4j.Logger; + +import java.time.Duration; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +@Alternative +@Priority(1) +@ApplicationScoped +@Unremovable +public class AppAuthenticationMechanism implements HttpAuthenticationMechanism { + + @ConfigProperty(name = "quarkus.oidc.tenant-enabled", defaultValue = "false") + @Info(category = "auth", description = "Enable auth", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.0.0.Final", studioAvailableSince = "1.0.0") + boolean oidcAuthEnabled; + + // back to fake auth and use another property + @Dynamic(label = "HTTP basic authentication", description = "When selected, users are permitted to authenticate using HTTP basic authentication (in addition to OAuth).", requires = "apicurio.authn.enabled=true") + @ConfigProperty(name = "apicurio.authn.basic-client-credentials.enabled", defaultValue = "false") + @Info(category = "auth", description = "Enable basic auth client credentials", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.1.0.Final", studioAvailableSince = "1.0.0") + Supplier basicClientCredentialsAuthEnabled; + + @ConfigProperty(name = "quarkus.http.auth.basic", defaultValue = "false") + @Info(category = "auth", description = "Enable basic auth", availableSince = "1.1.X-SNAPSHOT", registryAvailableSince = "3.X.X.Final", studioAvailableSince = "1.0.0") + boolean basicAuthEnabled; + + @ConfigProperty(name = "apicurio.authn.basic-client-credentials.cache-expiration", defaultValue = "10") + @Info(category = "auth", description = "Default client credentials token expiration time.", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.2.6.Final", studioAvailableSince = "1.0.0") + Integer accessTokenExpiration; + + @ConfigProperty(name = "apicurio.authn.basic-client-credentials.cache-expiration-offset", defaultValue = "10") + @Info(category = "auth", description = "Client credentials token expiration offset from JWT expiration.", availableSince = "0.2.7", registryAvailableSince = "2.5.9.Final", studioAvailableSince = "1.0.0") + Integer accessTokenExpirationOffset; + + @ConfigProperty(name = "apicurio.authn.basic.scope") + @Info(category = "auth", description = "Client credentials scope.", availableSince = "0.1.21-SNAPSHOT", registryAvailableSince = "2.5.0.Final", studioAvailableSince = "1.0.0") + Optional scope; + + @ConfigProperty(name = "apicurio.authn.audit.log.prefix", defaultValue = "audit") + @Info(category = "auth", description = "Prefix used for application audit logging.", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.2.6", studioAvailableSince = "1.0.0") + + String auditLogPrefix; + + @ConfigProperty(name = "quarkus.oidc.token-path", defaultValue = "") + @Info(category = "auth", description = "Authentication server token endpoint.", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.1.0.Final", studioAvailableSince = "1.0.0") + String authServerUrl; + + @ConfigProperty(name = "quarkus.oidc.client-secret") + @Info(category = "auth", description = "Client secret used by the server for authentication.", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.1.0.Final", studioAvailableSince = "1.0.0") + Optional clientSecret; + + @ConfigProperty(name = "quarkus.oidc.client-id", defaultValue = "") + @Info(category = "auth", description = "Client identifier used by the server for authentication.", availableSince = "0.1.18-SNAPSHOT", registryAvailableSince = "2.0.0.Final", studioAvailableSince = "1.0.0") + String clientId; + + @Inject + BasicAuthenticationMechanism basicAuthenticationMechanism; + + @Inject + OidcAuthenticationMechanism oidcAuthenticationMechanism; + + @Inject + AuditLogService auditLog; + + @Inject + Logger log; + + @Inject + Vertx vertx; + + @Inject + DefaultJWTParser jwtParser; + + private ApicurioHttpClient httpClient; + + private ConcurrentHashMap> cachedAccessTokens; + private ConcurrentHashMap> cachedAuthFailures; + + @PostConstruct + public void init() { + if (oidcAuthEnabled) { + cachedAccessTokens = new ConcurrentHashMap<>(); + cachedAuthFailures = new ConcurrentHashMap<>(); + httpClient = new VertxHttpClientProvider(vertx).create(authServerUrl, Collections.emptyMap(), + null, new AuthErrorHandler()); + } + } + + private HttpAuthenticationMechanism selectEnabledAuth() { + if (basicAuthEnabled) { + return basicAuthenticationMechanism; + } else if (oidcAuthEnabled) { + return oidcAuthenticationMechanism; + } else { + return null; + } + } + + @Override + public Uni authenticate(RoutingContext context, + IdentityProviderManager identityProviderManager) { + if (basicAuthEnabled) { + return basicAuthenticationMechanism.authenticate(context, identityProviderManager); + } else if (oidcAuthEnabled) { + setAuditLogger(context); + if (basicClientCredentialsAuthEnabled.get()) { + final Pair clientCredentials = CredentialsHelper + .extractCredentialsFromContext(context); + if (null != clientCredentials) { + try { + return authenticateWithClientCredentials(clientCredentials, context, + identityProviderManager); + } catch (AuthException | NotAuthorizedException ex) { + log.warn(String.format( + "Exception trying to get an access token with client credentials with client id: %s", + clientCredentials.getLeft()), ex); + return oidcAuthenticationMechanism.authenticate(context, identityProviderManager); + } + } else { + return customAuthentication(context, identityProviderManager); + } + } else { + // Once we're done with it in the auth layer, the context must be cleared. + return customAuthentication(context, identityProviderManager); + } + } else { + return Uni.createFrom().nullItem(); + } + } + + public Uni customAuthentication(RoutingContext context, + IdentityProviderManager identityProviderManager) { + if (clientSecret.isEmpty()) { + // if no secret is present, try to authenticate with oidc provider + return oidcAuthenticationMechanism.authenticate(context, identityProviderManager); + } else { + final Pair credentialsFromContext = CredentialsHelper + .extractCredentialsFromContext(context); + if (credentialsFromContext != null) { + OidcAuth oidcAuth = new OidcAuth(httpClient, clientId, clientSecret.get()); + String jwtToken = oidcAuth.obtainAccessTokenPasswordGrant(credentialsFromContext.getLeft(), + credentialsFromContext.getRight()); + if (jwtToken != null) { + // If we manage to get a token from basic credentials, try to authenticate it using the + // fetched token using the identity provider manager + context.request().headers().set("Authorization", "Bearer " + jwtToken); + return oidcAuthenticationMechanism.authenticate(context, identityProviderManager); + } + } else { + // If we cannot get a token, then try to authenticate using oidc provider as last resource + return oidcAuthenticationMechanism.authenticate(context, identityProviderManager); + } + } + return Uni.createFrom().nullItem(); + } + + private void setAuditLogger(RoutingContext context) { + BiConsumer failureHandler = context + .get(QuarkusHttpUser.AUTH_FAILURE_HANDLER); + BiConsumer auditWrapper = (ctx, ex) -> { + // this sends the http response + failureHandler.accept(ctx, ex); + // if it was an error response log it + if (ctx.response().getStatusCode() >= 400) { + Map metadata = new HashMap<>(); + metadata.put("method", ctx.request().method().name()); + metadata.put("path", ctx.request().path()); + metadata.put("response_code", String.valueOf(ctx.response().getStatusCode())); + if (ex != null) { + metadata.put("error_msg", ex.getMessage()); + } + + // request context for AuditHttpRequestContext does not exist at this point + auditLog.log(auditLogPrefix, "authenticate", AuditHttpRequestContext.FAILURE, metadata, + new AuditHttpRequestInfo() { + @Override + public String getSourceIp() { + return ctx.request().remoteAddress().toString(); + } + + @Override + public String getForwardedFor() { + return ctx.request() + .getHeader(AuditHttpRequestContext.X_FORWARDED_FOR_HEADER); + } + }); + } + }; + + context.put(QuarkusHttpUser.AUTH_FAILURE_HANDLER, auditWrapper); + } + + @Override + public Uni getChallenge(RoutingContext context) { + var enabledAuth = selectEnabledAuth(); + if (enabledAuth != null) { + return enabledAuth.getChallenge(context); + } else { + return Uni.createFrom().nullItem(); + } + } + + @Override + public Set> getCredentialTypes() { + Set> credentialTypes = new HashSet<>(); + credentialTypes.addAll(oidcAuthenticationMechanism.getCredentialTypes()); + credentialTypes.addAll(basicAuthenticationMechanism.getCredentialTypes()); + return credentialTypes; + } + + @Override + public Uni getCredentialTransport(RoutingContext context) { + var enabledAuth = selectEnabledAuth(); + if (enabledAuth != null) { + return enabledAuth.getCredentialTransport(context); + } else { + return Uni.createFrom().nullItem(); + } + } + + private Uni authenticateWithClientCredentials(Pair clientCredentials, + RoutingContext context, IdentityProviderManager identityProviderManager) { + String jwtToken; + String credentialsHash = getCredentialsHash( + clientCredentials.getLeft() + clientCredentials.getRight()); + if (authFailureIsCached(credentialsHash)) { + throw cachedAuthFailures.get(credentialsHash).getValue(); + } else if (accessTokenIsCached(credentialsHash)) { + jwtToken = cachedAccessTokens.get(credentialsHash).getValue(); + } else { + jwtToken = getAccessToken(clientCredentials, credentialsHash); + } + context.request().headers().set("Authorization", "Bearer " + jwtToken); + return oidcAuthenticationMechanism.authenticate(context, identityProviderManager); + } + + private boolean authFailureIsCached(String credentialsHash) { + return cachedAuthFailures.containsKey(credentialsHash) + && !cachedAuthFailures.get(credentialsHash).isExpired(); + } + + private boolean accessTokenIsCached(String credentialsHash) { + return cachedAccessTokens.containsKey(credentialsHash) + && !cachedAccessTokens.get(credentialsHash).isExpired(); + } + + @Retry(retryOn = AuthException.class, maxRetries = 4, delay = 1, delayUnit = ChronoUnit.SECONDS) + public String getAccessToken(Pair clientCredentials, String credentialsHash) { + OidcAuth oidcAuth = new OidcAuth(httpClient, clientCredentials.getLeft(), + clientCredentials.getRight(), Duration.ofSeconds(1), scope.orElse(null)); + try { + String jwtToken = oidcAuth.authenticate();// If we manage to get a token from basic credentials, + // try to authenticate it using the fetched token using + // the identity provider manager + cachedAccessTokens.put(credentialsHash, + new WrappedValue<>(getAccessTokenExpiration(jwtToken), Instant.now(), jwtToken)); + return jwtToken; + } catch (NotAuthorizedException | ForbiddenException ex) { + cachedAuthFailures.put(credentialsHash, + new WrappedValue<>(getAccessTokenExpiration(null), Instant.now(), ex)); + throw ex; + } + } + + /** + * Figure out how long to cache a given JWT. The token can be null (if authentication fails), in which + * case the configured default expiration time will be used. + */ + protected Duration getAccessTokenExpiration(String jwtToken) { + if (jwtToken == null) { + return Duration.ofMinutes(accessTokenExpiration); + } + try { + JsonWebToken parsedToken = jwtParser.parseOnly(jwtToken); + + // Convert the expiration to an Instant, and subtract the offset (we want to stop using it N + // seconds before it expires). + Instant expirationInstant = Instant.ofEpochSecond(parsedToken.getExpirationTime()) + .minusSeconds(accessTokenExpirationOffset); + Instant nowInstant = Instant.now(); + + // Convert the expiration instant to a duration + Duration timeUntilExpiration = Duration.between(nowInstant, expirationInstant); + return timeUntilExpiration; + } catch (ParseException e) { + // Could not parse the JWT, just return the default expiration. + log.error("Error parsing JWT from auth server (client credentials grant).", e); + return Duration.ofMinutes(accessTokenExpiration); + } + } + + private String getCredentialsHash(String credentials) { + return DigestUtils.sha256Hex(credentials); + } +} diff --git a/app/src/main/java/io/apicurio/registry/auth/CredentialsHelper.java b/app/src/main/java/io/apicurio/registry/auth/CredentialsHelper.java new file mode 100644 index 0000000000..e53f469a55 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/auth/CredentialsHelper.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.auth; + +import io.netty.handler.codec.http.HttpHeaderNames; +import io.vertx.ext.web.RoutingContext; +import org.apache.commons.lang3.tuple.Pair; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; +import java.util.Locale; + +/** + * Extracts credentials pair from header encoded as base 64 if exists return null otherwise. + */ +public class CredentialsHelper { + + private static final String BASIC = "basic"; + private static final String BASIC_PREFIX = BASIC + " "; + private static final String LOWERCASE_BASIC_PREFIX = BASIC_PREFIX.toLowerCase(Locale.ENGLISH); + private static final int PREFIX_LENGTH = BASIC_PREFIX.length(); + private static final Charset charset = StandardCharsets.UTF_8; + + public static Pair extractCredentialsFromContext(RoutingContext context) { + List authHeaders = context.request().headers().getAll(HttpHeaderNames.AUTHORIZATION); + if (authHeaders != null) { + for (String current : authHeaders) { + if (current.toLowerCase(Locale.ENGLISH).startsWith(LOWERCASE_BASIC_PREFIX)) { + String base64Challenge = current.substring(PREFIX_LENGTH); + String plainChallenge; + byte[] decode = Base64.getDecoder().decode(base64Challenge); + + plainChallenge = new String(decode, charset); + int colonPos; + String COLON = ":"; + if ((colonPos = plainChallenge.indexOf(COLON)) > -1) { + String userName = plainChallenge.substring(0, colonPos); + String password = plainChallenge.substring(colonPos + 1); + return Pair.of(userName, password); + } + } + } + } + // XX Intended null return + return null; + } +} diff --git a/app/src/main/java/io/apicurio/registry/auth/WrappedValue.java b/app/src/main/java/io/apicurio/registry/auth/WrappedValue.java new file mode 100644 index 0000000000..24e1d5f842 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/auth/WrappedValue.java @@ -0,0 +1,41 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.auth; + +import java.time.Duration; +import java.time.Instant; + +public class WrappedValue { + + private final Duration lifetime; + private final Instant lastUpdate; + private final V value; + + public WrappedValue(Duration lifetime, Instant lastUpdate, V value) { + this.lifetime = lifetime; + this.lastUpdate = lastUpdate; + this.value = value; + } + + public V getValue() { + return value; + } + + public boolean isExpired() { + return lastUpdate.plus(lifetime).isBefore(Instant.now()); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java index e6be4a4f5b..a83f313d1c 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/AbstractResource.java @@ -1,6 +1,5 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.util.Pair; import io.apicurio.registry.ccompat.dto.SchemaReference; import io.apicurio.registry.ccompat.rest.error.ConflictException; import io.apicurio.registry.ccompat.rest.error.UnprocessableEntityException; @@ -38,6 +37,7 @@ import org.apache.avro.AvroTypeException; import org.apache.avro.SchemaParseException; import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import java.util.Collections; @@ -83,7 +83,7 @@ private Pair toGAFromGroupConcatSubject(String subject) { } String groupId = subject.substring(0, sepIdx); String artifactId = subject.substring(sepIdx + cconfig.groupConcatSeparator.length()); - return new Pair<>(groupId, artifactId); + return Pair.of(groupId, artifactId); } protected GA getGA(String groupId, String artifactId) { diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java index d9b1b16f7a..1fcff0f514 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/CompatibilityResourceImpl.java @@ -1,6 +1,5 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -10,6 +9,7 @@ import io.apicurio.registry.ccompat.rest.v7.CompatibilityResource; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ConfigResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ConfigResourceImpl.java index dc15670688..cf667bc68b 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ConfigResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ConfigResourceImpl.java @@ -1,14 +1,14 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; -import io.apicurio.common.apps.logging.audit.AuditingConstants; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.ccompat.dto.CompatibilityLevelDto; import io.apicurio.registry.ccompat.dto.CompatibilityLevelParamDto; import io.apicurio.registry.ccompat.rest.v7.ConfigResource; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; +import io.apicurio.registry.logging.audit.AuditingConstants; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ContextResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ContextResourceImpl.java index 8afcf53a89..806f5e74c8 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ContextResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ContextResourceImpl.java @@ -1,7 +1,7 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.ccompat.rest.v7.ContextResource; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import jakarta.interceptor.Interceptors; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ExporterResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ExporterResourceImpl.java index faed330f4a..c46c11b41a 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ExporterResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ExporterResourceImpl.java @@ -1,10 +1,10 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.ccompat.dto.ExporterDto; import io.apicurio.registry.ccompat.dto.ExporterStatus; import io.apicurio.registry.ccompat.rest.error.Errors; import io.apicurio.registry.ccompat.rest.v7.ExporterResource; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import jakarta.interceptor.Interceptors; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ModeResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ModeResourceImpl.java index 785386da3b..a812a56a40 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ModeResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/ModeResourceImpl.java @@ -1,9 +1,9 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.ccompat.dto.ModeDto; import io.apicurio.registry.ccompat.rest.error.Errors; import io.apicurio.registry.ccompat.rest.v7.ModeResource; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import jakarta.interceptor.Interceptors; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java index b3c39b5216..1bdf1433bd 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SchemasResourceImpl.java @@ -1,6 +1,5 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -9,6 +8,7 @@ import io.apicurio.registry.ccompat.rest.v7.SchemasResource; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.storage.dto.ArtifactReferenceDto; diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java index 1a73f59170..8cf4e2173e 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectVersionsResourceImpl.java @@ -1,7 +1,5 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -16,6 +14,8 @@ import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; @@ -36,8 +36,8 @@ import java.util.Map; import java.util.stream.Collectors; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_VERSION; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_VERSION; @Interceptors({ ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class }) @Logged diff --git a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java index b42d633a23..49ddde2aee 100644 --- a/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/ccompat/rest/v7/impl/SubjectsResourceImpl.java @@ -1,7 +1,5 @@ package io.apicurio.registry.ccompat.rest.v7.impl; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -11,6 +9,8 @@ import io.apicurio.registry.ccompat.rest.error.SubjectNotSoftDeletedException; import io.apicurio.registry.ccompat.rest.error.SubjectSoftDeletedException; import io.apicurio.registry.ccompat.rest.v7.SubjectsResource; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GA; @@ -34,7 +34,7 @@ import java.util.function.Function; import java.util.stream.Collectors; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; @Interceptors({ ResponseErrorLivenessCheck.class, ResponseTimeoutReadinessCheck.class }) @Logged diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigProducer.java b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigProducer.java new file mode 100644 index 0000000000..5d2c763f9b --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigProducer.java @@ -0,0 +1,46 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl; + +import io.apicurio.common.apps.config.Dynamic; +import io.smallrye.config.inject.ConfigProducer; +import io.smallrye.config.inject.ConfigProducerUtil; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.InjectionPoint; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.util.function.Supplier; + +/** + * CDI producer used when injecting dynamic configuration properties into application injection points. + * + * @author eric.wittmann@gmail.com + */ +@ApplicationScoped +public class DynamicConfigProducer extends ConfigProducer { + + @Override + @Dependent + @Produces + @ConfigProperty + @Dynamic + protected Supplier produceSupplierConfigProperty(InjectionPoint ip) { + return () -> ConfigProducerUtil.getValue(ip, getConfig()); + } +} diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigPropertyIndexImpl.java b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigPropertyIndexImpl.java new file mode 100644 index 0000000000..2ee0ba4b9a --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigPropertyIndexImpl.java @@ -0,0 +1,136 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl; + +import io.apicurio.common.apps.config.DynamicConfigPropertyDef; +import io.apicurio.common.apps.config.DynamicConfigPropertyIndex; +import io.apicurio.common.apps.config.DynamicConfigPropertyList; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.Config; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author eric.wittmann@gmail.com + */ +@ApplicationScoped +public class DynamicConfigPropertyIndexImpl implements DynamicConfigPropertyIndex { + + private Map propertyIndex; + + @Inject + DynamicConfigPropertyList properties; + @Inject + Config config; + + /** + * Constructor. + */ + public DynamicConfigPropertyIndexImpl() { + } + + @PostConstruct + void onInit() { + indexProperties(properties.getDynamicConfigProperties()); + } + + private Map getPropertyIndex() { + return this.propertyIndex; + } + + private void indexProperties(List dynamicConfigProperties) { + this.propertyIndex = new HashMap<>(dynamicConfigProperties.size()); + for (DynamicConfigPropertyDef def : dynamicConfigProperties) { + this.propertyIndex.put(def.getName(), def); + } + } + + private boolean accept(DynamicConfigPropertyDef def) { + List requires = def.getRequires() == null ? new ArrayList<>(1) + : new ArrayList<>(Arrays.asList(def.getRequires())); + requires.add(def.getName() + ".dynamic.allow=true"); + for (String require : requires) { + String requiredPropertyName = require; + String requiredPropertyValue = null; + if (require.contains("=")) { + requiredPropertyName = require.substring(0, require.indexOf("=")).trim(); + requiredPropertyValue = require.substring(require.indexOf("=") + 1).trim(); + } + Optional actualPropertyValue = config.getOptionalValue(requiredPropertyName, + String.class); + if (requiredPropertyValue != null && (actualPropertyValue.isEmpty() + || !requiredPropertyValue.equals(actualPropertyValue.get()))) { + return false; + } + if (requiredPropertyValue == null && actualPropertyValue.isEmpty()) { + return false; + } + } + return true; + } + + /** + * @see DynamicConfigPropertyIndex#getProperty(String) + */ + @Override + public DynamicConfigPropertyDef getProperty(String name) { + return getPropertyIndex().get(name); + } + + /** + * @see DynamicConfigPropertyIndex#hasProperty(String) + */ + @Override + public boolean hasProperty(String name) { + return getPropertyIndex().containsKey(name); + } + + /** + * @see DynamicConfigPropertyIndex#getPropertyNames() + */ + @Override + public Set getPropertyNames() { + return getPropertyIndex().keySet(); + } + + /** + * @see DynamicConfigPropertyIndex#isAccepted(String) + */ + @Override + public boolean isAccepted(String propertyName) { + return this.getAcceptedPropertyNames().contains(propertyName); + } + + /** + * @see DynamicConfigPropertyIndex#getAcceptedPropertyNames() + */ + @Override + public Set getAcceptedPropertyNames() { + return this.propertyIndex.entrySet().stream().filter(entry -> accept(entry.getValue())) + .map(entry -> entry.getKey()).collect(Collectors.toSet()); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigSource.java b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigSource.java new file mode 100644 index 0000000000..91b14c24ec --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigSource.java @@ -0,0 +1,117 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl; + +import io.apicurio.common.apps.config.DynamicConfigPropertyDto; +import io.apicurio.common.apps.config.DynamicConfigStorage; +import org.eclipse.microprofile.config.spi.ConfigSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.Optional; +import java.util.Set; + +/** + * A microprofile-config configsource. This class uses the dynamic config storage to read/write configuration + * properties to, for example, a database. + *

+ * TODO cache properties. this would need to be multi-tenant aware? probably should be implemented in the + * storage layer! + * + * @author eric.wittmann@gmail.com + */ +public class DynamicConfigSource implements ConfigSource { + + private static final String LOG_PREFIX = "Could not get dynamic configuration value for {} in thread {}. "; + + private static final Logger log = LoggerFactory.getLogger(DynamicConfigSource.class); + + private static Optional storage = Optional.empty(); + + public static void setStorage(DynamicConfigStorage configStorage) { + storage = Optional.of(configStorage); + } + + private static Optional configIndex = Optional.empty(); + + public static void setConfigurationIndex(DynamicConfigPropertyIndexImpl index) { + configIndex = Optional.of(index); + } + + @Override + public int getOrdinal() { + return 450; // Very high ordinal value: + // https://quarkus.io/guides/config-reference#configuration-sources + } + + /** + * @see ConfigSource#getPropertyNames() + */ + @Override + public Set getPropertyNames() { + return Collections.emptySet(); + } + + /** + * @see ConfigSource#getValue(String) + */ + @Override + public String getValue(String propertyName) { + String pname = normalizePropertyName(propertyName); + if (configIndex.isPresent() && configIndex.get().hasProperty(pname)) { + if (storage.isPresent()) { + if (storage.get().isReady()) { // TODO Merge the ifs after removing logging + DynamicConfigPropertyDto dto = storage.get().getConfigProperty(pname); + if (dto != null) { + log.debug("Got dynamic configuration value {} for {} in thread {}", dto.getValue(), + pname, Thread.currentThread().getName()); + return dto.getValue(); + } else { + log.debug(LOG_PREFIX + "Storage returned null.", pname, + Thread.currentThread().getName()); + } + } else { + log.debug(LOG_PREFIX + "Storage is not ready.", pname, Thread.currentThread().getName()); + } + } else { + log.debug(LOG_PREFIX + "Storage is not present.", pname, Thread.currentThread().getName()); + } + } + return null; + } + + private String normalizePropertyName(String propertyName) { + if (propertyName == null || !propertyName.startsWith("%")) { + return propertyName; + } + int idx = propertyName.indexOf("."); + if (idx >= propertyName.length()) { + return propertyName; + } + return propertyName.substring(idx + 1); + } + + /** + * @see ConfigSource#getName() + */ + @Override + public String getName() { + return getClass().getSimpleName(); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigStartup.java b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigStartup.java new file mode 100644 index 0000000000..fc123fe971 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/DynamicConfigStartup.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl; + +import io.apicurio.common.apps.config.DynamicConfigStorageAccessor; +import io.quarkus.runtime.Startup; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * @author eric.wittmann@gmail.com + */ +@ApplicationScoped +@Startup +public class DynamicConfigStartup { + + private static final Logger log = LoggerFactory.getLogger(DynamicConfigStartup.class); + + @Inject + DynamicConfigStorageAccessor configStorageAccessor; + + @Inject + DynamicConfigPropertyIndexImpl configIndex; + + @PostConstruct + void onStart() { + log.debug("Initializing dynamic configuration source in thread {}", Thread.currentThread().getName()); + configStorageAccessor.getConfigStorage().isReady(); + DynamicConfigSource.setStorage(configStorageAccessor.getConfigStorage()); + DynamicConfigSource.setConfigurationIndex(configIndex); + log.debug("Dynamic configuration source initialized in thread {}", Thread.currentThread().getName()); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigPropertyDtoMapper.java b/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigPropertyDtoMapper.java new file mode 100644 index 0000000000..318937b595 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigPropertyDtoMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl.storage; + +import io.apicurio.common.apps.config.DynamicConfigPropertyDto; +import io.apicurio.registry.storage.impl.sql.jdb.RowMapper; +import jakarta.enterprise.context.ApplicationScoped; + +import java.sql.ResultSet; +import java.sql.SQLException; + +/** + * @author eric.wittmann@gmail.com + * @author Jakub Senko m@jsenko.net + */ +@ApplicationScoped +public class DynamicConfigPropertyDtoMapper implements RowMapper { + + /** + * @see RowMapper#map(ResultSet) + */ + @Override + public DynamicConfigPropertyDto map(ResultSet rs) throws SQLException { + String name = rs.getString("pname"); + String value = rs.getString("pvalue"); + return new DynamicConfigPropertyDto(name, value); + } +} diff --git a/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigSqlStorageComponent.java b/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigSqlStorageComponent.java new file mode 100644 index 0000000000..beccfab8d4 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/config/config/impl/storage/DynamicConfigSqlStorageComponent.java @@ -0,0 +1,140 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.config.config.impl.storage; + +import io.apicurio.common.apps.config.DynamicConfigPropertyDto; +import io.apicurio.common.apps.config.DynamicConfigSqlStorageStatements; +import io.apicurio.common.apps.config.DynamicConfigStorage; +import io.apicurio.registry.logging.LoggerProducer; +import io.apicurio.registry.storage.error.ConfigPropertyNotFoundException; +import io.apicurio.registry.storage.impl.sql.HandleFactory; +import jakarta.enterprise.context.ApplicationScoped; +import org.slf4j.Logger; + +import java.time.Instant; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import static java.util.Objects.requireNonNull; + +/** + * @author eric.wittmann@gmail.com + * @author Jakub Senko m@jsenko.net + */ +@ApplicationScoped +public class DynamicConfigSqlStorageComponent implements DynamicConfigStorage { + + private Logger log; + + private HandleFactory handles; + + private DynamicConfigSqlStorageStatements sqlStatements; + + private volatile boolean isStarting; + private volatile boolean ready; + + public synchronized void start(LoggerProducer loggerProducer, HandleFactory handles, + DynamicConfigSqlStorageStatements sqlStatements) { + if (isStarting) { + throw new RuntimeException("The DynamicConfigSqlStorageComponent can be started only once"); + } + isStarting = true; + requireNonNull(loggerProducer); + this.log = loggerProducer.getLogger(getClass()); + requireNonNull(handles); + this.handles = handles; + requireNonNull(sqlStatements); + this.sqlStatements = sqlStatements; + this.ready = true; + } + + @Override + public boolean isReady() { + return ready; + } + + @Override + public List getConfigProperties() { + log.debug("Getting all config properties."); + return handles.withHandleNoException(handle -> { + String sql = sqlStatements.selectConfigProperties(); + return handle.createQuery(sql).map(new DynamicConfigPropertyDtoMapper()).list().stream() + // Filter out possible null values. + .filter(Objects::nonNull).collect(Collectors.toList()); + }); + } + + @Override + public DynamicConfigPropertyDto getConfigProperty(String propertyName) { + log.debug("Selecting a single config property: {}", propertyName); + return handles.withHandleNoException(handle -> { + + String sql = sqlStatements.selectConfigPropertyByName(); + Optional res = handle.createQuery(sql).bind(0, propertyName) + .map(new DynamicConfigPropertyDtoMapper()).findOne(); + + return res.orElse(null); + }); + } + + @Override + public void setConfigProperty(DynamicConfigPropertyDto property) { + log.debug("Setting a config property with name: {} and value: {}", property.getName(), + property.getValue()); + handles.withHandleNoException(handle -> { + String propertyName = property.getName(); + String propertyValue = property.getValue(); + // TODO: Why delete and recreate? Can be replaced by upsert? + + // First delete the property row from the table + String sql = sqlStatements.deleteConfigProperty(); + handle.createUpdate(sql).bind(0, propertyName).execute(); + + // Then create the row again with the new value + sql = sqlStatements.insertConfigProperty(); + handle.createUpdate(sql).bind(0, propertyName).bind(1, propertyValue) + .bind(2, System.currentTimeMillis()).execute(); + + return null; + }); + } + + @Override + public void deleteConfigProperty(String propertyName) { + log.debug("Deleting a config property from storage: {}", propertyName); + handles.withHandleNoException(handle -> { + + String sql = sqlStatements.deleteConfigProperty(); + int rows = handle.createUpdate(sql).bind(0, propertyName).execute(); + + if (rows == 0) { + throw new ConfigPropertyNotFoundException(propertyName); + } + return null; + }); + } + + protected List getTenantsWithStaleConfigProperties(Instant since) { + log.debug("Getting all tenant IDs with stale config properties."); + return handles.withHandleNoException(handle -> { + String sql = sqlStatements.selectTenantIdsByConfigModifiedOn(); + return handle.createQuery(sql).bind(0, since.toEpochMilli()).mapTo(String.class).list(); + }); + } +} diff --git a/app/src/main/java/io/apicurio/registry/core/AppException.java b/app/src/main/java/io/apicurio/registry/core/AppException.java new file mode 100644 index 0000000000..54af3012c9 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/core/AppException.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.core; + +/** + * Generic application exception. + */ +public class AppException extends RuntimeException { + private static final long serialVersionUID = 7551763806044016474L; + + public AppException() { + } + + public AppException(String message) { + super(message); + } + + public AppException(String message, Throwable cause) { + super(message, cause); + } + + public AppException(Throwable cause) { + super(cause); + } + + public AppException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/app/src/main/java/io/apicurio/registry/core/System.java b/app/src/main/java/io/apicurio/registry/core/System.java new file mode 100644 index 0000000000..3b7112eea9 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/core/System.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.core; + +import io.apicurio.common.apps.config.Info; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * @author eric.wittmann@gmail.com + */ +@ApplicationScoped +public class System { + + @Inject + @ConfigProperty(name = "apicurio.app.name") + @Info(registryAvailableSince = "3.0.4") + String name; + + @Inject + @ConfigProperty(name = "apicurio.app.description") + @Info(registryAvailableSince = "3.0.4") + String description; + + @Inject + @ConfigProperty(name = "apicurio.app.version") + @Info(registryAvailableSince = "3.0.4") + String version; + + @Inject + @ConfigProperty(name = "apicurio.app.date") + @Info(registryAvailableSince = "3.0.4") + String date; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getVersion() { + return this.version; + } + + /** + * @return the versionDate + */ + public Date getDate() { + try { + if (date == null) { + return new Date(); + } else { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(date); + } + } catch (ParseException e) { + return new Date(); + } + } + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/DefaultLoggerClass.java b/app/src/main/java/io/apicurio/registry/logging/DefaultLoggerClass.java new file mode 100644 index 0000000000..af464b65c0 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/DefaultLoggerClass.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging; + +/** + * @author eric.wittmann@gmail.com + */ +public class DefaultLoggerClass { + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/Logged.java b/app/src/main/java/io/apicurio/registry/logging/Logged.java new file mode 100644 index 0000000000..28b4fb01a9 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/Logged.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging; + +import jakarta.interceptor.InterceptorBinding; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author eric.wittmann@gmail.com + */ +@InterceptorBinding +@Retention(RetentionPolicy.RUNTIME) +@Target({ ElementType.TYPE, ElementType.METHOD }) +public @interface Logged { +} diff --git a/app/src/main/java/io/apicurio/registry/logging/LoggerProducer.java b/app/src/main/java/io/apicurio/registry/logging/LoggerProducer.java new file mode 100644 index 0000000000..cd0729bb0a --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/LoggerProducer.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.InjectionPoint; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * @author eric.wittmann@gmail.com + */ +@ApplicationScoped +public class LoggerProducer { + + private final Map, Logger> loggers = new ConcurrentHashMap<>(); + + /** + * @param targetClass the target class of the logger + * @return a logger + */ + public Logger getLogger(Class targetClass) { + Logger logger = loggers.computeIfAbsent(targetClass, k -> { + return LoggerFactory.getLogger(targetClass); + }); + return logger; + } + + /** + * Produces a logger for injection. + * + * @param injectionPoint the logger injection point (typically a field) + * @return a logger + */ + @Produces + public Logger produceLogger(InjectionPoint injectionPoint) { + Class targetClass = injectionPoint.getBean().getBeanClass(); + return getLogger(targetClass); + } + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/LoggingInterceptor.java b/app/src/main/java/io/apicurio/registry/logging/LoggingInterceptor.java new file mode 100644 index 0000000000..db27a40758 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/LoggingInterceptor.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging; + +import jakarta.annotation.Priority; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import org.slf4j.Logger; + +/** + * @author eric.wittmann@gmail.com + */ +@Interceptor +@Priority(Interceptor.Priority.APPLICATION) +@Logged +public class LoggingInterceptor { + + @Inject + LoggerProducer loggerProducer; + + @AroundInvoke + public Object logMethodEntry(InvocationContext context) throws Exception { + Logger logger = null; + try { + Class targetClass = DefaultLoggerClass.class; + Object target = context.getTarget(); + if (target != null) { + targetClass = target.getClass(); + } + + logger = loggerProducer.getLogger(targetClass); + } catch (Throwable t) { + } + + logEnter(context, logger); + Object rval = context.proceed(); + logLeave(context, logger); + return rval; + } + + private void logEnter(InvocationContext context, Logger logger) { + if (context != null && context.getMethod() != null && context.getMethod().getName() != null + && context.getParameters() != null && logger != null) { + logger.trace("ENTERING method [{}] with {} parameters", context.getMethod().getName(), + context.getParameters().length); + } + } + + private void logLeave(InvocationContext context, Logger logger) { + if (context != null && context.getMethod() != null && context.getMethod().getName() != null + && context.getParameters() != null && logger != null) { + logger.trace("LEAVING method [{}]", context.getMethod().getName()); + } + } + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestContext.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestContext.java new file mode 100644 index 0000000000..b0b0648321 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestContext.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +import jakarta.enterprise.context.RequestScoped; + +@RequestScoped +public class AuditHttpRequestContext implements AuditHttpRequestInfo { + + public static final String X_FORWARDED_FOR_HEADER = "x-forwarded-for"; + public static final String FAILURE = "failure"; + public static final String SUCCESS = "success"; + + private String sourceIp; + private String forwardedFor; + private boolean auditEntryGenerated = false; + + @Override + public String getSourceIp() { + return sourceIp; + } + + public void setSourceIp(String sourceIp) { + this.sourceIp = sourceIp; + } + + @Override + public String getForwardedFor() { + return forwardedFor; + } + + public void setForwardedFor(String forwardedFor) { + this.forwardedFor = forwardedFor; + } + + public boolean isAuditEntryGenerated() { + return auditEntryGenerated; + } + + public void setAuditEntryGenerated(boolean auditEntryGenerated) { + this.auditEntryGenerated = auditEntryGenerated; + } + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestInfo.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestInfo.java new file mode 100644 index 0000000000..9dfc9871c7 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditHttpRequestInfo.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +public interface AuditHttpRequestInfo { + + /** + * @return the sourceIp + */ + String getSourceIp(); + + /** + * @return the forwardedFor + */ + String getForwardedFor(); + +} \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditLogService.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditLogService.java new file mode 100644 index 0000000000..e318539efe --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditLogService.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.control.ActivateRequestContext; +import jakarta.inject.Inject; +import org.slf4j.Logger; + +import java.util.Map; + +@ApplicationScoped +public class AuditLogService { + + @Inject + Logger log; + + @Inject + AuditHttpRequestContext context; + + @ActivateRequestContext + public void log(String invoker, String action, String result, Map metadata, + AuditHttpRequestInfo requestInfo) { + + String remoteAddress; + String forwardedRemoteAddress; + if (requestInfo != null) { + remoteAddress = requestInfo.getSourceIp(); + forwardedRemoteAddress = requestInfo.getForwardedFor(); + } else { + remoteAddress = context.getSourceIp(); + forwardedRemoteAddress = context.getForwardedFor(); + } + + StringBuilder m = new StringBuilder(); + m.append(invoker).append(" ").append("action=\"").append(action).append("\" ").append("result=\"") + .append(result).append("\" ").append("src_ip=\"").append(remoteAddress).append("\" "); + if (forwardedRemoteAddress != null) { + m.append("x_forwarded_for=\"").append(forwardedRemoteAddress).append("\" "); + } + for (Map.Entry e : metadata.entrySet()) { + m.append(e.getKey()).append("=\"").append(e.getValue()).append("\" "); + } + log.info(m.toString()); + // mark in the context that we already generated an audit entry for this request + context.setAuditEntryGenerated(true); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditMetaDataExtractor.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditMetaDataExtractor.java new file mode 100644 index 0000000000..223d777f3c --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditMetaDataExtractor.java @@ -0,0 +1,32 @@ +package io.apicurio.registry.logging.audit; + +import java.util.Map; + +/** + * Classes that implement this method get the opportunity to extract metadata property values from objects + * during auditing. This allows extraction of relevant auditable data from complex objects in methods + * annotated with @Audited. Without providing one of these for a complex object, the auditing + * system will simply call toString() on any method parameter that is being included in the audit log. Note + * that metadata extractors are only used when no parameters are defined on the @Audited + * annotation. + * + * @author eric.wittmann@gmail.com + */ +public interface AuditMetaDataExtractor { + + /** + * Returns true if this extractor should be used for the given parameter value. + * + * @param parameterValue + */ + public boolean accept(Object parameterValue); + + /** + * Extracts metadata from the given parameter value into the given map of metadata. + * + * @param parameterValue + * @param metaData + */ + public void extractMetaDataInto(Object parameterValue, Map metaData); + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/Audited.java b/app/src/main/java/io/apicurio/registry/logging/audit/Audited.java new file mode 100644 index 0000000000..f4de258562 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/Audited.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.interceptor.InterceptorBinding; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * This annotation is processed by {@link AuditedInterceptor} + */ +@InterceptorBinding +@Retention(RUNTIME) +@Target({ METHOD, TYPE }) +public @interface Audited { + + /** + * If empty or null the method name will be used as the action identifier + * + * @return the action identifier + */ + String action() default ""; + + /** + * If a method parameter value should be recorded to the auditing log, but there is no extractor defined + * (e.g. the value is a type without specific meaning, such as a String), this field can be used by adding + * two successive values: 1. Position of the given parameter, starting at 0, as String. Parameter name is + * not used, in case it is not available via reflection. 2. Key under which the value of the parameter + * should be recorded. There can be more than one such pair. Note that the position can also optionally + * include an additional property name. For example you could indicate "3.title" as the position. This + * will get the third parameter from the context and then look for a JavaBean property named "title". If + * one exists, it will be logged. This allows extraction of properties of complex object parameters to be + * added to the audit log. So, for example, if the 2nd parameter of a method is type "MyWidget", and the + * "MyWidget" class has a "description" property, then you could specific "1.description" as the position. + * + * @return the array of parameters to extract + */ + @Nonbinding + String[] extractParameters() default {}; + +} diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditedInterceptor.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditedInterceptor.java new file mode 100644 index 0000000000..2ce1b2e134 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditedInterceptor.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.Priority; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; +import jakarta.interceptor.AroundInvoke; +import jakarta.interceptor.Interceptor; +import jakarta.interceptor.InvocationContext; +import org.apache.commons.beanutils.BeanUtils; + +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.Map; + +/** + * Interceptor that executes around methods annotated with {@link Audited} + *

+ * This interceptor follows the execution of a method and marks the audit entry as failed if the inner method + * throws an exception. + *

+ * This interceptor reads the inner method parameters to gather extra information for the audit entry. + */ +@Audited +@Interceptor +@Priority(Interceptor.Priority.APPLICATION - 100) +// Runs before other application interceptors, e.g. *PermissionInterceptor +public class AuditedInterceptor { + + @Inject + AuditLogService auditLogService; + + @Inject + Instance securityIdentity; + + @Inject + Instance extractors; + + @AroundInvoke + public Object auditMethod(InvocationContext context) throws Exception { + + Audited annotation = context.getMethod().getAnnotation(Audited.class); + Map metadata = new HashMap<>(); + + if (securityIdentity.isResolvable() && !securityIdentity.get().isAnonymous()) { + metadata.put(AuditingConstants.KEY_PRINCIPAL_ID, securityIdentity.get().getPrincipal().getName()); + } + + extractMetaData(context, annotation, metadata); + + String action = annotation.action(); + if (action.isEmpty()) { + action = context.getMethod().getName(); + } + + String result = AuditHttpRequestContext.SUCCESS; + try { + return context.proceed(); + } catch (Exception e) { + result = AuditHttpRequestContext.FAILURE; + metadata.put("error_msg", e.getMessage()); + throw e; + } finally { + auditLogService.log("apicurio.audit", action, result, metadata, null); + } + } + + /** + * Extracts metadata from the context based on the "extractParameters" configuration of the "Audited" + * annotation. + * + * @param context + * @param annotation + * @param metadata + */ + protected void extractMetaData(InvocationContext context, Audited annotation, + Map metadata) { + final String[] annotationParams = annotation.extractParameters(); + if (annotationParams.length > 0) { + for (int i = 0; i <= annotationParams.length - 2; i += 2) { + String position = annotationParams[i]; + String metadataName = annotationParams[i + 1]; + + String propertyName = null; + if (position.contains(".")) { + position = extractPosition(position); + propertyName = extractPropertyName(position); + } + int positionInt = Integer.parseInt(position); + + Object parameterValue = context.getParameters()[positionInt]; + if (parameterValue != null && propertyName != null) { + parameterValue = getPropertyValueFromParam(parameterValue, propertyName); + } + if (parameterValue != null) { + metadata.put(metadataName, parameterValue.toString()); + } + } + } else if (extractors.iterator().hasNext()) { + // No parameters defined on the annotation. So try to use any configured + // extractors instead. Extract metadata from the collection of params on + // the context. + for (Object parameter : context.getParameters()) { + for (AuditMetaDataExtractor extractor : extractors) { + if (extractor.accept(parameter)) { + extractor.extractMetaDataInto(parameter, metadata); + } + } + } + } + } + + private String extractPosition(String position) { + return position.split(".")[0]; + } + + private String extractPropertyName(String position) { + return position.split(".")[1]; + } + + private String getPropertyValueFromParam(Object parameterValue, String propertyName) { + try { + return BeanUtils.getProperty(parameterValue, propertyName); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + return parameterValue.toString(); + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/AuditingConstants.java b/app/src/main/java/io/apicurio/registry/logging/audit/AuditingConstants.java index ae70ad6035..bc92e154f4 100644 --- a/app/src/main/java/io/apicurio/registry/logging/audit/AuditingConstants.java +++ b/app/src/main/java/io/apicurio/registry/logging/audit/AuditingConstants.java @@ -1,8 +1,47 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package io.apicurio.registry.logging.audit; public interface AuditingConstants { + String KEY_GROUP_ID = "group_id"; + String KEY_ARTIFACT_ID = "artifact_id"; + String KEY_UPDATE_STATE = "update_state"; + String KEY_VERSION = "version"; + String KEY_ARTIFACT_TYPE = "artifact_type"; + String KEY_IF_EXISTS = "if_exists"; + String KEY_CANONICAL = "canonical"; + String KEY_RULE = "rule"; + String KEY_RULE_TYPE = "rule_type"; + String KEY_EDITABLE_METADATA = "editable_metadata"; + String KEY_LOGGER = "logger"; + String KEY_LOG_CONFIGURATION = "log_configuration"; + String KEY_FOR_BROWSER = "for_browser"; + String KEY_ROLE_MAPPING = "role_mapping"; + String KEY_PRINCIPAL_ID = "principal_id"; + String KEY_UPDATE_ROLE = "update_role"; + String KEY_NAME = "name"; + String KEY_NAME_ENCODED = "name_encoded"; + String KEY_DESCRIPTION = "description"; + String KEY_DESCRIPTION_ENCODED = "description_encoded"; String KEY_PROPERTY_CONFIGURATION = "property_configuration"; + String KEY_FROM_URL = "from_url"; + String KEY_SHA = "artifact_sha"; + String KEY_OWNER = "owner"; } diff --git a/app/src/main/java/io/apicurio/registry/logging/audit/HttpRequestsAuditFilter.java b/app/src/main/java/io/apicurio/registry/logging/audit/HttpRequestsAuditFilter.java new file mode 100644 index 0000000000..2621d4c0d1 --- /dev/null +++ b/app/src/main/java/io/apicurio/registry/logging/audit/HttpRequestsAuditFilter.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.registry.logging.audit; + +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.ContainerResponseContext; +import jakarta.ws.rs.container.ContainerResponseFilter; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.SecurityContext; +import jakarta.ws.rs.ext.Provider; + +import java.io.IOException; +import java.security.Principal; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Filters REST API requests and responses to generate audit logs for failed requests + */ +@Provider +@Priority(Priorities.AUTHENTICATION) +@ApplicationScoped +public class HttpRequestsAuditFilter implements ContainerRequestFilter, ContainerResponseFilter { + + @Context + HttpServletRequest request; + + @Inject + AuditHttpRequestContext auditContext; + + @Inject + AuditLogService auditLog; + + @Override + public void filter(ContainerRequestContext requestContext) throws IOException { + auditContext.setSourceIp(request.getRemoteAddr()); + auditContext.setForwardedFor( + requestContext.getHeaderString(AuditHttpRequestContext.X_FORWARDED_FOR_HEADER)); + } + + @Override + public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) { + // check if there is already an audit entry for the logic executed in this request + if (auditContext.isAuditEntryGenerated()) { + return; + } + + if (responseContext.getStatus() >= 400) { + // failed request, generate audit log + Map metadata = new HashMap<>(); + metadata.put("method", requestContext.getMethod()); + metadata.put("path", requestContext.getUriInfo().getPath()); + metadata.put("response_code", String.valueOf(responseContext.getStatus())); + metadata.put("user", Optional.ofNullable(requestContext.getSecurityContext()) + .map(SecurityContext::getUserPrincipal).map(Principal::getName).orElseGet(() -> "")); + + auditLog.log("apicurio.audit", "request", AuditHttpRequestContext.FAILURE, metadata, null); + } + } +} diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java index 7a05c21cae..c8272d87f0 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/AdminResourceImpl.java @@ -3,12 +3,12 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDef; import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.DynamicConfigPropertyIndex; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.auth.RoleBasedAccessApiOperation; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.MissingRequiredParameterException; @@ -46,13 +46,13 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_FOR_BROWSER; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_NAME; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_PRINCIPAL_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ROLE_MAPPING; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE_TYPE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_UPDATE_ROLE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_FOR_BROWSER; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_NAME; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_PRINCIPAL_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_ROLE_MAPPING; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE_TYPE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_UPDATE_ROLE; import static io.apicurio.registry.utils.DtoUtil.appAuthPropertyToRegistry; import static io.apicurio.registry.utils.DtoUtil.registryAuthPropertyToApp; diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/DownloadsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/DownloadsResourceImpl.java index b3ca55ea91..906800617e 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/DownloadsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/DownloadsResourceImpl.java @@ -1,9 +1,9 @@ package io.apicurio.registry.rest.v2; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.storage.error.DownloadNotFoundException; diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java index eafab333ab..70c1879440 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/GroupsResourceImpl.java @@ -1,8 +1,6 @@ package io.apicurio.registry.rest.v2; import com.google.common.hash.Hashing; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -11,6 +9,8 @@ import io.apicurio.registry.content.extract.ContentExtractor; import io.apicurio.registry.content.extract.ExtractedMetaData; import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.BranchId; @@ -111,23 +111,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_TYPE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_CANONICAL; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_DESCRIPTION; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_DESCRIPTION_ENCODED; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_EDITABLE_METADATA; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_FROM_URL; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_GROUP_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_IF_EXISTS; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_NAME; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_NAME_ENCODED; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE_TYPE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_SHA; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_UPDATE_STATE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_VERSION; -import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_OWNER; +import static io.apicurio.registry.logging.audit.AuditingConstants.*; import static io.apicurio.registry.rest.v2.V2ApiUtil.defaultGroupIdToNull; /** diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java index 584f5d1ffb..5c1f93cb70 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/IdsResourceImpl.java @@ -1,11 +1,11 @@ package io.apicurio.registry.rest.v2; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.HeadersHack; diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java index 2cb48a9406..457a38f047 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/SearchResourceImpl.java @@ -1,6 +1,5 @@ package io.apicurio.registry.rest.v2; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; @@ -8,6 +7,7 @@ import io.apicurio.registry.content.TypedContent; import io.apicurio.registry.content.canon.ContentCanonicalizer; import io.apicurio.registry.content.util.ContentTypeUtil; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.v2.beans.ArtifactSearchResults; diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/SystemResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/SystemResourceImpl.java index ee4c790659..20efa7153c 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/SystemResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/SystemResourceImpl.java @@ -1,11 +1,11 @@ package io.apicurio.registry.rest.v2; -import io.apicurio.common.apps.core.System; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; +import io.apicurio.registry.core.System; import io.apicurio.registry.limits.RegistryLimitsConfiguration; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.v2.beans.Limits; diff --git a/app/src/main/java/io/apicurio/registry/rest/v2/UsersResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v2/UsersResourceImpl.java index c3ce2d52c0..b1d8ec4685 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v2/UsersResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v2/UsersResourceImpl.java @@ -1,12 +1,12 @@ package io.apicurio.registry.rest.v2; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.AdminOverride; import io.apicurio.registry.auth.AuthConfig; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.auth.RoleBasedAccessController; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.v2.beans.UserInfo; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java index 3a0d7d0156..5e326530c9 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/AdminResourceImpl.java @@ -5,12 +5,12 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.DynamicConfigPropertyIndex; import io.apicurio.common.apps.config.Info; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.auth.RoleBasedAccessApiOperation; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.ConflictException; @@ -77,14 +77,14 @@ import java.util.stream.Stream; import java.util.zip.ZipInputStream; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_FOR_BROWSER; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_NAME; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_PRINCIPAL_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_PROPERTY_CONFIGURATION; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ROLE_MAPPING; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE_TYPE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_UPDATE_ROLE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_FOR_BROWSER; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_NAME; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_PRINCIPAL_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_PROPERTY_CONFIGURATION; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_ROLE_MAPPING; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE_TYPE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_UPDATE_ROLE; import static io.apicurio.registry.utils.DtoUtil.appAuthPropertyToRegistry; import static io.apicurio.registry.utils.DtoUtil.registryAuthPropertyToApp; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/DownloadsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/DownloadsResourceImpl.java index 099d955ede..43e724c841 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/DownloadsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/DownloadsResourceImpl.java @@ -1,9 +1,9 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.v3.shared.DataExporter; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java index c49a86e1ca..40e9f3cbb4 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/GroupsResourceImpl.java @@ -1,12 +1,12 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.logging.Logged; -import io.apicurio.common.apps.logging.audit.Audited; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; +import io.apicurio.registry.logging.audit.Audited; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.BranchId; @@ -110,14 +110,14 @@ import java.util.Set; import java.util.function.Supplier; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_CANONICAL; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_EDITABLE_METADATA; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_GROUP_ID; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_IF_EXISTS; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_RULE_TYPE; -import static io.apicurio.common.apps.logging.audit.AuditingConstants.KEY_VERSION; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_ARTIFACT_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_CANONICAL; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_EDITABLE_METADATA; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_GROUP_ID; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_IF_EXISTS; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_RULE_TYPE; +import static io.apicurio.registry.logging.audit.AuditingConstants.KEY_VERSION; import static java.util.stream.Collectors.toList; /** diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java index 67e7219bc3..66338e272a 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/IdsResourceImpl.java @@ -1,11 +1,11 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.HeadersHack; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java index 1e73a7e181..021f84b4fe 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/SearchResourceImpl.java @@ -1,11 +1,11 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.model.GroupId; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java index 8049094e32..a073db5963 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/SystemResourceImpl.java @@ -1,12 +1,12 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.core.System; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.AuthConfig; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; +import io.apicurio.registry.core.System; import io.apicurio.registry.limits.RegistryLimitsConfiguration; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.RestConfig; diff --git a/app/src/main/java/io/apicurio/registry/rest/v3/UsersResourceImpl.java b/app/src/main/java/io/apicurio/registry/rest/v3/UsersResourceImpl.java index 76f8a7bbb0..e3c6222a9a 100644 --- a/app/src/main/java/io/apicurio/registry/rest/v3/UsersResourceImpl.java +++ b/app/src/main/java/io/apicurio/registry/rest/v3/UsersResourceImpl.java @@ -1,12 +1,12 @@ package io.apicurio.registry.rest.v3; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.auth.AdminOverride; import io.apicurio.registry.auth.AuthConfig; import io.apicurio.registry.auth.Authorized; import io.apicurio.registry.auth.AuthorizedLevel; import io.apicurio.registry.auth.AuthorizedStyle; import io.apicurio.registry.auth.RoleBasedAccessController; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.health.liveness.ResponseErrorLivenessCheck; import io.apicurio.registry.metrics.health.readiness.ResponseTimeoutReadinessCheck; import io.apicurio.registry.rest.v3.beans.UserInfo; diff --git a/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java b/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java index afe7e14698..845d8a3069 100644 --- a/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java +++ b/app/src/main/java/io/apicurio/registry/rules/compatibility/CompatibilityRuleExecutor.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.compatibility; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.rules.RuleContext; import io.apicurio.registry.rules.RuleExecutor; import io.apicurio.registry.rules.RuleViolation; diff --git a/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java b/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java index 348273ddf7..98da9e76ef 100644 --- a/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java +++ b/app/src/main/java/io/apicurio/registry/rules/integrity/IntegrityRuleExecutor.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rules.integrity; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.rest.v3.beans.ArtifactReference; import io.apicurio.registry.rules.RuleContext; import io.apicurio.registry.rules.RuleExecutor; diff --git a/app/src/main/java/io/apicurio/registry/rules/validity/ValidityRuleExecutor.java b/app/src/main/java/io/apicurio/registry/rules/validity/ValidityRuleExecutor.java index 77a8a2f955..10b27cc54f 100644 --- a/app/src/main/java/io/apicurio/registry/rules/validity/ValidityRuleExecutor.java +++ b/app/src/main/java/io/apicurio/registry/rules/validity/ValidityRuleExecutor.java @@ -1,6 +1,6 @@ package io.apicurio.registry.rules.validity; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.rules.RuleContext; import io.apicurio.registry.rules.RuleExecutor; import io.apicurio.registry.rules.RuleViolationException; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java index a387ca308e..aea412ff13 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/GitOpsRegistryStorage.java @@ -3,8 +3,8 @@ import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.DynamicConfigStorage; import io.apicurio.common.apps.config.Info; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.StorageMetricsApply; import io.apicurio.registry.model.BranchId; import io.apicurio.registry.model.GA; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/BlueSqlStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/BlueSqlStorage.java index 3e91acbf5f..1715ac8dd0 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/BlueSqlStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/BlueSqlStorage.java @@ -1,7 +1,7 @@ package io.apicurio.registry.storage.impl.gitops.sql; import io.agroal.api.AgroalDataSource; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.storage.impl.sql.AbstractSqlRegistryStorage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/GreenSqlStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/GreenSqlStorage.java index a113b04c15..f063652845 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/GreenSqlStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/gitops/sql/GreenSqlStorage.java @@ -1,7 +1,7 @@ package io.apicurio.registry.storage.impl.gitops.sql; import io.agroal.api.AgroalDataSource; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.storage.impl.sql.AbstractSqlRegistryStorage; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java index 6ec84c1248..b9c751bd02 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlRegistryStorage.java @@ -1,9 +1,9 @@ package io.apicurio.registry.storage.impl.kafkasql; import io.apicurio.common.apps.config.DynamicConfigPropertyDto; -import io.apicurio.common.apps.logging.Logged; import io.apicurio.registry.content.ContentHandle; import io.apicurio.registry.events.*; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.StorageMetricsApply; import io.apicurio.registry.metrics.health.liveness.PersistenceExceptionLivenessApply; import io.apicurio.registry.metrics.health.readiness.PersistenceTimeoutReadinessApply; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlSubmitter.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlSubmitter.java index 6a8b295c45..617bd7a9a4 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlSubmitter.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/KafkaSqlSubmitter.java @@ -1,6 +1,6 @@ package io.apicurio.registry.storage.impl.kafkasql; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.utils.kafka.ProducerActions; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.event.Observes; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/sql/KafkaSqlSink.java b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/sql/KafkaSqlSink.java index 8f05c578c6..b09f039e1b 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/sql/KafkaSqlSink.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/kafkasql/sql/KafkaSqlSink.java @@ -1,6 +1,6 @@ package io.apicurio.registry.storage.impl.kafkasql.sql; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.storage.impl.kafkasql.KafkaSqlCoordinator; import io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessage; import io.apicurio.registry.storage.impl.kafkasql.KafkaSqlMessageKey; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java index 03e46c691c..9491d57fbc 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/AbstractSqlRegistryStorage.java @@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.apicurio.common.apps.config.DynamicConfigPropertyDto; import io.apicurio.common.apps.config.Info; -import io.apicurio.common.apps.core.System; import io.apicurio.registry.content.TypedContent; +import io.apicurio.registry.core.System; import io.apicurio.registry.events.ArtifactCreated; import io.apicurio.registry.events.ArtifactDeleted; import io.apicurio.registry.events.ArtifactMetadataUpdated; diff --git a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlRegistryStorage.java b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlRegistryStorage.java index 818a87cd6d..c0dbb5f3a6 100644 --- a/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlRegistryStorage.java +++ b/app/src/main/java/io/apicurio/registry/storage/impl/sql/SqlRegistryStorage.java @@ -1,6 +1,6 @@ package io.apicurio.registry.storage.impl.sql; -import io.apicurio.common.apps.logging.Logged; +import io.apicurio.registry.logging.Logged; import io.apicurio.registry.metrics.StorageMetricsApply; import io.apicurio.registry.metrics.health.liveness.PersistenceExceptionLivenessApply; import io.apicurio.registry.metrics.health.readiness.PersistenceTimeoutReadinessApply; diff --git a/app/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource b/app/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource index 95f3f02639..d16c67d620 100644 --- a/app/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource +++ b/app/src/main/resources/META-INF/services/org.eclipse.microprofile.config.spi.ConfigSource @@ -1 +1,2 @@ io.apicurio.registry.services.RegistryConfigSource +io.apicurio.registry.config.config.impl.DynamicConfigSource diff --git a/app/src/test/java/io/apicurio/registry/rbac/MockAuditLogService.java b/app/src/test/java/io/apicurio/registry/rbac/MockAuditLogService.java index a08f0ee572..f6a3ec8027 100644 --- a/app/src/test/java/io/apicurio/registry/rbac/MockAuditLogService.java +++ b/app/src/test/java/io/apicurio/registry/rbac/MockAuditLogService.java @@ -1,7 +1,7 @@ package io.apicurio.registry.rbac; -import io.apicurio.common.apps.logging.audit.AuditHttpRequestInfo; -import io.apicurio.common.apps.logging.audit.AuditLogService; +import io.apicurio.registry.logging.audit.AuditHttpRequestInfo; +import io.apicurio.registry.logging.audit.AuditLogService; import io.quarkus.test.Mock; import java.util.HashMap; diff --git a/common/pom.xml b/common/pom.xml index b4ac54c8cc..27969ba453 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -64,7 +64,7 @@ io.apicurio - apicurio-common-app-components-config-definitions + apicurio-registry-config-definitions provided diff --git a/config-index/definitions/pom.xml b/config-index/definitions/pom.xml new file mode 100644 index 0000000000..b3fe480d8e --- /dev/null +++ b/config-index/definitions/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + + io.apicurio + apicurio-registry + 3.0.4-SNAPSHOT + ../../pom.xml + + + apicurio-registry-config-definitions + + + ${project.basedir}/../.. + + + + + io.quarkus + quarkus-undertow + provided + + + + + + + maven-surefire-plugin + + + + diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Dynamic.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Dynamic.java new file mode 100644 index 0000000000..551f59af73 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Dynamic.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * @author eric.wittmann@gmail.com + */ +@Qualifier +@Documented +@Retention(RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD }) +public @interface Dynamic { + + @Nonbinding + String label() default ""; + + @Nonbinding + String description() default ""; + + @Nonbinding + String[] requires() default {}; + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDef.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDef.java new file mode 100644 index 0000000000..2cba46c02a --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDef.java @@ -0,0 +1,194 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import java.util.Objects; + +/** + * @author eric.wittmann@gmail.com + */ +@SuppressWarnings("rawtypes") +public class DynamicConfigPropertyDef { + + private String name; + private Class type; + private String defaultValue; + private String label; + private String description; + private String[] requires; + + /** + * Constructor. + */ + public DynamicConfigPropertyDef() { + } + + /** + * Constructor. + * + * @param name the config property name + * @param type the property type + * @param defaultValue the default value of the config property + */ + public DynamicConfigPropertyDef(String name, Class type, String defaultValue) { + this.setName(name); + this.setType(type); + this.setDefaultValue(defaultValue); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the type + */ + public Class getType() { + return type; + } + + /** + * @param type the type to set + */ + public void setType(Class type) { + this.type = type; + } + + /** + * Returns true if the given value is valid for this dynamic property. + * + * @param value the value to test + * @return true if the value is valid + */ + public boolean isValidValue(String value) { + // TODO this is very rudimentary. We should try to leverage MP-Config if possible to validate values. + try { + if (this.getType() == Long.class) { + Long.parseLong(value); + } + if (this.getType() == Integer.class) { + Integer.parseInt(value); + } + if (this.getType() == Boolean.class) { + if (!"true".equalsIgnoreCase(value) && !"false".equalsIgnoreCase(value)) { + throw new Exception("Invalid boolean value: " + value); + } + } + return true; + } catch (Exception e) { + return false; + } + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Objects.hash(getName()); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DynamicConfigPropertyDef other = (DynamicConfigPropertyDef) obj; + return Objects.equals(getName(), other.getName()); + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "DynamicConfigPropertyDef [name=" + name + ", type=" + type + "]"; + } + + /** + * @return the defaultValue + */ + public String getDefaultValue() { + return defaultValue; + } + + /** + * @param defaultValue the defaultValue to set + */ + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } + + /** + * @param label the label to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the description + */ + public String getDescription() { + return description; + } + + /** + * @param description the description to set + */ + public void setDescription(String description) { + this.description = description; + } + + /** + * @return the requires + */ + public String[] getRequires() { + return requires; + } + + /** + * @param requires the requires to set + */ + public void setRequires(String[] requires) { + this.requires = requires; + } + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDto.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDto.java new file mode 100644 index 0000000000..27d21c7e90 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyDto.java @@ -0,0 +1,106 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import java.util.Objects; + +/** + * @author eric.wittmann@gmail.com + */ +public class DynamicConfigPropertyDto { + + private String name; + private String value; + + /** + * Constructor. + */ + public DynamicConfigPropertyDto() { + } + + /** + * Constructor. + * + * @param name the name of the property + * @param value the value of the property + */ + public DynamicConfigPropertyDto(String name, String value) { + super(); + this.name = name; + this.value = value; + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + /** + * @param value the value to set + */ + public void setValue(String value) { + this.value = value; + } + + /** + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "DynamicConfigPropertyDto [name=" + name + ", value=" + value + "]"; + } + + /** + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + DynamicConfigPropertyDto other = (DynamicConfigPropertyDto) obj; + return Objects.equals(name, other.name) && Objects.equals(value, other.value); + } + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyIndex.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyIndex.java new file mode 100644 index 0000000000..0ed8282bf6 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyIndex.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import java.util.Set; + +/** + * @author eric.wittmann@gmail.com + */ +public interface DynamicConfigPropertyIndex { + + public DynamicConfigPropertyDef getProperty(String name); + + public boolean hasProperty(String name); + + public Set getPropertyNames(); + + public boolean isAccepted(String propertyName); + + public Set getAcceptedPropertyNames(); + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyList.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyList.java new file mode 100644 index 0000000000..5818f2f2d6 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigPropertyList.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import java.util.List; + +public class DynamicConfigPropertyList { + + private List dynamicConfigProperties; + + public DynamicConfigPropertyList() { + } + + public DynamicConfigPropertyList(List dynamicConfigProperties) { + this.setDynamicConfigProperties(dynamicConfigProperties); + } + + public List getDynamicConfigProperties() { + return dynamicConfigProperties; + } + + public void setDynamicConfigProperties(List dynamicConfigProperties) { + this.dynamicConfigProperties = dynamicConfigProperties; + } + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigSqlStorageStatements.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigSqlStorageStatements.java new file mode 100644 index 0000000000..7d89a89360 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigSqlStorageStatements.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +/** + * @author eric.wittmann@gmail.com + * @author Jakub Senko m@jsenko.net + */ +public interface DynamicConfigSqlStorageStatements { + + String selectConfigProperties(); + + String deleteConfigProperty(); + + String insertConfigProperty(); + + String deleteAllConfigProperties(); + + String selectConfigPropertyByName(); + + String selectTenantIdsByConfigModifiedOn(); +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorage.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorage.java new file mode 100644 index 0000000000..6e0d4e8f88 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorage.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import java.util.List; + +/** + * @author eric.wittmann@gmail.com + */ +public interface DynamicConfigStorage { + + boolean isReady(); + + /** + * Should return the stored config property or null if not found. + * + * @param propertyName the name of a property + * @return the property value or null if not found + */ + public DynamicConfigPropertyDto getConfigProperty(String propertyName); + + /** + * Sets a new value for a config property. + * + * @param propertyDto the property name and value + */ + public void setConfigProperty(DynamicConfigPropertyDto propertyDto); + + /** + * Deletes a config property from storage. + * + * @param propertyName the name of a property + */ + public void deleteConfigProperty(String propertyName); + + /** + * Gets a list of all stored config properties. + * + * @return a list of stored properties + */ + public List getConfigProperties(); + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorageAccessor.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorageAccessor.java new file mode 100644 index 0000000000..d9d5880294 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/DynamicConfigStorageAccessor.java @@ -0,0 +1,26 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +/** + * @author eric.wittmann@gmail.com + */ +public interface DynamicConfigStorageAccessor { + + public DynamicConfigStorage getConfigStorage(); + +} diff --git a/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Info.java b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Info.java new file mode 100644 index 0000000000..e998869575 --- /dev/null +++ b/config-index/definitions/src/main/java/io/apicurio/common/apps/config/Info.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.apicurio.common.apps.config; + +import jakarta.enterprise.util.Nonbinding; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target({ ElementType.FIELD, ElementType.METHOD }) +public @interface Info { + + @Nonbinding + String category() default ""; + + @Nonbinding + String description() default ""; + + @Nonbinding + String availableSince() default ""; + + @Nonbinding + String registryAvailableSince() default ""; + + @Nonbinding + String studioAvailableSince() default ""; + + /** + * Lists related configuration properties. TODO: Not used in docs yet + */ + @Nonbinding + String[] seeAlso() default {}; + + /** + * Lists configuration properties that must be configured before using this property. TODO: Not used in + * docs yet + */ + @Nonbinding + String[] dependsOn() default {}; +} diff --git a/config-index/definitions/src/main/resources/META-INF/beans.xml b/config-index/definitions/src/main/resources/META-INF/beans.xml new file mode 100644 index 0000000000..c809805977 --- /dev/null +++ b/config-index/definitions/src/main/resources/META-INF/beans.xml @@ -0,0 +1,5 @@ + + diff --git a/config-index/deployment/pom.xml b/config-index/deployment/pom.xml new file mode 100644 index 0000000000..2f35d02b7c --- /dev/null +++ b/config-index/deployment/pom.xml @@ -0,0 +1,52 @@ + + + 4.0.0 + + io.apicurio + apicurio-registry + 3.0.4-SNAPSHOT + ../../pom.xml + + + apicurio-registry-config-index-deployment + + + ${project.basedir}/../.. + + + + + io.apicurio + apicurio-registry-config-definitions + + + io.apicurio + apicurio-registry-config-index + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-junit5-internal + test + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/config-index/deployment/src/main/java/apicurio/common/app/components/config/index/deployment/ConfigIndexProcessor.java b/config-index/deployment/src/main/java/apicurio/common/app/components/config/index/deployment/ConfigIndexProcessor.java new file mode 100644 index 0000000000..0569e62c1d --- /dev/null +++ b/config-index/deployment/src/main/java/apicurio/common/app/components/config/index/deployment/ConfigIndexProcessor.java @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package apicurio.common.app.components.config.index.deployment; + +import apicurio.common.app.components.config.index.DynamicPropertiesInfoRecorder; +import io.apicurio.common.apps.config.Dynamic; +import io.apicurio.common.apps.config.DynamicConfigPropertyDef; +import io.apicurio.common.apps.config.DynamicConfigPropertyList; +import io.quarkus.arc.deployment.BeanDiscoveryFinishedBuildItem; +import io.quarkus.arc.deployment.SyntheticBeanBuildItem; +import io.quarkus.arc.processor.InjectionPointInfo; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.runtime.RuntimeValue; +import org.eclipse.microprofile.config.inject.ConfigProperty; +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; +import org.jboss.jandex.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Collectors; + +class ConfigIndexProcessor { + + private static final Logger log = LoggerFactory.getLogger(ConfigIndexProcessor.class); + + @BuildStep + @Record(ExecutionTime.RUNTIME_INIT) + void syntheticBean(DynamicPropertiesInfoRecorder recorder, BeanDiscoveryFinishedBuildItem beanDiscovery, + BuildProducer syntheticBeans) { + List dynamicProperties = beanDiscovery.getInjectionPoints().stream() + .filter(ConfigIndexProcessor::isDynamicConfigProperty).map(injectionPointInfo -> { + try { + AnnotationInstance configPropertyAI = injectionPointInfo + .getRequiredQualifier(DotName.createSimple(ConfigProperty.class.getName())); + AnnotationInstance dynamicAI = injectionPointInfo + .getRequiredQualifier(DotName.createSimple(Dynamic.class.getName())); + + Type supplierType = injectionPointInfo.getRequiredType(); + Type actualType = supplierType.asParameterizedType().arguments().get(0); + + final String propertyName = configPropertyAI.value("name").asString(); + final Class propertyType = Class.forName(actualType.name().toString()); + final AnnotationValue defaultValueAV = configPropertyAI.value("defaultValue"); + if (defaultValueAV == null) { + throw new RuntimeException("Dynamic configuration property '" + propertyName + + "' must have a default value."); + } + final String defaultValue = defaultValueAV.asString(); + DynamicConfigPropertyDef def = new DynamicConfigPropertyDef(propertyName, + propertyType, defaultValue); + + final AnnotationValue labelAV = dynamicAI.value("label"); + final AnnotationValue descriptionAV = dynamicAI.value("description"); + final AnnotationValue requiresAV = dynamicAI.value("requires"); + if (labelAV != null) { + def.setLabel(labelAV.asString()); + } + if (descriptionAV != null) { + def.setDescription(descriptionAV.asString()); + } + if (requiresAV != null) { + def.setRequires(requiresAV.asStringArray()); + } + + return def; + } catch (Exception e) { + if (e.getMessage().contains("Not a parameterized type")) { + log.error("Invalid type for @Dynamic config property (must be Supplier)"); + } + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + + final RuntimeValue dynamicPropertiesHolderRuntimeValue = recorder + .initializePropertiesInfo(dynamicProperties); + + syntheticBeans.produce(SyntheticBeanBuildItem.configure(DynamicConfigPropertyList.class) + .runtimeValue(dynamicPropertiesHolderRuntimeValue).unremovable().setRuntimeInit().done()); + } + + private static boolean isDynamicConfigProperty(InjectionPointInfo injectionPointInfo) { + return injectionPointInfo + .getRequiredQualifier(DotName.createSimple(ConfigProperty.class.getName())) != null + && injectionPointInfo.isField() && injectionPointInfo.getTarget().asField() + .annotation(DotName.createSimple(Dynamic.class.getName())) != null; + } +} diff --git a/config-index/runtime/pom.xml b/config-index/runtime/pom.xml new file mode 100644 index 0000000000..9d54ee0212 --- /dev/null +++ b/config-index/runtime/pom.xml @@ -0,0 +1,64 @@ + + + 4.0.0 + + io.apicurio + apicurio-registry + 3.0.4-SNAPSHOT + ../../pom.xml + + + apicurio-registry-config-index + + + ${project.basedir}/../.. + + + + + io.apicurio + apicurio-registry-config-definitions + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-core + + + + + + io.quarkus + quarkus-extension-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + true + + + + + + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/config-index/runtime/src/main/java/apicurio/common/app/components/config/index/DynamicPropertiesInfoRecorder.java b/config-index/runtime/src/main/java/apicurio/common/app/components/config/index/DynamicPropertiesInfoRecorder.java new file mode 100644 index 0000000000..4443c29e8f --- /dev/null +++ b/config-index/runtime/src/main/java/apicurio/common/app/components/config/index/DynamicPropertiesInfoRecorder.java @@ -0,0 +1,34 @@ +/* + * Copyright 2022 Red Hat + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package apicurio.common.app.components.config.index; + +import io.apicurio.common.apps.config.DynamicConfigPropertyDef; +import io.apicurio.common.apps.config.DynamicConfigPropertyList; +import io.quarkus.runtime.RuntimeValue; +import io.quarkus.runtime.annotations.Recorder; + +import java.util.List; + +@Recorder +public class DynamicPropertiesInfoRecorder { + + public RuntimeValue initializePropertiesInfo( + List dynamicProperties) { + + return new RuntimeValue<>(new DynamicConfigPropertyList(dynamicProperties)); + } +} diff --git a/config-index/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/config-index/runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000..374365d4a4 --- /dev/null +++ b/config-index/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,9 @@ +name: Config Index +#description: Config Index ... +metadata: +# keywords: +# - config-index +# guide: ... +# categories: +# - "miscellaneous" +# status: "preview" \ No newline at end of file diff --git a/docs/generateAllConfigPartial.java b/docs/generateAllConfigPartial.java index 8831c70dce..d9600047ca 100644 --- a/docs/generateAllConfigPartial.java +++ b/docs/generateAllConfigPartial.java @@ -149,6 +149,10 @@ public static Map extractConfigurations(String jarFile, Map configAnnotations = index.getAnnotations(configProperty); for (AnnotationInstance annotation : configAnnotations) { + if (annotation.value("name") == null) { + continue; + } + var configName = annotation.value("name").value().toString(); if (allConfiguration.containsKey(configName)) { continue; @@ -216,7 +220,6 @@ public static void main(String... args) throws Exception { // TODO: include all the relevant jars, to be determined // Extract configuration from Jandex - extractConfigurations(baseDir + "/../app/target/lib/io.apicurio.apicurio-common-app-components-auth-" + commonComponentsVersion + ".jar", allConfiguration); extractConfigurations(baseDir + "/../app/target/apicurio-registry-app-" + currentVersion + ".jar", allConfiguration); // TODO diff --git a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc index 7301d0aece..f9668dbcbd 100644 --- a/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc +++ b/docs/modules/ROOT/partials/getting-started/ref-registry-all-configs.adoc @@ -3,6 +3,37 @@ The following {registry} configuration options are available for each component category: +== +. configuration options +[.table-expandable,width="100%",cols="6,3,2,3,5",options="header"] +|=== +|Name +|Type +|Default +|Available from +|Description +|`apicurio.app.date` +|`string` +| +|`3.0.4` +| +|`apicurio.app.description` +|`string` +| +|`3.0.4` +| +|`apicurio.app.name` +|`string` +| +|`3.0.4` +| +|`apicurio.app.version` +|`string` +| +|`3.0.4` +| +|=== + == api .api configuration options [.table-expandable,width="100%",cols="6,3,2,3,5",options="header"] @@ -151,7 +182,7 @@ The following {registry} configuration options are available for each component |`quarkus.http.auth.basic` |`boolean` |`false` -|`3.X.X.Final` +|`3.0.0` |Enable basic auth |`quarkus.oidc.client-id` |`string` diff --git a/docs/pom.xml b/docs/pom.xml index 670ae00c3d..390dbb6244 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -118,7 +118,7 @@ ${project.version} ${project.basedir} - ${apicurio-common-app-components.version} + ${project.version} diff --git a/pom.xml b/pom.xml index f4373e19af..84d4d3a010 100644 --- a/pom.xml +++ b/pom.xml @@ -70,6 +70,9 @@ common + config-index/definitions + config-index/deployment + config-index/runtime app utils/converter utils/kafka @@ -208,13 +211,14 @@ 1.18.36 1.17.1 + 1.2 1.2.1.Final 4.5.14 0.1.18.Final - 1.2.5 3.6.0 2.6.2.Final 3.3.1 + 1.9.4 2.20.4 2.3 @@ -254,6 +258,7 @@ 8.45.1 + 0.0.1 0.6.1 @@ -428,33 +433,23 @@ io.apicurio - apicurio-common-app-components-core - ${apicurio-common-app-components.version} - - - io.apicurio - apicurio-common-app-components-logging - ${apicurio-common-app-components.version} - - - io.apicurio - apicurio-common-app-components-config - ${apicurio-common-app-components.version} + apicurio-registry-config-definitions + ${project.version} io.apicurio - apicurio-common-app-components-config-definitions - ${apicurio-common-app-components.version} + apicurio-registry-config-index + ${project.version} io.apicurio - apicurio-common-app-components-config-index - ${apicurio-common-app-components.version} + apicurio-registry-config-index-deployment + ${project.version} io.apicurio - apicurio-common-app-components-auth - ${apicurio-common-app-components.version} + apicurio-maven-plugin + ${apicurio-maven-plugin.version} io.apicurio @@ -644,6 +639,11 @@ commons-codec ${commons-codec.version} + + commons-logging + commons-logging + 1.0.4 + org.apache.httpcomponents httpclient @@ -659,6 +659,11 @@ semver4j ${semver4j.version} + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + diff --git a/prod-verifier/pom.xml b/prod-verifier/pom.xml index 25ca6577f1..e86db59d88 100644 --- a/prod-verifier/pom.xml +++ b/prod-verifier/pom.xml @@ -35,8 +35,8 @@ io.apicurio - apicurio-common-app-components-maven-plugin - ${apicurio-common-app-components.version} + apicurio-registry-maven-plugin + ${apicurio-maven-plugin.version} verify-prod-dependencies diff --git a/schema-util/json/pom.xml b/schema-util/json/pom.xml index b1a2ba8f19..7359484001 100644 --- a/schema-util/json/pom.xml +++ b/schema-util/json/pom.xml @@ -32,10 +32,9 @@ - io.apicurio - apicurio-common-app-components-logging + com.google.guava + guava - com.github.erosb everit-json-schema