Moving the project to a multi-level maven project to allow for separation of the adapter from the core of the project

Fixes #28
This commit is contained in:
camilojc
2017-05-17 22:59:54 +01:00
committed by Eric Bottard
parent 3313140ecb
commit 9ca98cdd7d
65 changed files with 295 additions and 48 deletions

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2017 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
*
* http://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.shell2.legacy;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.shell.converters.ArrayConverter;
import org.springframework.shell.converters.AvailableCommandsConverter;
import org.springframework.shell.converters.SimpleFileConverter;
/**
* Main configuration class for the Shell 2 - Shell 1 adapter.
*
* @author Camilo Gonzalez
*/
@Configuration
@ComponentScan(basePackageClasses = {ArrayConverter.class}, excludeFilters = @ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {AvailableCommandsConverter.class, SimpleFileConverter.class}))
public class LegacyConfiguration {
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ApplicationContext;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell2.MethodTarget;
import org.springframework.shell2.MethodTargetResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
/**
* A {@link MethodTargetResolver} that discovers methods annotated with {@link CliCommand} on beans
* implementing the {@link CommandMarker} marker interface.
* @author Eric Bottard
* @author Florent Biville
*/
@Component
public class LegacyMethodTargetResolver implements MethodTargetResolver {
@Override
public Map<String, MethodTarget> resolve(ApplicationContext context) {
Map<String, MethodTarget> methodTargets = new HashMap<>();
Map<String, CommandMarker> beans = context.getBeansOfType(CommandMarker.class);
for (Object bean : beans.values()) {
Class<?> clazz = bean.getClass();
ReflectionUtils.doWithMethods(clazz, method -> {
CliCommand cliCommand = method.getAnnotation(CliCommand.class);
for (String key : cliCommand.value()) {
methodTargets.put(key, new MethodTarget(method, bean, cliCommand.help()));
}
}, method -> method.getAnnotation(CliCommand.class) != null);
}
return methodTargets;
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.shell.core.Converter;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.shell2.CompletionContext;
import org.springframework.shell2.CompletionProposal;
import org.springframework.shell2.ParameterDescription;
import org.springframework.shell2.ParameterResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
/**
* Resolves parameters by looking at the {@link CliOption} annotation and acting accordingly.
*
* @author Eric Bottard
*/
@Component
public class LegacyParameterResolver implements ParameterResolver {
@Autowired(required = false)
private Collection<Converter<?>> converters = new ArrayList<>();
@Override
public boolean supports(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CliOption.class);
}
@Override
public Object resolve(MethodParameter methodParameter, List<String> words) {
CliOption cliOption = methodParameter.getParameterAnnotation(CliOption.class);
Optional<Converter<?>> converter = converters.stream()
.filter(c -> c.supports(methodParameter.getParameterType(), cliOption.optionContext()))
.findFirst();
Map<String, String> values = parseOptions(words);
Map<String, Object> seenValues = convertValues(values, methodParameter, converter);
switch (seenValues.size()) {
case 0:
if (!cliOption.mandatory()) {
String value = cliOption.unspecifiedDefaultValue();
return converter
.orElseThrow(noConverterFound(cliOption.key()[0], value, methodParameter.getParameterType()))
.convertFromText(value, methodParameter.getParameterType(), cliOption.optionContext());
}
else {
throw new IllegalArgumentException("Could not find parameter values for " + prettifyKeys(Arrays.asList(cliOption.key())) + " in " + words);
}
case 1:
return seenValues.values().iterator().next();
default:
throw new RuntimeException("Option has been set multiple times via " + prettifyKeys(seenValues.keySet()));
}
}
@Override
public ParameterDescription describe(MethodParameter parameter) {
throw new UnsupportedOperationException();
}
@Override
public List<CompletionProposal> complete(MethodParameter parameter, CompletionContext context) {
return null;
}
private Map<String, String> parseOptions(List<String> words) {
Map<String, String> values = new HashMap<>();
for (int i = 0; i < words.size(); i++) {
String word = words.get(i);
if (word.startsWith("--")) {
String key = word.substring("--".length());
// If next word doesn't exist or starts with '--', this is an unary option. Store null
String value = i < words.size() - 1 && !words.get(i + 1).startsWith("--") ? words.get(++i) : null;
Assert.isTrue(!values.containsKey(key), String.format("Option --%s has already been set", key));
values.put(key, value);
} // Must be the 'anonymous' option
else {
Assert.isTrue(!values.containsKey(""), "Anonymous option has already been set");
values.put("", word);
}
}
return values;
}
private Map<String, Object> convertValues(Map<String, String> values, MethodParameter methodParameter, Optional<Converter<?>> converter) {
Map<String, Object> seenValues = new HashMap<>();
CliOption option = methodParameter.getParameterAnnotation(CliOption.class);
for (String key : option.key()) {
if (values.containsKey(key)) {
String value = values.get(key);
if (value == null && !"__NULL__".equals(option.specifiedDefaultValue())) {
value = option.specifiedDefaultValue();
}
Class<?> parameterType = methodParameter.getParameterType();
seenValues.put(key, converter
.orElseThrow(noConverterFound(key, value, parameterType))
.convertFromText(value, parameterType, option.optionContext()));
}
}
return seenValues;
}
/**
* Return the list of possible keys for an option, suitable for displaying in an error message.
*/
private String prettifyKeys(Collection<String> keys) {
return keys.stream().map(s -> "".equals(s) ? "<anonymous>" : "--" + s).collect(Collectors.joining(", ", "[", "]"));
}
private Supplier<IllegalStateException> noConverterFound(String key, String value, Class<?> parameterType) {
return () -> new IllegalStateException("No converter found for --" + key + " from '" + value + "' to type " + parameterType);
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright 2017 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
*
* http://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.
*/
/**
* Provides integration with Spring Shell 1.
*/
package org.springframework.shell2.legacy;

View File

@@ -0,0 +1,25 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
/**
* Created by ericbottard on 09/12/15.
*/
public enum ArtifactType {
source, processor, sink, task
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
import java.lang.reflect.Method;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.util.ReflectionUtils;
/**
* Created by ericbottard on 09/12/15.
*/
public class LegacyCommands implements CommandMarker {
public static final Method REGISTER_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "register", String.class, ArtifactType.class, String.class, boolean.class);
public static final Method SUM_METHOD = ReflectionUtils.findMethod(LegacyCommands.class, "sum", int.class, int.class);
@CliCommand(value = "register module", help = "Register a new module")
public String register(
@CliOption(mandatory = true,
key = {"", "name"},
help = "the name for the registered module")
String name,
@CliOption(mandatory = true,
key = {"type"},
help = "the type for the registered module")
ArtifactType type,
@CliOption(mandatory = true,
key = {"coordinates", "coords"},
help = "coordinates to the module archive")
String coordinates,
@CliOption(key = "force",
help = "force update if module already exists (only if not in use)",
specifiedDefaultValue = "true",
unspecifiedDefaultValue = "false")
boolean force) {
return String.format(("Successfully registered module '%s:%s'"), type, name);
}
@CliCommand(value = "sum", help = "adds two numbers")
public int sum(@CliOption(key = "v1", unspecifiedDefaultValue = "38") int a, @CliOption(key = "v2", specifiedDefaultValue = "42") int b) {
return a + b;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.shell2.MethodTarget;
import org.springframework.shell2.MethodTargetResolver;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
/**
* Created by ericbottard on 09/12/15.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LegacyMethodTargetResolverTest.Config.class)
public class LegacyMethodTargetResolverTest {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private LegacyCommands legacyCommands;
@Autowired
private MethodTargetResolver resolver;
@Test
public void findsMethodsAnnotatedWithCliCommand() throws Exception {
Map<String, MethodTarget> targets = resolver.resolve(applicationContext);
assertThat(targets).contains(entry(
"register module",
new MethodTarget(LegacyCommands.REGISTER_METHOD, legacyCommands, "Register a new module" )
));
}
@Configuration
static class Config {
@Bean
public LegacyCommands legacyCommands() {
return new LegacyCommands();
}
@Bean
public MethodTargetResolver methodTargetResolver() {
return new LegacyMethodTargetResolver();
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright 2015 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
*
* http://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.shell2.legacy;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.shell2.legacy.LegacyCommands.REGISTER_METHOD;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.MethodParameter;
import org.springframework.shell.converters.BooleanConverter;
import org.springframework.shell.converters.EnumConverter;
import org.springframework.shell.converters.StringConverter;
import org.springframework.shell.core.Converter;
import org.springframework.shell2.ParameterResolver;
import org.springframework.shell2.Utils;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = LegacyParameterResolverTest.Config.class)
public class LegacyParameterResolverTest {
private static final int NAME_OR_ANONYMOUS = 0;
private static final int TYPE = 1;
private static final int COORDINATES = 2;
private static final int FORCE = 3;
@Autowired
ParameterResolver parameterResolver;
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void supportsParameterAnnotatedWithCliOption() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
boolean result = parameterResolver.supports(methodParameter);
assertThat(result).isTrue();
}
@Test
public void resolvesParameterAnnotatedWithCliOption() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
assertThat(result).isEqualTo("baz");
}
@Test
public void resolvesAnonymousParameterAnnotatedWithCliOption() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, NAME_OR_ANONYMOUS);
Object result = resolve(methodParameter, "--foo bar baz --qix bux");
assertThat(result).isEqualTo("baz");
// As first param
result = resolve(methodParameter, "baz --foo bar --qix bux");
assertThat(result).isEqualTo("baz");
}
@Test
public void usesLegacyConverters() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, TYPE);
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux --type processor");
assertThat(result).isSameAs(ArtifactType.processor);
}
@Test
public void testUnspecifiedDefaultValue() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
Object result = resolve(methodParameter, "--foo bar --name baz --qix bux");
assertThat(result).isEqualTo(false);
}
@Test
public void testSpecifiedDefaultValue() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, FORCE);
assertThat(resolve(methodParameter, "--force --foo bar --name baz --qix bux")).isEqualTo(true);
assertThat(resolve(methodParameter, "--foo bar --name baz --qix bux --force")).isEqualTo(true);
}
@Test
public void testParameterNotFound() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Could not find parameter values for [--coordinates, --coords] in [--force, --foo, bar, --name, baz, --qix, bux]");
resolve(methodParameter, "--force --foo bar --name baz --qix bux");
}
@Test
public void testParameterFoundWithSameNameTooManyTimes() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(REGISTER_METHOD, COORDINATES);
thrown.expect(IllegalArgumentException.class);
thrown.expectMessage("Option --coordinates has already been set");
resolve(methodParameter, "--force --coordinates bar --coordinates baz --qix bux");
}
@Test
public void testNoConverterFound() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("No converter found for --v1 from '1' to type int");
resolve(methodParameter, "--v1 1 --v2 2");
}
@Test
public void testNoConverterFoundForUnspecifiedValue() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 0);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("No converter found for --v1 from '38' to type int");
resolve(methodParameter, "--v2 2");
}
@Test
public void testNoConverterFoundForSpecifiedValue() throws Exception {
MethodParameter methodParameter = Utils.createMethodParameter(LegacyCommands.SUM_METHOD, 1);
thrown.expect(IllegalStateException.class);
thrown.expectMessage("No converter found for --v2 from '42' to type int");
resolve(methodParameter, "--v1 1 --v2");
}
private Object resolve(MethodParameter methodParameter, String command) {
return parameterResolver.resolve(methodParameter, asList(command.split(" ")));
}
@Configuration
static class Config {
@Bean
public Converter<String> stringConverter() {
return new StringConverter();
}
@Bean
public Converter<Boolean> booleanConverter() {
return new BooleanConverter();
}
@Bean
public Converter<Enum<?>> enumConverter() {
return new EnumConverter();
}
@Bean
public ParameterResolver parameterResolver() {
return new LegacyParameterResolver();
}
}
}