/*
* (C) Copyright IBM Corp. 2008
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import org.apache.derby.iapi.store.access.Qualifier;
import org.apache.derby.iapi.types.DataValueDescriptor;
import com.ibm.db2j.PluralizableVTI;
import com.ibm.gaiandb.diags.GDBMessages;
/**
* @author DavidVyvyan
*
* This VTIWrapper type:
* - *does* now support stack pool management of data handles (e.g. db connection),
* and therefore provides a recycling method for them.
*
* - is based on data source identifier: vtiClassName + prefix argument to the VTI (others use filename or rdbms table),
*
* - does not support in memory loading of rows,
*
* - does require processing for re-initialisation (after config file updates)
*
* This VTIWrapper just takes a free-form string argument which is passed to the actual physical
* underlying VTI. Instances of VTIBasic must be disposed of independently after use.
*
*/
public class VTIBasic extends VTIWrapper {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008";
private static final Logger logger = new Logger( "VTIBasic", 30 );
public static final String EXEC_ARG_CUSTOM_VTI_ARGS = "VTIARG";
private final Class<GaianChildVTI> vtiClass;
private final boolean isSupportsEndpointConstants;
private final String vtiClassSimpleName;
private final Object reinitLock = new Object(); // Use dedicated object for locking around columns-mapping reload
private ResultSetMetaData vtiMetaDataMapped = null;
private String vtiArgs;
@Override
public boolean supportsEndpointConstants() { return isSupportsEndpointConstants; }
/**
* @param s
* @throws Exception
*/
public VTIBasic( String className, String nodeDefName, GaianResultSetMetaData logicalTableRSMD ) throws Exception {
// super( className + ':' + derivePrefixArgFromCSVArgs( GaianDBConfig.getVTIArguments(nodeDefName) ), nodeDefName );
super( className + ':' + GaianDBConfig.getVTIArguments(nodeDefName), nodeDefName );
// arguments is the underlying vti class name
vtiClass = (Class<GaianChildVTI>) GaianNode.getClassUsingGaianClassLoader(className);
// vtiClass = (Class<GaianChildVTI>) Class.forName(className);
isSupportsEndpointConstants = PluralizableVTI.class.isAssignableFrom( vtiClass );
logger.logInfo( nodeDefName + " is a " + vtiClass.getName() );
vtiClassSimpleName = vtiClass.getSimpleName();
reinitialise( logicalTableRSMD );
}
private Map<String, DataValueDescriptor[]> dsInstanceConstants = null;
@Override
public String[] getPluralizedInstances() {
String[] dsInstanceIDs = null;
if ( supportsEndpointConstants() ) {
try {
GaianChildVTI vtiInstance = vtiClass.getDeclaredConstructor(String.class).newInstance( vtiArgs );
dsInstanceIDs = ((PluralizableVTI) vtiInstance).getEndpointIDs().toArray(new String[0]);
dsInstanceConstants = new HashMap<String, DataValueDescriptor[]>( dsInstanceIDs.length );
for ( String id : dsInstanceIDs ) dsInstanceConstants.put(id, ((PluralizableVTI) vtiInstance).getEndpointConstants(id) );
vtiInstance.close();
} catch ( Exception e ) { logger.logException(GDBMessages.ENGINE_RESOLVING_PLURALIZED_VTI_ATTRIBUTES, "Unable to resolve VTI's pluralized instances: ", e); }
}
return dsInstanceIDs;
}
@Override
public DataValueDescriptor[] getPluralizedInstanceConstants(String dsInstanceID) { return null == dsInstanceConstants ? null : dsInstanceConstants.get( dsInstanceID ); }
public GaianChildVTI execute(ConcurrentMap<String,Object> arguments, Qualifier[][] qualifiers, int[] projectedColumns) throws Exception {
return execute( arguments, qualifiers, projectedColumns, null ); // 4th arg table is not used in this context
}
/**
* Instantiates and invokes a VTI data source endpoint.
* First, the VTI is instantiated with a 'vtiArgs' String object.
* Later, the setArgs() method is called and given a CSV string representation of the 'arguments' map.
* Finally the setExtractConditions() method is called with qualifiers (predicates), projected columns and resolved column mappings.
* vtiArgs is normally just the static _ARGS property defined in the config file for the data source. However it can also be overridden in
* the SQL by passing them via the GaianTable() arguments list. This is generally redundant though, because all arguments from this list are also passed
* via the VTI's setArgs() method, which also includes more general query arguments from the GaianTable() invocation.
* In future, we might pool instances of the VTI, meaning we would only be passing arguments through the setArgs() method for repeated query invocations.
*
* The dsInstanceID identifies a "pluralized instance" where each of them (resolved via getPluralizedInstances()) use the same gaiandb data source wrapper,
* exercising a same instance of this VTIBasic class concurrently. This reduces configuration required in the config file and reduces memory required too.
* The dsInstanceID is only passed to the setArgs() method.
*
* @see com.ibm.gaiandb.VTIWrapper#execute(org.apache.derby.iapi.store.access.Qualifier[][], int[])
*/
public GaianChildVTI execute(ConcurrentMap<String,Object> arguments, Qualifier[][] qualifiers, int[] projectedColumns, String dsInstanceID) throws Exception {
GaianChildVTI childVTI = null;
// if ( null == sourceHandles )
// sourceHandles = DataSourcesManager.getSourceHandlesStackPool( arguments, false );
//
// if ( sourceHandles.empty() ) {
// If a dynamic arguments value was passed in to GaianTable's arguments against special key: vtiClassSimpleName + EXEC_ARG_CUSTOM_VTI_ARGS, then use it instead
// of the static _ARGS value taken from the config file.
if ( null != arguments ) {
String dynamicArgs = (String) arguments.get(vtiClassSimpleName + EXEC_ARG_CUSTOM_VTI_ARGS);
if ( null != dynamicArgs ) {
// TODO: To support this, the calling code should be the one looking up the appropriate VTIBasic which has a matching prefix argument.
// We do not want to be changing the prefix argument once this VTIBasic has been constructed - The model assumes this is a "static" property
// for this VTIBasic and pool of VTI objects.
// Methods isBasedOn() and reinitialise() provide capability to pool VTIs which can be reinitialised without changing the core "connection"
// handle described by the prefix argument.
throw new Exception("Unsupported VTI mode: Cannot use dynamic args for VTIs yet...");
// vtiArgs = dynamicArgs;
}
}
logger.logInfo("Instantiating " + vtiClassSimpleName + " with argument: " + vtiArgs );
childVTI = (GaianChildVTI) getPooledSourceHandle();
if ( null == childVTI ) return null; // Unable to get a pooled handle - reason should have been logged - this data source will be skipped.
// try {
// // Use getDeclaredConstructor() so that we can put in arguments if we want, and so we can handle instantiation exceptions
// childVTI = vtiClass.getDeclaredConstructor(String.class).newInstance( vtiArgs );
//// childVTI = (GaianChildVTI) vtiClass.getDeclaredConstructor(String[].class).newInstance(Arrays.asList(vtiArgs)); // (GaianChildVTI) vtiClass.newInstance();
// } catch ( ClassCastException e ) {
// logger.logException( GDBMessages.ENGINE_PROCESS_IMPLEMENTATION_ERROR, "Unable to process VTIBasic as it does not implement GaianVTIChild: " + vtiClass.getName(), e );
// return null;
// } catch ( Exception e ) {
// logger.logException( GDBMessages.ENGINE_PROCESS_ERROR, "Unable to process VTIBasic " + vtiClassSimpleName + ":", e );
// return null;
// }
// if ( null != arguments ) {
// String args = (String) arguments.get(vtiClassSimpleName + EXEC_ARG_CUSTOM_VTI_ARGS);
// childVTI.setArgs( null == args ? new String[0] : new String[] { args } );
// }
List<String> args = new ArrayList<String>();
// If the VTI is a PluralizableVTI, the 1st arg must be the instance id (even if it's null)
if ( supportsEndpointConstants() ) args.add( dsInstanceID );
// Pass all global query arguments to the underlying VTI - these arguments originate from the GaianTable() which invoked this VTI. They are different to vtiArgs.
if ( null != arguments ) {
for ( Iterator<String> iter = arguments.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
args.add( key + '=' + arguments.get(key).toString() );
}
String dsWrapperDefaultSchemaProperty = GaianDBConfig.getVTISchema(nodeDefName); // may be overridden if VTI implements getMetaData()
args.add( DS_WRAPPER_DEFAULT_SCHEMA + '='
+ ( null != dsWrapperDefaultSchemaProperty ? dsWrapperDefaultSchemaProperty :
logicalTableRSMD.getColumnsDefinitionExcludingHiddenOnesAndConstantValues() ) );
}
childVTI.setArgs( args.toArray( new String[0]) );
// } else {
// childVTI = (GaianChildVTI) sourceHandles.pop();
// }
ResultSetMetaData vtiMetaData = childVTI.getMetaData();
if ( vtiMetaData != vtiMetaDataMapped )
// refresh the column mappings if necessary - only let one exec thread do this
synchronized( reinitLock ) { // synch on ds node instance
if ( false == isColumnNamesAndPositionsIdentical(vtiMetaData, vtiMetaDataMapped) ) {
safeExecNodeState.refreshColumnsMapping( vtiMetaData );
vtiMetaDataMapped = vtiMetaData;
}
}
childVTI.setExtractConditions( qualifiers, projectedColumns, safeExecNodeState.getColumnsMapping() );
return childVTI;
}
public final static String DS_WRAPPER_DEFAULT_SCHEMA = "DS_WRAPPER_DEFAULT_SCHEMA";
private static final boolean isColumnNamesAndPositionsIdentical( ResultSetMetaData rsmd1, ResultSetMetaData rsmd2 ) throws SQLException {
if ( rsmd1 == rsmd2 ) return true;
if ( null == rsmd1 || null == rsmd2 ) return false;
int colCount = rsmd1.getColumnCount();
if ( colCount != rsmd2.getColumnCount() ) return false;
for ( int i=1; i<=colCount; i++ )
if ( false == rsmd1.getColumnName(i).equals(rsmd2.getColumnName(i)) )
return false;
return true;
}
public boolean isBasedOn( final String vtiClassNameAndArgs ) {
// can only be re-initialized if the vti class is the same and _ARGS prefix value is the same.
return null != vtiClassNameAndArgs && vtiClassNameAndArgs.equals( sourceID ); // == vtiClass.getName() + ':' + prefixArg
}
// public boolean isBasedOn( final String vtiClassNameAndPrefixArgument ) {
// // can only be re-initialized if the vti class is the same and _ARGS prefix value is the same.
// return null != vtiClassNameAndPrefixArgument && vtiClassNameAndPrefixArgument.equals( sourceID ); // == vtiClass.getName() + ':' + prefixArg
// }
public String getSourceDescription( String dsInstanceID ) {
String vtiArgs = GaianDBConfig.getVTIArguments(nodeDefName);
if ( null != vtiArgs ) return vtiArgs + ( null==dsInstanceID ? "" : "::" + dsInstanceID );
String kname = vtiClass.getName();
return kname.substring( kname.lastIndexOf('.')+1 );
}
/**
* TODO
* This method should create a VTI object within a max time, beyond which it should return null but continue to
* create the VTI in the background and then if this eventually succeeds it should put it in the source handles pool.
* Ideally, we would use/re-factor the same code in DatabaseConnector to handle both types of handles (Creation of JDBC Connections and VTIs)
*
* When the code above requires a VTI, it should call getPooledSourceHandle(), which calls this one.
*/
@Override
protected Object getNewSourceHandleWithinTimeoutOrToSourcesPoolAsynchronously() throws Exception {
// To get the pool, use: DataSourcesManager.getSourceHandlesPool( sourceID ); ...
// Use getDeclaredConstructor() so that we can put in arguments if we want, and so we can handle instantiation exceptions
try { return vtiClass.getDeclaredConstructor(String.class).newInstance( vtiArgs ); }
catch ( Exception e ) { logger.logException( GDBMessages.ENGINE_PROCESS_ERROR, "Unable to create new VTI: " + vtiClassSimpleName + ":", e ); }
// The VTI class instantiation may take a long time (e.g. creating connectors to remote sources etc) -
// Therefore we always want to return quickly from this method and may not have any diagnostics.
return null;
}
// public static String derivePrefixArgFromCSVArgs( final String argsCSV ) {
// if ( null == argsCSV ) return null;
// int idx = argsCSV.indexOf(',');
// return 0 > idx ? argsCSV : argsCSV.substring(0, idx);
// }
protected void customReinitialise() throws SQLException {
vtiArgs = GaianDBConfig.getVTIArguments(nodeDefName);
// prefixArg = derivePrefixArgFromCSVArgs( vtiArgs ); // cannot change after construction (sourceID must be fixed - Use of isBasedOn() ensures this)
logger.logInfo("customReinitialise() derived arguments for VTIBasic " + vtiClassSimpleName + ": " + vtiArgs );
}
public void recycleOrCloseResultWrapper(GaianChildVTI result) throws Exception {
if ( result.reinitialise() && false == isPluralized() )
recycleSourceHandleToPool( result );
}
// protected void doClose() throws SQLException {
// }
// Not used - Rows are naturally in memory - in a vector.
GaianChildVTI getAllRows() throws Exception {
return null;
}
}