/*
* Copyright (c) 2013-2014 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 io.werval.runtime.routes;
import java.lang.reflect.InvocationHandler;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;
import io.werval.api.Application;
import io.werval.api.exceptions.RouteNotFoundException;
import io.werval.api.exceptions.WervalException;
import io.werval.api.http.Method;
import io.werval.api.routes.ControllerCallRecorder;
import io.werval.api.routes.ReverseRoute;
import io.werval.api.routes.ReverseRoutes;
import io.werval.api.routes.Route;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.Proxy;
import javassist.util.proxy.ProxyFactory;
import static io.werval.api.http.Method.CONNECT;
import static io.werval.api.http.Method.DELETE;
import static io.werval.api.http.Method.GET;
import static io.werval.api.http.Method.HEAD;
import static io.werval.api.http.Method.OPTIONS;
import static io.werval.api.http.Method.PATCH;
import static io.werval.api.http.Method.POST;
import static io.werval.api.http.Method.PUT;
import static io.werval.api.http.Method.TRACE;
import static io.werval.util.IllegalArguments.ensureNotNull;
public class ReverseRoutesInstance
implements ReverseRoutes
{
private final Application application;
public ReverseRoutesInstance( Application application )
{
this.application = application;
}
@Override
public <T> ReverseRoute options( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( OPTIONS, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute get( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( GET, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute head( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( HEAD, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute post( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( POST, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute put( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( PUT, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute delete( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( DELETE, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute trace( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( TRACE, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute patch( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( PATCH, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute connect( Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( CONNECT, controllerType, callRecorder );
}
@Override
public <T> ReverseRoute of( String httpMethod, Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
return of( Method.valueOf( httpMethod ), controllerType, callRecorder );
}
@Override
public <T> ReverseRoute of( Method httpMethod, Class<T> controllerType, ControllerCallRecorder<T> callRecorder )
{
ensureNotNull( "HTTP method", httpMethod );
ensureNotNull( "Controller Type", controllerType );
ensureNotNull( "Call Recorder", callRecorder );
// Prepare recording proxy
T controllerProxy;
ControllerCallHandler handler = new ControllerCallHandler();
if( controllerType.isInterface() )
{
Class<?>[] interfaces = new Class<?>[ 1 ];
interfaces[0] = controllerType;
controllerProxy = (T) java.lang.reflect.Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
interfaces,
handler
);
}
else
{
try
{
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setSuperclass( controllerType );
controllerProxy = (T) proxyFactory.createClass().newInstance();
( (Proxy) controllerProxy ).setHandler( handler );
}
catch( InstantiationException | IllegalAccessException ex )
{
throw new WervalException( "Unable to reverse route", ex );
}
}
// Record Controller Method Call
try
{
callRecorder.recordCall( controllerProxy );
}
catch( Exception ex )
{
throw new WervalException(
"Error while recording Controller call for Reverse routing: " + ex.getMessage(),
ex
);
}
// Find Route
for( Route route : application.routes() )
{
if( route.httpMethod().equals( httpMethod )
&& route.controllerType().getName().equals( controllerType.getName() )
&& route.controllerMethodName().equals( handler.methodName )
&& Arrays.equals( route.controllerMethod().getParameterTypes(), handler.paramsTypes ) )
{
// Found!
RouteInstance instance = (RouteInstance) route;
Map<String, Object> parameters = new LinkedHashMap<>();
int idx = 0;
for( String paramName : instance.controllerParams().names() )
{
parameters.put( paramName, handler.paramsValues[idx] );
idx++;
}
String unboundPath = route.unbindParameters( application.parameterBinders(), parameters );
return new ReverseRouteInstance( httpMethod, unboundPath, application.defaultCharset() );
}
}
// No matching Route Found
throw new RouteNotFoundException(
httpMethod,
controllerType + "." + handler.methodName
+ "(" + Arrays.toString( handler.paramsTypes ) + ")"
);
}
private static class ControllerCallHandler
implements MethodHandler, InvocationHandler
{
private String methodName;
private Class<?>[] paramsTypes;
private Object[] paramsValues;
@Override
public Object invoke( Object proxy, java.lang.reflect.Method controllerMethod,
java.lang.reflect.Method proceed, Object[] args )
throws Throwable
{
return invoke( proxy, controllerMethod, args );
}
@Override
public Object invoke( Object proxy, java.lang.reflect.Method controllerMethod, Object[] args )
throws Throwable
{
methodName = controllerMethod.getName();
if( args == null || args.length == 0 )
{
paramsTypes = new Class<?>[ 0 ];
}
else
{
paramsTypes = Arrays.stream( args )
.map( obj -> obj.getClass() )
.collect( Collectors.toList() )
.toArray( new Class<?>[ 0 ] );
}
paramsValues = args;
return null;
}
}
}