/*! ******************************************************************************
*
* 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.trans.steps.loadsave;
import static org.junit.Assert.assertNotSame;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.pentaho.di.base.LoadSaveBase;
import org.pentaho.di.core.database.DatabaseMeta;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.repository.Repository;
import org.pentaho.di.trans.step.StepMetaInterface;
import org.pentaho.di.trans.steps.loadsave.getter.Getter;
import org.pentaho.di.trans.steps.loadsave.initializer.InitializerInterface;
import org.pentaho.di.trans.steps.loadsave.setter.Setter;
import org.pentaho.di.trans.steps.loadsave.validator.DatabaseMetaLoadSaveValidator;
import org.pentaho.di.trans.steps.loadsave.validator.FieldLoadSaveValidator;
import org.pentaho.di.trans.steps.loadsave.validator.FieldLoadSaveValidatorFactory;
import org.pentaho.metastore.api.IMetaStore;
public class LoadSaveTester<T extends StepMetaInterface> extends LoadSaveBase<T> {
public LoadSaveTester( Class<T> clazz,
List<String> commonAttributes, List<String> xmlAttributes, List<String> repoAttributes,
Map<String, String> getterMap, Map<String, String> setterMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorTypeMap,
InitializerInterface<T> metaInitializerIFace ) {
super( clazz, commonAttributes, xmlAttributes, repoAttributes, getterMap, setterMap,
fieldLoadSaveValidatorAttributeMap, fieldLoadSaveValidatorTypeMap, metaInitializerIFace );
}
public LoadSaveTester( Class<T> clazz,
List<String> commonAttributes, List<String> xmlAttributes, List<String> repoAttributes,
Map<String, String> getterMap, Map<String, String> setterMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorTypeMap ) {
this( clazz, commonAttributes, xmlAttributes, repoAttributes, getterMap, setterMap,
fieldLoadSaveValidatorAttributeMap, fieldLoadSaveValidatorTypeMap, null );
}
public LoadSaveTester( Class<T> clazz, List<String> commonAttributes,
Map<String, String> getterMap, Map<String, String> setterMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorTypeMap ) {
this( clazz, commonAttributes, new ArrayList<String>(), new ArrayList<String>(), getterMap, setterMap,
fieldLoadSaveValidatorAttributeMap, fieldLoadSaveValidatorTypeMap );
}
public LoadSaveTester( Class<T> clazz, List<String> commonAttributes,
Map<String, String> getterMap, Map<String, String> setterMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorAttributeMap,
Map<String, FieldLoadSaveValidator<?>> fieldLoadSaveValidatorTypeMap,
InitializerInterface<T> metaInitializerIFace ) {
this( clazz, commonAttributes, new ArrayList<String>(), new ArrayList<String>(), getterMap, setterMap,
fieldLoadSaveValidatorAttributeMap, fieldLoadSaveValidatorTypeMap, metaInitializerIFace );
}
public LoadSaveTester( Class<T> clazz, List<String> commonAttributes,
List<String> xmlAttributes, List<String> repoAttributes,
Map<String, String> getterMap, Map<String, String> setterMap ) {
this( clazz, commonAttributes, xmlAttributes, repoAttributes, getterMap, setterMap,
new HashMap<String, FieldLoadSaveValidator<?>>(), new HashMap<String, FieldLoadSaveValidator<?>>() );
}
public LoadSaveTester( Class<T> clazz, List<String> commonAttributes,
Map<String, String> getterMap, Map<String, String> setterMap ) {
this( clazz, commonAttributes, new ArrayList<String>(), new ArrayList<String>(), getterMap, setterMap,
new HashMap<String, FieldLoadSaveValidator<?>>(), new HashMap<String, FieldLoadSaveValidator<?>>() );
}
public LoadSaveTester( Class<T> clazz, List<String> commonAttributes ) {
this( clazz, commonAttributes, new HashMap<String, String>(), new HashMap<String, String>() );
}
public FieldLoadSaveValidatorFactory getFieldLoadSaveValidatorFactory() {
return fieldLoadSaveValidatorFactory;
}
@Override
@SuppressWarnings( "unchecked" )
protected Map<String, FieldLoadSaveValidator<?>> createValidatorMapAndInvokeSetters( List<String> attributes,
T metaToSave ) {
Map<String, FieldLoadSaveValidator<?>> validatorMap = new HashMap<String, FieldLoadSaveValidator<?>>();
for ( String attribute : attributes ) {
Getter<?> getter = manipulator.getGetter( attribute );
@SuppressWarnings( "rawtypes" )
Setter setter = manipulator.getSetter( attribute );
FieldLoadSaveValidator<?> validator = fieldLoadSaveValidatorFactory.createValidator( getter );
try {
Object testValue = validator.getTestObject();
setter.set( metaToSave, testValue );
if ( validator instanceof DatabaseMetaLoadSaveValidator ) {
addDatabase( (DatabaseMeta) testValue );
validateStepUsesDatabaseMeta( metaToSave, (DatabaseMeta) testValue );
}
} catch ( Exception e ) {
throw new RuntimeException( "Unable to invoke setter for " + attribute, e );
}
validatorMap.put( attribute, validator );
}
return validatorMap;
}
private void validateStepUsesDatabaseMeta( T metaToSave, DatabaseMeta dbMeta )
throws KettleException {
// If a step makes use of a DatabaseMeta for configuration, it needs to report the usage
DatabaseMeta[] usedConnections = metaToSave.getUsedDatabaseConnections();
if ( usedConnections == null || usedConnections.length <= 0
|| !Arrays.asList( usedConnections ).contains( dbMeta ) ) {
throw new KettleException( "The step did not report a DatabaseMeta in getUsedDatabaseConnections()" );
}
}
@Override
protected void validateLoadedMeta( List<String> attributes, Map<String, FieldLoadSaveValidator<?>> validatorMap,
T metaSaved, T metaLoaded ) {
for ( String attribute : attributes ) {
try {
Getter<?> getterMethod = manipulator.getGetter( attribute );
Object originalValue = getterMethod.get( metaSaved );
Object value = getterMethod.get( metaLoaded );
FieldLoadSaveValidator<?> validator = validatorMap.get( attribute );
Method[] validatorMethods = validator.getClass().getMethods();
Method validatorMethod = null;
for ( Method method : validatorMethods ) {
if ( "validateTestObject".equals( method.getName() ) ) {
Class<?>[] types = method.getParameterTypes();
if ( types.length == 2 ) {
if ( types[1] == Object.class
&& ( originalValue == null || types[0].isAssignableFrom( originalValue.getClass() ) ) ) {
validatorMethod = method;
break;
}
}
}
}
if ( validatorMethod == null ) {
throw new RuntimeException( "Couldn't find proper validateTestObject method on "
+ validator.getClass().getCanonicalName() );
}
if ( !( (Boolean) validatorMethod.invoke( validator, originalValue, value ) ) ) {
throw new KettleException( "Attribute " + attribute + " started with value "
+ validatorMap.get( attribute ).getTestObject() + " ended with value " + value );
}
} catch ( Exception e ) {
throw new RuntimeException( "Error validating " + attribute, e );
}
}
}
public void testSerialization() throws KettleException {
testXmlRoundTrip();
testRepoRoundTrip();
testClone();
testMixedXmlRepoRoundTrip();
}
@SuppressWarnings( { "deprecation", "unchecked" } )
protected void testClone() throws KettleException {
T metaToSave = createMeta();
if ( initializer != null ) {
initializer.modify( metaToSave );
}
Map<String, FieldLoadSaveValidator<?>> validatorMap =
createValidatorMapAndInvokeSetters( xmlAttributes, metaToSave );
T metaLoaded = (T) metaToSave.clone();
assertNotSame( metaToSave, metaLoaded );
validateLoadedMeta( xmlAttributes, validatorMap, metaToSave, metaLoaded );
validateLoadedMeta( repoAttributes, validatorMap, metaToSave, metaLoaded );
}
/**
* @deprecated the {@link #testSerialization()} method should be used instead,
* as additional tests may be added in the future to cover other
* topics related to step serialization
* @throws KettleException
*/
@Deprecated
// TODO Change method visibility to protected
public void testXmlRoundTrip() throws KettleException {
T metaToSave = createMeta();
if ( initializer != null ) {
initializer.modify( metaToSave );
}
Map<String, FieldLoadSaveValidator<?>> validatorMap =
createValidatorMapAndInvokeSetters( xmlAttributes, metaToSave );
T metaLoaded = createMeta();
String xml = "<step>" + metaToSave.getXML() + "</step>";
InputStream is = new ByteArrayInputStream( xml.getBytes() );
metaLoaded.loadXML( XMLHandler.getSubNode( XMLHandler.loadXMLFile( is, null, false, false ), "step" ),
databases, (IMetaStore) null );
validateLoadedMeta( xmlAttributes, validatorMap, metaToSave, metaLoaded );
// TODO Remove after method visibility changed, it should be called in testSerialization
testClone();
}
/**
* @deprecated the {@link #testSerialization()} method should be used instead,
* as additional tests may be added in the future to cover other
* topics related to step serialization
* @throws KettleException
*/
@Deprecated
// TODO Change method visibility to protected
public void testRepoRoundTrip() throws KettleException {
T metaToSave = createMeta();
if ( initializer != null ) {
initializer.modify( metaToSave );
}
Map<String, FieldLoadSaveValidator<?>> validatorMap =
createValidatorMapAndInvokeSetters( repoAttributes, metaToSave );
T metaLoaded = createMeta();
Repository rep = new MemoryRepository();
metaToSave.saveRep( rep, null, null, null );
metaLoaded.readRep( rep, (IMetaStore) null, null, databases );
validateLoadedMeta( repoAttributes, validatorMap, metaToSave, metaLoaded );
}
@SuppressWarnings( "deprecation" )
protected void testMixedXmlRepoRoundTrip() throws KettleException {
T metaToSave = createMeta();
if ( initializer != null ) {
initializer.modify( metaToSave );
}
Map<String, FieldLoadSaveValidator<?>> validatorMap =
createValidatorMapAndInvokeSetters( repoAttributes, metaToSave );
T metaRepoLoaded = createMeta();
Repository rep = new MemoryRepository();
metaToSave.saveRep( rep, null, null, null );
metaRepoLoaded.readRep( rep, (IMetaStore) null, null, databases );
String xml = "<step>" + metaRepoLoaded.getXML() + "</step>";
InputStream is = new ByteArrayInputStream( xml.getBytes() );
T metaXMLLoaded = createMeta();
metaXMLLoaded.loadXML( XMLHandler.getSubNode( XMLHandler.loadXMLFile( is, null, false, false ), "step" ),
databases, (IMetaStore) null );
validateLoadedMeta( xmlAttributes, validatorMap, metaToSave, metaXMLLoaded );
}
}