/* * 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 com.webobjects.eoaccess.EOAttribute; import com.webobjects.eoaccess.EOEntity; import com.webobjects.eoaccess.EOJoin; import com.webobjects.eoaccess.EOQualifierSQLGeneration; import com.webobjects.eoaccess.EORelationship; import com.webobjects.eoaccess.EOSQLExpression; import com.webobjects.eocontrol.EOClassDescription; import com.webobjects.eocontrol.EOKeyValueQualifier; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableArray; import com.webobjects.foundation.NSMutableSet; import er.extensions.eof.ERXEOAccessUtilities; import er.extensions.foundation.ERXArrayUtilities; import er.extensions.qualifiers.ERXKeyValueQualifier; /** * Optimized toMany qualifier, much, much better SQL than the Apple provided qualifier. * Really nice when you want to find all the eos that have say five of the * ten eos in their toMany relationship. This qualifier will always only * generate three joins no matter how many eos you are trying to find. Example usage: * <pre><code> * NSArray employees; // given, can be null * // Find all of the departments that have all of those employees * ERXToManyQualifier q = new ERXToManyQualifier("toEmployees", employees); * EOFetchSpecification fs = new EOFetchSpecification("Department", q, null); * NSArray departments = ec.objectsWithFetchSpecification(fs); * </code></pre> * If you want to find say departments that have 5 or more of the given * employees (imagine you have a list of 10 or so), then you could * construct the qualifier like: <br> * <code> ERXToManyQualifier q = new ERXToManyQualifier("toEmployees", employees, 5);</code><br> * or to find any department that has at least one of the given employees<br> * <code> ERXToManyQualifier q = new ERXToManyQualifier("toEmployees", employees, 1);</code> */ public class ERXToManyQualifier extends ERXKeyValueQualifier implements Cloneable { /** * 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; /** register SQL generation support for the qualifier */ static { EOQualifierSQLGeneration.Support.setSupportForClass(new ToManyQualifierSQLGenerationSupport(), ERXToManyQualifier.class); } public static final String MatchesAllInArraySelectorName = "matchesAllInArray"; /** holds the to many key */ private String _toManyKey; /** holds the array of elements */ private NSArray _elements; /** holds the min count to match against, defaults to 0 */ private int _minCount = 0; public ERXToManyQualifier(String toManyKey, NSArray elements) { this(toManyKey,elements, 0); } public ERXToManyQualifier(String toManyKey, NSArray elements, int minCount) { super(toManyKey, EOQualifier.QualifierOperatorEqual, elements); _toManyKey=toManyKey; _elements=elements; _minCount = minCount; } public NSArray elements() { return _elements; } @Override public String key() { return _toManyKey; } public int minCount() { return _minCount; } /** * Description of the qualifier. * @return description of the key and which elements it * should contain. */ @Override public String toString() { return "<" +_toManyKey + " contains " + (_minCount > 0 ? " " + _minCount + " " : " all ") + " of " + _elements + ">"; } /** * Implementation of the Cloneable interface. * @return clone of the qualifier. */ @Override public Object clone() { return new ERXToManyQualifier(_toManyKey, _elements, _minCount); } /** * Adds SQL generation support. Note that the database needs to support * the IN operator. */ public static class ToManyQualifierSQLGenerationSupport extends EOQualifierSQLGeneration.Support { /** * Public constructor */ public ToManyQualifierSQLGenerationSupport() { super(); } protected static void appendColumnForAttributeToStringBuilder(EOAttribute attribute, StringBuilder sb) { sb.append(attribute.entity().externalName()); sb.append('.'); sb.append(attribute.columnName()); } @Override @SuppressWarnings("unchecked") public String sqlStringForSQLExpression(EOQualifier eoqualifier, EOSQLExpression e) { ERXToManyQualifier qualifier = (ERXToManyQualifier)eoqualifier; StringBuilder result = new StringBuilder(); EOEntity targetEntity=e.entity(); NSArray<String> toManyKeys=NSArray.componentsSeparatedByString(qualifier.key(),"."); EORelationship targetRelationship=null; for (int i=0; i<toManyKeys.count()-1;i++) { targetRelationship= targetEntity.anyRelationshipNamed(toManyKeys.objectAtIndex(i)); targetEntity=targetRelationship.destinationEntity(); } targetRelationship=targetEntity.relationshipNamed(toManyKeys.lastObject()); targetEntity=targetRelationship.destinationEntity(); if (targetRelationship.joins()==null || targetRelationship.joins().isEmpty()) { // we have a flattened many to many String definitionKeyPath=targetRelationship.definition(); NSArray<String> definitionKeys=NSArray.componentsSeparatedByString(definitionKeyPath,"."); EOEntity lastStopEntity=targetRelationship.entity(); EORelationship firstHopRelationship= lastStopEntity.relationshipNamed(definitionKeys.objectAtIndex(0)); EOEntity endOfFirstHopEntity= firstHopRelationship.destinationEntity(); EOJoin join= firstHopRelationship.joins().objectAtIndex(0); // assumes 1 join EOAttribute sourceAttribute=join.sourceAttribute(); EOAttribute targetAttribute=join.destinationAttribute(); EORelationship secondHopRelationship=endOfFirstHopEntity.relationshipNamed(definitionKeys.objectAtIndex(1)); join= secondHopRelationship.joins().objectAtIndex(0); // assumes 1 join EOAttribute secondHopSourceAttribute=join.sourceAttribute(); NSMutableArray<String> lastStopPKeyPath = toManyKeys.mutableClone(); lastStopPKeyPath.removeLastObject(); lastStopPKeyPath.addObject(firstHopRelationship.name()); lastStopPKeyPath.addObject(targetAttribute.name()); String firstHopRelationshipKeyPath=lastStopPKeyPath.componentsJoinedByString("."); result.append(e.sqlStringForAttributeNamed(firstHopRelationshipKeyPath)); result.append(" IN ( SELECT "); result.append(lastStopEntity.externalName()); result.append('.'); result.append(lastStopEntity.primaryKeyAttributes().objectAtIndex(0).columnName()); result.append(" FROM "); result.append(lastStopEntity.externalName()); result.append(','); lastStopPKeyPath.removeLastObject(); String tableAliasForJoinTable=(String)e.aliasesByRelationshipPath(). objectForKey(lastStopPKeyPath.componentsJoinedByString("."));//"j"; //+random# result.append(endOfFirstHopEntity.externalName()); result.append(' '); result.append(tableAliasForJoinTable); result.append(" WHERE "); appendColumnForAttributeToStringBuilder(sourceAttribute, result); result.append('='); result.append(e.sqlStringForAttributeNamed(firstHopRelationshipKeyPath)); if(qualifier.elements() != null) { NSArray pKeys=ERXEOAccessUtilities.primaryKeysForObjects(qualifier.elements()); result.append(" AND "); result.append(tableAliasForJoinTable); result.append('.'); result.append(secondHopSourceAttribute.columnName()); result.append(" IN ("); EOAttribute pk = targetEntity.primaryKeyAttributes().lastObject(); for(int i = 0; i < pKeys.count(); i++) { Object key = pKeys.objectAtIndex(i); String keyString = e.formatValueForAttribute(key, pk); //AK: default is is broken if("NULL".equals(keyString)) { keyString = "" + key; } result.append(keyString); if(i < pKeys.count()-1) { result.append(','); } } result.append(") "); } result.append(" GROUP BY "); appendColumnForAttributeToStringBuilder(sourceAttribute, result); result.append(" HAVING COUNT(*)"); if (qualifier.minCount() <= 0) { result.append("=" + qualifier.elements().count()); } else { result.append(">=" + qualifier.minCount()); } result.append(" )"); } else { throw new RuntimeException("not implemented!!"); } return result.toString(); } // ENHANCEME: This should support restrictive qualifiers on the root entity @Override public EOQualifier schemaBasedQualifierWithRootEntity(EOQualifier eoqualifier, EOEntity eoentity) { EOQualifier result = null; EOKeyValueQualifier qualifier = (EOKeyValueQualifier)eoqualifier; String key = qualifier.key(); if(qualifier.selector().name().equals(MatchesAllInArraySelectorName)) { EOQualifierSQLGeneration.Support support = EOQualifierSQLGeneration.Support.supportForClass(ERXToManyQualifier.class); NSArray array = (NSArray) qualifier.value(); ERXToManyQualifier q = new ERXToManyQualifier(key, array, array.count() ); result = support.schemaBasedQualifierWithRootEntity(q, eoentity); return result; } return (EOQualifier)eoqualifier.clone(); } @Override public EOQualifier qualifierMigratedFromEntityRelationshipPath(EOQualifier eoqualifier, EOEntity eoentity, String relationshipPath) { ERXToManyQualifier qualifier=(ERXToManyQualifier)eoqualifier; String newPath = EOQualifierSQLGeneration.Support._translateKeyAcrossRelationshipPath(qualifier.key(), relationshipPath, eoentity); return new ERXToManyQualifier(newPath, qualifier.elements(), qualifier.minCount()); } } @Override public EOQualifier qualifierWithBindings(NSDictionary arg0, boolean arg1) { if (arg0 != null && arg0.count() > 0) { throw new IllegalStateException(getClass().getName() + " doesn't support bindings"); } return this; } /* (non-Javadoc) * @see com.webobjects.eocontrol.EOQualifier#validateKeysWithRootClassDescription(com.webobjects.eocontrol.EOClassDescription) */ @Override public void validateKeysWithRootClassDescription(EOClassDescription arg0) { // TODO Auto-generated method stub } @Override public void addQualifierKeysToSet(NSMutableSet arg0) { throw new IllegalStateException(getClass().getName() + " doesn't support adding keys"); } @Override @SuppressWarnings("unchecked") public boolean evaluateWithObject(Object object) { boolean result = false; if (object != null && object instanceof NSKeyValueCoding) { Object obj = ((NSKeyValueCoding)object).valueForKey(key()); if (obj == null && object instanceof NSKeyValueCodingAdditions) { obj = ((NSKeyValueCodingAdditions)object).valueForKeyPath(key()); } if (obj instanceof NSArray) { NSArray objArray = (NSArray)obj; if (!objArray.isEmpty()) { if(_minCount == 0) { result = ERXArrayUtilities.arrayContainsArray(objArray, elements()); } else { result = ERXArrayUtilities.intersectingElements(objArray, elements()).count() >= _minCount; } } } } return result; } }