/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2017 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.metainject; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.refEq; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.pentaho.di.core.Result; import org.pentaho.di.core.RowMetaAndData; import org.pentaho.di.core.RowSet; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.exception.KettleValueException; import org.pentaho.di.core.injection.Injection; import org.pentaho.di.core.injection.InjectionSupported; import org.pentaho.di.core.logging.LogLevel; import org.pentaho.di.core.row.RowMeta; import org.pentaho.di.core.row.ValueMetaInterface; import org.pentaho.di.core.row.value.ValueMetaBigNumber; import org.pentaho.di.core.row.value.ValueMetaBoolean; import org.pentaho.di.core.row.value.ValueMetaDate; import org.pentaho.di.core.row.value.ValueMetaInteger; import org.pentaho.di.core.row.value.ValueMetaNumber; import org.pentaho.di.core.row.value.ValueMetaString; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.BaseStepMeta; import org.pentaho.di.trans.step.StepDataInterface; import org.pentaho.di.trans.step.StepInjectionMetaEntry; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaInjectionInterface; import org.pentaho.di.trans.step.StepMetaInterface; import org.pentaho.di.trans.steps.StepMockUtil; import org.pentaho.metastore.api.IMetaStore; public class MetaInjectTest { private static final String INJECTOR_STEP_NAME = "TEST_STEP_FOR_INJECTION"; private static final String TEST_VALUE = "TEST_VALUE"; private static final String TEST_VARIABLE = "TEST_VARIABLE"; private static final String TEST_PARAMETER = "TEST_PARAMETER"; private static final String TEST_TARGET_STEP_NAME = "TEST_TARGET_STEP_NAME"; private static final String TEST_SOURCE_STEP_NAME = "TEST_SOURCE_STEP_NAME"; private static final String TEST_ATTR_VALUE = "TEST_ATTR_VALUE"; private static final String TEST_FIELD = "TEST_FIELD"; private static final String UNAVAILABLE_STEP = "UNAVAILABLE_STEP"; private static final TargetStepAttribute UNAVAILABLE_TARGET_STEP = new TargetStepAttribute( UNAVAILABLE_STEP, TEST_ATTR_VALUE, false ); private static final SourceStepField UNAVAILABLE_SOURCE_STEP = new SourceStepField( UNAVAILABLE_STEP, TEST_FIELD ); private MetaInject metaInject; private MetaInjectMeta meta; private MetaInjectData data; private TransMeta transMeta; private StepMetaInjectionInterface metaInjectionInterface; private IMetaStore metaStore; @Before public void before() throws Exception { metaInject = StepMockUtil.getStep( MetaInject.class, MetaInjectMeta.class, "MetaInjectTest" ); metaInject = spy( metaInject ); metaStore = mock( IMetaStore.class ); metaInject.setMetaStore( metaStore ); transMeta = mock( TransMeta.class ); doReturn( transMeta ).when( metaInject ).getTransMeta(); meta = new MetaInjectMeta(); data = new MetaInjectData(); TransMeta internalTransMeta = mock( TransMeta.class ); StepMeta stepMeta = mock( StepMeta.class ); doReturn( INJECTOR_STEP_NAME ).when( stepMeta ).getName(); doReturn( Collections.singletonList( stepMeta ) ).when( internalTransMeta ).getUsedSteps(); StepMetaInterface stepMetaInterface = mock( StepMetaInterface.class ); doReturn( stepMetaInterface ).when( stepMeta ).getStepMetaInterface(); metaInjectionInterface = mock( StepMetaInjectionInterface.class ); doReturn( metaInjectionInterface ).when( stepMetaInterface ).getStepMetaInjectionInterface(); doReturn( internalTransMeta ).when( metaInject ).loadTransformationMeta(); } @Test public void injectMetaFromMultipleInputSteps() throws KettleException { Map<TargetStepAttribute, SourceStepField> targetSourceMapping = new LinkedHashMap<TargetStepAttribute, SourceStepField>(); targetSourceMapping.put( new TargetStepAttribute( INJECTOR_STEP_NAME, "DATA_TYPE", true ), new SourceStepField( "TYPE_INPUT", "col_type" ) ); targetSourceMapping.put( new TargetStepAttribute( INJECTOR_STEP_NAME, "NAME", true ), new SourceStepField( "NAME_INPUT", "col_name" ) ); meta.setTargetSourceMapping( targetSourceMapping ); doReturn( new String[] { "NAME_INPUT", "TYPE_INPUT" } ).when( transMeta ).getPrevStepNames( any( StepMeta.class ) ); RowSet nameInputRowSet = mock( RowSet.class ); RowMeta nameRowMeta = new RowMeta(); nameRowMeta.addValueMeta( new ValueMetaString( "col_name" ) ); doReturn( nameRowMeta ).when( nameInputRowSet ).getRowMeta(); doReturn( nameInputRowSet ).when( metaInject ).findInputRowSet( "NAME_INPUT" ); RowSet typeInputRowSet = mock( RowSet.class ); RowMeta typeRowMeta = new RowMeta(); typeRowMeta.addValueMeta( new ValueMetaString( "col_type" ) ); doReturn( typeRowMeta ).when( typeInputRowSet ).getRowMeta(); doReturn( typeInputRowSet ).when( metaInject ).findInputRowSet( "TYPE_INPUT" ); doReturn( new Object[] { "FIRST_NAME" } ).doReturn( null ).when( metaInject ).getRowFrom( nameInputRowSet ); doReturn( new Object[] { "String" } ).doReturn( null ).when( metaInject ).getRowFrom( typeInputRowSet ); List<StepInjectionMetaEntry> injectionMetaEntryList = new ArrayList<StepInjectionMetaEntry>(); StepInjectionMetaEntry fields = new StepInjectionMetaEntry( "FIELDS", ValueMetaInterface.TYPE_NONE, "" ); StepInjectionMetaEntry fieldEntry = new StepInjectionMetaEntry( "FIELD", ValueMetaInterface.TYPE_NONE, "" ); fields.getDetails().add( fieldEntry ); StepInjectionMetaEntry nameEntry = new StepInjectionMetaEntry( "NAME", ValueMetaInterface.TYPE_STRING, "" ); fieldEntry.getDetails().add( nameEntry ); StepInjectionMetaEntry dataEntry = new StepInjectionMetaEntry( "DATA_TYPE", ValueMetaInterface.TYPE_STRING, "" ); fieldEntry.getDetails().add( dataEntry ); injectionMetaEntryList.add( fields ); doReturn( injectionMetaEntryList ).when( metaInjectionInterface ).getStepInjectionMetadataEntries(); meta.setNoExecution( true ); assertTrue( metaInject.init( meta, data ) ); metaInject.processRow( meta, data ); StepInjectionMetaEntry expectedNameEntry = new StepInjectionMetaEntry( "NAME", "FIRST_NAME", ValueMetaInterface.TYPE_STRING, "" ); StepInjectionMetaEntry expectedDataEntry = new StepInjectionMetaEntry( "DATA_TYPE", "String", ValueMetaInterface.TYPE_STRING, "" ); verify( metaInject, atLeastOnce() ).setEntryValueIfFieldExists( refEq( expectedNameEntry ), any( RowMetaAndData.class ), any( SourceStepField.class ) ); verify( metaInject, atLeastOnce() ).setEntryValueIfFieldExists( refEq( expectedDataEntry ), any( RowMetaAndData.class ), any( SourceStepField.class ) ); } @Test public void testMetastoreIsSet() throws Exception { doReturn( new String[] { } ).when( transMeta ).getPrevStepNames( any( StepMeta.class ) ); data.stepInjectionMetasMap = new HashMap<>(); data.stepInjectionMap = new HashMap<>(); data.transMeta = new TransMeta(); meta.setNoExecution( false ); doReturn( LogLevel.ERROR ).when( metaInject ).getLogLevel(); // don't need to actually run anything to verify this. force it to "stopped" doReturn( true ).when( metaInject ).isStopped(); doNothing().when( metaInject ).waitUntilFinished( any( Trans.class ) ); // make sure the injected tranformation doesn't have a metastore first assertNull( data.transMeta.getMetaStore() ); metaInject.processRow( meta, data ); // now it should be set assertEquals( metaStore, data.transMeta.getMetaStore() ); } @Test public void testTransWaitsForListenersToFinish() throws Exception { doReturn( new String[] { } ).when( transMeta ).getPrevStepNames( any( StepMeta.class ) ); data.stepInjectionMetasMap = new HashMap<>(); data.stepInjectionMap = new HashMap<>(); data.transMeta = new TransMeta(); meta.setNoExecution( false ); Trans injectTrans = mock( Trans.class ); doReturn( injectTrans ).when( metaInject ).createInjectTrans(); when( injectTrans.isFinished() ).thenReturn( true ); Result result = mock( Result.class ); when( injectTrans.getResult() ).thenReturn( result ); metaInject.processRow( meta, data ); verify( injectTrans ).waitUntilFinished(); } @Test public void transVariablesPassedToChildTransformation() throws KettleException { doReturn( new String[] { TEST_VARIABLE } ).when( metaInject ).listVariables(); doReturn( TEST_VALUE ).when( metaInject ).getVariable( TEST_VARIABLE ); TransMeta transMeta = new TransMeta(); doReturn( transMeta ).when( metaInject ).getTransMeta(); TransMeta internalTransMeta = new TransMeta(); doReturn( internalTransMeta ).when( metaInject ).loadTransformationMeta(); assertTrue( metaInject.init( meta, data ) ); assertEquals( TEST_VALUE, internalTransMeta.getVariable( TEST_VARIABLE ) ); } @Test public void transParametersPassedToChildTransformation() throws KettleException { TransMeta transMeta = new TransMeta(); transMeta.addParameterDefinition( TEST_PARAMETER, "TEST_DEF_VALUE", "" ); transMeta.setParameterValue( TEST_PARAMETER, TEST_VALUE ); doReturn( transMeta ).when( metaInject ).getTransMeta(); TransMeta internalTransMeta = new TransMeta(); doReturn( internalTransMeta ).when( metaInject ).loadTransformationMeta(); assertTrue( metaInject.init( meta, data ) ); assertEquals( TEST_VALUE, internalTransMeta.getParameterValue( TEST_PARAMETER ) ); } @Test public void getUnavailableSourceSteps() { TargetStepAttribute targetStep = new TargetStepAttribute( TEST_TARGET_STEP_NAME, TEST_ATTR_VALUE, false ); SourceStepField unavailableSourceStep = new SourceStepField( UNAVAILABLE_STEP, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = Collections.singletonMap( targetStep, unavailableSourceStep ); TransMeta sourceTransMeta = mock( TransMeta.class ); doReturn( new String[0] ).when( sourceTransMeta ).getPrevStepNames( any( StepMeta.class ) ); Set<SourceStepField> actualSet = MetaInject.getUnavailableSourceSteps( targetMap, sourceTransMeta, mock( StepMeta.class ) ); assertTrue( actualSet.contains( unavailableSourceStep ) ); } @Test public void getUnavailableTargetSteps() { TargetStepAttribute unavailableTargetStep = new TargetStepAttribute( UNAVAILABLE_STEP, TEST_ATTR_VALUE, false ); SourceStepField sourceStep = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = Collections.singletonMap( unavailableTargetStep, sourceStep ); TransMeta injectedTransMeta = mock( TransMeta.class ); doReturn( Collections.emptyList() ).when( injectedTransMeta ).getUsedSteps(); Set<TargetStepAttribute> actualSet = MetaInject.getUnavailableTargetSteps( targetMap, injectedTransMeta ); assertTrue( actualSet.contains( unavailableTargetStep ) ); } @Test public void removeUnavailableStepsFromMapping_unavailable_source_step() { TargetStepAttribute unavailableTargetStep = new TargetStepAttribute( UNAVAILABLE_STEP, TEST_ATTR_VALUE, false ); SourceStepField unavailableSourceStep = new SourceStepField( UNAVAILABLE_STEP, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = new HashMap<TargetStepAttribute, SourceStepField>(); targetMap.put( unavailableTargetStep, unavailableSourceStep ); Set<SourceStepField> unavailableSourceSteps = Collections.singleton( UNAVAILABLE_SOURCE_STEP ); MetaInject.removeUnavailableStepsFromMapping( targetMap, unavailableSourceSteps, Collections .<TargetStepAttribute>emptySet() ); assertTrue( targetMap.isEmpty() ); } @Test public void removeUnavailableStepsFromMapping_unavailable_target_step() { TargetStepAttribute unavailableTargetStep = new TargetStepAttribute( UNAVAILABLE_STEP, TEST_ATTR_VALUE, false ); SourceStepField unavailableSourceStep = new SourceStepField( UNAVAILABLE_STEP, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = new HashMap<TargetStepAttribute, SourceStepField>(); targetMap.put( unavailableTargetStep, unavailableSourceStep ); Set<TargetStepAttribute> unavailableTargetSteps = Collections.singleton( UNAVAILABLE_TARGET_STEP ); MetaInject.removeUnavailableStepsFromMapping( targetMap, Collections.<SourceStepField>emptySet(), unavailableTargetSteps ); assertTrue( targetMap.isEmpty() ); } @Test public void removeUnavailableStepsFromMapping_unavailable_source_target_step() { TargetStepAttribute unavailableTargetStep = new TargetStepAttribute( UNAVAILABLE_STEP, TEST_ATTR_VALUE, false ); SourceStepField unavailableSourceStep = new SourceStepField( UNAVAILABLE_STEP, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = new HashMap<TargetStepAttribute, SourceStepField>(); targetMap.put( unavailableTargetStep, unavailableSourceStep ); Set<TargetStepAttribute> unavailableTargetSteps = Collections.singleton( UNAVAILABLE_TARGET_STEP ); Set<SourceStepField> unavailableSourceSteps = Collections.singleton( UNAVAILABLE_SOURCE_STEP ); MetaInject.removeUnavailableStepsFromMapping( targetMap, unavailableSourceSteps, unavailableTargetSteps ); assertTrue( targetMap.isEmpty() ); } @Test public void setEntryValue_string() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_STRING ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaString( TEST_FIELD ), TEST_VALUE ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( TEST_VALUE ); } @Test public void setEntryValue_boolean() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_BOOLEAN ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaBoolean( TEST_FIELD ), true ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( true ); } @Test public void setEntryValue_integer() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_INTEGER ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaInteger( TEST_FIELD ), new Long( 1 ) ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( 1L ); } @Test public void setEntryValue_number() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_NUMBER ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaNumber( TEST_FIELD ), new Double( 1 ) ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( 1.0D ); } @Test public void setEntryValue_date() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_DATE ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaDate( TEST_FIELD ), null ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( null ); } @Test public void setEntryValue_bignumber() throws KettleValueException { StepInjectionMetaEntry entry = mock( StepInjectionMetaEntry.class ); doReturn( ValueMetaInterface.TYPE_BIGNUMBER ).when( entry ).getValueType(); RowMetaAndData row = createRowMetaAndData( new ValueMetaBigNumber( TEST_FIELD ), new BigDecimal( 1 ) ); SourceStepField sourceField = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); MetaInject.setEntryValue( entry, row, sourceField ); verify( entry ).setValue( new BigDecimal( 1 ) ); } @Test public void convertToUpperCaseSet_null_array() { Set<String> actualResult = MetaInject.convertToUpperCaseSet( null ); assertNotNull( actualResult ); assertTrue( actualResult.isEmpty() ); } @Test public void convertToUpperCaseSet() { String[] input = new String[] { "Test_Step", "test_step1" }; Set<String> actualResult = MetaInject.convertToUpperCaseSet( input ); Set<String> expectedResult = new HashSet<>(); expectedResult.add( "TEST_STEP" ); expectedResult.add( "TEST_STEP1" ); assertEquals( expectedResult, actualResult ); } @Test public void testGetUnavailableTargetKeys() throws Exception { final String targetStepName = "injectable step name"; TargetStepAttribute unavailableTargetAttr = new TargetStepAttribute( targetStepName, "NOT_THERE", false ); TargetStepAttribute availableTargetAttr = new TargetStepAttribute( targetStepName, "THERE", false ); SourceStepField sourceStep = new SourceStepField( TEST_SOURCE_STEP_NAME, TEST_FIELD ); Map<TargetStepAttribute, SourceStepField> targetMap = new HashMap<>( 2 ); targetMap.put( unavailableTargetAttr, sourceStep ); targetMap.put( availableTargetAttr, sourceStep ); StepMetaInterface smi = new InjectableTestStepMeta(); TransMeta transMeta = mockSingleStepTransMeta( targetStepName, smi ); Set<TargetStepAttribute> unavailable = MetaInject.getUnavailableTargetKeys( targetMap, transMeta, Collections.<TargetStepAttribute>emptySet() ); assertEquals( 1, unavailable.size() ); assertTrue( unavailable.contains( unavailableTargetAttr ) ); } @Test public void testStepChangeListener() throws Exception { MetaInjectMeta mim = new MetaInjectMeta(); StepMeta sm = new StepMeta( "testStep", mim ); try { transMeta.addOrReplaceStep( sm ); } catch ( Exception ex ) { fail(); } } private TransMeta mockSingleStepTransMeta( final String targetStepName, StepMetaInterface smi ) { StepMeta stepMeta = mock( StepMeta.class ); when( stepMeta.getStepMetaInterface() ).thenReturn( smi ); when( stepMeta.getName() ).thenReturn( targetStepName ); TransMeta transMeta = mock( TransMeta.class ); when( transMeta.getUsedSteps() ).thenReturn( Collections.singletonList( stepMeta ) ); return transMeta; } @InjectionSupported( localizationPrefix = "", groups = "groups" ) private static class InjectableTestStepMeta extends BaseStepMeta implements StepMetaInterface { @Injection( name = "THERE" ) private String there; @Override public void setDefault() { } @Override public StepInterface getStep( StepMeta stepMeta, StepDataInterface stepDataInterface, int copyNr, TransMeta transMeta, Trans trans ) { return null; } @Override public StepDataInterface getStepData() { return null; } } private static RowMetaAndData createRowMetaAndData( ValueMetaInterface valueMeta, Object data ) { RowMetaAndData row = new RowMetaAndData(); RowMeta rowMeta = new RowMeta(); rowMeta.addValueMeta( valueMeta ); row.setRowMeta( rowMeta ); row.setData( new Object[] { data } ); return row; } }