/*! * 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.xml.xquery; import net.sf.saxon.trans.XPathException; import org.apache.commons.io.FilenameUtils; import org.apache.commons.logging.Log; import org.dom4j.Document; import org.dom4j.Node; import org.dom4j.io.SAXReader; 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.XQueryAction; import org.pentaho.actionsequence.dom.actions.XQueryConnectionAction; import org.pentaho.commons.connection.IPentahoConnection; import org.pentaho.commons.connection.IPentahoResultSet; import org.pentaho.platform.api.data.IPreparedComponent; import org.pentaho.platform.api.engine.IActionSequenceResource; import org.pentaho.platform.api.repository2.unified.RepositoryFilePermission; 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.engine.services.solution.StandardSettings; import org.pentaho.platform.plugin.action.messages.Messages; import org.pentaho.platform.plugin.services.connections.xquery.XQConnection; import org.pentaho.platform.util.xml.XMLParserFactoryProducer; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.URL; import java.util.Calendar; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; /** * XQueryBaseComponent provides a mechanism to run xqueries within the Pentaho BI Platform. * <p/> * <p/> * TODO: In regards to IPreparedComponent, implement a method for choosing the datasource on the fly */ public abstract class XQueryBaseComponent extends ComponentBase implements IPreparedComponent { private static final long serialVersionUID = -4390530975622541328L; private IPentahoResultSet rSet; /** * reference to connection object */ protected IPentahoConnection connection; /** * keeps track of ownership of connection */ protected boolean connectionOwner = true; private static final String FILENAME_PREFIX = "tmp"; //$NON-NLS-1$ private static final String EXTENSION = ".xml"; //$NON-NLS-1$ // private static final String TEMP_DIRECTORY = "system/tmp/"; //$NON-NLS-1$ private static final String XML_DOCUMENT_TAG = "XML_DOCUMENT"; //$NON-NLS-1$ @Override public abstract boolean validateSystemSettings(); @Override public abstract Log getLogger(); private int maxRows = -1; /** * string to hold prepared query until execution */ String preparedQuery = null; /** * string array to hold prepared column types until execution */ String[] preparedColumnTypes = null; public IPentahoResultSet getResultSet() { return rSet; } @Override protected boolean validateAction() { boolean result = false; IActionDefinition actionDefinition = getActionDefinition(); if ( actionDefinition instanceof XQueryAction ) { XQueryAction xQueryAction = (XQueryAction) actionDefinition; if ( ( xQueryAction.getSourceXml() == ActionInputConstant.NULL_INPUT ) && ( xQueryAction.getXmlDocument() == null ) ) { error( Messages.getInstance() .getString( "XQueryBaseComponent.ERROR_0008_SOURCE_NOT_DEFINED", getActionName() ) ); //$NON-NLS-1$ } else if ( xQueryAction.getQuery() == ActionInputConstant.NULL_INPUT ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ } else if ( ( xQueryAction.getOutputPreparedStatement() == null ) && ( xQueryAction.getOutputResultSet() == null ) ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ } else { result = true; } } else if ( actionDefinition instanceof XQueryConnectionAction ) { XQueryConnectionAction xQueryConnectionAction = (XQueryConnectionAction) actionDefinition; if ( xQueryConnectionAction.getOutputConnection() == null ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ } else { result = true; } } else { error( Messages.getInstance().getErrorString( "ComponentBase.ERROR_0001_UNKNOWN_ACTION_TYPE", actionDefinition.getElement().asXML() ) ); //$NON-NLS-1$ } return result; } @Override public void done() { // TODO Auto-generated method stub } @Override protected boolean executeAction() { boolean result = false; IActionDefinition actionDefinition = getActionDefinition(); // int maxRows = -1; // int queryTimeout = -1; if ( actionDefinition instanceof XQueryAction ) { XQueryAction xQueryAction = (XQueryAction) actionDefinition; // Not implemented yet // IActionInput queryTimeoutInput = xQueryAction.getQueryTimeout(); IActionInput maxRowsInput = xQueryAction.getMaxRows(); if ( maxRowsInput != ActionInputConstant.NULL_INPUT ) { this.setMaxRows( maxRowsInput.getIntValue() ); } IPreparedComponent sharedConnection = (IPreparedComponent) xQueryAction.getSharedConnection().getValue(); if ( sharedConnection != null ) { connectionOwner = false; connection = sharedConnection.shareConnection(); } else { connection = getConnection(); } if ( connection == null ) { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName() ) ); //$NON-NLS-1$ } else if ( connection.getDatasourceType() != IPentahoConnection.XML_DATASOURCE ) { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName() ) ); //$NON-NLS-1$ } else { result = runQuery( connection, xQueryAction.getQuery().getStringValue() ); } } else if ( actionDefinition instanceof XQueryConnectionAction ) { XQueryConnectionAction xQueryConnectionAction = (XQueryConnectionAction) getActionDefinition(); connection = getConnection(); if ( connection == null ) { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName() ) ); //$NON-NLS-1$ } else if ( connection.getDatasourceType() != IPentahoConnection.XML_DATASOURCE ) { error( Messages.getInstance().getErrorString( "IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName() ) ); //$NON-NLS-1$ } else { xQueryConnectionAction.getOutputConnection().setValue( this ); result = true; } } return result; } protected boolean runQuery( final IPentahoConnection localConnection, String rawQuery ) { XQueryAction xQueryAction = (XQueryAction) getActionDefinition(); try { if ( localConnection == null ) { return false; } if ( ComponentBase.debug ) { debug( Messages.getInstance().getString( "XQueryBaseComponent.DEBUG_RUNNING_QUERY", rawQuery ) ); //$NON-NLS-1$ } String documentPath = null; int resourceType = -1; String srcXml = xQueryAction.getSourceXml().getStringValue(); org.pentaho.actionsequence.dom.IActionResource xmlResource = xQueryAction.getXmlDocument(); InputStream inputStream = null; URL url = null; if ( srcXml != null ) { inputStream = new FileInputStream( new File( createTempXMLFile( srcXml ) ) ); } else if ( xmlResource != null ) { // we have a local document to use as the data source IActionSequenceResource resource = getResource( xmlResource.getName() ); resourceType = resource.getSourceType(); if ( ( resourceType == IActionSequenceResource.SOLUTION_FILE_RESOURCE ) || ( resourceType == IActionSequenceResource.FILE_RESOURCE ) ) { inputStream = resource.getInputStream( RepositoryFilePermission.READ ); } else if ( resourceType == IActionSequenceResource.XML ) { inputStream = new FileInputStream( new File( createTempXMLFile( resource.getAddress() ) ) ); } else { url = new URL( documentPath ); } } // Retrieve the column types String[] columnTypes = null; if ( retrieveColumnTypes() ) { try { SAXReader reader = XMLParserFactoryProducer.getSAXReader( null ); Document document; if ( url != null ) { document = reader.read( url ); } else { document = reader.read( inputStream ); } Node commentNode = document.selectSingleNode( "/result-set/comment()" ); //$NON-NLS-1$ if ( commentNode != null ) { String commentString = commentNode.getText(); StringTokenizer st = new StringTokenizer( commentString, "," ); //$NON-NLS-1$ List columnTypesList = new LinkedList(); while ( st.hasMoreTokens() ) { String token = st.nextToken().trim(); columnTypesList.add( token ); } columnTypes = (String[]) columnTypesList.toArray( new String[ 0 ] ); } } catch ( Exception e ) { getLogger().warn( Messages.getInstance().getString( "XQueryBaseComponent.ERROR_0009_ERROR_BUILDING_COLUMN_TYPES" ), e ); //$NON-NLS-1$ } } if ( rawQuery != null ) { if ( rawQuery.indexOf( "{" + XQueryBaseComponent.XML_DOCUMENT_TAG + "}" ) >= 0 ) { //$NON-NLS-1$//$NON-NLS-2$ rawQuery = TemplateUtil.applyTemplate( rawQuery, XQueryBaseComponent.XML_DOCUMENT_TAG, documentPath ); } else { Calendar now = Calendar.getInstance(); File temp = File.createTempFile( "tempXQuery" + now.getTimeInMillis(), ".xml" ); temp.deleteOnExit(); OutputStream out = new FileOutputStream( temp ); IActionSequenceResource resource = getResource( xmlResource.getName() ); inputStream = resource.getInputStream( RepositoryFilePermission.READ ); byte[] buf = new byte[ 1024 ]; int len; while ( ( len = inputStream.read( buf ) ) > 0 ) { out.write( buf, 0, len ); } out.close(); inputStream.close(); documentPath = temp.getAbsolutePath(); documentPath = FilenameUtils.separatorsToUnix( documentPath ); rawQuery = "doc(\"" + documentPath + "\")" + rawQuery; //$NON-NLS-1$ //$NON-NLS-2$ } } if ( xQueryAction.getOutputPreparedStatement() != null ) { return prepareFinalQuery( rawQuery, columnTypes ); } else { return runFinalQuery( localConnection, rawQuery, columnTypes ); } } catch ( Exception e ) { getLogger().error( Messages.getInstance().getString( "XQueryBaseComponent.ERROR_0010_ERROR_RUNNING_QUERY" ), e ); //$NON-NLS-1$ return false; } } protected boolean prepareFinalQuery( final String rawQuery, final String[] columnTypes ) { if ( rawQuery != null ) { preparedQuery = applyInputsToFormat( rawQuery ); } preparedColumnTypes = columnTypes; ( (XQueryAction) getActionDefinition() ).getOutputPreparedStatement().setValue( this ); return true; } protected boolean runFinalQuery( final IPentahoConnection localConnection, final String rawQuery, final String[] columnTypes ) { XQueryAction xQueryAction = (XQueryAction) getActionDefinition(); boolean success = false; String finalQuery = applyInputsToFormat( rawQuery ); // execute the query, read the results and cache them try { IPentahoResultSet resultSet = ( (XQConnection) localConnection ).executeQuery( finalQuery, columnTypes ); if ( resultSet != null ) { if ( !xQueryAction.getLive().getBooleanValue( true ) ) { resultSet = resultSet.memoryCopy(); } try { IActionOutput resultSetOutput = xQueryAction.getOutputResultSet(); if ( resultSetOutput != null ) { resultSetOutput.setValue( resultSet ); } success = true; } finally { resultSet.close(); } } } catch ( XPathException e ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return success; } protected String createTempXMLFile( final String xmlString ) { // Save it to a temporary file File file; String documentPath = null; try { file = PentahoSystem.getApplicationContext().createTempFile( getSession(), XQueryBaseComponent.FILENAME_PREFIX, XQueryBaseComponent.EXTENSION, true ); documentPath = file.getCanonicalPath(); BufferedWriter out = new BufferedWriter( new FileWriter( file ) ); out.write( xmlString ); out.close(); } catch ( IOException e ) { getLogger().error( Messages.getInstance().getString( "XQueryBaseComponent.ERROR_0011_ERROR_CREATING_TEMP_FILE" ), e ); //$NON-NLS-1$ } documentPath = documentPath.replaceAll( "\\\\", "/" ); //$NON-NLS-1$ //$NON-NLS-2$ return documentPath; } protected IPentahoConnection getConnection() { IPentahoConnection conn = null; try { conn = PentahoConnectionFactory.getConnection( IPentahoConnection.XML_DATASOURCE, getSession(), this ); if ( conn == null ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0005_INVALID_CONNECTION" ) ); //$NON-NLS-1$ return null; } if ( this.getMaxRows() >= 0 ) { conn.setMaxRows( this.getMaxRows() ); } return conn; } catch ( Exception e ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return null; } @Override public boolean init() { return true; } /** * implements IPreparedComponents shareConnection, allowing other xquery components to access the connection * * @return shared connection */ public IPentahoConnection shareConnection() { return connection; } /** * implements the IPreparedComponent executePrepared, which allows other components to execute the prepared * statement. * * @param preparedParams lookup for prepared parameters * @return pentaho result set */ public IPentahoResultSet executePrepared( final Map preparedParams ) { if ( connection == null ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0012_NO_CONNECTION", getActionName() ) ); //$NON-NLS-1$ return null; } if ( !connection.initialized() ) { error( Messages.getInstance() .getErrorString( "XQueryBaseComponent.ERROR_0012_NO_CONNECTION", getActionName() ) ); //$NON-NLS-1$ return null; } if ( preparedQuery == null ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName() ) ); //$NON-NLS-1$ return null; } String finalQuery = TemplateUtil.applyTemplate( preparedQuery, getRuntimeContext(), new MapParameterResolver( preparedParams, IPreparedComponent.PREPARE_LATER_PREFIX, getRuntimeContext() ) ); // execute the query, read the results and cache them try { IPentahoResultSet resultSet = ( (XQConnection) connection ).executeQuery( finalQuery, preparedColumnTypes ); if ( resultSet != null ) { boolean live = getInputBooleanValue( StandardSettings.LIVE, true ); if ( !live ) { resultSet = resultSet.memoryCopy(); } try { return resultSet; } finally { resultSet.close(); } } } catch ( XPathException e ) { error( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName() ), e ); //$NON-NLS-1$ } return null; } /** * Determines if the action should attempt to retrieve the columns types */ protected boolean retrieveColumnTypes() { return true; } public int getMaxRows() { return this.maxRows; } public void setMaxRows( final int value ) { if ( rSet == null ) { this.maxRows = value; } else { throw new UnsupportedOperationException( Messages.getInstance().getErrorString( "XQueryBaseComponent.ERROR_0013_INVALID_ORDER_OF_OPERATION" ) ); //$NON-NLS-1$ } } /** * disposes of the connection this is called by the runtime context if the object is used as an iprepared component */ public void dispose() { if ( connectionOwner ) { if ( connection != null ) { connection.close(); } } } }