diff --git a/.gitignore b/.gitignore index 5231862..a52a82d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea *.iml target +/env.bat diff --git a/src/main/java/fr/ybonnel/simpleweb4j/SimpleWeb4j.java b/src/main/java/fr/ybonnel/simpleweb4j/SimpleWeb4j.java index b97069a..2cd904e 100644 --- a/src/main/java/fr/ybonnel/simpleweb4j/SimpleWeb4j.java +++ b/src/main/java/fr/ybonnel/simpleweb4j/SimpleWeb4j.java @@ -28,6 +28,8 @@ import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.handler.AbstractHandler; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -240,6 +242,20 @@ public static void stop() { started = false; } + /** + * Adds the routes defined in the specified input stream. Each route is defined by a line with the following format: + * + * [HttpMethod] [ParamType] [routePath] [controllerMethod]\n + * + * @param inputStream Input that contains the route definitions. + * @throws NoSuchMethodException If the controller method does not exist. + * @throws IOException If an I/O error occurs + * @throws ClassNotFoundException In the controller class does not exist. + */ + public static void loadRoutes(InputStream inputStream) throws NoSuchMethodException, IOException, ClassNotFoundException { + jsonHandler.loadRoutes(inputStream); + } + /** * Add a new route for GET method. * Use : diff --git a/src/main/java/fr/ybonnel/simpleweb4j/handlers/JsonHandler.java b/src/main/java/fr/ybonnel/simpleweb4j/handlers/JsonHandler.java index d00c94b..57abb0e 100644 --- a/src/main/java/fr/ybonnel/simpleweb4j/handlers/JsonHandler.java +++ b/src/main/java/fr/ybonnel/simpleweb4j/handlers/JsonHandler.java @@ -22,14 +22,14 @@ import fr.ybonnel.simpleweb4j.exception.HttpErrorException; import fr.ybonnel.simpleweb4j.handlers.filter.AbstractFilter; import fr.ybonnel.simpleweb4j.model.SimpleEntityManager; +import fr.ybonnel.simpleweb4j.router.ControllerRoute; +import fr.ybonnel.simpleweb4j.util.StringUtils; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import java.io.StringWriter; +import java.io.*; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Enumeration; @@ -62,6 +62,47 @@ public class JsonHandler extends AbstractHandler { */ private Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXX").create(); + /** + * + * @param inputStream + * @throws IOException + * @throws NoSuchMethodException + * @throws ClassNotFoundException + */ + public void loadRoutes(InputStream inputStream) throws IOException, NoSuchMethodException, ClassNotFoundException { + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); + String line = null; + while ((line = reader.readLine()) != null){ + HttpMethod httpMethod = null; + Class paramType = null; + String routePath = null; + String controller = null; + + // Read Http Method + line = line.trim(); + int firstBlankCharacterIndex = StringUtils.getIndexOfBlank(line); + httpMethod = HttpMethod.valueOf(line.substring(0, firstBlankCharacterIndex).toUpperCase()); + + // Read Parameter type + line = line.substring(firstBlankCharacterIndex).trim(); + firstBlankCharacterIndex = StringUtils.getIndexOfBlank(line); + paramType = Class.forName(line.substring(0, firstBlankCharacterIndex)); + + // Read route routePath + line = line.substring(firstBlankCharacterIndex).trim(); + firstBlankCharacterIndex = StringUtils.getIndexOfBlank(line); + routePath = line.substring(0, firstBlankCharacterIndex); + + // Read controller method + line = line.substring(firstBlankCharacterIndex).trim(); + controller = line; + + // Create and add route. + ControllerRoute route = new ControllerRoute(routePath, paramType, controller); + addRoute(httpMethod, route); + } + + } /** * Add a route. * @param httpMethod http method of the route. diff --git a/src/main/java/fr/ybonnel/simpleweb4j/router/ControllerRoute.java b/src/main/java/fr/ybonnel/simpleweb4j/router/ControllerRoute.java new file mode 100644 index 0000000..b7118f8 --- /dev/null +++ b/src/main/java/fr/ybonnel/simpleweb4j/router/ControllerRoute.java @@ -0,0 +1,68 @@ +package fr.ybonnel.simpleweb4j.router; + +import fr.ybonnel.simpleweb4j.exception.HttpErrorException; +import fr.ybonnel.simpleweb4j.handlers.Response; +import fr.ybonnel.simpleweb4j.handlers.Route; +import fr.ybonnel.simpleweb4j.handlers.RouteParameters; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + */ +public class ControllerRoute extends Route { + + /** Controller method. */ + private Method controllerMethod; + + /** + * Constructor of a route. + * + * @param routePath routePath of the route. + * @param paramType class of the object in request's body. + */ + public ControllerRoute(String routePath, Class

paramType, String controller) throws ClassNotFoundException, NoSuchMethodException { + super(routePath, paramType); + + int lastDotIndex = controller.lastIndexOf("."); + if (lastDotIndex < 1) { + throw new IllegalArgumentException("Controller param is not a controller method."); + } + String controllerClassName = controller.substring(0, lastDotIndex); + String controllerMethodName = controller.substring(lastDotIndex + 1); + + Class controllerClass = Class.forName(controllerClassName); + + if (Void.class.equals(paramType)) { + controllerMethod = controllerClass.getMethod(controllerMethodName, RouteParameters.class); + } + else { + controllerMethod = controllerClass.getMethod(controllerMethodName, paramType, RouteParameters.class); + } + } + + /** + * Invokes the controller static method to compute the HTTP Response. + * + * @param param the parameter object in request's body. + * @param routeParams parameters in the routePath. + * @return The request response. + * @throws HttpErrorException If an error occured. + */ + @Override + public Response handle(P param, RouteParameters routeParams) throws HttpErrorException { + try { + R result = null; + if (Void.class.equals(getParamType())) { + result = (R) controllerMethod.invoke(null, routeParams); + } + else { + result = (R) controllerMethod.invoke(null, param, routeParams); + } + return new Response(result); + } catch (Exception e) { + e.printStackTrace(); + throw new HttpErrorException(500, e); + } + } +} diff --git a/src/main/java/fr/ybonnel/simpleweb4j/util/StringUtils.java b/src/main/java/fr/ybonnel/simpleweb4j/util/StringUtils.java new file mode 100644 index 0000000..ae017ac --- /dev/null +++ b/src/main/java/fr/ybonnel/simpleweb4j/util/StringUtils.java @@ -0,0 +1,25 @@ +package fr.ybonnel.simpleweb4j.util; + +/** + */ +public class StringUtils { + + public static int getIndexOfBlank(String value) { + if (value == null) { + return -1; + } + int firstSpaceIndex = value.indexOf(' '); + if (firstSpaceIndex < 0) { + firstSpaceIndex = Integer.MAX_VALUE; + } + int firstTabIndex = value.indexOf('\t'); + if (firstTabIndex < 0) { + firstTabIndex = Integer.MAX_VALUE; + } + int firstBlankCharacterIndex = Math.min(firstSpaceIndex, firstTabIndex); + if (firstBlankCharacterIndex == Integer.MAX_VALUE) { + firstBlankCharacterIndex = -1; + } + return firstBlankCharacterIndex; + } +} diff --git a/src/test/java/fr/ybonnel/simpleweb4j/handlers/JsonHandlerUnitTest.java b/src/test/java/fr/ybonnel/simpleweb4j/handlers/JsonHandlerUnitTest.java index b5a8b7d..524dc7f 100644 --- a/src/test/java/fr/ybonnel/simpleweb4j/handlers/JsonHandlerUnitTest.java +++ b/src/test/java/fr/ybonnel/simpleweb4j/handlers/JsonHandlerUnitTest.java @@ -19,7 +19,9 @@ import fr.ybonnel.simpleweb4j.entities.SimpleEntity; import fr.ybonnel.simpleweb4j.exception.HttpErrorException; import fr.ybonnel.simpleweb4j.model.SimpleEntityManager; +import fr.ybonnel.simpleweb4j.router.ControllerRoute; import org.eclipse.jetty.server.Request; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -27,6 +29,7 @@ import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; import java.io.IOException; +import java.io.InputStream; import java.nio.charset.Charset; import java.util.Collections; import java.util.Enumeration; @@ -48,6 +51,38 @@ public void setup() { setEntitiesClasses(); } + @Test + public void testLoadRoutes() throws NoSuchMethodException, IOException, ClassNotFoundException { + InputStream inputStream = null; + + try { + inputStream = getClass().getResourceAsStream("/fr/ybonnel/simpleweb4j/routes"); + handler.loadRoutes(inputStream); + + Route route = handler.findRoute("GET", "/test/empty"); + Assert.assertNotNull(route); + Assert.assertTrue(route instanceof ControllerRoute); + + route = handler.findRoute("POST", "/test/integer"); + Assert.assertNotNull(route); + Assert.assertTrue(route instanceof ControllerRoute); + + route = handler.findRoute("PUT", "/test/string"); + Assert.assertNotNull(route); + Assert.assertTrue(route instanceof ControllerRoute); + + route = handler.findRoute("DELETE", "/test/delete/long"); + Assert.assertNotNull(route); + Assert.assertTrue(route instanceof ControllerRoute); + } + finally { + if (inputStream != null) { + try { inputStream.close(); } + catch (Exception e) {} + } + } + } + @Test public void testHandleAlreadyHandle() throws IOException, ServletException { Request request = mock(Request.class); diff --git a/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/Forms.java b/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/Forms.java index 131d1f0..efd2c9a 100644 --- a/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/Forms.java +++ b/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/Forms.java @@ -22,27 +22,34 @@ import fr.ybonnel.simpleweb4j.handlers.RouteParameters; import fr.ybonnel.simpleweb4j.samples.forms.model.Countries; +import java.io.IOException; +import java.io.InputStream; import java.util.List; import static fr.ybonnel.simpleweb4j.SimpleWeb4j.*; public class Forms { - public static void startServer(int port) { + public static void startServer(int port) throws Exception { setPort(port); setPublicResourcesPath("/fr/ybonnel/simpleweb4j/samples/forms/public"); - get(new Route>("countries", Void.class) { - @Override - public Response> handle(Void param, RouteParameters routeParams) throws HttpErrorException { - return new Response<>(Countries.list()); + InputStream inputStream = null; + try { + inputStream = Forms.class.getResourceAsStream("/fr/ybonnel/simpleweb4j/samples/forms/routes"); + loadRoutes(inputStream); + } + finally { + if (inputStream != null) { + try { inputStream.close(); } + catch (Exception e) { } } - }); + } start(); } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { startServer(9999); } diff --git a/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/controllers/CountryController.java b/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/controllers/CountryController.java new file mode 100644 index 0000000..8315fa9 --- /dev/null +++ b/src/test/java/fr/ybonnel/simpleweb4j/samples/forms/controllers/CountryController.java @@ -0,0 +1,16 @@ +package fr.ybonnel.simpleweb4j.samples.forms.controllers; + +import fr.ybonnel.simpleweb4j.handlers.Response; +import fr.ybonnel.simpleweb4j.handlers.RouteParameters; +import fr.ybonnel.simpleweb4j.samples.forms.model.Countries; + +import java.util.List; + +/** + */ +public class CountryController { + + public static List getAll(RouteParameters routeParameters) { + return Countries.list(); + } +} diff --git a/src/test/java/fr/ybonnel/simpleweb4j/util/StringUtilsTest.java b/src/test/java/fr/ybonnel/simpleweb4j/util/StringUtilsTest.java new file mode 100644 index 0000000..bf673f3 --- /dev/null +++ b/src/test/java/fr/ybonnel/simpleweb4j/util/StringUtilsTest.java @@ -0,0 +1,21 @@ +package fr.ybonnel.simpleweb4j.util; + +import org.junit.Assert; +import org.junit.Test; + +/** + */ +public class StringUtilsTest { + + @Test + public void testGetIndexOfBlank() { + int index = StringUtils.getIndexOfBlank("test t"); + Assert.assertEquals(4, index); + + index = StringUtils.getIndexOfBlank("test\tt"); + Assert.assertEquals(4, index); + + index = StringUtils.getIndexOfBlank("test"); + Assert.assertEquals(-1, index); + } +} diff --git a/src/test/java/test/TestController.java b/src/test/java/test/TestController.java new file mode 100644 index 0000000..ff95af1 --- /dev/null +++ b/src/test/java/test/TestController.java @@ -0,0 +1,25 @@ +package test; + +import fr.ybonnel.simpleweb4j.handlers.RouteParameters; + +/** + */ +public class TestController { + + public static Object testEmpty(RouteParameters routeParameters) { + return null; + } + + public static Object testInteger(Integer param, RouteParameters routeParameters) { + return null; + } + + public static Object testString(String param, RouteParameters routeParameters) { + return null; + } + + public static Object testDeleteLong(Long param, RouteParameters routeParameters) { + return null; + } + +} diff --git a/src/test/resources/fr/ybonnel/simpleweb4j/routes b/src/test/resources/fr/ybonnel/simpleweb4j/routes new file mode 100644 index 0000000..dd061e1 --- /dev/null +++ b/src/test/resources/fr/ybonnel/simpleweb4j/routes @@ -0,0 +1,4 @@ +GET java.lang.Void /test/empty test.TestController.testEmpty +POST java.lang.Integer /test/integer test.TestController.testInteger +PUT java.lang.String /test/string test.TestController.testString +DELETE java.lang.Long /test/delete/long test.TestController.testDeleteLong \ No newline at end of file diff --git a/src/test/resources/fr/ybonnel/simpleweb4j/samples/forms/routes b/src/test/resources/fr/ybonnel/simpleweb4j/samples/forms/routes new file mode 100644 index 0000000..0846fd0 --- /dev/null +++ b/src/test/resources/fr/ybonnel/simpleweb4j/samples/forms/routes @@ -0,0 +1 @@ +GET java.lang.Void countries fr.ybonnel.simpleweb4j.samples.forms.controllers.CountryController.getAll \ No newline at end of file