/* * 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.impl; import java.util.Map; import java.util.regex.Pattern; import org.hibernate.ogm.datastore.document.association.impl.DocumentHelpers; import org.hibernate.ogm.datastore.document.options.MapStorageType; import org.hibernate.ogm.datastore.document.options.spi.MapStorageOption; import org.hibernate.ogm.dialect.spi.AssociationContext; import org.hibernate.ogm.model.key.spi.AssociationKey; /** * Provides functionality for dealing with (nested) fields of Map documents. * * @author Alan Fitton <alan at eth0.org.uk> * @author Emmanuel Bernard <emmanuel@hibernate.org> * @author Gunnar Morling * @author Mark Paluch */ public class DotPatternMapHelpers { private static final Pattern DOT_SEPARATOR_PATTERN = Pattern.compile( "\\." ); /** * Remove a column from the Map * * @param entity the {@link Map} with the column * @param column the column to remove */ public static void resetValue(Map<?, ?> entity, String column) { // fast path for non-embedded case if ( !column.contains( "." ) ) { entity.remove( column ); } else { String[] path = DOT_SEPARATOR_PATTERN.split( column ); Object field = entity; int size = path.length; for ( int index = 0; index < size; index++ ) { String node = path[index]; Map parent = (Map) field; field = parent.get( node ); if ( field == null && index < size - 1 ) { //TODO clean up the hierarchy of empty containers // no way to reach the leaf, nothing to do return; } if ( index == size - 1 ) { parent.remove( node ); } } } } public static boolean hasField(Map entity, String dotPath) { return getValueOrNull( entity, dotPath ) != null; } public static <T> T getValueOrNull(Map entity, String dotPath, Class<T> type) { Object value = getValueOrNull( entity, dotPath ); return type.isInstance( value ) ? type.cast( value ) : null; } public static Object getValueOrNull(Map entity, String dotPath) { // fast path for simple properties if ( !dotPath.contains( "." ) ) { return entity.get( dotPath ); } String[] path = DOT_SEPARATOR_PATTERN.split( dotPath ); int size = path.length; for ( int index = 0; index < size - 1; index++ ) { Object next = entity.get( path[index] ); if ( next == null || !( next instanceof Map ) ) { return null; } entity = (Map) next; } String field = path[size - 1]; return entity.get( field ); } /** * Links the two field names into a single left.right field name. * If the left field is empty, right is returned * * @param left one field name * @param right the other field name * * @return left.right or right if left is an empty string */ public static String flatten(String left, String right) { return left == null || left.isEmpty() ? right : left + "." + right; } /** * Whether the rows of the given association should be stored in a hash using the single row key column as key or * not. */ public static boolean organizeAssociationMapByRowKey( org.hibernate.ogm.model.spi.Association association, AssociationKey key, AssociationContext associationContext) { if ( association.isEmpty() ) { return false; } if ( key.getMetadata().getRowKeyIndexColumnNames().length != 1 ) { return false; } Object valueOfFirstRow = association.get( association.getKeys().iterator().next() ) .get( key.getMetadata().getRowKeyIndexColumnNames()[0] ); if ( !( valueOfFirstRow instanceof String ) ) { return false; } // The list style may be explicitly enforced for compatibility reasons return getMapStorage( associationContext ) == MapStorageType.BY_KEY; } private static MapStorageType getMapStorage(AssociationContext associationContext) { return associationContext.getAssociationTypeContext().getOptionsContext().getUnique( MapStorageOption.class ); } /** * Returns the shared prefix with a trailing dot (.) of the association columns or {@literal null} if there is no shared prefix. * * @param associationKey the association key * * @return the shared prefix with a trailing dot (.) of the association columns or {@literal null} */ public static String getColumnSharedPrefixOfAssociatedEntityLink(AssociationKey associationKey) { String[] associationKeyColumns = associationKey.getMetadata() .getAssociatedEntityKeyMetadata() .getAssociationKeyColumns(); // we used to check that columns are the same (in an ordered fashion) // but to handle List and Map and store indexes / keys at the same level as the id columns // this check is removed String prefix = DocumentHelpers.getColumnSharedPrefix( associationKeyColumns ); return prefix == null ? "" : prefix + "."; } }