package er.extensions.eof;
import java.util.Iterator;
import com.webobjects.eocontrol.EOEditingContext;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOKeyComparisonQualifier;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOQualifierEvaluation;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation.NSRange;
import er.extensions.crypting.ERXCrypto;
import er.extensions.qualifiers.ERXQualifierTraversal;
/**
* Extended fetch specification.
* <ul>
* <li>has an identifier for caching</li>
* <li>type-safe, can fetch objects of a certain type</li>
* <li>has a user info</li>
* </ul>
* @author ak
*
* @param <T> the type of objects this fetch spec will return
*/
public class ERXFetchSpecification<T extends EOEnterpriseObject> extends EOFetchSpecification {
/**
* 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;
private NSMutableDictionary _userInfo;
private boolean _includeEditingContextChanges;
private NSRange _fetchRange;
public static EOFetchSpecification fetchSpec(String entityName, EOQualifier qualifier, NSArray<EOSortOrdering> sortOrderings, boolean usesDistinct, boolean isDeep, NSDictionary hints) {
return new ERXFetchSpecification(entityName, qualifier, sortOrderings, usesDistinct, isDeep, hints);
}
public ERXFetchSpecification(String entityName, EOQualifier qualifier, NSArray<EOSortOrdering> sortOrderings, boolean usesDistinct, boolean isDeep, NSDictionary hints) {
super(entityName, qualifier, sortOrderings, usesDistinct, isDeep, hints);
}
public ERXFetchSpecification(String entityName, EOQualifier qualifier, NSArray<EOSortOrdering> sortOrderings) {
super(entityName, qualifier, sortOrderings);
}
public ERXFetchSpecification(EOFetchSpecification spec) {
super(spec.entityName(), spec.qualifier(), spec.sortOrderings(), spec.usesDistinct(), spec.isDeep(), spec.hints());
setFetchesRawRows(spec.fetchesRawRows());
setFetchLimit(spec.fetchLimit());
setLocksObjects(spec.locksObjects());
setRawRowKeyPaths(spec.rawRowKeyPaths());
setPromptsAfterFetchLimit(spec.promptsAfterFetchLimit());
setRefreshesRefetchedObjects(spec.refreshesRefetchedObjects());
setPrefetchingRelationshipKeyPaths(spec.prefetchingRelationshipKeyPaths());
}
public ERXFetchSpecification(ERXFetchSpecification<T> spec) {
this((EOFetchSpecification)spec);
_userInfo = spec.userInfo().count() > 0 ? null : spec.userInfo().mutableClone();
_fetchRange = spec.fetchRange();
}
/**
* Constructs a new fetch specification for the given entity with isDeep = true.
*
* @param entityName the name of the entity
*/
public ERXFetchSpecification(String entityName) {
super(entityName, null, null, false, true, null);
}
/**
* When true, objectsWithFetchSpecification will include newly inserted objects, newly removed objects, and newly updated
* objects in your fetch results (@see ERXEOControlUtilities.objectsWithQualifier).
*
* @param includeEditingContextChanges whether or not to include editing context changes
*/
public void setIncludeEditingContextChanges(boolean includeEditingContextChanges) {
_includeEditingContextChanges = includeEditingContextChanges;
}
/**
* Returns whether or not to include editing context changes.
*
* @return whether or not to include editing context changes
*/
public boolean includeEditingContextChanges() {
return _includeEditingContextChanges;
}
/**
* Sets a arbitrary value.
*
* @param value
* @param key
*/
public void setObjectForKey(Object value, String key) {
_userInfo = _userInfo == null ? new NSMutableDictionary() : _userInfo;
_userInfo.takeValueForKey(value, key);
}
/**
* Gets an arbitrary value.
*
* @param key
* @return object for given key
*/
public Object objectForKey(String key) {
return _userInfo!= null ? _userInfo.valueForKey(key) : null;
}
/**
* Gets the user info.
*
* @return user info dictionary
*/
public NSDictionary userInfo() {
return _userInfo == null ? NSDictionary.EmptyDictionary : _userInfo.immutableClone();
}
public NSRange fetchRange() {
return _fetchRange;
}
/**
* Defines a batch range that should be applied to the SQL statement. Only useful if the database plugin supports it and as an alternative to fetchLimit.
* The SQL generation behavior when both a fetchLimit and a fetchRange are specified is undefined and dependent on the individual database plugin.
*
* @param range
*/
public void setFetchRange(NSRange range) {
_fetchRange = range;
}
/**
* Type-safe method to fetch objects for this fetch spec.
*
* @param ec
* @return object array
*/
public NSArray<T> fetchObjects(EOEditingContext ec) {
return ec.objectsWithFetchSpecification(this);
}
/**
* Type-safe method to fetch raw rows.
*
* @param ec
* @return array of raw row dictionaries
*/
public NSArray<NSDictionary<String, Object>> fetchRawRows(EOEditingContext ec) {
boolean old = fetchesRawRows();
if(!old) {
setFetchesRawRows(true);
}
try {
return ec.objectsWithFetchSpecification(this);
} finally {
if(!old) {
setFetchesRawRows(old);
}
}
}
/**
* Sets a list of attribute keys to be fetched as raw data. Uses two params for backwards
* compatibility as a <code>setRawRowKeyPaths(null)</code> would be ambiguous otherwise.
*
* @see #setRawRowKeyPaths(NSArray)
* @param keyPath
* @param keyPaths list of attribute keys
*/
public void setRawRowKeyPaths(String keyPath, String... keyPaths) {
super.setRawRowKeyPaths(new NSArray<>(keyPath, keyPaths));
}
/**
* Sets the relationships to prefetch along with the main fetch.
*
* @see #setPrefetchingRelationshipKeyPaths(NSArray)
* @param prefetchingRelationshipKeyPaths list of keys to prefetch
*/
public void setPrefetchingRelationshipKeyPaths(ERXKey<?>... prefetchingRelationshipKeyPaths) {
NSMutableArray<String> keypaths = new NSMutableArray<>();
for (ERXKey<?> key : prefetchingRelationshipKeyPaths) {
keypaths.addObject(key.key());
}
setPrefetchingRelationshipKeyPaths(keypaths);
}
/**
* Collects all relevant attributes and the bindings and returns a key suitable for caching.
*
* @return identifier string
*/
public String identifier() {
return identifierForFetchSpec(this);
}
protected String additionalIdentifierInfo() {
return "";
}
@Override
public Object clone() {
ERXFetchSpecification<T> fs = fetchSpec((EOFetchSpecification) super.clone());
fs._fetchRange = _fetchRange;
fs._userInfo = _userInfo == null ? null : _userInfo.mutableClone();
fs._includeEditingContextChanges = _includeEditingContextChanges;
return fs;
}
/**
* Sets the qualifier on this fetch specification and returns "this" for chaining.
*
* @param qualifier the qualifier to set
* @return this
*/
public ERXFetchSpecification<T> qualify(EOQualifier qualifier) {
setQualifier(qualifier);
return this;
}
/**
* Sets the sort orderings on this fetch specification and returns "this" for chaining.
*
* @param sortOrderings the sort orderings to set
* @return this
*/
public ERXFetchSpecification<T> sort(NSArray<EOSortOrdering> sortOrderings) {
setSortOrderings(sortOrderings);
return this;
}
/**
* Sets the sort orderings on this fetch specification and returns "this" for chaining.
*
* @param sortOrdering the sort ordering to set
* @return this
*/
public ERXFetchSpecification<T> sort(EOSortOrdering sortOrdering) {
setSortOrderings(new NSArray<>(sortOrdering));
return this;
}
/**
* Converts a normal fetch spec to an ERX one that returns instances of T.
*
* @param <T>
* @param fs
* @param clazz
* @return converted fetch spec
*/
public static <T extends EOEnterpriseObject> ERXFetchSpecification<T> fetchSpec(EOFetchSpecification fs, Class<T> clazz) {
if (fs instanceof ERXFetchSpecification) {
return (ERXFetchSpecification) fs;
}
return new ERXFetchSpecification<>(fs);
}
/**
* Converts a normal fetch spec to an ERX one.
*
* @param <T>
* @param fs
* @return converted fetch spec
*/
public static <T extends EOEnterpriseObject> ERXFetchSpecification<T> fetchSpec(EOFetchSpecification fs) {
if (fs instanceof ERXFetchSpecification) {
return (ERXFetchSpecification) fs;
}
return new ERXFetchSpecification<>(fs);
}
/**
* Helper to create a string from a qualifier.
*
* @param qualifier
* @return qualifier string
*/
protected static String identifierForQualifier(EOQualifier qualifier) {
final StringBuilder sb = new StringBuilder();
if(qualifier != null) {
ERXQualifierTraversal traversal = new ERXQualifierTraversal() {
@Override
protected void visit(EOQualifierEvaluation q) {
sb.append(q.getClass().getName());
}
@Override
protected boolean traverseKeyComparisonQualifier(EOKeyComparisonQualifier q) {
sb.append(q.leftKey()).append(q.selector().name()).append(q.rightKey());
return super.traverseKeyComparisonQualifier(q);
}
@Override
protected boolean traverseKeyValueQualifier(EOKeyValueQualifier q) {
Object value = q.value();
if (value instanceof EOEnterpriseObject) {
EOEnterpriseObject eo = (EOEnterpriseObject) value;
value = ERXEOControlUtilities.primaryKeyStringForObject(eo);
} else if (value instanceof NSArray) {
NSArray arr = (NSArray) value;
String s = "";
for (Object object : arr) {
if (object instanceof EOEnterpriseObject) {
EOEnterpriseObject eo = (EOEnterpriseObject) object;
s += ERXEOControlUtilities.primaryKeyStringForObject(eo);
} else {
s += NSPropertyListSerialization.stringFromPropertyList(object);
}
}
value = s;
}
sb.append(q.key()).append(q.selector().name()).append(value);
return super.traverseKeyValueQualifier(q);
}
};
traversal.traverse(qualifier);
}
return sb.toString();
}
/**
* Builds an identifier for the given fetch spec which is suitable for caching.
*
* @param fs
* @return fetch spec string
*/
public static String identifierForFetchSpec(EOFetchSpecification fs) {
StringBuilder sb = new StringBuilder( identifierForQualifier(fs.qualifier()));
for (Iterator iterator = fs.sortOrderings().iterator(); iterator.hasNext();) {
EOSortOrdering so = (EOSortOrdering) iterator.next();
sb.append(so.key()).append(so.selector().name());
}
sb.append(fs.fetchesRawRows()).append(fs.fetchLimit()).append(fs.locksObjects()).append(fs.isDeep());
sb.append(fs.entityName());
sb.append(fs.hints());
if (fs instanceof ERXFetchSpecification) {
sb.append(((ERXFetchSpecification) fs).additionalIdentifierInfo());
}
String result = sb.toString();
result = ERXCrypto.base64HashedString(result);
return result;
}
}