Package org.pentaho.platform.scheduler2.quartz

Source Code of org.pentaho.platform.scheduler2.quartz.ActionAdapterQuartzJob$LoggingJobExecutionException

/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation..  All rights reserved.
*/

package org.pentaho.platform.scheduler2.quartz;

import java.io.OutputStream;
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.Callable;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.api.action.IAction;
import org.pentaho.platform.api.action.IStreamingAction;
import org.pentaho.platform.api.action.IVarArgsAction;
import org.pentaho.platform.api.engine.IPluginManager;
import org.pentaho.platform.api.engine.PluginBeanException;
import org.pentaho.platform.api.repository2.unified.ISourcesStreamEvents;
import org.pentaho.platform.api.repository2.unified.IStreamListener;
import org.pentaho.platform.api.repository2.unified.IUnifiedRepository;
import org.pentaho.platform.api.repository2.unified.RepositoryFile;
import org.pentaho.platform.api.repository2.unified.data.simple.SimpleRepositoryFileData;
import org.pentaho.platform.api.scheduler2.IBackgroundExecutionStreamProvider;
import org.pentaho.platform.api.scheduler2.IBlockoutManager;
import org.pentaho.platform.api.scheduler2.IJobTrigger;
import org.pentaho.platform.api.scheduler2.IScheduler;
import org.pentaho.platform.api.scheduler2.SimpleJobTrigger;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.engine.security.SecurityHelper;
import org.pentaho.platform.engine.services.solution.ActionSequenceCompatibilityFormatter;
import org.pentaho.platform.scheduler2.blockout.BlockoutAction;
import org.pentaho.platform.scheduler2.email.Emailer;
import org.pentaho.platform.scheduler2.messsages.Messages;
import org.pentaho.platform.util.beans.ActionHarness;
import org.pentaho.platform.util.messages.LocaleHelper;
import org.pentaho.platform.util.web.MimeHelper;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
* A Quartz job that is responsible for executing the {@link IAction} referred to in the job context.
*
* @author aphillips
*/
public class ActionAdapterQuartzJob implements Job {

  static final Log log = LogFactory.getLog( ActionAdapterQuartzJob.class );
  private static final long RETRY_COUNT = 6;
  private static final long RETRY_SLEEP_AMOUNT = 10000;

  private String outputFilePath = null;
  private Object lock = new Object();
 
  protected Class<?> resolveClass( JobDataMap jobDataMap ) throws PluginBeanException, JobExecutionException {
    String actionClass = jobDataMap.getString( QuartzScheduler.RESERVEDMAPKEY_ACTIONCLASS );
    String actionId = jobDataMap.getString( QuartzScheduler.RESERVEDMAPKEY_ACTIONID );

    Class<?> clazz = null;

    if ( StringUtils.isEmpty( actionId ) && StringUtils.isEmpty( actionClass ) ) {
      throw new LoggingJobExecutionException( Messages.getInstance().getErrorString(
          "ActionAdapterQuartzJob.ERROR_0001_REQUIRED_PARAM_MISSING", //$NON-NLS-1$
          QuartzScheduler.RESERVEDMAPKEY_ACTIONCLASS, QuartzScheduler.RESERVEDMAPKEY_ACTIONID ) );
    }

    for ( int i = 0; i < RETRY_COUNT; i++ ) {
      try {
        if ( !StringUtils.isEmpty( actionId ) ) {
          IPluginManager pluginManager = PentahoSystem.get( IPluginManager.class );
          clazz = pluginManager.loadClass( actionId );
          return clazz;
        } else if ( !StringUtils.isEmpty( actionClass ) ) {
          clazz = Class.forName( actionClass );
          return clazz;
        }
      } catch ( Throwable t ) {
        try {
          Thread.sleep( RETRY_SLEEP_AMOUNT );
        } catch ( InterruptedException ie ) {
          log.info( ie.getMessage(), ie );
        }
      }
    }

    // we have failed to locate the class for the actionClass
    // and we're giving up waiting for it to become available/registered
    // which can typically happen at system startup
    throw new LoggingJobExecutionException( Messages.getInstance().getErrorString(
        "ActionAdapterQuartzJob.ERROR_0002_FAILED_TO_CREATE_ACTION", //$NON-NLS-1$
        StringUtils.isEmpty( actionId ) ? actionClass : actionId ) );
  }

  @SuppressWarnings( "unchecked" )
  public void execute( JobExecutionContext context ) throws JobExecutionException {
    JobDataMap jobDataMap = context.getMergedJobDataMap();
    String actionUser = jobDataMap.getString( QuartzScheduler.RESERVEDMAPKEY_ACTIONUSER );

    Object bean;
    Class<?> actionClass = null;
    try {
      actionClass = resolveClass( jobDataMap );
      bean = actionClass.newInstance();
    } catch ( Exception e ) {
      throw new LoggingJobExecutionException( Messages.getInstance().getErrorString(
          "ActionAdapterQuartzJob.ERROR_0002_FAILED_TO_CREATE_ACTION", //$NON-NLS-1$
          ( actionClass == null ) ? "unknown" : actionClass.getName() ), e ); //$NON-NLS-1$
    }

    if ( !( bean instanceof IAction ) ) {
      throw new LoggingJobExecutionException( Messages.getInstance().getErrorString(
          "ActionAdapterQuartzJob.ERROR_0003_ACTION_WRONG_TYPE", actionClass.getName(), //$NON-NLS-1$
          IAction.class.getName() ) );
    }

    final IAction actionBean = (IAction) bean;

    try {
      invokeAction( actionBean, actionUser, context, jobDataMap.getWrappedMap() );

    } catch ( Throwable t ) {
      // ensure that scheduler thread isn't blocked on lock
      synchronized ( lock ) {
        lock.notifyAll();
      }

      // We should not distinguish between checked and unchecked exceptions here. All job execution failures
      // should result in a rethrow of a quartz exception
      throw new LoggingJobExecutionException( Messages.getInstance().getErrorString(
          "ActionAdapterQuartzJob.ERROR_0004_ACTION_FAILED", actionBean //$NON-NLS-1$
              .getClass().getName() ), t );
    }
  }

  protected void invokeAction( final IAction actionBean, final String actionUser, final JobExecutionContext context,
      final Map<String, Serializable> params ) throws Exception {

    final IScheduler scheduler = PentahoSystem.getObjectFactory().get( IScheduler.class, "IScheduler2", null );
    final Map<String, Serializable> jobParams = new HashMap<String, Serializable>( params ); // shallow copy

    // remove the scheduling infrastructure properties
    params.remove( QuartzScheduler.RESERVEDMAPKEY_ACTIONCLASS );
    params.remove( QuartzScheduler.RESERVEDMAPKEY_ACTIONID );
    params.remove( QuartzScheduler.RESERVEDMAPKEY_ACTIONUSER );
    final IBackgroundExecutionStreamProvider streamProvider =
        (IBackgroundExecutionStreamProvider) params.get( QuartzScheduler.RESERVEDMAPKEY_STREAMPROVIDER );
    params.remove( QuartzScheduler.RESERVEDMAPKEY_STREAMPROVIDER );
    params.remove( QuartzScheduler.RESERVEDMAPKEY_UIPASSPARAM );
    // The scheduled_fire_time is useful only to the blockoutAction see PDI-10171
    if ( actionBean instanceof BlockoutAction ) {
      params.put( IBlockoutManager.SCHEDULED_FIRE_TIME, context.getScheduledFireTime() );
    }

    if ( log.isDebugEnabled() ) {
      log.debug( MessageFormat.format(
          "Scheduling system invoking action {0} as user {1} with params [ {2} ]", actionBean //$NON-NLS-1$
              .getClass().getName(), actionUser, QuartzScheduler.prettyPrintMap( params ) ) );
    }

    Callable<Boolean> actionBeanRunner = new Callable<Boolean>() {

      public Boolean call() throws Exception {
        LocaleHelper.setLocaleOverride( (Locale) params.get( LocaleHelper.USER_LOCALE_PARAM ) );
        // sync job params to the action bean
        ActionHarness actionHarness = new ActionHarness( actionBean );
        boolean updateJob = false;

        final Map<String, Object> actionParams = new HashMap<String, Object>();
        actionParams.putAll( params );
        if ( streamProvider != null ) {
          actionParams.put( "inputStream", streamProvider.getInputStream() );
        }
        actionHarness.setValues( actionParams, new ActionSequenceCompatibilityFormatter() );

        if ( actionBean instanceof IVarArgsAction ) {
          actionParams.remove( "inputStream" );
          actionParams.remove( "outputStream" );
          ( (IVarArgsAction) actionBean ).setVarArgs( actionParams );
        }

        boolean waitForFileCreated = false;
        OutputStream stream = null;
       
        if ( streamProvider != null ) {
          actionParams.remove( "inputStream" );
          if ( actionBean instanceof IStreamingAction ) {
            streamProvider.setStreamingAction( (IStreamingAction) actionBean );
          }

          // BISERVER-9414 - validate that output path still exist
          SchedulerOutputPathResolver resolver =
              new SchedulerOutputPathResolver( streamProvider.getOutputPath(), actionUser );
          String outputPath = resolver.resolveOutputFilePath();
          actionParams.put( "useJcr", Boolean.TRUE );
          actionParams.put( "jcrOutputPath", outputPath.substring( 0, outputPath.lastIndexOf( "/" ) ) );

          if ( !outputPath.equals( streamProvider.getOutputPath() ) ) {
            streamProvider.setOutputFilePath( outputPath ); // set fallback path
            updateJob = true; // job needs to be deleted and recreated with the new output path
          }

          stream = streamProvider.getOutputStream();
          if ( stream instanceof ISourcesStreamEvents ) {
            ( (ISourcesStreamEvents) stream ).addListener( new IStreamListener() {
              public void fileCreated( final String filePath ) {
                synchronized ( lock ) {
                  outputFilePath = filePath;
                  lock.notifyAll();
                }
              }
            } );
            waitForFileCreated = true;
          }
          actionParams.put( "outputStream", stream );
          // The lineage_id is only useful for the metadata and not needed at this level see PDI-10171
          actionParams.remove( QuartzScheduler.RESERVEDMAPKEY_LINEAGE_ID );
          actionHarness.setValues( actionParams );
        }

        actionBean.execute();

        if (stream != null) {
          IOUtils.closeQuietly( stream );
        }

        if ( waitForFileCreated ) {
          synchronized ( lock ) {
            if ( outputFilePath == null ) {
              lock.wait();
            }
          }
          sendEmail( actionParams, params, outputFilePath );
        }
       
        return updateJob;
      }
    };

    boolean requiresUpdate = false;
    if ( ( actionUser == null ) || ( actionUser.equals( "system session" ) ) ) { //$NON-NLS-1$
      // For now, don't try to run quartz jobs as authenticated if the user
      // that created the job is a system user. See PPP-2350
      requiresUpdate = SecurityHelper.getInstance().runAsAnonymous( actionBeanRunner );
    } else {
      try {
        requiresUpdate = SecurityHelper.getInstance().runAsUser( actionUser, actionBeanRunner );
      } catch ( Throwable t ) {
        Object restartFlag = jobParams.get( QuartzScheduler.RESERVEDMAPKEY_RESTART_FLAG );
        if ( restartFlag == null ) {
          final SimpleJobTrigger trigger = new SimpleJobTrigger( new Date(), null, 0, 0 );
          final Class<IAction> iaction = (Class<IAction>) actionBean.getClass();
          // recreate the job in the context of the original creator
          SecurityHelper.getInstance().runAsUser( actionUser, new Callable<Void>() {
            @Override
            public Void call() throws Exception {
              if(streamProvider != null) {
                streamProvider.setStreamingAction( null ); // remove generated content 
              }
              QuartzJobKey jobKey = QuartzJobKey.parse( context.getJobDetail().getName() );
              String jobName = jobKey.getJobName();
              jobParams.put( QuartzScheduler.RESERVEDMAPKEY_RESTART_FLAG, Boolean.TRUE );
              scheduler.createJob( jobName, iaction, jobParams, trigger, streamProvider );
              log.warn( "New RunOnce job created for " + jobName + " -> possible startup synchronization error" );
              return null;
            }
          } );
        } else {
          log.warn( "RunOnce already created, skipping" );
          throw new Exception( t );
        }
      }
    }

    scheduler.fireJobCompleted( actionBean, actionUser, params, streamProvider );

    if ( requiresUpdate ) {
      log.warn( "Output path for job: " + context.getJobDetail().getName() + " has changed. Job requires update" );
      try {
        final IJobTrigger trigger = scheduler.getJob( context.getJobDetail().getName() ).getJobTrigger();
        final Class<IAction> iaction = (Class<IAction>) actionBean.getClass();

        // remove job with outdated/invalid output path
        scheduler.removeJob( context.getJobDetail().getName() );

        // recreate the job in the context of the original creator
        SecurityHelper.getInstance().runAsUser( actionUser, new Callable<Void>() {
          @Override
          public Void call() throws Exception {
            streamProvider.setStreamingAction( null ); // remove generated content
            QuartzJobKey jobKey = QuartzJobKey.parse( context.getJobDetail().getName() );
            String jobName = jobKey.getJobName();
            org.pentaho.platform.api.scheduler2.Job j =
                scheduler.createJob( jobName, iaction, jobParams, trigger, streamProvider );
            log.warn( "New Job: " + j.getJobId() + " created" );
            return null;
          }
        } );
      } catch ( Exception e ) {
        log.error( e.getMessage(), e );
      }
    }

    if ( log.isDebugEnabled() ) {
      log.debug( MessageFormat.format(
          "Scheduling system successfully invoked action {0} as user {1} with params [ {2} ]", actionBean //$NON-NLS-1$
              .getClass().getName(), actionUser, QuartzScheduler.prettyPrintMap( params ) ) );
    }

  }

  private void sendEmail( Map<String, Object> actionParams, Map<String, Serializable> params, String filePath ) {
    try {
      IUnifiedRepository repo = PentahoSystem.get( IUnifiedRepository.class );
      RepositoryFile sourceFile = repo.getFile( filePath );
      // add metadata
      Map<String, Serializable> metadata = repo.getFileMetadata( sourceFile.getId() );
      String lineageId = (String) params.get( QuartzScheduler.RESERVEDMAPKEY_LINEAGE_ID );
      metadata.put( QuartzScheduler.RESERVEDMAPKEY_LINEAGE_ID, lineageId );
      repo.setFileMetadata( sourceFile.getId(), metadata );
      // send email
      SimpleRepositoryFileData data =
          repo.getDataForRead( sourceFile.getId(), SimpleRepositoryFileData.class );     
     
      // if email is setup and we have tos, then do it
      Emailer emailer = new Emailer();
      if ( !emailer.setup() ) {
        // email not configured
        return;
      }
      String to = (String) actionParams.get( "_SCH_EMAIL_TO" );
      String cc = (String) actionParams.get( "_SCH_EMAIL_CC" );
      String bcc = (String) actionParams.get( "_SCH_EMAIL_BCC" );
      if ( ( to == null || "".equals( to ) ) && ( cc == null || "".equals( cc ) )
          && ( bcc == null || "".equals( bcc ) ) ) {
        // no destination
        return;
      }
      emailer.setTo( to );
      emailer.setCc( cc );
      emailer.setBcc( bcc );
      emailer.setAttachment( data.getInputStream() );
      emailer.setAttachmentName( "attachment" );
      String attachmentName = (String) actionParams.get( "_SCH_EMAIL_ATTACHMENT_NAME" );
      if ( attachmentName != null && !"".equals( attachmentName ) ) {
        String path = filePath;
        if ( path.endsWith( ".*" ) ) {
          path = path.replace( ".*", "" );
        }
        String extension = MimeHelper.getExtension( data.getMimeType() );
        if ( extension == null ) {
          extension = ".bin";
        }
        if ( !attachmentName.endsWith( extension ) ) {
          emailer.setAttachmentName( attachmentName + extension );
        } else {
          emailer.setAttachmentName( attachmentName );
        }
      } else if ( data != null ) {
        String path = filePath;
        if ( path.endsWith( ".*" ) ) {
          path = path.replace( ".*", "" );
        }
        String extension = MimeHelper.getExtension( data.getMimeType() );
        if ( extension == null ) {
          extension = ".bin";
        }
        path = path.substring( path.lastIndexOf( "/" ) + 1, path.length() );
        if ( !path.endsWith( extension ) ) {
          emailer.setAttachmentName( path + extension );
        } else {
          emailer.setAttachmentName( path );
        }
      }
      if ( data == null || data.getMimeType() == null || "".equals( data.getMimeType() ) ) {
        emailer.setAttachmentMimeType( "binary/octet-stream" );
      } else {
        emailer.setAttachmentMimeType( data.getMimeType() );
      }
      String subject = (String) actionParams.get( "_SCH_EMAIL_SUBJECT" );
      if ( subject != null && !"".equals( subject ) ) {
        emailer.setSubject( subject );
      } else {
        emailer.setSubject( "Pentaho Scheduler: " + emailer.getAttachmentName() );
      }
      String message = (String) actionParams.get( "_SCH_EMAIL_MESSAGE" );
      if ( subject != null && !"".equals( subject ) ) {
        emailer.setBody( message );
      }
      emailer.send();
    } catch ( Exception e ) {
      log.warn( e.getMessage(), e );
    }     
  }

  class LoggingJobExecutionException extends JobExecutionException {
    private static final long serialVersionUID = -4124907454208034326L;

    public LoggingJobExecutionException( String msg ) {
      super( msg );
      log.error( msg );
    }

    public LoggingJobExecutionException( String msg, Throwable t ) {
      super( msg, t );
      log.error( msg, t );
    }

  }

}
TOP

Related Classes of org.pentaho.platform.scheduler2.quartz.ActionAdapterQuartzJob$LoggingJobExecutionException

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.