/*! ******************************************************************************
*
* 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 javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.owasp.encoder.Encode;
import org.pentaho.di.cluster.HttpUtil;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.gui.Point;
import org.pentaho.di.core.logging.KettleLogStore;
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.BaseStepData.StepExecutionStatus;
import org.pentaho.di.trans.step.StepInterface;
import org.pentaho.di.trans.step.StepStatus;
public class GetTransStatusServlet 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/transStatus";
public GetTransStatusServlet() {
}
public GetTransStatusServlet( TransformationMap transformationMap ) {
super( transformationMap );
}
/**
<div id="mindtouch">
<h1>/kettle/transStatus</h1>
<a name="GET"></a>
<h2>GET</h2>
<p>Retrieves status of the specified transformation. Status is returned as HTML or XML output
depending on the input parameters. Status contains information about last execution of the transformation.</p>
<p><b>Example Request:</b><br />
<pre function="syntax.xml">
GET /kettle/transStatus/?name=dummy-trans&xml=Y
</pre>
</p>
<h3>Parameters</h3>
<table class="pentaho-table">
<tbody>
<tr>
<th>name</th>
<th>description</th>
<th>type</th>
</tr>
<tr>
<td>name</td>
<td>Name of the transformation to be used for status generation.</td>
<td>query</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 status generation.</td>
<td>query, optional</td>
</tr>
<tr>
<td>from</td>
<td>Start line number of the execution log to be included into response.</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 details about the transformation specified.
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"?>
<transstatus>
<transname>dummy-trans</transname>
<id>c56961b2-c848-49b8-abde-76c8015e29b0</id>
<status_desc>Stopped</status_desc>
<error_desc/>
<paused>N</paused>
<stepstatuslist>
<stepstatus><stepname>Dummy (do nothing)</stepname>
<copy>0</copy><linesRead>0</linesRead>
<linesWritten>0</linesWritten><linesInput>0</linesInput>
<linesOutput>0</linesOutput><linesUpdated>0</linesUpdated>
<linesRejected>0</linesRejected><errors>0</errors>
<statusDescription>Stopped</statusDescription><seconds>0.0</seconds>
<speed>-</speed><priority>-</priority><stopped>Y</stopped>
<paused>N</paused>
</stepstatus>
</stepstatuslist>
<first_log_line_nr>0</first_log_line_nr>
<last_log_line_nr>37</last_log_line_nr>
<result>
<lines_input>0</lines_input>
<lines_output>0</lines_output>
<lines_read>0</lines_read>
<lines_written>0</lines_written>
<lines_updated>0</lines_updated>
<lines_rejected>0</lines_rejected>
<lines_deleted>0</lines_deleted>
<nr_errors>0</nr_errors>
<nr_files_retrieved>0</nr_files_retrieved>
<entry_nr>0</entry_nr>
<result>Y</result>
<exit_status>0</exit_status>
<is_stopped>Y</is_stopped>
<log_channel_id>10e2c832-07da-409a-a5ba-4b90a234e957</log_channel_id>
<log_text/>
<result-file></result-file>
<result-rows></result-rows>
</result>
<logging_string><![CDATA[H4sIAAAAAAAAADMyMDTRNzTUNzJRMDSyMrC0MjFV0FVIKc3NrdQtKUrMKwbyXDKLCxJLkjMy89IViksSi0pSUxTS8osUwPJARm5iSWZ+nkI0kq5YXi4AQVH5bFoAAAA=]]></logging_string>
</transstatus>
</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.TransStatusRequested" ) );
}
String transName = request.getParameter( "name" );
String id = request.getParameter( "id" );
boolean useXML = "Y".equalsIgnoreCase( request.getParameter( "xml" ) );
int startLineNr = Const.toInt( request.getParameter( "from" ), 0 );
response.setStatus( HttpServletResponse.SC_OK );
if ( useXML ) {
response.setContentType( "text/xml" );
response.setCharacterEncoding( Const.XML_ENCODING );
} else {
response.setCharacterEncoding( "UTF-8" );
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 ) {
String status = trans.getStatus();
int lastLineNr = KettleLogStore.getLastBufferLineNr();
String logText =
KettleLogStore.getAppender().getBuffer(
trans.getLogChannel().getLogChannelId(), false, startLineNr, lastLineNr ).toString();
if ( useXML ) {
response.setContentType( "text/xml" );
response.setCharacterEncoding( Const.XML_ENCODING );
out.print( XMLHandler.getXMLHeader( Const.XML_ENCODING ) );
SlaveServerTransStatus transStatus = new SlaveServerTransStatus( transName, entry.getId(), status );
transStatus.setFirstLoggingLineNr( startLineNr );
transStatus.setLastLoggingLineNr( lastLineNr );
transStatus.setLogDate( trans.getLogDate() );
for ( int i = 0; i < trans.nrSteps(); i++ ) {
StepInterface baseStep = trans.getRunThread( i );
if ( ( baseStep.isRunning() ) || baseStep.getStatus() != StepExecutionStatus.STATUS_EMPTY ) {
StepStatus stepStatus = new StepStatus( baseStep );
transStatus.getStepStatusList().add( stepStatus );
}
}
// The log can be quite large at times, we are going to put a base64 encoding around a compressed stream
// of bytes to handle this one.
String loggingString = HttpUtil.encodeBase64ZippedString( logText );
transStatus.setLoggingString( loggingString );
// Also set the result object...
//
transStatus.setResult( trans.getResult() );
// Is the transformation paused?
//
transStatus.setPaused( trans.isPaused() );
// Send the result back as XML
//
try {
out.println( transStatus.getXML() );
} catch ( KettleException e ) {
throw new ServletException( "Unable to get the transformation status in XML format", e );
}
} else {
response.setContentType( "text/html;charset=UTF-8" );
out.println( "<HTML>" );
out.println( "<HEAD>" );
out.println( "<TITLE>"
+ BaseMessages.getString( PKG, "TransStatusServlet.KettleTransStatus" ) + "</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, "TransStatusServlet.TopTransStatus", transName ) )
+ "</H1>" );
try {
out.println( "<table border=\"1\">" );
out.print( "<tr> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.TransName" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.CarteObjectId" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.TransStatus" ) + "</th> </tr>" );
out.print( "<tr>" );
out.print( "<td>" + Encode.forHtml( transName ) + "</td>" );
out.print( "<td>" + Encode.forHtml( id ) + "</td>" );
out.print( "<td>" + Encode.forHtml( status ) + "</td>" );
out.print( "</tr>" );
out.print( "</table>" );
out.print( "<p>" );
// Get the transformation image
//
// out.print("<a href=\"" + convertContextPath(GetTransImageServlet.CONTEXT_PATH) + "?name=" +
// URLEncoder.encode(transName, "UTF-8") + "&id="+id+"\">"
// + BaseMessages.getString(PKG, "TransStatusServlet.GetTransImage") + "</a>");
Point max = trans.getTransMeta().getMaximum();
max.x += 20;
max.y += 20;
out.print( "<iframe height=\""
+ max.y + "\" width=\"" + max.x + "\" seamless src=\""
+ convertContextPath( GetTransImageServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" )
+ "\"></iframe>" );
out.print( "<p>" );
if ( ( trans.isFinished() && trans.isRunning() )
|| ( !trans.isRunning() && !trans.isPreparing() && !trans.isInitializing() ) ) {
out.print( "<a href=\""
+ convertContextPath( StartTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.StartTrans" ) + "</a>" );
out.print( "<p>" );
out.print( "<a href=\""
+ convertContextPath( PrepareExecutionTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.PrepareTrans" ) + "</a><br>" );
} else if ( trans.isRunning() ) {
out.print( "<a href=\""
+ convertContextPath( PauseTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "PauseStatusServlet.PauseResumeTrans" ) + "</a><br>" );
out.print( "<a href=\""
+ convertContextPath( StopTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.StopTrans" ) + "</a>" );
out.print( "<p>" );
}
out.print( "<a href=\""
+ convertContextPath( CleanupTransServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.CleanupTrans" ) + "</a>" );
out.print( "<p>" );
out.println( "<table border=\"1\">" );
out.print( "<tr> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Stepname" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.CopyNr" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Read" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Written" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Input" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Output" ) + "</th> " + "<th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Updated" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Rejected" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Errors" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Active" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Time" ) + "</th> " + "<th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.Speed" ) + "</th> <th>"
+ BaseMessages.getString( PKG, "TransStatusServlet.prinout" ) + "</th> </tr>" );
for ( int i = 0; i < trans.nrSteps(); i++ ) {
StepInterface step = trans.getRunThread( i );
if ( ( step.isRunning() ) || step.getStatus() != StepExecutionStatus.STATUS_EMPTY ) {
StepStatus stepStatus = new StepStatus( step );
boolean snif = false;
if ( step.isRunning() && !step.isStopped() && !step.isPaused() ) {
snif = true;
String sniffLink =
" <a href=\""
+ convertContextPath( SniffStepServlet.CONTEXT_PATH ) + "?trans="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" )
+ "&lines=50" + "©nr=" + step.getCopy() + "&type=" + SniffStepServlet.TYPE_OUTPUT
+ "&step=" + URLEncoder.encode( step.getStepname(), "UTF-8" ) + "\">"
+ Encode.forHtml( stepStatus.getStepname() ) + "</a>";
stepStatus.setStepname( sniffLink );
}
out.print( stepStatus.getHTMLTableRow( snif ) );
}
}
out.println( "</table>" );
out.println( "<p>" );
out.print( "<a href=\""
+ convertContextPath( GetTransStatusServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "&xml=y\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.ShowAsXml" ) + "</a><br>" );
out.print( "<a href=\""
+ convertContextPath( GetStatusServlet.CONTEXT_PATH ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.BackToStatusPage" ) + "</a><br>" );
out.print( "<p><a href=\""
+ convertContextPath( GetTransStatusServlet.CONTEXT_PATH ) + "?name="
+ URLEncoder.encode( transName, "UTF-8" ) + "&id=" + URLEncoder.encode( id, "UTF-8" ) + "\">"
+ BaseMessages.getString( PKG, "TransStatusServlet.Refresh" ) + "</a>" );
// Put the logging below that.
out.println( "<p>" );
out
.println( "<textarea id=\"translog\" cols=\"120\" rows=\"20\" "
+ "wrap=\"off\" name=\"Transformation log\" readonly=\"readonly\">"
+ Encode.forHtml( logText ) + "</textarea>" );
out.println( "<script type=\"text/javascript\"> " );
out.println( " translog.scrollTop=translog.scrollHeight; " );
out.println( "</script> " );
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, "TransStatusServlet.Log.CoundNotFindSpecTrans", transName ) ) );
} else {
out.println( "<H1>"
+ Encode.forHtml( BaseMessages.getString(
PKG, "TransStatusServlet.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;
}
}