/* * Hibernate, Relational Persistence for Idiomatic Java * * JBoss, Home of Professional Open Source * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors * as indicated by the @authors tag. All rights reserved. * See the copyright.txt in the distribution for a * full listing of individual contributors. * * This copyrighted material is made available to anyone wishing to use, * modify, copy, or redistribute it subject to the terms and conditions * of the GNU Lesser General Public License, v. 2.1. * This program is distributed in the hope that it will be useful, but WITHOUT A * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * You should have received a copy of the GNU Lesser General Public License, * v.2.1 along with this distribution; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ package org.hibernate.ogm.persister; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.ogm.datastore.impl.EmptyTupleSnapshot; import org.hibernate.ogm.datastore.spi.Association; import org.hibernate.ogm.datastore.spi.Tuple; import org.hibernate.ogm.dialect.GridDialect; import org.hibernate.ogm.grid.RowKey; import org.hibernate.ogm.grid.impl.RowKeyBuilder; import org.hibernate.ogm.type.GridType; import org.hibernate.ogm.util.impl.Log; import org.hibernate.ogm.util.impl.LoggerFactory; import org.hibernate.ogm.util.impl.LogicalPhysicalConverterHelper; import org.hibernate.ogm.util.impl.PropertyMetadataProvider; import org.hibernate.pretty.MessageHelper; import org.hibernate.tuple.entity.EntityMetamodel; import org.hibernate.type.Type; import java.io.Serializable; class EntityDehydrator { private static final Log log = LoggerFactory.make(); private Tuple resultset; private Object[] fields; private boolean[] includeProperties; private boolean[][] includeColumns; private int tableIndex; private Serializable id; private SessionImplementor session; private GridType[] gridPropertyTypes; private OgmEntityPersister persister; private boolean addPropertyMetadata = true; private boolean dehydrate = true; private boolean removePropertyMetadata = true; private GridType gridIdentifierType; private GridDialect gridDialect; // fluent methods populating data public EntityDehydrator persister(OgmEntityPersister persister) { this.persister = persister; return this; } public EntityDehydrator gridPropertyTypes(GridType[] gridPropertyTypes) { this.gridPropertyTypes = gridPropertyTypes; return this; } public EntityDehydrator gridIdentifierType(GridType gridIdentifierType) { this.gridIdentifierType = gridIdentifierType; return this; } public EntityDehydrator resultset(Tuple resultset) { this.resultset = resultset; return this; } public EntityDehydrator fields(Object[] fields) { this.fields = fields; return this; } public EntityDehydrator includeProperties(boolean[] includeProperties) { this.includeProperties = includeProperties; return this; } public EntityDehydrator includeColumns(boolean[][] includeColumns) { this.includeColumns = includeColumns; return this; } public EntityDehydrator tableIndex(int tableIndex) { this.tableIndex = tableIndex; return this; } public EntityDehydrator id(Serializable id) { this.id = id; return this; } public EntityDehydrator session(SessionImplementor session) { this.session = session; return this; } public EntityDehydrator gridDialect(GridDialect gridDialect) { this.gridDialect = gridDialect; return this; } //action methods public EntityDehydrator onlyRemovePropertyMetadata() { this.addPropertyMetadata = false; this.dehydrate = false; this.removePropertyMetadata = true; return this; } public void dehydrate() { if ( log.isTraceEnabled() ) { log.trace( "Dehydrating entity: " + MessageHelper.infoString( persister, id, persister.getFactory() ) ); } final EntityMetamodel entityMetamodel = persister.getEntityMetamodel(); final boolean[] uniqueness = persister.getPropertyUniqueness(); final Type[] propertyTypes = persister.getPropertyTypes(); for ( int propertyIndex = 0; propertyIndex < entityMetamodel.getPropertySpan(); propertyIndex++ ) { if ( persister.isPropertyOfTable( propertyIndex, tableIndex ) ) { final Type propertyType = propertyTypes[propertyIndex]; boolean isStarToOne = propertyType.isAssociationType() && ! propertyType.isCollectionType(); final boolean createMetadata = isStarToOne || uniqueness[propertyIndex]; if ( removePropertyMetadata && createMetadata ) { //remove from property cache Object[] oldColumnValues = LogicalPhysicalConverterHelper.getColumnValuesFromResultset( resultset, persister.getPropertyColumnNames( propertyIndex ) ); //don't index null columns, this means no association if ( ! isEmptyOrAllColumnsNull( oldColumnValues ) ) { doRemovePropertyMetadata( tableIndex, propertyIndex, oldColumnValues); } } if ( dehydrate && includeProperties[propertyIndex] ) { //dehydrate gridPropertyTypes[propertyIndex].nullSafeSet( resultset, fields[propertyIndex], persister.getPropertyColumnNames( propertyIndex ), includeColumns[propertyIndex], session ); } if ( addPropertyMetadata && createMetadata ) { //add to property cache Object[] newColumnValues = LogicalPhysicalConverterHelper.getColumnValuesFromResultset( resultset, persister.getPropertyColumnNames( propertyIndex ) ); //don't index null columns, this means no association if ( ! isEmptyOrAllColumnsNull( newColumnValues ) ) { doAddPropertyMetadata( tableIndex, propertyIndex, newColumnValues); } } } } } private void doAddPropertyMetadata(int tableIndex, int propertyIndex, Object[] newColumnValue) { String[] propertyColumnNames = persister.getPropertyColumnNames( propertyIndex ); String[] rowKeyColumnNames = buildRowKeyColumnNamesForStarToOne( persister, propertyColumnNames ); PropertyMetadataProvider metadataProvider = new PropertyMetadataProvider() .gridDialect(gridDialect) .keyColumnNames( propertyColumnNames ) .keyColumnValues( newColumnValue ) .session( session ) //does not set .collectionPersister as it does not make sense here for a ToOne or a unique key .tableName( persister.getTableName( tableIndex ) ) .propertyType( persister.getPropertyTypes()[propertyIndex] ) .rowKeyColumnNames( rowKeyColumnNames ); Tuple tuple = new Tuple( EmptyTupleSnapshot.SINGLETON ); //add the id column final String[] identifierColumnNames = persister.getIdentifierColumnNames(); gridIdentifierType.nullSafeSet( tuple, id, identifierColumnNames, session ); //add the fk column gridPropertyTypes[propertyIndex].nullSafeSet( tuple, fields[propertyIndex], propertyColumnNames, includeColumns[propertyIndex], session ); Object[] columnValues = LogicalPhysicalConverterHelper.getColumnValuesFromResultset(tuple, rowKeyColumnNames); final RowKey rowKey = new RowKey( persister.getTableName(), rowKeyColumnNames, columnValues ); Tuple assocEntryTuple = metadataProvider.createAndPutAssociationTuple( rowKey ); for ( String column : tuple.getColumnNames() ) { assocEntryTuple.put(column, tuple.get(column) ); } metadataProvider.flushToCache(); } // Here the RowKey is made of the foreign key columns pointing to the associated entity // and the identifier columns of the owner's entity // We use the same order as the collection: id column names, foreign key column names public static String[] buildRowKeyColumnNamesForStarToOne(OgmEntityPersister persister, String[] keyColumnNames) { String[] identifierColumnNames = persister.getIdentifierColumnNames(); int length = identifierColumnNames.length + keyColumnNames.length; String[] rowKeyColumnNames = new String[length]; System.arraycopy( identifierColumnNames, 0, rowKeyColumnNames, 0, identifierColumnNames.length ); System.arraycopy( keyColumnNames, 0, rowKeyColumnNames, identifierColumnNames.length, keyColumnNames.length ); return rowKeyColumnNames; } private void doRemovePropertyMetadata(int tableIndex, int propertyIndex, Object[] oldColumnValue) { String[] propertyColumnNames = persister.getPropertyColumnNames( propertyIndex ); String[] rowKeyColumnNames = buildRowKeyColumnNamesForStarToOne( persister, propertyColumnNames ); PropertyMetadataProvider metadataProvider = new PropertyMetadataProvider() .gridDialect(gridDialect) .keyColumnNames( propertyColumnNames ) .keyColumnValues( oldColumnValue ) .session( session ) //does not set .collectionPersister as it does not make sense here for a ToOne or a unique key .tableName( persister.getTableName( tableIndex ) ) .propertyType( persister.getPropertyTypes()[propertyIndex] ) .rowKeyColumnNames( rowKeyColumnNames ); //add fk column value in TupleKey Tuple tupleKey = new Tuple( EmptyTupleSnapshot.SINGLETON ); for (int index = 0 ; index < propertyColumnNames.length ; index++) { tupleKey.put( propertyColumnNames[index], oldColumnValue[index] ); } //add id value in TupleKey gridIdentifierType.nullSafeSet( tupleKey, id, persister.getIdentifierColumnNames(), session ); Association propertyValues = metadataProvider.getCollectionMetadata(); if ( propertyValues != null ) { //Map's equals operation delegates to all it's key and value, should be fine for now //this is a StarToOne case ie the FK is on the owning entity final RowKey matchingTuple = new RowKeyBuilder() .tableName( persister.getTableName() ) .addColumns( buildRowKeyColumnNamesForStarToOne( persister, propertyColumnNames ) ) .values( tupleKey ) .build(); //TODO what should we do if that's null? metadataProvider.getCollectionMetadata().remove( matchingTuple ); metadataProvider.flushToCache(); } } private boolean isEmptyOrAllColumnsNull(Object[] objects) { for ( Object object : objects ) { if ( object != null ) return false; } return true; } }