/*******************************************************************************
* Copyright (c) 2014, 2015 IBH SYSTEMS GmbH.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBH SYSTEMS GmbH - initial API and implementation
*******************************************************************************/
package org.eclipse.packagedrone.web.controller;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.packagedrone.web.ModelAndView;
import org.eclipse.packagedrone.web.RequestHandler;
import org.eclipse.packagedrone.web.controller.binding.Binder;
import org.eclipse.packagedrone.web.controller.binding.BindingManager;
import org.eclipse.packagedrone.web.controller.binding.ErrorBinder;
import org.eclipse.packagedrone.web.controller.binding.PathVariableBinder;
import org.eclipse.packagedrone.web.controller.binding.RequestParameterBinder;
import org.eclipse.packagedrone.web.controller.form.FormDataBinder;
import org.eclipse.packagedrone.web.controller.routing.RequestMappingInformation;
import org.eclipse.packagedrone.web.controller.routing.RequestMappingInformation.Match;
public class ControllerCall
{
private final RequestMappingInformation rmi;
private final Method m;
private final ControllerInterceptorProcessor[] interceptors;
private final Object controller;
public ControllerCall ( final Object controller, final RequestMappingInformation rmi, final Method m, final Set<ControllerInterceptorProcessor> interceptors )
{
this.controller = controller;
this.rmi = rmi;
this.m = m;
this.interceptors = interceptors.toArray ( new ControllerInterceptorProcessor[interceptors.size ()] );
}
public Match matches ( final HttpServletRequest request )
{
return this.rmi.matches ( request );
}
protected RequestHandler runForward ( final int index, final HttpServletRequest request, final HttpServletResponse response, final Callable<RequestHandler> last ) throws Exception
{
if ( index < this.interceptors.length )
{
return this.interceptors[index].before ( this.controller, this.m, request, response, ( chainReq, chainRes ) -> {
try
{
return runForward ( index + 1, chainReq, chainRes, last );
}
catch ( final RuntimeException e )
{
throw e;
}
catch ( final Exception e )
{
throw new RuntimeException ( e );
}
} );
}
else
{
return last.call ();
}
}
protected RequestHandler runBackward ( final int index, final HttpServletRequest request, final HttpServletResponse response, final Callable<RequestHandler> last ) throws Exception
{
if ( index > 0 )
{
return this.interceptors[index - 1].after ( this.controller, this.m, request, response, ( chainReq, chainRes ) -> {
try
{
return runBackward ( index - 1, chainReq, chainRes, last );
}
catch ( final Exception e )
{
throw new RuntimeException ( e );
}
} );
}
else
{
return last.call ();
}
}
public RequestHandler call ( final Match match, final HttpServletRequest request, final HttpServletResponse response )
{
// process call
try
{
final RequestHandler result = runForward ( 0, request, response, () -> processCall ( match, request, response ) );
return runBackward ( this.interceptors.length, request, response, new Callable<RequestHandler> () {
@Override
public RequestHandler call () throws Exception
{
return result;
}
} );
}
catch ( final RuntimeException e )
{
throw e;
}
catch ( final Exception e )
{
throw new RuntimeException ( e );
}
}
protected RequestHandler processCall ( final Match match, final HttpServletRequest request, final HttpServletResponse response ) throws Exception
{
final Map<String, Object> data = new HashMap<String, Object> ();
data.put ( "request", request );
data.put ( "response", response );
data.put ( "session", request.getSession () );
data.put ( "principal", request.getUserPrincipal () );
// create new binding manager
final BindingManager manager = BindingManager.create ( data );
// add controller binders
manager.addBinder ( new RequestParameterBinder ( request ) );
manager.addBinder ( new PathVariableBinder ( match ) );
manager.addBinder ( new FormDataBinder ( request, this.controller ) );
addMethodBinders ( manager, this.m );
final org.eclipse.packagedrone.web.controller.binding.BindingManager.Call call = manager.bind ( this.m, this.controller );
final Object result = call.invoke ();
if ( result instanceof ModelAndView )
{
return new ModelAndViewRequestHandler ( (ModelAndView)result, this.controller.getClass (), this.m );
}
else if ( result instanceof String )
{
return new ModelAndViewRequestHandler ( new ModelAndView ( (String)result ), this.controller.getClass (), this.m );
}
else if ( result == null )
{
return new NoOpRequestHandler ();
}
else
{
throw new IllegalStateException ( String.format ( "Response type %s is unsupported", result.getClass () ) );
}
}
/**
* Add custom binders assigned to the method
* <p>
* Custom binders assigned to the method will be added to the binding
* manager instance.
* </p>
*
* @param manager
* the manager to add binders to
* @param method
* the method to evaluate for additional binders
*/
protected static void addMethodBinders ( final BindingManager manager, final Method method )
{
final ControllerBinder[] binders = method.getAnnotationsByType ( ControllerBinder.class );
if ( binders == null )
{
return;
}
for ( final ControllerBinder binder : binders )
{
try
{
final Binder binderImpl = binder.value ().newInstance ();
if ( binderImpl instanceof ControllerBinderParametersAware )
{
( (ControllerBinderParametersAware)binderImpl ).setParameters ( binder.parameters () );
}
manager.addBinder ( binderImpl );
}
catch ( InstantiationException | IllegalAccessException e )
{
manager.addBinder ( new ErrorBinder ( e ) );
}
}
}
}