From: David ‘Bombe’ Roden Date: Tue, 12 Oct 2010 18:30:56 +0000 (+0200) Subject: Add interface between templates and toadlets. X-Git-Tag: 0.1-RC1~530 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=b2bc9337f91c303e2700f001c785e8b14a9990ec;p=Sone.git Add interface between templates and toadlets. --- diff --git a/src/main/java/net/pterodactylus/sone/web/page/CSSPage.java b/src/main/java/net/pterodactylus/sone/web/page/CSSPage.java new file mode 100644 index 0000000..9688f49 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/CSSPage.java @@ -0,0 +1,71 @@ +/* + * shortener - CSSPage.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +import java.io.InputStream; + +/** + * {@link Page} implementation that delivers CSS files from the class path. + * + * @author David ‘Bombe’ Roden + */ +public class CSSPage implements Page { + + /** The prefix for {@link #getPath()}. */ + private final String pathPrefix; + + /** The path used as prefix when loading resources. */ + private final String resourcePathPrefix; + + /** + * Creates a new CSS page. + * + * @param pathPrefix + * The prefix for {@link #getPath()} + * @param resourcePathPrefix + * The path prefix when loading resources + */ + public CSSPage(String pathPrefix, String resourcePathPrefix) { + this.pathPrefix = pathPrefix; + this.resourcePathPrefix = resourcePathPrefix; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return pathPrefix; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(Request request) { + String path = request.getURI().getPath(); + int lastSlash = path.lastIndexOf('/'); + String cssFilename = path.substring(lastSlash + 1); + InputStream cssInputStream = getClass().getResourceAsStream(resourcePathPrefix + cssFilename); + if (cssInputStream == null) { + return new Response(404, "Not found.", null, (String) null); + } + return new Response(200, "OK", "text/css", null, cssInputStream); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/Page.java b/src/main/java/net/pterodactylus/sone/web/page/Page.java new file mode 100644 index 0000000..a1125cc --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/Page.java @@ -0,0 +1,356 @@ +/* + * shortener - Page.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import freenet.clients.http.ToadletContext; +import freenet.support.api.HTTPRequest; + +/** + * A page is responsible for handling HTTP requests and creating appropriate + * responses. + * + * @author David ‘Bombe’ Roden + */ +public interface Page { + + /** + * Returns the path of this toadlet. + * + * @return The path of this toadlet + */ + public String getPath(); + + /** + * Handles a request. + * + * @param request + * The request to handle + * @return The response + */ + public Response handleRequest(Request request); + + /** + * Container for request data. + * + * @author David ‘Bombe’ Roden + */ + public class Request { + + /** The URI that was accessed. */ + private final URI uri; + + /** The HTTP method that was used. */ + private final String method; + + /** The HTTP request. */ + private final HTTPRequest httpRequest; + + /** The toadlet context. */ + private final ToadletContext toadletContext; + + /** + * Creates a new request that holds the given data. + * + * @param uri + * The URI of the request + * @param method + * The HTTP method of the request + * @param httpRequest + * The HTTP request + * @param toadletContext + * The toadlet context of the request + */ + public Request(URI uri, String method, HTTPRequest httpRequest, ToadletContext toadletContext) { + this.uri = uri; + this.method = method; + this.httpRequest = httpRequest; + this.toadletContext = toadletContext; + } + + /** + * Returns the URI that was accessed. + * + * @return The accessed URI + */ + public URI getURI() { + return uri; + } + + /** + * Returns the HTTP method that was used to access the page. + * + * @return The HTTP method + */ + public String getMethod() { + return method; + } + + /** + * Returns the HTTP request. + * + * @return The HTTP request + */ + public HTTPRequest getHttpRequest() { + return httpRequest; + } + + /** + * Returns the toadlet context. + * + * @return The toadlet context + */ + public ToadletContext getToadletContext() { + return toadletContext; + } + + } + + /** + * Container for the HTTP response of a {@link Page}. + * + * @author David ‘Bombe’ Roden + */ + public class Response { + + /** The HTTP status code of the response. */ + private final int statusCode; + + /** The HTTP status text of the response. */ + private final String statusText; + + /** The content type of the response. */ + private final String contentType; + + /** The headers of the response. */ + private final Map headers; + + /** The content of the response body. */ + private final InputStream content; + + /** + * Creates a new response. + * + * @param statusCode + * The HTTP status code of the response + * @param statusText + * The HTTP status text of the response + * @param contentType + * The content type of the response + * @param text + * The text in the response body + */ + public Response(int statusCode, String statusText, String contentType, String text) { + this(statusCode, statusText, contentType, getBytes(text)); + } + + /** + * Creates a new response. + * + * @param statusCode + * The HTTP status code of the response + * @param statusText + * The HTTP status text of the response + * @param contentType + * The content type of the response + * @param content + * The content of the reponse body + */ + public Response(int statusCode, String statusText, String contentType, byte[] content) { + this(statusCode, statusText, contentType, null, content); + } + + /** + * Creates a new response. + * + * @param statusCode + * The HTTP status code of the response + * @param statusText + * The HTTP status text of the response + * @param contentType + * The content type of the response + * @param headers + * The headers of the response + */ + public Response(int statusCode, String statusText, String contentType, Map headers) { + this(statusCode, statusText, contentType, headers, (InputStream) null); + } + + /** + * Creates a new response. + * + * @param statusCode + * The HTTP status code of the response + * @param statusText + * The HTTP status text of the response + * @param contentType + * The content type of the response + * @param headers + * The headers of the response + * @param content + * The content of the reponse body + */ + public Response(int statusCode, String statusText, String contentType, Map headers, byte[] content) { + this(statusCode, statusText, contentType, headers, new ByteArrayInputStream(content)); + } + + /** + * Creates a new response. + * + * @param statusCode + * The HTTP status code of the response + * @param statusText + * The HTTP status text of the response + * @param contentType + * The content type of the response + * @param headers + * The headers of the response + * @param content + * The content of the reponse body + */ + public Response(int statusCode, String statusText, String contentType, Map headers, InputStream content) { + this.statusCode = statusCode; + this.statusText = statusText; + this.contentType = contentType; + this.headers = headers; + this.content = content; + } + + /** + * Returns the HTTP status code of the response. + * + * @return The HTTP status code + */ + public int getStatusCode() { + return statusCode; + } + + /** + * Returns the HTTP status text. + * + * @return The HTTP status text + */ + public String getStatusText() { + return statusText; + } + + /** + * Returns the content type of the response. + * + * @return The content type of the reponse + */ + public String getContentType() { + return contentType; + } + + /** + * Returns HTTP headers of the response. May be {@code null} if no + * headers are returned. + * + * @return The response headers, or {@code null} if there are no + * response headers + */ + public Map getHeaders() { + return headers; + } + + /** + * Returns the content of the response body. May be {@code null} if the + * response does not have a body. + * + * @return The content of the response body + */ + public InputStream getContent() { + return content; + } + + // + // PRIVATE METHODS + // + + /** + * Returns the UTF-8 representation of the given text. + * + * @param text + * The text to encode + * @return The encoded text + */ + private static byte[] getBytes(String text) { + try { + return text.getBytes("UTF-8"); + } catch (UnsupportedEncodingException uee1) { + /* every JVM needs to support UTF-8. */ + } + return null; + } + + /** + * Creates a header map containing a single header. + * + * @param name + * The name of the header + * @param value + * The value of the header + * @return The map containing the single header + */ + protected static Map createHeader(String name, String value) { + Map headers = new HashMap(); + headers.put(name, value); + return headers; + } + + } + + /** + * {@link Response} implementation that performs an HTTP redirect. + * + * @author David ‘Bombe’ Roden + */ + public class RedirectResponse extends Response { + + /** + * Creates a new redirect response to the new location. + * + * @param newLocation + * The new location + */ + public RedirectResponse(String newLocation) { + this(newLocation, true); + } + + /** + * Creates a new redirect response to the new location. + * + * @param newLocation + * The new location + * @param permanent + * Whether the redirect should be marked as permanent + */ + public RedirectResponse(String newLocation, boolean permanent) { + super(permanent ? 302 : 307, "Redirected", null, createHeader("Location", newLocation)); + } + + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java new file mode 100644 index 0000000..af826f1 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadlet.java @@ -0,0 +1,178 @@ +/* + * shortener - PageToadlet.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +import java.io.IOException; +import java.net.URI; +import java.util.Map.Entry; + +import freenet.client.HighLevelSimpleClient; +import freenet.clients.http.LinkEnabledCallback; +import freenet.clients.http.Toadlet; +import freenet.clients.http.ToadletContext; +import freenet.clients.http.ToadletContextClosedException; +import freenet.support.MultiValueTable; +import freenet.support.api.Bucket; +import freenet.support.api.HTTPRequest; +import freenet.support.io.BucketTools; +import freenet.support.io.Closer; + +/** + * {@link Toadlet} implementation that is wrapped around a {@link Page}. + * + * @author David ‘Bombe’ Roden + */ +public class PageToadlet extends Toadlet implements LinkEnabledCallback { + + /** The name of the menu item. */ + private final String menuName; + + /** The page that handles processing. */ + private final Page page; + + /** The path prefix for the page. */ + private final String pathPrefix; + + /** + * Creates a new toadlet that hands off processing to a {@link Page}. + * + * @param highLevelSimpleClient + * @param menuName + * The name of the menu item + * @param page + * The page to handle processing + * @param pathPrefix + * Prefix that is prepended to all {@link Page#getPath()} return + * values + */ + protected PageToadlet(HighLevelSimpleClient highLevelSimpleClient, String menuName, Page page, String pathPrefix) { + super(highLevelSimpleClient); + this.menuName = menuName; + this.page = page; + this.pathPrefix = pathPrefix; + } + + /** + * Returns the name to display in the menu. + * + * @return The name in the menu + */ + public String getMenuName() { + return menuName; + } + + /** + * {@inheritDoc} + */ + @Override + public String path() { + return pathPrefix + page.getPath(); + } + + /** + * Handles a HTTP GET request. + * + * @param uri + * The URI of the request + * @param httpRequest + * The HTTP request + * @param toadletContext + * The toadlet context + * @throws IOException + * if an I/O error occurs + * @throws ToadletContextClosedException + * if the toadlet context is closed + */ + public void handleMethodGET(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException { + handleRequest(new Page.Request(uri, "GET", httpRequest, toadletContext)); + } + + /** + * Handles a HTTP POST request. + * + * @param uri + * The URI of the request + * @param httpRequest + * The HTTP request + * @param toadletContext + * The toadlet context + * @throws IOException + * if an I/O error occurs + * @throws ToadletContextClosedException + * if the toadlet context is closed + */ + public void handleMethodPOST(URI uri, HTTPRequest httpRequest, ToadletContext toadletContext) throws IOException, ToadletContextClosedException { + handleRequest(new Page.Request(uri, "POST", httpRequest, toadletContext)); + } + + /** + * {@inheritDoc} + */ + @Override + public String toString() { + return getClass().getName() + "[path=" + path() + ",page=" + page + "]"; + } + + /** + * Handles a HTTP request. + * + * @param pageRequest + * The request to handle + * @throws IOException + * if an I/O error occurs + * @throws ToadletContextClosedException + * if the toadlet context is closed + */ + private void handleRequest(Page.Request pageRequest) throws IOException, ToadletContextClosedException { + Bucket data = null; + try { + Page.Response pageResponse = page.handleRequest(pageRequest); + MultiValueTable headers = new MultiValueTable(); + if (pageResponse.getHeaders() != null) { + for (Entry headerEntry : pageResponse.getHeaders().entrySet()) { + headers.put(headerEntry.getKey(), headerEntry.getValue()); + } + } + data = pageRequest.getToadletContext().getBucketFactory().makeBucket(-1); + if (pageResponse.getContent() != null) { + try { + BucketTools.copyFrom(data, pageResponse.getContent(), -1); + } finally { + Closer.close(pageResponse.getContent()); + } + } else { + /* get an OutputStream and close it immediately. */ + Closer.close(data.getOutputStream()); + } + writeReply(pageRequest.getToadletContext(), pageResponse.getStatusCode(), pageResponse.getContentType(), pageResponse.getStatusText(), headers, data); + } catch (Throwable t1) { + writeInternalError(t1, pageRequest.getToadletContext()); + } finally { + Closer.close(data); + } + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEnabled(ToadletContext toadletContext) { + return true; + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java b/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java new file mode 100644 index 0000000..af87470 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/PageToadletFactory.java @@ -0,0 +1,75 @@ +/* + * shortener - PageToadletFactory.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +import freenet.client.HighLevelSimpleClient; + +/** + * Factory that creates {@link PageToadlet}s using a given + * {@link HighLevelSimpleClient}. + * + * @author David ‘Bombe’ Roden + */ +public class PageToadletFactory { + + /** The client to use when creating the toadlets. */ + private final HighLevelSimpleClient highLevelSimpleClient; + + /** The prefix for all pages’ paths. */ + private final String pathPrefix; + + /** + * Creates a new {@link PageToadlet} factory. + * + * @param highLevelSimpleClient + * The client to use when creating the toadlets + * @param pathPrefix + * The path that is prepended to all pages’ paths + */ + public PageToadletFactory(HighLevelSimpleClient highLevelSimpleClient, String pathPrefix) { + this.highLevelSimpleClient = highLevelSimpleClient; + this.pathPrefix = pathPrefix; + } + + /** + * Creates a {@link PageToadlet} that wraps the given page and does not + * appear in the node’s menu. + * + * @param page + * The page to wrap + * @return The toadlet wrapped around the page + */ + public PageToadlet createPageToadlet(Page page) { + return createPageToadlet(page, null); + } + + /** + * Creates a {@link PageToadlet} that wraps the given page and appears in + * the node’s menu under the given name. + * + * @param page + * The page to wrap + * @param menuName + * The name of the menu item + * @return The toadlet wrapped around the page + */ + public PageToadlet createPageToadlet(Page page, String menuName) { + return new PageToadlet(highLevelSimpleClient, menuName, page, pathPrefix); + } + +} diff --git a/src/main/java/net/pterodactylus/sone/web/page/TemplatePage.java b/src/main/java/net/pterodactylus/sone/web/page/TemplatePage.java new file mode 100644 index 0000000..7e478e7 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/web/page/TemplatePage.java @@ -0,0 +1,136 @@ +/* + * shortener - TemplatePage.java - Copyright © 2010 David Roden + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.pterodactylus.sone.web.page; + +import java.io.StringWriter; +import java.util.Collection; +import java.util.Collections; + +import net.pterodactylus.util.template.Template; +import freenet.clients.http.PageMaker; +import freenet.clients.http.PageNode; +import freenet.clients.http.ToadletContext; +import freenet.l10n.BaseL10n; + +/** + * Base class for all {@link Page}s that are rendered with {@link Template}s. + * + * @author David ‘Bombe’ Roden + */ +public class TemplatePage implements Page { + + /** The path of the page. */ + private final String path; + + /** The template to render. */ + private final Template template; + + /** The L10n handler. */ + private final BaseL10n l10n; + + /** The l10n key for the page title. */ + private final String pageTitleKey; + + /** + * Creates a new template page. + * + * @param path + * The path of the page + * @param template + * The template to render + * @param l10n + * The L10n handler + * @param pageTitleKey + * The l10n key of the title page + */ + public TemplatePage(String path, Template template, BaseL10n l10n, String pageTitleKey) { + this.path = path; + this.template = template; + this.l10n = l10n; + this.pageTitleKey = pageTitleKey; + } + + /** + * {@inheritDoc} + */ + @Override + public String getPath() { + return path; + } + + /** + * {@inheritDoc} + */ + @Override + public Response handleRequest(Request request) { + String redirectTarget = getRedirectTarget(request); + if (redirectTarget != null) { + return new RedirectResponse(redirectTarget); + } + + ToadletContext toadletContext = request.getToadletContext(); + PageMaker pageMaker = toadletContext.getPageMaker(); + PageNode pageNode = pageMaker.getPageNode(l10n.getString(pageTitleKey), toadletContext); + for (String styleSheet : getStyleSheets()) { + pageNode.addCustomStyleSheet(styleSheet); + } + + processTemplate(request, template); + StringWriter stringWriter = new StringWriter(); + template.render(stringWriter); + pageNode.content.addChild("%", stringWriter.toString()); + + return new Response(200, "OK", "text/html", pageNode.outer.generate()); + } + + /** + * Can be overridden to return a custom set of style sheets that are to be + * included in the page’s header. + * + * @return Additional style sheets to load + */ + protected Collection getStyleSheets() { + return Collections.emptySet(); + } + + /** + * Can be overridden when extending classes need to set variables in the + * template before it is rendered. + * + * @param request + * The request that is rendered + * @param template + * The template to set variables in + */ + protected void processTemplate(Request request, Template template) { + /* do nothing. */ + } + + /** + * Can be overridden to redirect the user to a different page, in case a log + * in is required, or something else is wrong. + * + * @param request + * The request that is processed + * @return The URL to redirect to, or {@code null} to not redirect + */ + protected String getRedirectTarget(Page.Request request) { + return null; + } + +}