/* * Copyright (C) NetStruxr, Inc. All rights reserved. * * This software is published under the terms of the NetStruxr * Public Software License version 0.5, a copy of which has been * included with this distribution in the LICENSE.NPL file. */ package er.extensions.eof.qualifiers; import java.util.Enumeration; import com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EODatabaseContext; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOQualifierSQLGeneration; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOSQLExpression; import com.webobjects.eoaccess.EOSQLExpressionFactory; import com.webobjects.eocontrol.EOClassDescription; import com.webobjects.eocontrol.EOFetchSpecification; import com.webobjects.eocontrol.EOObjectStoreCoordinator; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.eocontrol.EOQualifierEvaluation; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableSet; import er.extensions.eof.ERXEOAccessUtilities; /** * Generates a subquery for the qualifier given in argument: * * <pre><code> * EOQualifier q = EOQualifier.qualifierWithQualifierFormat("firstName = 'Max'", null); * ERXQualifierInSubquery qq = new ERXQualifierInSubquery(q, "User", "group"); * EOFetchSpecification fs = new EOFetchSpecification("Group", qq, null); * </code></pre> * * Would generate: "SELECT t0.GROUP_ID, t0.NAME FROM USER t0 WHERE t0.GROUP_ID * IN ( SELECT t0.GROUP_ID FROM GROUP t0 WHERE t0.NAME = ? ) " * * This class can be used to work around the EOF bug where OR queries involving * many-to-manies are incorrectly generated * * * It will also generate * * ... t0.FOREIGN_KEY_ID in (select t1.ID from X where [your qualifier here]) * * with the 3 arg constructor */ public class ERXQualifierInSubquery extends EOQualifier implements EOQualifierSQLGeneration, Cloneable, EOQualifierEvaluation { /** * Do I need to update serialVersionUID? * See section 5.6 <cite>Type Changes Affecting Serialization</cite> on page 51 of the * <a href="http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf">Java Object Serialization Spec</a> */ private static final long serialVersionUID = 1L; /** holds the subqualifier */ protected EOQualifier qualifier; /** holds the entity name */ protected String entityName; /** holds the relationship name */ protected String relationshipName; /** holds the attribute name */ protected String attributeName; /** holds the attribute name */ protected String destinationAttName; /** * Public single argument constructor. Use this constructor for * sub-qualification on the same table. * * @param qualifier * sub qualifier */ public ERXQualifierInSubquery(EOQualifier qualifier) { this(qualifier, null, null); } /** * @param qualifier * sub qualifier * @param entityName * of the sub qualification * @param relationshipName * relationship name */ public ERXQualifierInSubquery(EOQualifier qualifier, String entityName, String relationshipName) { this.qualifier = qualifier; this.entityName = entityName; if(relationshipName != null) { this.relationshipName = relationshipName; EORelationship rel = ERXEOAccessUtilities.entityNamed(null, entityName).relationshipNamed(relationshipName); attributeName = rel.sourceAttributes().lastObject().name(); destinationAttName = rel.destinationAttributes().lastObject().name(); } } /** * @param qualifier * sub qualifier * @param entityName * of the sub qualification * @param attributeName * foreign key attribute name * @param destinationAttName * destination key name */ public ERXQualifierInSubquery(EOQualifier qualifier, String entityName, String attributeName, String destinationAttName) { this.qualifier = qualifier; this.entityName = entityName; this.attributeName = attributeName; this.destinationAttName = destinationAttName; } /** * Only used with qualifier keys which are not supported in this qualifier * at this time. Does nothing. * * @param aSet * of qualifier keys */ // FIXME: Should do something here ... @Override public void addQualifierKeysToSet(NSMutableSet aSet) { } /** * Creates another qualifier after replacing the values of the bindings. * Since this qualifier does not support qualifier binding keys a clone of * the qualifier is returned. * * @param someBindings * some bindings * @param requiresAll * tells if the qualifier requires all bindings * @return clone of the current qualifier. */ @Override public EOQualifier qualifierWithBindings(NSDictionary someBindings, boolean requiresAll) { return (EOQualifier) clone(); } /** * This qualifier does not perform validation. This is a no-op method. * * @param aClassDescription * to validation the qualifier keys against. */ // FIXME: Should do something here ... @Override public void validateKeysWithRootClassDescription(EOClassDescription aClassDescription) { } /** * Generates the sql string for the given sql expression. Bulk of the logic * for generating the sub-query is in this method. * * @param e * a given sql expression * @return sql string for the current sub-query. */ public String sqlStringForSQLExpression(EOSQLExpression e) { StringBuilder sb = new StringBuilder(); if (attributeName != null) sb.append(e.sqlStringForAttributeNamed(attributeName)); else { EOAttribute pk = e.entity().primaryKeyAttributes().lastObject(); sb.append(e.sqlStringForAttribute(pk)); } sb.append(" IN ( "); EOEntity entity = entityName == null ? e.entity() : e.entity().model().modelGroup().entityNamed(entityName); EOFetchSpecification fs = new EOFetchSpecification(entity.name(), qualifier, null, false, true, null); if (qualifier != null) { qualifier = EOQualifierSQLGeneration.Support._schemaBasedQualifierWithRootEntity(qualifier, entity); } if (qualifier != fs.qualifier()) { fs.setQualifier(qualifier); } // ASSUME: This makes a few assumptions, if anyone can figure out a fool // proof way that would be nice to get the model // Note you can't use: // EOAdaptor.adaptorWithModel(e.entity().model()).expressionFactory(); // as this creates a // EODatabaseContext context = EODatabaseContext.registeredDatabaseContextForModel(entity.model(), EOObjectStoreCoordinator.defaultCoordinator()); EOSQLExpressionFactory factory = context.database().adaptor().expressionFactory(); NSArray subAttributes = destinationAttName != null ? new NSArray(entity.attributeNamed(destinationAttName)) : entity.primaryKeyAttributes(); EOSQLExpression subExpression = factory.expressionForEntity(entity); // Arroz: Having this table identifier replacement causes serious // problems if you have more than a table being processed in the subquery. Disabling // it will apparently not cause problems, because t0 inside the subquery is not // the same t0 outside it. // subExpression.aliasesByRelationshipPath().setObjectForKey("t1", ""); subExpression.setUseAliases(true); subExpression.prepareSelectExpressionWithAttributes(subAttributes, false, fs); // EOSQLExpression // expression=factory.selectStatementForAttributes(entity.primaryKeyAttributes(), // false, fs, entity); for (Enumeration bindEnumeration = subExpression.bindVariableDictionaries().objectEnumerator(); bindEnumeration.hasMoreElements();) { e.addBindVariableDictionary((NSDictionary) bindEnumeration.nextElement()); } // sb.append(ERXStringUtilities.replaceStringByStringInString("t0.", // "t1.", subExpression.statement())); sb.append(subExpression.statement()); sb.append(" ) "); return sb.toString(); } /** * Implementation of the EOQualifierSQLGeneration interface. Just clones the * qualifier. * * @param anEntity * an entity. * @return clone of the current qualifier. */ public EOQualifier schemaBasedQualifierWithRootEntity(EOEntity anEntity) { return (EOQualifier) clone(); } /** * Implementation of the EOQualifierSQLGeneration interface. Just clones the * qualifier. * * @param anEntity * an entity * @param aPath * relationship path * @return clone of the current qualifier. */ public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOEntity anEntity, String aPath) { return (EOQualifier) clone(); } /** * Description of the qualifier * * @return human readable description of the qualifier. */ @Override public String toString() { return " <" + getClass().getName() + "> '" + qualifier.toString() + "'"; } /** * Implementation of the Clonable interface. Clones the current qualifier. * * @return cloned qualifier. */ @Override public Object clone() { if(relationshipName != null) { return new ERXQualifierInSubquery(qualifier, entityName, relationshipName); } return new ERXQualifierInSubquery(qualifier, entityName, attributeName, destinationAttName); } @Override public boolean evaluateWithObject(Object object) { Object destinationValue = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, relationshipName); if(destinationValue != null) { if (destinationValue instanceof NSArray) { NSArray arr = (NSArray) destinationValue; return EOQualifier.filteredArrayWithQualifier(arr, qualifier).count() > 0; } return qualifier.evaluateWithObject(destinationValue); } return false; } }