├── .travis.yml ├── src ├── test │ ├── webapp │ │ ├── jsp │ │ │ └── render.jsp │ │ └── WEB-INF │ │ │ └── web.xml │ └── java │ │ └── com │ │ └── ghosthack │ │ └── turismo │ │ ├── example │ │ ├── AppRoutes.java │ │ ├── WebAppRoutes.java │ │ ├── JettyHelper.java │ │ ├── ExampleAppRoutes.java │ │ ├── ExampleWebAppRoutes.java │ │ └── ExampleAppRoutesList.java │ │ ├── HttpMocks.java │ │ └── routes │ │ └── RoutesMapTest.java └── main │ └── java │ └── com │ └── ghosthack │ └── turismo │ ├── multipart │ ├── package.html │ ├── ParseException.java │ ├── Parametrizable.java │ ├── MultipartRequest.java │ ├── MultipartFilter.java │ └── MultipartParser.java │ ├── Routes.java │ ├── action │ ├── NotFoundAction.java │ ├── behavior │ │ ├── MovedTemporarily.java │ │ ├── MovedPermanently.java │ │ ├── Redirect.java │ │ ├── StringPrinter.java │ │ ├── NotFound.java │ │ └── Alias.java │ ├── ActionException.java │ └── Action.java │ ├── Resolver.java │ ├── routes │ ├── ExtendedRoutesMap.java │ ├── RoutesMap.java │ └── RoutesList.java │ ├── resolver │ ├── MethodPathResolver.java │ ├── MapResolver.java │ └── ListResolver.java │ ├── util │ └── ClassForName.java │ └── servlet │ ├── Env.java │ └── Servlet.java ├── .gitignore ├── LICENSE.txt ├── pom.xml └── README.md /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | -------------------------------------------------------------------------------- /src/test/webapp/jsp/render.jsp: -------------------------------------------------------------------------------- 1 | <%@ page pageEncoding="UTF-8" %> 2 | <%=request.getAttribute("message")%> -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | src/main/webapp/WEB-INF/classes/ 2 | target/ 3 | .settings/ 4 | .project 5 | *.DS_Store 6 | .classpath 7 | settings.xml 8 | .idea 9 | turismo.iml 10 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/package.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Partial rfc1867 (multipart-form-data) parser. 8 | 9 |

10 | Contains also a and servlet filter implementation to transparently handle uploads. 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/ParseException.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | /** 4 | * This exception is used by the multipar parser 5 | * 6 | * @see MultipartParser 7 | */ 8 | public class ParseException extends Exception { 9 | ParseException(String desc) { 10 | super(desc); 11 | } 12 | 13 | ParseException() { 14 | super(); 15 | } 16 | 17 | /** 18 | * Serializable implementation specific. 19 | */ 20 | private static final long serialVersionUID = 1L; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2010 Adrian Fernandez 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/AppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import com.ghosthack.turismo.action.Action; 4 | import com.ghosthack.turismo.routes.RoutesMap; 5 | 6 | public class AppRoutes extends RoutesMap { 7 | 8 | @Override 9 | protected void map() { 10 | get("/", new Action() { 11 | @Override 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | 18 | public static void main(String[] args) throws Exception{ 19 | JettyHelper.server(8080, "/app/*", AppRoutes.class.getName()); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/WebAppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | 4 | import com.ghosthack.turismo.action.Action; 5 | import com.ghosthack.turismo.routes.RoutesMap; 6 | 7 | public class WebAppRoutes extends RoutesMap { 8 | 9 | @Override 10 | protected void map() { 11 | get("/", new Action() { 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | 18 | /** 19 | * Minics an application server environment 20 | */ 21 | public static void main(String[] args) throws Exception{ 22 | JettyHelper.server(8080, JettyHelper.webapp()); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/HttpMocks.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo; 2 | 3 | import static org.mockito.Mockito.mock; 4 | import static org.mockito.Mockito.when; 5 | 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | 9 | public class HttpMocks { 10 | 11 | public static HttpServletRequest getRequestMock(String method, String path) { 12 | HttpServletRequest req = mock(HttpServletRequest.class); 13 | when(req.getMethod()).thenReturn(method); 14 | when(req.getPathInfo()).thenReturn(path); 15 | return req; 16 | } 17 | 18 | public static HttpServletResponse getResponseMock() { 19 | HttpServletResponse res = mock(HttpServletResponse.class); 20 | return res; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/test/webapp/WEB-INF/web.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | webapp-servlet 8 | com.ghosthack.turismo.servlet.Servlet 9 | 10 | routes 11 | com.ghosthack.turismo.example.TestWebAppRoutes 12 | 13 | 14 | 15 | webapp-servlet 16 | /webapp/* 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/Parametrizable.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | /** 4 | * Separates Request implementation from Parser. 5 | * 6 | */ 7 | public interface Parametrizable { 8 | 9 | /** 10 | * Adds a parameter to the parameter map. 11 | * 12 | * @param name 13 | * @param value 14 | */ 15 | void addParameter(String name, String value); 16 | 17 | /** 18 | * Adds a parameter to the parameter map. 19 | * 20 | * @param name 21 | * @param value 22 | */ 23 | void addParameter(String name, String[] value); 24 | 25 | /** 26 | * Adds an attribute to the attribute map. 27 | * 28 | * @param name 29 | * @param value 30 | */ 31 | void setAttribute(String name, Object value); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/Routes.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo; 18 | 19 | 20 | public interface Routes { 21 | 22 | Resolver getResolver(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/NotFoundAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action; 18 | 19 | public class NotFoundAction extends Action { 20 | 21 | @Override 22 | public void run() { 23 | notFound(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/Resolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo; 18 | 19 | 20 | public interface Resolver { 21 | 22 | Runnable resolve(); 23 | 24 | void route(String method, String path, Runnable runnable); 25 | 26 | void route(String method, String fromPath, String targetPath); 27 | 28 | void route(Runnable runnable); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/MovedTemporarily.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import javax.servlet.http.HttpServletResponse; 20 | 21 | import com.ghosthack.turismo.servlet.Env; 22 | 23 | public class MovedTemporarily { 24 | 25 | public void send302(String location) { 26 | Env.res().setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); 27 | Env.res().setHeader("Location", location); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/MovedPermanently.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import javax.servlet.http.HttpServletResponse; 20 | 21 | import com.ghosthack.turismo.servlet.Env; 22 | 23 | public class MovedPermanently { 24 | 25 | public void send301(Object location) { 26 | Env.res().setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); 27 | Env.res().setHeader("Location", String.valueOf(location)); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/routes/ExtendedRoutesMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.routes; 18 | 19 | import com.ghosthack.turismo.action.Action; 20 | 21 | public abstract class ExtendedRoutesMap extends RoutesMap { 22 | 23 | protected void get(String path, final String target) { 24 | resolver.route(GET, path, new Action() { 25 | @Override public void run() { 26 | alias(target); 27 | } 28 | }); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/ActionException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action; 18 | 19 | public class ActionException extends RuntimeException { 20 | 21 | public ActionException() { 22 | super(); 23 | } 24 | 25 | public ActionException(String msg) { 26 | super(msg); 27 | } 28 | 29 | public ActionException(Throwable cause) { 30 | super(cause); 31 | } 32 | 33 | private static final long serialVersionUID = 1L; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/Redirect.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import java.io.IOException; 20 | 21 | import com.ghosthack.turismo.action.ActionException; 22 | import com.ghosthack.turismo.servlet.Env; 23 | 24 | public class Redirect { 25 | 26 | public void redirect(String location) { 27 | try { 28 | Env.res().sendRedirect(String.valueOf(location)); 29 | } catch (IOException e) { 30 | throw new ActionException(e); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/StringPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import java.io.IOException; 20 | 21 | import com.ghosthack.turismo.action.ActionException; 22 | import com.ghosthack.turismo.servlet.Env; 23 | 24 | public final class StringPrinter { 25 | 26 | public void print(String string) { 27 | try { 28 | Env.res().getWriter().write(string); 29 | } catch (IOException e) { 30 | throw new ActionException(e); 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/NotFound.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import java.io.IOException; 20 | 21 | import javax.servlet.http.HttpServletResponse; 22 | 23 | import com.ghosthack.turismo.action.ActionException; 24 | import com.ghosthack.turismo.servlet.Env; 25 | 26 | public class NotFound { 27 | 28 | public void send404() { 29 | try { 30 | Env.res().sendError(HttpServletResponse.SC_NOT_FOUND); 31 | } catch (IOException e) { 32 | throw new ActionException(e); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/behavior/Alias.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action.behavior; 18 | 19 | import java.io.IOException; 20 | 21 | import javax.servlet.RequestDispatcher; 22 | import javax.servlet.ServletException; 23 | 24 | import com.ghosthack.turismo.action.ActionException; 25 | import com.ghosthack.turismo.servlet.Env; 26 | 27 | 28 | public class Alias { 29 | 30 | public void forward(final String target) { 31 | final RequestDispatcher dispatcher = Env.ctx() 32 | .getRequestDispatcher(target); 33 | try { 34 | dispatcher.forward(Env.req(), Env.res()); 35 | } catch (ServletException e) { 36 | throw new ActionException(e); 37 | } catch (IOException e) { 38 | throw new ActionException(e); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/JettyHelper.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import org.eclipse.jetty.server.Handler; 4 | import org.eclipse.jetty.server.Server; 5 | import org.eclipse.jetty.servlet.ServletContextHandler; 6 | import org.eclipse.jetty.servlet.ServletHolder; 7 | import org.eclipse.jetty.webapp.WebAppContext; 8 | 9 | import com.ghosthack.turismo.servlet.Servlet; 10 | 11 | public class JettyHelper { 12 | 13 | public static void server(int port, String mapping, String routes) throws Exception { 14 | server(port, handler(mapping, routes)); 15 | } 16 | 17 | public static void server(int port, Handler handler) throws Exception { 18 | Server server = new Server(port); 19 | server.setHandler(handler); 20 | server.start(); 21 | server.join(); 22 | } 23 | 24 | public static ServletContextHandler handler(String mapping, String routes) { 25 | ServletContextHandler handler = new ServletContextHandler(ServletContextHandler.SESSIONS); 26 | handler.setContextPath("/"); 27 | handler.setResourceBase("./src/test/webapp/"); 28 | ServletHolder holder = new ServletHolder(new Servlet()); 29 | holder.setInitParameter("routes", routes); 30 | handler.addServlet(holder,mapping); 31 | return handler; 32 | } 33 | 34 | public static WebAppContext webapp() { 35 | WebAppContext root = new WebAppContext(); 36 | root.setContextPath("/"); 37 | root.setDescriptor("src/test/webapp/WEB-INF/web.xml"); 38 | root.setResourceBase("src/test/webapp/"); 39 | root.setParentLoaderPriority(true); 40 | return root; 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/resolver/MethodPathResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.resolver; 18 | 19 | import com.ghosthack.turismo.Resolver; 20 | import com.ghosthack.turismo.action.ActionException; 21 | import com.ghosthack.turismo.servlet.Env; 22 | 23 | public abstract class MethodPathResolver implements Resolver { 24 | 25 | private static final String UNDEFINED_PATH = "Undefined path"; 26 | 27 | @Override 28 | public Runnable resolve() throws ActionException { 29 | String path = extractPath(); 30 | String method = Env.req().getMethod(); 31 | Runnable route = resolve(method, path); 32 | return route; 33 | } 34 | 35 | protected abstract Runnable resolve(String method, String path); 36 | 37 | private String extractPath() throws ActionException { 38 | String path = Env.req().getPathInfo(); 39 | if (path == null) { 40 | path = Env.req().getServletPath(); 41 | if (path == null) { 42 | throw new ActionException(UNDEFINED_PATH); 43 | } 44 | } 45 | return path; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/ExampleAppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import com.ghosthack.turismo.action.Action; 4 | import com.ghosthack.turismo.routes.RoutesMap; 5 | import com.ghosthack.turismo.servlet.Env; 6 | 7 | public class ExampleAppRoutes extends RoutesMap { 8 | 9 | @Override 10 | protected void map() { 11 | get("/", new Action() { 12 | @Override 13 | public void run() { 14 | print("Hello World!"); 15 | } 16 | }); 17 | get("", new Action() { 18 | @Override 19 | public void run() { 20 | print("Hello World!"); 21 | } 22 | }); 23 | get("/redir1", new Action() { 24 | @Override 25 | public void run() { 26 | //301 moved permanently 27 | movedPermanently("/dest"); 28 | } 29 | }); 30 | get("/redir2", new Action() { 31 | @Override 32 | public void run() { 33 | redirect("/dest"); 34 | } 35 | }); 36 | get("/dest", new Action() { 37 | @Override 38 | public void run() { 39 | print("Hello Redirect"); 40 | } 41 | }); 42 | post("/search", new Action() { 43 | public void run() { 44 | String query = Env.req().getParameter("q"); 45 | print("Your search query was: " + query); 46 | } 47 | }); 48 | route(new Action() { 49 | @Override 50 | public void run() { 51 | notFound(); 52 | } 53 | }); 54 | } 55 | 56 | public static void main(String[] args) throws Exception{ 57 | JettyHelper.server(8080, "/*", ExampleAppRoutes.class.getName()); 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/ExampleWebAppRoutes.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import com.ghosthack.turismo.action.Action; 4 | import com.ghosthack.turismo.routes.RoutesMap; 5 | import com.ghosthack.turismo.servlet.Env; 6 | 7 | public class ExampleWebAppRoutes extends RoutesMap { 8 | 9 | @Override 10 | protected void map() { 11 | get("/", new Action() { 12 | @Override 13 | public void run() { 14 | print("Hello World!"); 15 | } 16 | }); 17 | get("/redir1", new Action() { 18 | @Override 19 | public void run() { 20 | //301 moved permanently 21 | movedPermanently("/dest"); 22 | } 23 | }); 24 | get("/redir2", new Action() { 25 | @Override 26 | public void run() { 27 | //302 redirect 28 | redirect("/dest"); 29 | } 30 | }); 31 | get("/dest", new Action() { 32 | @Override 33 | public void run() { 34 | print("Hello Redirect"); 35 | } 36 | }); 37 | get("/render", new Action() { 38 | public void run() { 39 | Env.req().setAttribute("message", "Hello Word!"); 40 | jsp("/jsp/render.jsp"); 41 | } 42 | }); 43 | post("/search", new Action() { 44 | public void run() { 45 | String query = Env.req().getParameter("q"); 46 | print("Your search query was: " + query); 47 | } 48 | }); 49 | route(new Action() { 50 | @Override 51 | public void run() { 52 | notFound(); 53 | } 54 | }); 55 | } 56 | 57 | public static void main(String[] args) throws Exception{ 58 | JettyHelper.server(8080, JettyHelper.webapp()); 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/util/ClassForName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.util; 18 | 19 | public class ClassForName { 20 | 21 | public static class ClassForNameException extends Exception { 22 | 23 | public ClassForNameException(Exception e) { 24 | super(e); 25 | } 26 | 27 | private static final long serialVersionUID = 1L; 28 | 29 | } 30 | 31 | public static T createInstance(String implClassName, 32 | Class interfaceClass) throws ClassForNameException { 33 | Class impl; 34 | try { 35 | impl = forName(implClassName, interfaceClass); 36 | T instance = impl.newInstance(); 37 | return instance; 38 | } catch (ClassNotFoundException e) { 39 | throw new ClassForNameException(e); 40 | } catch (InstantiationException e) { 41 | throw new ClassForNameException(e); 42 | } catch (IllegalAccessException e) { 43 | throw new ClassForNameException(e); 44 | } 45 | } 46 | 47 | public static Class forName(String implClassName, 48 | Class interfaceClass) throws ClassNotFoundException { 49 | Class clazz = Class.forName(implClassName); 50 | Class impl = clazz.asSubclass(interfaceClass); 51 | return impl; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/example/ExampleAppRoutesList.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.example; 2 | 3 | import com.ghosthack.turismo.action.Action; 4 | import com.ghosthack.turismo.routes.RoutesList; 5 | import com.ghosthack.turismo.servlet.Env; 6 | 7 | public class ExampleAppRoutesList extends RoutesList { 8 | 9 | @Override 10 | protected void map() { 11 | get("/", new Action() { 12 | @Override 13 | public void run() { 14 | print("Hello World!"); 15 | } 16 | }); 17 | get("", new Action() { 18 | @Override 19 | public void run() { 20 | print("Hello World!"); 21 | } 22 | }); 23 | get("/wild/*/card/:id", new Action() { 24 | @Override 25 | public void run() { 26 | String id = params("id"); 27 | String id2 = params("id2"); 28 | print("id " + id + " id2 " + id2); 29 | } 30 | }); 31 | get("/redir1", new Action() { 32 | @Override 33 | public void run() { 34 | //301 moved permanently 35 | movedPermanently("/dest"); 36 | } 37 | }); 38 | get("/redir2", new Action() { 39 | @Override 40 | public void run() { 41 | redirect("/dest"); 42 | } 43 | }); 44 | get("/dest", new Action() { 45 | @Override 46 | public void run() { 47 | print("Hello Redirect"); 48 | } 49 | }); 50 | post("/search", new Action() { 51 | public void run() { 52 | String query = Env.req().getParameter("q"); 53 | print("Your search query was: " + query); 54 | } 55 | }); 56 | route(new Action() { 57 | @Override 58 | public void run() { 59 | notFound(); 60 | } 61 | }); 62 | } 63 | 64 | public static void main(String[] args) throws Exception{ 65 | JettyHelper.server(8080, "/*", ExampleAppRoutesList.class.getName()); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/action/Action.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.action; 18 | 19 | import javax.servlet.ServletContext; 20 | import javax.servlet.http.HttpServletRequest; 21 | import javax.servlet.http.HttpServletResponse; 22 | 23 | import com.ghosthack.turismo.action.behavior.Alias; 24 | import com.ghosthack.turismo.action.behavior.MovedPermanently; 25 | import com.ghosthack.turismo.action.behavior.MovedTemporarily; 26 | import com.ghosthack.turismo.action.behavior.NotFound; 27 | import com.ghosthack.turismo.action.behavior.Redirect; 28 | import com.ghosthack.turismo.action.behavior.StringPrinter; 29 | import com.ghosthack.turismo.servlet.Env; 30 | 31 | 32 | public abstract class Action implements Runnable { 33 | 34 | protected String params(String key) { 35 | return Env.params(key); 36 | } 37 | 38 | protected HttpServletRequest req() { 39 | return Env.req(); 40 | } 41 | 42 | protected HttpServletResponse res() { 43 | return Env.res(); 44 | } 45 | 46 | protected ServletContext ctx() { 47 | return Env.ctx(); 48 | } 49 | 50 | protected void alias(String target) { 51 | forward(target); 52 | } 53 | 54 | protected void forward(String target) { 55 | new Alias().forward(target); 56 | } 57 | 58 | protected void jsp(String path) { 59 | forward(path); 60 | } 61 | 62 | protected void movedPermanently(String newLocation) { 63 | new MovedPermanently().send301(newLocation); 64 | } 65 | 66 | protected void movedTemporarily(String newLocation) { 67 | new MovedTemporarily().send302(newLocation); 68 | } 69 | 70 | protected void notFound() { 71 | new NotFound().send404(); 72 | } 73 | 74 | protected void redirect(String newLocation) { 75 | new Redirect().redirect(newLocation); 76 | } 77 | 78 | protected void print(String string) { 79 | new StringPrinter().print(string); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/resolver/MapResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.resolver; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | 22 | public class MapResolver extends MethodPathResolver { 23 | 24 | /** 25 | * { method => { path => action-route } } 26 | */ 27 | private final Map> methodPathMap = new HashMap>(); 28 | 29 | private Runnable notFoundRoute; 30 | 31 | public Runnable getNotFoundRoute() { 32 | return notFoundRoute; 33 | } 34 | 35 | @Override 36 | public Runnable resolve(String method, String path) { 37 | Runnable route = findRoute(method, path); 38 | if(route != null) { 39 | return route; 40 | } 41 | return getNotFoundRoute(); 42 | } 43 | 44 | @Override 45 | public void route(Runnable runnable) { 46 | notFoundRoute = runnable; 47 | } 48 | 49 | public void route(final String path, Runnable runnable) { 50 | route(null, path, runnable); 51 | } 52 | 53 | public void route(final String method, final String path, Runnable runnable) { 54 | r(method, path, runnable); 55 | } 56 | 57 | private Runnable findRoute(String method, String path) { 58 | Map methodMap = methodPathMap.get(method); 59 | 60 | if (methodMap == null) { 61 | methodMap = methodPathMap.get(null); 62 | } 63 | 64 | return methodMap != null? methodMap.get(path) : null; 65 | } 66 | 67 | private void r(final String method, final String path, Runnable action) { 68 | Map methodMap = methodPathMap.get(method); 69 | if(methodMap == null) { 70 | methodMap = new HashMap(); 71 | methodPathMap.put(method, methodMap); 72 | } 73 | methodMap.put(path, action); 74 | } 75 | 76 | @Override 77 | public void route(String method, String fromPath, String targetPath) { 78 | throw new IllegalAccessError("Not implemented"); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/routes/RoutesMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.routes; 18 | 19 | import com.ghosthack.turismo.Resolver; 20 | import com.ghosthack.turismo.Routes; 21 | import com.ghosthack.turismo.action.NotFoundAction; 22 | import com.ghosthack.turismo.resolver.MapResolver; 23 | 24 | public abstract class RoutesMap implements Routes { 25 | 26 | protected final Resolver resolver; 27 | 28 | public RoutesMap() { 29 | resolver = new MapResolver(); 30 | resolver.route(new NotFoundAction()); 31 | map(); 32 | } 33 | 34 | @Override 35 | public Resolver getResolver() { 36 | return resolver; 37 | } 38 | 39 | protected abstract void map(); 40 | 41 | // Shortcuts methods 42 | 43 | protected void get(final String path, Runnable runnable) { 44 | resolver.route(GET, path, runnable); 45 | } 46 | 47 | protected void post(final String path, Runnable runnable) { 48 | resolver.route(POST, path, runnable); 49 | } 50 | 51 | protected void put(final String path, Runnable runnable) { 52 | resolver.route(PUT, path, runnable); 53 | } 54 | 55 | protected void head(final String path, Runnable runnable) { 56 | resolver.route(HEAD, path, runnable); 57 | } 58 | 59 | protected void options(final String path, Runnable runnable) { 60 | resolver.route(OPTIONS, path, runnable); 61 | } 62 | 63 | protected void delete(final String path, Runnable runnable) { 64 | resolver.route(DELETE, path, runnable); 65 | } 66 | 67 | protected void trace(final String path, Runnable runnable) { 68 | resolver.route(TRACE, path, runnable); 69 | } 70 | 71 | protected void route(Runnable runnable) { 72 | resolver.route(runnable); 73 | } 74 | 75 | protected static final String POST = "POST"; 76 | protected static final String GET = "GET"; 77 | protected static final String HEAD = "HEAD"; 78 | protected static final String OPTIONS = "OPTIONS"; 79 | protected static final String PUT = "PUT"; 80 | protected static final String DELETE = "DELETE"; 81 | protected static final String TRACE = "TRACE"; 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/servlet/Env.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.servlet; 18 | 19 | import java.util.Collections; 20 | import java.util.Map; 21 | 22 | import javax.servlet.ServletContext; 23 | import javax.servlet.http.HttpServletRequest; 24 | import javax.servlet.http.HttpServletResponse; 25 | 26 | public class Env { 27 | 28 | private static ThreadLocal locals = new ThreadLocal(); 29 | 30 | public static Env get() { 31 | return locals.get(); 32 | } 33 | 34 | public static void create(HttpServletRequest req, HttpServletResponse res, 35 | ServletContext context) { 36 | locals.set(new Env(req, res, context)); 37 | } 38 | 39 | public static void destroy() { 40 | locals.remove(); 41 | } 42 | 43 | public static HttpServletRequest req() { 44 | return get().getReq(); 45 | } 46 | 47 | public static HttpServletResponse res() { 48 | return get().getRes(); 49 | } 50 | 51 | public static ServletContext ctx() { 52 | return get().getCtx(); 53 | } 54 | 55 | public static void setResourceParams(Map params) { 56 | get().setParams(params); 57 | } 58 | 59 | public static String params(String key) { 60 | Map params2 = get().getParams(); 61 | String string = params2.get(key); 62 | if(string == null) { 63 | return Env.req().getParameter(key); 64 | } 65 | return string; 66 | } 67 | 68 | private HttpServletRequest req; 69 | private HttpServletResponse res; 70 | private ServletContext ctx; 71 | private Map params = Collections.emptyMap(); 72 | 73 | private Env(HttpServletRequest req, HttpServletResponse res, 74 | ServletContext context) { 75 | this.req = req; 76 | this.res = res; 77 | this.ctx = context; 78 | } 79 | 80 | public ServletContext getCtx() { 81 | return ctx; 82 | } 83 | 84 | public HttpServletRequest getReq() { 85 | return req; 86 | } 87 | 88 | public HttpServletResponse getRes() { 89 | return res; 90 | } 91 | 92 | public void setParams(Map params) { 93 | this.params = params; 94 | } 95 | 96 | public Map getParams() { 97 | return params; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/routes/RoutesList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.routes; 18 | 19 | import com.ghosthack.turismo.Resolver; 20 | import com.ghosthack.turismo.Routes; 21 | import com.ghosthack.turismo.action.NotFoundAction; 22 | import com.ghosthack.turismo.resolver.ListResolver; 23 | 24 | public abstract class RoutesList implements Routes { 25 | 26 | protected final Resolver resolver; 27 | 28 | public RoutesList() { 29 | resolver = new ListResolver(); 30 | resolver.route(new NotFoundAction()); 31 | map(); 32 | } 33 | 34 | @Override 35 | public Resolver getResolver() { 36 | return resolver; 37 | } 38 | 39 | protected abstract void map(); 40 | 41 | // Route-alias shortcut methods 42 | 43 | protected void post(final String fromPath, final String targetPath) { 44 | resolver.route(POST, fromPath, targetPath); 45 | } 46 | 47 | protected void get(final String fromPath, final String targetPath) { 48 | resolver.route(GET, fromPath, targetPath); 49 | } 50 | 51 | protected void put(final String fromPath, final String targetPath) { 52 | resolver.route(PUT, fromPath, targetPath); 53 | } 54 | 55 | // Shortcuts methods 56 | 57 | protected void get(final String path, Runnable runnable) { 58 | resolver.route(GET, path, runnable); 59 | } 60 | 61 | protected void post(final String path, Runnable runnable) { 62 | resolver.route(POST, path, runnable); 63 | } 64 | 65 | protected void put(final String path, Runnable runnable) { 66 | resolver.route(PUT, path, runnable); 67 | } 68 | 69 | protected void head(final String path, Runnable runnable) { 70 | resolver.route(HEAD, path, runnable); 71 | } 72 | 73 | protected void options(final String path, Runnable runnable) { 74 | resolver.route(OPTIONS, path, runnable); 75 | } 76 | 77 | protected void delete(final String path, Runnable runnable) { 78 | resolver.route(DELETE, path, runnable); 79 | } 80 | 81 | protected void trace(final String path, Runnable runnable) { 82 | resolver.route(TRACE, path, runnable); 83 | } 84 | 85 | protected void route(Runnable runnable) { 86 | resolver.route(runnable); 87 | } 88 | 89 | protected String POST = "POST"; 90 | protected String GET = "GET"; 91 | protected String HEAD = "HEAD"; 92 | protected String OPTIONS = "OPTIONS"; 93 | protected String PUT = "PUT"; 94 | protected String DELETE = "DELETE"; 95 | protected String TRACE = "TRACE"; 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/servlet/Servlet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.servlet; 18 | 19 | import static com.ghosthack.turismo.util.ClassForName.createInstance; 20 | 21 | import java.io.IOException; 22 | 23 | import javax.servlet.ServletConfig; 24 | import javax.servlet.ServletContext; 25 | import javax.servlet.ServletException; 26 | import javax.servlet.http.HttpServlet; 27 | import javax.servlet.http.HttpServletRequest; 28 | import javax.servlet.http.HttpServletResponse; 29 | 30 | import com.ghosthack.turismo.Resolver; 31 | import com.ghosthack.turismo.Routes; 32 | import com.ghosthack.turismo.action.ActionException; 33 | import com.ghosthack.turismo.util.ClassForName.ClassForNameException; 34 | 35 | 36 | /** 37 | * Action servlet. 38 | *

39 | * Resolves an action based on the request, Each route executes an 40 | * action. On init configures the {@link Routes} {@link Resolver}. 41 | * 42 | *

43 |  * 	<servlet>
44 |  * 		<servlet-name>app-action-servlet</servlet-name>
45 |  * 		<servlet-class>action.Servlet</servlet-class>
46 |  * 		<init-param>
47 |  * 			<param-name>routes</param-name>
48 |  * 			<param-value>example.Routes</param-value>
49 |  * 		</init-param>
50 |  * 	</servlet>
51 |  * 	<servlet-mapping>
52 |  * 		<servlet-name>app-action-servlet</servlet-name>
53 |  * 		<url-pattern>/app/*</url-pattern>
54 |  * 	</servlet-mapping>
55 |  * 
56 | */ 57 | public class Servlet extends HttpServlet { 58 | 59 | private static final String ROUTES = "routes"; 60 | private static final long serialVersionUID = 1L; 61 | 62 | protected transient Routes routes; 63 | protected transient ServletContext context; 64 | 65 | @Override 66 | public void service(HttpServletRequest req, HttpServletResponse res) 67 | throws ServletException, IOException { 68 | Env.create(req, res, context); 69 | try { 70 | final Runnable action = routes.getResolver().resolve(); 71 | action.run(); 72 | } catch (ActionException e) { 73 | throw new ServletException(e); 74 | } finally { 75 | Env.destroy(); 76 | } 77 | } 78 | 79 | @Override 80 | public void init(ServletConfig config) throws ServletException { 81 | super.init(); 82 | context = config.getServletContext(); 83 | final String routesParam = config.getInitParameter(ROUTES); 84 | try { 85 | routes = createInstance(routesParam, Routes.class); 86 | } catch (ClassForNameException e) { 87 | throw new ServletException(e); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/com/ghosthack/turismo/routes/RoutesMapTest.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.routes; 2 | 3 | import static com.ghosthack.turismo.HttpMocks.getRequestMock; 4 | import static com.ghosthack.turismo.HttpMocks.getResponseMock; 5 | import static org.mockito.Mockito.verify; 6 | 7 | import java.io.IOException; 8 | 9 | import javax.servlet.http.HttpServletRequest; 10 | import javax.servlet.http.HttpServletResponse; 11 | 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | import com.ghosthack.turismo.servlet.Env; 16 | 17 | public class RoutesMapTest { 18 | 19 | private RoutesMap routes; 20 | 21 | @Before 22 | public void setUp() { 23 | routes = new RoutesMap() { 24 | @Override 25 | protected void map() { 26 | // TEST GET 27 | get("/", new Runnable() { 28 | @Override 29 | public void run(){ 30 | Env.res().setStatus(200); 31 | } 32 | }); 33 | get("/foo", new Runnable() { 34 | @Override 35 | public void run() { 36 | Env.res().setStatus(201); 37 | } 38 | }); 39 | put("/", new Runnable() { 40 | @Override 41 | public void run() { 42 | Env.res().setStatus(202); 43 | } 44 | }); 45 | post("/bar", new Runnable() { 46 | @Override 47 | public void run() { 48 | Env.res().setStatus(203); 49 | } 50 | }); 51 | route(new Runnable() { 52 | @Override 53 | public void run() { 54 | Env.res().setStatus(404); 55 | } 56 | }); 57 | } 58 | }; 59 | } 60 | 61 | @Test 62 | public void testGET1() throws IOException { 63 | 64 | HttpServletRequest req = getRequestMock("GET", "/"); 65 | HttpServletResponse res = getResponseMock(); 66 | Env.create(req, res, null); 67 | routes.getResolver().resolve().run(); 68 | 69 | verify(res).setStatus(200); 70 | } 71 | 72 | @Test 73 | public void testGET2() throws IOException { 74 | 75 | HttpServletRequest req = getRequestMock("GET", "/foo"); 76 | HttpServletResponse res = getResponseMock(); 77 | Env.create(req, res, null); 78 | routes.getResolver().resolve().run(); 79 | 80 | verify(res).setStatus(201); 81 | } 82 | 83 | @Test 84 | public void testPUT() throws IOException { 85 | 86 | HttpServletRequest req = getRequestMock("PUT", "/"); 87 | HttpServletResponse res = getResponseMock(); 88 | Env.create(req, res, null); 89 | routes.getResolver().resolve().run(); 90 | 91 | verify(res).setStatus(202); 92 | } 93 | 94 | @Test 95 | public void testPOST() throws IOException { 96 | 97 | HttpServletRequest req = getRequestMock("POST", "/bar"); 98 | HttpServletResponse res = getResponseMock(); 99 | Env.create(req, res, null); 100 | routes.getResolver().resolve().run(); 101 | 102 | verify(res).setStatus(203); 103 | } 104 | 105 | @Test 106 | public void testNotFound() throws IOException { 107 | 108 | HttpServletRequest req = getRequestMock("POST", "/everyThingElse"); 109 | HttpServletResponse res = getResponseMock(); 110 | Env.create(req, res, null); 111 | routes.getResolver().resolve().run(); 112 | 113 | verify(res).setStatus(404); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/MultipartRequest.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | import javax.servlet.http.HttpServletRequest; 4 | import javax.servlet.http.HttpServletRequestWrapper; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.util.Collections; 9 | import java.util.Enumeration; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | /** 14 | * Wrapper that stores multipart form parameters. 15 | * 16 | */ 17 | public class MultipartRequest extends HttpServletRequestWrapper implements 18 | Parametrizable { 19 | 20 | /** Multipart form data boundary key */ 21 | public static final String MULTIPART_FORM_DATA_BOUNDARY = "multipart/form-data; boundary="; 22 | 23 | private static final String BOUNDARY_HEAD = "--"; 24 | private static final int MULTIPART_SIZE = MULTIPART_FORM_DATA_BOUNDARY 25 | .length(); 26 | 27 | private final Map parameterMap = new HashMap(); 28 | private String boundary; 29 | 30 | /** @see HttpServletRequestWrapper#HttpServletRequestWrapper(HttpServletRequest) */ 31 | public MultipartRequest(HttpServletRequest servletRequest) { 32 | super(servletRequest); 33 | final String contentType = getContentType(); 34 | if (contentType != null && contentType.length() > MULTIPART_SIZE) { 35 | boundary = BOUNDARY_HEAD + contentType.substring(MULTIPART_SIZE); 36 | } 37 | } 38 | 39 | /** 40 | * @see javax.servlet.ServletRequest#getParameterMap() 41 | * @return Map 42 | */ 43 | public Map getParameteMap() { 44 | return parameterMap; 45 | } 46 | 47 | /** @see javax.servlet.ServletRequest#getParameter(java.lang.String) */ 48 | public String getParameter(String name) { 49 | final String[] values = getParameterValues(name); 50 | return (values == null) ? null : values[0]; 51 | } 52 | 53 | /** @see javax.servlet.ServletRequest#getParameterNames() */ 54 | public Enumeration getParameterNames() { 55 | return Collections.enumeration(parameterMap.keySet()); 56 | } 57 | 58 | /** @see javax.servlet.ServletRequest#getParameterValues(java.lang.String) */ 59 | public String[] getParameterValues(String name) { 60 | return parameterMap.get(name); 61 | } 62 | 63 | /** @see Parametrizable#addParameter(String, String) */ 64 | public void addParameter(String name, String value) { 65 | String[] prev = parameterMap.put(name, new String[] { value }); 66 | if (prev != null) { 67 | int length = prev.length; 68 | length++; 69 | final String[] values = new String[length]; 70 | System.arraycopy(prev, 0, values, 0, prev.length); 71 | values[prev.length] = value; 72 | parameterMap.put(name, values); 73 | } 74 | } 75 | 76 | /** @see Parametrizable#addParameter(String, String[]) */ 77 | public void addParameter(String name, String[] value) { 78 | parameterMap.put(name, value); 79 | } 80 | 81 | /** 82 | * Gets the boundary obtained from the underlying request. 83 | * 84 | * @return boundary 85 | */ 86 | public String getBoundary() { 87 | return boundary; 88 | } 89 | 90 | public static MultipartRequest wrapAndParse(HttpServletRequest req) throws ParseException, IOException { 91 | final MultipartRequest multipart = new MultipartRequest(req); 92 | final String boundary = multipart.getBoundary(); 93 | final int size = req.getContentLength(); 94 | String encoding = req.getCharacterEncoding(); 95 | if(encoding == null) { 96 | encoding = MultipartFilter.CHARSET_NAME; 97 | } 98 | InputStream is = null; 99 | try { 100 | is = req.getInputStream(); 101 | new MultipartParser(is, boundary, multipart, encoding, size).parse(); 102 | } finally { 103 | if (is != null) 104 | is.close(); 105 | } 106 | return multipart; 107 | } 108 | 109 | } 110 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | org.sonatype.oss 6 | oss-parent 7 | 6 8 | 9 | 10 | 4.0.0 11 | 12 | com.ghosthack 13 | turismo 14 | 1.1.4-SNAPSHOT 15 | jar 16 | 17 | turismo 18 | turismo web framework 19 | http://turismo.ghosthack.com/ 20 | 21 | 22 | UTF-8 23 | 7.4.5.v20110725 24 | 25 | 26 | 27 | 28 | The Apache Software License, Version 2.0 29 | http://www.apache.org/licenses/LICENSE-2.0.txt 30 | repo 31 | 32 | 33 | 34 | 35 | scm:git:git@github.com:ghosthack/turismo.git 36 | scm:git:git@github.com:ghosthack/turismo.git 37 | https://github.com/ghosthack/turismo 38 | 39 | 40 | 41 | 42 | 43 | maven-compiler-plugin 44 | 2.3.2 45 | 46 | ${project.build.sourceEncoding} 47 | 1.6 48 | 1.6 49 | 50 | 51 | 52 | maven-source-plugin 53 | 2.1.2 54 | 55 | 56 | attach-sources 57 | 58 | jar 59 | 60 | 61 | 62 | 63 | 64 | maven-javadoc-plugin 65 | 2.8 66 | 67 | 68 | attach-javadocs 69 | 70 | jar 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | javax.servlet 81 | servlet-api 82 | 2.5 83 | provided 84 | 85 | 86 | junit 87 | junit 88 | 4.4 89 | test 90 | 91 | 92 | org.mockito 93 | mockito-core 94 | 1.9.0-rc1 95 | test 96 | 97 | 98 | org.eclipse.jetty 99 | jetty-servlet 100 | ${jetty.version} 101 | test 102 | 103 | 104 | org.eclipse.jetty 105 | jetty-jsp-2.1 106 | ${jetty.version} 107 | test 108 | 109 | 110 | org.eclipse.jetty 111 | jetty-webapp 112 | ${jetty.version} 113 | test 114 | 115 | 116 | org.apache.tomcat 117 | jasper 118 | 6.0.33 119 | test 120 | 121 | 122 | 123 | 124 | 125 | adrian 126 | adrian 127 | adrian@ghosthack.com 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/MultipartFilter.java: -------------------------------------------------------------------------------- 1 | package com.ghosthack.turismo.multipart; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.FilterChain; 6 | import javax.servlet.FilterConfig; 7 | import javax.servlet.ServletException; 8 | import javax.servlet.ServletRequest; 9 | import javax.servlet.ServletResponse; 10 | import javax.servlet.http.HttpServletRequest; 11 | 12 | /** 13 | * Parses submitted multipart form data and creates a new request object. 14 | *

15 | * If you have a custom Request object added to the filter chain, this filter 16 | * should be the first. 17 | *

18 | *

19 | * The file parameter data is added as a byte array attribute with the same 20 | * parameter name.

Example: 21 | * 22 | *
 23 |  *      <input type="file" name="imageFile"/>
 24 |  * 
25 | * 26 | * File contents are be obtained via: 27 | * 28 | *
 29 |  * byte[] imageFileBytes = request.getAttribute("imageFile");
 30 |  * 
31 | * 32 | * for later manipulation.
33 | * 34 | * The file-name and content-type data sent by the browser are stored as a 35 | * String array in the request object.
Example: 36 | * 37 | *
 38 |  *      <input type="file" name="imageFile"/>
 39 |  * 
40 | * 41 | * File name and content type are obtained like this: 42 | * 43 | *
 44 |  * String contentType = request.getParameter("imageFile")[0];
 45 |  * String fileName = request.getParameter("imageFile")[1];
 46 |  * 
47 | * 48 | *
49 | * 50 | *

51 | *

52 | * Configuration details: 53 | * 54 | *

 55 |  *  <filter>
 56 |  *      <filter-name>multipart-filter</filter-name>
 57 |  *      <filter-class>multipart.Filter</filter-class>
 58 |  *      <init-param>
 59 |  *          <param-name>charset-name</param-name>
 60 |  *          <param-value>ISO-8859-1</param-value>
 61 |  *      </init-param>
 62 |  *  </filter>
 63 |  *  <filter-mapping>
 64 |  *      <filter-name>multipart-filter</filter-name>
 65 |  *      <url-pattern>/eon/*</url-pattern>
 66 |  *  </filter-mapping>
 67 |  * 
68 | * 69 | *

70 | * 71 | */ 72 | public class MultipartFilter implements javax.servlet.Filter { 73 | 74 | private static final String CHARSET_NAME_PARAMETER = "charset-name"; 75 | public static String CHARSET_NAME = "ISO-8859-1"; 76 | 77 | // private static final java.util.logging.Logger LOG = 78 | // java.util.logging.Logger.getLogger(Filter.class.getName()); 79 | 80 | /** 81 | * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 82 | * javax.servlet.ServletResponse, javax.servlet.FilterChain) 83 | */ 84 | public void doFilter(ServletRequest request, ServletResponse response, 85 | FilterChain chain) throws ServletException, IOException { 86 | 87 | final String contentType = request.getContentType(); 88 | if (contentType != null 89 | && contentType.startsWith(MultipartRequest.MULTIPART_FORM_DATA_BOUNDARY)) { 90 | final MultipartRequest multipartRequest; 91 | try { 92 | multipartRequest = MultipartRequest.wrapAndParse((HttpServletRequest) request); 93 | } catch (ParseException e) { 94 | dump(request); 95 | throw new ServletException(e); 96 | } 97 | chain.doFilter(multipartRequest, response); 98 | } else { 99 | chain.doFilter(request, response); 100 | } 101 | 102 | } 103 | 104 | private void dump(ServletRequest request) throws IOException { 105 | // final String data = new Dumper().dump(request.getInputStream(), 106 | // Filter.CHARSET_NAME); 107 | // LOG.info(data); 108 | } 109 | 110 | /** 111 | * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 112 | */ 113 | public void init(FilterConfig config) throws ServletException { 114 | final String charsetName = config 115 | .getInitParameter(CHARSET_NAME_PARAMETER); 116 | if (charsetName != null) { 117 | MultipartFilter.CHARSET_NAME = charsetName; 118 | } 119 | } 120 | 121 | /** 122 | * @see javax.servlet.Filter#destroy() 123 | */ 124 | public void destroy() { 125 | // nothing today 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | turismo -- a sinatra-like Java web framework. 2 | ============================================= 3 | 4 | [![Build Status](https://travis-ci.org/ghosthack/turismo.svg?branch=master)](https://travis-ci.org/ghosthack/turismo) [![Javadocs](https://javadoc.io/badge/com.ghosthack/turismo.svg)](https://javadoc.io/doc/com.ghosthack/turismo) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.ghosthack/turismo/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.ghosthack/turismo) 5 | 6 | Quick intro 7 | ----------- 8 | ```java 9 | public class AppRoutes extends RoutesList { 10 | protected void map() { 11 | get("/", new Action() { 12 | public void run() { 13 | print("Hello World!"); 14 | } 15 | }); 16 | } 17 | } 18 | ``` 19 | 20 | Using wildcards and resource identifiers 21 | ---------------------------------------- 22 | 23 | ```java 24 | public class AppRoutes extends RoutesList { 25 | protected void map() { 26 | get("/wildcard/*/:id", new Action() { 27 | public void run() { 28 | String id = params("id"); 29 | print("wildcard id " + id); 30 | } 31 | }); 32 | get("/alias/*/:id", "/wildcard/*/:id"); 33 | } 34 | } 35 | ``` 36 | 37 | Testing with standalone jetty 38 | ----------------------------- 39 | 40 | ```java 41 | package com.ghosthack.turismo.example; 42 | 43 | import com.ghosthack.turismo.action.*; 44 | import com.ghosthack.turismo.routes.*; 45 | 46 | public class AppRoutes extends RoutesList { 47 | 48 | @Override 49 | protected void map() { 50 | get("/", new Action() { 51 | @Override 52 | public void run() { 53 | print("Hello World!"); 54 | } 55 | }); 56 | } 57 | 58 | public static void main(String[] args) throws Exception{ 59 | JettyHelper.server(8080, "/*", AppRoutes.class.getName()); 60 | } 61 | 62 | } 63 | ``` 64 | 65 | Getting started, as webapp 66 | -------------------------- 67 | 68 | Using a webapp descriptor: `web.xml` 69 | 70 | ```xml 71 | 72 | 75 | 76 | 77 | webapp-servlet 78 | com.ghosthack.turismo.servlet.Servlet 79 | 80 | routes 81 | com.ghosthack.turismo.example.WebAppRoutes 82 | 83 | 84 | 85 | webapp-servlet 86 | /* 87 | 88 | 89 | 90 | ``` 91 | 92 | Implementing routes 93 | 94 | ```java 95 | package com.ghosthack.turismo.example; 96 | 97 | import com.ghosthack.turismo.action.*; 98 | import com.ghosthack.turismo.routes.*; 99 | 100 | public class WebAppRoutes extends RoutesList { 101 | 102 | @Override 103 | protected void map() { 104 | get("/", new Action() { 105 | @Override 106 | public void run() { 107 | print("Hello World!"); 108 | } 109 | }); 110 | } 111 | 112 | } 113 | ``` 114 | 115 | Rendering "templates" 116 | --------------------- 117 | 118 | Using a jsp: 119 | 120 | ```java 121 | get("/render", new Action() { 122 | public void run() { 123 | req().setAttribute("message", "Hello Word!"); 124 | jsp("/jsp/render.jsp"); 125 | } 126 | }); 127 | ``` 128 | 129 | And the `render.jsp` contains: 130 | 131 | ```xml 132 | <%=request.getAttribute("message")%> 133 | ``` 134 | 135 | Other mappings 136 | -------------- 137 | 138 | Methods for GET, POST, PUT, DELETE, HEAD, OPTIONS, TRACE 139 | 140 | ```java 141 | post("/search", new Action() { 142 | public void run() { 143 | String query = req().getParameter("q"); 144 | print("Your search query was: " + query) 145 | } 146 | }); 147 | ``` 148 | 149 | The default route in RoutesMap/RoutesList sends a 404. Rewire with another action: 150 | 151 | ```java 152 | route(new Action() { 153 | public void run() { 154 | try { 155 | res().sendError(404, "Not Found"); 156 | } catch (IOException e) { 157 | throw new ActionException(e); 158 | } 159 | } 160 | }); 161 | ``` 162 | 163 | Multipart 164 | --------- 165 | 166 | ```java 167 | post("/image", new Action() { 168 | void run() { 169 | MultipartRequest request = MultipartFilter.wrapAndParse(req()); 170 | String[] meta = request.getParameterValues("image"); 171 | byte[] bytes = (byte[]) request.getAttribute("image"); 172 | LOG.info("type: %s, name: %s, %d bytes", meta[0], meta[1], bytes.length); 173 | } 174 | }); 175 | ``` 176 | 177 | 178 | Maven repository 179 | ---------------- 180 | 181 | ```xml 182 | 183 | com.ghosthack 184 | turismo 185 | 1.1.3 186 | 187 | ``` 188 | 189 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/resolver/ListResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Adrian Fernandez 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 | * use this file except in compliance with the License. You may obtain a copy of 6 | * the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations under 14 | * the License. 15 | */ 16 | 17 | package com.ghosthack.turismo.resolver; 18 | 19 | import java.util.ArrayList; 20 | import java.util.HashMap; 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.Map.Entry; 24 | import java.util.Set; 25 | 26 | import com.ghosthack.turismo.servlet.Env; 27 | 28 | 29 | public class ListResolver extends MethodPathResolver { 30 | 31 | private HashMap> methodPathList; 32 | private Runnable defaultRunnable; 33 | private static final String SYMBOL_PREFIX = ":"; 34 | private static final String WILDCARD = "*"; 35 | 36 | public static class ParsedEntry { 37 | private final Runnable runnable; 38 | private final String path; 39 | private String[] parts; 40 | private List paramList; 41 | private List wildcardList; 42 | private Set> params; 43 | public ParsedEntry(Runnable runnable, String path) { 44 | super(); 45 | if(path == null) 46 | throw new IllegalArgumentException(); 47 | this.runnable = runnable; 48 | this.path = path; 49 | parts = path.split("/"); 50 | if(parts.length>0) { 51 | Map paramMap = new HashMap(parts.length); 52 | paramList = new ArrayList(parts.length); 53 | wildcardList = new ArrayList(parts.length); 54 | for(int i = 0; i> getParams() { 77 | return params; 78 | } 79 | public boolean pathEquals(String path) { 80 | return this.path.equals(path); 81 | } 82 | public String[] getParts() { 83 | return parts; 84 | } 85 | public Runnable getRunnable() { 86 | return runnable; 87 | } 88 | public String getPath() { 89 | return path; 90 | } 91 | } 92 | 93 | public ListResolver() { 94 | methodPathList = new HashMap>(); 95 | } 96 | 97 | /** 98 | * The target path must exist. Target can't have a different parameter spec. A hashmap could be used to enhance impl. 99 | * @param method 100 | * @param newPath 101 | * @param targetPath 102 | * @throws IllegalArgumentException if the parameter HTTP method hasn't anything mapped, also, when the "target" path isn't found 103 | */ 104 | @Override 105 | public void route(String method, String newPath, String targetPath) { 106 | List pathList = methodPathList.get(method); 107 | if(pathList == null) throw new IllegalArgumentException(method); 108 | for(ParsedEntry parsedEntry: pathList) { 109 | if(parsedEntry.getPath().equals(targetPath)) { 110 | Runnable runnable = parsedEntry.getRunnable(); 111 | pathList.add(new ParsedEntry(runnable, newPath)); 112 | return; 113 | } 114 | } 115 | throw new IllegalArgumentException(targetPath); 116 | } 117 | 118 | @Override 119 | public void route(String method, String path, Runnable runnable) { 120 | List pathList = methodPathList.get(method); 121 | if(pathList == null) { 122 | pathList = new ArrayList(); 123 | methodPathList.put(method, pathList); 124 | } 125 | ParsedEntry parsed = new ParsedEntry(runnable, path); 126 | pathList.add(parsed); 127 | } 128 | 129 | public void route(Runnable runnable) { 130 | this.defaultRunnable = runnable; 131 | } 132 | 133 | @Override 134 | protected Runnable resolve(String method, String path) { 135 | List pathList = methodPathList.get(method); 136 | if(pathList != null && path != null) { 137 | for(ParsedEntry parsedEntry: pathList) { 138 | String[] parts = parsedEntry.getParts(); 139 | if(parts == null) { 140 | if(parsedEntry.pathEquals(path)) { 141 | return parsedEntry.getRunnable(); 142 | } 143 | } else { 144 | String[] splitted = path.split("/"); 145 | if(parts.length == splitted.length) { 146 | boolean match = true; 147 | boolean hasParams = false; 148 | for(int i = 0; i < splitted.length; i++) { 149 | if(parsedEntry.isWildcard(i)) { 150 | // skipped 151 | } else if(parsedEntry.isParam(i)) { 152 | // it's a resource "symbol" 153 | hasParams = true; 154 | } else if(parts[i].equals(splitted[i])) { 155 | // exact match 156 | } else { 157 | // not a match 158 | match = false; 159 | break; 160 | } 161 | } 162 | if(match) { 163 | if(hasParams) { 164 | Map params = new HashMap(); 165 | for(Map.Entry paramsEntry: parsedEntry.getParams()) { 166 | String paramKey = paramsEntry.getKey(); 167 | Integer paramPos = paramsEntry.getValue(); 168 | params.put(paramKey, splitted[paramPos]); 169 | } 170 | Env.setResourceParams(params); 171 | } 172 | return parsedEntry.getRunnable(); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | //default route, no mapping found 179 | return defaultRunnable; 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /src/main/java/com/ghosthack/turismo/multipart/MultipartParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Created on May 2, 2004 3 | */ 4 | package com.ghosthack.turismo.multipart; 5 | 6 | import java.io.BufferedInputStream; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.ByteBuffer; 10 | import java.nio.charset.CharacterCodingException; 11 | import java.nio.charset.Charset; 12 | import java.nio.charset.CharsetDecoder; 13 | import java.util.Arrays; 14 | import java.util.logging.Logger; 15 | 16 | /** 17 | * Partial implementation of rfc1867. 19 | * 20 | * @author Adrian 21 | */ 22 | public final class MultipartParser { 23 | 24 | /** 25 | * Constructs a new parser for multipart form data. 26 | * 27 | * @param is 28 | * the byte input stream, can't be null. 29 | * @param boundary 30 | * the complete boundary (including the extra starting "--"), 31 | * can't be null. 32 | * @param parameters 33 | * the container used to store parameters, can't be null. 34 | * @param charsetName 35 | * the charset used to decode bytes as strings, can't be null. 36 | * @param size 37 | * the size used to create the byte buffer, usually request content-length. 38 | */ 39 | public MultipartParser(final InputStream is, final String boundary, 40 | final Parametrizable parameters, final String charsetName, 41 | final int size) { 42 | if (is == null || boundary == null || parameters == null 43 | || charsetName == null) 44 | throw new IllegalArgumentException(); 45 | this.is = new BufferedInputStream(is, BUFFER_SIZE); 46 | this.parameters = parameters; 47 | charsetDecoder = Charset.forName(charsetName).newDecoder(); 48 | boundarySize = boundary.getBytes().length; 49 | separator = (LINE_STRING + boundary).getBytes(); 50 | buffer = new byte[separator.length + OFFSET]; 51 | // Allocates the full file size in memmory, will throw OOME if memory is 52 | // not enough 53 | // Beware: there is a bug in 1.4.2_04 and earlier versions 54 | // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4879883 55 | bb = ByteBuffer.allocateDirect(size); 56 | } 57 | 58 | /** 59 | * Parses the multipart form data. 60 | * 61 | * @throws ParseException 62 | * if end of stream is reached prematurely. 63 | * @throws java.io.IOException 64 | * throwed by the underlying input stream. 65 | */ 66 | public final void parse() throws ParseException, IOException { 67 | 68 | final long t0 = System.currentTimeMillis(); 69 | 70 | skip(boundarySize + END_SIZE); 71 | 72 | do { 73 | 74 | skip(CONTENT_DISPOSITION_2_SIZE); 75 | 76 | final String name = decodeUntil(QUOTE); 77 | 78 | if (isFile = skipUntilAny(QUOTE, LINE)) { 79 | final String[] file = new String[FILE_DESC]; 80 | file[NAME_POS] = decodeUntil(QUOTE); 81 | skip(CONTENT_TYPE_SIZE); 82 | file[CONTENT_TYPE_POS] = decodeUntil(LINE); 83 | parameters.addParameter(name, file); 84 | } 85 | 86 | skip(LINE_SIZE); 87 | readUntil(separator); 88 | read(eval); 89 | 90 | if (isFile) { 91 | parameters.setAttribute(name, bytes()); 92 | } else { 93 | parameters.addParameter(name, decode()); 94 | } 95 | 96 | } while (!Arrays.equals(eval, END)); 97 | 98 | LOG.fine(PARSE_TIME + (System.currentTimeMillis() - t0)); 99 | 100 | } 101 | 102 | private String decode() throws CharacterCodingException { 103 | return charsetDecoder.decode(bb).toString(); 104 | } 105 | 106 | private byte[] bytes() { 107 | final byte[] bytes = new byte[bb.remaining()]; 108 | bb.get(bytes); 109 | return bytes; 110 | } 111 | 112 | private void read() throws ParseException, IOException { 113 | if ((bi = is.read()) == END_OF_STREAM) 114 | throw new ParseException(); 115 | b = (byte) bi; 116 | } 117 | 118 | private void read(final byte[] bytes) throws ParseException, IOException { 119 | if (is.read(bytes) == END_OF_STREAM) 120 | throw new ParseException(); 121 | } 122 | 123 | private void skip(final long n) throws ParseException, IOException { 124 | if (n != is.skip(n)) 125 | throw new ParseException(); 126 | } 127 | 128 | /** 129 | * Reads until the limit is met. 130 | */ 131 | private void readUntil(final byte[] limit) throws ParseException, 132 | IOException { 133 | boolean coincidenceComplete = false; 134 | int coincidence = 0; 135 | bb.clear(); 136 | while (!coincidenceComplete) { 137 | read(); 138 | if (coincidence > 0 && b != limit[coincidence]) { 139 | bb.put(buffer, 0, coincidence); 140 | coincidence = 0; 141 | } 142 | if (b == limit[coincidence]) { 143 | buffer[coincidence] = b; 144 | coincidenceComplete = (++coincidence == limit.length); 145 | } else { 146 | bb.put(b); 147 | } 148 | } 149 | bb.flip(); 150 | } 151 | 152 | private String decodeUntil(final byte[] limit) throws ParseException, 153 | IOException { 154 | readUntil(limit); 155 | return decode(); 156 | } 157 | 158 | /** 159 | * Reads until one of the limits are met. 160 | * 161 | * @return true when matched with the first limit. 162 | */ 163 | private boolean skipUntilAny(final byte[] limit, final byte[] limit2) 164 | throws ParseException, IOException { 165 | boolean coincidenceComplete = false; 166 | boolean coincidenceComplete2 = false; 167 | int coincidenceNumber = 0; 168 | int coincidenceNumber2 = 0; 169 | while (!coincidenceComplete && !coincidenceComplete2) { 170 | read(); 171 | if (coincidenceNumber > 0 && b != limit[coincidenceNumber]) 172 | coincidenceNumber = 0; 173 | if (coincidenceNumber2 > 0 && b != limit2[coincidenceNumber2]) 174 | coincidenceNumber2 = 0; 175 | 176 | if (b == limit[coincidenceNumber]) { 177 | coincidenceComplete = (++coincidenceNumber == limit.length); 178 | } else if (b == limit2[coincidenceNumber2]) { 179 | coincidenceComplete2 = (++coincidenceNumber2 == limit2.length); 180 | } 181 | } 182 | return coincidenceComplete; 183 | } 184 | 185 | private static final String LINE_STRING = "\r\n"; 186 | private static final byte[] LINE = LINE_STRING.getBytes(); 187 | private static final int LINE_SIZE = LINE.length; 188 | private static final int CONTENT_TYPE_SIZE = LINE_SIZE 189 | + "Content-Type: ".getBytes().length; 190 | private static final int CONTENT_DISPOSITION_SIZE = LINE_SIZE 191 | + "Content-Disposition: form-data; name=\"".getBytes().length; 192 | private static final int BUFFER_SIZE = 10 * 1024; 193 | private static final byte[] QUOTE = "\"".getBytes(); 194 | private static final byte[] END = "--".getBytes(); 195 | private static final int END_SIZE = END.length; 196 | private static final int CONTENT_DISPOSITION_2_SIZE = CONTENT_DISPOSITION_SIZE 197 | - END_SIZE; 198 | private static final int OFFSET = 1; 199 | private static final int FILE_DESC = 2; 200 | private static final int CONTENT_TYPE_POS = 0; 201 | private static final int NAME_POS = 1; 202 | private static final int END_OF_STREAM = -1; 203 | private static final String PARSE_TIME = "parseTime[ms]: "; 204 | private static final Logger LOG = Logger.getLogger(MultipartParser.class.getName()); 205 | 206 | private InputStream is; 207 | private Parametrizable parameters; 208 | private CharsetDecoder charsetDecoder; 209 | private int boundarySize; 210 | private byte[] separator; 211 | private byte[] buffer; 212 | 213 | private boolean isFile; 214 | 215 | private byte b; 216 | private int bi; 217 | private ByteBuffer bb; 218 | 219 | private final byte[] eval = new byte[END_SIZE]; 220 | 221 | } 222 | --------------------------------------------------------------------------------