Introduce ResultHandlerService
- Replace main use of ResultHandler with ResultHandlerService which is a framework type of impl for handlers found from conversion service. This handles types better and easier to handle with bean cycles, etc. - Removed IterableResultHandler to think about these use cases later when further refactoring is done. - TypeHierarchyResultHandler is removed and better functionality now via ResultHandlerService. - Relates #336
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
package org.springframework.shell.boot;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
@@ -33,8 +34,9 @@ import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.core.convert.converter.GenericConverter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.shell.ResultHandler;
|
||||
import org.springframework.shell.ResultHandlerService;
|
||||
import org.springframework.shell.Shell;
|
||||
import org.springframework.shell.result.IterableResultHandler;
|
||||
import org.springframework.shell.result.GenericResultHandlerService;
|
||||
import org.springframework.shell.result.ResultHandlerConfig;
|
||||
|
||||
/**
|
||||
@@ -71,8 +73,16 @@ public class SpringShellAutoConfiguration {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Shell shell(@Qualifier("main") ResultHandler resultHandler, @Qualifier("iterableResultHandler") IterableResultHandler iterableResultHandler) {
|
||||
iterableResultHandler.setDelegate(resultHandler);
|
||||
return new Shell(resultHandler);
|
||||
public ResultHandlerService resultHandlerService(Set<ResultHandler<?>> resultHandlers) {
|
||||
GenericResultHandlerService service = new GenericResultHandlerService();
|
||||
for (ResultHandler<?> resultHandler : resultHandlers) {
|
||||
service.addResultHandler(resultHandler);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Shell shell(ResultHandlerService resultHandlerService) {
|
||||
return new Shell(resultHandlerService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright 2021 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;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* A service interface for result handling.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public interface ResultHandlerService {
|
||||
|
||||
/**
|
||||
* Handle result.
|
||||
*
|
||||
* @param result the result
|
||||
*/
|
||||
void handle(@Nullable Object result);
|
||||
|
||||
/**
|
||||
* Handle result to the specified {@ resultType}.
|
||||
*
|
||||
* @param result the result
|
||||
* @param resultType the result type
|
||||
*/
|
||||
void handle(@Nullable Object result, @Nullable TypeDescriptor resultType);
|
||||
}
|
||||
@@ -55,10 +55,11 @@ import org.springframework.util.ReflectionUtils;
|
||||
* </p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class Shell {
|
||||
|
||||
private final ResultHandler resultHandler;
|
||||
private final ResultHandlerService resultHandlerService;
|
||||
|
||||
/**
|
||||
* Marker object returned to signify that there was no input to turn into a command
|
||||
@@ -84,8 +85,8 @@ public class Shell {
|
||||
*/
|
||||
protected static final Object UNRESOLVED = new Object();
|
||||
|
||||
public Shell(ResultHandler resultHandler) {
|
||||
this.resultHandler = resultHandler;
|
||||
public Shell(ResultHandlerService resultHandlerService) {
|
||||
this.resultHandlerService = resultHandlerService;
|
||||
}
|
||||
|
||||
@Autowired(required = false)
|
||||
@@ -125,7 +126,7 @@ public class Shell {
|
||||
if (e instanceof ExitRequest) { // Handles ExitRequest thrown from hitting CTRL-C
|
||||
break;
|
||||
}
|
||||
resultHandler.handleResult(e);
|
||||
resultHandlerService.handle(e);
|
||||
continue;
|
||||
}
|
||||
if (input == null) {
|
||||
@@ -134,7 +135,7 @@ public class Shell {
|
||||
|
||||
result = evaluate(input);
|
||||
if (result != NO_INPUT && !(result instanceof ExitRequest)) {
|
||||
resultHandler.handleResult(result);
|
||||
resultHandlerService.handle(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.springframework.shell.result;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
|
||||
public interface GenericResultHandler {
|
||||
|
||||
Set<Class<?>> getHandlerTypes();
|
||||
|
||||
void handle(Object result, TypeDescriptor resultType);
|
||||
|
||||
boolean matches(TypeDescriptor resultType);
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
/*
|
||||
* Copyright 2021 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.result;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.shell.ResultHandler;
|
||||
import org.springframework.shell.ResultHandlerService;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base {@ResultHandlerService} implementation suitable for use in most
|
||||
* environments.
|
||||
*
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
public class GenericResultHandlerService implements ResultHandlerService {
|
||||
|
||||
private final ResultHandlers resultHandlers = new ResultHandlers();
|
||||
|
||||
@Override
|
||||
public void handle(Object source) {
|
||||
handle(source, TypeDescriptor.forObject(source));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Object result, TypeDescriptor resultType) {
|
||||
if (result == null) {
|
||||
return;
|
||||
}
|
||||
GenericResultHandler handler = getResultHandler(resultType);
|
||||
if (handler != null) {
|
||||
invokeHandler(handler, result, resultType);
|
||||
return;
|
||||
}
|
||||
handleResultHandlerNotFound(result, resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plain result handler to this registry.
|
||||
*
|
||||
* @param resultHandler the result handler
|
||||
*/
|
||||
public void addResultHandler(ResultHandler<?> resultHandler) {
|
||||
ResolvableType[] typeInfo = getRequiredTypeInfo(resultHandler.getClass(), ResultHandler.class);
|
||||
if (typeInfo == null) {
|
||||
throw new IllegalArgumentException("Unable to determine result type <T> for your " +
|
||||
"ResultHandler [" + resultHandler.getClass().getName() + "]; does the class parameterize those types?");
|
||||
}
|
||||
addResultHandler(new ResultHandlerAdapter(resultHandler, typeInfo[0]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a plain result handler to this registry.
|
||||
*
|
||||
* @param <T> the type of result handler
|
||||
* @param resultType the class of a result type
|
||||
* @param resultHandler the result handler
|
||||
*/
|
||||
public <T> void addResultHandler(Class<T> resultType, ResultHandler<? super T> resultHandler) {
|
||||
addResultHandler(new ResultHandlerAdapter(resultHandler, ResolvableType.forClass(resultType)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a generic result handler this this registry.
|
||||
*
|
||||
* @param handler the generic result handler
|
||||
*/
|
||||
public void addResultHandler(GenericResultHandler handler) {
|
||||
this.resultHandlers.add(handler);
|
||||
}
|
||||
|
||||
private GenericResultHandler getResultHandler(TypeDescriptor resultType) {
|
||||
return this.resultHandlers.find(resultType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object handleResultHandlerNotFound(
|
||||
@Nullable Object source, @Nullable TypeDescriptor sourceType) {
|
||||
if (source == null) {
|
||||
return null;
|
||||
}
|
||||
if (sourceType == null) {
|
||||
return source;
|
||||
}
|
||||
throw new ResultHandlerNotFoundException(sourceType);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private ResolvableType[] getRequiredTypeInfo(Class<?> handlerClass, Class<?> genericIfc) {
|
||||
ResolvableType resolvableType = ResolvableType.forClass(handlerClass).as(genericIfc);
|
||||
ResolvableType[] generics = resolvableType.getGenerics();
|
||||
if (generics.length < 1) {
|
||||
return null;
|
||||
}
|
||||
Class<?> resultType = generics[0].resolve();
|
||||
if (resultType == null) {
|
||||
return null;
|
||||
}
|
||||
return generics;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private final static class ResultHandlerAdapter implements GenericResultHandler {
|
||||
|
||||
ResultHandler<Object> handler;
|
||||
Class<?> result;
|
||||
|
||||
public ResultHandlerAdapter(ResultHandler<?> handler, ResolvableType resultType) {
|
||||
this.handler = (ResultHandler<Object>) handler;
|
||||
this.result = resultType.toClass();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> getHandlerTypes() {
|
||||
return Collections.singleton(this.result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(Object result, TypeDescriptor resultType) {
|
||||
this.handler.handleResult(result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matches(TypeDescriptor resultType) {
|
||||
// always true until we create conditional handlers
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Manages handlers registered with a specific {@link Class}.
|
||||
*/
|
||||
private static class ResultHandlersForType {
|
||||
|
||||
private final Deque<GenericResultHandler> handlers = new ConcurrentLinkedDeque<>();
|
||||
|
||||
public void add(GenericResultHandler handler) {
|
||||
this.handlers.addFirst(handler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GenericResultHandler getHandler(TypeDescriptor resultType) {
|
||||
for (GenericResultHandler handler : this.handlers) {
|
||||
if (handler.matches(resultType)) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtils.collectionToCommaDelimitedString(this.handlers);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ResultHandlers {
|
||||
|
||||
private final Set<GenericResultHandler> globalHandlers = new CopyOnWriteArraySet<>();
|
||||
private final Map<Class<?>, ResultHandlersForType> handlers = new ConcurrentHashMap<>(16);
|
||||
|
||||
public void add(GenericResultHandler handler) {
|
||||
Set<Class<?>> handlerTypes = handler.getHandlerTypes();
|
||||
if (handlerTypes == null) {
|
||||
this.globalHandlers.add(handler);
|
||||
}
|
||||
else {
|
||||
for (Class<?> handlerType : handlerTypes) {
|
||||
getMatchableConverters(handlerType).add(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ResultHandlersForType getMatchableConverters(Class<?> handlerType) {
|
||||
return this.handlers.computeIfAbsent(handlerType, k -> new ResultHandlersForType());
|
||||
}
|
||||
|
||||
public GenericResultHandler find(TypeDescriptor resultType) {
|
||||
List<Class<?>> resultCandidates = getClassHierarchy(resultType.getType());
|
||||
for (Class<?> resultCandidate : resultCandidates) {
|
||||
GenericResultHandler handler = getRegisteredHandler(resultType, resultCandidate);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private GenericResultHandler getRegisteredHandler(TypeDescriptor resultType, Class<?> handlerType) {
|
||||
ResultHandlersForType resultHandlersForType = this.handlers.get(handlerType);
|
||||
if (resultHandlersForType != null) {
|
||||
GenericResultHandler handler = resultHandlersForType.getHandler(resultType);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
for (GenericResultHandler globalHandler : this.globalHandlers) {
|
||||
if (globalHandler.matches(resultType)) {
|
||||
return globalHandler;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<Class<?>> getClassHierarchy(Class<?> type) {
|
||||
List<Class<?>> hierarchy = new ArrayList<>(20);
|
||||
Set<Class<?>> visited = new HashSet<>(20);
|
||||
addToClassHierarchy(0, ClassUtils.resolvePrimitiveIfNecessary(type), false, hierarchy, visited);
|
||||
boolean array = type.isArray();
|
||||
|
||||
int i = 0;
|
||||
while (i < hierarchy.size()) {
|
||||
Class<?> candidate = hierarchy.get(i);
|
||||
candidate = (array ? candidate.getComponentType() : ClassUtils.resolvePrimitiveIfNecessary(candidate));
|
||||
Class<?> superclass = candidate.getSuperclass();
|
||||
if (superclass != null && superclass != Object.class && superclass != Enum.class) {
|
||||
addToClassHierarchy(i + 1, candidate.getSuperclass(), array, hierarchy, visited);
|
||||
}
|
||||
addInterfacesToClassHierarchy(candidate, array, hierarchy, visited);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(type)) {
|
||||
addToClassHierarchy(hierarchy.size(), Enum.class, array, hierarchy, visited);
|
||||
addToClassHierarchy(hierarchy.size(), Enum.class, false, hierarchy, visited);
|
||||
addInterfacesToClassHierarchy(Enum.class, array, hierarchy, visited);
|
||||
}
|
||||
|
||||
addToClassHierarchy(hierarchy.size(), Object.class, array, hierarchy, visited);
|
||||
addToClassHierarchy(hierarchy.size(), Object.class, false, hierarchy, visited);
|
||||
return hierarchy;
|
||||
}
|
||||
|
||||
private void addInterfacesToClassHierarchy(Class<?> type, boolean asArray,
|
||||
List<Class<?>> hierarchy, Set<Class<?>> visited) {
|
||||
for (Class<?> implementedInterface : type.getInterfaces()) {
|
||||
addToClassHierarchy(hierarchy.size(), implementedInterface, asArray, hierarchy, visited);
|
||||
}
|
||||
}
|
||||
|
||||
private void addToClassHierarchy(int index, Class<?> type, boolean asArray,
|
||||
List<Class<?>> hierarchy, Set<Class<?>> visited) {
|
||||
if (asArray) {
|
||||
type = Array.newInstance(type, 0).getClass();
|
||||
}
|
||||
if (visited.add(type)) {
|
||||
hierarchy.add(index, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void invokeHandler(GenericResultHandler handler, Object result, TypeDescriptor resultType) {
|
||||
handler.handle(result, resultType);;
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.result;
|
||||
|
||||
import org.springframework.shell.ResultHandler;
|
||||
|
||||
/**
|
||||
* A {@link ResultHandler} that deals with {@link Iterable}s and delegates to
|
||||
* {@link TypeHierarchyResultHandler} for each element in turn.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class IterableResultHandler implements ResultHandler<Iterable> {
|
||||
|
||||
private ResultHandler delegate;
|
||||
|
||||
// Setter injection to avoid circular dependency at creation time
|
||||
public void setDelegate(ResultHandler delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleResult(Iterable result) {
|
||||
for (Object o : result) {
|
||||
delegate.handleResult(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
* Copyright 2017-2021 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.
|
||||
@@ -16,33 +16,20 @@
|
||||
|
||||
package org.springframework.shell.result;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.shell.ResultHandler;
|
||||
import org.springframework.shell.TerminalSizeAware;
|
||||
|
||||
/**
|
||||
* Used for explicit configuration of {@link org.springframework.shell.ResultHandler}s.
|
||||
*
|
||||
* @author Eric Bottard
|
||||
* @author Janne Valkealahti
|
||||
*/
|
||||
@Configuration
|
||||
public class ResultHandlerConfig {
|
||||
|
||||
@Bean
|
||||
@Qualifier("main")
|
||||
public ResultHandler<?> mainResultHandler() {
|
||||
return new TypeHierarchyResultHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Qualifier("iterableResultHandler")
|
||||
public IterableResultHandler iterableResultHandler() {
|
||||
return new IterableResultHandler();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnClass(TerminalSizeAware.class)
|
||||
public TerminalSizeAwareResultHandler terminalSizeAwareResultHandler() {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2021 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.result;
|
||||
|
||||
import org.springframework.core.convert.TypeDescriptor;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public class ResultHandlerNotFoundException extends ResultHandlingException {
|
||||
|
||||
@Nullable
|
||||
private final TypeDescriptor resultType;
|
||||
|
||||
/**
|
||||
* Create a new handling executor not found exception.
|
||||
*
|
||||
* @param resultType the result type requested to handle from
|
||||
*/
|
||||
public ResultHandlerNotFoundException(@Nullable TypeDescriptor resultType) {
|
||||
super("No handler found capable of handling from type [" + resultType + "]");
|
||||
this.resultType = resultType;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the source type that was requested to convert from.
|
||||
*/
|
||||
@Nullable
|
||||
public TypeDescriptor getResultType() {
|
||||
return this.resultType;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright 2021 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.result;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
public class ResultHandlingException extends NestedRuntimeException {
|
||||
|
||||
/**
|
||||
* Construct a new result handling exception.
|
||||
*
|
||||
* @param message the exception message
|
||||
*/
|
||||
public ResultHandlingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new result handling exception.
|
||||
*
|
||||
* @param message the exception message
|
||||
* @param cause the cause
|
||||
*/
|
||||
public ResultHandlingException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
* 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.result;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.shell.ResultHandler;
|
||||
|
||||
/**
|
||||
* A delegating {@link ResultHandler} that dispatches handling based on the type of the result.
|
||||
* <p>
|
||||
* If no direct match is found, the type hierarchy of the result is considered, including implemented interfaces.
|
||||
* Auto-populates the handler map based on Generics type declaration of each discovered {@link ResultHandler} in the
|
||||
* ApplicationContext.
|
||||
* </p>
|
||||
*
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
public class TypeHierarchyResultHandler implements ResultHandler<Object> {
|
||||
|
||||
private Map<Class<?>, ResultHandler<?>> resultHandlers = new HashMap<>();
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void handleResult(Object result) {
|
||||
if (result == null) { // void methods
|
||||
return;
|
||||
}
|
||||
Class<?> clazz = result.getClass();
|
||||
ResultHandler handler = getResultHandler(clazz);
|
||||
handler.handleResult(result);
|
||||
}
|
||||
|
||||
private ResultHandler getResultHandler(Class<?> clazz) {
|
||||
ResultHandler handler = resultHandlers.get(clazz);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
else {
|
||||
for (Class type : clazz.getInterfaces()) {
|
||||
handler = getResultHandler(type);
|
||||
if (handler != null) {
|
||||
return handler;
|
||||
}
|
||||
}
|
||||
return clazz.getSuperclass() != null ? getResultHandler(clazz.getSuperclass()) : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public void setResultHandlers(Set<ResultHandler<?>> resultHandlers) {
|
||||
for (ResultHandler<?> resultHandler : resultHandlers) {
|
||||
ResolvableType type = ResolvableType.forInstance(resultHandler).as(ResultHandler.class);
|
||||
registerHandler(type.resolveGeneric(0), resultHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerHandler(Class<?> type, ResultHandler<?> resultHandler) {
|
||||
ResultHandler<?> previous = this.resultHandlers.put(type, resultHandler);
|
||||
if (previous != null) {
|
||||
throw new IllegalArgumentException(String.format("Multiple ResultHandlers configured for %s: both %s and %s", type, previous, resultHandler));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -46,17 +46,13 @@ import static org.mockito.Mockito.when;
|
||||
* @author Eric Bottard
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
// @RunWith(JUnitPlatform.class)
|
||||
public class ShellTest {
|
||||
|
||||
// @Rule
|
||||
// public MockitoRule mockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private InputProvider inputProvider;
|
||||
|
||||
@Mock
|
||||
private ResultHandler resultHandler;
|
||||
ResultHandlerService resultHandlerService;
|
||||
|
||||
@Mock
|
||||
private ParameterResolver parameterResolver;
|
||||
@@ -79,7 +75,7 @@ public class ShellTest {
|
||||
when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?", null);
|
||||
valueResult = new ValueResult(null, "test");
|
||||
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
|
||||
doThrow(new Exit()).when(resultHandler).handleResult(any());
|
||||
doThrow(new Exit()).when(resultHandlerService).handle(any());
|
||||
|
||||
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
|
||||
|
||||
@@ -97,7 +93,7 @@ public class ShellTest {
|
||||
@Test
|
||||
public void commandNotFound() throws IOException {
|
||||
when(inputProvider.readInput()).thenReturn(() -> "hello world how are you doing ?", null);
|
||||
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
|
||||
doThrow(new Exit()).when(resultHandlerService).handle(isA(CommandNotFound.class));
|
||||
|
||||
shell.methodTargets = Collections.singletonMap("bonjour", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
|
||||
|
||||
@@ -114,7 +110,7 @@ public class ShellTest {
|
||||
// See https://github.com/spring-projects/spring-shell/issues/142
|
||||
public void commandNotFoundPrefix() throws IOException {
|
||||
when(inputProvider.readInput()).thenReturn(() -> "helloworld how are you doing ?", null);
|
||||
doThrow(new Exit()).when(resultHandler).handleResult(isA(CommandNotFound.class));
|
||||
doThrow(new Exit()).when(resultHandlerService).handle(isA(CommandNotFound.class));
|
||||
|
||||
shell.methodTargets = Collections.singletonMap("hello", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
|
||||
|
||||
@@ -133,7 +129,7 @@ public class ShellTest {
|
||||
when(inputProvider.readInput()).thenReturn(() -> "", () -> "hello world how are you doing ?", null);
|
||||
valueResult = new ValueResult(null, "test");
|
||||
when(parameterResolver.resolve(any(), any())).thenReturn(valueResult);
|
||||
doThrow(new Exit()).when(resultHandler).handleResult(any());
|
||||
doThrow(new Exit()).when(resultHandlerService).handle(any());
|
||||
|
||||
shell.methodTargets = Collections.singletonMap("hello world", MethodTarget.of("helloWorld", this, new Command.Help("Say hello")));
|
||||
|
||||
@@ -151,7 +147,7 @@ public class ShellTest {
|
||||
@Test
|
||||
public void commandThrowingAnException() throws IOException {
|
||||
when(inputProvider.readInput()).thenReturn(() -> "fail", null);
|
||||
doThrow(new Exit()).when(resultHandler).handleResult(isA(SomeException.class));
|
||||
doThrow(new Exit()).when(resultHandlerService).handle(isA(SomeException.class));
|
||||
|
||||
shell.methodTargets = Collections.singletonMap("fail", MethodTarget.of("failing", this, new Command.Help("Will throw an exception")));
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2021 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.jline;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.shell.ResultHandler;
|
||||
import org.springframework.shell.result.GenericResultHandlerService;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class GenericResultHandlerServiceTests {
|
||||
|
||||
@Test
|
||||
public void testSimpleHandling() {
|
||||
StringResultHandler stringResultHandler = new StringResultHandler();
|
||||
IntegerResultHandler integerResultHandler = new IntegerResultHandler();
|
||||
GenericResultHandlerService resultHandlerService = new GenericResultHandlerService();
|
||||
resultHandlerService.addResultHandler(stringResultHandler);
|
||||
resultHandlerService.addResultHandler(integerResultHandler);
|
||||
resultHandlerService.handle("string");
|
||||
assertThat(stringResultHandler.result).isEqualTo("string");
|
||||
assertThat(integerResultHandler.result).isNull();;
|
||||
resultHandlerService.handle(0);
|
||||
assertThat(integerResultHandler.result).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testObjectHandling() {
|
||||
ObjectResultHandler resultHandler = new ObjectResultHandler();
|
||||
GenericResultHandlerService resultHandlerService = new GenericResultHandlerService();
|
||||
resultHandlerService.addResultHandler(resultHandler);
|
||||
resultHandlerService.handle("string");
|
||||
assertThat(resultHandler.result).isEqualTo("string");
|
||||
}
|
||||
|
||||
private static class StringResultHandler implements ResultHandler<String> {
|
||||
|
||||
String result;
|
||||
|
||||
@Override
|
||||
public void handleResult(String result) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class IntegerResultHandler implements ResultHandler<Integer> {
|
||||
|
||||
Integer result;
|
||||
|
||||
@Override
|
||||
public void handleResult(Integer result) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ObjectResultHandler implements ResultHandler<Object> {
|
||||
|
||||
Object result;
|
||||
|
||||
@Override
|
||||
public void handleResult(Object result) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user