/*! ****************************************************************************** * * 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.core; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import javax.xml.bind.annotation.XmlRootElement; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleFileException; import org.pentaho.di.core.row.RowMeta; import org.pentaho.di.core.row.RowMetaInterface; import org.pentaho.di.core.xml.XMLHandler; import org.w3c.dom.Node; /** * Describes the result of the execution of a Transformation or a Job. The information available includes the following: * <p> * <ul> * <li>Number of errors the job or transformation encountered</li> * <li>Number of lines input</li> * <li>Number of lines output</li> * <li>Number of lines updated</li> * <li>Number of lines read</li> * <li>Number of lines written</li> * <li>Number of lines deleted</li> * <li>Number of lines rejected</li> * <li>Number of files retrieved</li> * <li>Boolean result of the execution</li> * <li>Exit status value</li> * <li>Whether the transformation was stopped</li> * <li>Logging information (channel ID and text)</li> * </p> * * After execution of a job or transformation, the Result can be evaluated. * * @author Matt * @since 05-11-2003 */ @XmlRootElement public class Result implements Cloneable { /** A constant specifying the tag value for the XML node of the result object */ public static final String XML_TAG = "result"; /** A constant specifying the tag value for the XML node for result files entry */ public static final String XML_FILES_TAG = "result-file"; /** A constant specifying the tag value for the XML node for the result file entry */ public static final String XML_FILE_TAG = "result-file"; /** A constant specifying the tag value for the XML node for the result rows entry */ public static final String XML_ROWS_TAG = "result-rows"; /** The number of errors during the transformation or job */ private long nrErrors; /** The number of lines input. */ private long nrLinesInput; /** The number of lines output. */ private long nrLinesOutput; /** The number of lines updated. */ private long nrLinesUpdated; /** The number of lines read. */ private long nrLinesRead; /** The number of lines written. */ private long nrLinesWritten; /** The number of lines deleted. */ private long nrLinesDeleted; /** The number of files retrieved. */ private long nrFilesRetrieved; /** The result of the job or transformation, true if successful, false otherwise. */ private boolean result; /** The entry number. */ private long entryNr; /** The exit status. */ private int exitStatus; /** The rows. */ private List<RowMetaAndData> rows; /** The result files. */ private Map<String, ResultFile> resultFiles; /** Whether the job or transformation was stopped. */ public boolean stopped; /** The number of lines rejected. */ private long nrLinesRejected; /** The log channel id. */ private String logChannelId; /** The log text. */ private String logText; /** * Instantiates a new Result object, setting default values for all members */ public Result() { nrErrors = 0L; nrLinesInput = 0L; nrLinesOutput = 0L; nrLinesUpdated = 0L; nrLinesRead = 0L; nrLinesWritten = 0L; result = false; exitStatus = 0; rows = new ArrayList<RowMetaAndData>(); resultFiles = new ConcurrentHashMap<String, ResultFile>(); stopped = false; entryNr = 0; } /** * Instantiates a new Result object, setting default values for all members and the entry number * * @param nr * the entry number for the Result */ public Result( int nr ) { this(); this.entryNr = nr; } /** * Performs a semi-deep copy/clone but does not clone the rows from the Result * * @return An almost-clone of the Result, minus the rows */ public Result lightClone() { // This light-weight clone doesn't clone rows try { Result result = (Result) super.clone(); result.setRows( null ); if ( resultFiles != null ) { Map<String, ResultFile> clonedFiles = new ConcurrentHashMap<String, ResultFile>(); Collection<ResultFile> files = resultFiles.values(); for ( ResultFile file : files ) { clonedFiles.put( file.getFile().toString(), file.clone() ); } result.setResultFiles( clonedFiles ); } return result; } catch ( CloneNotSupportedException e ) { return null; } } /** * Clones the Result, including rows and files. To perform a clone without rows, use lightClone() * * @see java.lang.Object#clone() * @see Result#lightClone * * @return A clone of the Result object */ @Override public Result clone() { try { Result result = (Result) super.clone(); // Clone result rows and files as well... if ( rows != null ) { List<RowMetaAndData> clonedRows = new ArrayList<RowMetaAndData>(); for ( int i = 0; i < rows.size(); i++ ) { clonedRows.add( ( rows.get( i ) ).clone() ); } result.setRows( clonedRows ); } if ( resultFiles != null ) { Map<String, ResultFile> clonedFiles = new ConcurrentHashMap<String, ResultFile>(); Collection<ResultFile> files = resultFiles.values(); for ( ResultFile file : files ) { clonedFiles.put( file.getFile().toString(), file.clone() ); } result.setResultFiles( clonedFiles ); } return result; } catch ( CloneNotSupportedException e ) { return null; } } /** * Creates a string containing the read/write throughput. Throughput in this case is defined as two measures, number * of lines read or written and number of lines read/written per second. * * @param seconds * the number of seconds with which to determine the read/write throughput * @return a string containing the read write throughput measures with labelling text */ public String getReadWriteThroughput( int seconds ) { String throughput = null; if ( seconds != 0 ) { String readClause = null, writtenClause = null; if ( getNrLinesRead() > 0 ) { readClause = String.format( "lines read: %d ( %d lines/s)", getNrLinesRead(), ( getNrLinesRead() / seconds ) ); } if ( getNrLinesWritten() > 0 ) { writtenClause = String.format( "%slines written: %d ( %d lines/s)", ( getNrLinesRead() > 0 ? "; " : "" ), getNrLinesWritten(), ( getNrLinesWritten() / seconds ) ); } if ( readClause != null || writtenClause != null ) { throughput = String.format( "Transformation %s%s", ( getNrLinesRead() > 0 ? readClause : "" ), ( getNrLinesWritten() > 0 ? writtenClause : "" ) ); } } return throughput; } /** * Returns a string representation of the Result object * * @see java.lang.Object#toString() */ @Override public String toString() { return "nr=" + entryNr + ", errors=" + nrErrors + ", exit_status=" + exitStatus + ( stopped ? " (Stopped)" : "" + ", result=" + result ); } /** * Returns the number of files retrieved during execution of this transformation or job * * @return the number of files retrieved */ public long getNrFilesRetrieved() { return nrFilesRetrieved; } /** * Sets the number of files retrieved to the specified value * * @param filesRetrieved * The number of files retrieved to set. */ public void setNrFilesRetrieved( long filesRetrieved ) { this.nrFilesRetrieved = filesRetrieved; } /** * Returns the entry number * * @return the entry number */ public long getEntryNr() { return entryNr; } /** * Sets the entry number to the specified value * * @param entryNr * The entry number to set. */ public void setEntryNr( long entryNr ) { this.entryNr = entryNr; } /** * Returns the exit status value. * * @return the exit status. */ public int getExitStatus() { return exitStatus; } /** * Sets the exit status value to the specified value * * @param exitStatus * The exit status to set. */ public void setExitStatus( int exitStatus ) { this.exitStatus = exitStatus; } /** * Returns the number of errors that occurred during this transformation or job * * @return the number of errors */ public long getNrErrors() { return nrErrors; } /** * Sets the number of errors that occurred during execution of this transformation or job * * @param nrErrors * The number of errors to set */ public void setNrErrors( long nrErrors ) { this.nrErrors = nrErrors; } /** * Returns the number of lines input during execution of this transformation or job * * @return the number of lines input */ public long getNrLinesInput() { return nrLinesInput; } /** * Sets the number of lines input during execution of this transformation or job * * @param nrLinesInput * The number of lines input to set. */ public void setNrLinesInput( long nrLinesInput ) { this.nrLinesInput = nrLinesInput; } /** * Returns the number of lines output during execution of this transformation or job * * @return the number of lines output */ public long getNrLinesOutput() { return nrLinesOutput; } /** * Sets the number of lines output during execution of this transformation or job * * @param nrLinesOutput * The number of lines output to set */ public void setNrLinesOutput( long nrLinesOutput ) { this.nrLinesOutput = nrLinesOutput; } /** * Returns the number of lines read during execution of this transformation or job * * @return the number of lines read */ public long getNrLinesRead() { return nrLinesRead; } /** * Sets the number of lines read during execution of this transformation or job * * @param nrLinesRead * The number of lines read to set. */ public void setNrLinesRead( long nrLinesRead ) { this.nrLinesRead = nrLinesRead; } /** * Returns the number of lines updated during execution of this transformation or job * * @return the number of lines updated */ public long getNrLinesUpdated() { return nrLinesUpdated; } /** * Sets the number of lines updated during execution of this transformation or job * * @param nrLinesUpdated * The number of lines updated to set. */ public void setNrLinesUpdated( long nrLinesUpdated ) { this.nrLinesUpdated = nrLinesUpdated; } /** * Returns the number of lines written during execution of this transformation or job * * @return the number of lines written */ public long getNrLinesWritten() { return nrLinesWritten; } /** * Sets the number of lines written during execution of this transformation or job * * @param nrLinesWritten * The number of lines written to set. */ public void setNrLinesWritten( long nrLinesWritten ) { this.nrLinesWritten = nrLinesWritten; } /** * Returns the number of lines deleted during execution of this transformation or job * * @return the number of lines deleted */ public long getNrLinesDeleted() { return nrLinesDeleted; } /** * Sets the number of lines deleted during execution of this transformation or job * * @param nrLinesDeleted * The number of lines deleted to set. */ public void setNrLinesDeleted( long nrLinesDeleted ) { this.nrLinesDeleted = nrLinesDeleted; } /** * Returns the boolean result of this transformation or job * * @return true if the transformation or job was successful, false otherwise */ public boolean getResult() { return result; } /** * Sets the result of the transformation or job. A value of true should indicate a successful execution, a value of * false should indicate an error condition. * * @param result * The boolean result to set. */ public void setResult( boolean result ) { this.result = result; } /** * Returns the resulting rowset from the job or transformation. For example, Result rows are used in jobs where * entries wish to receive the results of previous executions of jobs or transformations. The Result rows can be used * to do many kinds of transformation or job post-processing. * * @return a List of rows associated with the result of execution of a job or transformation */ public List<RowMetaAndData> getRows() { return rows; } /** * Sets the resulting rowset from the job or transformation execution * * @param rows * The List of rows to set. */ public void setRows( List<RowMetaAndData> rows ) { this.rows = rows; } /** * Returns whether the transformation or job was stopped before completion * * @return true if stopped, false otherwise */ public boolean isStopped() { return stopped; } /** * Sets whether the transformation or job was stopped before completion * * @param stopped * true if the transformation or job was stopped, false otherwise */ public void setStopped( boolean stopped ) { this.stopped = stopped; } /** * Clears the numbers in this result, setting them all to zero. Also deletes the logging text */ public void clear() { nrLinesInput = 0; nrLinesOutput = 0; nrLinesRead = 0; nrLinesWritten = 0; nrLinesUpdated = 0; nrLinesRejected = 0; nrLinesDeleted = 0; nrErrors = 0; nrFilesRetrieved = 0; logText = null; } /** * Add the numbers of lines from a different result to this result * * @param res * The Result object from which to add */ public void add( Result res ) { nrLinesInput += res.getNrLinesInput(); nrLinesOutput += res.getNrLinesOutput(); nrLinesRead += res.getNrLinesRead(); nrLinesWritten += res.getNrLinesWritten(); nrLinesUpdated += res.getNrLinesUpdated(); nrLinesRejected += res.getNrLinesRejected(); nrLinesDeleted += res.getNrLinesDeleted(); nrErrors += res.getNrErrors(); nrFilesRetrieved += res.getNrFilesRetrieved(); resultFiles.putAll( res.getResultFiles() ); logChannelId = res.getLogChannelId(); logText = res.getLogText(); rows.addAll( res.getRows() ); } /** * Returns a String object with the Result object serialized as XML * * @return This Result object serialized as XML */ public String getXML() { try { StringBuilder xml = new StringBuilder(); xml.append( XMLHandler.openTag( XML_TAG ) ); setBasicXmlAttrs( xml ); // Export the result files // xml.append( XMLHandler.openTag( XML_FILES_TAG ) ); for ( ResultFile resultFile : resultFiles.values() ) { xml.append( resultFile.getXML() ); } xml.append( XMLHandler.closeTag( XML_FILES_TAG ) ); xml.append( XMLHandler.openTag( XML_ROWS_TAG ) ); boolean firstRow = true; RowMetaInterface rowMeta = null; for ( RowMetaAndData row : rows ) { if ( firstRow ) { firstRow = false; rowMeta = row.getRowMeta(); xml.append( rowMeta.getMetaXML() ); } xml.append( rowMeta.getDataXML( row.getData() ) ); } xml.append( XMLHandler.closeTag( XML_ROWS_TAG ) ); xml.append( XMLHandler.closeTag( XML_TAG ) ); return xml.toString(); } catch ( IOException e ) { throw new RuntimeException( "Unexpected error encoding job result as XML", e ); } } private StringBuilder setBasicXmlAttrs( StringBuilder xml ) { // First the metrics... // xml.append( XMLHandler.addTagValue( "lines_input", nrLinesInput ) ); xml.append( XMLHandler.addTagValue( "lines_output", nrLinesOutput ) ); xml.append( XMLHandler.addTagValue( "lines_read", nrLinesRead ) ); xml.append( XMLHandler.addTagValue( "lines_written", nrLinesWritten ) ); xml.append( XMLHandler.addTagValue( "lines_updated", nrLinesUpdated ) ); xml.append( XMLHandler.addTagValue( "lines_rejected", nrLinesRejected ) ); xml.append( XMLHandler.addTagValue( "lines_deleted", nrLinesDeleted ) ); xml.append( XMLHandler.addTagValue( "nr_errors", nrErrors ) ); xml.append( XMLHandler.addTagValue( "nr_files_retrieved", nrFilesRetrieved ) ); xml.append( XMLHandler.addTagValue( "entry_nr", entryNr ) ); // The high level results... // xml.append( XMLHandler.addTagValue( "result", result ) ); xml.append( XMLHandler.addTagValue( "exit_status", exitStatus ) ); xml.append( XMLHandler.addTagValue( "is_stopped", stopped ) ); xml.append( XMLHandler.addTagValue( "log_channel_id", logChannelId ) ); xml.append( XMLHandler.addTagValue( "log_text", logText ) ); return xml; } public String getBasicXml() { StringBuilder xml = new StringBuilder(); xml.append( XMLHandler.openTag( XML_TAG ) ); setBasicXmlAttrs( xml ); xml.append( XMLHandler.closeTag( XML_TAG ) ); return xml.toString(); } /** * Instantiates a new Result object from a DOM node * * @param node * the DOM root node representing the desired Result * @throws KettleException * if any errors occur during instantiation */ public Result( Node node ) throws KettleException { this(); // First we read the metrics... // nrLinesInput = Const.toLong( XMLHandler.getTagValue( node, "lines_input" ), 0L ); nrLinesOutput = Const.toLong( XMLHandler.getTagValue( node, "lines_output" ), 0L ); nrLinesRead = Const.toLong( XMLHandler.getTagValue( node, "lines_read" ), 0L ); nrLinesWritten = Const.toLong( XMLHandler.getTagValue( node, "lines_written" ), 0L ); nrLinesUpdated = Const.toLong( XMLHandler.getTagValue( node, "lines_updated" ), 0L ); nrLinesRejected = Const.toLong( XMLHandler.getTagValue( node, "lines_rejected" ), 0L ); nrLinesDeleted = Const.toLong( XMLHandler.getTagValue( node, "lines_deleted" ), 0L ); nrErrors = Const.toLong( XMLHandler.getTagValue( node, "nr_errors" ), 0L ); nrFilesRetrieved = Const.toLong( XMLHandler.getTagValue( node, "nr_files_retrieved" ), 0L ); entryNr = Const.toLong( XMLHandler.getTagValue( node, "entry_nr" ), 0L ); // The high level results... // result = "Y".equalsIgnoreCase( XMLHandler.getTagValue( node, "result" ) ); exitStatus = Integer.parseInt( XMLHandler.getTagValue( node, "exit_status" ) ); stopped = "Y".equalsIgnoreCase( XMLHandler.getTagValue( node, "is_stopped" ) ); logChannelId = XMLHandler.getTagValue( node, "log_channel_id" ); logText = XMLHandler.getTagValue( node, "log_text" ); // Now read back the result files... // Node resultFilesNode = XMLHandler.getSubNode( node, XML_FILES_TAG ); int nrResultFiles = XMLHandler.countNodes( resultFilesNode, XML_FILE_TAG ); for ( int i = 0; i < nrResultFiles; i++ ) { try { ResultFile resultFile = new ResultFile( XMLHandler.getSubNodeByNr( resultFilesNode, XML_FILE_TAG, i ) ); resultFiles.put( resultFile.getFile().toString(), resultFile ); } catch ( KettleFileException e ) { throw new KettleException( "Unexpected error reading back a ResultFile object from XML", e ); } } // Let's also read back the result rows... // Node resultRowsNode = XMLHandler.getSubNode( node, XML_ROWS_TAG ); List<Node> resultNodes = XMLHandler.getNodes( resultRowsNode, RowMeta.XML_DATA_TAG ); if ( !resultNodes.isEmpty() ) { // OK, get the metadata first... // RowMeta rowMeta = new RowMeta( XMLHandler.getSubNode( resultRowsNode, RowMeta.XML_META_TAG ) ); for ( Node resultNode : resultNodes ) { Object[] rowData = rowMeta.getRow( resultNode ); rows.add( new RowMetaAndData( rowMeta, rowData ) ); } } } /** * Returns the result files as a Map with the filename as key and the ResultFile object as value * * @see org.pentaho.di.core.ResultFile * * @return a Map with String as key and ResultFile as value. */ public Map<String, ResultFile> getResultFiles() { return resultFiles; } /** * Returns the result files as a List of type ResultFile * * @see org.pentaho.di.core.ResultFile * @return a list of type ResultFile containing this Result's ResultFile objects */ public List<ResultFile> getResultFilesList() { return new ArrayList<ResultFile>( resultFiles.values() ); } /** * Sets the result files for this Result to the specified Map of ResultFile objects * * @see org.pentaho.di.core.ResultFile * * @param usedFiles * The Map of result files to set. This is a Map with the filename as key and ResultFile object as value */ public void setResultFiles( Map<String, ResultFile> usedFiles ) { this.resultFiles = usedFiles; } /** * Returns the number of lines rejected during execution of this transformation or job * * @return the number of lines rejected */ public long getNrLinesRejected() { return nrLinesRejected; } /** * Sets the number of lines rejected during execution of this transformation or job * * @param nrLinesRejected * the number of lines rejected to set */ public void setNrLinesRejected( long nrLinesRejected ) { this.nrLinesRejected = nrLinesRejected; } /** * Returns the log channel id of the object that was executed (trans, job, job entry, etc) * * @return the log channel id */ public String getLogChannelId() { return logChannelId; } /** * Sets the log channel id of the object that was executed (trans, job, job entry, etc) * * @param logChannelId * the logChannelId to set */ public void setLogChannelId( String logChannelId ) { this.logChannelId = logChannelId; } /** * Increases the number of lines read by the specified value * * @param incr * the amount to increment */ public void increaseLinesRead( long incr ) { nrLinesRead += incr; } /** * Increases the number of lines written by the specified value * * @param incr * the amount to increment */ public void increaseLinesWritten( long incr ) { nrLinesWritten += incr; } /** * Increases the number of lines input by the specified value * * @param incr * the amount to increment */ public void increaseLinesInput( long incr ) { nrLinesInput += incr; } /** * Increases the number of lines output by the specified value * * @param incr * the amount to increment */ public void increaseLinesOutput( long incr ) { nrLinesOutput += incr; } /** * Increases the number of lines updated by the specified value * * @param incr * the amount to increment */ public void increaseLinesUpdated( long incr ) { nrLinesUpdated += incr; } /** * Increases the number of lines deleted by the specified value * * @param incr * the amount to increment */ public void increaseLinesDeleted( long incr ) { nrLinesDeleted += incr; } /** * Increases the number of lines rejected by the specified value * * @param incr * the amount to increment */ public void increaseLinesRejected( long incr ) { nrLinesRejected += incr; } /** * Increases the number of errors by the specified value * * @param incr * the amount to increment */ public void increaseErrors( long incr ) { nrErrors += incr; } /** * Returns all the text from any logging performed by the transformation or job * * @return the logging text as a string */ public String getLogText() { return logText; } /** * Sets the logging text to the specified String * * @param logText * the logText to set */ public void setLogText( String logText ) { this.logText = logText; } }