package er.memoryadaptor;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eoaccess.EORelationship;
import com.webobjects.eocontrol.EOAndQualifier;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSRange;
import er.extensions.eof.ERXFetchSpecification;
import er.extensions.eof.ERXQ;
import er.extensions.foundation.ERXStringUtilities;
/**
* EREntityStore is an abstract datastore implementation for a single "table"
* in non relational EOAdaptors like ERMemoryAdaptor. It provides basic fetch support.
* Additionally, this tracks a sequence number for the entity (for pk generation).
*
* @author q
*/
public abstract class EREntityStore {
private int _sequence = 0;
public interface JoinEntityStore { }
public void clear() {
_sequence = 0;
}
public void commitFromTransactionStore(EREntityStore store) {
throw new UnsupportedOperationException("Transactions are not supported in " + getClass().getName());
}
public int deleteRowsDescribedByQualifier(EOQualifier qualifier, EOEntity entity) {
try {
int count = 0;
Iterator<NSMutableDictionary<String, Object>> i = iterator();
while (i.hasNext()) {
NSMutableDictionary<String, Object> rawRow = i.next();
NSMutableDictionary<String, Object> row = rowFromStoredValues(rawRow, entity);
if (qualifier == null || qualifier.evaluateWithObject(row)) {
i.remove();
count++;
}
}
return count;
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to delete '" + entity.name() + "' with qualifier " + qualifier + ": " + e.getMessage());
}
}
public NSMutableArray<NSMutableDictionary<String, Object>> fetch(NSArray<EOAttribute> attributesToFetch,
EOFetchSpecification fetchSpecification, boolean shouldLock, EOEntity entity, ERMemoryAdaptorContext context) {
EOQualifier qualifier = null;
int fetchLimit = 0;
NSArray<EOSortOrdering> sortOrderings = null;
if (fetchSpecification != null) {
qualifier = fetchSpecification.qualifier();
fetchLimit = fetchSpecification.fetchLimit();
sortOrderings = fetchSpecification.sortOrderings();
}
if (entity.restrictingQualifier() != null) {
if (qualifier != null) {
qualifier = new EOAndQualifier(new NSArray(new EOQualifier[] { qualifier, entity.restrictingQualifier() }));
} else {
qualifier = entity.restrictingQualifier();
}
}
NSMutableArray<EORelationship> mergeRelationships = new NSMutableArray<>();
if (qualifier != null && context != null) {
NSArray<EOKeyValueQualifier> keyValueQualifiers = ERXQ.extractKeyValueQualifiers(qualifier);
for (EOKeyValueQualifier keyValueQualifier : keyValueQualifiers) {
String qualifierKey = keyValueQualifier.key();
String relationshipName = qualifierKey;
if (relationshipName.contains(".")) {
relationshipName = ERXStringUtilities.firstPropertyKeyInKeyPath(relationshipName);
}
EORelationship mergeRelationship = entity.relationshipNamed(relationshipName);
if (mergeRelationship != null) {
mergeRelationships.add(mergeRelationship);
qualifier = ERXQ.replaceQualifierWithQualifier(qualifier, keyValueQualifier,
ERXQ.has(qualifierKey, new NSArray(keyValueQualifier.value())));
} else if (qualifierKey.equals(entity.primaryKeyAttributeNames().get(0))
&& keyValueQualifier.selector().name().equals("doesContain") && !(keyValueQualifier.value() instanceof NSArray)) {
// fix wrong schemaBasedQualifier
qualifier= ERXQ.replaceQualifierWithQualifier(qualifier, keyValueQualifier,
ERXQ.is(qualifierKey, keyValueQualifier.value()));
}
}
}
// int count = 0;
NSMutableArray<NSMutableDictionary<String, Object>> fetchedRows = new NSMutableArray<NSMutableDictionary<String, Object>>();
Iterator<NSMutableDictionary<String, Object>> i = iterator();
while (i.hasNext()) {
NSMutableDictionary<String, Object> rawRow = i.next();
NSMutableDictionary<String, Object> row = rowFromStoredValues(rawRow, entity);
for (EORelationship mergeRelationship : mergeRelationships) {
NSArray<NSMutableDictionary<String, Object>> found = null;
if (mergeRelationship.isFlattened() && mergeRelationship.isToMany()) {
found = fetchRelatedManyToManyRows(entity, row, mergeRelationship, context);
} else {
found = fetchRelatedRows(entity, row, mergeRelationship, context);
}
if (found != null && !found.isEmpty()) {
row.setObjectForKey(found, mergeRelationship.name());
}
}
if (qualifier == null || qualifier.evaluateWithObject(row)) {
for (EORelationship mergeRelationship : mergeRelationships) {
row.removeObjectForKey(mergeRelationship.name());
}
fetchedRows.addObject(row);
// count++;
}
// if (fetchLimit > 0 && count == fetchLimit) {
// break;
// }
}
if (sortOrderings != null) {
EOSortOrdering.sortArrayUsingKeyOrderArray(fetchedRows, sortOrderings);
}
if (fetchLimit > 0 && fetchedRows.count() > fetchLimit) {
fetchedRows.removeObjectsInRange(new NSRange(fetchLimit, fetchedRows.count() - fetchLimit));
}
return fetchedRows;
}
/**
* Will fetch related rows for the given row via the passed many-to-many
* relationship. The context will be used to access the needed entity
* stores.
*
* @param entity
* the current entity
* @param row
* the currently selected row
* @param relationship
* the many-to-many relationship
* @param context
* the memory adaptor context
* @return array of rows from related entity store
*/
protected NSArray<NSMutableDictionary<String, Object>> fetchRelatedManyToManyRows(EOEntity entity,
NSDictionary<String, Object> row, EORelationship relationship, ERMemoryAdaptorContext context) {
String relationshipPath = relationship.relationshipPath();
String toJoinKey = ERXStringUtilities.firstPropertyKeyInKeyPath(relationshipPath);
String toDestKey = ERXStringUtilities.keyPathWithoutFirstProperty(relationshipPath);
EORelationship toJoinRelationship = entity.anyRelationshipNamed(toJoinKey);
EOEntity joinEntity = toJoinRelationship.destinationEntity();
EREntityStore joinStore = context._entityStoreForEntity(joinEntity);
String sourceAttribute = toJoinRelationship.sourceAttributes().get(0).name();
String destinationAttribute = toJoinRelationship.destinationAttributes().get(0).name();
ERXFetchSpecification fs = new ERXFetchSpecification(joinEntity.name(), ERXQ.equals(destinationAttribute,
row.valueForKey(sourceAttribute)), null);
NSArray<NSMutableDictionary<String, Object>> fetchedObjects = joinStore.fetch(joinEntity.attributesToFetch(),
fs, false, joinEntity, context);
if (fetchedObjects.isEmpty()) {
return NSArray.EmptyArray;
}
EORelationship destRelationship = joinEntity.anyRelationshipNamed(toDestKey);
sourceAttribute = destRelationship.sourceAttributes().get(0).name();
destinationAttribute = destRelationship.destinationAttributes().get(0).name();
NSArray<Object> destValues = (NSArray<Object>) fetchedObjects.valueForKey(sourceAttribute);
EOEntity destEntity = relationship.destinationEntity();
fs = new ERXFetchSpecification(destEntity.name(), ERXQ.in(destinationAttribute, destValues), null);
EREntityStore destinationStore = context._entityStoreForEntity(destEntity);
fetchedObjects = destinationStore.fetch(destEntity.attributesToFetch(), fs, false, destEntity, context);
return fetchedObjects;
}
/**
* Will fetch related rows for the given row relationship. The
* context will be used to access the needed entity store.
*
* @param entity
* the current entity
* @param row
* the currently selected row
* @param relationship
* the relationship
* @param context
* the memory adaptor context
* @return array of rows from related entity store
*/
protected NSArray<NSMutableDictionary<String, Object>> fetchRelatedRows(EOEntity entity,
NSDictionary<String, Object> row, EORelationship relationship, ERMemoryAdaptorContext context) {
EOEntity destEntity = relationship.destinationEntity();
EREntityStore destStore = context._entityStoreForEntity(destEntity);
String sourceAttribute = relationship.sourceAttributes().get(0).name();
String destinationAttribute = relationship.destinationAttributes().get(0).name();
ERXFetchSpecification fs = new ERXFetchSpecification(destEntity.name(), ERXQ.equals(destinationAttribute,
row.valueForKey(sourceAttribute)), null);
return destStore.fetch(destEntity.attributesToFetch(), fs, false, destEntity, context);
}
protected NSMutableDictionary<String, Object> rowFromStoredValues(NSMutableDictionary<String, Object> rawRow, EOEntity entity) {
NSMutableDictionary<String, Object> row = new NSMutableDictionary<>(rawRow.count());
for (EOAttribute attribute : entity.attributesToFetch()) {
Object value = rawRow.objectForKey(attribute.columnName());
if (attribute.isDerived()) {
if (!attribute.isFlattened()) {
// Evaluate derived attribute expression
/*
//This is a hack to support SQL string concatenation in derived attributes
String expression = attribute.definition().replaceAll("\\|\\|", "+ '' +");
try {
value = Ognl.getValue(expression, rawRow);
} catch (Throwable t) {
t.printStackTrace();
}
*/
} else {
String dstKey = attribute.definition();
value = rawRow.objectForKey(dstKey);
}
}
row.setObjectForKey(value != null ? value : NSKeyValueCoding.NullValue, attribute.name());
}
return row;
}
protected abstract void _insertRow(NSMutableDictionary<String, Object> row, EOEntity entity);
public void insertRow(NSDictionary<String, Object> row, EOEntity entity) {
try {
NSMutableDictionary<String, Object> mutableRow = new NSMutableDictionary<>(row.size());
for (Enumeration e = entity.attributes().objectEnumerator(); e.hasMoreElements();) {
EOAttribute attribute = (EOAttribute) e.nextElement();
Object value = row.objectForKey(attribute.name());
if (!attribute.isDerived())
mutableRow.setObjectForKey(value != null ? value : NSKeyValueCoding.NullValue, attribute.columnName());
}
_insertRow(mutableRow, entity);
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to insert '" + entity.name() + "' with row " + row + ": " + e.getMessage());
}
}
public abstract Iterator<NSMutableDictionary<String, Object>> iterator();
public int nextSequence() {
return ++_sequence;
}
public EREntityStore transactionStore() {
throw new UnsupportedOperationException("Transactions are not supported in " + getClass().getName());
}
public int updateValuesInRowsDescribedByQualifier(NSDictionary<String, Object> updatedRow, EOQualifier qualifier, EOEntity entity) {
try {
int count = 0;
Iterator<NSMutableDictionary<String, Object>> i = iterator();
while (i.hasNext()) {
NSMutableDictionary<String, Object> rawRow = i.next();
NSMutableDictionary<String, Object> row = rowFromStoredValues(rawRow, entity);
if (qualifier == null || qualifier.evaluateWithObject(row)) {
for (Map.Entry<String, Object> entry : updatedRow.entrySet()) {
EOAttribute attribute = entity.attributeNamed(entry.getKey());
rawRow.setObjectForKey(entry.getValue(), attribute.columnName());
}
count++;
}
}
return count;
}
catch (EOGeneralAdaptorException e) {
e.printStackTrace();
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to update '" + entity.name() + "' row " + updatedRow + " with qualifier " + qualifier + ": " + e.getMessage());
}
}
}