Query tables across multiple tenants (same table name) - java

I have a system where there is an unknown number of tenants (different database instances on same database server). I have working code where a user logs in and the correct tenant is selected, and I can read the configuration table for that tenant.
I want the application at start time to loop through all tenants, read the configuration and act upon it. Prior to moving to Spring Data JPA (backed by hibernate) this was easy as I was connecting to each database instance separately.
I don't think I can use Spring's #Transactional as it only sets up a single connection.
I hope to use the same repository interface with the same bean, as this works when i only need to hit one tenant at a time.
I do have a class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl that will give me a dataSource for a given tenant, but I'm not sure how to use that in a #Service class's method?

I'm not sure if I should remove my previous answer, edit it or what. So if a MOD can let me know proper procedure I'll be happy to comply.
Turns out I was right about the use of #Transactional not going to work. I ended up using an custom implementation of and AbstractRoutingDataSource to replace my MultiTenantConnectionProviderImpl and CurrentTenantResolverImpl. I use this new data source instead of setting the hibernate.multiTenancy hibernate.multi_tenant_connection_provider and hibernate.tenant_identifier_resolver
My temporary override class looks like this:
public class MultitenancyTemporaryOverride implements AutoCloseable
{
static final ThreadLocal<String> tenantOverride = new NamedThreadLocal<>("temporaryTenantOverride");
public void setCurrentTenant(String tenantId)
{
tenantOverride.set(tenantId);
}
public String getCurrentTenant()
{
return tenantOverride.get();
}
#Override
public void close() throws Exception
{
tenantOverride.remove();
}
}
My TenantRoutingDataSource looks like this:
#Component
public class TenantRoutingDataSource extends AbstractDataSource implements InitializingBean
{
#Override
public Connection getConnection() throws SQLException
{
return determineTargetDataSource().getConnection();
}
#Override
public Connection getConnection(String username, String password) throws SQLException
{
return determineTargetDataSource().getConnection(username, password);
}
#Override
public void afterPropertiesSet() throws Exception
{
}
protected String determineCurrentLookupKey()
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String database = "shared";
if (authentication != null && authentication.getPrincipal() instanceof MyUser)
{
MyUser user = (MyUser) authentication.getPrincipal();
database = user.getTenantId();
}
String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get();
if (temporaryOverride != null)
{
database = temporaryOverride;
}
return database;
}
protected DataSource determineTargetDataSource()
{
return selectDataSource(determineCurrentLookupKey());
}
public DataSource selectDataSource(String tenantIdentifier)
{
//I use C3P0 for my connection pool
PooledDataSource pds = C3P0Registry.pooledDataSourceByName(tenantIdentifier);
if (pds == null)
pds = getComboPooledDataSource(tenantIdentifier);
return pds;
}
private ComboPooledDataSource getComboPooledDataSource(String tenantIdentifier)
{
ComboPooledDataSource cpds = new ComboPooledDataSource(tenantIdentifier);
cpds.setJdbcUrl("A JDBC STRING HERE");
cpds.setUser("MyDbUsername");
cpds.setPassword("MyDbPassword");
cpds.setInitialPoolSize(10);
cpds.setMaxConnectionAge(10000);
try
{
cpds.setDriverClass("com.informix.jdbc.IfxDriver");
}
catch (PropertyVetoException e)
{
throw new RuntimeException("Weird error when setting the driver class", e);
}
return cpds;
}
}
Then i just provide my custom data source to my Entity Manager factory bean when creating it.
#Service
public class TestService
{
public void doSomeGets()
{
List<String> tenants = getListSomehow();
try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride())
{
for(String tenant : tenants)
{
tempOverride.setCurrentTenant(tenant);
//do some work here, which only applies to the tenant
}
}
catch (Exception e)
{
logger.error(e);
}
}
}

I think I'm close to one solution, but I'm not too entirely happy with it. I would love for a better answer to come up.
EDITED: turns out this doesn't quite work, as Spring or Hibernate appears to only call the current tenant identifier resolver once, not for each time a #Transactional method is called
It involves changing the CurrentTenantIdentifierResolver implementation to not only look at the current user (if it is set) to get their current tenant id (up to implementor to figure out how to set that)...it also needs to look at a thread local variable to see if an override has been set.
Using this approach, I can temporarily set the tenantID...call a service method with my multi tenancy transaction manager specified and then get the data.
My Test Service:
#Service
public class TestService
{
#Transactional(transactionManager = "sharedTxMgr")
public void doSomeGets()
{
List<String> tenants = getListSomehow();
try(MultitenancyTemporaryOverride tempOverride = new MultitenancyTemporaryOverride())
{
for(String tenant : tenants)
{
tempOverride.setCurrentTenant(tenant);
doTenantSpecificWork();
}
}
catch (Exception e)
{
logger.error(e);
}
}
#Transactional(transactionManager = "tenantSpecificTxMgr")
public void doTenantSpecificWork()
{
//do some work here, which only applies to the tenant
}
}
My class that wraps setting ThreadLocal, implementing AutoCloseable to help make sure variable is cleaned up
public class MultitenancyTemporaryOverride implements AutoCloseable
{
static final ThreadLocal<String> tenantOverride = new ThreadLocal<>();
public void setCurrentTenant(String tenantId)
{
tenantOverride.set(tenantId);
}
public String getCurrentTenant()
{
return tenantOverride.get();
}
#Override
public void close() throws Exception
{
tenantOverride.remove();
}
}
My tenant resolver implementation that uses the thread local
public class CurrentTenantIdentifierResolverImpl implements CurrentTenantIdentifierResolver
{
#Override
public String resolveCurrentTenantIdentifier()
{
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
logger.debug(ToStringBuilder.reflectionToString(authentication));
String database = "shared";
if (authentication != null && authentication.getPrincipal() instanceof MyUser)
{
MyUser user = (MyUser) authentication.getPrincipal();
database = user.getTenantId();
}
String temporaryOverride = MultitenancyTemporaryOverride.tenantOverride.get();
if(temporaryOverride != null)
{
database = temporaryOverride;
}
return database;
}

Related

Adding additional Spring Security method annotations

I'm writing a library that uses Spring Security and method security to check whether a user is licensed to perform a certain operation. This is in addition to the usual role-based security, and this is causing a problem.
The annotations look like they do in this test class:
#RestController
class TestController {
#RolesAllowed("ROLE_USER")
#Licensed("a")
public ResponseEntity<String> a() {
return ResponseEntity.ok("a");
}
#RolesAllowed("ROLE_USER")
#Licensed("b")
public ResponseEntity<String> b() {
return ResponseEntity.ok("b");
}
#RolesAllowed("ROLE_USER")
#Licensed("c")
public ResponseEntity<String> c() {
return ResponseEntity.ok("c");
}
}
Having the annotations processed seems simple enough, because you add a customMethodSecurityDataSource:
#EnableGlobalMethodSecurity(
securedEnabled = true,
jsr250Enabled = true,
prePostEnabled = true
)
#Configuration
public class LicenceSecurityConfiguration extends GlobalMethodSecurityConfiguration {
#Override protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new LicensedAnnotationSecurityMetadataSource();
}
// more configurations
}
But the problem is in Spring's implementation:
#Override
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
DefaultCacheKey cacheKey = new DefaultCacheKey(method, targetClass);
synchronized (this.attributeCache) {
Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey);
// Check for canonical value indicating there is no config attribute,
if (cached != null) {
return cached;
}
// No cached value, so query the sources to find a result
Collection<ConfigAttribute> attributes = null;
for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) {
attributes = s.getAttributes(method, targetClass);
if (attributes != null && !attributes.isEmpty()) {
break;
}
}
// Put it in the cache.
if (attributes == null || attributes.isEmpty()) {
this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE);
return NULL_CONFIG_ATTRIBUTE;
}
this.logger.debug(LogMessage.format("Caching method [%s] with attributes %s", cacheKey, attributes));
this.attributeCache.put(cacheKey, attributes);
return attributes;
}
My custom metadata source is processed first, and as soon as it finds an annotation that it recognises, it stops processing. Specifically, in this if-block:
if (attributes != null && !attributes.isEmpty()) {
break;
}
The result is that my LicenceDecisionVoter votes to abstain; after all, there could be other annotation processors that check roles. And because there are no more attributes to vote upon, only ACCESS_ABSTAIN is returned, and as per Spring's default and recommended configuration, access is denied. The roles are never checked.
Do I have an alternative, other than to implement scanning for Spring's own annotation processors, like the #Secured and JSR-250 annotations?
Or was the mistake to use Spring Security in the first place for this specific purpose?
As promised, the solution. It was more work than I imagined, and the code may have issues because it is partly copied from Spring, and some of that code looks dodgy (or at least, IntelliJ thinks it does).
The key is to remove the GlobalMethodSecurityConfiguration. Leave that to the application itself. The (auto) configuration class looks like the following:
#EnableConfigurationProperties(LicenceProperties.class)
#Configuration
#Import(LicensedMetadataSourceAdvisorRegistrar.class)
public class LicenceAutoConfiguration {
#Bean public <T extends Licence> LicenceChecker<T> licenceChecker(
#Lazy #Autowired final LicenceProperties properties,
#Lazy #Autowired final LicenceFactory<T> factory
) throws InvalidSignatureException, LicenceExpiredException, WrappedApiException,
IOException, ParseException, InvalidKeySpecException {
final LicenceLoader loader = new LicenceLoader(factory.getPublicKey());
final T licence = loader.load(properties.getLicenceFile(), factory.getType());
return factory.getChecker(licence);
}
#Bean MethodSecurityInterceptor licenceSecurityInterceptor(
final LicensedMetadataSource metadataSource,
final LicenceChecker<?> licenceChecker
) {
final MethodSecurityInterceptor interceptor = new MethodSecurityInterceptor();
interceptor.setAccessDecisionManager(decisionManager(licenceChecker));
interceptor.setSecurityMetadataSource(metadataSource);
return interceptor;
}
#Bean LicenceAccessDecisionManager decisionManager(#Autowired final LicenceChecker<?> licenceChecker) {
return new LicenceAccessDecisionManager(licenceChecker);
}
#Bean LicensedMetadataSource licensedMetadataSource() {
return new LicensedMetadataSource();
}
}
The registrar:
public class LicensedMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {
#Override
public void registerBeanDefinitions(final AnnotationMetadata importingClassMetadata,
final BeanDefinitionRegistry registry) {
final BeanDefinitionBuilder advisor = BeanDefinitionBuilder
.rootBeanDefinition(LicensedMetadataSourceAdvisor.class);
advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
advisor.addConstructorArgReference("licensedMetadataSource");
registry.registerBeanDefinition("licensedMetadataSourceAdvisor", advisor.getBeanDefinition());
}
}
And finally, the advisor:
public class LicensedMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private final LicenceMetadataSourcePointcut pointcut = new LicenceMetadataSourcePointcut();
private transient LicensedMetadataSource attributeSource;
private transient BeanFactory beanFactory;
private transient MethodInterceptor interceptor;
private transient volatile Object adviceMonitor = new Object();
public LicensedMetadataSourceAdvisor(final LicensedMetadataSource attributeSource) {
this.attributeSource = attributeSource;
}
#Override public void setBeanFactory(final BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
#Override public Pointcut getPointcut() {
return pointcut;
}
#Override public Advice getAdvice() {
synchronized (this.adviceMonitor) {
if (this.interceptor == null) {
Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve 'adviceBeanName'");
this.interceptor = this.beanFactory.getBean("licenceSecurityInterceptor", MethodInterceptor.class);
}
return this.interceptor;
}
}
class LicenceMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {
#Override public boolean matches(final Method method, final Class<?> targetClass) {
final LicensedMetadataSource source = LicensedMetadataSourceAdvisor.this.attributeSource;
final Collection<ConfigAttribute> attributes = source.getAttributes(method, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
}
The latter two classes are copied and modified from Spring. The advisor was copied from MethodSecurityMetadataSourceAdvisor, and that's a class that somebody at Spring might have a look at, because of the transient volatile synchronisation object (which I copied, because I can't yet establish if it should be final instead), and because it has a private method that is never used.

Add #PreAuthorize role for spring schedule job

In my Backend, I have added #PreAuthorize("hasRole('ROLE_ADMIN') to allow user to access the functions in service layer. And now I would like to use my schedule job (springframework scheduling) to access these service but obviously it can not. My question is how can I add ROLE_ADMIN role or generate a user principal for the schedule job?
#PreAuthorize("hasRole('ROLE_ADMIN')")
JsonNode loadSMS(String additionalPath) {
.....
}
Either have another method not annotated with the #PreAuthorize which your scheduler calls. Move the implementation into this new method, and change the existing loadSMS to use this new method to reduce code duplication. Otherwise you could add a role at runtime, but I don't think that is such a great idea.
You can try below code
#Service
class SchedulerService {
#Autowired
private YourService service;
#Scheduled(fixedRate = 600000L, initialDelay = 60000L)
public void executeTask() throws IOException {
RunAs.runAsAdmin(() -> {
service.loadSMS(String additionalPath) {
});
}
}
public class RunAs {
#FunctionalInterface
public interface RunAsMethod {
default void run() {
try {
runWithException();
} catch (Exception e) {
}
}
void runWithException() throws Exception;
}
public static void runAsAdmin(final RunAsMethod func) {
final AnonymousAuthenticationToken token = new AnonymousAuthenticationToken("adminUser", "adminPassword",
ImmutableList.of(new SimpleGrantedAuthority("ROLE_ADMIN")));
final Authentication originalAuthentication = SecurityContextHolder.getContext().getAuthentication();
SecurityContextHolder.getContext().setAuthentication(token);
func.run();
SecurityContextHolder.getContext().setAuthentication(originalAuthentication);
}
}

Accessing UserDetails from Filter (Spring)

Specification
A filter that can call loadUserFromUsername() via userDetailsService in order to retrieve the tenant DB details from the custom UserDetails instance.
Problem
Regardless of what the filter precedence is set to, this custom filter runs before the security filter, and so the spring security context is unpopulated or null. I've confirmed that this context is populated when I access the principal object from a controller.
Attempts
I've set the spring security order in application.properties to 5, and when registering this filter I've used larger and smaller values, but it always runs before.
I'm aware that the generic filter bean should allow me to set it to come after in security configuration, but I don't know how to move the configuration and filter into one generic filter bean.
TenantFilter.java
#Component
public class TenantFilter implements Filter {
#Autowired
private TenantStore tenantStore;
#Autowired
private UserService userService;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
User user = null;
try {
user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
} catch (UsernameNotFoundException ignored) {}
String tenantId = user != null ? user.getSchool().getCode() : "";
try {
this.tenantStore.setTenantId(tenantId);
chain.doFilter(servletRequest, servletResponse);
} finally {
// Otherwise when a previously used container thread is used, it will have the old tenant id set and
// if for some reason this filter is skipped, tenantStore will hold an unreliable value
this.tenantStore.clear();
}
}
#Override
public void destroy() {
}
}
TenantFilterConfig.java
#Configuration
public class TenantFilterConfig {
#Bean
public Filter tenantFilter() {
return new TenantFilter();
}
#Bean
public FilterRegistrationBean tenantFilterRegistration() {
FilterRegistrationBean result = new FilterRegistrationBean();
result.setFilter(this.tenantFilter());
result.setUrlPatterns(Lists.newArrayList("/*"));
result.setName("Tenant Store Filter");
result.setOrder(Ordered.LOWEST_PRECEDENCE-1);
return result;
}
#Bean(destroyMethod = "destroy")
public ThreadLocalTargetSource threadLocalTenantStore() {
ThreadLocalTargetSource result = new ThreadLocalTargetSource();
result.setTargetBeanName("tenantStore");
return result;
}
#Primary
#Bean(name = "proxiedThreadLocalTargetSource")
public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
ProxyFactoryBean result = new ProxyFactoryBean();
result.setTargetSource(threadLocalTargetSource);
return result;
}
#Bean(name = "tenantStore")
#Scope(scopeName = "prototype")
public TenantStore tenantStore() {
return new TenantStore();
}
}
Found a different way that works real nicely: Aspects!
The pointcut expression used means that this runs around all method calls from all classes within the controllers package in that project.
The tenant store is based of a safer usage of threadlocal to avoid memory leaks, as this way it is always cleared (due to the finally block)
Happy coding!
TenantAspect.java
#Component
#Aspect
public class TenantAspect {
private final
TenantStore tenantStore;
#Autowired
public TenantAspect(TenantStore tenantStore) {
this.tenantStore = tenantStore;
}
#Around(value = "execution(* com.things.stuff.controller..*(..))")
public Object assignForController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
return assignTenant(proceedingJoinPoint);
}
private Object assignTenant(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
try {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
if (user != null) tenantStore.setTenantId(user.getSchool().getCode());
} finally {
Object retVal;
retVal = proceedingJoinPoint.proceed();
tenantStore.clear();
return retVal;
}
}
}

Firebase multi-tenancy with play framework depends on header value of HTTP request

I have a play framework project that provides APIs that shared between multiple front-ends, currently, I'm working on single front-end but I want to create a multi-tenant backend, each front-end got its own Firebase account.
My problem that I have to consider which firebase project to access depends on the request header value, that came with different values depends on the front end.
What I have now:
FirebaseAppProvider.java:
public class FirebaseAppProvider implements Provider<FirebaseApp> {
private final Logger.ALogger logger;
private final Environment environment;
private final Configuration configuration;
#Inject
public FirebaseAppProvider(Environment environment, Configuration configuration) {
this.logger = Logger.of(this.getClass());
this.environment = environment;
this.configuration = configuration;
}
#Singleton
#Override
public FirebaseApp get() {
HashMap<String, String> firebaseProjects = (HashMap<String, String>) configuration.getObject("firebase");
firebaseProjects.forEach((websiteId, projectId) -> {
FileInputStream serviceAccount = null;
try {
serviceAccount = new FileInputStream(environment.classLoader().getResource(String.format("firebase/%s.json", projectId)).getPath());
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
FirebaseOptions options = new FirebaseOptions.Builder().setCredential(FirebaseCredentials.fromCertificate(serviceAccount))
.setDatabaseUrl(String.format("https://%s.firebaseio.com/", projectId))
.build();
FirebaseApp firebaseApp = FirebaseApp.initializeApp(options, projectId);
logger.info("FirebaseApp initialized");
});
return FirebaseApp.getInstance();
}
}
Also for Database:
FirebaseDatabaseProvider.java
public class FirebaseDatabaseProvider implements Provider<FirebaseDatabase> {
private final FirebaseApp firebaseApp;
public static List<TaxItem> TAXES = new ArrayList<>();
#Inject
public FirebaseDatabaseProvider(FirebaseApp firebaseApp) {
this.firebaseApp = firebaseApp;
fetchTaxes();
}
#Singleton
#Override
public FirebaseDatabase get() {
return FirebaseDatabase.getInstance(firebaseApp);
}
#Singleton
public DatabaseReference getUserDataReference() {
return this.get().getReference("/usersData");
}
#Singleton
public DatabaseReference getTaxesConfigurationReference() {
return this.get().getReference("/appData/taxConfiguration");
}
private void fetchTaxes() {
DatabaseReference bundlesRef = getTaxesConfigurationReference().child("taxes");
bundlesRef.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
TAXES.clear();
dataSnapshot.getChildren().forEach(tax -> TAXES.add(tax.getValue(TaxItem.class)));
Logger.info(String.format("==> %d taxes records loaded", TAXES.size()));
}
#Override
public void onCancelled(DatabaseError databaseError) {
Logger.warn("The read failed: " + databaseError.getCode());
}
});
}
}
So I bind them as well from Module.java:
public class Module extends AbstractModule {
#Override
public void configure() { bind(FirebaseApp.class).toProvider(FirebaseAppProvider.class).asEagerSingleton();
bind(FirebaseAuth.class).toProvider(FirebaseAuthProvider.class).asEagerSingleton();
bind(FirebaseDatabase.class).toProvider(FirebaseDatabaseProvider.class).asEagerSingleton();
}
}
my ActionCreator:
public class ActionCreator implements play.http.ActionCreator {
#Inject
public ActionCreator() {
}
#Override
public Action createAction(Http.Request request, Method actionMethod) {
switchTenancyId(request);
return new Action.Simple() {
#Override
public CompletionStage<Result> call(Http.Context ctx) {
return delegate.call(ctx);
}
};
}
private void switchTenancyId(Http.RequestHeader request) {
// DO something here
}
private Optional<String> getTenancyId(Http.RequestHeader request) {
String websiteId = request.getHeader("Website-ID");
System.out.println(websiteId);
return null;
}
}
What I want is when I use Database service, or auth service, I read the website id and decide which firebase project to access, I really tried the solution like this answer here:
Multi tenancy with Guice Custom Scopes and Jersey
Please note I'm willing to use differents projects, not the same firebase project for each front-end.
But kinda lost, especially the request can be only accessed from controller or ActionCreator, so what I got from the question above is load providers by key into ThreadLocal and switch them for each request depends on the annotation, but I was unable to do this because of the lack of knowledge.
The minimized version of my project can be found here: https://github.com/almothafar/play-with-multi-tenant-firebase
Also, I uploaded taxes-data-export.json file to import inside firebase project for a test.
Right, so I know Play a lot better than FireBase, but it seems to me you want to extract a tenancy ID from the request prior to feeding this into your FrieBase backend? Context when writing Java in play is Thread local, but even when doing things async you can make sure the Http.context info goes along for the ride by injecting the execution context. I would not do this via the action creator, unless you want to intercept which action is called. (Though I have a hackish solution for that as well.)
So, after a comment I'll try to elucidate here, your incoming request will be routed to a controller, like below (let me know if you need clearing up on routing etc):
Below is a solution for caching a retrieved FireBaseApp based on a "Website-ID" retrieved from the request, though I would likely put the tenancyId in the session.
import javax.inject.Inject;
import java.util.concurrent.CompletionStage;
public class MyController extends Controller {
private HttpExecutionContext ec; //This is the execution-context.
private FirebaseAppProvider appProvider;
private CacheApi cache;
#Inject
public MyController(HttpExecutionContext ec, FireBaseAppProvider provider,CacheApi cache) {
this.ec = ec;
this.appProvider = provider;
this.cache = cache;
}
/**
*Retrieves a website-id from request and attempts to retrieve
*FireBaseApp object from Cache.
*If not found a new FireBaseApp will be constructed by
*FireBaseAppProvider and cached.
**/
private FireBaseApp getFireBaseApp(){
String tenancyId = request.getHeader("Website-ID);
FireBaseApp app = (FireBaseApp)cache.get(tenancyId);
if(app==null){
app=appProvider.get();
cache.put(tenancyId,app);
}
return app;
}
public CompletionStage<Result> index() {
return CompletableFuture.supplyAsync(() -> {
FireBaseApp app = getFireBaseApp();
//Do things with app.
}, ec.current()); //Used here.
}
}
Now in FireBaseAppProvider you can access the header via play.mvc.Controller, the only thing you need is to provide the HttpExecutionContext via ec.current. So (once again, I'm avoiding anything FireBase specific), in FireBaseProvider:
import play.mvc.Controller;
public class FireBaseAppProvider {
public String getWebsiteKey(){
String website = Controller.request().getHeader("Website-ID");
//If you need to handle a missing header etc, do it here.
return website;
}
public FireBaseApp get(){
String tenancyId = getWebsiteKey();
//Code to do actual construction here.
}
}
Let me know if this is close to what you're asking and I'll clean it up for you.
Also, if you want to store token validations etc, it's best to put them in the "session" of the return request, this is signed by Play Framework and allows storing data over requests. For larger data you can cache this using the session-id as part of the key.
I believe Custom Scopes for this is overkill. I would recommend doing the Request-Scoped seeding from Guice's own wiki. In your case that would be something like
public class TenancyFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String tenancyId = httpRequest.getHeader("YOUR-TENANCY-ID-HEADER-NAME");
httpRequest.setAttribute(
Key.get(String.class, Names.named("tenancyId")).toString(),
userId
);
chain.doFilter(request, response);
}
#Override
public void init(FilterConfig filterConfig) throws ServletException { }
#Override
public void destroy() { }
};
It has to be bound in a ServletModule
public class YourModule extends ServletModule {
#Override
protected void configureServlets() {
filter("/*").through(TenancyFilter.class);
}
#Provides
#RequestScoped
#Named("tenancyId")
String provideTenancyId() {
throw new IllegalStateException("user id must be manually seeded");
}
}
Then anywhere you need to get the Tenancy ID you just inject
public class SomeClass {
private final Provider<String> tenancyIdProvider;
#Inject
SomeClass(#Named("tenancyId") Provider<String> tenancyIdProvider) {
this.tenancyIdProvider = tenancyIdProvider;
}
// Methods in request call tenancyIdProvider.get() to get and take action based on Tenancy ID.
}

Guice, JDBC and managing database connections

I'm looking to create a sample project while learning Guice which uses JDBC to read/write to a SQL database. However, after years of using Spring and letting it abstract away connection handling and transactions I'm struggling to work it our conceptually.
I'd like to have a service which starts and stops a transaction and calls numerous repositories which reuse the same connection and participate in the same transaction. My questions are:
Where do I create my Datasource?
How do I give the repositories access to the connection? (ThreadLocal?)
Best way to manage the transaction (Creating an Interceptor for an annotation?)
The code below shows how I would do this in Spring. The JdbcOperations injected into each repository would have access to the connection associated with the active transaction.
I haven't been able to find many tutorials which cover this, beyond ones which show creating interceptors for transactions.
I am happy with continuing to use Spring as it is working very well in my projects, but I'd like to know how to do this in pure Guice and JBBC (No JPA/Hibernate/Warp/Reusing Spring)
#Service
public class MyService implements MyInterface {
#Autowired
private RepositoryA repositoryA;
#Autowired
private RepositoryB repositoryB;
#Autowired
private RepositoryC repositoryC;
#Override
#Transactional
public void doSomeWork() {
this.repositoryA.someInsert();
this.repositoryB.someUpdate();
this.repositoryC.someSelect();
}
}
#Repository
public class MyRepositoryA implements RepositoryA {
#Autowired
private JdbcOperations jdbcOperations;
#Override
public void someInsert() {
//use jdbcOperations to perform an insert
}
}
#Repository
public class MyRepositoryB implements RepositoryB {
#Autowired
private JdbcOperations jdbcOperations;
#Override
public void someUpdate() {
//use jdbcOperations to perform an update
}
}
#Repository
public class MyRepositoryC implements RepositoryC {
#Autowired
private JdbcOperations jdbcOperations;
#Override
public String someSelect() {
//use jdbcOperations to perform a select and use a RowMapper to produce results
return "select result";
}
}
If your database change infrequently, you could use the data source that comes with the database's JDBC driver and isolate the calls to the 3rd party library in a provider (My example uses the one provided by the H2 dataabse, but all JDBC providers should have one). If you change to a different implementation of the DataSource (e.g. c3PO, Apache DBCP, or one provided by app server container) you can simply write a new Provider implementation to get the datasource from the appropriate place. Here I've use singleton scope to allow the DataSource instance to be shared amongst those classes that depend on it (necessary for pooling).
public class DataSourceModule extends AbstractModule {
#Override
protected void configure() {
Names.bindProperties(binder(), loadProperties());
bind(DataSource.class).toProvider(H2DataSourceProvider.class).in(Scopes.SINGLETON);
bind(MyService.class);
}
static class H2DataSourceProvider implements Provider<DataSource> {
private final String url;
private final String username;
private final String password;
public H2DataSourceProvider(#Named("url") final String url,
#Named("username") final String username,
#Named("password") final String password) {
this.url = url;
this.username = username;
this.password = password;
}
#Override
public DataSource get() {
final JdbcDataSource dataSource = new JdbcDataSource();
dataSource.setURL(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
}
static class MyService {
private final DataSource dataSource;
#Inject
public MyService(final DataSource dataSource) {
this.dataSource = dataSource;
}
public void singleUnitOfWork() {
Connection cn = null;
try {
cn = dataSource.getConnection();
// Use the connection
} finally {
try {
cn.close();
} catch (Exception e) {}
}
}
}
private Properties loadProperties() {
// Load properties from appropriate place...
// should contain definitions for:
// url=...
// username=...
// password=...
return new Properties();
}
}
To handle transactions a Transaction Aware data source should be used. I wouldn't recommend implementing this manually. Using something like warp-persist or a container supplied transaction management, however it would look something like this:
public class TxModule extends AbstractModule {
#Override
protected void configure() {
Names.bindProperties(binder(), loadProperties());
final TransactionManager tm = getTransactionManager();
bind(DataSource.class).annotatedWith(Real.class).toProvider(H2DataSourceProvider.class).in(Scopes.SINGLETON);
bind(DataSource.class).annotatedWith(TxAware.class).to(TxAwareDataSource.class).in(Scopes.SINGLETON);
bind(TransactionManager.class).toInstance(tm);
bindInterceptor(Matchers.any(), Matchers.annotatedWith(Transactional.class), new TxMethodInterceptor(tm));
bind(MyService.class);
}
private TransactionManager getTransactionManager() {
// Get the transaction manager
return null;
}
static class TxMethodInterceptor implements MethodInterceptor {
private final TransactionManager tm;
public TxMethodInterceptor(final TransactionManager tm) {
this.tm = tm;
}
#Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
// Start tx if necessary
return invocation.proceed();
// Commit tx if started here.
}
}
static class TxAwareDataSource implements DataSource {
static ThreadLocal<Connection> txConnection = new ThreadLocal<Connection>();
private final DataSource ds;
private final TransactionManager tm;
#Inject
public TxAwareDataSource(#Real final DataSource ds, final TransactionManager tm) {
this.ds = ds;
this.tm = tm;
}
public Connection getConnection() throws SQLException {
try {
final Transaction transaction = tm.getTransaction();
if (transaction != null && transaction.getStatus() == Status.STATUS_ACTIVE) {
Connection cn = txConnection.get();
if (cn == null) {
cn = new TxAwareConnection(ds.getConnection());
txConnection.set(cn);
}
return cn;
} else {
return ds.getConnection();
}
} catch (final SystemException e) {
throw new SQLException(e);
}
}
// Omitted delegate methods.
}
static class TxAwareConnection implements Connection {
private final Connection cn;
public TxAwareConnection(final Connection cn) {
this.cn = cn;
}
public void close() throws SQLException {
try {
cn.close();
} finally {
TxAwareDataSource.txConnection.set(null);
}
}
// Omitted delegate methods.
}
static class MyService {
private final DataSource dataSource;
#Inject
public MyService(#TxAware final DataSource dataSource) {
this.dataSource = dataSource;
}
#Transactional
public void singleUnitOfWork() {
Connection cn = null;
try {
cn = dataSource.getConnection();
// Use the connection
} catch (final SQLException e) {
throw new RuntimeException(e);
} finally {
try {
cn.close();
} catch (final Exception e) {}
}
}
}
}
I would use something like c3po to create datasources directly. If you use ComboPooledDataSource you only need instance (pooling is done under the covers), which you can bind directly or through a provider.
Then I'd create an interceptor on top of that, one that e.g. picks up #Transactional, manages a connection and commit/ rollback. You could make Connection injectable as well, but you need to make sure you close the connections somewhere to allow them to be checked into the pool again.
To inject a data source, you probably don't need to be bound to a single data source instance since the database you are connecting to features in the url. Using Guice, it is possible to force programmers to provide a binding to a DataSource implementation (link) . This data source can be injected into a ConnectionProvider to return a data source.
The connection has to be in a thread local scope. You can even implement your thread local scope but all thread local connections must be closed & removed from ThreadLocal object after commit or rollback operations to prevent memory leakage. After hacking around, I have found that you need to have a hook to the Injector object to remove ThreadLocal elements. An injector can easily be injected into your Guice AOP interceptor, some thing like this:
protected void visitThreadLocalScope(Injector injector,
DefaultBindingScopingVisitor visitor) {
if (injector == null) {
return;
}
for (Map.Entry, Binding> entry :
injector.getBindings().entrySet()) {
final Binding binding = entry.getValue();
// Not interested in the return value as yet.
binding.acceptScopingVisitor(visitor);
}
}
/**
* Default implementation that exits the thread local scope. This is
* essential to clean up and prevent any memory leakage.
*
* The scope is only visited iff the scope is an sub class of or is an
* instance of {#link ThreadLocalScope}.
*/
private static final class ExitingThreadLocalScopeVisitor
extends DefaultBindingScopingVisitor {
#Override
public Void visitScope(Scope scope) {
// ThreadLocalScope is the custom scope.
if (ThreadLocalScope.class.isAssignableFrom(scope.getClass())) {
ThreadLocalScope threadLocalScope = (ThreadLocalScope) scope;
threadLocalScope.exit();
}
return null;
}
}
Make sure you call this after the method has been invoked and closing the connection. Try this to see if this works.
Please check the solution I provided: Transactions with Guice and JDBC - Solution discussion
it is just a very basic version and simple approach. but it works just fine to handle transactions with Guice and JDBC.

Categories

Resources