001/*
002 * Copyright 2013-2014 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.springframework.batch.core.jsr.job.flow;
017
018import org.springframework.batch.core.ExitStatus;
019import org.springframework.batch.core.JobExecution;
020import org.springframework.batch.core.JobExecutionException;
021import org.springframework.batch.core.JobInterruptedException;
022import org.springframework.batch.core.Step;
023import org.springframework.batch.core.configuration.xml.SimpleFlowFactoryBean.DelegateState;
024import org.springframework.batch.core.explore.JobExplorer;
025import org.springframework.batch.core.job.AbstractJob;
026import org.springframework.batch.core.job.flow.Flow;
027import org.springframework.batch.core.job.flow.FlowExecutionException;
028import org.springframework.batch.core.job.flow.FlowJob;
029import org.springframework.batch.core.job.flow.JobFlowExecutor;
030import org.springframework.batch.core.job.flow.State;
031import org.springframework.batch.core.job.flow.support.state.FlowState;
032import org.springframework.batch.core.jsr.job.JsrStepHandler;
033import org.springframework.batch.core.jsr.job.flow.support.JsrFlow;
034import org.springframework.batch.core.jsr.job.flow.support.state.JsrStepState;
035import org.springframework.batch.core.jsr.step.DecisionStep;
036import org.springframework.batch.core.launch.NoSuchJobException;
037import org.springframework.batch.core.launch.support.ExitCodeMapper;
038
039/**
040 * JSR-352 specific extension of the {@link FlowJob}.
041 *
042 * @author Michael Minella
043 * @since 3.0
044 */
045public class JsrFlowJob extends FlowJob {
046
047        private JobExplorer jobExplorer;
048
049        /**
050         * No arg constructor (invalid state)
051         */
052        public JsrFlowJob() {
053                super();
054        }
055
056        /**
057         * Main constructor
058         *
059         * @param name of the flow
060         */
061        public JsrFlowJob(String name) {
062                super(name);
063        }
064
065        public void setJobExplorer(JobExplorer jobExplorer) {
066                this.jobExplorer = jobExplorer;
067        }
068
069        /**
070         * @see AbstractJob#doExecute(JobExecution)
071         */
072        @Override
073        protected void doExecute(final JobExecution execution) throws JobExecutionException {
074                try {
075                        JobFlowExecutor executor = new JsrFlowExecutor(getJobRepository(),
076                                        new JsrStepHandler(getJobRepository(), jobExplorer), execution);
077
078                        State startState = ((JsrFlow)flow).getStartState();
079
080                        validateFirstStep(startState);
081
082                        executor.updateJobExecutionStatus(flow.start(executor).getStatus());
083                }
084                catch (FlowExecutionException e) {
085                        if (e.getCause() instanceof JobExecutionException) {
086                                throw (JobExecutionException) e.getCause();
087                        }
088                        throw new JobExecutionException("Flow execution ended unexpectedly", e);
089                }
090        }
091
092        private void validateFirstStep(State startState)
093                        throws JobExecutionException {
094                while(true) {
095                        if(startState instanceof DelegateState) {
096                                startState = ((DelegateState) startState).getState();
097                        } else if(startState instanceof JsrStepState) {
098                                String stepName = startState.getName().substring(startState.getName().indexOf(".") + 1, startState.getName().length());
099                                Step step = ((JsrStepState) startState).getStep(stepName);
100                                if(step instanceof DecisionStep) {
101                                        throw new JobExecutionException("Decision step is an invalid first step");
102                                } else {
103                                        break;
104                                }
105                        } else if(startState instanceof FlowState){
106                                Flow firstFlow = ((FlowState) startState).getFlows().iterator().next();
107                                startState = firstFlow.getStates().iterator().next();
108                        } else {
109                                break;
110                        }
111                }
112        }
113
114        /**
115         * Default mapping from throwable to {@link ExitStatus}.
116         *
117         * @param ex the cause of the failure
118         * @return an {@link ExitStatus}
119         */
120        @Override
121        protected ExitStatus getDefaultExitStatusForFailure(Throwable ex, JobExecution execution) {
122                if(!ExitStatus.isNonDefaultExitStatus(execution.getExitStatus())) {
123                        return execution.getExitStatus();
124                } else {
125                        ExitStatus exitStatus;
126                        if (ex instanceof JobInterruptedException
127                                        || ex.getCause() instanceof JobInterruptedException) {
128                                exitStatus = ExitStatus.STOPPED
129                                                .addExitDescription(JobInterruptedException.class.getName());
130                        } else if (ex instanceof NoSuchJobException
131                                        || ex.getCause() instanceof NoSuchJobException) {
132                                exitStatus = new ExitStatus(ExitCodeMapper.NO_SUCH_JOB, ex
133                                                .getClass().getName());
134                        } else {
135                                exitStatus = ExitStatus.FAILED.addExitDescription(ex);
136                        }
137
138                        return exitStatus;
139                }
140        }
141}