Skip to content

Commit

Permalink
feat: Add method argument resolver for tenant identifier
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Vitale <[email protected]>
  • Loading branch information
ThomasVitale committed Mar 31, 2024
1 parent f8f9664 commit 30c9123
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 2 deletions.
2 changes: 1 addition & 1 deletion arconia-spring-boot-autoconfigure/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ dependencies {
optional 'jakarta.servlet:jakarta.servlet-api'

optional 'org.springframework:spring-context'
optional 'org.springframework:spring-web'
optional 'org.springframework:spring-webmvc'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/
@AutoConfiguration(after = MultitenancyCoreAutoConfiguration.class)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@Import(HttpTenantResolutionConfiguration.class)
@Import({ HttpTenantResolutionConfiguration.class, WebMvcConfiguration.class })
public class MultitenancyWebAutoConfiguration {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package io.arconia.autoconfigure.multitenancy.web;

import java.util.List;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import io.arconia.web.multitenancy.context.annotations.TenantIdentifierArgumentResolver;

/**
* Register Arconia-specific Spring Web MVC configuration.
*/
@Configuration(proxyBeanMethods = false)
public class WebMvcConfiguration implements WebMvcConfigurer {

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TenantIdentifierArgumentResolver());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.arconia.web.multitenancy.context.annotations;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that is used to resolve the current tenant identifier as a method argument.
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TenantIdentifier {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.arconia.web.multitenancy.context.annotations;

import org.springframework.core.MethodParameter;
import org.springframework.lang.Nullable;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import io.arconia.core.multitenancy.context.TenantContextHolder;

/**
* Allows resolving the current tenant identifier using the {@link TenantIdentifier}
* annotation.
* <p>
* Example:
*
* <pre>
* &#64;RestController
* class MyRestController {
* &#64;GetMapping("/tenant")
* String getCurrentTenant(@CurrentTenantIdentifier String tenantIdentifier) {
* return tenantIdentifier;
* }
* }
* </pre>
*/
public final class TenantIdentifierArgumentResolver implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(TenantIdentifier.class) != null
&& parameter.getParameterType().getTypeName().equals(String.class.getTypeName());
}

@Nullable
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
return TenantContextHolder.getTenantIdentifier();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@NonNullApi
@NonNullFields
package io.arconia.web.multitenancy.context.annotations;

import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package io.arconia.web.multitenancy.context.annotations;

import java.lang.reflect.Method;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.MethodParameter;
import org.springframework.util.ReflectionUtils;

import io.arconia.core.multitenancy.context.TenantContextHolder;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Unit tests for {@link TenantIdentifierArgumentResolver}.
*/
class TenantIdentifierArgumentResolverTests {

private final TenantIdentifierArgumentResolver argumentResolver = new TenantIdentifierArgumentResolver();

@AfterEach
void cleanup() {
TenantContextHolder.clear();
}

@Test
void doesNotSupportParameterWithoutAnnotation() {
assertThat(argumentResolver.supportsParameter(showTenantIdentifierNoAnnotation())).isFalse();
}

@Test
void supportsParameterWithAnnotation() {
assertThat(argumentResolver.supportsParameter(showTenantIdentifierAnnotation())).isTrue();
}

@Test
void doesNotSupportParameterWithWrongType() {
assertThat(argumentResolver.supportsParameter(showTenantIdentifierErrorOnInvalidType())).isFalse();
}

@Test
void resolveTenantIdentifierArgument() {
String expectedTenantIdentifier = "acme";
TenantContextHolder.setTenantIdentifier(expectedTenantIdentifier);
String actualTenantIdentifier = (String) argumentResolver.resolveArgument(showTenantIdentifierAnnotation(),
null, null, null);
assertThat(actualTenantIdentifier).isEqualTo(expectedTenantIdentifier);
}

private MethodParameter showTenantIdentifierNoAnnotation() {
return getMethodParameter("showTenantIdentifierNoAnnotation", String.class);
}

private MethodParameter showTenantIdentifierAnnotation() {
return getMethodParameter("showTenantIdentifierAnnotation", String.class);
}

private MethodParameter showTenantIdentifierErrorOnInvalidType() {
return getMethodParameter("showTenantIdentifierErrorOnInvalidType", Long.class);
}

private MethodParameter getMethodParameter(String methodName, Class<?>... paramTypes) {
Method method = ReflectionUtils.findMethod(TestController.class, methodName, paramTypes);
return new MethodParameter(method, 0);
}

static class TestController {

public void showTenantIdentifierNoAnnotation(String tenantIdentifier) {
}

public void showTenantIdentifierAnnotation(@TenantIdentifier String tenantIdentifier) {
}

public void showTenantIdentifierErrorOnInvalidType(@TenantIdentifier Long tenantIdentifier) {
}

}

}

0 comments on commit 30c9123

Please sign in to comment.