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
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2022 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.shell.boot;
|
||||
|
||||
import org.jline.terminal.Terminal;
|
||||
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.springframework.shell.component.flow.ComponentFlow;
|
||||
import org.springframework.shell.component.flow.ComponentFlow.Builder;
|
||||
import org.springframework.shell.style.TemplateExecutor;
|
||||
|
||||
/**
|
||||
* {@link EnableAutoConfiguration Auto-configuration} for {@link ComponentFlow}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(ComponentFlow.class)
|
||||
public class ComponentFlowAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@Scope("prototype")
|
||||
@ConditionalOnMissingBean
|
||||
public ComponentFlow.Builder componentFlowBuilder(ObjectProvider<ComponentFlowCustomizer> customizerProvider) {
|
||||
ComponentFlow.Builder builder = ComponentFlow.builder();
|
||||
customizerProvider.orderedStream().forEach((customizer) -> customizer.customize(builder));
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
protected static class ComponentFlowConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@Order(0)
|
||||
public ComponentFlowCustomizer exchangeStrategiesCustomizer(ObjectProvider<Terminal> terminal,
|
||||
ObjectProvider<ResourceLoader> resourceLoader, ObjectProvider<TemplateExecutor> templateExecutor) {
|
||||
return new CommonComponentFlowCustomizer(terminal, resourceLoader, templateExecutor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static class CommonComponentFlowCustomizer implements ComponentFlowCustomizer {
|
||||
|
||||
private final ObjectProvider<Terminal> terminal;
|
||||
private final ObjectProvider<ResourceLoader> resourceLoader;
|
||||
private final ObjectProvider<TemplateExecutor> templateExecutor;
|
||||
|
||||
CommonComponentFlowCustomizer(ObjectProvider<Terminal> terminal, ObjectProvider<ResourceLoader> resourceLoader,
|
||||
ObjectProvider<TemplateExecutor> templateExecutor) {
|
||||
this.terminal = terminal;
|
||||
this.resourceLoader = resourceLoader;
|
||||
this.templateExecutor = templateExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void customize(Builder componentFlowBuilder) {
|
||||
terminal.ifAvailable(dep -> componentFlowBuilder.terminal(dep));
|
||||
resourceLoader.ifAvailable(dep -> componentFlowBuilder.resourceLoader(dep));
|
||||
templateExecutor.ifAvailable(dep -> componentFlowBuilder.templateExecutor(dep));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2022 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.shell.boot;
|
||||
|
||||
import org.springframework.shell.component.flow.ComponentFlow;
|
||||
|
||||
/**
|
||||
* Callback interface that can be used to customize a {@link ComponentFlow.Builder}.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ComponentFlowCustomizer {
|
||||
|
||||
/**
|
||||
* Callback to customize a {@link ComponentFlow.Builder} instance.
|
||||
* @param componentFlowBuilder the component flow builder to customize
|
||||
*/
|
||||
void customize(ComponentFlow.Builder componentFlowBuilder);
|
||||
}
|
||||
@@ -12,4 +12,5 @@ org.springframework.shell.boot.JCommanderParameterResolverAutoConfiguration,\
|
||||
org.springframework.shell.boot.ParameterResolverAutoConfiguration,\
|
||||
org.springframework.shell.boot.StandardAPIAutoConfiguration,\
|
||||
org.springframework.shell.boot.ThemingAutoConfiguration,\
|
||||
org.springframework.shell.boot.StandardCommandsAutoConfiguration
|
||||
org.springframework.shell.boot.StandardCommandsAutoConfiguration,\
|
||||
org.springframework.shell.boot.ComponentFlowAutoConfiguration
|
||||
|
||||
@@ -70,11 +70,10 @@ public interface ComponentFlow {
|
||||
/**
|
||||
* Gets a new instance of an input wizard builder.
|
||||
*
|
||||
* @param terminal the terminal
|
||||
* @return the input wizard builder
|
||||
*/
|
||||
public static Builder builder(Terminal terminal) {
|
||||
return new DefaultBuilder(terminal);
|
||||
public static Builder builder() {
|
||||
return new DefaultBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +134,14 @@ public interface ComponentFlow {
|
||||
*/
|
||||
MultiItemSelectorSpec withMultiItemSelector(String id);
|
||||
|
||||
/**
|
||||
* Sets a {@link Terminal}.
|
||||
*
|
||||
* @param terminal the terminal
|
||||
* @return a builder
|
||||
*/
|
||||
Builder terminal(Terminal terminal);
|
||||
|
||||
/**
|
||||
* Sets a {@link ResourceLoader}.
|
||||
*
|
||||
@@ -151,6 +158,20 @@ public interface ComponentFlow {
|
||||
*/
|
||||
Builder templateExecutor(TemplateExecutor templateExecutor);
|
||||
|
||||
/**
|
||||
* Clones existing builder.
|
||||
*
|
||||
* @return a builder
|
||||
*/
|
||||
Builder clone();
|
||||
|
||||
/**
|
||||
* Resets existing builder.
|
||||
*
|
||||
* @return a builder
|
||||
*/
|
||||
Builder reset();
|
||||
|
||||
/**
|
||||
* Builds instance of input wizard.
|
||||
*
|
||||
@@ -161,7 +182,6 @@ public interface ComponentFlow {
|
||||
|
||||
static abstract class BaseBuilder implements Builder {
|
||||
|
||||
private Terminal terminal;
|
||||
private final List<BaseStringInput> stringInputs = new ArrayList<>();
|
||||
private final List<BasePathInput> pathInputs = new ArrayList<>();
|
||||
private final List<BaseConfirmationInput> confirmationInputs = new ArrayList<>();
|
||||
@@ -169,11 +189,11 @@ public interface ComponentFlow {
|
||||
private final List<BaseMultiItemSelector> multiItemSelectors = new ArrayList<>();
|
||||
private final AtomicInteger order = new AtomicInteger();
|
||||
private final HashSet<String> uniqueIds = new HashSet<>();
|
||||
private Terminal terminal;
|
||||
private ResourceLoader resourceLoader;
|
||||
private TemplateExecutor templateExecutor;
|
||||
|
||||
BaseBuilder(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
BaseBuilder() {
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -207,6 +227,12 @@ public interface ComponentFlow {
|
||||
return new DefaultMultiInputSpec(this, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder terminal(Terminal terminal) {
|
||||
this.terminal = terminal;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder resourceLoader(ResourceLoader resourceLoader) {
|
||||
this.resourceLoader = resourceLoader;
|
||||
@@ -219,6 +245,23 @@ public interface ComponentFlow {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder clone() {
|
||||
return new DefaultBuilder(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder reset() {
|
||||
stringInputs.clear();
|
||||
pathInputs.clear();
|
||||
confirmationInputs.clear();
|
||||
singleItemSelectors.clear();
|
||||
multiItemSelectors.clear();
|
||||
order.set(0);
|
||||
uniqueIds.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
void addStringInput(BaseStringInput input) {
|
||||
checkUniqueId(input.getId());
|
||||
input.setOrder(order.getAndIncrement());
|
||||
@@ -249,6 +292,10 @@ public interface ComponentFlow {
|
||||
multiItemSelectors.add(input);
|
||||
}
|
||||
|
||||
Terminal getTerminal() {
|
||||
return terminal;
|
||||
}
|
||||
|
||||
ResourceLoader getResourceLoader() {
|
||||
return resourceLoader;
|
||||
}
|
||||
@@ -267,8 +314,14 @@ public interface ComponentFlow {
|
||||
|
||||
static class DefaultBuilder extends BaseBuilder {
|
||||
|
||||
DefaultBuilder(Terminal terminal) {
|
||||
super(terminal);
|
||||
DefaultBuilder() {
|
||||
super();
|
||||
}
|
||||
|
||||
DefaultBuilder(BaseBuilder other) {
|
||||
terminal(other.getTerminal());
|
||||
resourceLoader(other.getResourceLoader());
|
||||
templateExecutor(other.getTemplateExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,8 @@ public class ComponentFlowTests extends AbstractShellTests {
|
||||
single1SelectItems.put("key2", "value2");
|
||||
List<SelectItem> multi1SelectItems = Arrays.asList(SelectItem.of("key1", "value1"),
|
||||
SelectItem.of("key2", "value2"), SelectItem.of("key3", "value3"));
|
||||
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
.withStringInput("field1")
|
||||
@@ -107,7 +108,8 @@ public class ComponentFlowTests extends AbstractShellTests {
|
||||
|
||||
@Test
|
||||
public void testSkipsGivenComponents() throws InterruptedException {
|
||||
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.withStringInput("id1")
|
||||
.name("name")
|
||||
.resultValue("value1")
|
||||
@@ -154,7 +156,8 @@ public class ComponentFlowTests extends AbstractShellTests {
|
||||
|
||||
@Test
|
||||
public void testChoosesDynamically() throws InterruptedException {
|
||||
ComponentFlow wizard = ComponentFlow.builder(getTerminal())
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
.withStringInput("id1")
|
||||
|
||||
@@ -20,6 +20,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.shell.component.flow.ComponentFlow;
|
||||
import org.springframework.shell.component.flow.SelectItem;
|
||||
import org.springframework.shell.standard.AbstractShellComponent;
|
||||
@@ -29,6 +30,9 @@ import org.springframework.shell.standard.ShellMethod;
|
||||
@ShellComponent
|
||||
public class ComponentFlowCommands extends AbstractShellComponent {
|
||||
|
||||
@Autowired
|
||||
private ComponentFlow.Builder componentFlowBuilder;
|
||||
|
||||
@ShellMethod(key = "flow showcase", value = "Showcase", group = "Flow")
|
||||
public void showcase() {
|
||||
Map<String, String> single1SelectItems = new HashMap<>();
|
||||
@@ -36,9 +40,7 @@ public class ComponentFlowCommands extends AbstractShellComponent {
|
||||
single1SelectItems.put("key2", "value2");
|
||||
List<SelectItem> multi1SelectItems = Arrays.asList(SelectItem.of("key1", "value1"),
|
||||
SelectItem.of("key2", "value2"), SelectItem.of("key3", "value3"));
|
||||
ComponentFlow flow = ComponentFlow.builder(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
ComponentFlow flow = componentFlowBuilder.clone().reset()
|
||||
.withStringInput("field1")
|
||||
.name("Field1")
|
||||
.defaultValue("defaultField1Value")
|
||||
@@ -69,9 +71,7 @@ public class ComponentFlowCommands extends AbstractShellComponent {
|
||||
Map<String, String> single1SelectItems = new HashMap<>();
|
||||
single1SelectItems.put("Field1", "field1");
|
||||
single1SelectItems.put("Field2", "field2");
|
||||
ComponentFlow flow = ComponentFlow.builder(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
ComponentFlow flow = componentFlowBuilder.clone().reset()
|
||||
.withSingleItemSelector("single1")
|
||||
.name("Single1")
|
||||
.selectItems(single1SelectItems)
|
||||
|
||||
Reference in New Issue
Block a user