OneToManyWithAssociationTableConfigurer.java
package org.codefilarete.stalactite.engine.configurer.onetomany;
import java.util.Collection;
import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy;
import org.codefilarete.stalactite.dsl.naming.AssociationTableNamingStrategy.ReferencedColumnNames;
import org.codefilarete.stalactite.engine.configurer.AssociationRecordMapping;
import org.codefilarete.stalactite.engine.configurer.CascadeConfigurationResult;
import org.codefilarete.stalactite.engine.configurer.IndexedAssociationRecordMapping;
import org.codefilarete.stalactite.engine.runtime.AssociationRecord;
import org.codefilarete.stalactite.engine.runtime.AssociationRecordPersister;
import org.codefilarete.stalactite.engine.runtime.AssociationTable;
import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
import org.codefilarete.stalactite.engine.runtime.IndexedAssociationRecord;
import org.codefilarete.stalactite.engine.runtime.IndexedAssociationTable;
import org.codefilarete.stalactite.engine.runtime.onetomany.AbstractOneToManyWithAssociationTableEngine;
import org.codefilarete.stalactite.engine.runtime.onetomany.IndexedAssociationTableManyRelationDescriptor;
import org.codefilarete.stalactite.engine.runtime.onetomany.ManyRelationDescriptor;
import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithAssociationTableEngine;
import org.codefilarete.stalactite.engine.runtime.onetomany.OneToManyWithIndexedAssociationTableEngine;
import org.codefilarete.stalactite.sql.ConnectionConfiguration;
import org.codefilarete.stalactite.sql.Dialect;
import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
import org.codefilarete.stalactite.sql.ddl.structure.Table;
import org.codefilarete.tool.collection.Iterables;
import static org.codefilarete.tool.Nullable.nullable;
/**
* Configurer dedicated to association that needs an intermediary table between source and target entities
* @author Guillaume Mary
*/
class OneToManyWithAssociationTableConfigurer<SRC, TRGT, SRCID, TRGTID, C extends Collection<TRGT>,
LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>>
extends OneToManyConfigurerTemplate<SRC, TRGT, SRCID, TRGTID, C, LEFTTABLE> {
private final AssociationTableNamingStrategy associationTableNamingStrategy;
private final Dialect dialect;
private final boolean maintainAssociationOnly;
private final ConnectionConfiguration connectionConfiguration;
OneToManyWithAssociationTableConfigurer(OneToManyAssociationConfiguration<SRC, TRGT, SRCID, TRGTID, C, LEFTTABLE> associationConfiguration,
boolean loadSeparately,
AssociationTableNamingStrategy associationTableNamingStrategy,
boolean maintainAssociationOnly,
Dialect dialect,
ConnectionConfiguration connectionConfiguration) {
super(associationConfiguration, loadSeparately);
this.associationTableNamingStrategy = associationTableNamingStrategy;
this.dialect = dialect;
this.maintainAssociationOnly = maintainAssociationOnly;
this.connectionConfiguration = connectionConfiguration;
}
@Override
protected String configure(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
AbstractOneToManyWithAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, ? extends AssociationRecord, ?> associationTableEngine = prepare(targetPersister);
String relationJoinNodeName = associationTableEngine.addSelectCascade(associationConfiguration.getSrcPersister(), loadSeparately);
addWriteCascades(associationTableEngine, targetPersister);
return relationJoinNodeName;
}
private AbstractOneToManyWithAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, ?, ?> prepare(ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
// case : Collection mapping without reverse property : an association table is needed
PrimaryKey<RIGHTTABLE, TRGTID> rightPrimaryKey = targetPersister.<RIGHTTABLE>getMapping().getTargetTable().getPrimaryKey();
String associationTableName = nullable(associationConfiguration.getOneToManyRelation().getAssociationTableName()).getOr(() -> associationTableNamingStrategy.giveName(accessorDefinitionForTableNaming,
associationConfiguration.getLeftPrimaryKey(), rightPrimaryKey));
if (associationConfiguration.getOneToManyRelation().isOrdered()) {
return assignEngineForIndexedAssociation(rightPrimaryKey, associationTableName, targetPersister);
} else {
return assignEngineForNonIndexedAssociation(rightPrimaryKey, associationTableName, targetPersister);
}
}
@Override
public CascadeConfigurationResult<SRC, TRGT> configureWithSelectIn2Phases(String tableAlias,
ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister,
FirstPhaseCycleLoadListener<SRC, TRGTID> firstPhaseCycleLoadListener) {
AbstractOneToManyWithAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, ?, ?> associationTableEngine = prepare(targetPersister);
associationTableEngine.addSelectCascadeIn2Phases(firstPhaseCycleLoadListener);
addWriteCascades(associationTableEngine, targetPersister);
return new CascadeConfigurationResult<>(associationTableEngine.getManyRelationDescriptor().getRelationFixer(), associationConfiguration.getSrcPersister());
}
private void addWriteCascades(AbstractOneToManyWithAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, ? extends AssociationRecord, ? extends AssociationTable> oneToManyWithAssociationTableEngine,
ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
if (associationConfiguration.isWriteAuthorized()) {
oneToManyWithAssociationTableEngine.addInsertCascade(maintainAssociationOnly, targetPersister);
oneToManyWithAssociationTableEngine.addUpdateCascade(associationConfiguration.isOrphanRemoval(), maintainAssociationOnly, targetPersister);
oneToManyWithAssociationTableEngine.addDeleteCascade(associationConfiguration.isOrphanRemoval(), dialect, targetPersister);
}
}
private <ASSOCIATIONTABLE extends AssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
OneToManyWithAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, ASSOCIATIONTABLE> assignEngineForNonIndexedAssociation(
PrimaryKey<RIGHTTABLE, TRGTID> rightPrimaryKey,
String associationTableName,
ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
// we don't create foreign key for table-per-class because source columns should reference different tables (the one
// per entity) which databases do not allow
boolean createOneSideForeignKey = !(associationConfiguration.getOneToManyRelation().isSourceTablePerClassPolymorphic());
boolean createManySideForeignKey = !associationConfiguration.getOneToManyRelation().isTargetTablePerClassPolymorphic();
ReferencedColumnNames<LEFTTABLE, RIGHTTABLE> columnNames = associationTableNamingStrategy.giveColumnNames(
accessorDefinitionForTableNaming,
associationConfiguration.getLeftPrimaryKey(),
rightPrimaryKey);
if (associationConfiguration.getOneToManyRelation().getSourceJoinColumnName() != null) {
columnNames.setLeftColumnName(Iterables.first(associationConfiguration.getLeftPrimaryKey().getColumns()), associationConfiguration.getOneToManyRelation().getSourceJoinColumnName());
}
if (associationConfiguration.getOneToManyRelation().getSourceJoinColumnName() != null) {
columnNames.setRightColumnName(Iterables.first(rightPrimaryKey.getColumns()), associationConfiguration.getOneToManyRelation().getTargetJoinColumnName());
}
ASSOCIATIONTABLE intermediaryTable = (ASSOCIATIONTABLE) new AssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>(
associationConfiguration.getLeftPrimaryKey().getTable().getSchema(),
associationTableName,
associationConfiguration.getLeftPrimaryKey(),
rightPrimaryKey,
columnNames,
associationConfiguration.getForeignKeyNamingStrategy(),
createOneSideForeignKey,
createManySideForeignKey
);
associationConfiguration.getSrcPersister().getMapping().getIdMapping();
targetPersister.getMapping().getIdMapping();
AssociationRecordPersister<AssociationRecord, ASSOCIATIONTABLE> associationPersister = new AssociationRecordPersister<>(
new AssociationRecordMapping<>(
intermediaryTable,
associationConfiguration.getSrcPersister().getMapping().getIdMapping().getIdentifierAssembler(),
targetPersister.getMapping().getIdMapping().getIdentifierAssembler()),
dialect,
connectionConfiguration);
ManyRelationDescriptor<SRC, TRGT, C> manyRelationDescriptor = new ManyRelationDescriptor<>(
associationConfiguration.getCollectionGetter(),
associationConfiguration.getSetter()::set,
associationConfiguration.getCollectionFactory(),
associationConfiguration.getOneToManyRelation().getReverseLink());
return new OneToManyWithAssociationTableEngine<>(
associationConfiguration.getSrcPersister(),
targetPersister,
manyRelationDescriptor,
associationPersister,
dialect.getWriteOperationFactory());
}
private <ASSOCIATIONTABLE extends IndexedAssociationTable<ASSOCIATIONTABLE, LEFTTABLE, RIGHTTABLE, SRCID, TRGTID>>
OneToManyWithIndexedAssociationTableEngine<SRC, TRGT, SRCID, TRGTID, C, LEFTTABLE, RIGHTTABLE, ASSOCIATIONTABLE>
assignEngineForIndexedAssociation(PrimaryKey<RIGHTTABLE, TRGTID> rightPrimaryKey,
String associationTableName,
ConfiguredRelationalPersister<TRGT, TRGTID> targetPersister) {
OneToManyRelation<SRC, TRGT, TRGTID, C> relation = associationConfiguration.getOneToManyRelation();
// we don't create foreign key for table-per-class because source columns should reference different tables (the one
// per entity) which databases do not allow
boolean createOneSideForeignKey = !(relation.isSourceTablePerClassPolymorphic());
boolean createManySideForeignKey = !relation.isTargetTablePerClassPolymorphic();
ReferencedColumnNames<LEFTTABLE, RIGHTTABLE> columnNames = associationTableNamingStrategy.giveColumnNames(
accessorDefinitionForTableNaming,
associationConfiguration.getLeftPrimaryKey(),
rightPrimaryKey);
if (relation.getSourceJoinColumnName() != null) {
columnNames.setLeftColumnName(Iterables.first(associationConfiguration.getLeftPrimaryKey().getColumns()), relation.getSourceJoinColumnName());
}
if (relation.getSourceJoinColumnName() != null) {
columnNames.setRightColumnName(Iterables.first(rightPrimaryKey.getColumns()), relation.getTargetJoinColumnName());
}
String indexingColumnName = nullable(associationConfiguration.getIndexingColumnName()).getOr(() -> associationConfiguration.getIndexColumnNamingStrategy().giveName(accessorDefinitionForTableNaming));
// NB: index column is part of the primary key
ASSOCIATIONTABLE intermediaryTable = (ASSOCIATIONTABLE) new IndexedAssociationTable<>(
associationConfiguration.getLeftPrimaryKey().getTable().getSchema(),
associationTableName,
associationConfiguration.getLeftPrimaryKey(),
rightPrimaryKey,
columnNames,
associationConfiguration.getForeignKeyNamingStrategy(),
createOneSideForeignKey,
createManySideForeignKey,
indexingColumnName);
AssociationRecordPersister<IndexedAssociationRecord, ASSOCIATIONTABLE> indexedAssociationPersister =
new AssociationRecordPersister<>(
new IndexedAssociationRecordMapping<>(intermediaryTable,
associationConfiguration.getSrcPersister().getMapping().getIdMapping().getIdentifierAssembler(),
targetPersister.getMapping().getIdMapping().getIdentifierAssembler(),
intermediaryTable.getLeftIdentifierColumnMapping(),
intermediaryTable.getRightIdentifierColumnMapping()),
dialect,
connectionConfiguration);
IndexedAssociationTableManyRelationDescriptor<SRC, TRGT, C, SRCID> manyRelationDescriptor = new IndexedAssociationTableManyRelationDescriptor<>(
associationConfiguration.getCollectionGetter(),
associationConfiguration.getSetter()::set,
associationConfiguration.getCollectionFactory(),
relation.getReverseLink(),
associationConfiguration.getSrcPersister()::getId
);
return new OneToManyWithIndexedAssociationTableEngine<>(
associationConfiguration.getSrcPersister(),
targetPersister,
manyRelationDescriptor,
indexedAssociationPersister,
intermediaryTable.getIndexColumn(),
dialect.getWriteOperationFactory());
}
}