All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.ondeck.datapipes.flow.audit.BaseAuditVisitor Maven / Gradle / Ivy

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 OnDeck
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * Except as contained in this notice, the name of a copyright holder shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization of the copyright holder.
 *
 */

package com.ondeck.datapipes.flow.audit;

import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Stack;

import com.google.common.reflect.TypeToken;
import com.ondeck.datapipes.flow.ConditionalStep;
import com.ondeck.datapipes.flow.DataPipeStep;
import com.ondeck.datapipes.flow.DataProviderStep;
import com.ondeck.datapipes.flow.FlowException;
import com.ondeck.datapipes.flow.FlowStep;
import com.ondeck.datapipes.flow.FlowVisitor;
import com.ondeck.datapipes.flow.FunctionStep;
import com.ondeck.datapipes.flow.IntermediaryFlowStep;
import com.ondeck.datapipes.flow.MergeStep;
import com.ondeck.datapipes.flow.MultiStep;

public abstract class BaseAuditVisitor> implements FlowVisitor
{
  // Result of all the steps
  private final Map, Object> stepResults;
  // Configuration of the audit (associates a step with an auditor)
  private final AuditConfiguration auditConfiguration;
  // The top of the stack is the child step of the currently visited step.
  private final Stack auditReports = new Stack<>();
  // Root auditReports (result of the audit of a flow)
  private final LinkedHashSet result = new LinkedHashSet<>();
  // Caches the AuditReport for each step to avoid having to recreate the report if the step
  // has already been visited
  private final Map, AuditReportType> cachedAuditReports = new HashMap<>();
  private final TypeToken auditReportClass;

  public BaseAuditVisitor(Map, Object> stepResults, AuditConfiguration auditConfiguration,
      TypeToken auditReportClass)
  {
    this.stepResults = stepResults;
    this.auditConfiguration = auditConfiguration;
    this.auditReportClass = auditReportClass;
  }

  // If the step has been configured for auditing, create a new AuditReport or retrieve the already existing
  // AuditReport for a step and push it on the stack so parent AuditReports can be added as input of that AuditReport.
  @SuppressWarnings("unchecked")
  private  AuditReportType pushAuditReport(FlowStep step)
  {
    Auditor auditor = auditConfiguration.getAuditor(step);
    AuditReportType newAuditReport = null;
    if (auditor != null)
    {
      newAuditReport = cachedAuditReports.get(step);
      if (newAuditReport == null)
      {
        T stepResult = (T) stepResults.get(step);
        newAuditReport = createAuditReport(auditor, step, stepResult);
        newAuditReport.setExecuted(stepResults.containsKey(step));
        cachedAuditReports.put(step, newAuditReport);
      }
      if (!auditReports.empty())
      {
        auditReports.peek().getInputs().add(newAuditReport);
      }
      auditReports.push(newAuditReport);
    }
    return newAuditReport;
  }

  private  AuditReportType createAuditReport(Auditor auditor, FlowStep step, T stepResult)
  {
    return createAuditReport(auditor, step, stepResult, 0);
  }

  abstract protected  AuditReportType createAuditReport(Auditor auditor, FlowStep step, T stepResult, int index);

  // Remove the AuditReport from the stack, if that AuditReport was the last on the stack add it to the result so
  // it can be returned later.
  private void popAuditReport(AuditReportType auditReport)
  {
    if (auditReport != null)
    {
      auditReports.pop();
      if (auditReports.empty())
      {
        result.add(auditReport);
      }
    }
  }


  @Override
  public  void visit(DataProviderStep step) throws FlowException
  {
    AuditReportType auditReport = pushAuditReport(step);
    popAuditReport(auditReport);
  }

  @Override
  public  void visit(DataPipeStep step) throws FlowException
  {
    AuditReportType auditReport = pushAuditReport(step);
    step.getParentStep().accept(this);
    popAuditReport(auditReport);
  }

  @Override
  public  void visit(FunctionStep step) throws FlowException
  {
    AuditReportType auditReport = pushAuditReport(step);
    step.getParentStep().accept(this);
    popAuditReport(auditReport);
  }

  @Override
  public  void visit(MergeStep step) throws FlowException
  {
    AuditReportType auditReport = pushAuditReport(step);
    for (FlowStep parentStep: step.getParentSteps())
    {
      parentStep.accept(this);
    }
    popAuditReport(auditReport);
  }

  protected AuditReportType instantiateAuditReport()
      throws NoSuchMethodException, InvocationTargetException, IllegalAccessException
  {
    return auditReportClass.constructor(auditReportClass.getRawType().getConstructor()).invoke(null);
  }

  // A MultiStep AuditReport is not going to be part of the audit graph. If it's configured to be audited
  // the substep results will be audited and will be part of the graph.
  @SuppressWarnings("unchecked")
  @Override
  public , T extends Collection> void visit(MultiStep step)
      throws FlowException
  {
    AuditReportType auditReport = null;
    LinkedHashSet subAuditReports = null;
    Auditor auditor = auditConfiguration.getAuditor(step);
    if (auditor != null)
    {
      auditReport = cachedAuditReports.get(step);
      if (auditReport == null)
      {
        try
        {
          auditReport = instantiateAuditReport();
        }
        catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e)
        {
          throw new FlowException(e);
        }
        T stepResult = (T) stepResults.get(step);
        subAuditReports = new LinkedHashSet<>();
        auditReport.setData(subAuditReports);
        auditReport.setType(auditor.getType());
        auditReport.setExecuted(stepResults.containsKey(step));
        if (stepResult != null)
        {
          int index = 0;
          for (O subResult : stepResult)
          {
            AuditReportType subAuditReport = createAuditReport(auditor, step, subResult, index);
            subAuditReport.setExecuted(true);
            subAuditReports.add(subAuditReport);
            ++index;
          }
        }
        cachedAuditReports.put(step, auditReport);
      }
      else
      {
        subAuditReports = (LinkedHashSet) auditReport.getData();
      }
    }
    if (auditReport != null)
    {
      if (!auditReports.empty() && subAuditReports != null)
      {
        // if there is an AuditReport on the stack, set its input to be the AuditReports generated by the substep.
        auditReports.peek().getInputs().addAll(subAuditReports);
      }
      // push the MultiStep AuditReport on the stack so it can collect the AuditReports of its parent.
      auditReports.push(auditReport);
    }
    step.getParentStep().accept(this);
    if (auditReport != null)
    {
      // remove the MultiStep AuditReport from the stack and set the substep inputs to be the inputs of the MultiStep
      // AuditReport
      auditReports.pop();

      if (stepResults.get(step) == null)
      {
        auditReport.setData(null);
        if (auditReports.empty())
        {
          result.add(auditReport);
        }
        else
        {
          auditReports.peek().getInputs().add(auditReport);
        }
      }
      else
      {
        LinkedHashSet inputs = auditReport.getInputs();
        Iterator inputsIterator = inputs.iterator();

        boolean subStepInputIsAMultiStep = isAuditedParentStepAMultiStep(step, subAuditReports.size());

        for (AuditReportType subAuditReport : subAuditReports)
        {
          if (subStepInputIsAMultiStep)
          {
            if (inputsIterator.hasNext())
            {
              subAuditReport.getInputs().add(inputsIterator.next());
            }
          }
          else
          {
            subAuditReport.setInputs(inputs);
          }
        }

        if (auditReports.empty())
        {
          result.addAll(subAuditReports);
        }
      }
    }
  }

  private boolean isAuditedParentStepAMultiStep(IntermediaryFlowStep step, int size)
  {
    IntermediaryFlowStep currentStep = step;
    while (currentStep.getParentStep() != null)
    {
      // if we encounter a conditional step we need to determine which was the actual parent
      if (currentStep.getParentStep() instanceof ConditionalStep)
      {
        @SuppressWarnings("unchecked")
        ConditionalStep conditionalStep = (ConditionalStep) currentStep.getParentStep();
        if (auditConfiguration.getAuditor(conditionalStep) != null)
        {
          return false;
        }
        Object conditionResult = stepResults.get(conditionalStep.getConditionalStep());
        FlowStep parentStep = conditionalStep.execute(conditionResult);
        if (parentStep != null && parentStep instanceof IntermediaryFlowStep)
        {
          currentStep = (IntermediaryFlowStep) parentStep;
        }
        else
        {
          return false;
        }
      }
      else if (currentStep.getParentStep() instanceof IntermediaryFlowStep)
      {
        currentStep = (IntermediaryFlowStep) currentStep.getParentStep();
      }
      else
      {
        return false;
      }

      // we're on an IntermediaryStep let's check that its return type is a Collection of the same size as the multistep
      if (Collection.class.isAssignableFrom(currentStep.getReturnType().getRawType()))
      {
        Collection col = (Collection) stepResults.get(currentStep);
        if (col == null || col.size() != size)
        {
          return false;
        }
      }
      if (auditConfiguration.getAuditor(currentStep) != null)
      {
        return currentStep instanceof MultiStep;
      }
    }
    return false;
  }

  @SuppressWarnings("unchecked")
  @Override
  public  void visit(ConditionalStep step) throws FlowException
  {
    AuditReportType auditReport = pushAuditReport(step);
    U conditionResult = (U) stepResults.get(step.getConditionalStep());
    FlowStep parentStep = step.execute(conditionResult);
    if (parentStep != null)
    {
      parentStep.accept(this);
    }
    popAuditReport(auditReport);
  }

  public Collection audit(FlowStep step) throws FlowException
  {
    step.accept(this);
    return result;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy