001/*
002 * Copyright 2012-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.configuration.xml;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Comparator;
022import java.util.List;
023
024import org.springframework.batch.core.job.flow.Flow;
025import org.springframework.batch.core.job.flow.FlowExecutionStatus;
026import org.springframework.batch.core.job.flow.FlowExecutor;
027import org.springframework.batch.core.job.flow.FlowHolder;
028import org.springframework.batch.core.job.flow.State;
029import org.springframework.batch.core.job.flow.support.SimpleFlow;
030import org.springframework.batch.core.job.flow.support.StateTransition;
031import org.springframework.batch.core.job.flow.support.state.AbstractState;
032import org.springframework.batch.core.job.flow.support.state.StepState;
033import org.springframework.beans.factory.FactoryBean;
034import org.springframework.beans.factory.InitializingBean;
035import org.springframework.util.Assert;
036
037/**
038 * Convenience factory for SimpleFlow instances for use in XML namespace. It
039 * replaces the states in the input with proxies that have a unique name formed
040 * from the flow name and the original state name (unless the name is already in
041 * that form, in which case it is not modified).
042 *
043 * @author Dave Syer
044 * @author Michael Minella
045 */
046public class SimpleFlowFactoryBean implements FactoryBean<SimpleFlow>, InitializingBean {
047
048        private String name;
049
050        private List<StateTransition> stateTransitions;
051
052        private String prefix;
053
054        private Comparator<StateTransition> stateTransitionComparator;
055
056        private Class<SimpleFlow> flowType;
057
058        /**
059         * @param stateTransitionComparator {@link Comparator} implementation that addresses
060         * the ordering of state evaluation
061         */
062        public void setStateTransitionComparator(Comparator<StateTransition> stateTransitionComparator) {
063                this.stateTransitionComparator = stateTransitionComparator;
064        }
065
066        /**
067         * @param flowType Used to inject the type of flow (regular Spring Batch or JSR-352)
068         */
069        public void setFlowType(Class<SimpleFlow> flowType) {
070                this.flowType = flowType;
071        }
072
073        /**
074         * The name of the flow that is created by this factory.
075         *
076         * @param name the value of the name
077         */
078        public void setName(String name) {
079                this.name = name;
080                this.prefix = name + ".";
081        }
082
083        /**
084         * The raw state transitions for the flow. They will be transformed into
085         * proxies that have the same behavior but unique names prefixed with the
086         * flow name.
087         *
088         * @param stateTransitions the list of transitions
089         */
090        public void setStateTransitions(List<StateTransition> stateTransitions) {
091                this.stateTransitions = stateTransitions;
092        }
093
094        /**
095         * Check mandatory properties (name).
096         *
097         * @throws Exception thrown if error occurs.
098         */
099        @Override
100        public void afterPropertiesSet() throws Exception {
101                Assert.hasText(name, "The flow must have a name");
102
103                if(flowType == null) {
104                        flowType = SimpleFlow.class;
105                }
106        }
107
108        /* (non-Javadoc)
109         * @see org.springframework.beans.factory.FactoryBean#getObject()
110         */
111        @Override
112        public SimpleFlow getObject() throws Exception {
113                SimpleFlow flow = flowType.getConstructor(String.class).newInstance(name);
114
115                flow.setStateTransitionComparator(stateTransitionComparator);
116
117                List<StateTransition> updatedTransitions = new ArrayList<StateTransition>();
118                for (StateTransition stateTransition : stateTransitions) {
119                        State state = getProxyState(stateTransition.getState());
120                        updatedTransitions.add(StateTransition.switchOriginAndDestination(stateTransition, state,
121                                        getNext(stateTransition.getNext())));
122                }
123
124                flow.setStateTransitions(updatedTransitions);
125                flow.afterPropertiesSet();
126                return flow;
127
128        }
129
130        private String getNext(String next) {
131                if (next == null) {
132                        return null;
133                }
134                return (next.startsWith(this.prefix) ? "" : this.prefix) + next;
135        }
136
137        /**
138         * Convenience method to get a state that proxies the input but with a
139         * different name, appropriate to this flow. If the state is a StepState
140         * then the step name is also changed.
141         *
142         * @param state
143         * @return
144         */
145        private State getProxyState(State state) {
146                String oldName = state.getName();
147                if (oldName.startsWith(prefix)) {
148                        return state;
149                }
150                String stateName = prefix + oldName;
151                if (state instanceof StepState) {
152                        return createNewStepState(state, oldName, stateName);
153                }
154                return new DelegateState(stateName, state);
155        }
156
157        /**
158         * Provides an extension point to provide alternative {@link StepState}
159         * implementations within a {@link SimpleFlow}
160         *
161         * @param state The state that will be used to create the StepState
162         * @param oldName The name to be replaced
163         * @param stateName The name for the new State
164         * @return a state for the requested data
165         */
166        protected State createNewStepState(State state, String oldName,
167                        String stateName) {
168                return new StepState(stateName, ((StepState) state).getStep(oldName));
169        }
170
171        @Override
172        public Class<?> getObjectType() {
173                return SimpleFlow.class;
174        }
175
176        @Override
177        public boolean isSingleton() {
178                return true;
179        }
180
181        /**
182         * A State that proxies a delegate and changes its name but leaves its
183         * behavior unchanged.
184         *
185         * @author Dave Syer
186         *
187         */
188        public static class DelegateState extends AbstractState implements FlowHolder {
189                private final State state;
190
191                private DelegateState(String name, State state) {
192                        super(name);
193                        this.state = state;
194                }
195
196                public State getState() {
197                        return this.state;
198                }
199
200                @Override
201                public boolean isEndState() {
202                        return state.isEndState();
203                }
204
205                @Override
206                public FlowExecutionStatus handle(FlowExecutor executor) throws Exception {
207                        return state.handle(executor);
208                }
209
210                @Override
211                public Collection<Flow> getFlows() {
212                        return (state instanceof FlowHolder) ? ((FlowHolder)state).getFlows() : Collections.<Flow>emptyList();
213                }
214
215        }
216
217}