/* * Hibernate OGM, Domain model persistence for NoSQL datastores * * License: GNU Lesser General Public License (LGPL), version 2.1 or later * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.ogm.model.impl; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.hibernate.ogm.model.key.spi.AssociatedEntityKeyMetadata; import org.hibernate.ogm.model.key.spi.AssociationKeyMetadata; import org.hibernate.ogm.model.key.spi.AssociationKind; import org.hibernate.ogm.model.key.spi.AssociationType; import org.hibernate.ogm.model.key.spi.EntityKeyMetadata; import org.hibernate.ogm.util.impl.ArrayHelper; /** * Stores metadata information common to all keys related * to a given association * * @author Emmanuel Bernard <emmanuel@hibernate.org> */ public class DefaultAssociationKeyMetadata implements AssociationKeyMetadata { private final String table; private final String[] columnNames; private final int hashCode; // not part of the object identity private final String[] rowKeyColumnNames; private final String[] rowKeyIndexColumnNames; private final boolean isInverse; private final EntityKeyMetadata entityKeyMetadata; private final AssociatedEntityKeyMetadata associatedEntityKeyMetadata; private final String collectionRole; private final AssociationKind associationKind; private final AssociationType associationType; private DefaultAssociationKeyMetadata(Builder builder) { this.table = builder.table; this.columnNames = builder.columnNames; this.rowKeyColumnNames = builder.rowKeyColumnNames; this.rowKeyIndexColumnNames = builder.rowKeyIndexColumnNames; this.isInverse = builder.isInverse; this.entityKeyMetadata = builder.entityKeyMetadata; this.associatedEntityKeyMetadata = builder.associatedEntityKeyMetadata; this.collectionRole = builder.collectionRole; this.associationKind = builder.associationKind; this.associationType = builder.associationType; // table hashing should be specific enough this.hashCode = table.hashCode(); } public static class Builder { private String table; private String[] columnNames; private String[] rowKeyColumnNames; private String[] rowKeyIndexColumnNames = ArrayHelper.EMPTY_STRING_ARRAY; private boolean isInverse; private EntityKeyMetadata entityKeyMetadata; private AssociatedEntityKeyMetadata associatedEntityKeyMetadata; private String collectionRole; private AssociationKind associationKind; private AssociationType associationType; public Builder table(String table) { this.table = table; return this; } public Builder columnNames(String[] columnNames) { this.columnNames = columnNames; return this; } public Builder rowKeyColumnNames(String[] rowKeyColumnNames) { this.rowKeyColumnNames = rowKeyColumnNames; return this; } public Builder rowKeyIndexColumnNames(String[] rowKeyIndexColumnNames) { this.rowKeyIndexColumnNames = rowKeyIndexColumnNames; return this; } public Builder inverse(boolean isInverse) { this.isInverse = isInverse; return this; } public Builder entityKeyMetadata(EntityKeyMetadata entityKeyMetadata) { this.entityKeyMetadata = entityKeyMetadata; return this; } public Builder associatedEntityKeyMetadata(AssociatedEntityKeyMetadata associatedEntityKeyMetadata) { this.associatedEntityKeyMetadata = associatedEntityKeyMetadata; return this; } public Builder collectionRole(String collectionRole) { this.collectionRole = collectionRole; return this; } public Builder associationKind(AssociationKind associationKind) { this.associationKind = associationKind; return this; } public Builder associationType(AssociationType associationType) { this.associationType = associationType; return this; } public DefaultAssociationKeyMetadata build() { return new DefaultAssociationKeyMetadata( this ); } } @Override public String getTable() { return table; } @Override public String[] getColumnNames() { return columnNames; } /** * The columns identifying an element of the association */ @Override public String[] getRowKeyColumnNames() { return rowKeyColumnNames; } /** * The columns representing the index of the element of the association. * <p> * For example, the key columns of a map-type property or the column with the order if the property is annotated with * {@link javax.persistence.OrderColumn} */ @Override public String[] getRowKeyIndexColumnNames() { return rowKeyIndexColumnNames; } @Override public EntityKeyMetadata getEntityKeyMetadata() { return entityKeyMetadata; } /** * Returns meta-data about the entity key referenced by associations of this key family. * * @return meta-data about the entity key referenced by associations of this key family. */ @Override public AssociatedEntityKeyMetadata getAssociatedEntityKeyMetadata() { return associatedEntityKeyMetadata; } /** * Returns all those columns from the given candidate list which are not part of this key family. * <p> * Stores can opt to persist only the returned columns when writing a row of this key family. All other columns can * be retrieved from the key meta-data itself when reading an association row. * <p> * The column order is not preserved. */ @Override public String[] getColumnsWithoutKeyColumns(Iterable<String> candidates) { List<String> nonKeyColumns = new ArrayList<String>(); for ( String column : candidates ) { // exclude columns from the associationKey as they can be guessed via metadata if ( !isKeyColumn( column ) ) { nonKeyColumns.add( column ); } } return nonKeyColumns.toArray( new String[nonKeyColumns.size()] ); } /** * Returns the name of the single row key column which is not a column of this key itself, in case such a column * exists. * <p> * If e.g. an association key contains the column "bankAccounts_id" and the row key columns are "bankAccounts_id" * and "owners_id", this method will return "owners_id". But if the row columns were "bankAccounts_id", "owners_id" * and "order", {@code null} would be returned as there were more than one column which are not part of the * association key. * * @return the name of the single row key column which is not a column of this key itself or {@code null} if there * is either no or more than one such column. */ @Override public String getSingleRowKeyColumnNotContainedInAssociationKey() { String nonKeyColumn = null; for ( String column : getRowKeyColumnNames() ) { if ( !isKeyColumn( column ) ) { if ( nonKeyColumn != null ) { // more than one column which is not contained in the association key; thus rows of this // association will be represented by DBObjects rather than single String, int etc. objects return null; } else { nonKeyColumn = column; } } } return nonKeyColumn; } /** * Whether the given column is part of this key family or not. * * @return {@code true} if the given column is part of this key, {@code false} otherwise. */ @Override public boolean isKeyColumn(String columnName) { for ( String keyColumName : getColumnNames() ) { if ( keyColumName.equals( columnName ) ) { return true; } } return false; } /** * Whether this key meta-data represents the inverse side of a bi-directional association. * * @return {@code true} if this key meta-data represents the inverse side of a bi-directional association, * {@code false} otherwise. */ @Override public boolean isInverse() { return isInverse; } /** * Returns the association role. */ @Override public String getCollectionRole() { return collectionRole; } /** * Returns the type of association */ @Override public AssociationKind getAssociationKind() { return associationKind; } @Override public AssociationType getAssociationType() { return associationType; } @Override public boolean equals(Object o) { if ( this == o ) { return true; } if ( o == null || DefaultAssociationKeyMetadata.class != o.getClass() ) { return false; } DefaultAssociationKeyMetadata that = (DefaultAssociationKeyMetadata) o; // order of comparison matters on performance: if ( !table.equals( that.table ) ) { return false; } if ( !Arrays.equals( columnNames, that.columnNames ) ) { return false; } return true; } @Override public int hashCode() { return hashCode; } @Override public String toString() { return "DefaultAssociationKeyMetadata [table=" + table + ", columnNames=" + Arrays.toString( columnNames ) + ", isInverse=" + isInverse + ", collectionRole=" + collectionRole + ", associationKind=" + associationKind + ", rowKeyColumnNames=" + Arrays.toString( rowKeyColumnNames ) + ", rowKeyIndexColumnNames=" + Arrays.toString( rowKeyIndexColumnNames ) + ", associatedEntityKeyMetadata=" + associatedEntityKeyMetadata + "]"; } }