/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright 2005 - 2009 Pentaho Corporation. All rights reserved.
*
*
* Created Sep 8, 2005
* @author mbatchel
*/
package org.pentaho.platform.plugin.action.sql;
import java.text.Format;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import org.apache.commons.logging.Log;
import org.pentaho.actionsequence.dom.ActionInputConstant;
import org.pentaho.actionsequence.dom.IActionDefinition;
import org.pentaho.actionsequence.dom.IActionInput;
import org.pentaho.actionsequence.dom.IActionOutput;
import org.pentaho.actionsequence.dom.actions.AbstractRelationalDbAction;
import org.pentaho.actionsequence.dom.actions.SqlConnectionAction;
import org.pentaho.commons.connection.IPentahoConnection;
import org.pentaho.commons.connection.IPentahoMetaData;
import org.pentaho.commons.connection.IPentahoResultSet;
import org.pentaho.commons.connection.PentahoDataTransmuter;
import org.pentaho.commons.connection.memory.MemoryMetaData;
import org.pentaho.commons.connection.memory.MemoryResultSet;
import org.pentaho.platform.api.data.IDataComponent;
import org.pentaho.platform.api.data.IPreparedComponent;
import org.pentaho.platform.api.engine.IParameterResolver;
import org.pentaho.platform.engine.services.connection.PentahoConnectionFactory;
import org.pentaho.platform.engine.services.runtime.TemplateUtil;
import org.pentaho.platform.engine.services.solution.ComponentBase;
import org.pentaho.platform.plugin.action.messages.Messages;
import org.pentaho.platform.plugin.services.connections.sql.SQLConnection;
/**
* SQLBaseComponent is the base class for SQLExecute and SQLLookupRule. it does the majority
* of work when interacting with Pentaho's BI Platform, including implementing the necessary
* component features. It also implements IDataComponent and IPreparedComponent.
*
* @see SQLExecute
* @see SQLLookupRule
*/
public abstract class SQLBaseComponent extends ComponentBase implements IDataComponent, IPreparedComponent,
IParameterResolver {
public static final String PREPARE_PARAMETER_PREFIX = "PREPARE"; //$NON-NLS-1$
/** stores the prepared query for later use */
protected String preparedQuery = null;
/** stores the prepared parameters for later use */
protected List preparedParameters = new ArrayList();
/** is set to false if using another IPreparedComponents connection vs own */
protected boolean connectionOwner = true;
/** reference to latest result set */
private IPentahoResultSet rSet;
/** reference to connection object */
protected IPentahoConnection connection;
//Added by Arijit Chatterjee.Takes the value of timeout
private int timeout = -1;
private int maxRows = -1; // Add ability to set this as an input
private boolean readOnly = false;
@Override
public abstract boolean validateSystemSettings();
public abstract String getResultOutputName();
@Override
public abstract Log getLogger();
/**
* returns the result set object
*
* @return pentaho result set
*/
public IPentahoResultSet getResultSet() {
return rSet;
}
/**
* validates the action. checks to verify inputs are available to execute
*
* - verify query is available
* - verify connection is available, via jndi, connection string, or prepared component
* - verify output is specified
*
*
*/
@Override
public boolean validateAction() {
boolean result = true;
IActionDefinition actionDefinition = getActionDefinition();
String actionName = getActionName();
if (actionDefinition instanceof AbstractRelationalDbAction) {
AbstractRelationalDbAction relationalDbAction = (AbstractRelationalDbAction) actionDefinition;
IActionInput query = relationalDbAction.getQuery();
IActionInput dbUrl = relationalDbAction.getDbUrl();
IActionInput jndi = relationalDbAction.getJndi();
IActionInput sharedConnection = relationalDbAction.getSharedConnection();
if (query == ActionInputConstant.NULL_INPUT) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", actionName)); //$NON-NLS-1$
result = false;
}
if ((jndi == ActionInputConstant.NULL_INPUT) && (dbUrl == ActionInputConstant.NULL_INPUT)
&& (sharedConnection == ActionInputConstant.NULL_INPUT)) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0002_CONNECTION_NOT_SPECIFIED", actionName)); //$NON-NLS-1$
result = false;
}
} else if (actionDefinition instanceof SqlConnectionAction) {
SqlConnectionAction sqlConnectionAction = (SqlConnectionAction) actionDefinition;
IActionInput dbUrl = sqlConnectionAction.getDbUrl();
IActionInput jndi = sqlConnectionAction.getJndi();
if ((jndi == ActionInputConstant.NULL_INPUT) && (dbUrl == ActionInputConstant.NULL_INPUT)) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0002_CONNECTION_NOT_SPECIFIED", actionName)); //$NON-NLS-1$
result = false;
}
} else {
error(Messages.getInstance().getErrorString(
"ComponentBase.ERROR_0001_UNKNOWN_ACTION_TYPE", actionDefinition.getElement().asXML())); //$NON-NLS-1$
result = false;
}
return result;
}
/**
* nothing to do in done call from runtime context.
*/
@Override
public void done() {
}
/**
* determines state of component, and executes accordingly.
*
* various inputs that impact the state include:
*
* live - returns a live result set vs. an in memory copy
* transform - transform a result set based on additional inputs
* prepared_component - if available, use existing connection from prepared component
* max_rows - sets the number of rows that should be returned in result sets
*
* The specified output also impacts the state of the execution. If prepared_component is defined
* as an output, setup the query but delay execution.
*
*/
@Override
protected boolean executeAction() {
IActionDefinition actionDefinition = getActionDefinition();
try {
if (actionDefinition instanceof AbstractRelationalDbAction) {
AbstractRelationalDbAction relationalDbAction = (AbstractRelationalDbAction) actionDefinition;
//Added by Arijit Chatterjee
IActionInput queryTimeoutInput = relationalDbAction.getQueryTimeout();
IActionInput maxRowsInput = relationalDbAction.getMaxRows();
IActionInput readOnlyInput = relationalDbAction.getReadOnly();
String baseQuery = getQuery();
if (baseQuery == null) {
error(Messages.getInstance().getErrorString(
"SQLBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", actionDefinition.getDescription())); //$NON-NLS-1$
return false;
}
IPreparedComponent sharedConnection = (IPreparedComponent) relationalDbAction.getSharedConnection().getValue();
if (readOnlyInput != ActionInputConstant.NULL_INPUT) {
this.setReadOnly(readOnlyInput.getBooleanValue());
}
if (sharedConnection != null) {
connectionOwner = false;
IPentahoConnection conn = sharedConnection.shareConnection();
if (conn == null) {
error(Messages.getInstance().getErrorString("IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName())); //$NON-NLS-1$
return false;
} else if (conn.getDatasourceType() == IPentahoConnection.SQL_DATASOURCE) {
connection = conn;
} else {
error(Messages.getInstance().getErrorString("IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName())); //$NON-NLS-1$
return false;
}
} else {
dispose();
connection = getDatasourceConnection();
}
if (connection == null) {
return false;
}
// Check if this is a prepared query that will be executed later. If so cache the
// query and set this component as the output. This query will be run later from a subreport.
if (relationalDbAction.getOutputPreparedStatement() != null) {
prepareQuery(baseQuery);
IActionOutput actionOutput = relationalDbAction.getOutputPreparedStatement();
if (actionOutput != null) {
actionOutput.setValue(this);
}
return true;
}
// TODO not sure if this should be allowed without connection ownership?
// int maxRows = relationalDbAction.getMaxRows().getIntValue(-1);
if (maxRowsInput != ActionInputConstant.NULL_INPUT) {
this.setMaxRows(maxRowsInput.getIntValue());
}
//Added by Arijit Chatterjee.Sets the value of timeout. Default is -1, if parameter not found.
if (queryTimeoutInput != ActionInputConstant.NULL_INPUT) {
this.setQueryTimeout(queryTimeoutInput.getIntValue());
}
if (relationalDbAction.getPerformTransform().getBooleanValue(false)) {
runQuery(baseQuery, false); // The side effect of
// transform rSet here
rSet = PentahoDataTransmuter.crossTab(rSet, relationalDbAction.getTransformPivotColumn().getIntValue(-1) - 1,
relationalDbAction.getTransformMeasuresColumn().getIntValue(-1) - 1, relationalDbAction
.getTransformSortColumn().getIntValue(0) - 1, (Format) relationalDbAction
.getTransformPivotDataFormat().getValue(), (Format) relationalDbAction.getTransformSortDataFormat()
.getValue(), relationalDbAction.getTransformOrderOutputColumns().getBooleanValue(false));
IActionOutput actionOutput = relationalDbAction.getOutputResultSet();
if (actionOutput != null) {
actionOutput.setValue(rSet);
}
return true;
} else {
return runQuery(baseQuery, relationalDbAction.getLive().getBooleanValue(false));
}
} else if (actionDefinition instanceof SqlConnectionAction) {
SqlConnectionAction sqlConnectionAction = (SqlConnectionAction) actionDefinition;
dispose();
connection = getDatasourceConnection();
if (connection == null) {
return false;
} else {
IActionOutput actionOutput = sqlConnectionAction.getOutputConnection();
if (actionOutput != null) {
actionOutput.setValue(this);
return true;
} else {
return false;
}
}
}
} catch (Exception e) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
}
return false;
}
/**
* returns metadata based on the result set. if not live, create an in memory version
*
* @param resultSet result set object to find metadata
* @param live if false, create an in memory version
*
* @return metadata object
*/
protected IPentahoMetaData getMetadata(final IPentahoResultSet resultSet, final boolean live) {
if (live) {
return resultSet.getMetaData();
} else {
Object columnHeaders[][] = resultSet.getMetaData().getColumnHeaders();
return new MemoryMetaData(columnHeaders, null);
}
}
/**
* This inner class is used as a resolver for TemplateUtil.
*/
private class ParamResolver implements IParameterResolver {
List paramList;
Map lookupMap;
public ParamResolver(final List list, final Map map) {
lookupMap = map;
paramList = list;
}
/**
* This method is called when TemplateUtil.applyTemplate() encounters a parameter.
*
* @param template the source string
* @param parameter the parameter value
* @param parameterMatcher the regex parameter matcher
* @param copyStart the start of the copy
* @param results the output result
* @return the next copystart
*/
public int resolveParameter(final String template, final String parameter, final Matcher parameterMatcher,
int copyStart, final StringBuffer results) {
StringTokenizer tokenizer = new StringTokenizer(parameter, ":"); //$NON-NLS-1$
if (tokenizer.countTokens() == 2) { // Currently, the component only handles one bit of metadata
String parameterPrefix = tokenizer.nextToken();
String inputName = tokenizer.nextToken();
if (parameterPrefix.equals(IPreparedComponent.PREPARE_LATER_INTER_PREFIX)) {
// We know this parameter is for us.
// First, is this a special input
Object parameterValue = TemplateUtil.getSystemInput(inputName, getRuntimeContext());
if ((parameterValue == null) && (lookupMap != null) && lookupMap.containsKey(inputName)) {
parameterValue = lookupMap.get(inputName);
}
if (parameterValue != null) {
// We have a parameter value - now, it's time to create a parameter and build up the
// parameter string
int start = parameterMatcher.start();
int end = parameterMatcher.end();
// First, find out if the parameter was quoted...
if ((start > 0) && (end < template.length())) {
if ((template.charAt(start - 1) == '\'') && (template.charAt(end) == '\'')) {
// Ok, the parameter was quoted as near as we can tell. So, we need
// to increase the size of the amount we overwrite by one in each
// direction. This is for backward compatibility.
start--;
end++;
}
}
// We now have a valid start and end. It's time to see whether we're dealing
// with an array, a result set, or a scalar.
StringBuffer parameterBuffer = new StringBuffer();
// find and remove the next placeholder, to be replaced by the new value
int index = paramList.indexOf(IPreparedComponent.PREPARE_LATER_PLACEHOLDER);
paramList.remove(index);
if (parameterValue instanceof String) {
paramList.add(index, parameterValue);
// preparedParameters.add(parameterValue);
parameterBuffer.append('?');
} else if (parameterValue instanceof Object[]) {
Object[] pObj = (Object[]) parameterValue;
for (Object element : pObj) {
paramList.add(index++, element);
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (parameterValue instanceof IPentahoResultSet) {
IPentahoResultSet rs = (IPentahoResultSet) parameterValue;
// See if we can find a column in the metadata with the same
// name as the input
IPentahoMetaData md = rs.getMetaData();
int columnIdx = -1;
if (md.getColumnCount() == 1) {
columnIdx = 0;
} else {
columnIdx = md.getColumnIndex(new String[] { parameter });
}
if (columnIdx < 0) {
error(Messages.getInstance().getErrorString("Template.ERROR_0005_COULD_NOT_DETERMINE_COLUMN")); //$NON-NLS-1$
return -1;
}
int rowCount = rs.getRowCount();
Object valueCell = null;
// TODO support non-string columns
for (int i = 0; i < rowCount; i++) {
valueCell = rs.getValueAt(i, columnIdx);
paramList.add(index++, valueCell);
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (parameterValue instanceof List) {
List pObj = (List) parameterValue;
for (int i = 0; i < pObj.size(); i++) {
paramList.add(index++, pObj.get(i));
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else {
// If we're here, we know parameterValue is not null and not a string
paramList.add(index, parameterValue);
parameterBuffer.append('?');
}
// OK - We have a parameterBuffer and have filled out the preparedParameters
// list. It's time to change the SQL to insert our parameter marker and tell
// the caller we've done our job.
results.append(template.substring(copyStart, start));
copyStart = end;
results.append(parameterBuffer);
return copyStart;
}
}
}
return -1; // Nothing here for us - let default behavior through
}
}
/**
* executes a prepared method that returns a result set
* executePrepared looks up any "PREPARELATER" params
* in the preparedParams map.
*
* @param preparedParams a map of possible parameters.
* @return result set
*/
public IPentahoResultSet executePrepared(final Map preparedParams) {
try {
if (connection == null) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0007_NO_CONNECTION")); //$NON-NLS-1$
return null;
}
if (!connection.initialized()) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0007_NO_CONNECTION")); //$NON-NLS-1$
return null;
}
if (preparedQuery == null) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName())); //$NON-NLS-1$
return null;
}
// copy the preparedParams list, so it can be used multiple times.
ArrayList copyOfPreparedParameters = new ArrayList(preparedParameters);
// parse preparedQuery, replacing any {PREPARELATER:NAME} with appropriate values
String query = TemplateUtil.applyTemplate(preparedQuery, getRuntimeContext(), new ParamResolver(
copyOfPreparedParameters, preparedParams));
if (ComponentBase.debug) {
dumpQuery(query);
}
// evaluate
IPentahoResultSet resultSet = null;
if (preparedParameters.size() > 0) {
resultSet = connection.prepareAndExecuteQuery(query, copyOfPreparedParameters);
} else {
resultSet = connection.executeQuery(query);
}
if (connection instanceof SQLConnection){
if (((SQLConnection)connection).isForcedForwardOnly()){
warn(Messages.getInstance().getString("SQLBaseComponent.WARN_FALL_BACK_TO_NONSCROLLABLE")); //$NON-NLS-1$
}
}
rSet = resultSet;
return resultSet;
} catch (Exception e) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
}
return null;
}
/**
* called when in prepared-component mode, this method populates the preparedQuery string and
* preparedParameters object.
*
* @param rawQuery
* @return
*/
protected boolean prepareQuery(final String rawQuery) {
try {
if (connection == null) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0007_NO_CONNECTION")); //$NON-NLS-1$
return false;
}
if (!connection.initialized()) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0007_NO_CONNECTION")); //$NON-NLS-1$
return false;
}
preparedQuery = rawQuery;
return true;
} catch (Exception e) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
}
return false;
}
/**
* executes the specified query template. The query template is first formatted and then
* executed. If live, the original result set is made available as an output. If not live,
* the result set is converted into memory and the connection and live result set are closed.
*
* @param rawQuery query template
* @param live returns original result set if true, memory result set if false
* @return true if successful
*/
protected boolean runQuery(final String rawQuery, boolean live) {
try {
if ((connection == null) || !connection.initialized()) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0007_NO_CONNECTION")); //$NON-NLS-1$
return false;
}
String query = applyInputsToFormat(rawQuery);
SQLConnection sqlConnection = null;
if ((connection instanceof SQLConnection)) {
sqlConnection = (SQLConnection) connection;
}
// Some of the following Added by Arijit Chatterjee passing the timeout value to SQLConnection class
if (sqlConnection != null) {
if (this.getQueryTimeout() >= 0 ) {
sqlConnection.setQueryTimeout(this.getQueryTimeout());
}
if (this.getMaxRows() >= 0) {
sqlConnection.setMaxRows(this.getMaxRows());
}
if (this.getReadOnly()) {
sqlConnection.setReadOnly(true);
}
}
AbstractRelationalDbAction relationalDbAction = (AbstractRelationalDbAction) getActionDefinition();
IPentahoResultSet resultSet = null;
boolean isForwardOnly = relationalDbAction.getUseForwardOnlyResultSet().getBooleanValue(false);
resultSet = doQuery(sqlConnection, query, isForwardOnly);
if (sqlConnection.isForcedForwardOnly()){
isForwardOnly = true;
live = false;
warn(Messages.getInstance().getString("SQLBaseComponent.WARN_FALL_BACK_TO_NONSCROLLABLE")); //$NON-NLS-1$
}
if (live) {
// set the result set as the output
rSet = resultSet;
// After preparation and execution, we need to clear out the
// prepared parameters.
preparedParameters.clear();
if (resultSet != null) {
getMetadata(resultSet, true);
IActionOutput actionOutput = relationalDbAction.getOutputResultSet();
if (actionOutput != null) {
actionOutput.setValue(resultSet);
}
return true;
} else {
// close the connection if owner
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName())); //$NON-NLS-1$
if (connectionOwner) {
connection.close();
}
return false;
}
} else {
// execute the query, read the results and cache them
try {
// After preparation and execution, we need to clear out the
// prepared parameters.
preparedParameters.clear();
IPentahoResultSet cachedResultSet = resultSet.memoryCopy();
rSet = cachedResultSet;
IActionOutput actionOutput = relationalDbAction.getOutputResultSet();
if (actionOutput != null) {
actionOutput.setValue(cachedResultSet);
}
} finally {
// close the connection if owner
if (connectionOwner) {
connection.close();
connection = null;
}
}
}
return true;
} catch (Exception e) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
}
return false;
}
public IPentahoResultSet doQuery(final SQLConnection sqlConnection, final String query, boolean forwardOnlyResultset)
throws Exception {
//
// At this point, 'connection' and 'sqlConnection' should be pointers to
// the same object iff the 'connection' is a subclass of pentaho's SQLConnection.
// It is possible that the sqlConnection will be null, but the connection
// won't be if someone is using their own implementation of the SQLConnection from
// the factory.
//
IPentahoResultSet resultSet = null;
if (ComponentBase.debug) {
dumpQuery(query);
}
if (preparedParameters.size() > 0) {
if (!forwardOnlyResultset) {
resultSet = connection.prepareAndExecuteQuery(query, preparedParameters);
} else {
if (sqlConnection != null) {
resultSet = sqlConnection.prepareAndExecuteQuery(query, preparedParameters,
SQLConnection.RESULTSET_FORWARDONLY, SQLConnection.CONCUR_READONLY);
} else {
throw new IllegalStateException(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0008_UNSUPPORTED_CURSOR_TYPE")); //$NON-NLS-1$
}
}
} else {
if (!forwardOnlyResultset) {
resultSet = connection.executeQuery(query);
} else {
if (sqlConnection != null) {
resultSet = sqlConnection.executeQuery(query, SQLConnection.RESULTSET_FORWARDONLY,
SQLConnection.CONCUR_READONLY);
} else {
throw new IllegalStateException(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0008_UNSUPPORTED_CURSOR_TYPE")); //$NON-NLS-1$
}
}
}
return resultSet;
}
/**
* dispose of the resultset, and if the owner, dispose of the connection.
*/
public void dispose() {
rSet = null;
// close connection if owner
if (connectionOwner) {
if (connection != null) {
connection.close();
}
connection = null;
}
}
/**
* This method is called when TemplateUtil.applyTemplate() encounters a parameter.
* TemplateUtil.applyTemplate is called when someone makes a call to applyInputsToFormat()
* In this class it is called in the above "runQuery()" method.
*
* @param template the source string
* @param parameter the parameter value
* @param parameterMatcher the regex parameter matcher
* @param copyStart the start of the copy
* @param results the output result
* @return the next copystart
*/
@Override
public int resolveParameter(final String template, final String parameter, final Matcher parameterMatcher,
int copyStart, final StringBuffer results) {
StringTokenizer tokenizer = new StringTokenizer(parameter, ":"); //$NON-NLS-1$
if (tokenizer.countTokens() == 2) { // Currently, the component only handles one bit of metadata
String parameterPrefix = tokenizer.nextToken();
String inputName = tokenizer.nextToken();
// if the template contains a prepare later prefix,
// mark a spot in the preparedParameters list and move on.
if (parameterPrefix.equals(IPreparedComponent.PREPARE_LATER_PREFIX)) {
if (!isDefinedOutput(IPreparedComponent.PREPARED_COMPONENT_NAME)) {
error(Messages.getInstance().getErrorString("IPreparedComponent.ERROR_0003_INVALID_PARAMETER_STATE")); //$NON-NLS-1$
return -1;
}
preparedParameters.add(IPreparedComponent.PREPARE_LATER_PLACEHOLDER);
int start = parameterMatcher.start();
int end = parameterMatcher.end();
results.append(template.substring(copyStart, start));
results.append("{" + IPreparedComponent.PREPARE_LATER_INTER_PREFIX + ":" + inputName + "}"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
return end;
}
if (parameterPrefix.equals(SQLBaseComponent.PREPARE_PARAMETER_PREFIX)) {
// We know this parameter is for us.
// First, is this a special input
Object parameterValue = TemplateUtil.getSystemInput(inputName, getRuntimeContext());
if ((parameterValue == null) && isDefinedInput(inputName)) {
parameterValue = this.getInputValue(inputName);
}
if (parameterValue != null) {
// We have a parameter value - now, it's time to create a parameter and build up the
// parameter string
int start = parameterMatcher.start();
int end = parameterMatcher.end();
// First, find out if the parameter was quoted...
if ((start > 0) && (end < template.length())) {
if ((template.charAt(start - 1) == '\'') && (template.charAt(end) == '\'')) {
// Ok, the parameter was quoted as near as we can tell. So, we need
// to increase the size of the amount we overwrite by one in each
// direction. This is for backward compatibility.
start--;
end++;
}
}
// We now have a valid start and end. It's time to see whether we're dealing
// with an array, a result set, or a scalar.
StringBuffer parameterBuffer = new StringBuffer();
if (parameterValue instanceof String) {
preparedParameters.add(parameterValue);
parameterBuffer.append('?');
} else if (parameterValue instanceof Object[]) {
Object[] pObj = (Object[]) parameterValue;
for (Object element : pObj) {
preparedParameters.add(element);
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (parameterValue instanceof IPentahoResultSet) {
IPentahoResultSet rs = (IPentahoResultSet) parameterValue;
// See if we can find a column in the metadata with the same
// name as the input
IPentahoMetaData md = rs.getMetaData();
int columnIdx = -1;
if (md.getColumnCount() == 1) {
columnIdx = 0;
} else {
columnIdx = md.getColumnIndex(new String[] { parameter });
}
if (columnIdx < 0) {
error(Messages.getInstance().getErrorString("Template.ERROR_0005_COULD_NOT_DETERMINE_COLUMN")); //$NON-NLS-1$
return -1;
}
int rowCount = rs.getRowCount();
Object valueCell = null;
// TODO support non-string columns
for (int i = 0; i < rowCount; i++) {
valueCell = rs.getValueAt(i, columnIdx);
preparedParameters.add(valueCell);
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else if (parameterValue instanceof List) {
List pObj = (List) parameterValue;
for (int i = 0; i < pObj.size(); i++) {
preparedParameters.add(pObj.get(i));
parameterBuffer.append((parameterBuffer.length() == 0) ? "?" : ",?"); //$NON-NLS-1$ //$NON-NLS-2$
}
} else {
// If we're here, we know parameterValue is not null and not a string
this.preparedParameters.add(parameterValue);
parameterBuffer.append('?');
}
// OK - We have a parameterBuffer and have filled out the preparedParameters
// list. It's time to change the SQL to insert our parameter marker and tell
// the caller we've done our job.
results.append(template.substring(copyStart, start));
copyStart = end;
results.append(parameterBuffer);
return copyStart;
}
}
}
return -1; // Nothing here for us - let default behavior through
}
/**
* attempt to aquire a connection. if connection isn't available, wait a certain period of time
* before trying again.
*
* @return connection
*/
public IPentahoConnection getDatasourceConnection() {
IPentahoConnection con;
int timeouts[] = { 200, 500, 2000 };
for (int element : timeouts) {
try {
con = getConnection();
try {
con.clearWarnings();
} catch (Exception ex) {
}
return con;
} catch (Exception ex) {
}
waitFor(element);
}
con = getConnection();
try {
con.clearWarnings();
} catch (Exception ex) {
}
return con;
}
/**
* pause the thread a certain number of milliseconds
*
* @param millis time to sleep
*/
protected void waitFor(final int millis) {
try {
if (ComponentBase.debug) {
debug(Messages.getInstance().getString("SQLBaseComponent.DEBUG_WAITING_FOR_CONNECTION", Integer.toString(millis))); //$NON-NLS-1$
}
Thread.sleep(millis);
} catch (Exception ex) {
// ignore the interrupted exception, if it happens
}
}
/**
* return this class's connection. This implements the IPreparedComponent
* interface, which may share its connection with others.
*
* @return connection object
*/
public IPentahoConnection shareConnection() {
return connection;
}
/**
* pass through to getConnection(defaultConnection)
*
* @return connection
*/
protected IPentahoConnection getConnection() {
return getConnection(null);
}
/**
* This method retrieves a connection based on the components
* inputs.
*
* @param defaultConnection a default connection to use if no other is available
* @return new connection object
*/
protected IPentahoConnection getConnection(final IPentahoConnection defaultConnection) {
IPentahoConnection localConnection = null;
try {
String jndiName = null;
String driver = null;
String userId = null;
String password = null;
String connectionInfo = null;
if (getActionDefinition() instanceof SqlConnectionAction) {
SqlConnectionAction sqlConnectionAction = (SqlConnectionAction) getActionDefinition();
jndiName = sqlConnectionAction.getJndi().getStringValue();
driver = sqlConnectionAction.getDriver().getStringValue();
userId = sqlConnectionAction.getUserId().getStringValue();
password = sqlConnectionAction.getPassword().getStringValue();
connectionInfo = sqlConnectionAction.getDbUrl().getStringValue();
} else if (getActionDefinition() instanceof AbstractRelationalDbAction) {
AbstractRelationalDbAction relationalDbAction = (AbstractRelationalDbAction) getActionDefinition();
jndiName = relationalDbAction.getJndi().getStringValue();
driver = relationalDbAction.getDriver().getStringValue();
userId = relationalDbAction.getUserId().getStringValue();
password = relationalDbAction.getPassword().getStringValue();
connectionInfo = relationalDbAction.getDbUrl().getStringValue();
}
if (jndiName != null) {
localConnection = PentahoConnectionFactory.getConnection(IPentahoConnection.SQL_DATASOURCE, jndiName,
getSession(), this);
}
if (localConnection == null) {
if ((driver == null) && (connectionInfo == null)) {
// TODO raise an error
} else {
localConnection = PentahoConnectionFactory.getConnection(IPentahoConnection.SQL_DATASOURCE, driver,
connectionInfo, userId, password, getSession(), this);
}
}
if (localConnection == null) {
if (defaultConnection == null) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0005_INVALID_CONNECTION")); //$NON-NLS-1$
return null;
} else {
localConnection = defaultConnection;
}
}
return localConnection;
} catch (Exception e) {
error(Messages.getInstance().getErrorString("SQLBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
}
return null;
}
/**
* nothing is done in the init function
*
* @return true always
*/
@Override
public boolean init() {
return true;
}
//Added By Arijit Chatterjee,This method is not used anywhere added for only testing purposes
public int getQueryTimeout() {
// removed the destruction of parameters on a get.
// preparedParameters.clear();
return timeout;
}
//Added By Arijit Chatterjee.Sets the value of timeout
public void setQueryTimeout(final int timeInSec) {
timeout = timeInSec;
}
public int getMaxRows() {
return this.maxRows;
}
public void setMaxRows(final int value) {
this.maxRows = value;
}
public String getQuery() {
preparedParameters.clear();
return ((AbstractRelationalDbAction) getActionDefinition()).getQuery().getStringValue();
}
public void setReadOnly(final boolean value) {
this.readOnly = value;
}
public boolean getReadOnly() {
return this.readOnly;
}
private void dumpQuery(final String query) {
if (timeout == 0) {
debug(Messages.getInstance().getString("SQLBaseComponent.DEBUG_RUNNING_QUERY", query)); //$NON-NLS-1$
} else {
debug(Messages.getInstance().getString("SQLBaseComponent.DEBUG_RUNNING_QUERY_TIMEOUT", query, "" + timeout)); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}