All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.holonplatform.jdbc.internal.DefaultMultiTenantDataSource Maven / Gradle / Ivy

There is a newer version: 5.7.0
Show newest version
/*
 * Copyright 2016-2017 Axioma srl.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.holonplatform.jdbc.internal;

import java.io.Closeable;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.LinkedList;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.sql.DataSource;

import org.apache.commons.lang3.exception.ExceptionUtils;

import com.holonplatform.core.internal.Logger;
import com.holonplatform.core.tenancy.TenantResolver;
import com.holonplatform.jdbc.MultiTenantDataSource;
import com.holonplatform.jdbc.TenantDataSourceProvider;

/**
 * Default {@link MultiTenantDataSource} implementation.
 * 

* An internal cache is used to store and reuse tenant DataSource instances. *

* * @since 4.3.0 */ public class DefaultMultiTenantDataSource implements MultiTenantDataSource { /** * Logger */ private static final Logger LOGGER = JdbcLogger.create(); /** * Default tenant id representing not tenant available */ private static final String NO_TENANT = DefaultMultiTenantDataSource.class.getName() + ".NO_TENANT"; /** * Tenant resolver */ private TenantResolver tenantResolver; /** * Tenant DataSource provider */ private TenantDataSourceProvider tenantDataSourceProvider; /** * Tenant DataSources cache */ protected final ConcurrentMap tenantDataSources; /** * Constructor */ public DefaultMultiTenantDataSource() { super(); this.tenantDataSources = new ConcurrentHashMap<>(); } /* * (non-Javadoc) * @see com.holonplatform.jdbc.tenancy.MultiTenantDataSource#reset() */ @Override public void reset() { tenantDataSources.clear(); } /* * (non-Javadoc) * @see com.holonplatform.jdbc.tenancy.MultiTenantDataSource#reset(java.lang.String) */ @Override public void reset(String tenantId) { tenantDataSources.remove((tenantId == null) ? NO_TENANT : tenantId); } /** * Set the {@link TenantResolver} to use to obtain the current tenant id. *

* If not setted, the default context resource {@link TenantResolver#getCurrent()} is used if available. *

* @param tenantResolver the TenantResolver to set */ public void setTenantResolver(TenantResolver tenantResolver) { this.tenantResolver = tenantResolver; } /** * Set the {@link TenantDataSourceProvider} to use to obtain configured DataSource instances according to current * tenant id. *

* If not setted, the default context resource {@link TenantDataSourceProvider#getCurrent()} is used if available. *

* @param tenantDataSourceProvider the TenantDataSourceProvider to set */ public void setTenantDataSourceProvider(TenantDataSourceProvider tenantDataSourceProvider) { this.tenantDataSourceProvider = tenantDataSourceProvider; } /** * Gets the {@link TenantResolver} to use to obtain the current tenant id. * @return the TenantResolver. If not explicitly setted, {@link TenantResolver#getCurrent()} is returned if * available */ protected Optional getTenantResolver() { return (tenantResolver != null) ? Optional.of(tenantResolver) : TenantResolver.getCurrent(); } /** * Gets the {@link TenantDataSourceProvider} to use to obtain configured DataSource instances according to current * tenant id. * @return the TenantDataSourceProvider */ protected Optional getTenantDataSourceProvider() { return (tenantDataSourceProvider != null) ? Optional.of(tenantDataSourceProvider) : TenantDataSourceProvider.getCurrent(); } /** * Determine concrete DataSource to use relying on {@link TenantResolver#getTenantId()} to resolve current tenant id * and {@link TenantDataSourceProvider#getDataSource(String)} to obtain DataSource instance to use * @return Concrete DataSource * @throws SQLException Error resolving tenant id or building DataSource */ protected DataSource determineCurrentDataSource() throws SQLException { try { // resolve current tenant id TenantResolver resolver = getTenantResolver() .orElseThrow(() -> new SQLException("Failed to resolve tenant DataSource: Missing TenantResolver")); String tenantId = resolver.getTenantId().orElse(NO_TENANT); LOGGER.debug(() -> "Try to resolve DataSource for tenant id: " + tenantId); // get the tenant DataSource provider TenantDataSourceProvider provider = getTenantDataSourceProvider().orElseThrow( () -> new SQLException("Failed to resolve tenant DataSource: Missing TenantDataSourceProvider")); // obtain the DataSource from cache or from provider DataSource dataSource = tenantDataSources.computeIfAbsent(tenantId, (id) -> provider.getDataSource(NO_TENANT.equals(id) ? null : id)); if (dataSource == null) { throw new SQLException("Failed to resolve tenant DataSource - TenantDataSourceProvider returned " + "a null DataSource for tenant id: " + tenantId); } LOGGER.debug( () -> "Resolved DataSource for tenant id: " + tenantId + " - DataSource instance: " + dataSource); return dataSource; } catch (Exception e) { throw new SQLException(e); } } /* * (non-Javadoc) * @see javax.sql.DataSource#getConnection() */ @Override public Connection getConnection() throws SQLException { return determineCurrentDataSource().getConnection(); } /* * (non-Javadoc) * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) */ @Override public Connection getConnection(String username, String password) throws SQLException { return determineCurrentDataSource().getConnection(username, password); } /* * (non-Javadoc) * @see javax.sql.CommonDataSource#getLogWriter() */ @Override public PrintWriter getLogWriter() throws SQLException { throw new SQLFeatureNotSupportedException("getLogWriter"); } /* * (non-Javadoc) * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter) */ @Override public void setLogWriter(PrintWriter out) throws SQLException { throw new SQLFeatureNotSupportedException("setLogWriter"); } /* * (non-Javadoc) * @see javax.sql.CommonDataSource#setLoginTimeout(int) */ @Override public void setLoginTimeout(int seconds) throws SQLException { throw new SQLFeatureNotSupportedException("setLoginTimeout"); } /* * (non-Javadoc) * @see javax.sql.CommonDataSource#getLoginTimeout() */ @Override public int getLoginTimeout() throws SQLException { return 0; } /* * (non-Javadoc) * @see javax.sql.CommonDataSource#getParentLogger() */ @Override public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException { return java.util.logging.Logger.getLogger(java.util.logging.Logger.GLOBAL_LOGGER_NAME); } /* * (non-Javadoc) * @see java.sql.Wrapper#unwrap(java.lang.Class) */ @SuppressWarnings("unchecked") @Override public I unwrap(Class iface) throws SQLException { if (iface.isInstance(this)) { return (I) this; } return determineCurrentDataSource().unwrap(iface); } /* * (non-Javadoc) * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) */ @Override public boolean isWrapperFor(Class iface) throws SQLException { return (iface.isInstance(this) || determineCurrentDataSource().isWrapperFor(iface)); } /* * (non-Javadoc) * @see java.io.Closeable#close() */ @Override public void close() throws IOException { if (!tenantDataSources.isEmpty()) { LinkedList exceptions = new LinkedList<>(); for (DataSource dataSource : tenantDataSources.values()) { if (dataSource instanceof Closeable) { try { ((Closeable) dataSource).close(); } catch (Exception e) { exceptions.add(e); } } } if (!exceptions.isEmpty()) { if (exceptions.size() == 1) { throw new IOException(exceptions.getFirst()); } StringBuilder sb = new StringBuilder(); sb.append("Multiple exceptions detected when closing tenant DataSources: "); for (Throwable exception : exceptions) { sb.append(ExceptionUtils.getRootCauseMessage(exception)); sb.append(";"); } throw new IOException(sb.toString()); } } } // Builder /** * Default {@link MultiTenantDataSource} builder. */ public static class DefaultBuilder implements Builder { private final DefaultMultiTenantDataSource instance = new DefaultMultiTenantDataSource(); /* * (non-Javadoc) * @see com.holonplatform.jdbc.tenancy.MultiTenantDataSource.Builder#resolver(com.holonplatform.core.tenancy. * TenantResolver) */ @Override public Builder resolver(TenantResolver resolver) { this.instance.setTenantResolver(resolver); return this; } /* * (non-Javadoc) * @see com.holonplatform.jdbc.tenancy.MultiTenantDataSource.Builder#provider(com.holonplatform.jdbc.tenancy. * TenantDataSourceProvider) */ @Override public Builder provider(TenantDataSourceProvider provider) { this.instance.setTenantDataSourceProvider(provider); return this; } /* * (non-Javadoc) * @see com.holonplatform.jdbc.tenancy.MultiTenantDataSource.Builder#build() */ @Override public MultiTenantDataSource build() { return instance; } } }