WorkspaceResolver.java
package com.taxonomy.workspace.service;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
/**
* Resolves the current username plus request-bound workspace and repository contexts.
*
* <p>Successfully resolved contexts are cached for the lifetime of the HTTP request.
* All collaborators in one request therefore observe the same logical repository
* even when active-workspace metadata changes concurrently.</p>
*/
@Component
public class WorkspaceResolver {
private static final String REQUEST_CONTEXT_ATTRIBUTE =
WorkspaceResolver.class.getName() + ".resolvedContext";
private static final String REQUEST_REPOSITORY_CONTEXT_ATTRIBUTE =
WorkspaceResolver.class.getName() + ".resolvedRepositoryContext";
private final WorkspaceContextResolver contextResolver;
public WorkspaceResolver(WorkspaceContextResolver contextResolver) {
this.contextResolver = contextResolver;
}
/** Resolve the authenticated username or the configured default user. */
public String resolveCurrentUsername() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated()
&& !"anonymousUser".equals(auth.getPrincipal())) {
return auth.getName();
}
return WorkspaceManager.DEFAULT_USER;
}
/**
* Resolve the compatibility workspace context for the current request.
*
* <p>The returned value is enriched from the same request-stable
* {@link RepositoryContext}. Legacy callers therefore cannot lose the logical
* repository identity or continue with a branch that differs from the
* canonical repository selection.</p>
*/
public WorkspaceContext resolveCurrentContext() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes != null) {
Object cached = attributes.getAttribute(
REQUEST_CONTEXT_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (cached instanceof WorkspaceContext context) {
return context;
}
}
RepositoryContext repository = resolveCurrentRepositoryContext();
WorkspaceContext resolved = new WorkspaceContext(
repository.username(),
repository.workspaceId(),
repository.branch(),
repository.repositoryId());
cache(attributes, REQUEST_CONTEXT_ATTRIBUTE, resolved);
return resolved;
}
/**
* Resolve the mandatory logical repository identity for the current request.
* A central context always carries a repository ID; it never reuses the
* ambiguous legacy {@link WorkspaceContext#SHARED} sentinel.
*/
public RepositoryContext resolveCurrentRepositoryContext() {
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
if (attributes != null) {
Object cached = attributes.getAttribute(
REQUEST_REPOSITORY_CONTEXT_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
if (cached instanceof RepositoryContext context) {
return context;
}
}
RepositoryContext resolved = contextResolver.resolveRepositoryContextForUser(
resolveCurrentUsername());
if (resolved == null) {
throw new IllegalStateException("Repository context resolver returned null");
}
cache(attributes, REQUEST_REPOSITORY_CONTEXT_ATTRIBUTE, resolved);
return resolved;
}
private static void cache(
RequestAttributes attributes, String attributeName, Object value) {
if (attributes != null) {
attributes.setAttribute(
attributeName, value, RequestAttributes.SCOPE_REQUEST);
}
}
}