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, Object> conditionalStep = (ConditionalStep, Object>) 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