/* * 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.mongodb.dialect.impl; import static org.hibernate.ogm.datastore.mongodb.dialect.impl.MongoHelpers.getValueOrNull; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import org.hibernate.ogm.datastore.document.association.impl.DocumentHelpers; import org.hibernate.ogm.datastore.document.association.spi.AssociationRows; import org.hibernate.ogm.datastore.mongodb.MongoDBDialect; import org.hibernate.ogm.model.key.spi.AssociationKey; import org.hibernate.ogm.model.key.spi.AssociationType; import org.bson.Document; /** * An association snapshot based on a {@link Document} retrieved from MongoDB. * * @author Alan Fitton <alan at eth0.org.uk> * @author Emmanuel Bernard <emmanuel@hibernate.org> * @author Gunnar Morling */ public class MongoDBAssociationSnapshot extends AssociationRows { private static final String EMBEDDABLE_COLUMN_PREFIX = ".value."; private final Document dbObject; public MongoDBAssociationSnapshot(Document document, AssociationKey associationKey, AssociationStorageStrategy storageStrategy) { super( associationKey, getRows( document, associationKey, storageStrategy ), MongoDBAssociationRowFactory.INSTANCE ); this.dbObject = document; } //not for embedded public Document getQueryObject() { Document query = new Document(); query.put( MongoDBDialect.ID_FIELDNAME, dbObject.get( MongoDBDialect.ID_FIELDNAME ) ); return query; } private static Collection<?> getRows(Document document, AssociationKey associationKey, AssociationStorageStrategy storageStrategy) { Collection<?> rows = null; if ( associationKey.getMetadata().getAssociationType() == AssociationType.ONE_TO_ONE ) { Object oneToOneValue = getValueOrNull( document, associationKey.getMetadata().getCollectionRole(), Object.class ); if ( oneToOneValue != null ) { rows = Collections.singletonList( oneToOneValue ); } } else { Object toManyValue; if ( storageStrategy == AssociationStorageStrategy.IN_ENTITY ) { toManyValue = getValueOrNull( document, associationKey.getMetadata().getCollectionRole() ); } else { toManyValue = document.get( MongoDBDialect.ROWS_FIELDNAME ); } // list of rows if ( toManyValue instanceof Collection ) { rows = (Collection<?>) toManyValue; } // a map-typed association, rows are organized by row key else if ( toManyValue instanceof Document ) { rows = getRowsFromMapAssociation( associationKey, (Document) toManyValue ); } } return rows != null ? rows : Collections.emptyList(); } /** * Restores the list representation of the given map-typed association. E.g. { 'home' : 123, 'work' : 456 } will be * transformed into [{ 'addressType='home', 'address_id'=123}, { 'addressType='work', 'address_id'=456} ]) as * expected by the row accessor. */ private static Collection<?> getRowsFromMapAssociation(AssociationKey associationKey, Document value) { String rowKeyIndexColumn = associationKey.getMetadata().getRowKeyIndexColumnNames()[0]; List<Document> rows = new ArrayList<Document>(); String[] associationKeyColumns = associationKey.getMetadata() .getAssociatedEntityKeyMetadata() .getAssociationKeyColumns(); // Omit shared prefix of compound ids, will be handled in the row accessor String prefix = DocumentHelpers.getColumnSharedPrefix( associationKeyColumns ); prefix = prefix == null ? "" : prefix + "."; String embeddedValueColumnPrefix = associationKey.getMetadata().getCollectionRole() + EMBEDDABLE_COLUMN_PREFIX; // restore the list representation for ( String rowKey : value.keySet() ) { Object mapRow = value.get( rowKey ); // include the row key index column Document row = new Document(); row.put( rowKeyIndexColumn, rowKey ); // several value columns, copy them all if ( mapRow instanceof Document ) { for ( String column : associationKey.getMetadata().getAssociatedEntityKeyMetadata().getAssociationKeyColumns() ) { // The column is part of an element collection; Restore the "value" node in the hierarchy if ( column.startsWith( embeddedValueColumnPrefix ) ) { MongoHelpers.setValue( row, column.substring( associationKey.getMetadata().getCollectionRole().length() + 1 ), ( (Document) mapRow ).get( column.substring( embeddedValueColumnPrefix.length() ) ) ); } else { row.put( column.substring( prefix.length() ), ( (Document) mapRow ).get( column.substring( prefix.length() ) ) ); } } } // single value column else { row.put( associationKey.getMetadata().getAssociatedEntityKeyMetadata().getAssociationKeyColumns()[0], mapRow ); } rows.add( row ); } return rows; } // TODO This only is used for tests; Can we get rid of it? public Document getDocument() { return this.dbObject; } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append( "MongoDBAssociationSnapshot(" ); sb.append( size() ); sb.append( ") RowKey entries)." ); return sb.toString(); } }