/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.translator.coherence;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.teiid.core.types.DataTypeManager;
import org.teiid.core.types.TransformationException;
import org.teiid.core.util.StringUtil;
import org.teiid.language.ColumnReference;
import org.teiid.language.Expression;
import org.teiid.language.Literal;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.metadata.Column;
import org.teiid.metadata.KeyRecord;
import org.teiid.metadata.MetadataFactory;
import org.teiid.metadata.Schema;
import org.teiid.metadata.Table;
import org.teiid.translator.TranslatorException;
import org.teiid.translator.coherence.util.ObjectSourceMethodManager;
import org.teiid.translator.coherence.visitor.CoherenceVisitor;
public abstract class SourceCacheAdapter {
protected MetadataFactory metadataFactory = null;
/*****************
* Methods for Adding the source metadata
*****************/
protected Schema addSchema(String s) throws TranslatorException {
Schema sc = new Schema();
sc.setName(s);
sc.setPhysical(true);
metadataFactory.getMetadataStore().addSchema(sc);
return sc;
}
protected Table addTable(String t) throws TranslatorException {
return metadataFactory.addTable(t); //$NON-NLS-1$
}
protected KeyRecord addPrimaryKey(String name, List<String> columnNames, Table table) throws TranslatorException {
return metadataFactory.addPrimaryKey(name, columnNames, table); //$NON-NLS-1$
}
protected KeyRecord addForeignKey(String name, List<String> columnNames, Table pktable, Table table) throws TranslatorException {
return metadataFactory.addForiegnKey(name, columnNames, pktable, table);
}
protected void addColumn(String columnName, String nameInSource, String dataType, Table t) throws TranslatorException {
Column c = metadataFactory.addColumn(columnName, dataType, t); //$NON-NLS-1$
c.setNameInSource(nameInSource);
}
/**
* END of Methods for Adding source metadata
*/
/**
* Called so the implementor can defined its table/column metadata
* Use the methods @see #addTable and @see #addColumn.
*/
public void addMetadata() throws TranslatorException {
}
/**
* Called to translate the list of <code>objects</code> returned from the Coherence cache.
* The implementor will use the <code>visitor</code> to obtain sql parsed information
* needed to understand the columns requested. Then use the @see #retrieveValue method
* in order to get the object value returned in the correct type to be passed in the
* rows returned to the engine.
*/
public List translateObjects(List objects, CoherenceVisitor visitor) throws TranslatorException {
List row = null;
List rows = new ArrayList();
Map columnObjectMap = null;
String[] attributeNames = visitor.getAttributeNames();
Class[] attributeTypes = visitor.getAttributeTypes();
// these pinpoint the column where a collection is found
// and the name nodes it takes to traverse to get the collection
//**********************************
// NOTE: ONLY 1 COLLECTION CAN BE DEFINED IN A QUERY
//**********************************
String attributeNameForCollection = "COL_TAG";
int attributeLocForCollection = -1;
String attributeNamePrefixForCollection = null;
List<String> attributeTokensForCollection = null;
int collectionNodeDepth = -1;
Set<String> collectionNames = new HashSet<String>();
// 1st time thru, call to get the objects, if a collection is found, that is stored for
// processing in the next step because it impacts the number rows returned
for (Iterator<Object> it = objects.iterator(); it.hasNext();) {
// each object represent 1 row, but will be expanded, if a collection is found in its results
Object o = (Object) it.next();
columnObjectMap = new HashMap();
for (int i=0; i<attributeNames.length; i++) {
final String n = attributeNames[i];
List<String> tokens = StringUtil.getTokens(n, ".");
final ParmHolder holder = ParmHolder.createParmHolder(visitor.getTableName(), tokens, attributeTypes[i]);
Object value = retrieveValue(holder, o, 0);
if (holder.isCollection) {
if (attributeLocForCollection == -1) {
// if a collection type has not been found, then identify it
attributeLocForCollection = i;
attributeTokensForCollection = tokens;
collectionNodeDepth = holder.collectionNodeDepth;
for (int x = 0; x <= holder.collectionNodeDepth; x++) {
if (x > 0) {
attributeNamePrefixForCollection+=".";
}
attributeNamePrefixForCollection+=tokens.get(x);
}
columnObjectMap.put(attributeNameForCollection, value);
collectionNames.add(n);
} else if (collectionNodeDepth == holder.collectionNodeDepth) {
// if a collection was requested in another column, check to see if the
// same node method was called, if not, then this represents a different collection being retrieved
String a = attributeTokensForCollection.get(collectionNodeDepth);
String b = tokens.get(holder.collectionNodeDepth);
if (!b.equals(a)) {
throw new TranslatorException("Query Error: multiple collections found between " + attributeTokensForCollection.toString() + " and " + holder.nameNodes.toString() +" (table: " + holder.tableName + " at node depth " + collectionNodeDepth + ", only 1 is supported per query" );
}
collectionNames.add(n);
}
} else {
columnObjectMap.put(n, value);
} // end of isCollection
}
if (attributeLocForCollection != -1) {
Object colObj = columnObjectMap.get(attributeNameForCollection);
Iterator colIt = null;
if (colObj.getClass().isArray()) {
List objRows = Arrays.asList((Object[]) colObj);
colIt = objRows.iterator();
} else if (colObj instanceof Collection) {
Collection objRows = (Collection) colObj;
colIt = objRows.iterator();
} else if (colObj instanceof Map) {
Map objRows = (Map) colObj;
colIt = objRows.values().iterator();
} else {
throw new TranslatorException("Program Error: A container type of object is unsupported: " + colObj.getClass().getName());
}
for (Iterator<Object> objit = colIt; colIt.hasNext();) {
Object colt = (Object) colIt.next();
row = new ArrayList<Object>(attributeNames.length);
for (int i=0; i<attributeNames.length; i++) {
String n = attributeNames[i];
if (collectionNames.contains(n)) {
// when a collection is found, need to find the value for the row
// in order to do that, need to pick where retrieve process left off
List<String> tokens = StringUtil.getTokens(n, ".");
Object colvalue = getValue(visitor.getTableName(), tokens, attributeTypes[i], colt, collectionNodeDepth + 1);
row.add(colvalue);
} else {
row.add(columnObjectMap.get(n));
}
}
rows.add(row);
}
} else {
row = new ArrayList<Object>(attributeNames.length);
for (int i=0; i<attributeNames.length; i++) {
String n = attributeNames[i];
Object attributeObject = columnObjectMap.get(n);
row.add(attributeObject);
}
rows.add(row);
}
columnObjectMap.clear();
collectionNames.clear();
attributeLocForCollection = -1;
// don't reset the following because, once set, they should be the same for all
// attributeNamePrefixForCollection
// collectionNodeDepth
}
return rows;
}
protected void setMetadataFactory(MetadataFactory metadataFactory) throws TranslatorException {
this.metadataFactory = metadataFactory;
addMetadata();
}
public Object getValue(String tableName, List<String> nameTokens, Class<?> type, Object cachedObject, int level ) throws TranslatorException {
final ParmHolder holder = ParmHolder.createParmHolder(tableName, nameTokens, type);
Object value = retrieveValue(holder, cachedObject, level);
return value;
}
private Object retrieveValue(ParmHolder holder, Object cachedObject, int level) throws TranslatorException {
// do not retrieve a value for these types
if (cachedObject.getClass().isArray() || cachedObject instanceof Collection || cachedObject instanceof Map) {
return cachedObject;
}
final String columnName = holder.nameNodes.get(level);
boolean atTheBottom = false;
if (holder.nodeSize == (level + 1)) atTheBottom = true;
String methodName = null;
// only the last parsed name can be where the boolean call can be made
// example: x.y.z z will be where "is" is called
// or x x could be where "is" is called
if (atTheBottom && holder.attributeType != null && holder.attributeType == Boolean.class) {
methodName = "is" + columnName;
} else {
methodName = "get" + columnName;
}
final Object value = ObjectSourceMethodManager.getValue(methodName, cachedObject);
// if an array or collection, return, this will be processed after all objects are obtained
// in order the number of rows can be created
if (value == null) {
return null;
}
if (value.getClass().isArray() || value instanceof Collection || value instanceof Map) {
holder.setCollection(level);
return value;
}
if (atTheBottom) {
return value;
}
return retrieveValue(holder, value, ++level);
}
public Object createObjectFromMetadata(String metadataName) throws TranslatorException {
return ObjectSourceMethodManager.createObject(metadataName);
}
public Object createObject(List<ColumnReference> columnList, List<Expression> valueList, CoherenceVisitor visitor, Table t ) throws TranslatorException {
if(columnList.size() != valueList.size()) {
throw new TranslatorException("Error: columns.size and values.size are not the same.");
}
// create the new object that will either be added as a top level object or added to the parent container
String tableName = visitor.getNameFromTable(t);
Object newObject = createObjectFromMetadata(tableName);
for (int i=0; i < columnList.size(); i++) {
final ColumnReference insertElement = columnList.get(i);
if (!insertElement.getMetadataObject().isUpdatable()) continue;
final String elementName = visitor.getNameFromElement(insertElement.getMetadataObject());
final Object value = valueList.get(i);
Object val;
if(value instanceof Literal) {
Literal literalValue = (Literal)value;
val = literalValue.getValue();
//.toString();
if(null != val && val instanceof String) {
//!val.isEmpty()) {
val = stripQutes((String) val);
}
} else {
val = value;
//.toString();
}
setValue(tableName, elementName, newObject, val, insertElement.getType());
}
return newObject;
}
private String stripQutes(String id) {
if((id.startsWith("'") && id.endsWith("'"))) {
id = id.substring(1,id.length()-1);
} else if ((id.startsWith("\"") && id.endsWith("\""))) {
id = id.substring(1,id.length()-1);
}
return id;
}
public Object setValue(String tableName, String columnName, Object cachedObject, Object value, Class classtype) throws TranslatorException {
LogManager.logDetail(LogConstants.CTX_CONNECTOR, "Setting value to attribute: " + columnName); //$NON-NLS-1$
List<String> tokens = StringUtil.getTokens(columnName, ".");
final ParmHolder holder = ParmHolder.createParmHolder(tableName, tokens, classtype);
return setValue(holder, cachedObject, value, 0);
}
private Object setValue(ParmHolder holder, Object cachedObject, Object value, int level) throws TranslatorException {
// if there are muliple nameNodes,
// then do "get" for all but the last node, then set
// if a "container" type object is encountered, then create the object type for the table and add it to the container
// if a map, do a put
// if a collection, do an add
final String columnName = holder.nameNodes.get(level);
boolean atTheBottom = false;
if (holder.nodeSize == (level + 1)) atTheBottom = true;
if (!atTheBottom) {
final Object containerObject = ObjectSourceMethodManager.getValue("get" + columnName, cachedObject);
if (containerObject.getClass().isArray() || containerObject instanceof Collection || containerObject instanceof Map) {
return cachedObject;
}
}
ArrayList argTypes = new ArrayList(1);
argTypes.add(holder.attributeType);
Method m = ObjectSourceMethodManager.getMethod(cachedObject.getClass(), "set" + columnName, argTypes);
Class[] setTypes = m.getParameterTypes();
Object newValue = null;
if (value instanceof Collection || value instanceof Map || value.getClass().isArray() ) {
newValue = value;
} else {
try {
newValue = DataTypeManager.transformValue(value, setTypes[0]);
} catch (TransformationException e) {
// TODO Auto-generated catch block
throw new TranslatorException(e);
}
}
ObjectSourceMethodManager.executeMethod(m, cachedObject, new Object[] {newValue});
LogManager.logTrace(LogConstants.CTX_CONNECTOR, "Set value " + newValue); //$NON-NLS-1$
return newValue;
}
// private static Object retrieveValue(Integer code, Object value) throws Exception {
// if(code != null) {
// // Calling the specific methods here is more likely to get uniform (and fast) results from different
// // data sources as the driver likely knows the best and fastest way to convert from the underlying
// // raw form of the data to the expected type. We use a switch with codes in order without gaps
// // as there is a special bytecode instruction that treats this case as a map such that not every value
// // needs to be tested, which means it is very fast.
// switch(code.intValue()) {
// case INTEGER_CODE: {
// return Integer.valueOf(value);
// }
// case LONG_CODE: {
// return Long.valueOf(value);
// }
// case DOUBLE_CODE: {
// return Double.valueOf(value);
// }
// case BIGDECIMAL_CODE: {
// return value;
// }
// case SHORT_CODE: {
// return Short.valueOf(value);
// }
// case FLOAT_CODE: {
// return Float.valueOf(value);
// }
// case TIME_CODE: {
// return value;
// }
// case DATE_CODE: {
// return value;
// }
// case TIMESTAMP_CODE: {
// return value;
// }
// case BLOB_CODE: {
// return value;
// }
// case CLOB_CODE: {
// return value;
// }
// case BOOLEAN_CODE: {
// return Boolean.valueOf(value);
// }
// }
// }
//
// return value;
// }
}
final class ParmHolder {
static ParmHolder holder = new ParmHolder();
String tableName;
List<String> nameNodes;
Class attributeType;
int nodeSize;
// these parameters are use when the retrieved object is a collection type
// the node name path need to be captured
//
boolean isCollection = false;
String nodeNamePathToGeCollection=null;
int collectionNodeDepth = -1;
private ParmHolder() {
}
static ParmHolder createParmHolder(String tablename, List<String> parsedAttributeName, Class<?> type) {
holder.tableName = tablename;
holder.nameNodes = parsedAttributeName;
holder.attributeType = type;
holder.nodeSize = parsedAttributeName.size();
holder.isCollection = false;
holder.nodeNamePathToGeCollection=null;
holder.collectionNodeDepth = -1;
return holder;
}
void setCollection(int depth) {
isCollection = true;
collectionNodeDepth = depth;
}
}