/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2016 by Pentaho : http://www.pentaho.com
*
*******************************************************************************
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/
package org.pentaho.di.www;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.owasp.encoder.Encode;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.exception.KettleStepException;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.core.row.ValueMetaInterface;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.trans.Trans;
import org.pentaho.di.trans.step.RowListener;
import org.pentaho.di.trans.step.StepInterface;
public class SniffStepServlet extends BaseHttpServlet implements CartePluginInterface {
private static Class<?> PKG = GetTransStatusServlet.class; // for i18n purposes, needed by Translator2!!
private static final long serialVersionUID = 3634806745372015720L;
public static final String CONTEXT_PATH = "/kettle/sniffStep";
public static final String TYPE_INPUT = "input";
public static final String TYPE_OUTPUT = "output";
public static final String XML_TAG = "step-sniff";
final class MetaAndData {
public RowMetaInterface bufferRowMeta;
public List<Object[]> bufferRowData;
}
public SniffStepServlet() {
}
public SniffStepServlet( TransformationMap transformationMap ) {
super( transformationMap );
}
/**
<div id="mindtouch">
<h1>/kettle/sniffStep</h1>
<a name="GET"></a>
<h2>GET</h2>
<p>Sniff metadata and data from the specified step of the specified transformation.</p>
<p><b>Example Request:</b><br />
<pre function="syntax.xml">
GET /kettle/sniffStep?trans=dummy-trans&step=tf&xml=Y&lines=10
</pre>
</p>
<h3>Parameters</h3>
<table class="pentaho-table">
<tbody>
<tr>
<th>name</th>
<th>description</th>
<th>type</th>
</tr>
<tr>
<td>trans</td>
<td>Name of the transformation containing required step.</td>
<td>query</td>
</tr>
<tr>
<td>stepName</td>
<td>Name of the transformation step to collect data for.</td>
<td>query</td>
</tr>
<tr>
<td>copynr</td>
<td>Copy number of the step to be used for collecting data. If not provided 0 is used.</td>
<td>integer, optional</td>
</tr>
<tr>
<td>type</td>
<td>Type of the data to be collected (<code>input</code> or <code>output</code>).
If not provided output data is collected.</td>
<td>query, optional</td>
</tr>
<tr>
<td>xml</td>
<td>Boolean flag which defines output format <code>Y</code> forces XML output to be generated.
HTML is returned otherwise.</td>
<td>boolean, optional</td>
</tr>
<tr>
<td>id</td>
<td>Carte id of the transformation to be used for step lookup.</td>
<td>query, optional</td>
</tr>
<tr>
<td>lines</td>
<td>Number of lines to collect and include into response. If not provided 0 lines will be collected.</td>
<td>integer, optional</td>
</tr>
</tbody>
</table>
<h3>Response Body</h3>
<table class="pentaho-table">
<tbody>
<tr>
<td align="right">element:</td>
<td>(custom)</td>
</tr>
<tr>
<td align="right">media types:</td>
<td>text/xml, text/html</td>
</tr>
</tbody>
</table>
<p>Response XML or HTML response containing data and metadata of the step.
If an error occurs during method invocation <code>result</code> field of the response
will contain <code>ERROR</code> status.</p>
<p><b>Example Response:</b></p>
<pre function="syntax.xml">
<?xml version="1.0" encoding="UTF-8"?>
<step-sniff>
<row-meta>
<value-meta><type>String</type>
<storagetype>normal</storagetype>
<name>Field1</name>
<length>0</length>
<precision>-1</precision>
<origin>tf</origin>
<comments/>
<conversion_Mask/>
<decimal_symbol>.</decimal_symbol>
<grouping_symbol>,</grouping_symbol>
<currency_symbol>$</currency_symbol>
<trim_type>none</trim_type>
<case_insensitive>N</case_insensitive>
<sort_descending>N</sort_descending>
<output_padding>N</output_padding>
<date_format_lenient>Y</date_format_lenient>
<date_format_locale>en_US</date_format_locale>
<date_format_timezone>America/Bahia</date_format_timezone>
<lenient_string_to_number>N</lenient_string_to_number>
</value-meta>
</row-meta>
<nr_rows>10</nr_rows>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data </value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
<row-data><value-data>my-data</value-data>
</row-data>
</step-sniff>
</pre>
<h3>Status Codes</h3>
<table class="pentaho-table">
<tbody>
<tr>
<th>code</th>
<th>description</th>
</tr>
<tr>
<td>200</td>
<td>Request was processed.</td>
</tr>
<tr>
<td>500</td>
<td>Internal server error occurs during request processing.</td>
</tr>
</tbody>
</table>
</div>
*/
public void doGet( HttpServletRequest request, HttpServletResponse response ) throws ServletException,
IOException {
if ( isJettyMode() && !request.getContextPath().startsWith( CONTEXT_PATH ) ) {
return;
}
if ( log.isDebug() ) {
logDebug( BaseMessages.getString( PKG, "TransStatusServlet.Log.SniffStepRequested" ) );
}
String transName = request.getParameter( "trans" );
String id = request.getParameter( "id" );
String stepName = request.getParameter( "step" );
int copyNr = Const.toInt( request.getParameter( "copynr" ), 0 );
final int nrLines = Const.toInt( request.getParameter( "lines" ), 0 );
String type = Const.NVL( request.getParameter( "type" ), TYPE_OUTPUT );
boolean useXML = "Y".equalsIgnoreCase( request.getParameter( "xml" ) );
response.setStatus( HttpServletResponse.SC_OK );
if ( useXML ) {
response.setContentType( "text/xml" );
response.setCharacterEncoding( Const.XML_ENCODING );
} else {
response.setContentType( "text/html;charset=UTF-8" );
}
PrintWriter out = response.getWriter();
// ID is optional...
//
Trans trans;
CarteObjectEntry entry;
if ( Utils.isEmpty( id ) ) {
// get the first transformation that matches...
//
entry = getTransformationMap().getFirstCarteObjectEntry( transName );
if ( entry == null ) {
trans = null;
} else {
id = entry.getId();
trans = getTransformationMap().getTransformation( entry );
}
} else {
// Take the ID into account!
//
entry = new CarteObjectEntry( transName, id );
trans = getTransformationMap().getTransformation( entry );
}
if ( trans != null ) {
// Find the step to look at...
//
StepInterface step = null;
List<StepInterface> stepInterfaces = trans.findBaseSteps( stepName );
for ( int i = 0; i < stepInterfaces.size(); i++ ) {
StepInterface look = stepInterfaces.get( i );
if ( look.getCopy() == copyNr ) {
step = look;
}
}
if ( step != null ) {
// Add a listener to the transformation step...
//
final boolean read = type.equalsIgnoreCase( TYPE_INPUT );
final boolean written = type.equalsIgnoreCase( TYPE_OUTPUT ) || !read;
final MetaAndData metaData = new MetaAndData();
metaData.bufferRowMeta = null;
metaData.bufferRowData = new ArrayList<Object[]>();
RowListener rowListener = new RowListener() {
public void rowReadEvent( RowMetaInterface rowMeta, Object[] row ) throws KettleStepException {
if ( read && metaData.bufferRowData.size() < nrLines ) {
metaData.bufferRowMeta = rowMeta;
metaData.bufferRowData.add( row );
}
}
public void rowWrittenEvent( RowMetaInterface rowMeta, Object[] row ) throws KettleStepException {
if ( written && metaData.bufferRowData.size() < nrLines ) {
metaData.bufferRowMeta = rowMeta;
metaData.bufferRowData.add( row );
}
}
public void errorRowWrittenEvent( RowMetaInterface rowMeta, Object[] row ) throws KettleStepException {
}
};
step.addRowListener( rowListener );
// Wait until we have enough rows...
//
while ( metaData.bufferRowData.size() < nrLines
&& step.isRunning() && !trans.isFinished() && !trans.isStopped() ) {
try {
Thread.sleep( 100 );
} catch ( InterruptedException e ) {
// Ignore
//
break;
}
}
// Remove the row listener
//
step.removeRowListener( rowListener );
// Pass along the rows of data...
//
if ( useXML ) {
// Send the result back as XML
//
response.setContentType( "text/xml" );
response.setCharacterEncoding( Const.XML_ENCODING );
out.print( XMLHandler.getXMLHeader( Const.XML_ENCODING ) );
out.println( XMLHandler.openTag( XML_TAG ) );
if ( metaData.bufferRowMeta != null ) {
// Row Meta data
//
out.println( metaData.bufferRowMeta.getMetaXML() );
// Nr of lines
//
out.println( XMLHandler.addTagValue( "nr_rows", metaData.bufferRowData.size() ) );
// Rows of data
//
for ( int i = 0; i < metaData.bufferRowData.size(); i++ ) {
Object[] rowData = metaData.bufferRowData.get( i );
out.println( metaData.bufferRowMeta.getDataXML( rowData ) );
}
}
out.println( XMLHandler.closeTag( XML_TAG ) );
} else {
response.setContentType( "text/html;charset=UTF-8" );
out.println( "<HTML>" );
out.println( "<HEAD>" );
out.println( "<TITLE>" + BaseMessages.getString( PKG, "SniffStepServlet.SniffResults" ) + "</TITLE>" );
out.println( "<META http-equiv=\"Refresh\" content=\"10;url="
+ convertContextPath( CONTEXT_PATH ) + "?name=" + URLEncoder.encode( transName, "UTF-8" ) + "&id="
+ URLEncoder.encode( id, "UTF-8" ) + "\">" );
out.println( "<META http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">" );
out.println( "</HEAD>" );
out.println( "<BODY>" );
out.println( "<H1>"
+ Encode.forHtml( BaseMessages.getString(
PKG, "SniffStepServlet.SniffResultsForStep", stepName ) ) + "</H1>" );
try {
out.println( "<table border=\"1\">" );
if ( metaData.bufferRowMeta != null ) {
// Print a header row containing all the field names...
//
out.print( "<tr><th>#</th>" );
for ( ValueMetaInterface valueMeta : metaData.bufferRowMeta.getValueMetaList() ) {
out.print( "<th>" + valueMeta.getName() + "</th>" );
}
out.println( "</tr>" );
// Now output the data rows...
//
for ( int r = 0; r < metaData.bufferRowData.size(); r++ ) {
Object[] rowData = metaData.bufferRowData.get( r );
out.print( "<tr>" );
out.println( "<td>" + ( r + 1 ) + "</td>" );
for ( int v = 0; v < metaData.bufferRowMeta.size(); v++ ) {
ValueMetaInterface valueMeta = metaData.bufferRowMeta.getValueMeta( v );
Object valueData = rowData[v];
out.println( "<td>" + valueMeta.getString( valueData ) + "</td>" );
}
out.println( "</tr>" );
}
}
out.println( "</table>" );
out.println( "<p>" );
} catch ( Exception ex ) {
out.println( "<p>" );
out.println( "<pre>" );
out.println( Encode.forHtml( Const.getStackTracker( ex ) ) );
out.println( "</pre>" );
}
out.println( "<p>" );
out.println( "</BODY>" );
out.println( "</HTML>" );
}
} else {
if ( useXML ) {
out.println( new WebResult( WebResult.STRING_ERROR, BaseMessages.getString(
PKG, "SniffStepServlet.Log.CoundNotFindSpecStep", stepName ) ).getXML() );
} else {
out.println( "<H1>"
+ Encode.forHtml( BaseMessages.getString(
PKG, "SniffStepServlet.Log.CoundNotFindSpecStep", stepName ) ) + "</H1>" );
out.println( "<a href=\""
+ convertContextPath( GetStatusServlet.CONTEXT_PATH ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.BackToStatusPage" ) + "</a><p>" );
}
}
} else {
if ( useXML ) {
out.println( new WebResult( WebResult.STRING_ERROR, BaseMessages.getString(
PKG, "SniffStepServlet.Log.CoundNotFindSpecTrans", transName ) ).getXML() );
} else {
out.println( "<H1>"
+ Encode.forHtml( BaseMessages.getString(
PKG, "SniffStepServlet.Log.CoundNotFindTrans", transName ) ) + "</H1>" );
out.println( "<a href=\""
+ convertContextPath( GetStatusServlet.CONTEXT_PATH ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.BackToStatusPage" ) + "</a><p>" );
}
}
}
public String toString() {
return "Trans Status Handler";
}
public String getService() {
return CONTEXT_PATH + " (" + toString() + ")";
}
public String getContextPath() {
return CONTEXT_PATH;
}
}