Skip to content

Commit

Permalink
HHH-6054 - Support for discriminator-based multi-tenancy
Browse files Browse the repository at this point in the history
  • Loading branch information
sebersole committed May 16, 2012
1 parent 556ca03 commit 93882f4
Show file tree
Hide file tree
Showing 19 changed files with 538 additions and 54 deletions.
Expand Up @@ -53,21 +53,25 @@ public enum MultiTenancyStrategy {
* No multi-tenancy
*/
NONE;

private static final CoreMessageLogger LOG = Logger.getMessageLogger(
CoreMessageLogger.class,
MultiTenancyStrategy.class.getName()
);
public static MultiTenancyStrategy determineMultiTenancyStrategy(Map properties) {
final Object strategy = properties.get( Environment.MULTI_TENANT );
if ( strategy == null ) {

public static MultiTenancyStrategy fromConfigValue(Object value) {
if ( value == null ) {
return MultiTenancyStrategy.NONE;
}

if ( MultiTenancyStrategy.class.isInstance( strategy ) ) {
return (MultiTenancyStrategy) strategy;
if ( MultiTenancyStrategy.class.isInstance( value ) ) {
return (MultiTenancyStrategy) value;
}

final String strategyName = strategy.toString();
return fromExternalName( value.toString() );
}

private static MultiTenancyStrategy fromExternalName(String strategyName) {
try {
return MultiTenancyStrategy.valueOf( strategyName.toUpperCase() );
}
Expand All @@ -76,4 +80,8 @@ public static MultiTenancyStrategy determineMultiTenancyStrategy(Map properties)
return MultiTenancyStrategy.NONE;
}
}

public static MultiTenancyStrategy determineMultiTenancyStrategy(Map properties) {
return fromConfigValue( properties.get( Environment.MULTI_TENANT ) );
}
}
@@ -0,0 +1,54 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.annotations;

import java.lang.annotation.Retention;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
* Annotation used to indicate that an entity represents shared (non tenant aware) data in a multi-tenant
* application.
*
* Valid only at the root of an inheritance hierarchy.
*
* @author Steve Ebersole
*/
@java.lang.annotation.Target(TYPE)
@Retention(RUNTIME)
public @interface MultiTenancy {
public boolean shared() default true;

/**
* The discriminator values can be either be handled as literals or handled through JDBC parameter binding.
* {@code true} here (the default) indicates that the parameter binding approach should be used; {@code false}
* indicates the value should be handled as a literal.
* <p/>
* Care should be used specifying to use literals here. PreparedStatements will not be able to be reused
* nearly as often by the database/driver which can potentially cause a significant performance impact to your
* application.
*/
public boolean useParameterBinding() default true;
}
Expand Up @@ -31,17 +31,13 @@
/**
* Describes the column to use as the multi-tenancy discriminator value for the entity.
*
* NOTE : For sure this will go away and get replaced with the JPA standard one..
*
* @todo better (shorter) name? TenantDiscriminatorColumn? TenantColumn? Similar for formula too
*
* @author Steve Ebersole
*/
@java.lang.annotation.Target( TYPE )
@Retention( RUNTIME )
public @interface MultiTenancyDiscriminatorColumn {
public @interface TenantColumn {
/**
* Names of the column to use.
* Name of the column to use.
*/
public String name();

Expand All @@ -51,17 +47,6 @@
*/
public String table() default "";

/**
* The discriminator values can be either be handled as literals or handled through JDBC parameter binding.
* {@code true} here (the default) indicates that the parameter binding approach should be used; {@code false}
* indicates the value should be handled as a literal.
* <p/>
* Care should be used specifying to use literals here. PreparedStatements will not be able to be reused
* nearly as often by the database/driver which can potentially cause a significant performance impact to your
* application.
*/
public boolean useParameterBinding() default true;

/**
* Names the Hibernate mapping type to use for mapping values to/from the specified column. Defaults to
* {@code "string"} which is a {@link String}/{@link java.sql.Types#VARCHAR VARCHAR} mapping.
Expand Down
Expand Up @@ -36,7 +36,7 @@
*/
@java.lang.annotation.Target(TYPE)
@Retention(RUNTIME)
public @interface MultiTenancyDiscriminatorFormula {
public @interface TenantFormula {
/**
* The formula fragment.
*/
Expand Down
Expand Up @@ -28,6 +28,7 @@

import org.xml.sax.EntityResolver;

import org.hibernate.MultiTenancyStrategy;
import org.hibernate.SessionFactory;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.NamingStrategy;
Expand Down Expand Up @@ -58,6 +59,7 @@ public static interface Options {
public boolean isGloballyQuotedIdentifiers();
public String getDefaultSchemaName();
public String getDefaultCatalogName();
public MultiTenancyStrategy getMultiTenancyStrategy();
}

/**
Expand Down
Expand Up @@ -36,6 +36,7 @@

import org.hibernate.AssertionFailure;
import org.hibernate.EntityMode;
import org.hibernate.MultiTenancyStrategy;
import org.hibernate.TruthValue;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.NamingStrategy;
Expand Down Expand Up @@ -118,6 +119,7 @@
import org.hibernate.metamodel.spi.source.MetaAttributeContext;
import org.hibernate.metamodel.spi.source.MetaAttributeSource;
import org.hibernate.metamodel.spi.source.MetadataImplementor;
import org.hibernate.metamodel.spi.source.MultiTenancySource;
import org.hibernate.metamodel.spi.source.NonAggregatedCompositeIdentifierSource;
import org.hibernate.metamodel.spi.source.Orderable;
import org.hibernate.metamodel.spi.source.PluralAttributeIndexSource;
Expand Down Expand Up @@ -554,7 +556,8 @@ private AbstractPluralAttributeBinding bindSetAttribute(
propertyAccessorName( attributeSource ),
attributeSource.isIncludedInOptimisticLocking(),
false,
createMetaAttributeContext( attributeBindingContainer, attributeSource ) );
createMetaAttributeContext( attributeBindingContainer, attributeSource )
);
}

private AbstractPluralAttributeBinding bindListAttribute(
Expand All @@ -572,7 +575,8 @@ private AbstractPluralAttributeBinding bindListAttribute(
attributeSource.isIncludedInOptimisticLocking(),
false,
createMetaAttributeContext( attributeBindingContainer, attributeSource ),
attributeSource.getIndexSource().base() );
attributeSource.getIndexSource().base()
);
}

private AbstractPluralAttributeBinding bindMapAttribute(
Expand All @@ -589,7 +593,8 @@ private AbstractPluralAttributeBinding bindMapAttribute(
propertyAccessorName( attributeSource ),
attributeSource.isIncludedInOptimisticLocking(),
false,
createMetaAttributeContext( attributeBindingContainer, attributeSource ) );
createMetaAttributeContext( attributeBindingContainer, attributeSource )
);
}

private void bindBasicCollectionElement(
Expand Down Expand Up @@ -659,7 +664,7 @@ private void bindCollectionIndex(
defaultIndexJavaTypeName );
Type resolvedElementType = heuristicType( indexBinding.getHibernateTypeDescriptor() );
bindHibernateResolvedType( indexBinding.getHibernateTypeDescriptor(), resolvedElementType );
bindJdbcDataType( resolvedElementType, ( AbstractValue ) indexBinding.getIndexRelationalValue() );
bindJdbcDataType( resolvedElementType, (AbstractValue) indexBinding.getIndexRelationalValue() );
}

private void bindCollectionKey(
Expand Down Expand Up @@ -815,6 +820,51 @@ private void bindDiscriminator( final EntityBinding rootEntityBinding, final Roo
bindJdbcDataType( resolvedType, value );
}

private void bindMultiTenancy(EntityBinding rootEntityBinding, RootEntitySource rootEntitySource) {
final MultiTenancySource multiTenancySource = rootEntitySource.getMultiTenancySource();
if ( multiTenancySource == null ) {
return;
}

// if (1) the strategy is discriminator based and (2) the entity is not shared, we need to either (a) extract
// the user supplied tenant discriminator value mapping or (b) generate an implicit one
final boolean needsTenantIdentifierValueMapping =
MultiTenancyStrategy.DISCRIMINATOR == metadata.getOptions().getMultiTenancyStrategy()
&& ! multiTenancySource.isShared();

if ( needsTenantIdentifierValueMapping ) {
// NOTE : the table for tenant identifier/discriminator is always the primary table
final Value tenantDiscriminatorValue;
final RelationalValueSource valueSource = multiTenancySource.getRelationalValueSource();
if ( valueSource == null ) {
// user supplied no explicit information, so use implicit mapping with default name
tenantDiscriminatorValue = rootEntityBinding.getPrimaryTable().locateOrCreateColumn( "tenant_id" );
}
else {
tenantDiscriminatorValue = buildRelationValue( valueSource, rootEntityBinding.getPrimaryTable() );
}
rootEntityBinding.getHierarchyDetails().getTenantDiscrimination().setDiscriminatorValue( tenantDiscriminatorValue );
}

rootEntityBinding.getHierarchyDetails().getTenantDiscrimination().setShared( multiTenancySource.isShared() );
rootEntityBinding.getHierarchyDetails().getTenantDiscrimination().setUseParameterBinding( multiTenancySource.bindAsParameter() );
}

private Value buildRelationValue(RelationalValueSource valueSource, TableSpecification table) {
if ( valueSource instanceof ColumnSource ) {
return createColumn(
table,
( ColumnSource ) valueSource,
bindingContexts.peek().getMappingDefaults().getDiscriminatorColumnName(),
false,
false
);
}
else {
return table.locateOrCreateDerivedValue( ( ( DerivedValueSource ) valueSource ).getExpression() );
}
}

private EntityBinding bindEntities( final EntityHierarchy entityHierarchy ) {
final RootEntitySource rootEntitySource = entityHierarchy.getRootEntitySource();
// Return existing binding if available
Expand All @@ -835,6 +885,7 @@ private EntityBinding bindEntities( final EntityHierarchy entityHierarchy ) {
bindVersion( rootEntityBinding, rootEntitySource.getVersioningAttributeSource() );
bindDiscriminator( rootEntityBinding, rootEntitySource );
createIdentifierGenerator( rootEntityBinding );
bindMultiTenancy( rootEntityBinding, rootEntitySource );
rootEntityBinding.getHierarchyDetails().setCaching( rootEntitySource.getCaching() );
rootEntityBinding.getHierarchyDetails().setExplicitPolymorphism( rootEntitySource.isExplicitPolymorphism() );
rootEntityBinding.getHierarchyDetails().setOptimisticLockStyle( rootEntitySource.getOptimisticLockStyle() );
Expand Down
Expand Up @@ -27,6 +27,7 @@

import org.xml.sax.EntityResolver;

import org.hibernate.MultiTenancyStrategy;
import org.hibernate.cache.spi.access.AccessType;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.EJB3DTDEntityResolver;
Expand All @@ -38,6 +39,7 @@
import org.hibernate.metamodel.MetadataSources;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.config.spi.ConfigurationService;
import org.hibernate.service.config.spi.StandardConverters;

/**
* @author Steve Ebersole
Expand Down Expand Up @@ -104,6 +106,7 @@ public static class OptionsImpl implements Metadata.Options {
private boolean globallyQuotedIdentifiers;
private String defaultSchemaName;
private String defaultCatalogName;
private MultiTenancyStrategy multiTenancyStrategy;

public OptionsImpl(ServiceRegistry serviceRegistry) {
ConfigurationService configService = serviceRegistry.getService( ConfigurationService.class );
Expand All @@ -121,47 +124,38 @@ public AccessType convert(Object value) {

useNewIdentifierGenerators = configService.getSetting(
AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS,
new ConfigurationService.Converter<Boolean>() {
@Override
public Boolean convert(Object value) {
return Boolean.parseBoolean( value.toString() );
}
},
StandardConverters.BOOLEAN,
false
);

defaultSchemaName = configService.getSetting(
AvailableSettings.DEFAULT_SCHEMA,
new ConfigurationService.Converter<String>() {
@Override
public String convert(Object value) {
return value.toString();
}
},
StandardConverters.STRING,
null
);

defaultCatalogName = configService.getSetting(
AvailableSettings.DEFAULT_CATALOG,
new ConfigurationService.Converter<String>() {
@Override
public String convert(Object value) {
return value.toString();
}
},
StandardConverters.STRING,
null
);

globallyQuotedIdentifiers = configService.getSetting(
AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS,
new ConfigurationService.Converter<Boolean>() {
@Override
public Boolean convert(Object value) {
return Boolean.parseBoolean( value.toString() );
}
},
StandardConverters.BOOLEAN,
false
);

multiTenancyStrategy = configService.getSetting(
AvailableSettings.MULTI_TENANT,
new ConfigurationService.Converter<org.hibernate.MultiTenancyStrategy>() {
@Override
public MultiTenancyStrategy convert(Object value) {
return MultiTenancyStrategy.fromConfigValue( value );
}
},
MultiTenancyStrategy.NONE
);
}


Expand Down Expand Up @@ -209,5 +203,10 @@ public String getDefaultSchemaName() {
public String getDefaultCatalogName() {
return defaultCatalogName;
}

@Override
public MultiTenancyStrategy getMultiTenancyStrategy() {
return multiTenancyStrategy;
}
}
}

0 comments on commit 93882f4

Please sign in to comment.