/*! * 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 (c) 2002-2016 Pentaho Corporation.. All rights reserved. */ package org.pentaho.platform.plugin.action.mdx; import com.google.common.annotations.VisibleForTesting; import mondrian.olap.Util; import mondrian.rolap.RolapConnectionProperties; import mondrian.util.Pair; import org.apache.commons.logging.Log; import org.pentaho.actionsequence.dom.ActionInputConstant; import org.pentaho.actionsequence.dom.IActionOutput; import org.pentaho.actionsequence.dom.actions.MdxConnectionAction; import org.pentaho.actionsequence.dom.actions.MdxQueryAction; import org.pentaho.commons.connection.IPentahoConnection; import org.pentaho.commons.connection.IPentahoResultSet; import org.pentaho.platform.api.data.IDBDatasourceService; import org.pentaho.platform.api.data.IDataComponent; import org.pentaho.platform.api.data.IPreparedComponent; import org.pentaho.platform.api.engine.IActionSequenceResource; import org.pentaho.platform.api.repository2.unified.IUnifiedRepository; import org.pentaho.platform.api.repository2.unified.UnifiedRepositoryException; import org.pentaho.platform.engine.core.system.PentahoSessionHolder; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.engine.services.connection.PentahoConnectionFactory; import org.pentaho.platform.engine.services.runtime.MapParameterResolver; 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.action.mondrian.catalog.IMondrianCatalogService; import org.pentaho.platform.plugin.action.mondrian.catalog.MondrianCatalog; import org.pentaho.platform.plugin.action.mondrian.catalog.MondrianCatalogHelper; import org.pentaho.platform.plugin.services.connections.mondrian.MDXConnection; import org.pentaho.platform.plugin.services.connections.mondrian.MDXResultSet; import org.pentaho.platform.util.messages.LocaleHelper; import java.util.Iterator; import java.util.Map; import java.util.Properties; public abstract class MDXBaseComponent extends ComponentBase implements IDataComponent, IPreparedComponent { private static final long serialVersionUID = 495868243986115468L; public static final String FORMATTED_CELL_VALUES = "formattedCellValues"; //$NON-NLS-1$ private IPentahoResultSet rSet; /** is set to false if using another IPreparedComponents connection vs own */ private boolean connectionOwner = true; /** keep a reference to the connection for prepared component functionality */ private IPentahoConnection connection; /** stores the prepared query for later use */ String preparedQuery = null; @Override public abstract boolean validateSystemSettings(); @Override public abstract Log getLogger(); public IPentahoResultSet getResultSet() { return rSet; } @Override protected boolean validateAction() { boolean actionValidated = true; MdxQueryAction queryAction = null; MdxConnectionAction connAction = null; try { if ( getActionDefinition() instanceof MdxQueryAction ) { queryAction = (MdxQueryAction) getActionDefinition(); actionValidated = isConnectionInfoSpecified( queryAction ); if ( actionValidated ) { if ( queryAction.getQuery() == ActionInputConstant.NULL_INPUT ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ actionValidated = false; } } if ( actionValidated ) { if ( ( queryAction.getOutputResultSet() == null ) && ( queryAction.getOutputPreparedStatement() == null ) ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ actionValidated = false; } } } else if ( getActionDefinition() instanceof MdxConnectionAction ) { connAction = (MdxConnectionAction) getActionDefinition(); actionValidated = isConnectionInfoSpecified( connAction ); if ( connAction.getOutputConnection() == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ actionValidated = false; } } } catch ( Exception e ) { actionValidated = false; error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0004_VALIDATION_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return actionValidated; } /* * */ private boolean isConnectionInfoSpecified( final MdxConnectionAction connAction ) { boolean value = true; if ( connAction instanceof MdxQueryAction ) { if ( ( connAction.getConnection() == ActionInputConstant.NULL_INPUT ) && ( connAction.getMdxConnectionString() == null ) && ( connAction.getJndi() == ActionInputConstant.NULL_INPUT ) && ( connAction.getConnectionProps() == ActionInputConstant.NULL_INPUT ) && ( ( (MdxQueryAction) connAction ).getMdxConnection() == ActionInputConstant.NULL_INPUT ) ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0002_CONNECTION_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ value = false; } } else if ( connAction instanceof MdxConnectionAction ) { if ( ( connAction.getConnection() == ActionInputConstant.NULL_INPUT ) && ( connAction.getMdxConnectionString() == null ) && ( connAction.getJndi() == ActionInputConstant.NULL_INPUT ) && ( connAction.getConnectionProps() == ActionInputConstant.NULL_INPUT ) ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0002_CONNECTION_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ value = false; } } return value; } @Override public void done() { } @Override protected boolean executeAction() { boolean value = false; /* * This is the query part. You would need a connection to execute the query. The connection will either come in as * an INPUT (prepared_component) or will be specified right there. * * So check if a prepared component exists, if not create a new connection. If connection is not null, proceed to * work on the query part. * * In the query section you can either execute the query right away or prepare it to be used later by a sub report. */ try { if ( getActionDefinition() instanceof MdxQueryAction ) { MdxQueryAction queryAction = (MdxQueryAction) getActionDefinition(); // if there is a prepared component specified as an input, use its connection // instead of creating our own. if ( queryAction.getMdxConnection() != ActionInputConstant.NULL_INPUT ) { if ( queryAction.getMdxConnection().getValue() != null ) { connectionOwner = false; IPreparedComponent component = (IPreparedComponent) queryAction.getMdxConnection().getValue(); IPentahoConnection conn = component.shareConnection(); if ( conn.getDatasourceType() == IPentahoConnection.MDX_DATASOURCE ) { connection = conn; } else { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName() ) ); //$NON-NLS-1$ } } else { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName() ) ); //$NON-NLS-1$ } } else { dispose(); connection = getDatasourceConnection(); } if ( connection != null ) { String query = queryAction.getQuery().getStringValue(); if ( queryAction.getOutputPreparedStatement() != null ) { // prepare the query for execution, but don't execute quite yet. prepareQuery( query ); // set the output as self, which will be used later by another component. setOutputValue( IPreparedComponent.PREPARED_COMPONENT_NAME, this ); value = true; } else { value = runQuery( connection, query ); } } else { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0004_NO_CONNECTION_INFO", getActionName() ) ); //$NON-NLS-1$ } } else if ( getActionDefinition() instanceof MdxConnectionAction ) { dispose(); connection = getDatasourceConnection(); if ( connection != null ) { setOutputValue( IPreparedComponent.PREPARED_COMPONENT_NAME, this ); value = true; } } else { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0004_VALIDATION_FAILED", getActionName() ) ); //$NON-NLS-1$ } } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return value; } /** * 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( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return false; } if ( !connection.initialized() ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return false; } if ( rawQuery != null ) { preparedQuery = applyInputsToFormat( rawQuery ); } return true; } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return false; } /** * if the owner, dispose of the connection */ public void dispose() { if ( connectionOwner ) { if ( connection != null ) { connection.close(); } } connection = null; } /** * 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; } /** * 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( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return null; } if ( !connection.initialized() ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return null; } if ( preparedQuery == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ return null; } // parse preparedQuery, replacing any {PREPARELATER:NAME} with appropriate values String query = TemplateUtil.applyTemplate( preparedQuery, getRuntimeContext(), new MapParameterResolver( preparedParams, IPreparedComponent.PREPARE_LATER_PREFIX, getRuntimeContext() ) ); if ( ComponentBase.debug ) { debug( Messages.getInstance().getString( "MDXBaseComponent.DEBUG_RUNNING_QUERY", query ) ); //$NON-NLS-1$ } // evaluate IPentahoResultSet resultSet = connection.executeQuery( query ); rSet = resultSet; return resultSet; } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return null; } protected boolean runQuery( final IPentahoConnection localConnection, final String rawQuery ) { try { if ( localConnection == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return false; } if ( !localConnection.initialized() ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0008_NO_CONNECTION" ) ); //$NON-NLS-1$ return false; } if ( rawQuery == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ return false; } if ( ComponentBase.debug ) { debug( Messages.getInstance().getString( "MDXBaseComponent.DEBUG_RUNNING_QUERY", rawQuery ) ); //$NON-NLS-1$ } // execute the query, read the results and cache them IPentahoResultSet resultSet = localConnection.executeQuery( rawQuery ); if ( resultSet != null && resultSet instanceof MDXResultSet ) { // BISERVER-3543 - set the result set to return formatted cell values boolean formattedCellValues = false; if ( isDefinedInput( FORMATTED_CELL_VALUES ) ) { formattedCellValues = getInputBooleanValue( FORMATTED_CELL_VALUES, false ); } ( (MDXResultSet) resultSet ).setFormattedCellValues( formattedCellValues ); } rSet = resultSet; if ( resultSet != null ) { MdxQueryAction mdxQueryAction = (MdxQueryAction) getActionDefinition(); IActionOutput actionOutput = mdxQueryAction.getOutputResultSet(); if ( actionOutput != null ) { actionOutput.setValue( resultSet ); } return true; } else { // close the connection error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ) ); //$NON-NLS-1$ localConnection.close(); return false; } } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return false; } /** * 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 ) { //ignored } return con; } catch ( Exception ex ) { //ignored } waitFor( element ); } con = getConnection(); try { con.clearWarnings(); } catch ( Exception ex ) { //ignore } return con; } protected void waitFor( final int millis ) { try { if ( ComponentBase.debug ) { debug( Messages.getInstance().getString( "MDXBaseComponent.DEBUG_WAITING_FOR_CONNECTION", Integer.toString( millis ) ) ); //$NON-NLS-1$ } Thread.sleep( millis ); } catch ( Exception ex ) { // ignore the interrupted exception, if it happens } } protected IPentahoConnection getConnection() { // first attempt to get the connection metadata from the catalog service. if that is not successful, // get the connection using the original approach. MdxConnectionAction connAction = (MdxConnectionAction) getActionDefinition(); String catalogName = connAction.getCatalog().getStringValue(); IMondrianCatalogService mondrianCatalogService = PentahoSystem.get( IMondrianCatalogService.class, "IMondrianCatalogService", PentahoSessionHolder.getSession() ); //$NON-NLS-1$ MondrianCatalog catalog = mondrianCatalogService.getCatalog( catalogName, PentahoSessionHolder.getSession() ); if ( catalog == null ) { return getConnectionOrig(); } Util.PropertyList connectProperties = Util.parseConnectString( catalog.getDataSourceInfo() ); Properties properties = new Properties(); Iterator<Pair<String, String>> iter = connectProperties.iterator(); while ( iter.hasNext() ) { Pair<String, String> pair = iter.next(); properties.put( pair.getKey(), pair.getValue() ); } properties.put( "Catalog", catalog.getDefinition() ); properties.put( "Provider", "mondrian" ); properties.put( "PoolNeeded", "false" ); properties.put( RolapConnectionProperties.Locale.name(), LocaleHelper.getLocale().toString() ); debug( "Mondrian Connection Properties: " + properties.toString() ); MDXConnection mdxConnection = (MDXConnection) PentahoConnectionFactory.getConnection( IPentahoConnection.MDX_DATASOURCE, properties, PentahoSessionHolder.getSession(), this ); if ( connAction != null ) { if ( ( connAction.getExtendedColumnNames() != ActionInputConstant.NULL_INPUT ) ) { mdxConnection.setUseExtendedColumnNames( connAction.getExtendedColumnNames().getBooleanValue() ); } } return mdxConnection; } protected IPentahoConnection getConnectionOrig() { IPentahoConnection localConnection = null; MdxConnectionAction connAction = (MdxConnectionAction) getActionDefinition(); try { String mdxConnectionStr = connAction.getMdxConnectionString().getStringValue(); Properties mdxConnectionProps = (Properties) connAction.getConnectionProps().getValue(); String jdbcStr = connAction.getConnection().getStringValue(); String jndiStr = connAction.getJndi().getStringValue(); String location = connAction.getLocation().getStringValue(); String role = connAction.getRole().getStringValue(); String catalog = connAction.getCatalog().getStringValue(); if ( ( catalog == null ) && ( connAction.getCatalogResource() != null ) ) { IActionSequenceResource resource = getResource( connAction.getCatalogResource().getName() ); catalog = resource.getAddress(); if ( resource.getSourceType() == IActionSequenceResource.URL_RESOURCE ) { if ( !catalog.startsWith( "solution:" ) && !catalog.startsWith( "http:" ) ) { //$NON-NLS-1$ if ( fileExistsInRepository( catalog ) ) { // About allowed "solution:" // Extra step to make sure that remote mondrian models // fully qualified aren't munged // MB catalog = "solution:" + catalog; //$NON-NLS-1$ } } } else if ( ( resource.getSourceType() == IActionSequenceResource.SOLUTION_FILE_RESOURCE ) || ( resource.getSourceType() == IActionSequenceResource.FILE_RESOURCE ) ) { if ( !catalog.startsWith( "solution:" ) ) { catalog = "solution:" + catalog; //$NON-NLS-1$ } } } if ( catalog == null ) { warn( Messages.getInstance().getString( "MDXBaseComponent.ERROR_0007_CATALOG_NOT_DEFINED", getActionName() ) ); //$NON-NLS-1$ } else { if ( mdxConnectionProps != null ) { mdxConnectionProps.put( MdxConnectionAction.CATALOG_ELEMENT, catalog ); } } String userId = connAction.getUserId().getStringValue(); String password = connAction.getPassword().getStringValue(); if ( mdxConnectionProps != null ) { localConnection = PentahoConnectionFactory.getConnection( IPentahoConnection.MDX_DATASOURCE, mdxConnectionProps, getSession(), this ); } else { if ( mdxConnectionStr != null ) { localConnection = PentahoConnectionFactory.getConnection( IPentahoConnection.MDX_DATASOURCE, mdxConnectionStr, getSession(), this ); } else { String connectStr = null; if ( jdbcStr != null ) { connectStr = jdbcStr + "; Catalog=" + catalog; //$NON-NLS-1$ } else if ( jndiStr != null ) { IDBDatasourceService datasourceService = PentahoSystem.getObjectFactory().get( IDBDatasourceService.class, null ); if ( datasourceService.getDataSource( jndiStr ) == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0005_INVALID_CONNECTION" ) ); //$NON-NLS-1$ return null; } connectStr = "dataSource=" + jndiStr + "; Catalog=" + catalog; //$NON-NLS-1$ //$NON-NLS-2$ // Add extra definitions from platform mondrian metadata MondrianCatalog mc = getMondrianCatalog( catalog ); try { connectStr += ";" + mc.getDataSourceInfo(); } catch ( Exception e ) { // Just swallow the exception } } if ( role != null ) { connectStr += "; Role=" + role; //$NON-NLS-1$ } Properties props = new Properties(); props.setProperty( IPentahoConnection.CONNECTION, connectStr ); props.setProperty( IPentahoConnection.PROVIDER, location ); if ( userId != null ) { props.setProperty( IPentahoConnection.USERNAME_KEY, userId ); } if ( password != null ) { props.setProperty( IPentahoConnection.PASSWORD_KEY, password ); } localConnection = PentahoConnectionFactory.getConnection( IPentahoConnection.MDX_DATASOURCE, props, getSession(), this ); } if ( localConnection == null ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0005_INVALID_CONNECTION" ) ); //$NON-NLS-1$ return null; } } if ( localConnection instanceof MDXConnection ) { MDXConnection mdxConn = (MDXConnection) localConnection; if ( connAction != null ) { if ( ( connAction.getExtendedColumnNames() != ActionInputConstant.NULL_INPUT ) ) { mdxConn.setUseExtendedColumnNames( connAction.getExtendedColumnNames().getBooleanValue() ); } } } return localConnection; } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "MDXBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return null; } @Override public boolean init() { return true; } @Override protected void setOutputValue( String outputName, Object value ) { super.setOutputValue( outputName, value ); } // for unit testing protected void setConnection( IPentahoConnection connection ) { this.connection = connection; } // for unit testing @Override protected String applyInputsToFormat( String format ) { return super.applyInputsToFormat( format ); } @VisibleForTesting MondrianCatalog getMondrianCatalog( String catalog ) { return MondrianCatalogHelper.getInstance().getCatalog( catalog, getSession() ); } @VisibleForTesting boolean fileExistsInRepository( String catalog ) { try { IUnifiedRepository unifiedRepository = PentahoSystem.get( IUnifiedRepository.class, PentahoSessionHolder.getSession() ); return unifiedRepository.getFile( catalog ) != null; } catch ( UnifiedRepositoryException e ) { // this can happen if file name does not start from "/", // for example, in windows os return false; } } }