/*! ******************************************************************************
*
* Pentaho Data Integration
*
* Copyright (C) 2002-2015 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.trans.steps.prioritizestreams;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.pentaho.di.core.BlockingRowSet;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.RowSet;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.logging.LoggingObjectInterface;
import org.pentaho.di.core.row.RowMeta;
import org.pentaho.di.core.row.RowMetaInterface;
import org.pentaho.di.trans.step.StepDataInterface;
import org.pentaho.di.trans.steps.mock.StepMockHelper;
@RunWith( Parameterized.class )
public class PrioritizeStreamsExecutionIT {
private String old_timeout_get;
private static StepMockHelper<PrioritizeStreamsMeta, StepDataInterface> stepMockHelper;
private static PrioritizeStreamsMeta meta;
private TestData code;
private static ExecutorService service = Executors.newCachedThreadPool();
public PrioritizeStreamsExecutionIT( TestData code ) {
this.code = code;
}
@BeforeClass
public static void setup() {
stepMockHelper =
new StepMockHelper<PrioritizeStreamsMeta, StepDataInterface>( "Priority Streams Test",
PrioritizeStreamsMeta.class, StepDataInterface.class );
when( stepMockHelper.logChannelInterfaceFactory.create( any(), any( LoggingObjectInterface.class ) ) ).thenReturn(
stepMockHelper.logChannelInterface );
when( stepMockHelper.trans.isRunning() ).thenReturn( true );
meta = mock( PrioritizeStreamsMeta.class );
}
@AfterClass
public static void tearDown() {
stepMockHelper.cleanUp();
service.shutdown();
}
@Before
public void before() {
old_timeout_get = System.getProperty( Const.KETTLE_ROWSET_GET_TIMEOUT );
// 1 sec
System.setProperty( Const.KETTLE_ROWSET_GET_TIMEOUT, "1000" );
}
@After
public void after() {
if ( old_timeout_get != null ) {
System.setProperty( Const.KETTLE_ROWSET_GET_TIMEOUT, old_timeout_get );
}
}
enum TestData {
ABC( "aaabbbccc" ), BAC( "bbbaaaccc" ), BCA( "bbbcccaaa" ), CAB( "cccaaabbb" ), CBA( "cccbbbaaa" );
private String res;
TestData( String res ) {
this.res = res;
}
public String toString() {
return res;
}
}
@Parameterized.Parameters
public static Collection<Object[]> primeNumbers() {
TestData[] td = TestData.values();
Object[][] ret = new Object[td.length][1];
for ( int i = 0; i < ret.length; i++ ) {
ret[i] = new Object[] { td[i] };
}
return Arrays.asList( ret );
}
/**
* Test that priority streams step does respect input streams priority and input streams delay with their put row
* method.
*
* Please pay attention, this test is based on concurrent execution environment,
* so put input rowset delay should correlate with KETTLE_ROWSET_GET_TIMEOUT value.
*
* @throws KettleException
*/
@Test
public void testProcessRow() throws KettleException {
PrioritizeStreamsData data = getTestData( code );
PrioritizeStreamsInner ps = new PrioritizeStreamsInner( stepMockHelper );
ps.first = false;
for ( int i = 0; i < 9; i++ ) {
ps.processRow( meta, data );
}
Assert.assertEquals( "Output stream collect all rows: " + ps.toString(), 9, ps.output.size() );
Assert.assertEquals( "Stream output respect priority", code.toString(), ps.toString() );
}
private PrioritizeStreamsData getTestData( TestData code ) {
PrioritizeStreamsData retData = new PrioritizeStreamsData();
RowSet in1 = new BlockingRowSet( 4 );
RowSet in2 = new BlockingRowSet( 4 );
RowSet in3 = new BlockingRowSet( 4 );
Object[][] data1 = { { "a" }, { "a" }, { "a" } };
Object[][] data2 = { { "b" }, { "b" }, { "b" } };
Object[][] data3 = { { "c" }, { "c" }, { "c" } };
RowMetaInterface rmi = new RowMeta();
Runnable stream1 = this.getInputProduser( in1, rmi, data1 );
Runnable stream2 = this.getInputProduser( in2, rmi, data2 );
Runnable stream3 = this.getInputProduser( in3, rmi, data3 );
retData.outputRowMeta = rmi;
switch ( code ) {
case ABC: {
retData.rowSets = new RowSet[] { in1, in2, in3 };
break;
}
case BAC: {
retData.rowSets = new RowSet[] { in2, in1, in3 };
break;
}
case BCA: {
retData.rowSets = new RowSet[] { in2, in3, in1 };
break;
}
case CAB: {
retData.rowSets = new RowSet[] { in3, in1, in2 };
break;
}
case CBA: {
retData.rowSets = new RowSet[] { in3, in2, in1 };
break;
}
default: {
Assert.fail( "This test data does not supported: " + code.toString() );
}
}
retData.currentRowSet = retData.rowSets[0];
retData.stepnr = 0;
retData.stepnrs = 3;
service.execute( stream1 );
service.execute( stream2 );
service.execute( stream3 );
return retData;
}
private Runnable getInputProduser( final RowSet in, final RowMetaInterface rmi, final Object[][] data ) {
return new InputProducer( in, rmi, data );
}
/**
* This class simulates working delay before actual data will be available for
* input stream. Pay attention to KETTLE_ROWSET_GET_TIMEOUT value. Default
* value can be too small - and empty (even not started to fill) rowset will
* be treated as empty (returns 'null' for offer method call on blocking queue).
*
*/
private class InputProducer implements Runnable {
private Queue<Object[]> data;
private RowSet rs;
private RowMetaInterface rmi;
private boolean first = true;
InputProducer( RowSet in, RowMetaInterface rmi, Object[][] data ) {
this.data = new LinkedList<Object[]>();
this.data.addAll( Arrays.asList( data ) );
this.rs = in;
this.rmi = rmi;
}
@Override
public void run() {
while ( data.peek() != null ) {
// first row always came with delay
if ( first ) {
first = false;
try {
Thread.sleep( 50 );
} catch ( InterruptedException e ) {
// this will never happens
e.printStackTrace();
}
}
Object[] putTo = data.poll();
rs.putRowWait( rmi, putTo, 20, TimeUnit.SECONDS );
}
rs.setDone();
}
}
private class PrioritizeStreamsInner extends PrioritizeStreams {
List<Object[]> output = new ArrayList<Object[]>();
public PrioritizeStreamsInner( StepMockHelper<PrioritizeStreamsMeta, StepDataInterface> stepMockHelper ) {
super( stepMockHelper.stepMeta, stepMockHelper.stepDataInterface, 0, stepMockHelper.transMeta,
stepMockHelper.trans );
}
@Override
public Object[] getRowFrom( RowSet rs ) {
return rs.getRow();
}
@Override
public void putRow( RowMetaInterface rmi, Object[] row ) {
output.add( row );
}
public String toString() {
StringBuilder sb = new StringBuilder();
Iterator<Object[]> it = output.iterator();
while ( it.hasNext() ) {
String val = String.class.cast( it.next()[0] );
sb.append( val );
}
return sb.toString();
}
}
}