/* * 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 21, 2005 * @author wseyler */ package org.pentaho.platform.plugin.action.xml.xquery; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.net.URL; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import net.sf.saxon.trans.XPathException; 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.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; /** * XQueryBaseComponent provides a mechanism to run xqueries within the Pentaho BI Platform. * * * 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(); if (srcXml != null) { documentPath = createTempXMLFile(srcXml); resourceType = IActionSequenceResource.FILE_RESOURCE; } 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) { documentPath = PentahoSystem.getApplicationContext().getSolutionPath(resource.getAddress()); } else if (resourceType == IActionSequenceResource.XML) { documentPath = createTempXMLFile(resource.getAddress()); } else { documentPath = resource.getAddress(); } } File documentFile = null; if (resourceType != IActionSequenceResource.URL_RESOURCE) { // check that the document exists documentFile = new File(documentPath); if (!documentFile.exists()) { error(Messages.getInstance().getString("XQueryBaseComponent.ERROR_0007_FILE_NOT_FOUND", documentPath)); //$NON-NLS-1$ return false; } // convert any '\' to '/' documentPath = documentFile.getCanonicalPath(); documentPath = documentPath.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$ } // Retrieve the column types String columnTypes[] = null; if (retrieveColumnTypes()) { try { SAXReader reader = new SAXReader(); Document document; if (resourceType == IActionSequenceResource.URL_RESOURCE) { document = reader.read(new URL(documentPath)); } else { document = reader.read(documentFile); } 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 { 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(); } } } }