Amigos,
Houve mais uma situação que acho útil citar aqui. Como tinha dito não é necessario ter um controller responsavel por cada url/pagina, como normalmente fariamos no VRaptor, pois o Spring WebFlow vai encontrar os jsps, invocar os beans que venham a ser necessários antes da renderizacao da pagina, etc. Mas precisei criar um controller que era invocado via ajax, e nesse controller precisava acessar os dados no escopo “flow”.
Esse escopo é gerenciado pelo WebFlow durante a execução do fluxo e é o que permite manter o estado de objetos durante varias requisicoes, e o framework também se encarrega de limpar esse escopo ao fim do fluxo. Os beans incluidos nesse escopo são gerenciados a parte pelo WebFlow e não são necessariamente “beans gerenciados” pelo Spring; na verdade são gerenciados pelo WebFlow meio “por fora” do IOC-Container e não é possível injeta-los em ontros beans.
Enfim, eu precisava desse objeto do escopo “flow” dentro do controller, e fiz da seguinte forma:
Uma anotação de parametro:
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Flow {
}
Um interceptor:
@Intercepts(before=ParametersInstantiatorInterceptor.class)
@Lazy
public class WebFlowParametersInstantiatorInterceptor implements Interceptor {
private final WebFlowParameterResolver webFlowParameterResolver;
public WebFlowParametersInstantiatorInterceptor(WebFlowParameterResolver webFlowParameterResolver){
this.webFlowParameterResolver = webFlowParameterResolver;
}
@Override
public void intercept(InterceptorStack stack, ResourceMethod method, Object resourceInstance) throws InterceptionException {
webFlowParameterResolver.resolve(method.getMethod());
stack.next(method, resourceInstance);
}
@Override
public boolean accepts(ResourceMethod method) {
return hasFlowParameters(method.getMethod());
}
private boolean hasFlowParameters(Method method){
if (method.getParameterTypes().length == 0)
return false;
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (Annotation[] annotations : parameterAnnotations){
for (Annotation annotation : annotations){
if (annotation instanceof Flow) return true;
}
}
return false;
}
}
E um componente que lê o escopo “flow” para buscar os beans
@Component @RequestScoped
public class WebFlowParameterResolver {
private final FlowExecutor flowExecutor;
private final HttpServletRequest request;
private final HttpServletResponse response;
private final ServletContext servletContext;
private final FlowUrlHandler flowUrlHandler = new DefaultFlowUrlHandler();
public WebFlowParameterResolver(FlowExecutor flowExecutor, HttpServletRequest request, HttpServletResponse response, ServletContext servletContext){
this.flowExecutor = flowExecutor;
this.request = request;
this.response = response;
this.servletContext = servletContext;
}
private FlowExecutionRepository flowRepository(){
return ((FlowExecutorImpl) flowExecutor).getExecutionRepository();
}
private FlowExecution currentFlowExecution(){
String key = flowUrlHandler.getFlowExecutionKey(request);
if (key == null)
throw new IllegalArgumentException("No flow execution key found in request");
FlowExecutionRepository repository = flowRepository();
FlowExecutionKey executionKey = repository.parseFlowExecutionKey(key);
return repository.getFlowExecution(executionKey);
}
private Map<?, ?> flowScope(){
FlowExecution flowExecution = currentFlowExecution();
return flowExecution.getActiveSession().getScope().asMap();
}
private void resolveParameters(Method method){
Map<?, ?> flowScope = flowScope();
Class<?>[] parameterTypes = method.getParameterTypes();
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
int i = 0;
for (Annotation[] annotations : parameterAnnotations){
Class<?> parameterType = parameterTypes[i++];
for (Annotation annotation : annotations){
if (annotation instanceof Flow){
resolveParameter(parameterType, flowScope);
}
}
}
}
private void resolveParameter(Class<?> parameterType, Map<?, ?> flowScope){
Map<?, ?> values = filterValues(flowScope, instanceOf(parameterType));
if (values.size() != 0){
for (Entry<?, ?> entry : values.entrySet())
request.setAttribute(entry.getKey().toString(), entry.getValue());
}
}
private void createExternalContext(){
ExternalContextHolder.setExternalContext(new MvcExternalContext(servletContext, request, response, flowUrlHandler));
}
private void destroyExternalContext(){
ExternalContextHolder.setExternalContext(null);
}
public void resolve(Method method) {
createExternalContext();
resolveParameters(method);
destroyExternalContext();
}
}
Dessa forma o controller poderia ser dessa forma
@Resource
public class NomeController {
public void teste(@Flow TipoQueEstaNoFlow xpto){
}
}
O interceptor vai ser executado quando o metodo que atende a url tiver ao menos um parametro anotado com @Flow
O componente mais acima vai buscar no escopo flow o objeto do mesmo tipo do parametro do metodo, e inclui o cara como atributo da request; depois o VRaptor vai tratar de injetar isso no parametro (estou assumindo que o parametro tem o mesmo nome que o nome do bean no flow scope).
Não curti muito essa injeção pelo metodo, mas me pareceu meio forçado fazer um ComponentFactory nesse cenario, uma vez que os objetos que vao pro flow scope, via de regra, são os objetos temporarios que duram a execução do fluxo. São objetos de representação do modelo, não necessariamente “componentes”.
Bom, espero que possa ajudar pra quem mais precisar integrar o WebFlow com o VRaptor.
Valeu amigos.