/*
* 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.datastore.document.association.spi;
import org.hibernate.ogm.datastore.document.association.impl.DocumentHelpers;
import org.hibernate.ogm.datastore.document.association.spi.AssociationRow.AssociationRowAccessor;
import org.hibernate.ogm.model.key.spi.AssociationKey;
import org.hibernate.ogm.util.impl.Contracts;
/**
* Base class for {@link AssociationRowFactory} implementations which support association rows stored as key/value
* tuples as well as rows stored as collections of single values.
* Also removes top property from embedded id references.
* This is opening the way to optimise the storage and remove the column name from the structure for collections of
* single values.
* <p>
* The single value form may be used to persist association rows with exactly one column (which is the case for collections of
* simple values such as {@code int}s, {@code String} s etc. as well as associations based on non-composite keys). In
* this case a row object of type {@code R} will be created using the column value and the single row key column which
* is not part of the association key.
* <p>
* For rows with more than one column it is assumed that they are already of type {@code R} and they are thus passed
* through as is.
* If the referenced entity uses composite keys, the embedded id property name is removed from the reference.
* <code>
* 'games': [ { 'id.id1': 'foo', 'id.id2': 'bar' } ]
* or
* 'games': [ { 'id': { 'id1': 'foo', 'id2': 'bar' } } ]
*
* are replaced by
* 'games': [ { 'id1': 'foo', 'id2': 'bar' } ]
* </code>
*
* @author Gunnar Morling
* @author Emmanuel Bernard <emmanuel@hibernate.org>
* @param <R> The type of key/value association rows supported by this factory.
*/
public abstract class StructureOptimizerAssociationRowFactory<R> implements AssociationRowFactory {
/**
* The type of key/value association rows supported by this factory; This basically corresponds to {@code Class<R>}
* but this form is used to support parameterized types such as {@code Map}.
*/
private final Class<?> associationRowType;
protected StructureOptimizerAssociationRowFactory(Class<?> associationRowType) {
this.associationRowType = associationRowType;
}
@Override
@SuppressWarnings("unchecked")
public AssociationRow<?> createAssociationRow(AssociationKey associationKey, Object row) {
R rowObject = null;
AssociationRowAccessor<R> accessor;
if ( associationRowType.isInstance( row ) ) {
// if the columns are only made of the embedded id columns, add back the embedded id property prefix
// { id1: "foo", id2: "bar" } becomes { embeddedid.id1: "foo", "embeddedid.id2: "bar" }
String[] associationKeyColumns = associationKey.getMetadata()
.getAssociatedEntityKeyMetadata()
.getAssociationKeyColumns();
String prefix = DocumentHelpers.getColumnSharedPrefix( associationKeyColumns );
// pass the columns that are not prefixed and the prefix
accessor = getAssociationRowAccessor( associationKeyColumns, prefix );
rowObject = (R) row;
}
else {
accessor = getAssociationRowAccessor( null, null );
String columnName = associationKey.getMetadata().getSingleRowKeyColumnNotContainedInAssociationKey();
Contracts.assertNotNull( columnName, "columnName" );
rowObject = getSingleColumnRow( columnName, row );
}
return new AssociationRow<R>( associationKey, accessor, rowObject );
}
/**
* Creates a row object with the given column name and value.
*/
protected abstract R getSingleColumnRow(String columnName, Object value);
/**
* Returns the {@link AssociationRowAccessor} to be used to obtain values from the {@link AssociationRow}
* created by this factory.
* If some columns lost their prefix in the persisted representation, pass the prefixedColumns
* and the prefix. Otherwise the prefix is null.
*/
protected abstract AssociationRowAccessor<R> getAssociationRowAccessor(String[] prefixedColumns, String prefix);
}