org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer   F
last analyzed

Complexity

Total Complexity 85

Size/Duplication

Total Lines 741
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
dl 0
loc 741
c 0
b 0
f 0
rs 1.263
wmc 85

79 Methods

Rating   Name   Duplication   Size   Complexity  
A AbstractCasWebflowConfigurer(FlowBuilderServices,FlowDefinitionRegistry,ApplicationContext,CasConfigurationProperties) 0 8 1
A initialize() 0 11 3
A createEvaluateActionForExistingActionState(Flow,String,String) 0 10 1
A createEndState(Flow,String) 0 3 1
createStateModelBinding 0 2 ?
createTransitionForState 0 4 ?
A getTransitionExecutionCriteriaChainForTransition(Transition) 0 15 3
A createEvaluateAction(String) 0 11 2
createStateDefaultTransition 0 7 ?
createClonedActionState 0 5 ?
A createTransition(String,TransitionableState) 0 3 1
A createFlowVariable(Flow,String,Class) 0 9 2
createEvaluateActionForExistingActionState 0 10 ?
A createMappingToSubflowState(String,String,boolean,Class) 0 13 1
createStateBinderConfiguration 0 4 ?
A buildFlow(String,String) 0 7 1
A setStartState(Flow,TransitionableState) 0 3 1
setLogoutFlowDefinitionRegistry 0 2 ?
A createEndState(Flow,String,Expression) 0 11 1
registerMultifactorProvidersStateTransitionsIntoWebflow 0 4 ?
A createStateModelBinding(TransitionableState,String,Class) 0 2 1
A createStateDefaultTransition(TransitionableState,String) 0 7 2
A createSubflowState(Flow,String,String,Action) 0 13 3
getSpringExpressionParser 0 18 ?
getTransitionExecutionCriteriaChainForTransition 0 15 ?
A getLoginFlow() 0 13 3
getViewStateBinderConfiguration 0 4 ?
A containsFlowState(Flow,String) 0 6 2
A createStateBinderConfiguration(List) 0 4 1
A containsSubflowState(Flow,String) 0 5 2
A containsTransition(TransitionableState,String) 0 6 2
A createViewState(Flow,String,String,BinderConfiguration) 0 3 1
containsFlowState 0 6 ?
A createTransition(String,String) 0 3 1
A getFlowBuilderServices() 0 2 1
A createExpression(String,Class) 0 3 1
A getState(Flow,String,Class) 0 6 2
B createViewState(Flow,String,Expression,BinderConfiguration) 0 24 3
getTransitionableState 0 6 ?
A getStartState(Flow) 0 3 1
A getViewStateBinderConfiguration(ViewState) 0 4 1
A createExpression(String) 0 2 1
A getLogoutFlow() 0 7 2
createSubflowAttributeMapper 0 2 ?
A createClonedActionState(Flow,String,String) 0 5 1
createFlowVariable 0 9 ?
A getTransitionableState(Flow,String,Class) 0 6 2
A registerMultifactorProvidersStateTransitionsIntoWebflow(TransitionableState) 0 4 1
A createActionState(Flow,String) 0 2 1
A createTransition(Expression,String) 0 13 2
A createEndState(Flow,String,ViewFactory) 0 17 3
A createTransitionForState(TransitionableState,String,String) 0 4 1
A createActionState(Flow,String,Action) 0 2 2
A createSubflowAttributeMapper(Mapper,Mapper) 0 2 1
containsSubflowState 0 5 ?
createExpression 0 3 ?
A getSpringExpressionParser() 0 18 1
A setLogoutFlowDefinitionRegistry(FlowDefinitionRegistry) 0 2 1
A createTransitionForState(TransitionableState,String,String,boolean) 0 20 4
A cloneActionState(ActionState,ActionState) 0 14 1
A createTransition(String) 0 4 1
A getTransitionableState(Flow,String) 0 5 2
cloneActionState 0 14 ?
A createEndState(Flow,String,String) 0 3 1
A setStartState(Flow,String) 0 5 1
A createMapperToSubflowState(List) 0 4 1
A createSubflowState(Flow,String,String) 0 3 1
A createEndState(Flow,String,String,boolean) 0 8 2
createMappingToSubflowState 0 13 ?
A getExpressionStringFromAction(EvaluateAction) 0 4 1
createMapperToSubflowState 0 4 ?
A createViewState(Flow,String,String) 0 3 1
getExpressionStringFromAction 0 4 ?
containsTransition 0 6 ?
getFlowBuilderServices 0 2 ?
A createStateDefaultTransition(TransitionableState,StateDefinition) 0 2 1
getLoginFlowDefinitionRegistry 0 2 ?
A getLoginFlowDefinitionRegistry() 0 2 1
A createDecisionState(Flow,String,String,String,String) 0 18 2

How to fix   Complexity   

Complexity

Complex classes like org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package org.apereo.cas.web.flow.configurer;
2
3
import org.apereo.cas.authentication.MultifactorAuthenticationUtils;
4
import org.apereo.cas.configuration.CasConfigurationProperties;
5
import org.apereo.cas.services.MultifactorAuthenticationProvider;
6
import org.apereo.cas.web.flow.CasWebflowConfigurer;
7
import org.slf4j.Logger;
8
import org.slf4j.LoggerFactory;
9
import org.springframework.binding.convert.ConversionExecutor;
10
import org.springframework.binding.convert.service.RuntimeBindingConversionExecutor;
11
import org.springframework.binding.expression.Expression;
12
import org.springframework.binding.expression.ExpressionParser;
13
import org.springframework.binding.expression.ParserContext;
14
import org.springframework.binding.expression.spel.SpringELExpressionParser;
15
import org.springframework.binding.expression.support.FluentParserContext;
16
import org.springframework.binding.expression.support.LiteralExpression;
17
import org.springframework.binding.mapping.Mapper;
18
import org.springframework.binding.mapping.impl.DefaultMapper;
19
import org.springframework.binding.mapping.impl.DefaultMapping;
20
import org.springframework.context.ApplicationContext;
21
import org.springframework.context.expression.BeanExpressionContextAccessor;
22
import org.springframework.context.expression.EnvironmentAccessor;
23
import org.springframework.context.expression.MapAccessor;
24
import org.springframework.expression.spel.SpelParserConfiguration;
25
import org.springframework.expression.spel.standard.SpelExpressionParser;
26
import org.springframework.expression.spel.support.ReflectivePropertyAccessor;
27
import org.springframework.util.ReflectionUtils;
28
import org.springframework.webflow.action.EvaluateAction;
29
import org.springframework.webflow.action.ExternalRedirectAction;
30
import org.springframework.webflow.action.ViewFactoryActionAdapter;
31
import org.springframework.webflow.config.FlowDefinitionRegistryBuilder;
32
import org.springframework.webflow.definition.StateDefinition;
33
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
34
import org.springframework.webflow.engine.ActionState;
35
import org.springframework.webflow.engine.DecisionState;
36
import org.springframework.webflow.engine.EndState;
37
import org.springframework.webflow.engine.Flow;
38
import org.springframework.webflow.engine.FlowExecutionExceptionHandler;
39
import org.springframework.webflow.engine.FlowVariable;
40
import org.springframework.webflow.engine.SubflowAttributeMapper;
41
import org.springframework.webflow.engine.SubflowState;
42
import org.springframework.webflow.engine.Transition;
43
import org.springframework.webflow.engine.TransitionCriteria;
44
import org.springframework.webflow.engine.TransitionableState;
45
import org.springframework.webflow.engine.ViewState;
46
import org.springframework.webflow.engine.WildcardTransitionCriteria;
47
import org.springframework.webflow.engine.builder.BinderConfiguration;
48
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
49
import org.springframework.webflow.engine.support.ActionExecutingViewFactory;
50
import org.springframework.webflow.engine.support.BeanFactoryVariableValueFactory;
51
import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
52
import org.springframework.webflow.engine.support.DefaultTransitionCriteria;
53
import org.springframework.webflow.engine.support.GenericSubflowAttributeMapper;
54
import org.springframework.webflow.engine.support.TransitionCriteriaChain;
55
import org.springframework.webflow.execution.Action;
56
import org.springframework.webflow.execution.ViewFactory;
57
import org.springframework.webflow.expression.spel.ActionPropertyAccessor;
58
import org.springframework.webflow.expression.spel.BeanFactoryPropertyAccessor;
59
import org.springframework.webflow.expression.spel.FlowVariablePropertyAccessor;
60
import org.springframework.webflow.expression.spel.MapAdaptablePropertyAccessor;
61
import org.springframework.webflow.expression.spel.MessageSourcePropertyAccessor;
62
import org.springframework.webflow.expression.spel.ScopeSearchingPropertyAccessor;
63
64
import java.lang.reflect.Field;
65
import java.util.ArrayList;
66
import java.util.Arrays;
67
import java.util.List;
68
import java.util.Map;
69
import java.util.Optional;
70
import java.util.stream.Collectors;
71
import java.util.stream.StreamSupport;
72
73
/**
74
 * The {@link AbstractCasWebflowConfigurer} is responsible for
75
 * providing an entry point into the CAS webflow.
76
 *
77
 * @author Misagh Moayyed
78
 * @since 4.2
79
 */
80
public abstract class AbstractCasWebflowConfigurer implements CasWebflowConfigurer {
81
82
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCasWebflowConfigurer.class);
83
84
    /**
85
     * The logout flow definition registry.
86
     */
87
    protected FlowDefinitionRegistry logoutFlowDefinitionRegistry;
88
89
    /**
90
     * The Login flow definition registry.
91
     */
92
    protected final FlowDefinitionRegistry loginFlowDefinitionRegistry;
93
94
    /**
95
     * Application context.
96
     */
97
    protected final ApplicationContext applicationContext;
98
99
    /**
100
     * CAS Properties.
101
     */
102
    protected final CasConfigurationProperties casProperties;
103
104
    /**
105
     * Flow builder services.
106
     */
107
    protected final FlowBuilderServices flowBuilderServices;
108
109
    public AbstractCasWebflowConfigurer(final FlowBuilderServices flowBuilderServices,
110
                                        final FlowDefinitionRegistry loginFlowDefinitionRegistry,
111
                                        final ApplicationContext applicationContext,
112
                                        final CasConfigurationProperties casProperties) {
113
        this.flowBuilderServices = flowBuilderServices;
114
        this.loginFlowDefinitionRegistry = loginFlowDefinitionRegistry;
115
        this.applicationContext = applicationContext;
116
        this.casProperties = casProperties;
117
    }
118
119
    @Override
120
    public void initialize() {
121
        try {
122
            LOGGER.debug("Initializing CAS webflow configuration...");
123
            if (casProperties.getWebflow().isAutoconfigure()) {
124
                doInitialize();
125
            } else {
126
                LOGGER.warn("Webflow auto-configuration is disabled. CAS will not modify the webflow via [{}]", getClass().getName());
127
            }
128
        } catch (final Exception e) {
129
            LOGGER.error(e.getMessage(), e);
130
        }
131
    }
132
133
    /**
134
     * Handle the initialization of the webflow.
135
     *
136
     */
137
    protected abstract void doInitialize();
138
139
    @Override
140
    public Flow buildFlow(final String location, final String id) {
141
        final FlowDefinitionRegistryBuilder builder = new FlowDefinitionRegistryBuilder(this.applicationContext, this.flowBuilderServices);
142
        builder.setParent(this.loginFlowDefinitionRegistry);
143
        builder.addFlowLocation(location, id);
144
        final FlowDefinitionRegistry registry = builder.build();
145
        return (Flow) registry.getFlowDefinition(id);
146
    }
147
    
148
    @Override
149
    public Flow getLoginFlow() {
150
        if (this.loginFlowDefinitionRegistry == null) {
151
            LOGGER.error("Login flow registry is not configured and/or initialized correctly.");
152
            return null;
153
        }
154
        final boolean found = Arrays.stream(this.loginFlowDefinitionRegistry.getFlowDefinitionIds()).anyMatch(f -> f.equals(FLOW_ID_LOGIN));
155
        if (found) {
156
            return (Flow) this.loginFlowDefinitionRegistry.getFlowDefinition(FLOW_ID_LOGIN);
157
        }
158
        LOGGER.error("Could not find flow definition [{}]. Available flow definition ids are [{}]", FLOW_ID_LOGIN,
159
                this.loginFlowDefinitionRegistry.getFlowDefinitionIds());
160
        return null;
161
    }
162
163
    @Override
164
    public Flow getLogoutFlow() {
165
        if (this.logoutFlowDefinitionRegistry == null) {
166
            LOGGER.error("Logout flow registry is not configured correctly.");
167
            return null;
168
        }
169
        return (Flow) this.logoutFlowDefinitionRegistry.getFlowDefinition(FLOW_ID_LOGOUT);
170
    }
171
172
    @Override
173
    public TransitionableState getStartState(final Flow flow) {
174
        return TransitionableState.class.cast(flow.getStartState());
175
    }
176
177
    /**
178
     * Create action state action state.
179
     *
180
     * @param flow the flow
181
     * @param name the name
182
     * @return the action state
183
     */
184
    public ActionState createActionState(final Flow flow, final String name) {
185
        return createActionState(flow, name, new Action[]{});
186
    }
187
188
    /**
189
     * Create action state action state.
190
     *
191
     * @param flow   the flow
192
     * @param name   the name
193
     * @param action the action
194
     * @return the action state
195
     */
196
    public ActionState createActionState(final Flow flow, final String name, final Action action) {
197
        return createActionState(flow, name, new Action[]{action});
198
    }
199
200
    @Override
201
    public ActionState createActionState(final Flow flow, final String name, final Action... actions) {
202
        if (containsFlowState(flow, name)) {
203
            LOGGER.debug("Flow [{}] already contains a definition for state id [{}]", flow.getId(), name);
204
            return getTransitionableState(flow, name, ActionState.class);
205
        }
206
        final ActionState actionState = new ActionState(flow, name);
207
        LOGGER.debug("Created action state [{}]", actionState.getId());
208
        actionState.getActionList().addAll(actions);
209
        LOGGER.debug("Added action to the action state [{}] list of actions: [{}]", actionState.getId(), actionState.getActionList());
210
        return actionState;
211
    }
212
    
213
    @Override
214
    public DecisionState createDecisionState(final Flow flow, final String id, final String testExpression,
215
                                             final String thenStateId, final String elseStateId) {
216
        if (containsFlowState(flow, id)) {
217
            LOGGER.debug("Flow [{}] already contains a definition for state id [{}]", flow.getId(), id);
218
            return getTransitionableState(flow, id, DecisionState.class);
219
        }
220
221
        final DecisionState decisionState = new DecisionState(flow, id);
222
223
        final Expression expression = createExpression(testExpression, Boolean.class);
224
        final Transition thenTransition = createTransition(expression, thenStateId);
225
        decisionState.getTransitionSet().add(thenTransition);
226
227
        final Transition elseTransition = createTransition("*", elseStateId);
228
        decisionState.getTransitionSet().add(elseTransition);
229
230
        return decisionState;
231
232
    }
233
234
    public FlowDefinitionRegistry getLoginFlowDefinitionRegistry() {
235
        return loginFlowDefinitionRegistry;
236
    }
237
238
    public FlowBuilderServices getFlowBuilderServices() {
239
        return flowBuilderServices;
240
    }
241
242
    @Override
243
    public void setStartState(final Flow flow, final String state) {
244
        flow.setStartState(state);
245
        final TransitionableState startState = getStartState(flow);
246
        LOGGER.debug("Start state is now set to [{}]", startState.getId());
247
    }
248
249
    @Override
250
    public void setStartState(final Flow flow, final TransitionableState state) {
251
        setStartState(flow, state.getId());
252
    }
253
254
    @Override
255
    public EvaluateAction createEvaluateAction(final String expression) {
256
        if (this.flowBuilderServices == null) {
257
            LOGGER.error("Flow builder services is not configured correctly.");
258
            return null;
259
        }
260
        final ParserContext ctx = new FluentParserContext();
261
        final Expression action = this.flowBuilderServices.getExpressionParser().parseExpression(expression, ctx);
262
        final EvaluateAction newAction = new EvaluateAction(action, null);
263
        LOGGER.debug("Created evaluate action for expression [{}]", action.getExpressionString());
264
        return newAction;
265
    }
266
267
    /**
268
     * Add a default transition to a given state.
269
     *
270
     * @param state       the state to include the default transition
271
     * @param targetState the id of the destination state to which the flow should transfer
272
     */
273
    public void createStateDefaultTransition(final TransitionableState state, final String targetState) {
274
        if (state == null) {
275
            LOGGER.debug("Cannot add default transition of [{}] to the given state is null and cannot be found in the flow.", targetState);
276
            return;
277
        }
278
        final Transition transition = createTransition(targetState);
279
        state.getTransitionSet().add(transition);
280
    }
281
282
    /**
283
     * Create state default transition.
284
     *
285
     * @param state       the state
286
     * @param targetState the target state
287
     */
288
    public void createStateDefaultTransition(final TransitionableState state, final StateDefinition targetState) {
289
        createStateDefaultTransition(state, targetState.getId());
290
    }
291
    /**
292
     * Create transition for state transition.
293
     *
294
     * @param state           the state
295
     * @param criteriaOutcome the criteria outcome
296
     * @param targetState     the target state
297
     * @return the transition
298
     */
299
    public Transition createTransitionForState(final TransitionableState state,
300
                                               final String criteriaOutcome,
301
                                               final String targetState) {
302
        return createTransitionForState(state, criteriaOutcome, targetState, false);
303
    }
304
305
    /**
306
     * Add transition to action state.
307
     *
308
     * @param state           the action state
309
     * @param criteriaOutcome the criteria outcome
310
     * @param targetState     the target state
311
     * @param removeExisting  the remove existing
312
     * @return the transition
313
     */
314
    public Transition createTransitionForState(final TransitionableState state,
315
                                               final String criteriaOutcome,
316
                                               final String targetState,
317
                                               final boolean removeExisting) {
318
        try {
319
            if (removeExisting) {
320
                final Transition success = (Transition) state.getTransition(criteriaOutcome);
321
                if (success != null) {
322
                    state.getTransitionSet().remove(success);
323
                }
324
            }
325
326
            final Transition transition = createTransition(criteriaOutcome, targetState);
327
            state.getTransitionSet().add(transition);
328
            LOGGER.debug("Added transition [{}] to the state [{}]", transition.getId(), state.getId());
329
            return transition;
330
        } catch (final Exception e) {
331
            LOGGER.error(e.getMessage(), e);
332
        }
333
        return null;
334
    }
335
336
    @Override
337
    public Transition createTransition(final String criteriaOutcome, final String targetState) {
338
        return createTransition(new LiteralExpression(criteriaOutcome), targetState);
339
    }
340
341
    @Override
342
    public Transition createTransition(final String criteriaOutcome, final TransitionableState targetState) {
343
        return createTransition(new LiteralExpression(criteriaOutcome), targetState.getId());
344
    }
345
346
    @Override
347
    public Transition createTransition(final String targetState) {
348
        final DefaultTargetStateResolver resolver = new DefaultTargetStateResolver(targetState);
349
        return new Transition(resolver);
350
    }
351
352
    @Override
353
    public Transition createTransition(final Expression criteriaOutcomeExpression, final String targetState) {
354
        final TransitionCriteria criteria;
355
356
        if (criteriaOutcomeExpression.toString().equals(WildcardTransitionCriteria.WILDCARD_EVENT_ID)) {
357
            criteria = WildcardTransitionCriteria.INSTANCE;
358
        } else {
359
            criteria = new DefaultTransitionCriteria(criteriaOutcomeExpression);
360
        }
361
362
        final DefaultTargetStateResolver resolver = new DefaultTargetStateResolver(targetState);
363
        final Transition t = new Transition(criteria, resolver);
364
        return t;
365
    }
366
367
    /**
368
     * Create expression expression.
369
     *
370
     * @param expression   the expression
371
     * @param expectedType the expected type
372
     * @return the expression
373
     */
374
    public Expression createExpression(final String expression, final Class expectedType) {
375
        final ParserContext parserContext = new FluentParserContext().expectResult(expectedType);
376
        return getSpringExpressionParser().parseExpression(expression, parserContext);
377
    }
378
379
    /**
380
     * Create expression.
381
     *
382
     * @param expression the expression
383
     * @return the expression
384
     */
385
    public Expression createExpression(final String expression) {
386
        return createExpression(expression, null);
387
    }
388
    
389
    /**
390
     * Gets spring expression parser.
391
     *
392
     * @return the spring expression parser
393
     */
394
    public SpringELExpressionParser getSpringExpressionParser() {
395
        final SpelParserConfiguration configuration = new SpelParserConfiguration();
396
        final SpelExpressionParser spelExpressionParser = new SpelExpressionParser(configuration);
397
        final SpringELExpressionParser parser = new SpringELExpressionParser(spelExpressionParser,
398
                this.flowBuilderServices.getConversionService());
399
400
        parser.addPropertyAccessor(new ActionPropertyAccessor());
401
        parser.addPropertyAccessor(new BeanFactoryPropertyAccessor());
402
        parser.addPropertyAccessor(new FlowVariablePropertyAccessor());
403
        parser.addPropertyAccessor(new MapAdaptablePropertyAccessor());
404
        parser.addPropertyAccessor(new MessageSourcePropertyAccessor());
405
        parser.addPropertyAccessor(new ScopeSearchingPropertyAccessor());
406
        parser.addPropertyAccessor(new BeanExpressionContextAccessor());
407
        parser.addPropertyAccessor(new MapAccessor());
408
        parser.addPropertyAccessor(new MapAdaptablePropertyAccessor());
409
        parser.addPropertyAccessor(new EnvironmentAccessor());
410
        parser.addPropertyAccessor(new ReflectivePropertyAccessor());
411
        return parser;
412
413
    }
414
415
416
    @Override
417
    public EndState createEndState(final Flow flow, final String id) {
418
        return createEndState(flow, id, (ViewFactory) null);
419
    }
420
421
    @Override
422
    public EndState createEndState(final Flow flow, final String id, final String viewId) {
423
        return createEndState(flow, id, new LiteralExpression(viewId));
424
    }
425
426
    @Override
427
    public EndState createEndState(final Flow flow, final String id, final Expression expression) {
428
        final ViewFactory viewFactory = this.flowBuilderServices.getViewFactoryCreator().createViewFactory(
429
                expression,
430
                this.flowBuilderServices.getExpressionParser(),
431
                this.flowBuilderServices.getConversionService(),
432
                null,
433
                this.flowBuilderServices.getValidator(),
434
                this.flowBuilderServices.getValidationHintResolver());
435
436
        return createEndState(flow, id, viewFactory);
437
    }
438
439
    @Override
440
    public EndState createEndState(final Flow flow, final String id, final String viewId, final boolean redirect) {
441
        if (!redirect) {
442
            return createEndState(flow, id, viewId);
443
        }
444
        final Expression expression = createExpression(viewId, String.class);
445
        final ActionExecutingViewFactory viewFactory = new ActionExecutingViewFactory(new ExternalRedirectAction(expression));
446
        return createEndState(flow, id, viewFactory);
447
    }
448
449
    @Override
450
    public EndState createEndState(final Flow flow, final String id, final ViewFactory viewFactory) {
451
452
        if (containsFlowState(flow, id)) {
453
            LOGGER.debug("Flow [{}] already contains a definition for state id [{}]", flow.getId(), id);
454
            return (EndState) flow.getStateInstance(id);
455
        }
456
457
        final EndState endState = new EndState(flow, id);
458
        if (viewFactory != null) {
459
            final Action finalResponseAction = new ViewFactoryActionAdapter(viewFactory);
460
            endState.setFinalResponseAction(finalResponseAction);
461
            LOGGER.debug("Created end state state [{}] on flow id [{}], backed by view factory [{}]", id, flow.getId(), viewFactory);
462
        } else {
463
            LOGGER.debug("Created end state state [{}] on flow id [{}]", id, flow.getId());
464
        }
465
        return endState;
466
467
    }
468
469
    @Override
470
    public ViewState createViewState(final Flow flow, final String id, final Expression expression,
471
                                     final BinderConfiguration binder) {
472
        try {
473
            if (containsFlowState(flow, id)) {
474
                LOGGER.debug("Flow [{}] already contains a definition for state id [{}]", flow.getId(), id);
475
                return getTransitionableState(flow, id, ViewState.class);
476
            }
477
478
            final ViewFactory viewFactory = this.flowBuilderServices.getViewFactoryCreator().createViewFactory(
479
                    expression,
480
                    this.flowBuilderServices.getExpressionParser(),
481
                    this.flowBuilderServices.getConversionService(),
482
                    binder,
483
                    this.flowBuilderServices.getValidator(),
484
                    this.flowBuilderServices.getValidationHintResolver());
485
486
            final ViewState viewState = new ViewState(flow, id, viewFactory);
487
            LOGGER.debug("Added view state [{}]", viewState.getId());
488
            return viewState;
489
        } catch (final Exception e) {
490
            LOGGER.error(e.getMessage(), e);
491
        }
492
        return null;
493
    }
494
495
    @Override
496
    public ViewState createViewState(final Flow flow, final String id, final String viewId) {
497
        return createViewState(flow, id, new LiteralExpression(viewId), null);
498
    }
499
500
    @Override
501
    public ViewState createViewState(final Flow flow, final String id, final String viewId, final BinderConfiguration binder) {
502
        return createViewState(flow, id, new LiteralExpression(viewId), binder);
503
    }
504
505
    @Override
506
    public SubflowState createSubflowState(final Flow flow, final String id, final String subflow, final Action entryAction) {
507
        if (containsFlowState(flow, id)) {
508
            LOGGER.debug("Flow [{}] already contains a definition for state id [{}]", flow.getId(), id);
509
            return getTransitionableState(flow, id, SubflowState.class);
510
        }
511
512
        final SubflowState state = new SubflowState(flow, id, new BasicSubflowExpression(subflow, this.loginFlowDefinitionRegistry));
513
        if (entryAction != null) {
514
            state.getEntryActionList().add(entryAction);
515
        }
516
517
        return state;
518
    }
519
520
    @Override
521
    public SubflowState createSubflowState(final Flow flow, final String id, final String subflow) {
522
        return createSubflowState(flow, id, subflow, null);
523
    }
524
525
    /**
526
     * Create mapper to subflow state.
527
     *
528
     * @param mappings the mappings
529
     * @return the mapper
530
     */
531
    public Mapper createMapperToSubflowState(final List<DefaultMapping> mappings) {
532
        final DefaultMapper inputMapper = new DefaultMapper();
533
        mappings.forEach(inputMapper::addMapping);
534
        return inputMapper;
535
    }
536
537
    /**
538
     * Create mapping to subflow state.
539
     *
540
     * @param name     the name
541
     * @param value    the value
542
     * @param required the required
543
     * @param type     the type
544
     * @return the default mapping
545
     */
546
    public DefaultMapping createMappingToSubflowState(final String name, final String value, final boolean required, final Class type) {
547
        final ExpressionParser parser = this.flowBuilderServices.getExpressionParser();
548
549
        final Expression source = parser.parseExpression(value, new FluentParserContext());
550
        final Expression target = parser.parseExpression(name, new FluentParserContext());
551
552
        final DefaultMapping mapping = new DefaultMapping(source, target);
553
        mapping.setRequired(required);
554
555
        final ConversionExecutor typeConverter =
556
                new RuntimeBindingConversionExecutor(type, this.flowBuilderServices.getConversionService());
557
        mapping.setTypeConverter(typeConverter);
558
        return mapping;
559
    }
560
561
    /**
562
     * Create subflow attribute mapper.
563
     *
564
     * @param inputMapper  the input mapper
565
     * @param outputMapper the output mapper
566
     * @return the subflow attribute mapper
567
     */
568
    public SubflowAttributeMapper createSubflowAttributeMapper(final Mapper inputMapper, final Mapper outputMapper) {
569
        return new GenericSubflowAttributeMapper(inputMapper, outputMapper);
570
    }
571
572
    public void setLogoutFlowDefinitionRegistry(final FlowDefinitionRegistry logoutFlowDefinitionRegistry) {
573
        this.logoutFlowDefinitionRegistry = logoutFlowDefinitionRegistry;
574
    }
575
576
    /**
577
     * Contains flow state?
578
     *
579
     * @param flow    the flow
580
     * @param stateId the state id
581
     * @return true if flow contains the state.
582
     */
583
    public boolean containsFlowState(final Flow flow, final String stateId) {
584
        if (flow == null) {
585
            LOGGER.error("Flow is not configured correctly and cannot be null.");
586
            return false;
587
        }
588
        return flow.containsState(stateId);
589
    }
590
591
    /**
592
     * Contains subflow state.
593
     *
594
     * @param flow    the flow
595
     * @param stateId the state id
596
     * @return the boolean
597
     */
598
    public boolean containsSubflowState(final Flow flow, final String stateId) {
599
        if (containsFlowState(flow, stateId)) {
600
            return getState(flow, stateId, SubflowState.class) != null;
601
        }
602
        return false;
603
    }
604
    /**
605
     * Contains transition boolean.
606
     *
607
     * @param state      the state
608
     * @param transition the transition
609
     * @return the boolean
610
     */
611
    public boolean containsTransition(final TransitionableState state, final String transition) {
612
        if (state == null) {
613
            LOGGER.error("State is not configured correctly and cannot be null.");
614
            return false;
615
        }
616
        return state.getTransition(transition) != null;
617
    }
618
619
    /**
620
     * Create flow variable flow variable.
621
     *
622
     * @param flow the flow
623
     * @param id   the id
624
     * @param type the type
625
     * @return the flow variable
626
     */
627
    public FlowVariable createFlowVariable(final Flow flow, final String id, final Class type) {
628
        final Optional<FlowVariable> opt = Arrays.stream(flow.getVariables()).filter(v -> v.getName().equalsIgnoreCase(id)).findFirst();
629
        if (opt.isPresent()) {
630
            return opt.get();
631
        }
632
        final FlowVariable flowVar = new FlowVariable(id, new BeanFactoryVariableValueFactory(type,
633
                applicationContext.getAutowireCapableBeanFactory()));
634
        flow.addVariable(flowVar);
635
        return flowVar;
636
    }
637
638
    /**
639
     * Create state model bindings.
640
     *
641
     * @param properties the properties
642
     * @return the binder configuration
643
     */
644
    public BinderConfiguration createStateBinderConfiguration(final List<String> properties) {
645
        final BinderConfiguration binder = new BinderConfiguration();
646
        properties.forEach(p -> binder.addBinding(new BinderConfiguration.Binding(p, null, true)));
647
        return binder;
648
    }
649
650
    /**
651
     * Create state model binding.
652
     *
653
     * @param state     the state
654
     * @param modelName the model name
655
     * @param modelType the model type
656
     */
657
    public void createStateModelBinding(final TransitionableState state, final String modelName, final Class modelType) {
658
        state.getAttributes().put("model", createExpression(modelName, modelType));
659
    }
660
661
    /**
662
     * Gets state binder configuration.
663
     *
664
     * @param state the state
665
     * @return the state binder configuration
666
     */
667
    public BinderConfiguration getViewStateBinderConfiguration(final ViewState state) {
668
        final Field field = ReflectionUtils.findField(state.getViewFactory().getClass(), "binderConfiguration");
669
        ReflectionUtils.makeAccessible(field);
670
        return (BinderConfiguration) ReflectionUtils.getField(field, state.getViewFactory());
671
    }
672
673
    /**
674
     * Clone action state.
675
     *
676
     * @param source the source
677
     * @param target the target
678
     */
679
    public void cloneActionState(final ActionState source, final ActionState target) {
680
        source.getActionList().forEach(a -> target.getActionList().add(a));
681
        source.getExitActionList().forEach(a -> target.getExitActionList().add(a));
682
        source.getAttributes().asMap().forEach((k, v) -> target.getAttributes().put(k, v));
683
        source.getTransitionSet().forEach(t -> target.getTransitionSet().addAll(t));
684
685
        final Field field = ReflectionUtils.findField(target.getExceptionHandlerSet().getClass(), "exceptionHandlers");
686
        ReflectionUtils.makeAccessible(field);
687
        final List<FlowExecutionExceptionHandler> list = (List<FlowExecutionExceptionHandler>)
688
                ReflectionUtils.getField(field, target.getExceptionHandlerSet());
689
        list.forEach(h -> source.getExceptionHandlerSet().add(h));
690
691
        target.setDescription(source.getDescription());
692
        target.setCaption(source.getCaption());
693
    }
694
695
    /**
696
     * Gets transition execution criteria chain for transition.
697
     *
698
     * @param def the def
699
     * @return the transition execution criteria chain for transition
700
     */
701
    public List<TransitionCriteria> getTransitionExecutionCriteriaChainForTransition(final Transition def) {
702
703
        if (def.getExecutionCriteria() instanceof TransitionCriteriaChain) {
704
            final TransitionCriteriaChain chain = (TransitionCriteriaChain) def.getExecutionCriteria();
705
            final Field field = ReflectionUtils.findField(chain.getClass(), "criteriaChain");
706
            ReflectionUtils.makeAccessible(field);
707
            return (List<TransitionCriteria>) ReflectionUtils.getField(field, chain);
708
        }
709
710
        if (def.getExecutionCriteria() != null) {
711
            final List c = new ArrayList<>();
712
            c.add(def.getExecutionCriteria());
713
            return c;
714
        }
715
        return new ArrayList<>(0);
716
    }
717
718
    /**
719
     * Gets expression string from action.
720
     *
721
     * @param act the act
722
     * @return the expression string from action
723
     */
724
    public Expression getExpressionStringFromAction(final EvaluateAction act) {
725
        final Field field = ReflectionUtils.findField(act.getClass(), "expression");
726
        ReflectionUtils.makeAccessible(field);
727
        return (Expression) ReflectionUtils.getField(field, act);
728
    }
729
730
    /**
731
     * Register multifactor providers state transitions into webflow.
732
     *
733
     * @param state the state
734
     */
735
    public void registerMultifactorProvidersStateTransitionsIntoWebflow(final TransitionableState state) {
736
        final Map<String, MultifactorAuthenticationProvider> providerMap =
737
                MultifactorAuthenticationUtils.getAvailableMultifactorAuthenticationProviders(this.applicationContext);
738
        providerMap.forEach((k, v) -> createTransitionForState(state, v.getId(), v.getId()));
739
    }
740
741
    /**
742
     * Create evaluate action for action state action.
743
     *
744
     * @param flow             the flow
745
     * @param actionStateId    the action state id
746
     * @param evaluateActionId the evaluate action id
747
     * @return the action
748
     */
749
    public Action createEvaluateActionForExistingActionState(final Flow flow, final String actionStateId,
750
                                                             final String evaluateActionId) {
751
        final ActionState action = getState(flow, actionStateId, ActionState.class);
752
        final List<Action> actions = StreamSupport.stream(action.getActionList().spliterator(), false)
753
                .collect(Collectors.toList());
754
        final Action evaluateAction = createEvaluateAction(evaluateActionId);
755
        actions.add(0, evaluateAction);
756
        action.getActionList().forEach(a -> action.getActionList().remove(a));
757
        actions.forEach(action.getActionList()::add);
758
        return evaluateAction;
759
    }
760
761
    /**
762
     * Clone and create action state.
763
     *
764
     * @param flow                 the flow
765
     * @param actionStateId        the action state id
766
     * @param actionStateIdToClone the action state id to clone
767
     */
768
    public void createClonedActionState(final Flow flow, final String actionStateId,
769
                                        final String actionStateIdToClone) {
770
        final ActionState generateServiceTicket = getState(flow, actionStateIdToClone, ActionState.class);
771
        final ActionState consentTicketAction = createActionState(flow, actionStateId);
772
        cloneActionState(generateServiceTicket, consentTicketAction);
773
    }
774
775
    /**
776
     * Gets state.
777
     *
778
     * @param <T>     the type parameter
779
     * @param flow    the flow
780
     * @param stateId the state id
781
     * @param clazz   the clazz
782
     * @return the state
783
     */
784
    public <T> T getState(final Flow flow, final String stateId, final Class<T> clazz) {
785
        if (containsFlowState(flow, stateId)) {
786
            final StateDefinition state = flow.getState(stateId);
787
            return clazz.cast(state);
788
        }
789
        return null;
790
    }
791
792
    /**
793
     * Gets transitionable state.
794
     *
795
     * @param <T>     the type parameter
796
     * @param flow    the flow
797
     * @param stateId the state id
798
     * @param clazz   the clazz
799
     * @return the transitionable state
800
     */
801
    public <T extends TransitionableState> T getTransitionableState(final Flow flow, final String stateId, final Class<T> clazz) {
802
        if (containsFlowState(flow, stateId)) {
803
            final StateDefinition state = flow.getTransitionableState(stateId);
804
            return clazz.cast(state);
805
        }
806
        return null;
807
    }
808
809
    /**
810
     * Gets transitionable state.
811
     *
812
     * @param flow    the flow
813
     * @param stateId the state id
814
     * @return the transitionable state
815
     */
816
    public TransitionableState getTransitionableState(final Flow flow, final String stateId) {
817
        if (containsFlowState(flow, stateId)) {
818
            return TransitionableState.class.cast(flow.getTransitionableState(stateId));
819
        }
820
        return null;
821
    }
822
}
823