Skip to content

Commit 30e7c4d

Browse files
committed
Add auto-config for ComponentFlow
- ComponentFlowAutoConfiguration which creates a builder bean and configures ComponentFlowCustomizer to set needed defaults for terminal, resource loader and template executor. - Fixes #387
1 parent 85976cf commit 30e7c4d

File tree

6 files changed

+192
-18
lines changed

6 files changed

+192
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.boot;
17+
18+
import org.jline.terminal.Terminal;
19+
20+
import org.springframework.beans.factory.ObjectProvider;
21+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
22+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
23+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
24+
import org.springframework.context.annotation.Bean;
25+
import org.springframework.context.annotation.Configuration;
26+
import org.springframework.context.annotation.Scope;
27+
import org.springframework.core.annotation.Order;
28+
import org.springframework.core.io.ResourceLoader;
29+
import org.springframework.shell.component.flow.ComponentFlow;
30+
import org.springframework.shell.component.flow.ComponentFlow.Builder;
31+
import org.springframework.shell.style.TemplateExecutor;
32+
33+
/**
34+
* {@link EnableAutoConfiguration Auto-configuration} for {@link ComponentFlow}.
35+
*
36+
* @author Janne Valkealahti
37+
*/
38+
@Configuration(proxyBeanMethods = false)
39+
@ConditionalOnClass(ComponentFlow.class)
40+
public class ComponentFlowAutoConfiguration {
41+
42+
@Bean
43+
@Scope("prototype")
44+
@ConditionalOnMissingBean
45+
public ComponentFlow.Builder componentFlowBuilder(ObjectProvider<ComponentFlowCustomizer> customizerProvider) {
46+
ComponentFlow.Builder builder = ComponentFlow.builder();
47+
customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));
48+
return builder;
49+
}
50+
51+
@Configuration(proxyBeanMethods = false)
52+
protected static class ComponentFlowConfiguration {
53+
54+
@Bean
55+
@ConditionalOnMissingBean
56+
@Order(0)
57+
public ComponentFlowCustomizer exchangeStrategiesCustomizer(ObjectProvider<Terminal> terminal,
58+
ObjectProvider<ResourceLoader> resourceLoader, ObjectProvider<TemplateExecutor> templateExecutor) {
59+
return new CommonComponentFlowCustomizer(terminal, resourceLoader, templateExecutor);
60+
}
61+
62+
}
63+
64+
private static class CommonComponentFlowCustomizer implements ComponentFlowCustomizer {
65+
66+
private final ObjectProvider<Terminal> terminal;
67+
private final ObjectProvider<ResourceLoader> resourceLoader;
68+
private final ObjectProvider<TemplateExecutor> templateExecutor;
69+
70+
CommonComponentFlowCustomizer(ObjectProvider<Terminal> terminal, ObjectProvider<ResourceLoader> resourceLoader,
71+
ObjectProvider<TemplateExecutor> templateExecutor) {
72+
this.terminal = terminal;
73+
this.resourceLoader = resourceLoader;
74+
this.templateExecutor = templateExecutor;
75+
}
76+
77+
@Override
78+
public void customize(Builder componentFlowBuilder) {
79+
terminal.ifAvailable(dep -> componentFlowBuilder.terminal(dep));
80+
resourceLoader.ifAvailable(dep -> componentFlowBuilder.resourceLoader(dep));
81+
templateExecutor.ifAvailable(dep -> componentFlowBuilder.templateExecutor(dep));
82+
}
83+
}
84+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.shell.boot;
17+
18+
import org.springframework.shell.component.flow.ComponentFlow;
19+
20+
/**
21+
* Callback interface that can be used to customize a {@link ComponentFlow.Builder}.
22+
*
23+
* @author Janne Valkealahti
24+
*/
25+
@FunctionalInterface
26+
public interface ComponentFlowCustomizer {
27+
28+
/**
29+
* Callback to customize a {@link ComponentFlow.Builder} instance.
30+
* @param componentFlowBuilder the component flow builder to customize
31+
*/
32+
void customize(ComponentFlow.Builder componentFlowBuilder);
33+
}

spring-shell-autoconfigure/src/main/resources/META-INF/spring.factories

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ org.springframework.shell.boot.JCommanderParameterResolverAutoConfiguration,\
1212
org.springframework.shell.boot.ParameterResolverAutoConfiguration,\
1313
org.springframework.shell.boot.StandardAPIAutoConfiguration,\
1414
org.springframework.shell.boot.ThemingAutoConfiguration,\
15-
org.springframework.shell.boot.StandardCommandsAutoConfiguration
15+
org.springframework.shell.boot.StandardCommandsAutoConfiguration,\
16+
org.springframework.shell.boot.ComponentFlowAutoConfiguration

spring-shell-core/src/main/java/org/springframework/shell/component/flow/ComponentFlow.java

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,10 @@ public interface ComponentFlow {
7070
/**
7171
* Gets a new instance of an input wizard builder.
7272
*
73-
* @param terminal the terminal
7473
* @return the input wizard builder
7574
*/
76-
public static Builder builder(Terminal terminal) {
77-
return new DefaultBuilder(terminal);
75+
public static Builder builder() {
76+
return new DefaultBuilder();
7877
}
7978

8079
/**
@@ -135,6 +134,14 @@ interface Builder {
135134
*/
136135
MultiItemSelectorSpec withMultiItemSelector(String id);
137136

137+
/**
138+
* Sets a {@link Terminal}.
139+
*
140+
* @param terminal the terminal
141+
* @return a builder
142+
*/
143+
Builder terminal(Terminal terminal);
144+
138145
/**
139146
* Sets a {@link ResourceLoader}.
140147
*
@@ -151,6 +158,20 @@ interface Builder {
151158
*/
152159
Builder templateExecutor(TemplateExecutor templateExecutor);
153160

161+
/**
162+
* Clones existing builder.
163+
*
164+
* @return a builder
165+
*/
166+
Builder clone();
167+
168+
/**
169+
* Resets existing builder.
170+
*
171+
* @return a builder
172+
*/
173+
Builder reset();
174+
154175
/**
155176
* Builds instance of input wizard.
156177
*
@@ -161,19 +182,18 @@ interface Builder {
161182

162183
static abstract class BaseBuilder implements Builder {
163184

164-
private Terminal terminal;
165185
private final List<BaseStringInput> stringInputs = new ArrayList<>();
166186
private final List<BasePathInput> pathInputs = new ArrayList<>();
167187
private final List<BaseConfirmationInput> confirmationInputs = new ArrayList<>();
168188
private final List<BaseSingleItemSelector> singleItemSelectors = new ArrayList<>();
169189
private final List<BaseMultiItemSelector> multiItemSelectors = new ArrayList<>();
170190
private final AtomicInteger order = new AtomicInteger();
171191
private final HashSet<String> uniqueIds = new HashSet<>();
192+
private Terminal terminal;
172193
private ResourceLoader resourceLoader;
173194
private TemplateExecutor templateExecutor;
174195

175-
BaseBuilder(Terminal terminal) {
176-
this.terminal = terminal;
196+
BaseBuilder() {
177197
}
178198

179199
@Override
@@ -207,6 +227,12 @@ public MultiItemSelectorSpec withMultiItemSelector(String id) {
207227
return new DefaultMultiInputSpec(this, id);
208228
}
209229

230+
@Override
231+
public Builder terminal(Terminal terminal) {
232+
this.terminal = terminal;
233+
return this;
234+
}
235+
210236
@Override
211237
public Builder resourceLoader(ResourceLoader resourceLoader) {
212238
this.resourceLoader = resourceLoader;
@@ -219,6 +245,23 @@ public Builder templateExecutor(TemplateExecutor templateExecutor) {
219245
return this;
220246
}
221247

248+
@Override
249+
public Builder clone() {
250+
return new DefaultBuilder(this);
251+
}
252+
253+
@Override
254+
public Builder reset() {
255+
stringInputs.clear();
256+
pathInputs.clear();
257+
confirmationInputs.clear();
258+
singleItemSelectors.clear();
259+
multiItemSelectors.clear();
260+
order.set(0);
261+
uniqueIds.clear();
262+
return this;
263+
}
264+
222265
void addStringInput(BaseStringInput input) {
223266
checkUniqueId(input.getId());
224267
input.setOrder(order.getAndIncrement());
@@ -249,6 +292,10 @@ void addMultiItemSelector(BaseMultiItemSelector input) {
249292
multiItemSelectors.add(input);
250293
}
251294

295+
Terminal getTerminal() {
296+
return terminal;
297+
}
298+
252299
ResourceLoader getResourceLoader() {
253300
return resourceLoader;
254301
}
@@ -267,8 +314,14 @@ private void checkUniqueId(String id) {
267314

268315
static class DefaultBuilder extends BaseBuilder {
269316

270-
DefaultBuilder(Terminal terminal) {
271-
super(terminal);
317+
DefaultBuilder() {
318+
super();
319+
}
320+
321+
DefaultBuilder(BaseBuilder other) {
322+
terminal(other.getTerminal());
323+
resourceLoader(other.getResourceLoader());
324+
templateExecutor(other.getTemplateExecutor());
272325
}
273326
}
274327

spring-shell-core/src/test/java/org/springframework/shell/component/flow/ComponentFlowTests.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ public void testSimpleFlow() throws InterruptedException {
4141
single1SelectItems.put("key2", "value2");
4242
List<SelectItem> multi1SelectItems = Arrays.asList(SelectItem.of("key1", "value1"),
4343
SelectItem.of("key2", "value2"), SelectItem.of("key3", "value3"));
44-
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
44+
ComponentFlow wizard = ComponentFlow.builder()
45+
.terminal(getTerminal())
4546
.resourceLoader(getResourceLoader())
4647
.templateExecutor(getTemplateExecutor())
4748
.withStringInput("field1")
@@ -107,7 +108,8 @@ public void testSimpleFlow() throws InterruptedException {
107108

108109
@Test
109110
public void testSkipsGivenComponents() throws InterruptedException {
110-
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
111+
ComponentFlow wizard = ComponentFlow.builder()
112+
.terminal(getTerminal())
111113
.withStringInput("id1")
112114
.name("name")
113115
.resultValue("value1")
@@ -154,7 +156,8 @@ public void testSkipsGivenComponents() throws InterruptedException {
154156

155157
@Test
156158
public void testChoosesDynamically() throws InterruptedException {
157-
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
159+
ComponentFlow wizard = ComponentFlow.builder()
160+
.terminal(getTerminal())
158161
.resourceLoader(getResourceLoader())
159162
.templateExecutor(getTemplateExecutor())
160163
.withStringInput("id1")

spring-shell-samples/src/main/java/org/springframework/shell/samples/standard/ComponentFlowCommands.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.util.List;
2121
import java.util.Map;
2222

23+
import org.springframework.beans.factory.annotation.Autowired;
2324
import org.springframework.shell.component.flow.ComponentFlow;
2425
import org.springframework.shell.component.flow.SelectItem;
2526
import org.springframework.shell.standard.AbstractShellComponent;
@@ -29,16 +30,17 @@
2930
@ShellComponent
3031
public class ComponentFlowCommands extends AbstractShellComponent {
3132

33+
@Autowired
34+
private ComponentFlow.Builder componentFlowBuilder;
35+
3236
@ShellMethod(key = "flow showcase", value = "Showcase", group = "Flow")
3337
public void showcase() {
3438
Map<String, String> single1SelectItems = new HashMap<>();
3539
single1SelectItems.put("key1", "value1");
3640
single1SelectItems.put("key2", "value2");
3741
List<SelectItem> multi1SelectItems = Arrays.asList(SelectItem.of("key1", "value1"),
3842
SelectItem.of("key2", "value2"), SelectItem.of("key3", "value3"));
39-
ComponentFlow flow = ComponentFlow.builder(getTerminal())
40-
.resourceLoader(getResourceLoader())
41-
.templateExecutor(getTemplateExecutor())
43+
ComponentFlow flow = componentFlowBuilder.clone().reset()
4244
.withStringInput("field1")
4345
.name("Field1")
4446
.defaultValue("defaultField1Value")
@@ -69,9 +71,7 @@ public void conditional() {
6971
Map<String, String> single1SelectItems = new HashMap<>();
7072
single1SelectItems.put("Field1", "field1");
7173
single1SelectItems.put("Field2", "field2");
72-
ComponentFlow flow = ComponentFlow.builder(getTerminal())
73-
.resourceLoader(getResourceLoader())
74-
.templateExecutor(getTemplateExecutor())
74+
ComponentFlow flow = componentFlowBuilder.clone().reset()
7575
.withSingleItemSelector("single1")
7676
.name("Single1")
7777
.selectItems(single1SelectItems)

0 commit comments

Comments
 (0)