package de.axone.web.rest;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.axone.data.Charsets;
import de.axone.data.Mime.MimeTypes;
import de.axone.exception.IllegalNamedArgumentException;
import de.axone.web.Method;
import de.axone.web.SuperURL;
import de.axone.web.SuperURLBuilders;
public class RestFunctionRegistry<DATA, REQUEST extends RestRequest> implements Serializable {
private static final long serialVersionUID = 1L;
private static final Logger log = LoggerFactory.getLogger( RestFunctionRegistry.class );
private List<RestFunctionRoute> routes = new LinkedList<>();
private List<RestFunction<DATA, REQUEST>> functions = new LinkedList<>();
private final int stepBack;
public RestFunctionRegistry(){
this( 1 );
}
public RestFunctionRegistry( int stepBack ){
this.stepBack = stepBack;
}
public void run( @Nullable DATA data, REQUEST req,
HttpServletResponse resp ) throws Exception {
SuperURL url = SuperURLBuilders.fromRequest().build( req );
if( url.getPath() == null || url.getPath().length() < stepBack ) {
renderHelp( req, resp, null, false );
} else {
for( int i=0; i<stepBack; i++ ){
url.getPath().removeFirst();
}
RestFunction<DATA, REQUEST> f = null;
Map<String,String> parameters = null;
for( int i=0; i<routes.size(); i++ ){
RestFunctionRoute route = routes.get( i );
url.getPath().setEndsWithSlash( false );
String path = url.getPath().toString();
parameters = route.match(
req.getRestMethod(), path );
if( parameters != null ){
f = functions.get( i );
break;
}
}
if( f == null ) {
log.warn( "No function: " + req.getRequestURI() );
renderHelp( req, resp, null, false );
} else {
log.info( "Client requested {}", f.name() );
url.getPath().removeFirst();
try {
log.debug( "Running: {}", f.name() );
f.run( data, req.getRestMethod(), parameters, url,
resp.getWriter(), req, resp );
} catch( Throwable e ){
handleException( f, e, req, resp );
}
}
}
}
protected void handleException( RestFunction<?,?> f,
Throwable e, REQUEST req, HttpServletResponse resp ) throws IOException{
int status;
if( e instanceof RestFunctionException ){
status = ((RestFunctionException) e).code();
} else if( e instanceof LoginException ){
status = 403;
} else if( e instanceof IllegalNamedArgumentException ){
status = 420; // Policy Not Fulfilled
} else {
status = 500;
}
if( !resp.isCommitted() ){
resp.setStatus( status );
}
try( PrintWriter out = resp.getWriter(); ){
JsonResponse response;
if( e instanceof IllegalNamedArgumentException ){
response = JsonResponseImpl.INVALID( (IllegalNamedArgumentException) e );
} else {
response = JsonResponseImpl.ERROR( status, e );
}
req.mapper().writeValue( out, response );
log.error( "Exception while running '" + f.name() + "'", e );
}
}
private void renderHelp( RestRequest req, HttpServletResponse resp, String message, boolean detailed )
throws Exception {
resp.setCharacterEncoding( Charsets.utf8 );
resp.setContentType( MimeTypes.HTML.text() );
try( PrintWriter out = resp.getWriter(); ){
out.println( "<!DOCTYPE html>" );
out.println( "<html><head><title>Functions</title>" );
out.println( "<script src=\"//ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-yui-compressed.js\"></script>" );
out.println( "<script src=\"/static/admin/js/ajax_help.js?yui=false\"></script>" );
out.println( "</head><body>\n" );
if( message != null ){
out.write( "<h1>" + message + "</h1>" );
}
for( RestFunction<DATA, REQUEST> function : functions ){
RestFunctionDescription description = function.description();
if( detailed ){
String template = description.getTemplate();
if( template != null ){
out.write( template );
}
}
out.write( description.toHtml( detailed ) );
}
out.println( "\n</body></html>" );
}
}
public void register( RestFunction<DATA, REQUEST> function ) {
register( new RestFunctionRoute.Simple( "/" + function.name() ), function );
}
public void register( String route, RestFunction<DATA, REQUEST> function ){
register( new RestFunctionRoute.Simple( route ), function );
}
public void register( String route, Method methods, RestFunction<DATA, REQUEST> function ){
register( new RestFunctionRoute.Simple( route, methods ), function );
}
public void register( String route, EnumSet<Method> methods, RestFunction<DATA, REQUEST> function ){
register( new RestFunctionRoute.Simple( route, methods ), function );
}
public void register( RestFunctionRoute route, RestFunction<DATA, REQUEST> function ){
function.description().setRoute( route );
routes.add( route );
functions.add( function );
}
}