org.hibernate.cfg.ResultSetMappingBinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate Show documentation
Show all versions of hibernate Show documentation
Relational Persistence for Java
//$Id: ResultSetMappingBinder.java 10181 2006-07-28 20:17:50Z epbernard $
package org.hibernate.cfg;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import org.dom4j.Element;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.engine.query.sql.NativeSQLQueryCollectionReturn;
import org.hibernate.engine.ResultSetMappingDefinition;
import org.hibernate.engine.query.sql.NativeSQLQueryJoinReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryRootReturn;
import org.hibernate.engine.query.sql.NativeSQLQueryScalarReturn;
import org.hibernate.mapping.Component;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Value;
import org.hibernate.mapping.Property;
import org.hibernate.mapping.ToOne;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.StringHelper;
/**
* @author Emmanuel Bernard
*/
public abstract class ResultSetMappingBinder {
/**
* Build a ResultSetMappingDefinition given a containing element for the "return-XXX" elements
*
* @param resultSetElem The element containing the return definitions.
* @param path No clue...
* @param mappings The current processing state.
* @return The description of the mappings...
*/
protected static ResultSetMappingDefinition buildResultSetMappingDefinition(Element resultSetElem, String path, Mappings mappings) {
String resultSetName = resultSetElem.attribute( "name" ).getValue();
if ( path != null ) {
resultSetName = path + '.' + resultSetName;
}
ResultSetMappingDefinition definition = new ResultSetMappingDefinition( resultSetName );
int cnt = 0;
Iterator returns = resultSetElem.elementIterator();
while ( returns.hasNext() ) {
cnt++;
Element returnElem = (Element) returns.next();
String name = returnElem.getName();
if ( "return-scalar".equals( name ) ) {
String column = returnElem.attributeValue( "column" );
String typeFromXML = HbmBinder.getTypeFromXML( returnElem );
Type type = null;
if(typeFromXML!=null) {
type = TypeFactory.heuristicType( typeFromXML );
if ( type == null ) {
throw new MappingException( "could not determine type " + type );
}
}
definition.addQueryReturn( new NativeSQLQueryScalarReturn( column, type ) );
}
else if ( "return".equals( name ) ) {
definition.addQueryReturn( bindReturn( returnElem, mappings, cnt ) );
}
else if ( "return-join".equals( name ) ) {
definition.addQueryReturn( bindReturnJoin( returnElem, mappings ) );
}
else if ( "load-collection".equals( name ) ) {
definition.addQueryReturn( bindLoadCollection( returnElem, mappings ) );
}
}
return definition;
}
private static NativeSQLQueryRootReturn bindReturn(Element returnElem, Mappings mappings, int elementCount) {
String alias = returnElem.attributeValue( "alias" );
if( StringHelper.isEmpty(alias)) {
alias = "alias_" + elementCount; // hack/workaround as sqlquery impl depend on having a key.
}
String entityName = HbmBinder.getEntityName(returnElem, mappings);
if(entityName==null) {
throw new MappingException( " must specify either a class or entity-name");
}
LockMode lockMode = getLockMode( returnElem.attributeValue( "lock-mode" ) );
PersistentClass pc = mappings.getClass( entityName );
java.util.Map propertyResults = bindPropertyResults(alias, returnElem, pc, mappings );
return new NativeSQLQueryRootReturn(
alias,
entityName,
propertyResults,
lockMode
);
}
private static NativeSQLQueryJoinReturn bindReturnJoin(Element returnElem, Mappings mappings) {
String alias = returnElem.attributeValue( "alias" );
String roleAttribute = returnElem.attributeValue( "property" );
LockMode lockMode = getLockMode( returnElem.attributeValue( "lock-mode" ) );
int dot = roleAttribute.lastIndexOf( '.' );
if ( dot == -1 ) {
throw new MappingException(
"Role attribute for sql query return [alias=" + alias +
"] not formatted correctly {owningAlias.propertyName}"
);
}
String roleOwnerAlias = roleAttribute.substring( 0, dot );
String roleProperty = roleAttribute.substring( dot + 1 );
//FIXME: get the PersistentClass
java.util.Map propertyResults = bindPropertyResults(alias, returnElem, null, mappings );
return new NativeSQLQueryJoinReturn(
alias,
roleOwnerAlias,
roleProperty,
propertyResults, // TODO: bindpropertyresults(alias, returnElem)
lockMode
);
}
private static NativeSQLQueryCollectionReturn bindLoadCollection(Element returnElem, Mappings mappings) {
String alias = returnElem.attributeValue( "alias" );
String collectionAttribute = returnElem.attributeValue( "role" );
LockMode lockMode = getLockMode( returnElem.attributeValue( "lock-mode" ) );
int dot = collectionAttribute.lastIndexOf( '.' );
if ( dot == -1 ) {
throw new MappingException(
"Collection attribute for sql query return [alias=" + alias +
"] not formatted correctly {OwnerClassName.propertyName}"
);
}
String ownerClassName = HbmBinder.getClassName( collectionAttribute.substring( 0, dot ), mappings );
String ownerPropertyName = collectionAttribute.substring( dot + 1 );
//FIXME: get the PersistentClass
java.util.Map propertyResults = bindPropertyResults(alias, returnElem, null, mappings );
return new NativeSQLQueryCollectionReturn(
alias,
ownerClassName,
ownerPropertyName,
propertyResults,
lockMode
);
}
private static java.util.Map bindPropertyResults(
String alias, Element returnElement, PersistentClass pc, Mappings mappings
) {
HashMap propertyresults = new HashMap(); // maybe a concrete SQLpropertyresult type, but Map is exactly what is required at the moment
Element discriminatorResult = returnElement.element("return-discriminator");
if(discriminatorResult!=null) {
ArrayList resultColumns = getResultColumns(discriminatorResult);
propertyresults.put("class", ArrayHelper.toStringArray(resultColumns) );
}
Iterator iterator = returnElement.elementIterator("return-property");
List properties = new ArrayList();
List propertyNames = new ArrayList();
while ( iterator.hasNext() ) {
Element propertyresult = (Element) iterator.next();
String name = propertyresult.attributeValue("name");
if ( pc == null || name.indexOf( '.') == -1) { //if dotted and not load-collection nor return-join
//regular property
properties.add(propertyresult);
propertyNames.add(name);
}
else {
/**
* Reorder properties
* 1. get the parent property
* 2. list all the properties following the expected one in the parent property
* 3. calculate the lowest index and insert the property
*/
if (pc == null)
throw new MappingException("dotted notation in or not yet supported");
int dotIndex = name.lastIndexOf( '.' );
String reducedName = name.substring( 0, dotIndex );
Value value = pc.getRecursiveProperty( reducedName ).getValue();
Iterator parentPropIter;
if ( value instanceof Component ) {
Component comp = (Component) value;
parentPropIter = comp.getPropertyIterator();
}
else if ( value instanceof ToOne ) {
ToOne toOne = (ToOne) value;
PersistentClass referencedPc = mappings.getClass( toOne.getReferencedEntityName() );
if ( toOne.getReferencedPropertyName() != null ) {
try {
parentPropIter = ( (Component) referencedPc.getRecursiveProperty( toOne.getReferencedPropertyName() ).getValue() ).getPropertyIterator();
} catch (ClassCastException e) {
throw new MappingException("dotted notation reference neither a component nor a many/one to one", e);
}
}
else {
try {
if ( referencedPc.getIdentifierMapper() == null ) {
parentPropIter = ( (Component) referencedPc.getIdentifierProperty().getValue() ).getPropertyIterator();
}
else {
parentPropIter = referencedPc.getIdentifierMapper().getPropertyIterator();
}
}
catch (ClassCastException e) {
throw new MappingException("dotted notation reference neither a component nor a many/one to one", e);
}
}
}
else {
throw new MappingException("dotted notation reference neither a component nor a many/one to one");
}
boolean hasFollowers = false;
List followers = new ArrayList();
while ( parentPropIter.hasNext() ) {
String currentPropertyName = ( (Property) parentPropIter.next() ).getName();
String currentName = reducedName + '.' + currentPropertyName;
if (hasFollowers) {
followers.add( currentName );
}
if ( name.equals( currentName ) ) hasFollowers = true;
}
int index = propertyNames.size();
int followersSize = followers.size();
for (int loop = 0 ; loop < followersSize ; loop++) {
String follower = (String) followers.get(loop);
int currentIndex = getIndexOfFirstMatchingProperty(propertyNames, follower);
index = currentIndex != -1 && currentIndex < index ? currentIndex : index;
}
propertyNames.add(index, name);
properties.add(index, propertyresult);
}
}
Set uniqueReturnProperty = new HashSet();
iterator = properties.iterator();
while ( iterator.hasNext() ) {
Element propertyresult = (Element) iterator.next();
String name = propertyresult.attributeValue("name");
if ( "class".equals(name) ) {
throw new MappingException(
"class is not a valid property name to use in a , use instead"
);
}
//TODO: validate existing of property with the chosen name. (secondpass )
ArrayList allResultColumns = getResultColumns(propertyresult);
if ( allResultColumns.isEmpty() ) {
throw new MappingException(
"return-property for alias " + alias +
" must specify at least one column or return-column name"
);
}
if ( uniqueReturnProperty.contains( name ) ) {
throw new MappingException(
"duplicate return-property for property " + name +
" on alias " + alias
);
}
uniqueReturnProperty.add(name);
// the issue here is that for representing an entity collection,
// the collection element values (the property values of the associated entity)
// are represented as 'element.{propertyname}'. Thus the StringHelper.root()
// here puts everything under 'element' (which additionally has significant
// meaning). Probably what we need to do is to something like this instead:
// String root = StringHelper.root( name );
// String key = root; // by default
// if ( !root.equals( name ) ) {
// // we had a dot
// if ( !root.equals( alias ) {
// // the root does not apply to the specific alias
// if ( "elements".equals( root ) {
// // we specifically have a representing an entity collection
// // and this is one of that entity's properties
// key = name;
// }
// }
// }
// but I am not clear enough on the intended purpose of this code block, especially
// in relation to the "Reorder properties" code block above...
// String key = StringHelper.root( name );
String key = name;
ArrayList intermediateResults = (ArrayList) propertyresults.get( key );
if (intermediateResults == null) {
propertyresults.put( key, allResultColumns );
}
else {
intermediateResults.addAll( allResultColumns );
}
}
Iterator entries = propertyresults.entrySet().iterator();
while ( entries.hasNext() ) {
Map.Entry entry = (Map.Entry) entries.next();
if (entry.getValue() instanceof ArrayList) {
ArrayList list = (ArrayList) entry.getValue();
entry.setValue( list.toArray( new String[ list.size() ] ) );
}
}
return propertyresults.isEmpty() ? CollectionHelper.EMPTY_MAP : propertyresults;
}
private static int getIndexOfFirstMatchingProperty(List propertyNames, String follower) {
int propertySize = propertyNames.size();
for (int propIndex = 0 ; propIndex < propertySize ; propIndex++) {
if ( ( (String) propertyNames.get(propIndex) ).startsWith( follower ) ) {
return propIndex;
}
}
return -1;
}
private static ArrayList getResultColumns(Element propertyresult) {
String column = unquote(propertyresult.attributeValue("column"));
ArrayList allResultColumns = new ArrayList();
if(column!=null) allResultColumns.add(column);
Iterator resultColumns = propertyresult.elementIterator("return-column");
while ( resultColumns.hasNext() ) {
Element element = (Element) resultColumns.next();
allResultColumns.add( unquote(element.attributeValue("name")) );
}
return allResultColumns;
}
private static String unquote(String name) {
if (name!=null && name.charAt(0)=='`') {
name=name.substring( 1, name.length()-1 );
}
return name;
}
private static LockMode getLockMode(String lockMode) {
if ( lockMode == null || "read".equals( lockMode ) ) {
return LockMode.READ;
}
else if ( "none".equals( lockMode ) ) {
return LockMode.NONE;
}
else if ( "upgrade".equals( lockMode ) ) {
return LockMode.UPGRADE;
}
else if ( "upgrade-nowait".equals( lockMode ) ) {
return LockMode.UPGRADE_NOWAIT;
}
else if ( "write".equals( lockMode ) ) {
return LockMode.WRITE;
}
else if ( "force".equals( lockMode ) ) {
return LockMode.FORCE;
}
else {
throw new MappingException( "unknown lockmode" );
}
}
}