blob: c75ae57d1a2ca15be98f902947735dce2b8a2c93 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.polygene.entitystore.sql;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.polygene.api.association.AssociationDescriptor;
import org.apache.polygene.api.common.QualifiedName;
import org.apache.polygene.api.entity.EntityDescriptor;
import org.apache.polygene.api.property.PropertyDescriptor;
import org.apache.polygene.api.util.Classes;
import org.jooq.Constraint;
import org.jooq.CreateTableColumnStep;
import org.jooq.DataType;
import org.jooq.Field;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.Table;
import org.jooq.impl.DSL;
/**
* This class is effectively the manager of the {@link MixinTable} instances.
*/
class TypesTable
{
// Common in all tables
private static final String IDENTITY_COLUMN_NAME = "_identity";
private static final String CREATED_COLUMN_NAME = "_created_at";
private static final String LASTMODIFIED_COLUMN_NAME = "_modified_at";
// Types Table
private static final String TABLENAME_COLUMN_NAME = "_table_name";
// Common Fields
private Field<String> identityColumn;
private Field<Timestamp> createdColumn;
private Field<Timestamp> modifiedColumn;
// Types Table
private Field<String> tableNameColumn;
private final Map<Class<?>, Table<Record>> mixinTablesCache = new ConcurrentHashMap<>();
private final Table<Record> typesTable;
private final SQLDialect dialect;
private final SqlEntityStoreConfiguration config;
private final JooqDslContext dsl;
TypesTable( JooqDslContext dsl,
SQLDialect dialect,
String typesTablesName,
SqlEntityStoreConfiguration config
)
{
this.dialect = dialect;
this.config = config;
typesTable = dsl.tableOf( typesTablesName );
this.dsl = dsl;
Integer idMaxLength = config.identityLength().get();
identityColumn = SqlType.makeField( IDENTITY_COLUMN_NAME, String.class, dialect );
createdColumn = SqlType.makeField( CREATED_COLUMN_NAME, Timestamp.class, dialect );
modifiedColumn = SqlType.makeField( LASTMODIFIED_COLUMN_NAME, Timestamp.class, dialect );
tableNameColumn = SqlType.makeField( TABLENAME_COLUMN_NAME, String.class, dialect );
}
private String tableNameOf( Class<?> mixinType )
{
Result<Record> typeInfo = fetchTypeInfoFromTable( mixinType );
if( typeInfo.isEmpty() )
{
return null;
}
return typeInfo.getValue( 0, tableNameColumn );
}
Table<Record> tableFor( Class<?> type, EntityDescriptor descriptor )
{
return mixinTablesCache.computeIfAbsent( type, t ->
{
String tableName = tableNameOf( t );
if( tableName == null )
{
Result<Record> newMixinTable = createNewMixinTable( type, descriptor );
return dsl.tableOf( newMixinTable.getValue( 0, tableNameColumn ) );
}
return dsl.tableOf( tableName );
} );
}
private Result<Record> fetchTypeInfoFromTable( Class<?> mixinTableName )
{
return dsl.select()
.from( typesTable )
.where( identityColumn.eq( mixinTableName.getName() ) )
.fetch();
}
private Result<Record> createNewMixinTable( Class<?> mixinType, EntityDescriptor descriptor )
{
String mixinTypeName = mixinType.getName();
String tableName = createNewTableName( mixinType );
CreateTableColumnStep primaryTable = dsl.createTable( dsl.tableOf( tableName ) )
.column( identityColumn )
.column( createdColumn );
descriptor.state().properties().forEach(
property ->
{
QualifiedName qualifiedName = property.qualifiedName();
if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) )
{
primaryTable.column( fieldOf( property ) );
}
} );
descriptor.state().associations().forEach(
assoc ->
{
QualifiedName qualifiedName = assoc.qualifiedName();
if( qualifiedName.type().replace( '-', '$' ).equals( mixinTypeName ) )
{
primaryTable.column( fieldOf( assoc ) );
}
} );
primaryTable.constraint( DSL.primaryKey( identityColumn ) );
int result1 = primaryTable.execute();
int result3 = dsl.insertInto( typesTable )
.set( identityColumn, mixinTypeName )
.set( tableNameColumn, tableName )
.set( createdColumn, new Timestamp( System.currentTimeMillis() ) )
.set( modifiedColumn, new Timestamp( System.currentTimeMillis() ) )
.execute();
return fetchTypeInfoFromTable( mixinType );
}
private String createNewTableName( Class<?> mixinType )
{
String typeName = mixinType.getSimpleName();
String postFix = "";
int counter = 1;
boolean found;
do
{
found = checkForTableNamed( typeName + postFix );
postFix = "_" + counter++;
} while( found );
return typeName;
}
private boolean checkForTableNamed( String tableName )
{
if( tableName.equalsIgnoreCase( config.entitiesTableName().get() ) || tableName.equalsIgnoreCase( config.typesTableName().get() ) )
{
return true;
}
return dsl.select()
.from( typesTable )
.where( tableNameColumn.eq( tableName ) )
.fetch().size() > 0;
}
Field<Object> fieldOf( PropertyDescriptor descriptor )
{
String propertyName = descriptor.qualifiedName().name();
return DSL.field( DSL.name( propertyName ), dataTypeOf( descriptor ) );
}
Field<String> fieldOf( AssociationDescriptor descriptor )
{
String propertyName = descriptor.qualifiedName().name();
return DSL.field( DSL.name( propertyName ), dataTypeOf( descriptor ) );
}
private <T> DataType<T> dataTypeOf( PropertyDescriptor property )
{
Type type = property.type();
@SuppressWarnings( "unchecked" )
Class<T> rawType = (Class<T>) Classes.RAW_CLASS.apply( type );
return SqlType.getSqlDataTypeFor( dialect, rawType, false );
}
private <T> DataType<T> dataTypeOf( AssociationDescriptor property )
{
Type type = property.type();
@SuppressWarnings( "unchecked" )
Class<T> rawType = (Class<T>) Classes.RAW_CLASS.apply( type );
return SqlType.getSqlDataTypeFor( dialect, rawType, false );
}
Field<String> identityColumn()
{
return identityColumn;
}
Field<Timestamp> modifiedColumn()
{
return modifiedColumn;
}
Field<Timestamp> createdColumn()
{
return createdColumn;
}
void create()
{
dsl.createTableIfNotExists( typesTable )
.column( identityColumn )
.column( tableNameColumn )
.column( createdColumn )
.column( modifiedColumn )
.constraint( DSL.primaryKey( identityColumn ) )
.execute();
}
}