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}