Sunday, August 19, 2018

Spring magic: Dynamically set URI prefix for group of controllers

Overview

Say you have a big Spring or Spring Boot project with a lot of controller, which you want do separate in different groups with different prefixes.

Annotation

Create annotation which is metaannotated with @Controler or @RestController, It will be used as a marker for controller for which we want to change prefix. In our example this annotation is @ApiController
package co.jware.uri.demo.annotation;
import org.springframework.web.bind.annotation.RestController;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ApiController {
}

Controller

Annotate your controllers with marker annotation (@ApiContgroller) and put @RequestMapping annotation on path level with path attribute set. This attribute will be changed by the postprocessor.
package co.jware.uri.demo.controllers;
import co.jware.uri.demo.annotation.ApiController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@ApiController
@RequestMapping("/demo")
public class DemoController {
@GetMapping
public String handle() {
return "demo";
}
}

Postprocessor

Spring uses postprocessors to enhance beans. We use postprocessor which selects beans on existence of our marker annotation (@ApiController); then checks for @RequestMapping annotation and copies all its attributes in a map. Now we have all attributes at disposiotion and can change it as we like. In our our case we change only value and path attributes by appending configured prefix. This prefix is taken from the environment, but other sources can be implemente. The postprocessor is able to process only one marker annotation, but this can be enhanced in future versions.
Two things are very important in postprocessor implementation:

  1. Annotation Data in Class - java.la ng.Class has method called annotationData(),which gives acces to allannotation information. It has to be accessedvia reflection since it is not part of the public API.
  2. Syntesised annotations Spring framework has special way of annotation processing to support some non trivial features as @AliasFor support.We utilise this mechanism to create clone with changed attributes and to substitute annotatios at runtime. 
package co.jware.uri.demo.spring;
import co.jware.uri.demo.annotation.ApiController;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.RequestMapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
@Slf4j
public class ApiControllerAnnotationBeanPostProcessor implements BeanPostProcessor, EnvironmentAware {
private static final String ANNOTATION_METHOD = "annotationData";
private static final String DECLARED_ANNOTATIONS = "declaredAnnotations";
private Environment env;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
ApiController annotation = AnnotationUtils.findAnnotation(bean.getClass(), ApiController.class);
if (null != annotation) {
RequestMapping requestMapping = AnnotationUtils.findAnnotation(bean.getClass(), RequestMapping.class);
if (null != requestMapping) {
Map<String, Object> attributes = AnnotationUtils.getAnnotationAttributes(requestMapping);
String apiPrefix = env.getProperty("api.prefix", "/api");
String[] values = (String[]) attributes.get("path");
values = Arrays.stream(values)
.filter(s -> !s.startsWith(apiPrefix))
.map(s -> apiPrefix + s).toArray(String[]::new);
if (values.length > 0) {
attributes.put("value", values);
attributes.put("path", values);
RequestMapping target = AnnotationUtils.synthesizeAnnotation(attributes,
RequestMapping.class, null);
changeAnnotationValue(bean.getClass(), RequestMapping.class, target);
// changeAnnotationValueMethodHandles(bean.getClass(), RequestMapping.class, target);
}
}
}
return bean;
}
@SuppressWarnings("unchecked")
private static void changeAnnotationValue(Class<?> targetClass, Class<? extends Annotation> targetAnnotation, Annotation targetValue) {
try {
Method method = Class.class.getDeclaredMethod(ANNOTATION_METHOD);
method.setAccessible(true);
Object annotationData = method.invoke(targetClass);
Field annotations = annotationData.getClass().getDeclaredField(DECLARED_ANNOTATIONS);
annotations.setAccessible(true);
Map<Class<? extends Annotation>, Annotation> map = (Map<Class<? extends Annotation>, Annotation>) annotations.get(annotationData);
map.put(targetAnnotation, targetValue);
} catch (Exception e) {
log.error("Error changing annotation", e);
}
}
@Override
public void setEnvironment(Environment environment) {
env = environment;
}
}

Project

Source code for example project can be found on GitHub

Friday, August 25, 2017

Dynamic database connection switching and application multitenancy with database routing


by Emil Korladinov

Introduction

Projects with  requirements to dynamically switch connections at runtime  and exchange data with different datasources along with multi-tenat capabilities are very common. 
For a project I worked on I compiled a solution using Spring Framework, Spring Data Jpa, Hibernate and Aspect Oriented Programming  with Spring (SpringAOP)
Here  I'd like to share my solution with all of you.

Implementation

Basic idea is to wrap unit of work into a transaction and to exexute it against preconfigured databases(datasources). Transactions are demarcated using standard Spring annotation-based transaction management. Since annotations are used to mark code which will use database routing a new annotation is introduced: @DatabaseRouting which is meta annotated with Spring @Transactional. Information for the currently used tenant id is kept in a ThreadLocal variable on a per-thread basis. On the database side a special DataSource is used - DatabaseRoutingDatasource which extends Spring's AbstractRoutingDataSource. 
Working horse of the solution is aspect with an @Around pointcut. Second annotation, applied on method parameter is used to convey information for chosen datasource down to Spring transaction.

Annotations

Two annotations are used:
@DatabaseRouting is method level annotation marking method which uses database routing to dynamically select database connection and exchange data. Methods that have this anotation are wrapped in transaction. This annotation is matched by an around aspect which sets current connection discriminator (tenantId), proceeds with methd execution and resets connection to default.
This annotation is defined as follows:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public @interface DatabaseRouting {
}
@TenantId is parameter level annotation, used as a vehicle for required connection discriminator (tenantId) to the processing aspect. Parameter with this annotation is not used for business logic in the method body, but removing it from the method signature with break processing and method will be executed on the default connection.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface TenantId {
}
view raw TenantId.java hosted with ❤ by GitHub

ThreadLocals

TenantIdHolder is standard Java ThreadLocal containing current value of tenantId.
public class TenantIdHolder {
private static final ThreadLocal<String> holder = new ThreadLocal<>();
public static String get() {
return holder.get();
}
public static void set(String tenantId) {
if (null == tenantId) {
throw new NullPointerException();
}
holder.set(tenantId);
}
public static void clear() {
holder.remove();
}
}

Datasources

RoutingDataSource Extends AbstractRoutingDataSource from spring, overriding method for key lookup (tenantId), linking it with TenantIdHolder.

Aspects

DatabaseRoutingAspect
This is the working horse of the solution. Purpose of this aspect is twofold - First to extract tenantId from the method arguments through annotated parameter on the method signature; and second execute business logic of the method and reset connection to default.

 
@Component
@Aspect
@Order(200)
public class DatebaseRoutingAspect {
@Autowired
private Map dataSources;
@Pointcut("@annotation(DatabaseRouting))")
public void databaseRouting() {
}
@Pointcut("execution(* *(..))")
public void atExecution() {
}
@Around("@annotation(databaseRouting)")
public Object switchDatabase(ProceedingJoinPoint pjp, DatabaseRouting databaseRouting) throws Throwable {
String tenantId = retrieveTenantId(pjp);
try {
if (null != tenantId) {
if (!dataSources.containsKey(tenantId)) {
throw new IllegalArgumentException("Unknown database key: " + tenantId);
}
TenantIdHolder.set(tenantId);
}
return pjp.proceed();
} finally {
TenantIdHolder.clear();
}
}
private String retrieveTenantId(ProceedingJoinPoint pjp) {
MethodSignature signature = (MethodSignature) pjp.getSignature();
Annotation[][] parameterAnnotations = signature.getMethod().getParameterAnnotations();
Object[] args = pjp.getArgs();
int idx = 0;
Object tenantId = null;
for (Annotation[] annotation : parameterAnnotations) {
tenantId = args[idx++];
for (Annotation annot : annotation) {
if (TenantId.class.equals(annot.annotationType())) {
break;
}
}
}
return (String) tenantId;
}
}
Sources can be found on GitHub

Wednesday, September 24, 2014

Using wildcards for spring MessageSources


by Emil Korladinov 



Overview 

Recently I worked on a Spring project which uses a lot of property files located all over the source tree. The requirement is to use these property files as part of the messageSource configured by Spring Framework.After dome time spent in searching suitable solution, I came up with a custom solution which I want to share with all interested.

Wildcard enabled Spring message source

public class WildcardReloadableResourceBundleMessageSource extends
org.springframework.context.support.ReloadableResourceBundleMessageSource {
private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
@Override
public void setBasenames(String... basenames) {
if (basenames != null) {
List<String> baseNames = new ArrayList<String>();
for (int i = 0; i < basenames.length; i++) {
String basename = trimToEmpty(basenames[i]);
if (isNotBlank(basename)) {
try {
Resource[] resources = resourcePatternResolver.getResources(basename);
for (int j = 0; j < resources.length; j++) {
Resource resource = resources[j];
String uri = resource.getURI().toString();
String baseName = null;
if (resource instanceof FileSystemResource) {
baseName = "classpath:" + substringBetween(uri, "/classes/", ".properties");
} else if (resource instanceof ClassPathResource) {
baseName = substringBefore(uri, ".properties");
} else if (resource instanceof UrlResource) {
baseName = "classpath:" + substringBetween(uri, ".jar!/", ".properties");
}
if (baseName != null) {
String fullName = processBasename(baseName);
baseNames.add(fullName);
}
}
} catch (IOException e) {
logger.debug("No message source files found for basename " + basename + ".");
}
}
String[] resourceBasenames = Op.onList(baseNames).toSet().toArrayOf(Types.STRING).get();
super.setBasenames(resourceBasenames);
}
}
}
String processBasename(String baseName) {
String prefix = substringBeforeLast(baseName, "/");
String name = substringAfterLast(baseName, "/");
do {
name = substringBeforeLast(name, "_");
} while (name.contains("_"));
return prefix + "/" + name;
}
}

XML Configuration

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="messageSource"
class="co.jware.spring.WildcardReloadableResourceBundleMessageSource">
<property name="basenames">
<array>
<value>classpath*:**/msg-*</value>
<value>classpath*:**/label-*</value>
</array>
</property>
</bean>
</beans>

Java Configuration

@Bean(name = "messageSource")
public ReloadableResourceBundleMessageSource resourceBundleMessageSource() {
WildcardReloadableResourceBundleMessageSource messageSource =
new WildcardReloadableResourceBundleMessageSource();
String[] baseNames = StringUtils.commaDelimitedListToStringArray("classpath*:**/label-*");
messageSource.setBasenames(baseNames);
return messageSource;
}

Spring magic: Dynamically set URI prefix for group of controllers

Overview Say you have a big Spring or Spring Boot project with a lot of controller, which you want do separate in different groups with d...