package er.restadaptor;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import com.webobjects.eoaccess.EOAdaptorChannel;
import com.webobjects.eoaccess.EOAttribute;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOGeneralAdaptorException;
import com.webobjects.eoaccess.EOModel;
import com.webobjects.eoaccess.EOSQLExpression;
import com.webobjects.eoaccess.EOStoredProcedure;
import com.webobjects.eocontrol.EOAndQualifier;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOKeyValueQualifier;
import com.webobjects.eocontrol.EOOrQualifier;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSSet;
import com.webobjects.foundation.NSTimestampFormatter;
import er.extensions.foundation.ERXMutableURL;
import er.extensions.localization.ERXLocalizer;
public class ERRESTAdaptorChannel extends EOAdaptorChannel {
private static final NSTimestampFormatter restDateFormat = new NSTimestampFormatter("%Y-%m-%dT%H:%M:%SZ");
private NSArray<EOAttribute> _attributes;
private NSMutableArray<NSMutableDictionary<String, Object>> _fetchedRows;
private int _fetchIndex;
private boolean _open;
public ERRESTAdaptorChannel(ERRESTAdaptorContext context) {
super(context);
_fetchIndex = -1;
}
public ERRESTAdaptorContext context() {
return (ERRESTAdaptorContext) _context;
}
@Override
public NSArray<EOAttribute> attributesToFetch() {
return _attributes;
}
@Override
public void cancelFetch() {
_fetchedRows = null;
_fetchIndex = -1;
}
@Override
public void closeChannel() {
_open = false;
}
@Override
public NSArray describeResults() {
return _attributes;
}
@Override
public NSArray describeTableNames() {
return NSArray.EmptyArray;
}
@Override
public EOModel describeModelWithTableNames(NSArray anArray) {
return null;
}
@Override
public void evaluateExpression(EOSQLExpression anExpression) {
throw new UnsupportedOperationException("ERRESTAdaptorChannel.evaluateExpression");
}
@Override
public void executeStoredProcedure(EOStoredProcedure aStoredProcedure, NSDictionary someValues) {
throw new UnsupportedOperationException("ERRESTAdaptorChannel.executeStoredProcedure");
}
@Override
public NSMutableDictionary fetchRow() {
NSMutableDictionary row = null;
if (_fetchedRows != null && _fetchIndex < _fetchedRows.count()) {
row = _fetchedRows.objectAtIndex(_fetchIndex++);
}
return row;
}
@Override
public boolean isFetchInProgress() {
return _fetchedRows != null && _fetchIndex < _fetchedRows.count();
}
@Override
public boolean isOpen() {
return _open;
}
@Override
public void openChannel() {
if (!_open) {
_open = true;
}
}
@Override
public NSDictionary returnValuesForLastStoredProcedureInvocation() {
throw new UnsupportedOperationException("ERRESTAdaptorChannel.returnValuesForLastStoredProcedureInvocation");
}
public NSArray<String> urlPrefixes(EOEntity entity) {
String urlPrefix = null;
String externalName = entity.externalName();
if (externalName != null) {
String[] externalNames = externalName.split(",");
if (externalNames.length > 0 && externalNames[0].contains("/")) {
urlPrefix = externalNames[0];
}
}
if (urlPrefix == null) {
urlPrefix = "/" + pluralName(entity);
}
return new NSArray<String>(urlPrefix.split("\\|"));
}
public String pluralName(EOEntity entity) {
String pluralName = null;
String externalName = entity.externalName();
if (externalName != null) {
String[] externalNames = externalName.split(",");
if (externalNames.length > 0) {
if (externalNames[0].contains("/")) {
if (externalNames.length > 2) {
pluralName = externalNames[2];
}
}
else if (externalNames.length > 1) {
pluralName = externalNames[1];
}
}
}
if (pluralName == null) {
pluralName = ERXLocalizer.defaultLocalizer().plurifiedString(singularName(entity), 2);
}
return pluralName;
}
public String singularName(EOEntity entity) {
String singularName = null;
String externalName = entity.externalName();
if (externalName != null) {
String[] externalNames = externalName.split(",");
if (externalNames.length > 0) {
if (externalNames[0].contains("/")) {
if (externalNames.length > 1) {
singularName = externalNames[1];
}
}
else {
singularName = externalNames[0];
}
}
}
if (singularName == null) {
singularName = entity.name();
}
return singularName;
}
public String textValue(Element element) {
NodeList children = element.getChildNodes();
String textValue = null;
for (int i = 0; i < children.getLength(); i++) {
String value = children.item(i).getNodeValue();
if (value != null) {
value = value.trim();
textValue = value;
break;
}
}
return textValue;
}
public Object convertValue(String strValue, EOAttribute attribute) {
return strValue;
}
@SuppressWarnings( { "cast", "unchecked" })
public String urlForQualifier(EOEntity entity, EOQualifier qualifier, NSMutableDictionary<String, Object> attributesFromQualifier) {
StringBuffer urlBuffer = new StringBuffer();
NSDictionary connectionDictionary = adaptorContext().adaptor().connectionDictionary();
String url = (String) connectionDictionary.objectForKey("URL");
if (url == null || url.length() == 0) {
throw new IllegalArgumentException("There is no URL specified for the connection dictionary " + connectionDictionary + ".");
}
if (url.endsWith("/")) {
urlBuffer.append(url.substring(0, url.length() - 1));
}
else {
urlBuffer.append(url);
}
NSArray<String> urlPrefixes = urlPrefixes(entity);
if (urlPrefixes.count() == 1) {
urlBuffer.append(urlPrefixes.objectAtIndex(0));
}
else {
String bestUrlPrefix = null;
int bestMatchCount = 0;
NSSet<String> qualifierKeys = (NSSet<String>) qualifier.allQualifierKeys();
for (String urlPrefix : urlPrefixes) {
NSSet<String> urlVariableNames = variableNamesForUrl(urlPrefix);
if (urlVariableNames.count() >= bestMatchCount && urlVariableNames.isSubsetOfSet(qualifierKeys)) {
bestUrlPrefix = urlPrefix;
bestMatchCount = urlVariableNames.count();
}
}
if (bestUrlPrefix == null) {
throw new IllegalArgumentException("The qualifier " + qualifier + " was insufficient to fetch " + entity.name() + " objects with one of the URLs " + urlPrefixes + ".");
}
urlBuffer.append(bestUrlPrefix);
}
if (qualifier != null) {
processQualifier(entity, qualifier, urlBuffer, attributesFromQualifier);
}
urlBuffer.append(".xml");
System.out.println("ERRESTAdaptorChannel.urlForQualifier: " + urlBuffer);
return urlBuffer.toString();
}
protected NSSet<String> variableNamesForUrl(String url) {
NSMutableSet<String> variableNames = new NSMutableSet<String>();
Matcher matcher = Pattern.compile("\\[([^]]+)\\]").matcher(url);
while (matcher.find()) {
variableNames.addObject(matcher.group(1));
}
return variableNames;
}
@SuppressWarnings( { "cast", "unchecked" })
protected void processQualifier(EOEntity entity, EOQualifier qualifier, StringBuffer urlBuffer, NSMutableDictionary<String, Object> attributesFromQualifier) {
if (qualifier instanceof EOKeyValueQualifier) {
EOKeyValueQualifier eokvq = (EOKeyValueQualifier) qualifier;
String key = eokvq.key();
Object value = eokvq.value();
EOAttribute keyAttribute = entity.attributeNamed(key);
if (entity.primaryKeyAttributes().containsObject(keyAttribute)) {
urlBuffer.append("/");
urlBuffer.append(value);
}
else {
String var = "[" + key + "]";
int varIndex = urlBuffer.indexOf(var);
if (varIndex != -1) {
urlBuffer.replace(varIndex, varIndex + var.length(), value.toString());
}
}
if (value != null) {
attributesFromQualifier.setObjectForKey(value, key);
}
}
else if (qualifier instanceof EOAndQualifier) {
NSArray<EOQualifier> childQualifiers = (NSArray<EOQualifier>) ((EOAndQualifier) qualifier).qualifiers();
for (EOQualifier childQualifier : childQualifiers) {
processQualifier(entity, childQualifier, urlBuffer, attributesFromQualifier);
}
}
else if (qualifier instanceof EOOrQualifier) {
NSArray<EOQualifier> childQualifiers = (NSArray<EOQualifier>) ((EOOrQualifier) qualifier).qualifiers();
if (childQualifiers.count() == 1) {
processQualifier(entity, childQualifiers.objectAtIndex(0), urlBuffer, attributesFromQualifier);
}
}
}
@Override
public void selectAttributes(NSArray attributesToFetch, EOFetchSpecification fetchSpecification, boolean shouldLock, EOEntity entity) {
if (entity == null) {
throw new IllegalArgumentException("null entity.");
}
if (attributesToFetch == null) {
throw new IllegalArgumentException("null attributes.");
}
setAttributesToFetch(attributesToFetch);
try {
_fetchIndex = 0;
NSMutableDictionary<String, Object> attributesFromQualifier = new NSMutableDictionary<String, Object>();
ERXMutableURL url = new ERXMutableURL(urlForQualifier(entity, fetchSpecification.qualifier(), attributesFromQualifier));
InputStream urlStream = new BufferedInputStream(url.toURL().openStream());
Document document;
try {
document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(urlStream);
}
finally {
urlStream.close();
}
document.normalize();
_fetchedRows = new NSMutableArray<NSMutableDictionary<String, Object>>();
NodeList rowElements = document.getElementsByTagName(singularName(entity));
for (int rowNum = 0; rowNum < rowElements.getLength(); rowNum++) {
NSMutableDictionary<String, Object> row = new NSMutableDictionary<String, Object>();
Element rowElement = (Element) rowElements.item(rowNum);
for (int attributeNum = 0; attributeNum < attributesToFetch.count(); attributeNum++) {
EOAttribute attribute = (EOAttribute) attributesToFetch.objectAtIndex(attributeNum);
String columnName = attribute.columnName();
NodeList attributeElements = rowElement.getElementsByTagName(columnName);
Object value;
if (attributeElements.getLength() == 0) {
if (rowElement.hasAttribute(columnName)) {
Attr columnAttribute = rowElement.getAttributeNode(columnName);
value = convertValue(columnAttribute.getValue(), attribute);
}
else {
value = null;
}
}
else if (attributeElements.getLength() > 1) {
throw new EOGeneralAdaptorException("There was more than one column named '" + columnName + "'.");
}
else {
Element attributeElement = (Element) attributeElements.item(0);
if ("true".equals(attributeElement.getAttribute("nil"))) {
value = null;
}
else {
String strValue = textValue(attributeElement);
if (attributeElement.hasAttribute("type")) {
String type = attributeElement.getAttribute("type");
if ("bigit".equals(type)) {
value = new BigDecimal(strValue);
}
else if ("boolean".equals(type)) {
value = Boolean.parseBoolean(strValue);
}
else if ("datetime".equals(type)) {
value = ERRESTAdaptorChannel.restDateFormat.parseObject(strValue);
}
else if ("double".equals(type)) {
value = Double.parseDouble(strValue);
}
else if ("float".equals(type)) {
value = Float.parseFloat(strValue);
}
else if ("integer".equals(type)) {
value = Integer.parseInt(strValue);
}
else if ("short".equals(type)) {
value = Short.parseShort(strValue);
}
else {
throw new IllegalArgumentException("Unknown type '" + type + "'.");
}
}
else {
value = convertValue(strValue, attribute);
}
}
}
if (value != null) {
row.setObjectForKey(value, attribute.name());
}
}
// Just in case you qualified against attributes that ended up not
// being returned in the results, we augment the resulting row
// with the key-value pairs from your qualifier.
if (attributesFromQualifier.count() > 0) {
for (String qualifierAttributeName : attributesFromQualifier.allKeys()) {
if (!row.containsKey(qualifierAttributeName)) {
EOAttribute qualifierAttribute = entity.attributeNamed(qualifierAttributeName);
if (qualifierAttribute != null && attributesToFetch.containsObject(qualifierAttribute)) {
row.setObjectForKey(attributesFromQualifier.objectForKey(qualifierAttributeName), qualifierAttributeName);
}
}
}
}
// Because we can only interpret parts of your qualifier, we now run the
// fetched dictionaries through the in-memory qualifier and let it weed
// out results using the more complicatd qualifiers.
EOQualifier qualifier = fetchSpecification.qualifier();
if (qualifier == null || qualifier.evaluateWithObject(row)) {
_fetchedRows.addObject(row);
}
else {
System.out.println("ERRESTAdaptorChannel.selectAttributes: skipping " + row + " (" + qualifier + ")");
}
}
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to fetch '" + entity.name() + "' with fetch specification '" + fetchSpecification + "': " + e.getMessage());
}
}
@SuppressWarnings("unchecked")
@Override
public void setAttributesToFetch(NSArray attributesToFetch) {
if (attributesToFetch == null) {
throw new IllegalArgumentException("ERMemoryAdaptorChannel.setAttributesToFetch: null attributes.");
}
_attributes = attributesToFetch;
}
@Override
public int updateValuesInRowsDescribedByQualifier(NSDictionary updatedRow, EOQualifier qualifier, EOEntity entity) {
try {
throw new EOGeneralAdaptorException("ERRESTAdaptorChannel.updateValuesInRowsDescribedByQualifier not supported.");
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to update '" + entity.name() + "' row " + updatedRow + " with qualifier " + qualifier + ": " + e.getMessage());
}
}
@Override
public void insertRow(NSDictionary row, EOEntity entity) {
try {
throw new EOGeneralAdaptorException("ERRESTAdaptorChannel.insertRow not supported.");
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to insert '" + entity.name() + "' with row " + row + ": " + e.getMessage());
}
}
@Override
public int deleteRowsDescribedByQualifier(EOQualifier qualifier, EOEntity entity) {
try {
throw new EOGeneralAdaptorException("ERRESTAdaptorChannel.deleteRowsDescribedByQualifier not supported.");
}
catch (EOGeneralAdaptorException e) {
throw e;
}
catch (Throwable e) {
e.printStackTrace();
throw new EOGeneralAdaptorException("Failed to delete '" + entity.name() + "' with qualifier " + qualifier + ": " + e.getMessage());
}
}
}