blob: ec395a432817976791d4fdab53a22dc9e16eec89 [file] [log] [blame]
/*
* Copyright (c) 2010, Stanislav Muhametsin. All Rights Reserved.
* Copyright (c) 2012, Paul Merlin. All Rights Reserved.
*
* 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 org.apache.zest.index.sql.support.skeletons;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.zest.api.Qi4j;
import org.apache.zest.api.association.AssociationDescriptor;
import org.apache.zest.api.common.QualifiedName;
import org.apache.zest.api.entity.EntityReference;
import org.apache.zest.api.entity.Identity;
import org.apache.zest.api.injection.scope.Service;
import org.apache.zest.api.injection.scope.Structure;
import org.apache.zest.api.injection.scope.This;
import org.apache.zest.api.injection.scope.Uses;
import org.apache.zest.api.property.PropertyDescriptor;
import org.apache.zest.api.property.StateHolder;
import org.apache.zest.api.service.ServiceDescriptor;
import org.apache.zest.api.structure.Application;
import org.apache.zest.api.value.ValueComposite;
import org.apache.zest.api.value.ValueDescriptor;
import org.apache.zest.functional.Iterables;
import org.apache.zest.functional.Specification;
import org.apache.zest.index.sql.support.api.SQLIndexing;
import org.apache.zest.index.sql.support.common.DBNames;
import org.apache.zest.index.sql.support.common.QNameInfo;
import org.apache.zest.index.sql.support.common.QNameInfo.QNameType;
import org.apache.zest.index.sql.support.postgresql.PostgreSQLTypeHelper;
import org.apache.zest.index.sql.support.skeletons.SQLSkeletonUtil.Lazy;
import org.apache.zest.index.sql.support.skeletons.SQLSkeletonUtil.LazyInit;
import org.apache.zest.library.sql.common.SQLUtil;
import org.apache.zest.spi.Qi4jSPI;
import org.apache.zest.spi.entity.EntityState;
import org.apache.zest.spi.entity.EntityStatus;
import org.sql.generation.api.grammar.builders.modification.ColumnSourceByValuesBuilder;
import org.sql.generation.api.grammar.builders.modification.DeleteBySearchBuilder;
import org.sql.generation.api.grammar.builders.modification.UpdateBySearchBuilder;
import org.sql.generation.api.grammar.builders.query.QuerySpecificationBuilder;
import org.sql.generation.api.grammar.factories.BooleanFactory;
import org.sql.generation.api.grammar.factories.ColumnsFactory;
import org.sql.generation.api.grammar.factories.LiteralFactory;
import org.sql.generation.api.grammar.factories.ModificationFactory;
import org.sql.generation.api.grammar.factories.QueryFactory;
import org.sql.generation.api.grammar.factories.TableReferenceFactory;
import org.sql.generation.api.grammar.modification.DeleteStatement;
import org.sql.generation.api.grammar.modification.InsertStatement;
import org.sql.generation.api.grammar.modification.UpdateSourceByExpression;
import org.sql.generation.api.grammar.modification.UpdateStatement;
import org.sql.generation.api.grammar.query.QueryExpression;
import org.sql.generation.api.vendor.SQLVendor;
import static org.apache.zest.index.sql.support.common.DBNames.ENTITY_TABLE_NAME;
import static org.apache.zest.index.sql.support.common.DBNames.ENTITY_TYPES_JOIN_TABLE_NAME;
/**
* TODO SQL-Generator now has support for auto-generated columns. Use it in indexing ( should make
* some things quite faster and simpler ).
*/
public abstract class AbstractSQLIndexing
implements SQLIndexing
{
public static final Integer AMOUNT_OF_COLUMNS_IN_ENTITY_TABLE = 5;
public static final Integer AMOUNT_OF_COLUMNS_IN_ALL_QNAMES_TABLE = 2;
public static final Integer AMOUNT_OF_COLUMNS_IN_ASSO_TABLE = 2;
public static final Integer AMOUNT_OF_COLUMNS_IN_MANY_ASSO_TABLE = 3;
@Structure
private Application _app;
@Structure
private Qi4jSPI _qi4SPI;
@This
private SQLDBState _state;
@This
private PostgreSQLTypeHelper _sqlTypeHelper;
@Uses
private ServiceDescriptor descriptor;
@Service
private DataSource _dataSource;
@Override
public void indexEntities( Iterable<EntityState> changedStates )
throws SQLException
{
final String schemaName = this._state.schemaName().get();
final SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class );
Connection connectionTest = AbstractSQLStartup.CONNECTION_FOR_REINDEXING.get();
boolean connectionFromStartupWasNull = connectionTest == null;
boolean wasAutoCommit = false;
boolean wasReadOnly = false;
if( connectionFromStartupWasNull )
{
connectionTest = this._dataSource.getConnection();
}
else
{
wasAutoCommit = connectionTest.getAutoCommit();
wasReadOnly = connectionTest.isReadOnly();
}
final Connection connection = connectionTest;
PreparedStatement updateEntityTablePS = null;
PreparedStatement removeEntityPS = null;
PreparedStatement insertToPropertyQNamesPS = null;
PreparedStatement clearEntityDataPS = null;
Lazy<PreparedStatement, SQLException> queryEntityPKPS = new Lazy<>(
new LazyInit<PreparedStatement, SQLException>()
{
@Override
public PreparedStatement create()
throws SQLException
{
return connection.prepareStatement(
vendor.toString( createQueryEntityPkByIdentityStatement( schemaName, vendor ) ) );
}
} );
Lazy<PreparedStatement, SQLException> insertToEntityTableAutoGenerated = new Lazy<>(
new LazyInit<PreparedStatement, SQLException>()
{
@Override
public PreparedStatement create()
throws SQLException
{
return connection.prepareStatement(
createInsertStatementWithAutoGeneratedIDForEntitiesTable( schemaName,
ENTITY_TABLE_NAME,
vendor ).toString() );
}
} );
Lazy<PreparedStatement, SQLException> insertToEntityTypeTablePS = new Lazy<>(
new LazyInit<PreparedStatement, SQLException>()
{
@Override
public PreparedStatement create()
throws SQLException
{
return connection.prepareStatement(
createInsertEntityTypeStatement( schemaName, vendor ).toString() );
}
} );
Map<QualifiedName, PreparedStatement> qNameInsertPSs = new HashMap<>();
try
{
connection.setAutoCommit( false );
connection.setReadOnly( false );
// TODO cache all queries.
updateEntityTablePS = connection.prepareStatement(
this.createUpdateEntityTableStatement( schemaName, vendor ).toString() );
removeEntityPS = connection.prepareStatement(
this.createDeleteFromEntityTableStatement( schemaName, vendor ).toString() );
insertToPropertyQNamesPS = connection.prepareStatement(
vendor.toString( this.createInsertStatement( schemaName,
DBNames.ALL_QNAMES_TABLE_NAME,
AMOUNT_OF_COLUMNS_IN_ALL_QNAMES_TABLE,
vendor ) ) );
clearEntityDataPS = connection.prepareStatement(
this.createClearEntityDataStatement( schemaName, vendor ).toString() );
Map<Long, EntityState> statesByPK = new HashMap<>();
Map<Long, Integer> qNamePKs = new HashMap<>();
Iterable<EntityState> relatedStates = Iterables.filter( new Specification<EntityState>()
{
@Override
public boolean satisfiedBy( EntityState item )
{
return item.entityDescriptor().queryable();
}
}, Iterables.map( SQLCompatEntityStateWrapper.WRAP, changedStates ) );
for( EntityState eState : relatedStates )
{
EntityStatus status = eState.status();
Long pk = null;
boolean needToInsert = status.equals( EntityStatus.NEW );
if( !needToInsert )
{
if( status.equals( EntityStatus.UPDATED ) )
{
pk = this.findEntityPK( eState, queryEntityPKPS );
if( pk == null )
{
// Happens when reindexing
needToInsert = true;
}
else
{
// TODO if multiple applications with different application model use
// indexing, need to sync type-table.
this.updateEntityInfoAndProperties( connection, qNameInsertPSs,
insertToPropertyQNamesPS,
clearEntityDataPS,
updateEntityTablePS,
eState,
pk, qNamePKs );
}
}
else if( status.equals( EntityStatus.REMOVED ) )
{
this.removeEntity( eState, removeEntityPS );
}
else
{
// TODO possibly handle LOADED state somehow
// throw new
// UnsupportedOperationException("Did not understand what to do with state [id = "
// +
// eState.identity().identity() + ", status = " + status + "].");
}
}
if( needToInsert )
{
pk = this.getPKFromAutoGeneratedIDInsert( eState,
insertToEntityTableAutoGenerated.getValue(),
vendor,
connection );
this.insertPropertyType( connection, insertToEntityTypeTablePS.getValue(),
eState, pk );
this.insertProperties( connection, qNameInsertPSs,
insertToPropertyQNamesPS, eState, pk, qNamePKs );
}
if( pk != null )
{
statesByPK.put( pk, eState );
}
}
removeEntityPS.executeBatch();
updateEntityTablePS.executeBatch();
clearEntityDataPS.executeBatch();
if( insertToEntityTypeTablePS.hasValue() )
{
insertToEntityTypeTablePS.getValue().executeBatch();
}
for( Map.Entry<Long, EntityState> entry : statesByPK.entrySet() )
{
EntityState eState = entry.getValue();
Long pk = entry.getKey();
this.insertAssoAndManyAssoQNames( qNameInsertPSs, insertToPropertyQNamesPS,
eState,
qNamePKs.get( pk ), pk );
}
insertToPropertyQNamesPS.executeBatch();
for( PreparedStatement ps : qNameInsertPSs.values() )
{
ps.executeBatch();
}
connection.commit();
}
catch( SQLException sqle )
{
SQLUtil.rollbackQuietly( connection );
throw sqle;
}
finally
{
try
{
if( queryEntityPKPS.hasValue() )
{
SQLUtil.closeQuietly( queryEntityPKPS.getValue() );
}
if( insertToEntityTableAutoGenerated.hasValue() )
{
SQLUtil.closeQuietly( insertToEntityTableAutoGenerated.getValue() );
}
SQLUtil.closeQuietly( updateEntityTablePS );
SQLUtil.closeQuietly( removeEntityPS );
SQLUtil.closeQuietly( insertToPropertyQNamesPS );
SQLUtil.closeQuietly( clearEntityDataPS );
for( PreparedStatement ps : qNameInsertPSs.values() )
{
SQLUtil.closeQuietly( ps );
}
}
finally
{
if( connectionFromStartupWasNull )
{
SQLUtil.closeQuietly( connection );
}
else
{
connection.setReadOnly( wasReadOnly );
connection.setAutoCommit( wasAutoCommit );
}
}
}
}
protected InsertStatement createInsertStatement( String schemaName, String tableName,
Integer amountOfColumns,
SQLVendor vendor
)
{
ModificationFactory m = vendor.getModificationFactory();
LiteralFactory l = vendor.getLiteralFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
ColumnSourceByValuesBuilder columnBuilder = m.columnSourceByValues();
for( Integer x = 0; x < amountOfColumns; ++x )
{
columnBuilder.addValues( l.param() );
}
return m.insert().setTableName( t.tableName( schemaName, tableName ) )
.setColumnSource( columnBuilder.createExpression() ).createExpression();
}
protected abstract InsertStatement createInsertStatementWithAutoGeneratedIDForEntitiesTable(
String schemaName, String tableName, SQLVendor vendor );
protected void addEntityInfoToInsertToEntityTablePS( EntityState state, PreparedStatement ps,
int startingIndex )
throws SQLException
{
ps.setString( startingIndex, state.identity().identity() );
ps.setTimestamp( startingIndex + 1, new Timestamp( state.lastModified() ) );
ps.setString( startingIndex + 2, state.version() );
ps.setString( startingIndex + 3, this._app.version() );
}
protected Long findEntityPK( EntityState state,
Lazy<PreparedStatement, SQLException> queryPKPS )
throws SQLException
{
// TODO build cache: Zest Identity -> PK
Long entityPK = null;
PreparedStatement ps = queryPKPS.getValue();
ps.setString( 1, state.identity().identity() );
ResultSet rs = null;
try
{
rs = ps.executeQuery();
if( rs.next() )
{
entityPK = rs.getLong( 1 );
}
}
finally
{
SQLUtil.closeQuietly( rs );
}
return entityPK;
}
protected abstract long getPKFromAutoGeneratedIDInsert(
EntityState state, PreparedStatement autoGeneratedIDStatement, SQLVendor vendor,
Connection connection )
throws SQLException;
protected UpdateStatement
createUpdateEntityTableStatement( String schemaName, SQLVendor vendor )
{
ModificationFactory m = vendor.getModificationFactory();
BooleanFactory b = vendor.getBooleanFactory();
LiteralFactory l = vendor.getLiteralFactory();
ColumnsFactory c = vendor.getColumnsFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
// "UPDATE " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + //
// "SET " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = ?, " + //
// ENTITY_TABLE_MODIFIED_COLUMN_NAME + " = ?, " + //
// ENTITY_TABLE_VERSION_COLUMN_NAME + " = ?, " + //
// ENTITY_TABLE_APPLICATION_VERSION_COLUMN_NAME + " = ?" + "\n" + //
// "WHERE " + ENTITY_TABLE_PK_COLUMN_NAME + " = ?" + "\n" + //
// ";" //
UpdateSourceByExpression paramSource = m.updateSourceByExp( l.param() );
UpdateBySearchBuilder builder = m.updateBySearch();
builder
.setTargetTable(
m.createTargetTable( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) )
.addSetClauses( m.setClause( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME, paramSource ),
m.setClause( DBNames.ENTITY_TABLE_MODIFIED_COLUMN_NAME, paramSource ),
m.setClause( DBNames.ENTITY_TABLE_VERSION_COLUMN_NAME, paramSource ),
m.setClause( DBNames.ENTITY_TABLE_APPLICATION_VERSION_COLUMN_NAME, paramSource ) )
.getWhereBuilder()
.reset( b.eq( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ), l.param() ) );
return builder.createExpression();
}
protected QueryExpression createQueryEntityPkByIdentityStatement( String schemaName,
SQLVendor vendor )
{
BooleanFactory b = vendor.getBooleanFactory();
LiteralFactory l = vendor.getLiteralFactory();
ColumnsFactory c = vendor.getColumnsFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
QueryFactory q = vendor.getQueryFactory();
// "SELECT " + ENTITY_TABLE_PK_COLUMN_NAME + "\n" + //
// "FROM " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + //
// "WHERE " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = ?" + "\n" + //
// ";" //
QuerySpecificationBuilder query = q.querySpecificationBuilder();
query.getSelect().addUnnamedColumns( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ) );
query.getFrom().addTableReferences(
t.tableBuilder( t.table( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) ) );
query.getWhere().reset(
b.eq( c.colName( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME ), l.param() ) );
return q.createQuery( query.createExpression() );
}
protected DeleteStatement createDeleteFromEntityTableStatement( String schemaName,
SQLVendor vendor )
{
return this.createDeleteFromTableStatement( schemaName, DBNames.ENTITY_TABLE_NAME,
DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME, vendor );
}
protected DeleteStatement
createClearEntityDataStatement( String schemaName, SQLVendor vendor )
{
return this.createDeleteFromTableStatement( schemaName, DBNames.ALL_QNAMES_TABLE_NAME,
DBNames.ENTITY_TABLE_PK_COLUMN_NAME, vendor );
}
protected DeleteStatement createDeleteFromTableStatement( String schemaName, String tableName,
String columnName,
SQLVendor vendor
)
{
ModificationFactory m = vendor.getModificationFactory();
BooleanFactory b = vendor.getBooleanFactory();
LiteralFactory l = vendor.getLiteralFactory();
ColumnsFactory c = vendor.getColumnsFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
// "DELETE FROM " + "%s" + "." + "%s" + "\n" + //
// "WHERE " + "%s" + " = ? " + "\n" + //
// ";" //
DeleteBySearchBuilder delete = m.deleteBySearch();
delete.setTargetTable( m.createTargetTable( t.tableName( schemaName, tableName ) ) )
.getWhere()
.reset( b.eq( c.colName( columnName ), l.param() ) );
return delete.createExpression();
}
protected InsertStatement createPropertyInsert( QNameInfo qNameInfo, SQLVendor vendor )
{
String tableName = qNameInfo.getTableName();
ModificationFactory m = vendor.getModificationFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
LiteralFactory l = vendor.getLiteralFactory();
ColumnSourceByValuesBuilder columnBuilder = m.columnSourceByValues()
.addValues( l.param(), l.param(), l.param() );
if( qNameInfo.getCollectionDepth() > 0 )
{
columnBuilder.addValues( l.func( "text2ltree", l.param() ) );
}
columnBuilder.addValues( l.param() );
return m.insert().setTableName( t.tableName( this._state.schemaName().get(), tableName ) )
.setColumnSource( columnBuilder.createExpression() ).createExpression();
}
protected InsertStatement createAssoInsert( QNameInfo qNameInfo, SQLVendor vendor,
Integer amountOfParams )
{
ModificationFactory m = vendor.getModificationFactory();
LiteralFactory l = vendor.getLiteralFactory();
ColumnsFactory c = vendor.getColumnsFactory();
QueryFactory q = vendor.getQueryFactory();
TableReferenceFactory t = vendor.getTableReferenceFactory();
BooleanFactory b = vendor.getBooleanFactory();
String schemaName = this._state.schemaName().get();
// "INSERT INTO " + "%s" + "." + "%s" + "\n" + //
// "SELECT " + "?, " + "?, " + ENTITY_TABLE_PK_COLUMN_NAME + "\n" + // <-- here is 4 params
// when many-asso
// "FROM " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + //
// "WHERE " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = " + "?";
QuerySpecificationBuilder qBuilder = q.querySpecificationBuilder();
for( Integer x = 0; x < amountOfParams; ++x )
{
qBuilder.getSelect().addUnnamedColumns( c.colExp( l.param() ) );
}
qBuilder.getSelect().addUnnamedColumns( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ) );
qBuilder.getFrom().addTableReferences(
t.tableBuilder( t.table( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) ) );
qBuilder.getWhere().reset(
b.eq( c.colName( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME ), l.param() ) );
return m
.insert()
.setTableName( t.tableName( schemaName, qNameInfo.getTableName() ) )
.setColumnSource( m.columnSourceByQuery( q.createQuery( qBuilder.createExpression() ) ) )
.createExpression();
}
protected InsertStatement createInsertEntityTypeStatement( String schemaName, SQLVendor vendor )
{
return this.createInsertStatement( schemaName, ENTITY_TYPES_JOIN_TABLE_NAME, 2, vendor );
}
private void syncQNamesInsertPSs( Connection connection,
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
Set<QualifiedName> qNames
)
throws SQLException
{
Set<QualifiedName> copy = new HashSet<QualifiedName>( qNames );
copy.removeAll( qNameInsertPSs.keySet() );
for( QualifiedName qName : copy )
{
QNameInfo info = this._state.qNameInfos().get().get( qName );
if( info == null )
{
throw new InternalError(
"Could not find database information about qualified name [" + qName + "]" );
}
QNameType type = info.getQNameType();
if( type.equals( QNameType.PROPERTY ) )
{
qNameInsertPSs.put( qName, this.createInsertPropertyPS( connection, info ) );
}
else if( type.equals( QNameType.ASSOCIATION ) )
{
qNameInsertPSs.put( qName, this.createInsertAssociationPS( connection, info ) );
}
else if( type.equals( QNameType.MANY_ASSOCIATION ) )
{
qNameInsertPSs.put( qName, this.createInsertManyAssociationPS( connection, info ) );
}
else
{
throw new IllegalArgumentException( "Did not know what to do with QName of type "
+ type + "." );
}
}
}
private PreparedStatement createInsertPropertyPS( Connection connection, QNameInfo qNameInfo )
throws SQLException
{
SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class );
return connection.prepareStatement( vendor.toString( this.createPropertyInsert( qNameInfo,
vendor ) ) );
}
private PreparedStatement
createInsertAssociationPS( Connection connection, QNameInfo qNameInfo )
throws SQLException
{
SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class );
return connection.prepareStatement( vendor.toString( this.createAssoInsert( qNameInfo,
vendor,
AMOUNT_OF_COLUMNS_IN_ASSO_TABLE ) ) );
}
private PreparedStatement createInsertManyAssociationPS( Connection connection,
QNameInfo qNameInfo )
throws SQLException
{
SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class );
return connection.prepareStatement( vendor.toString( this.createAssoInsert( qNameInfo,
vendor,
AMOUNT_OF_COLUMNS_IN_MANY_ASSO_TABLE ) ) );
}
private void clearAllEntitysQNames( PreparedStatement clearPropertiesPS, Long pk )
throws SQLException
{
clearPropertiesPS.setLong( 1, pk );
clearPropertiesPS.addBatch();
}
private Integer insertPropertyQNames( Connection connection,
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS, EntityState state, Long entityPK
)
throws SQLException
{
Set<QualifiedName> qNames = this._state.entityUsedQNames().get().get( state.entityDescriptor() );
this.syncQNamesInsertPSs( connection, qNameInsertPSs, qNames );
Integer propertyPK = 0;
for( PropertyDescriptor pDesc : state.entityDescriptor().state().properties() )
{
if( SQLSkeletonUtil.isQueryable( pDesc.accessor() ) )
{
propertyPK = this.insertProperty(
qNameInsertPSs,
insertAllQNamesPS,
propertyPK,
entityPK,
pDesc.qualifiedName(),
state.propertyValueOf( pDesc.qualifiedName() ),
null //
);
}
}
return propertyPK;
}
private void insertAssoAndManyAssoQNames( Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertToAllQNamesPS,
EntityState state,
Integer qNamePK,
Long entityPK
)
throws SQLException
{
for( AssociationDescriptor aDesc : state.entityDescriptor().state().associations() )
{
if( SQLSkeletonUtil.isQueryable( aDesc.accessor() ) )
{
QualifiedName qName = aDesc.qualifiedName();
PreparedStatement ps = qNameInsertPSs.get( qName );
EntityReference ref = state.associationValueOf( qName );
if( ref != null )
{
insertToAllQNamesPS.setInt( 1, qNamePK );
insertToAllQNamesPS.setLong( 2, entityPK );
insertToAllQNamesPS.addBatch();
ps.setInt( 1, qNamePK );
ps.setLong( 2, entityPK );
ps.setString( 3, ref.identity() );
ps.addBatch();
++qNamePK;
}
}
}
for( AssociationDescriptor mDesc : state.entityDescriptor().state().manyAssociations() )
{
if( SQLSkeletonUtil.isQueryable( mDesc.accessor() ) )
{
QualifiedName qName = mDesc.qualifiedName();
PreparedStatement ps = qNameInsertPSs.get( qName );
Integer index = 0;
for( EntityReference ref : state.manyAssociationValueOf( qName ) )
{
if( ref != null )
{
insertToAllQNamesPS.setInt( 1, qNamePK );
insertToAllQNamesPS.setLong( 2, entityPK );
insertToAllQNamesPS.addBatch();
ps.setInt( 1, qNamePK );
ps.setLong( 2, entityPK );
ps.setInt( 3, index );
ps.setString( 4, ref.identity() );
ps.addBatch();
++qNamePK;
}
++index;
}
}
}
}
private Integer insertProperty(
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
QualifiedName qName,
Object property,
Integer parentQNameID
)
throws SQLException
{
Integer result = propertyPK;
if( property != null )
{
if( !qName.type().equals( Identity.class.getName() ) )
{
QNameInfo info = this._state.qNameInfos().get().get( qName );
if( info.getCollectionDepth() > 0 )
{
result = this.storeCollectionProperty( qNameInsertPSs, insertAllQNamesPS,
propertyPK, entityPK,
qName, (Collection<?>) property, parentQNameID );
}
else if( info.isFinalTypePrimitive() )
{
result = this.storePrimitiveProperty( qNameInsertPSs, insertAllQNamesPS, propertyPK,
entityPK,
qName, property, parentQNameID );
}
else
{
result = this.storeValueCompositeProperty( qNameInsertPSs, insertAllQNamesPS,
propertyPK, entityPK,
qName, property, parentQNameID );
}
}
}
return result;
}
private Integer storeCollectionProperty(
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
QualifiedName qName,
Collection<?> property,
Integer parentQNameID
)
throws SQLException
{
QNameInfo info = this._state.qNameInfos().get().get( qName );
PreparedStatement ps = qNameInsertPSs.get( qName );
propertyPK = this.storeCollectionInfo( insertAllQNamesPS, propertyPK, entityPK, parentQNameID, ps,
info );
propertyPK = this.storeCollectionItems( qNameInsertPSs, property, insertAllQNamesPS,
DBNames.QNAME_TABLE_COLLECTION_PATH_TOP_LEVEL_NAME, ps, info.getTableName(),
propertyPK, entityPK,
parentQNameID, info.getFinalType(), info.isFinalTypePrimitive() );
return propertyPK;
}
private Integer storeCollectionInfo( PreparedStatement insertAllQNamesPS, Integer propertyPK,
Long entityPK,
Integer parentQNameID, PreparedStatement ps, QNameInfo info
)
throws SQLException
{
insertAllQNamesPS.setInt( 1, propertyPK );
insertAllQNamesPS.setLong( 2, entityPK );
insertAllQNamesPS.addBatch();
ps.setInt( 1, propertyPK );
ps.setLong( 2, entityPK );
ps.setObject( 3, parentQNameID, Types.BIGINT );
ps.setString( 4, DBNames.QNAME_TABLE_COLLECTION_PATH_TOP_LEVEL_NAME );
if( info.isFinalTypePrimitive() )
{
this.storePrimitiveUsingPS( ps, 5, null, info.getFinalType() );
}
else
{
this.storeVCClassIDUsingPS( ps, 5, null );
}
ps.addBatch();
return propertyPK + 1;
}
private Integer storeCollectionItems( Map<QualifiedName, PreparedStatement> qNameInsertPSs,
Collection<?> collection,
PreparedStatement insertAllQNamesPS,
String path,
PreparedStatement ps,
String tableName,
Integer propertyPK,
Long entityPK,
Integer parentPK,
Type finalType,
Boolean isFinalTypePrimitive
)
throws SQLException
{
Integer index = 0;
for( Object o : collection )
{
String itemPath = path + DBNames.QNAME_TABLE_COLLECTION_PATH_SEPARATOR + index;
if( o instanceof Collection<?> )
{
propertyPK = this.storeCollectionItems( qNameInsertPSs, (Collection<?>) o,
insertAllQNamesPS, itemPath,
ps, tableName, propertyPK, entityPK, parentPK, finalType,
isFinalTypePrimitive );
}
else
{
propertyPK = this.storeCollectionItem( qNameInsertPSs, ps, insertAllQNamesPS, propertyPK,
entityPK,
parentPK, itemPath, o, isFinalTypePrimitive, finalType );
ps.addBatch();
}
++index;
}
return propertyPK;
}
private Integer storeCollectionItem(
Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement ps,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
Integer parentPK,
String path,
Object item,
Boolean isFinalTypePrimitive,
Type finalType
)
throws SQLException
{
insertAllQNamesPS.setInt( 1, propertyPK );
insertAllQNamesPS.setLong( 2, entityPK );
insertAllQNamesPS.addBatch();
ps.setInt( 1, propertyPK );
ps.setLong( 2, entityPK );
ps.setObject( 3, parentPK, Types.INTEGER );
ps.setString( 4, path );
if( isFinalTypePrimitive )
{
this.storePrimitiveUsingPS( ps, 5, item, finalType );
++propertyPK;
}
else
{
this.storeVCClassIDUsingPS( ps, 5, item );
propertyPK = this.storePropertiesOfVC( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, item );
}
return propertyPK;
}
private Integer storePrimitiveProperty(
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
QualifiedName qName,
Object property,
Integer parentQNameID
)
throws SQLException
{
QNameInfo info = this._state.qNameInfos().get().get( qName );
insertAllQNamesPS.setInt( 1, propertyPK );
insertAllQNamesPS.setLong( 2, entityPK );
insertAllQNamesPS.addBatch();
PreparedStatement ps = qNameInsertPSs.get( qName );
ps.setInt( 1, propertyPK );
ps.setLong( 2, entityPK );
ps.setObject( 3, parentQNameID, Types.INTEGER );
Type type = info.getFinalType();
this.storePrimitiveUsingPS( ps, 4, property, type );
ps.addBatch();
return propertyPK + 1;
}
private Integer storeValueCompositeProperty(
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
QualifiedName qName,
Object property,
Integer parentQNameID
)
throws SQLException
{
PreparedStatement ps = qNameInsertPSs.get( qName );
insertAllQNamesPS.setInt( 1, propertyPK );
insertAllQNamesPS.setLong( 2, entityPK );
insertAllQNamesPS.addBatch();
ps.setInt( 1, propertyPK );
ps.setLong( 2, entityPK );
ps.setObject( 3, parentQNameID, Types.INTEGER );
this.storeVCClassIDUsingPS( ps, 4, property );
ps.addBatch();
return this.storePropertiesOfVC( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK,
property );
}
private Integer storePropertiesOfVC( Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
Integer propertyPK,
Long entityPK,
Object property
)
throws SQLException
{
ValueDescriptor vDesc = this._qi4SPI.valueDescriptorFor( (ValueComposite) property );
StateHolder state = Qi4j.FUNCTION_COMPOSITE_INSTANCE_OF.map( (ValueComposite) property ).state();
Integer originalPropertyPK = propertyPK;
++propertyPK;
for( PropertyDescriptor pDesc : vDesc.state().properties() )
{
propertyPK = this.insertProperty(
qNameInsertPSs,
insertAllQNamesPS,
propertyPK,
entityPK,
pDesc.qualifiedName(),
state.propertyFor( pDesc.accessor() ).get(),
originalPropertyPK
);
}
return propertyPK;
}
private void storePrimitiveUsingPS( PreparedStatement ps, Integer nextFreeIndex,
Object primitive,
Type primitiveType
)
throws SQLException
{
if( primitiveType instanceof ParameterizedType )
{
primitiveType = ( (ParameterizedType) primitiveType ).getRawType();
}
if( primitiveType instanceof Class<?>
&& Enum.class.isAssignableFrom( (Class<?>) primitiveType ) )
{
ps.setInt(
nextFreeIndex,
this._state.enumPKs().get().get(
QualifiedName.fromClass( (Class<?>) primitiveType, primitive.toString() ).toString() )
);
}
else
{
this._sqlTypeHelper.addPrimitiveToPS( ps, nextFreeIndex, primitive, primitiveType );
}
}
private void storeVCClassIDUsingPS( PreparedStatement ps, Integer nextFreeIndex, Object vc )
throws SQLException
{
if( vc == null )
{
ps.setNull( nextFreeIndex, Types.INTEGER );
}
else
{
ValueDescriptor vDesc = this._qi4SPI.valueDescriptorFor( vc );
Integer classID = this._state.usedClassesPKs().get().get( vDesc );
ps.setInt( nextFreeIndex, classID );
}
}
private void updateEntityInfoAndProperties( Connection connection,
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
PreparedStatement clearPropertiesPS,
PreparedStatement ps,
EntityState state,
Long entityPK,
Map<Long, Integer> qNamePKs
)
throws SQLException
{
this.clearAllEntitysQNames( clearPropertiesPS, entityPK );
// Update state
ps.setString( 1, state.identity().identity() );
ps.setTimestamp( 2, new Timestamp( state.lastModified() ) );
ps.setString( 3, state.version() );
ps.setString( 4, this._app.version() );
ps.setLong( 5, entityPK );
ps.addBatch();
Integer nextUsableQNamePK = this.insertPropertyQNames( connection, qNameInsertPSs, insertAllQNamesPS,
state, entityPK );
qNamePKs.put( entityPK, nextUsableQNamePK );
}
private void insertProperties( Connection connection,
Map<QualifiedName, PreparedStatement> qNameInsertPSs,
PreparedStatement insertAllQNamesPS,
EntityState state,
Long entityPK,
Map<Long, Integer> qNamePKs
)
throws SQLException
{
Integer nextQnamePK = this.insertPropertyQNames( connection,
qNameInsertPSs, insertAllQNamesPS,
state, entityPK );
qNamePKs.put( entityPK, nextQnamePK );
}
private void removeEntity( EntityState state, PreparedStatement ps )
throws SQLException
{
ps.setString( 1, state.identity().identity() );
ps.addBatch();
}
private void insertPropertyType( Connection connection, PreparedStatement insertPropertyTypePS,
EntityState state, Long entityPK )
throws SQLException
{
for( Class<?> clazz : state.entityDescriptor().types() )
{
Integer typePK = this._state.entityTypePKs().get().get( clazz.getName() );
if( typePK == null )
{
throw new InternalError( "Tried to get entity : " + clazz
+ ", but only aware of the following entities: " + this._state
.entityTypePKs().get().keySet() );
}
insertPropertyTypePS.setLong( 1, entityPK );
insertPropertyTypePS.setInt( 2, typePK );
insertPropertyTypePS.addBatch();
}
}
}