/*
* 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.test.loading;
import static org.fest.assertions.Assertions.assertThat;
import static org.hibernate.ogm.util.impl.TransactionContextHelper.transactionContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.ogm.datastore.document.cfg.DocumentStoreProperties;
import org.hibernate.ogm.datastore.document.options.AssociationStorageType;
import org.hibernate.ogm.datastore.mongodb.MongoDBDialect;
import org.hibernate.ogm.datastore.mongodb.MongoDBProperties;
import org.hibernate.ogm.datastore.mongodb.dialect.impl.MongoDBAssociationSnapshot;
import org.hibernate.ogm.datastore.mongodb.dialect.impl.MongoDBTupleSnapshot;
import org.hibernate.ogm.datastore.mongodb.impl.MongoDBDatastoreProvider;
import org.hibernate.ogm.datastore.mongodb.options.AssociationDocumentStorageType;
import org.hibernate.ogm.datastore.spi.DatastoreProvider;
import org.hibernate.ogm.dialect.impl.AssociationContextImpl;
import org.hibernate.ogm.dialect.impl.AssociationTypeContextImpl;
import org.hibernate.ogm.dialect.spi.AssociationContext;
import org.hibernate.ogm.dialect.spi.GridDialect;
import org.hibernate.ogm.dialect.spi.TupleContext;
import org.hibernate.ogm.entityentry.impl.TuplePointer;
import org.hibernate.ogm.model.impl.DefaultAssociatedEntityKeyMetadata;
import org.hibernate.ogm.model.impl.DefaultAssociationKeyMetadata;
import org.hibernate.ogm.model.impl.DefaultEntityKeyMetadata;
import org.hibernate.ogm.model.key.spi.AssociationKey;
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.EntityKey;
import org.hibernate.ogm.model.spi.Association;
import org.hibernate.ogm.model.spi.Tuple;
import org.hibernate.ogm.model.spi.Tuple.SnapshotType;
import org.hibernate.ogm.options.navigation.impl.OptionsContextImpl;
import org.hibernate.ogm.options.navigation.source.impl.OptionValueSources;
import org.hibernate.ogm.options.spi.Option;
import org.hibernate.ogm.options.spi.OptionsContext;
import org.hibernate.ogm.options.spi.UniqueOption;
import org.hibernate.ogm.util.configurationreader.spi.ConfigurationPropertyReader;
import org.hibernate.ogm.utils.EmptyOptionsContext;
import org.hibernate.ogm.utils.GridDialectOperationContexts;
import org.hibernate.ogm.utils.OgmTestCase;
import org.hibernate.service.Service;
import org.hibernate.service.spi.ServiceRegistryImplementor;
import org.junit.Test;
import com.mongodb.client.MongoCollection;
import com.mongodb.client.MongoDatabase;
import org.bson.Document;
/**
* @author Guillaume Scheibel <guillaume.scheibel@gmail.com>
*/
public class LoadSelectedColumnsCollectionTest extends OgmTestCase {
@Test
public void testLoadSelectedColumns() {
final String collectionName = "Drink";
MongoDBDatastoreProvider provider = (MongoDBDatastoreProvider) this.getService( DatastoreProvider.class );
MongoDatabase database = provider.getDatabase();
MongoCollection<Document> collection = database.getCollection( collectionName );
Document water = new Document();
water.put( "_id", "1234" );
water.put( "name", "Water" );
water.put( "volume", "1L" );
collection.insertOne( water );
List<String> selectedColumns = new ArrayList<>();
selectedColumns.add( "name" );
Tuple tuple = this.getTuple( collectionName, "1234", selectedColumns );
assertNotNull( tuple );
Set<String> retrievedColumn = tuple.getColumnNames();
/*
* The dialect will return all columns (which include _id field) so we have to substract 1 to check if
* the right number of columns has been loaded.
*/
assertEquals( selectedColumns.size(), retrievedColumn.size() - 1 );
assertTrue( retrievedColumn.containsAll( selectedColumns ) );
collection.deleteMany( water );
}
@Test
public void testLoadSelectedAssociationColumns() {
Session session = openSession();
final Transaction transaction = session.getTransaction();
transaction.begin();
Module mongodb = new Module();
mongodb.setName( "MongoDB" );
session.persist( mongodb );
Module infinispan = new Module();
infinispan.setName( "Infinispan" );
session.persist( infinispan );
List<Module> modules = new ArrayList<>();
modules.add( mongodb );
modules.add( infinispan );
Project hibernateOGM = new Project();
hibernateOGM.setId( "projectID" );
hibernateOGM.setName( "HibernateOGM" );
hibernateOGM.setModules( modules );
session.persist( hibernateOGM );
transaction.commit();
this.addExtraColumn();
AssociationKeyMetadata metadata = new DefaultAssociationKeyMetadata.Builder()
.table( "Project_Module" )
.columnNames( new String[] { "Project_id" } )
.rowKeyColumnNames( new String[] { "Project_id", "module_id" } )
.associatedEntityKeyMetadata( new DefaultAssociatedEntityKeyMetadata( new String[] { "module_id" }, new DefaultEntityKeyMetadata( "Module", new String[] { "id" } ) ) )
.inverse( false )
.collectionRole( "modules" )
.associationKind( AssociationKind.ASSOCIATION )
.associationType( AssociationType.LIST )
.build();
AssociationKey associationKey = new AssociationKey(
metadata,
new Object[] { "projectID" },
new EntityKey(
new DefaultEntityKeyMetadata( "Project", new String[] { "id" } ),
new String[] { "projectID" }
)
);
AssociationContext associationContext = new AssociationContextImpl(
new AssociationTypeContextImpl(
OptionsContextImpl.forProperty(
OptionValueSources.getDefaultSources( new ConfigurationPropertyReader( getSessionFactory().getProperties(), new ClassLoaderServiceImpl() ) ),
Project.class,
"modules"
),
EmptyOptionsContext.INSTANCE,
GridDialectOperationContexts.emptyTupleTypeContext(),
new DefaultAssociatedEntityKeyMetadata( null, null ),
null
),
new TuplePointer( new Tuple( new MongoDBTupleSnapshot( null, null ), SnapshotType.UPDATE ) ),
transactionContext( session )
);
final Association association = getService( GridDialect.class ).getAssociation( associationKey, associationContext );
final MongoDBAssociationSnapshot associationSnapshot = (MongoDBAssociationSnapshot) association.getSnapshot();
final Document assocObject = associationSnapshot.getDocument();
this.checkLoading( assocObject );
session.delete( mongodb );
session.delete( infinispan );
session.delete( hibernateOGM );
session.close();
}
private Tuple getTuple(String collectionName, String id, List<String> selectedColumns) {
EntityKey key = new EntityKey(
new DefaultEntityKeyMetadata( collectionName, new String[] { MongoDBDialect.ID_FIELDNAME } ),
new Object[] { id }
);
TupleContext tupleContext = new GridDialectOperationContexts.TupleContextBuilder()
.tupleTypeContext(
new GridDialectOperationContexts.TupleTypeContextBuilder()
.selectableColumns( selectedColumns )
.optionContext( TestOptionContext.INSTANCE )
.buildTupleTypeContext() )
.buildTupleContext();
return getService( GridDialect.class ).getTuple( key, tupleContext );
}
protected <S extends Service> S getService(Class<S> serviceRole) {
SessionFactoryImplementor factory = super.getSessionFactory();
ServiceRegistryImplementor serviceRegistry = factory.getServiceRegistry();
return serviceRegistry.getService( serviceRole );
}
@Override
protected void configure(Map<String, Object> settings) {
settings.put(
DocumentStoreProperties.ASSOCIATIONS_STORE,
AssociationStorageType.ASSOCIATION_DOCUMENT
);
settings.put(
MongoDBProperties.ASSOCIATION_DOCUMENT_STORAGE,
AssociationDocumentStorageType.COLLECTION_PER_ASSOCIATION
);
}
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class<?>[] { Project.class, Module.class };
}
/**
* To be sure the datastoreProvider retrieves only the columns we want,
* an extra column is manually added to the association document
*/
protected void addExtraColumn() {
MongoDBDatastoreProvider provider = (MongoDBDatastoreProvider) this.getService( DatastoreProvider.class );
MongoDatabase database = provider.getDatabase();
MongoCollection<Document> collection = database.getCollection( "associations_Project_Module" );
Document query = new Document( );
query.put( "_id", new Document( "Project_id", "projectID" ) );
Document updater = new Document( );
updater.put( "$push", new Document( "extraColumn", 1 ) );
collection.updateMany( query, updater );
}
protected void checkLoading(Document associationObject) {
/*
* The only column (except _id) that needs to be retrieved is "rows"
* So we should have 2 columns
*/
final Set<?> retrievedColumns = associationObject.keySet();
assertThat( retrievedColumns ).hasSize( 2 ).containsOnly( MongoDBDialect.ID_FIELDNAME, MongoDBDialect.ROWS_FIELDNAME );
}
private static class TestOptionContext implements OptionsContext {
public static OptionsContext INSTANCE = new TestOptionContext();
@Override
public <I, V, O extends Option<I, V>> V get(Class<O> optionType, I identifier) {
try {
Option<I,V> optionInstance = optionType.newInstance();
return optionInstance.getDefaultValue( new ConfigurationPropertyReader( Collections.EMPTY_MAP ) );
}
catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException( e );
}
}
@Override
public <V, O extends UniqueOption<V>> V getUnique(Class<O> optionType) {
try {
UniqueOption<V> optionInstance = optionType.newInstance();
return optionInstance.getDefaultValue( new ConfigurationPropertyReader( Collections.EMPTY_MAP ) );
}
catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException( e );
}
}
@Override
public <I, V, O extends Option<I, V>> Map<I, V> getAll(Class<O> optionType) {
return null;
}
}
}