001: /*
002: * Copyright 2006 Pentaho Corporation. All rights reserved.
003: * This software was developed by Pentaho Corporation and is provided under the terms
004: * of the Mozilla Public License, Version 1.1, or any later version. You may not use
005: * this file except in compliance with the license. If you need a copy of the license,
006: * please go to http://www.mozilla.org/MPL/MPL-1.1.txt. The Original Code is the Pentaho
007: * BI Platform. The Initial Developer is Pentaho Corporation.
008: *
009: * Software distributed under the Mozilla Public License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. Please refer to
011: * the license for the specific language governing your rights and limitations.
012: *
013: * Created Sep 21, 2005
014: * @author wseyler
015: */
016: package org.pentaho.plugin.xquery;
017:
018: import java.io.BufferedWriter;
019: import java.io.File;
020: import java.io.FileWriter;
021: import java.io.IOException;
022: import java.net.URL;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.StringTokenizer;
027:
028: import net.sf.saxon.trans.XPathException;
029:
030: import org.apache.commons.logging.Log;
031: import org.dom4j.Document;
032: import org.dom4j.Node;
033: import org.dom4j.io.SAXReader;
034: import org.pentaho.actionsequence.dom.ActionOutput;
035: import org.pentaho.actionsequence.dom.ActionResource;
036: import org.pentaho.actionsequence.dom.IActionInputValueProvider;
037: import org.pentaho.actionsequence.dom.actions.ActionDefinition;
038: import org.pentaho.actionsequence.dom.actions.XQueryAction;
039: import org.pentaho.actionsequence.dom.actions.XQueryConnectionAction;
040: import org.pentaho.core.component.IPreparedComponent;
041: import org.pentaho.commons.connection.IPentahoConnection;
042: import org.pentaho.commons.connection.IPentahoResultSet;
043: import org.pentaho.core.solution.IActionResource;
044: import org.pentaho.core.system.PentahoSystem;
045: import org.pentaho.core.util.MapParameterResolver;
046: import org.pentaho.core.util.TemplateUtil;
047: import org.pentaho.data.PentahoConnectionFactory;
048: import org.pentaho.data.connection.xquery.XQConnection;
049: import org.pentaho.messages.Messages;
050: import org.pentaho.plugin.ComponentBase;
051: import org.pentaho.plugin.core.StandardSettings;
052:
053: /**
054: * XQueryBaseComponent provides a mechanism to run xqueries within the Pentaho BI Platform.
055: *
056: *
057: * TODO: In regards to IPreparedComponent, implement a method for choosing the datasource on the fly
058: */
059: public abstract class XQueryBaseComponent extends ComponentBase
060: implements IPreparedComponent {
061:
062: private IPentahoResultSet rSet;
063:
064: /** reference to connection object */
065: protected IPentahoConnection connection;
066:
067: /** keeps track of ownership of connection */
068: protected boolean connectionOwner = true;
069:
070: private static final String FILENAME_PREFIX = "tmp"; //$NON-NLS-1$
071:
072: private static final String EXTENSION = ".xml"; //$NON-NLS-1$
073:
074: private static final String DOCUMENT = "document"; //$NON-NLS-1$
075:
076: private static final String TEMP_DIRECTORY = "system/tmp/"; //$NON-NLS-1$
077:
078: private static final String XML_DOCUMENT_TAG = "XML_DOCUMENT"; //$NON-NLS-1$
079:
080: public abstract boolean validateSystemSettings();
081:
082: public abstract Log getLogger();
083:
084: /** string to hold prepared query until execution */
085: String preparedQuery = null;
086:
087: /** string array to hold prepared column types until execution */
088: String preparedColumnTypes[] = null;
089:
090: public IPentahoResultSet getResultSet() {
091: return rSet;
092: }
093:
094: protected boolean validateAction() {
095: boolean result = false;
096: ActionDefinition actionDefinition = getActionDefinition();
097:
098: if (actionDefinition instanceof XQueryAction) {
099: XQueryAction xQueryAction = (XQueryAction) actionDefinition;
100: if ((xQueryAction.getSourceXml() == IActionInputValueProvider.NULL_INPUT)
101: && (xQueryAction.getXmlDocument() == null)) {
102: error(Messages
103: .getString(
104: "XQueryBaseComponent.ERROR_0008_SOURCE_NOT_DEFINED", getActionName())); //$NON-NLS-1$
105: } else if (xQueryAction.getQuery() == IActionInputValueProvider.NULL_INPUT) {
106: error(Messages
107: .getErrorString(
108: "XQueryBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName())); //$NON-NLS-1$
109: } else if ((xQueryAction.getOutputPreparedStatement() == null)
110: && (xQueryAction.getOutputResultSet() == null)) {
111: error(Messages
112: .getErrorString(
113: "XQueryBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName())); //$NON-NLS-1$
114: } else {
115: result = true;
116: }
117: } else if (actionDefinition instanceof XQueryConnectionAction) {
118: XQueryConnectionAction xQueryConnectionAction = (XQueryConnectionAction) actionDefinition;
119: if (xQueryConnectionAction.getOutputConnection() == null) {
120: error(Messages
121: .getErrorString(
122: "XQueryBaseComponent.ERROR_0003_OUTPUT_NOT_SPECIFIED", getActionName())); //$NON-NLS-1$
123: } else {
124: result = true;
125: }
126: } else {
127: error(Messages
128: .getErrorString(
129: "ComponentBase.ERROR_0001_UNKNOWN_ACTION_TYPE", actionDefinition.getElement().asXML())); //$NON-NLS-1$
130: }
131: return result;
132: }
133:
134: public void done() {
135: // TODO Auto-generated method stub
136: }
137:
138: protected boolean executeAction() {
139: boolean result = false;
140: ActionDefinition actionDefinition = getActionDefinition();
141: if (actionDefinition instanceof XQueryAction) {
142: XQueryAction xQueryAction = (XQueryAction) actionDefinition;
143: IPreparedComponent sharedConnection = (IPreparedComponent) xQueryAction
144: .getSharedConnection().getValue();
145: if (sharedConnection != null) {
146: connectionOwner = false;
147: connection = sharedConnection.shareConnection();
148: } else {
149: connection = getConnection();
150: }
151: if (connection == null) {
152: error(Messages
153: .getErrorString(
154: "IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName())); //$NON-NLS-1$
155: } else if (connection.getDatasourceType() != IPentahoConnection.XML_DATASOURCE) {
156: error(Messages
157: .getErrorString(
158: "IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName())); //$NON-NLS-1$
159: } else {
160: result = runQuery(connection, xQueryAction.getQuery()
161: .getStringValue());
162: }
163: } else if (actionDefinition instanceof XQueryConnectionAction) {
164: XQueryConnectionAction xQueryConnectionAction = (XQueryConnectionAction) getActionDefinition();
165: connection = getConnection();
166: if (connection == null) {
167: error(Messages
168: .getErrorString(
169: "IPreparedComponent.ERROR_0002_CONNECTION_NOT_AVAILABLE", getActionName())); //$NON-NLS-1$
170: } else if (connection.getDatasourceType() != IPentahoConnection.XML_DATASOURCE) {
171: error(Messages
172: .getErrorString(
173: "IPreparedComponent.ERROR_0001_INVALID_CONNECTION_TYPE", getActionName())); //$NON-NLS-1$
174: } else {
175: xQueryConnectionAction.getOutputConnection().setValue(
176: this );
177: result = true;
178: }
179: }
180: return result;
181: }
182:
183: protected boolean runQuery(IPentahoConnection localConnection,
184: String rawQuery) {
185: XQueryAction xQueryAction = (XQueryAction) getActionDefinition();
186: try {
187: if (localConnection == null) {
188: return false;
189: }
190: if (debug) {
191: debug(Messages
192: .getString(
193: "XQueryBaseComponent.DEBUG_RUNNING_QUERY", rawQuery)); //$NON-NLS-1$
194: }
195: String documentPath = null;
196: int resourceType = -1;
197: String srcXml = xQueryAction.getSourceXml()
198: .getStringValue();
199: ActionResource xmlResource = xQueryAction.getXmlDocument();
200: if (srcXml != null) {
201: documentPath = createTempXMLFile(srcXml);
202: resourceType = IActionResource.FILE_RESOURCE;
203: } else if (xmlResource != null) {
204: // we have a local document to use as the data source
205: IActionResource resource = getResource(xmlResource
206: .getName());
207: resourceType = resource.getSourceType();
208: if (resourceType == IActionResource.SOLUTION_FILE_RESOURCE) {
209: documentPath = PentahoSystem
210: .getApplicationContext().getSolutionPath(
211: resource.getAddress());
212: } else if (resourceType == IActionResource.XML) {
213: documentPath = createTempXMLFile(resource
214: .getAddress());
215: } else {
216: documentPath = resource.getAddress();
217: }
218: }
219:
220: File documentFile = null;
221: if (resourceType != IActionResource.URL_RESOURCE) {
222: // check that the document exists
223: documentFile = new File(documentPath);
224: if (!documentFile.exists()) {
225: error(Messages
226: .getString(
227: "XQueryBaseComponent.ERROR_0007_FILE_NOT_FOUND", documentPath)); //$NON-NLS-1$
228: return false;
229: }
230: // convert any '\' to '/'
231: documentPath = documentFile.getCanonicalPath();
232: documentPath = documentPath.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
233: }
234:
235: // Retrieve the column types
236: String columnTypes[] = null;
237: if (retrieveColumnTypes()) {
238: try {
239: SAXReader reader = new SAXReader();
240: Document document;
241: if (resourceType == IActionResource.URL_RESOURCE) {
242: document = reader.read(new URL(documentPath));
243: } else {
244: document = reader.read(documentFile);
245: }
246: Node commentNode = document
247: .selectSingleNode("/result-set/comment()"); //$NON-NLS-1$
248: if (commentNode != null) {
249: String commentString = commentNode.getText();
250: StringTokenizer st = new StringTokenizer(
251: commentString, ","); //$NON-NLS-1$
252: List columnTypesList = new LinkedList();
253: while (st.hasMoreTokens()) {
254: String token = st.nextToken().trim();
255: columnTypesList.add(token);
256: }
257: columnTypes = (String[]) columnTypesList
258: .toArray(new String[0]);
259: }
260: } catch (Exception e) {
261: getLogger()
262: .warn(
263: Messages
264: .getString("XQueryBaseComponent.ERROR_0009_ERROR_BUILDING_COLUMN_TYPES"), e); //$NON-NLS-1$
265: }
266: }
267:
268: if (rawQuery != null) {
269: if (rawQuery.indexOf("{" + XML_DOCUMENT_TAG + "}") >= 0) { //$NON-NLS-1$//$NON-NLS-2$
270: rawQuery = TemplateUtil.applyTemplate(rawQuery,
271: XML_DOCUMENT_TAG, documentPath);
272: } else {
273: rawQuery = "doc(\"" + documentPath + "\")" + rawQuery; //$NON-NLS-1$ //$NON-NLS-2$
274: }
275: }
276:
277: if (xQueryAction.getOutputPreparedStatement() != null) {
278: return prepareFinalQuery(rawQuery, columnTypes);
279: } else {
280: return runFinalQuery(localConnection, rawQuery,
281: columnTypes);
282: }
283: } catch (Exception e) {
284: getLogger()
285: .error(
286: Messages
287: .getString("XQueryBaseComponent.ERROR_0010_ERROR_RUNNING_QUERY"), e); //$NON-NLS-1$
288: return false;
289: }
290: }
291:
292: protected boolean prepareFinalQuery(String rawQuery,
293: String[] columnTypes) {
294: if (rawQuery != null) {
295: preparedQuery = applyInputsToFormat(rawQuery);
296: }
297: preparedColumnTypes = columnTypes;
298: ((XQueryAction) getActionDefinition())
299: .getOutputPreparedStatement().setValue(this );
300: return true;
301: }
302:
303: protected boolean runFinalQuery(IPentahoConnection localConnection,
304: String rawQuery, String[] columnTypes) {
305: XQueryAction xQueryAction = (XQueryAction) getActionDefinition();
306: boolean success = false;
307: String finalQuery = applyInputsToFormat(rawQuery);
308: // execute the query, read the results and cache them
309: try {
310: IPentahoResultSet resultSet = ((XQConnection) localConnection)
311: .executeQuery(finalQuery, columnTypes);
312: if (resultSet != null) {
313: if (!xQueryAction.getLive().getBooleanValue(true)) {
314: resultSet = resultSet.memoryCopy();
315: }
316: try {
317: ActionOutput resultSetOutput = xQueryAction
318: .getOutputResultSet();
319: if (resultSetOutput != null) {
320: resultSetOutput.setValue(resultSet);
321: }
322: success = true;
323: } finally {
324: resultSet.close();
325: }
326: }
327: } catch (XPathException e) {
328: error(
329: Messages
330: .getErrorString(
331: "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
332: }
333: return success;
334: }
335:
336: protected String createTempXMLFile(String xmlString) {
337: // Save it to a temporary file
338: File file;
339: String documentPath = null;
340: try {
341: file = File.createTempFile(FILENAME_PREFIX, EXTENSION,
342: new File(PentahoSystem.getApplicationContext()
343: .getFileOutputPath(TEMP_DIRECTORY)));
344: file.deleteOnExit();
345: documentPath = file.getCanonicalPath();
346:
347: BufferedWriter out = new BufferedWriter(
348: new FileWriter(file));
349: out.write(xmlString);
350: out.close();
351: } catch (IOException e) {
352: getLogger()
353: .error(
354: Messages
355: .getString("XQueryBaseComponent.ERROR_0011_ERROR_CREATING_TEMP_FILE"), e); //$NON-NLS-1$
356: }
357:
358: documentPath = documentPath.replaceAll("\\\\", "/"); //$NON-NLS-1$ //$NON-NLS-2$
359: return documentPath;
360: }
361:
362: protected IPentahoConnection getConnection() {
363: IPentahoConnection conn = null;
364: try {
365: conn = PentahoConnectionFactory.getConnection(
366: IPentahoConnection.XML_DATASOURCE, this );
367: if (conn == null) {
368: error(Messages
369: .getErrorString("XQueryBaseComponent.ERROR_0005_INVALID_CONNECTION")); //$NON-NLS-1$
370: return null;
371: }
372: return conn;
373: } catch (Exception e) {
374: error(
375: Messages
376: .getErrorString(
377: "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
378: }
379: return null;
380: }
381:
382: public boolean init() {
383: return true;
384: }
385:
386: /**
387: * implements IPreparedComponents shareConnection, allowing
388: * other xquery components to access the connection
389: *
390: * @return shared connection
391: */
392: public IPentahoConnection shareConnection() {
393: return connection;
394: }
395:
396: /**
397: * implements the IPreparedComponent executePrepared, which
398: * allows other components to execute the prepared statement.
399: *
400: * @param preparedParams lookup for prepared parameters
401: * @return pentaho result set
402: */
403: public IPentahoResultSet executePrepared(Map preparedParams) {
404:
405: if (connection == null) {
406: error(Messages
407: .getErrorString(
408: "XQueryBaseComponent.ERROR_0007_NO_CONNECTION", getActionName())); //$NON-NLS-1$
409: return null;
410: }
411: if (!connection.initialized()) {
412: error(Messages
413: .getErrorString(
414: "XQueryBaseComponent.ERROR_0007_NO_CONNECTION", getActionName())); //$NON-NLS-1$
415: return null;
416: }
417:
418: if (preparedQuery == null) {
419: error(Messages
420: .getErrorString(
421: "XQueryBaseComponent.ERROR_0001_QUERY_NOT_SPECIFIED", getActionName())); //$NON-NLS-1$
422: return null;
423: }
424:
425: String finalQuery = TemplateUtil.applyTemplate(preparedQuery,
426: getRuntimeContext(), new MapParameterResolver(
427: preparedParams, PREPARE_LATER_PREFIX,
428: getRuntimeContext()));
429:
430: // execute the query, read the results and cache them
431: try {
432: IPentahoResultSet resultSet = ((XQConnection) connection)
433: .executeQuery(finalQuery, preparedColumnTypes);
434: if (resultSet != null) {
435: boolean live = getInputBooleanValue(
436: StandardSettings.LIVE, true);
437: if (!live) {
438: resultSet = resultSet.memoryCopy();
439: }
440: try {
441: return resultSet;
442: } finally {
443: resultSet.close();
444: }
445: }
446: } catch (XPathException e) {
447: error(
448: Messages
449: .getErrorString(
450: "XQueryBaseComponent.ERROR_0006_EXECUTE_FAILED", getActionName()), e); //$NON-NLS-1$
451: }
452: return null;
453: }
454:
455: /**
456: * Determines if the action should attempt to retrieve the columns types
457: */
458: protected boolean retrieveColumnTypes() {
459: return true;
460: }
461:
462: /**
463: * disposes of the connection
464: * this is called by the runtime context
465: * if the object is used as an iprepared component
466: */
467: public void dispose() {
468: if (connectionOwner) {
469: if (connection != null) {
470: connection.close();
471: }
472: }
473: }
474: }
|