Commits

Stephen McKamey committed 63dfb50

adding staticapp routing servlet

Comments (0)

Files changed (7)

duel-staticapps-maven-plugin/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>duel-staticapps-maven-plugin</artifactId>
-	<version>0.8.4</version>
+	<version>0.8.5</version>
 	<packaging>maven-plugin</packaging>
 
 	<name>DUEL Static Apps Maven Plugin</name>

duel-staticapps/example-config.json

 {
+	"isDevMode": false,
+	"contentType": "text/html",
+	"encoding": "UTF-8",
 	"targetDir": "target/www/",
 	"sourceDir": "target/foo-web/",
 	"serverPrefix": "com.example.web.views",
 	"cdnMap": "cdn",
 	"cdnLinksMap": "cdnLinks",
 	"cdnHost": ".",
-	"isDevMode": false,
 	"views": {
 		"index.html":
 			{

duel-staticapps/pom.xml

 
 	<groupId>org.duelengine</groupId>
 	<artifactId>duel-staticapps</artifactId>
-	<version>0.8.4</version>
+	<version>0.8.5</version>
 	<packaging>jar</packaging>
 
 	<name>DUEL Static Apps</name>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 
 		<duel.version>0.8.2</duel.version>
-		<jackson.version>1.9.4</jackson.version>
+		<jackson.version>1.9.6</jackson.version>
 		<codec.version>1.6</codec.version>
 		<slf4j.version>1.6.4</slf4j.version>
+		<servlet.version>2.5</servlet.version>
 		<junit.version>4.9</junit.version>
 		<jvm.version>1.6</jvm.version>
 	</properties>
 			<scope>test</scope>
 		</dependency>
 
+		<!-- Servlet interfaces -->
+		<dependency>
+			<groupId>javax.servlet</groupId>
+			<artifactId>servlet-api</artifactId>
+			<version>${servlet.version}</version>
+			<scope>provided</scope>
+		</dependency>
+
 		<!-- Unit test library -->
 		<dependency>
 			<groupId>junit</groupId>

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/RoutingServlet.java

+package org.duelengine.duel.staticapps;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.codehaus.jackson.map.ObjectMapper;
+import org.duelengine.duel.CDNLinkInterceptor;
+import org.duelengine.duel.DuelContext;
+import org.duelengine.duel.DuelView;
+import org.duelengine.duel.FormatPrefs;
+import org.duelengine.duel.LinkInterceptor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class RoutingServlet extends HttpServlet {
+	private static final Logger log = LoggerFactory.getLogger(RoutingServlet.class);
+
+	private static final long serialVersionUID = 8465487004837241467L;
+	private final static String DEFAULT_DOC = "index.html";
+
+	private SiteConfig config;
+	private FormatPrefs format;
+	private LinkInterceptor linkInterceptor;
+
+	@Override
+	public void init(ServletConfig servletConfig)
+			throws ServletException {
+
+		super.init(servletConfig);
+
+		try {
+			// load from config file
+			String configPath = servletConfig.getInitParameter("config-path");
+			File configFile = new File(configPath);
+			config = new ObjectMapper().reader(SiteConfig.class).readValue(configFile);
+
+		} catch (Exception ex) {
+			log.error("Error loading staticapp config", ex);
+			config = new SiteConfig();
+		}
+
+		String devModeOverride = servletConfig.getInitParameter("dev-mode-override");
+		if (devModeOverride != null && !devModeOverride.isEmpty()) {
+			log.info("dev-mode-override="+devModeOverride);
+			config.isDevMode( Boolean.parseBoolean(devModeOverride) );
+		}
+
+		format = new FormatPrefs()
+			.setEncoding(config.encoding())
+			.setIndent(config.isDevMode() ? "\t" : "")
+			.setNewline(config.isDevMode() ? "\n" : "");
+
+		try {
+			String bundleName = config.cdnMap();
+			ResourceBundle cdnBundle =
+				(bundleName == null) || bundleName.isEmpty() ? null :
+				ResourceBundle.getBundle(bundleName, Locale.ROOT);
+
+			linkInterceptor = new CDNLinkInterceptor(
+				config.cdnHost(),
+				cdnBundle,
+				config.isDevMode());
+
+		} catch (URISyntaxException ex) {
+			log.error("CDN URI Error", ex);
+
+			linkInterceptor = new LinkInterceptor() {
+				@Override
+				public String transformURL(String url) {
+					return url+"#CDN-ERROR";
+				}
+			};
+		}
+	}
+
+	/**
+	 * Service the request
+	 */
+	public void doGet(HttpServletRequest request, HttpServletResponse response) {
+		try {
+			// response headers
+			response.setContentType(config.contentType());
+			response.setCharacterEncoding(config.encoding());
+
+			SiteViewPage sitePage = route(request.getServletPath());
+			if (sitePage == null) {
+				defaultServlet(request, response);
+				return;
+			}
+
+			DuelContext context = new DuelContext()
+				.setFormat(format)
+				.setLinkInterceptor(linkInterceptor)
+				.setData(sitePage.data())
+				.setOutput(response.getWriter());
+	
+			if (sitePage.extras() != null) {
+				// ambient client-side data
+				context.putExtras(sitePage.extras());
+			}
+
+			DuelView view = sitePage.viewInstance(config.serverPrefix(), Thread.currentThread().getContextClassLoader());
+			if (view == null) {
+				defaultServlet(request, response);
+				return;
+			}
+
+			// response body
+			view.render(context);
+
+		} catch (Exception ex) {
+			try {
+				ex.printStackTrace(response.getWriter());
+				response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
+
+			} catch (IOException ex2) {
+				ex2.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * Use the config to statically route the request to the view.
+	 * @param servletPath
+	 * @return
+	 */
+	private SiteViewPage route(String servletPath) {
+		log.info("routing: "+servletPath);
+
+		if (config.views() == null) {
+			return null;
+		}
+
+		// TODO: expand routing capabilities beyond exact match and default doc
+		
+		SiteViewPage page = config.views().get(servletPath.substring(1));
+		if (page == null) {
+			if (servletPath.endsWith("/")) {
+				// continue to attempt to resolve with default document
+				return route(servletPath+DEFAULT_DOC);
+			}
+			return null;
+		}
+
+		return page;
+	}
+
+	/**
+	 * Pass the request onto the default servlet
+	 * @param request
+	 * @param response
+	 * @throws ServletException
+	 * @throws IOException
+	 */
+	private void defaultServlet(HttpServletRequest request, HttpServletResponse response)
+			throws ServletException, IOException {
+
+		getServletContext().getNamedDispatcher("default").forward(request, response);
+	}
+}

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/SiteBuilder.java

 		}
 
 		FormatPrefs formatPrefs = new FormatPrefs()
-			.setEncoding("UTF-8")
-			.setIndent("")
-			.setNewline("");
+			.setEncoding(config.encoding())
+			.setIndent(config.isDevMode() ? "\t" : "")
+			.setNewline(config.isDevMode() ? "\n" : "");
 
 		Map<String, SiteViewPage> views = config.views();
 		if (views != null) {
 						context.putExtras(extras);
 					}
 
-					viewClass(config.serverPrefix(), sitePage.view()).newInstance().render(context);
+					DuelView view = sitePage.viewInstance(config.serverPrefix(), classLoader);
+					if (view != null) {
+						view.render(context);
+					}
 
 				} catch (Exception ex) {
 					log.error(ex.getMessage(), ex);
 		log.info("Copying "+path+" as "+cdnPath);
 		FileUtil.copy(source, target, true, buffer);
 	}
-
-	/**
-	 * @return the view class
-	 * @throws ClassNotFoundException 
-	 */
-	private Class<? extends DuelView> viewClass(String serverPrefix, String viewName)
-			throws ClassNotFoundException {
-
-		if (serverPrefix != null && !serverPrefix.isEmpty()) {
-			if (serverPrefix.endsWith(".")) {
-				viewName = serverPrefix + viewName;
-			} else {
-				viewName = serverPrefix + '.' + viewName;
-			}
-		}
-		return Class.forName(viewName, true, classLoader).asSubclass(DuelView.class);
-	}
 }

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/SiteConfig.java

 @JsonIgnoreProperties(ignoreUnknown=true)
 public class SiteConfig {
 
+	private final static String TEXT_HTML = "text/html";
+	private final static String UTF8 = "UTF-8";
+
+	private String contentType;
+	private String encoding;
 	private String targetDir;
 	private String sourceDir;
 	private String serverPrefix;
 	private File sourceDirFile;
 	private File targetDirFile;
 
+	public String contentType() {
+		if (contentType == null || contentType.isEmpty()) {
+			return TEXT_HTML;
+		}
+		return contentType;
+	}
+
+	public SiteConfig contentType(String value) {
+		contentType = value;
+		return this;
+	}
+
+	public String encoding() {
+		if (encoding == null || encoding.isEmpty()) {
+			return UTF8;
+		}
+		return encoding;
+	}
+
+	public SiteConfig encoding(String value) {
+		encoding = value;
+		return this;
+	}
+
 	/**
 	 * Gets the target directory
 	 */

duel-staticapps/src/main/java/org/duelengine/duel/staticapps/SiteViewPage.java

 import java.util.Map;
 
 import org.codehaus.jackson.annotate.JsonProperty;
+import org.duelengine.duel.DuelView;
 
 public class SiteViewPage {
 
 		extras = value;
 		return this;
 	}
+
+	/**
+	 * @return the view class
+	 * @throws ClassNotFoundException 
+	 */
+	protected Class<? extends DuelView> viewClass(String serverPrefix, ClassLoader classLoader)
+			throws ClassNotFoundException {
+
+		if (view == null) {
+			return null;
+		}
+
+		String type = view;
+		if (serverPrefix != null && !serverPrefix.isEmpty()) {
+			if (serverPrefix.endsWith(".")) {
+				type = serverPrefix + type;
+			} else {
+				type = serverPrefix + '.' + type;
+			}
+		}
+
+		return Class.forName(type, true, classLoader).asSubclass(DuelView.class);
+	}
+
+	/**
+	 * @return the view instance
+	 * @throws ClassNotFoundException 
+	 * @throws IllegalAccessException 
+	 * @throws InstantiationException 
+	 */
+	protected DuelView viewInstance(String serverPrefix, ClassLoader classLoader)
+			throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+		Class<? extends DuelView> viewClass = viewClass(serverPrefix, classLoader);
+
+		return (viewClass != null) ? viewClass.newInstance() : null;
+	}
 }