org.springframework.batch.core.scope.context.StepContext.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.batch.core.scope.context.StepContext.java

Source

/*
 * Copyright 2006-2019 the original author or authors.
 *
 * 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
 *
 *      https://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.springframework.batch.core.scope.context;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;

import org.springframework.batch.core.JobInstance;
import org.springframework.batch.core.JobParameter;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.UnexpectedJobExecutionException;
import org.springframework.batch.core.jsr.configuration.support.BatchPropertyContext;
import org.springframework.batch.core.scope.StepScope;
import org.springframework.batch.item.ExecutionContext;
import org.springframework.batch.repeat.context.SynchronizedAttributeAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * A context object that can be used to interrogate the current
 * {@link StepExecution} and some of its associated properties using expressions
 * based on bean paths. Has public getters for the step execution and
 * convenience methods for accessing commonly used properties like the
 * {@link ExecutionContext} associated with the step or its enclosing job
 * execution.
 *
 * @author Dave Syer
 * @author Michael Minella
 * @author Mahmoud Ben Hassine
 * @author Nicolas Widart
 *
 */
public class StepContext extends SynchronizedAttributeAccessor {

    private StepExecution stepExecution;

    private Map<String, Set<Runnable>> callbacks = new HashMap<>();

    private BatchPropertyContext propertyContext = null;

    /**
     * Create a new instance of {@link StepContext} for this
     * {@link StepExecution}.
     *
     * @param stepExecution a step execution
     */
    public StepContext(StepExecution stepExecution) {
        super();
        Assert.notNull(stepExecution, "A StepContext must have a non-null StepExecution");
        this.stepExecution = stepExecution;
    }

    public StepContext(StepExecution stepExecution, BatchPropertyContext propertyContext) {
        super();
        Assert.notNull(stepExecution, "A StepContext must have a non-null StepExecution");
        this.stepExecution = stepExecution;
        this.propertyContext = propertyContext;
    }

    /**
     * Convenient accessor for current step name identifier. Usually this is the
     * same as the bean name of the step that is executing (but might not be
     * e.g. in a partition).
     *
     * @return the step name identifier of the current {@link StepExecution}
     */
    public String getStepName() {
        return stepExecution.getStepName();
    }

    /**
     * Convenient accessor for current job name identifier.
     *
     * @return the job name identifier of the enclosing {@link JobInstance}
     * associated with the current {@link StepExecution}
     */
    public String getJobName() {
        Assert.state(stepExecution.getJobExecution() != null, "StepExecution does not have a JobExecution");
        Assert.state(stepExecution.getJobExecution().getJobInstance() != null,
                "StepExecution does not have a JobInstance");
        return stepExecution.getJobExecution().getJobInstance().getJobName();
    }

    /**
     * Convenient accessor for current {@link JobInstance} identifier.
     *
     * @return the identifier of the enclosing {@link JobInstance}
     * associated with the current {@link StepExecution}
     */
    public Long getJobInstanceId() {
        Assert.state(stepExecution.getJobExecution() != null, "StepExecution does not have a JobExecution");
        Assert.state(stepExecution.getJobExecution().getJobInstance() != null,
                "StepExecution does not have a JobInstance");
        return stepExecution.getJobExecution().getJobInstance().getInstanceId();
    }

    /**
     * Convenient accessor for System properties to make it easy to access them
     * from placeholder expressions.
     *
     * @return the current System properties
     */
    public Properties getSystemProperties() {
        return System.getProperties();
    }

    /**
     * @return a map containing the items from the step {@link ExecutionContext}
     */
    public Map<String, Object> getStepExecutionContext() {
        Map<String, Object> result = new HashMap<>();
        for (Entry<String, Object> entry : stepExecution.getExecutionContext().entrySet()) {
            result.put(entry.getKey(), entry.getValue());
        }
        return Collections.unmodifiableMap(result);
    }

    /**
     * @return a map containing the items from the job {@link ExecutionContext}
     */
    public Map<String, Object> getJobExecutionContext() {
        Map<String, Object> result = new HashMap<>();
        for (Entry<String, Object> entry : stepExecution.getJobExecution().getExecutionContext().entrySet()) {
            result.put(entry.getKey(), entry.getValue());
        }
        return Collections.unmodifiableMap(result);
    }

    /**
     * @return a map containing the items from the {@link JobParameters}
     */
    public Map<String, Object> getJobParameters() {
        Map<String, Object> result = new HashMap<>();
        for (Entry<String, JobParameter> entry : stepExecution.getJobParameters().getParameters().entrySet()) {
            result.put(entry.getKey(), entry.getValue().getValue());
        }
        return Collections.unmodifiableMap(result);
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public Map<String, Object> getPartitionPlan() {
        Map<String, Object> partitionPlanProperties = new HashMap<>();

        if (propertyContext != null) {
            Map partitionProperties = propertyContext.getStepProperties(getStepName());
            partitionPlanProperties = partitionProperties;
        }

        return Collections.unmodifiableMap(partitionPlanProperties);
    }

    /**
     * Allow clients to register callbacks for clean up on close.
     *
     * @param name the callback id (unique attribute key in this context)
     * @param callback a callback to execute on close
     */
    public void registerDestructionCallback(String name, Runnable callback) {
        synchronized (callbacks) {
            Set<Runnable> set = callbacks.get(name);
            if (set == null) {
                set = new HashSet<>();
                callbacks.put(name, set);
            }
            set.add(callback);
        }
    }

    private void unregisterDestructionCallbacks(String name) {
        synchronized (callbacks) {
            callbacks.remove(name);
        }
    }

    /**
     * Override base class behaviour to ensure destruction callbacks are
     * unregistered as well as the default behaviour.
     *
     * @see SynchronizedAttributeAccessor#removeAttribute(String)
     */
    @Override
    @Nullable
    public Object removeAttribute(String name) {
        unregisterDestructionCallbacks(name);
        return super.removeAttribute(name);
    }

    /**
     * Clean up the context at the end of a step execution. Must be called once
     * at the end of a step execution to honour the destruction callback
     * contract from the {@link StepScope}.
     */
    public void close() {

        List<Exception> errors = new ArrayList<>();

        Map<String, Set<Runnable>> copy = Collections.unmodifiableMap(callbacks);

        for (Entry<String, Set<Runnable>> entry : copy.entrySet()) {
            Set<Runnable> set = entry.getValue();
            for (Runnable callback : set) {
                if (callback != null) {
                    /*
                     * The documentation of the interface says that these
                     * callbacks must not throw exceptions, but we don't trust
                     * them necessarily...
                     */
                    try {
                        callback.run();
                    } catch (RuntimeException t) {
                        errors.add(t);
                    }
                }
            }
        }

        if (errors.isEmpty()) {
            return;
        }

        Exception error = errors.get(0);
        if (error instanceof RuntimeException) {
            throw (RuntimeException) error;
        } else {
            throw new UnexpectedJobExecutionException(
                    "Could not close step context, rethrowing first of " + errors.size() + " exceptions.", error);
        }
    }

    /**
     * The current {@link StepExecution} that is active in this context.
     *
     * @return the current {@link StepExecution}
     */
    public StepExecution getStepExecution() {
        return stepExecution;
    }

    /**
     * @return unique identifier for this context based on the step execution
     */
    public String getId() {
        Assert.state(stepExecution.getId() != null,
                "StepExecution has no id.  " + "It must be saved before it can be used in step scope.");
        return "execution#" + stepExecution.getId();
    }

    /**
     * Extend the base class method to include the step execution itself as a
     * key (i.e. two contexts are only equal if their step executions are the
     * same).
     *
     * @see SynchronizedAttributeAccessor#equals(Object)
     */
    @Override
    public boolean equals(Object other) {
        if (!(other instanceof StepContext)) {
            return false;
        }
        if (other == this) {
            return true;
        }
        StepContext context = (StepContext) other;
        if (context.stepExecution == stepExecution) {
            return true;
        }
        return stepExecution.equals(context.stepExecution);
    }

    /**
     * Overrides the default behaviour to provide a hash code based only on the
     * step execution.
     *
     * @see SynchronizedAttributeAccessor#hashCode()
     */
    @Override
    public int hashCode() {
        return stepExecution.hashCode();
    }

    @Override
    public String toString() {
        return super.toString() + ", stepExecutionContext=" + getStepExecutionContext() + ", jobExecutionContext="
                + getJobExecutionContext() + ", jobParameters=" + getJobParameters();
    }

}