/* * ! ****************************************************************************** * * 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.ael.adapters; import org.pentaho.di.core.exception.KettleException; import org.pentaho.di.core.logging.LogChannelInterface; import org.pentaho.di.engine.api.Engine; import org.pentaho.di.engine.api.ExecutionContext; import org.pentaho.di.engine.api.ExecutionResult; import org.pentaho.di.engine.api.events.PDIEvent; import org.pentaho.di.engine.api.model.Operation; import org.pentaho.di.engine.api.model.Transformation; import org.pentaho.di.engine.api.reporting.LogEntry; import org.pentaho.di.engine.api.reporting.LogLevel; import org.pentaho.di.engine.model.ActingPrincipal; import org.pentaho.di.engine.api.reporting.Status; import org.pentaho.di.trans.RowProducer; import org.pentaho.di.trans.Trans; import org.pentaho.di.trans.TransMeta; import org.pentaho.di.trans.step.StepInterface; import org.pentaho.di.trans.step.StepMeta; import org.pentaho.di.trans.step.StepMetaDataCombi; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import java.security.Principal; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.function.Function; import static java.util.stream.Collectors.toMap; /** * Created by nbaker on 1/24/17. */ public class TransEngineAdapter extends Trans { public static final String ANONYMOUS_PRINCIPAL = "anonymous"; private final Transformation transformation; private final ExecutionContext executionContext; private CompletableFuture<ExecutionResult> executionResultFuture; public static final Map<org.pentaho.di.core.logging.LogLevel, LogLevel> LEVEL_MAP = new HashMap<>(); static { LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.BASIC, LogLevel.BASIC ); LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.DEBUG, LogLevel.DEBUG ); LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.DETAILED, LogLevel.DETAILED ); LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.ERROR, LogLevel.ERROR ); LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.MINIMAL, LogLevel.MINIMAL ); LEVEL_MAP.put( org.pentaho.di.core.logging.LogLevel.ROWLEVEL, LogLevel.TRACE ); } public TransEngineAdapter( Engine engine, TransMeta transMeta ) { transformation = TransMetaConverter.convert( transMeta ); executionContext = engine.prepare( transformation ); executionContext.setActingPrincipal( getActingPrincipal( transMeta ) ); this.transMeta = transMeta; } @Override public void setLogLevel( org.pentaho.di.core.logging.LogLevel logLogLevel ) { executionContext.setLoggingLogLevel( LEVEL_MAP.getOrDefault( logLogLevel, LogLevel.MINIMAL ) ); } @Override public void killAll() { throw new UnsupportedOperationException( "Not yet implemented" ); } @Override public void prepareExecution( String[] arguments ) throws KettleException { activateParameters(); transMeta.activateParameters(); transMeta.setInternalKettleVariables(); Map<String, Object> env = Arrays.stream( transMeta.listVariables() ) .collect( toMap( Function.identity(), transMeta::getVariable ) ); executionContext.setEnvironment( env ); setSteps( new ArrayList<>( opsToSteps() ) ); wireStatusToTransListeners(); subscribeToOpLogging(); executionContext.subscribe( transformation, LogEntry.class, new Subscriber<PDIEvent<Transformation, LogEntry>>() { @Override public void onSubscribe( Subscription subscription ) { subscription.request( Long.MAX_VALUE ); } @Override public void onNext( PDIEvent<Transformation, LogEntry> event ) { LogEntry data = event.getData(); logToChannel( getLogChannel(), data ); } @Override public void onError( Throwable throwable ) { } @Override public void onComplete() { } } ); setReadyToStart( true ); } private void logToChannel( LogChannelInterface logChannel, LogEntry data ) { LogLevel logLogLevel = data.getLogLogLevel(); switch ( logLogLevel ) { case ERROR: logChannel.logError( data.getMessage() ); break; case MINIMAL: logChannel.logMinimal( data.getMessage() ); break; case BASIC: logChannel.logBasic( data.getMessage() ); break; case DETAILED: logChannel.logDetailed( data.getMessage() ); break; case DEBUG: logChannel.logDebug( data.getMessage() ); break; case TRACE: logChannel.logRowlevel( data.getMessage() ); break; } } private void subscribeToOpLogging() { transformation.getOperations().forEach( operation -> { executionContext.subscribe( operation, LogEntry.class, logEntry -> { StepInterface stepInterface = findStepInterface( operation.getId(), 0 ); if ( stepInterface != null ) { LogChannelInterface logChannel = stepInterface.getLogChannel(); logToChannel( logChannel, logEntry ); } else { // Could not find step, log at transformation level instead logToChannel( getLogChannel(), logEntry ); } } ); } ); } private void wireStatusToTransListeners() { executionContext.subscribe( transformation, Status.class, new Subscriber<PDIEvent<Transformation, Status>>() { @Override public void onSubscribe( Subscription s ) { s.request( Long.MAX_VALUE ); } @Override public void onNext( PDIEvent<Transformation, Status> transStatusEvent ) { addStepPerformanceSnapShot(); getTransListeners().forEach( l -> { try { switch ( transStatusEvent.getData() ) { case RUNNING: l.transStarted( TransEngineAdapter.this ); l.transActive( TransEngineAdapter.this ); break; case PAUSED: break; case STOPPED: break; case FAILED: case FINISHED: l.transFinished( TransEngineAdapter.this ); setFinished( true ); break; } } catch ( KettleException e ) { throw new RuntimeException( e ); } } ); } @Override public void onError( Throwable t ) { getLogChannel().logError( "Error Executing Transformation", t ); setFinished( true ); // emit error on all steps getSteps().stream().map( stepMetaDataCombi -> stepMetaDataCombi.step ).forEach( step -> { step.setStopped( true ); step.setRunning( false ); } ); getTransListeners().forEach( l -> { try { l.transFinished( TransEngineAdapter.this ); } catch ( KettleException e ) { getLogChannel().logError( "Error notifying trans listener", e ); } } ); } @Override public void onComplete() { setFinished( true ); getTransListeners().forEach( l -> { try { l.transFinished( TransEngineAdapter.this ); } catch ( KettleException e ) { getLogChannel().logError( "Error notifying trans listener", e ); } } ); } } ); } private Collection<StepMetaDataCombi> opsToSteps() { Map<Operation, StepMetaDataCombi> operationToCombi = transformation.getOperations().stream() .collect( toMap( Function.identity(), op -> { StepMetaDataCombi combi = new StepMetaDataCombi(); combi.stepMeta = StepMeta.fromXml( (String) op.getConfig().get( TransMetaConverter.STEP_META_CONF_KEY ) ); combi.data = new StepDataInterfaceEngineAdapter( op, executionContext ); combi.step = new StepInterfaceEngineAdapter( op, executionContext, combi.stepMeta, transMeta, combi.data, this ); combi.meta = combi.stepMeta.getStepMetaInterface(); combi.stepname = combi.stepMeta.getName(); return combi; } ) ); return operationToCombi.values(); } @Override public void startThreads() throws KettleException { executionResultFuture = executionContext.execute(); } @Override public void waitUntilFinished() { try { ExecutionResult result = executionResultFuture.get(); } catch ( InterruptedException e ) { throw new RuntimeException( "Waiting for transformation to be finished interrupted!", e ); } catch ( ExecutionException e ) { throw new RuntimeException( "Error executing Transformation or waiting for it to stop", e ); } } // ======================== May want to implement ================================= // @Override public RowProducer addRowProducer( String stepname, int copynr ) throws KettleException { throw new UnsupportedOperationException( "Not yet implemented" ); } private Principal getActingPrincipal( TransMeta transMeta ) { if ( transMeta.getRepository() == null || transMeta.getRepository().getUserInfo() == null ) { return new ActingPrincipal( ANONYMOUS_PRINCIPAL ); } return new ActingPrincipal( transMeta.getRepository().getUserInfo().getName() ); } }