feat(ext-data): add unified Data gateway; mcp: derive roles claim from OIDC
Data/RepositoryFactory/HibernateData give applications one cached, stateless entry point for repositories per entity type instead of per-request instantiation, with write()/afterCommit() replacing manual transaction+reload choreography. McpConfig.rolesClaimPath is removed - MCP now derives the claim path from OidcMiddleware.rolesClaimPath() so applications never duplicate the roles-claim config between OIDC and MCP. McpPackageScanner is rebuilt on the shared PackageScanner.discover(packageName) primitive, doing only McpTool/McpResource/McpPrompt classification itself. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
891ef99b8e
commit
9e045287c1
+9
-1
@@ -1,6 +1,7 @@
|
||||
package dev.relism.flash.ext.data;
|
||||
|
||||
import dev.relism.flash.ext.data.core.Tx;
|
||||
import dev.relism.flash.ext.data.core.Data;
|
||||
import dev.relism.flash.ext.data.core.TxDefinition;
|
||||
import dev.relism.flash.ext.data.core.TxManager;
|
||||
import dev.relism.flash.ext.data.core.TransactionPropagation;
|
||||
@@ -19,16 +20,23 @@ public final class DataExtension implements FlashExtension {
|
||||
private static final MiddlewareKey TRANSACTION = MiddlewareKey.of("flash.data.transaction");
|
||||
private final TxManager txManager;
|
||||
private final Tx tx;
|
||||
private final Data data;
|
||||
|
||||
public DataExtension(TxManager txManager) {
|
||||
this(txManager, null);
|
||||
}
|
||||
|
||||
public DataExtension(TxManager txManager, Data data) {
|
||||
this.txManager = Objects.requireNonNull(txManager);
|
||||
this.tx = new Tx(txManager);
|
||||
this.data = data;
|
||||
this.tx = data != null ? data.tx() : new Tx(txManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(FlashRegistrar<?> app, FlashContext ctx) {
|
||||
ctx.provide(Tx.class, tx);
|
||||
ctx.provide(TxManager.class, txManager);
|
||||
if (data != null) ctx.provide(Data.class, data);
|
||||
ctx.addAnnotationProcessor(handlerClass -> {
|
||||
Transactional ann = handlerClass.getAnnotation(Transactional.class);
|
||||
if (ann == null) {
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.io.Serializable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Application-facing data gateway. Repositories are created once per entity type and are safe to
|
||||
* share: transaction/session state stays in {@link Tx}, never in the repository instance.
|
||||
*/
|
||||
public final class Data {
|
||||
private final Tx tx;
|
||||
private final RepositoryFactory repositories;
|
||||
private final ConcurrentHashMap<Class<?>, Repository<?, ?>> cache = new ConcurrentHashMap<>();
|
||||
|
||||
public Data(Tx tx, RepositoryFactory repositories) {
|
||||
this.tx = Objects.requireNonNull(tx, "tx");
|
||||
this.repositories = Objects.requireNonNull(repositories, "repositories");
|
||||
}
|
||||
|
||||
public Tx tx() { return tx; }
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T, ID extends Serializable> Repository<T, ID> repository(Class<T> type) {
|
||||
Objects.requireNonNull(type, "type");
|
||||
return (Repository<T, ID>) cache.computeIfAbsent(type, key -> repositories.create(tx, type));
|
||||
}
|
||||
|
||||
public void read(Tx.TxRunnable work) { tx.run(tx.readOnly(), work); }
|
||||
public <T> T read(Tx.TxCallable<T> work) { return tx.call(tx.readOnly(), work); }
|
||||
public void write(Tx.TxRunnable work) { tx.run(work); }
|
||||
public <T> T write(Tx.TxCallable<T> work) { return tx.call(work); }
|
||||
/** Transitional low-level access for infrastructure that needs an explicit definition. */
|
||||
public void run(Tx.TxRunnable work) { tx.run(work); }
|
||||
public <T> T call(Tx.TxCallable<T> work) { return tx.call(work); }
|
||||
public <T> T call(TxDefinition definition, Tx.TxCallable<T> work) { return tx.call(definition, work); }
|
||||
public TxDefinition readOnly() { return tx.readOnly(); }
|
||||
|
||||
/** Registers work that runs only after the enclosing write transaction commits. */
|
||||
public void afterCommit(Runnable work) {
|
||||
if (!tx.isActive()) throw new IllegalStateException("afterCommit requires an active transaction");
|
||||
ResourceRegistry.addSynchronization(new TxSynchronization() {
|
||||
@Override public void afterCommit() { work.run(); }
|
||||
});
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
package dev.relism.flash.ext.data.core;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/** Creates the backend-specific, stateless repository for one entity type. */
|
||||
@FunctionalInterface
|
||||
public interface RepositoryFactory {
|
||||
<T, ID extends Serializable> Repository<T, ID> create(Tx tx, Class<T> type);
|
||||
}
|
||||
Reference in New Issue
Block a user