Browse Source

JFinal 1.1.4 Release

JamesZhan 13 years ago
parent
commit
e3838d7635

+ 1 - 1
src/com/jfinal/config/Constants.java

@@ -284,7 +284,7 @@ final public class Constants {
 			throw new IllegalArgumentException("mainRenderFactory can not be null.");
 		
 		this.viewType = ViewType.OTHER;
-		RenderFactory.setmainRenderFactory(mainRenderFactory);
+		RenderFactory.setMainRenderFactory(mainRenderFactory);
 	}
 }
 

+ 3 - 3
src/com/jfinal/config/Interceptors.java

@@ -27,9 +27,9 @@ final public class Interceptors {
 	
 	private final List<Interceptor> interceptorList = new ArrayList<Interceptor>();
 	
-	public Interceptors add(Interceptor defaultInterceptor) {
-		if (defaultInterceptor != null)
-			this.interceptorList.add(defaultInterceptor);
+	public Interceptors add(Interceptor globalInterceptor) {
+		if (globalInterceptor != null)
+			this.interceptorList.add(globalInterceptor);
 		return this;
 	}
 	

+ 10 - 3
src/com/jfinal/core/Config.java

@@ -23,6 +23,7 @@ import com.jfinal.config.Routes;
 import com.jfinal.config.Plugins;
 import com.jfinal.config.Handlers;
 import com.jfinal.config.Interceptors;
+import com.jfinal.log.Logger;
 import com.jfinal.plugin.IPlugin;
 
 class Config {
@@ -32,6 +33,7 @@ class Config {
 	private static final Plugins plugins = new Plugins();
 	private static final Interceptors interceptors = new Interceptors();
 	private static final Handlers handlers = new Handlers();
+	private static Logger log;
 	
 	// prevent new Config();
 	private Config() {
@@ -41,7 +43,7 @@ class Config {
 	 * Config order: constant, route, plugin, interceptor, handler
 	 */
 	static void configJFinal(JFinalConfig jfinalConfig) {
-		jfinalConfig.configConstant(constants);
+		jfinalConfig.configConstant(constants);				initLoggerFactory();
 		jfinalConfig.configRoute(routes);
 		jfinalConfig.configPlugin(plugins);					startPlugins();	// very important!!!
 		jfinalConfig.configInterceptor(interceptors);
@@ -75,15 +77,20 @@ class Config {
 				try {
 					boolean success = plugin.start();
 					if (!success) {
-						System.err.println("Plugin start error: " + plugin.getClass().getName());
+						log.error("Plugin start error: " + plugin.getClass().getName());
 						throw new RuntimeException("Plugin start error: " + plugin.getClass().getName());
 					}
 				}
 				catch (Exception e) {
-					System.err.println("Plugin start error: " + plugin.getClass().getName());
+					log.error("Plugin start error: " + plugin.getClass().getName(), e);
 					throw new RuntimeException("Plugin start error: " + plugin.getClass().getName(), e);
 				}
 			}
 		}
 	}
+	
+	private static void initLoggerFactory() {
+		log = Logger.getLogger(Config.class);
+		JFinalFilter.initLogger();
+	}
 }

+ 2 - 2
src/com/jfinal/core/Const.java

@@ -24,13 +24,13 @@ import com.jfinal.render.ViewType;
  */
 public interface Const {
 	
-	String JFINAL_VERSION = "1.1.3";
+	String JFINAL_VERSION = "1.1.4";
 	
 	ViewType DEFAULT_VIEW_TYPE = ViewType.FREE_MARKER;
 	
 	String DEFAULT_ENCODING = "utf-8";
 	
-	String DEFAULT_URL_PARA_SEPARATOR = "-";				// before 1.1.2 is "_";
+	String DEFAULT_URL_PARA_SEPARATOR = "-";
 	
 	String DEFAULT_FILE_CONTENT_TYPE = "application/octet-stream";
 	

+ 2 - 2
src/com/jfinal/core/Controller.java

@@ -110,14 +110,14 @@ public abstract class Controller {
 	}
 	
 	/**
-	 * Returns the value of a request parameter as a String, or null if the parameter does not exist.
+	 * Returns the value of a request parameter as a String, or default value if the parameter does not exist.
 	 * @param name a String specifying the name of the parameter
 	 * @param defaultValue a String value be returned when the value of parameter is null
 	 * @return a String representing the single value of the parameter
 	 */
 	public String getPara(String name, String defaultValue) {
 		String result = request.getParameter(name);
-		return result != null ? result : defaultValue;
+		return result != null && !"".equals(result) ? result : defaultValue;
 	}
 	
 	/**

+ 1 - 6
src/com/jfinal/core/JFinal.java

@@ -62,13 +62,12 @@ public final class JFinal {
 		
 		initPathUtil();
 		
-		Config.configJFinal(jfinalConfig);
+		Config.configJFinal(jfinalConfig);	// start plugin and init logger factory in this method
 		constants = Config.getConstants();
 		
 		initActionMapping();
 		initHandler();
 		initRender();
-		initLoggerFactory();
 		initActiveRecord();
 		initOreillyCos();
 		initI18n();
@@ -125,10 +124,6 @@ public final class JFinal {
 		renderFactory.init(constants, servletContext);
 	}
 	
-	private void initLoggerFactory() {
-		JFinalFilter.initLogger();
-	}
-	
 	private void initActionMapping() {
 		actionMapping = new ActionMapping(Config.getRoutes(), Config.getInterceptors());
 		actionMapping.buildActionMapping();

+ 1 - 1
src/com/jfinal/ext/handler/ContextPathHandler.java

@@ -25,7 +25,7 @@ import com.jfinal.util.StringKit;
  * Provide a context path to view if you need.
  * <br>
  * Example:<br>
- * In JFinalFilter: handlers.add(new ContextPathHandler("BASE_PATH"));<br>
+ * In JFinalFilter: handlers.add(new ContextPathHandler("CONTEXT_PATH"));<br>
  * in freemarker: <img src="${BASE_PATH}/images/logo.png" />
  */
 public class ContextPathHandler extends Handler {

+ 46 - 41
src/com/jfinal/ext/interceptor/SessionInViewInterceptor.java

@@ -17,6 +17,8 @@
 package com.jfinal.ext.interceptor;
 
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
 import javax.servlet.ServletContext;
 import javax.servlet.http.HttpSession;
 import com.jfinal.aop.Interceptor;
@@ -28,26 +30,34 @@ import com.jfinal.core.Controller;
  */
 public class SessionInViewInterceptor implements Interceptor {
 	
+	private boolean createSession = false;
+	
+	public SessionInViewInterceptor() {
+	}
+	
+	public SessionInViewInterceptor(boolean createSession) {
+		this.createSession = createSession;
+	}
+	
+	@SuppressWarnings({"rawtypes", "unchecked"})	
 	public void intercept(ActionInvocation ai) {
 		ai.invoke();
 		
 		Controller c = ai.getController();
-		HttpSession hs = c.getSession(false);
+		HttpSession hs = c.getSession(createSession);
 		if (hs != null) {
-			c.setAttr("session", new JFinalSession(hs));
+			Map session = new JFinalSession(hs);
+			for (Enumeration<String> names=hs.getAttributeNames(); names.hasMoreElements();) {
+				String name = names.nextElement();
+				session.put(name, hs.getAttribute(name));
+			}
+			c.setAttr("session", session);
 		}
 	}
 }
 
-@SuppressWarnings({"deprecation", "rawtypes"})
-class JFinalSession implements HttpSession {
-	
-	/**
-	 * Added by JFinal for FreeMarker and Beetl.
-	 */
-	public Object get(String key) {
-		return s.getAttribute(key);
-	}
+@SuppressWarnings({"deprecation", "serial", "rawtypes"})
+class JFinalSession extends HashMap implements HttpSession {
 	
 	private HttpSession s;
 	
@@ -55,8 +65,8 @@ class JFinalSession implements HttpSession {
 		this.s = session;
 	}
 	
-	public Object getAttribute(String arg0) {
-		return s.getAttribute(arg0);
+	public Object getAttribute(String key) {
+		return s.getAttribute(key);
 	}
 	
 	public Enumeration getAttributeNames() {
@@ -87,8 +97,8 @@ class JFinalSession implements HttpSession {
 		return s.getSessionContext();
 	}
 	
-	public Object getValue(String arg0) {
-		return s.getValue(arg0);
+	public Object getValue(String key) {
+		return s.getValue(key);
 	}
 	
 	public String[] getValueNames() {
@@ -103,40 +113,35 @@ class JFinalSession implements HttpSession {
 		return s.isNew();
 	}
 	
-	public void putValue(String arg0, Object arg1) {
-		s.putValue(arg0, arg1);
+	public void putValue(String key, Object value) {
+		s.putValue(key, value);
 	}
 	
-	public void removeAttribute(String arg0) {
-		s.removeAttribute(arg0);
+	public void removeAttribute(String key) {
+		s.removeAttribute(key);
 	}
 	
-	public void removeValue(String arg0) {
-		s.removeValue(arg0);
+	public void removeValue(String key) {
+		s.removeValue(key);
 	}
 	
-	public void setAttribute(String arg0, Object arg1) {
-		s.setAttribute(arg0, arg1);
+	public void setAttribute(String key, Object value) {
+		s.setAttribute(key, value);
 	}
 	
-	public void setMaxInactiveInterval(int arg0) {
-		s.setMaxInactiveInterval(arg0);
+	public void setMaxInactiveInterval(int maxInactiveInterval) {
+		s.setMaxInactiveInterval(maxInactiveInterval);
 	}
 }
 
-//@SuppressWarnings({"rawtypes", "unchecked"})	
-//public void intercept(ActionInvocation ai) {
-//	ai.invoke();
-//	
-//	Controller c = ai.getController();
-//	HttpSession hs = c.getSession(false);
-//	if (hs != null) {
-//		Map session = new HashMap();
-//		for (Enumeration<String> names=hs.getAttributeNames(); names.hasMoreElements();) {
-//			String name = names.nextElement();
-//			session.put(name, hs.getAttribute(name));
-//		}
-//		c.setAttr("session", session);
-//	}
-//}
-
+/*
+public void intercept(ActionInvocation ai) {
+	ai.invoke();
+	
+	Controller c = ai.getController();
+	HttpSession hs = c.getSession(createSession);
+	if (hs != null) {
+		c.setAttr("session", new JFinalSession(hs));
+	}
+}
+*/

+ 1 - 1
src/com/jfinal/util/SessionIdGenerator.java

@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.jfinal.util;
+package com.jfinal.ext.util;
 
 import java.security.SecureRandom;
 import java.util.Random;

+ 3 - 3
src/com/jfinal/i18n/I18N.java

@@ -52,11 +52,11 @@ public class I18N {
 	}
 	
 	public static I18N me() {
-		if (me == null) {
+		if (me == null)
 			synchronized (I18N.class) {
-				me = new I18N();
+				if (me == null)
+					me = new I18N();
 			}
-		}
 		return me;
 	}
 	

+ 13 - 4
src/com/jfinal/plugin/activerecord/ActiveRecordPlugin.java

@@ -31,6 +31,7 @@ import com.jfinal.plugin.activerecord.dialect.Dialect;
  */
 public class ActiveRecordPlugin implements IPlugin {
 	
+	private static boolean isStarted = false;
 	private static DataSource dataSource;
 	private static IDataSourceProvider dataSourceProvider;
 	private static final List<TableInfo> tableMappings = new ArrayList<TableInfo>();
@@ -56,6 +57,11 @@ public class ActiveRecordPlugin implements IPlugin {
 		return this;
 	}
 	
+	public ActiveRecordPlugin setMapFactory(IMapFactory mapFactory) {
+		DbKit.setMapFactory(mapFactory);
+		return this;
+	}
+	
 	public ActiveRecordPlugin(IDataSourceProvider dataSourceProvider) {
 		ActiveRecordPlugin.dataSourceProvider = dataSourceProvider;
 	}
@@ -85,20 +91,23 @@ public class ActiveRecordPlugin implements IPlugin {
 	}
 	
 	public boolean start() {
-		if (dataSourceProvider != null) {
+		if (isStarted)
+			return true;
+		
+		if (dataSourceProvider != null)
 			dataSource = dataSourceProvider.getDataSource();
-		}
 		
-		if (dataSource == null){
+		if (dataSource == null)
 			throw new RuntimeException("ActiveRecord start error: ActiveRecordPlugin need DataSource or DataSourceProvider");
-		}
 		
 		DbKit.setDataSource(dataSource);
 		
+		isStarted = true;
 		return TableInfoBuilder.buildTableInfo(tableMappings);
 	}
 	
 	public boolean stop() {
+		isStarted = false;
 		return true;
 	}
 }

+ 53 - 0
src/com/jfinal/plugin/activerecord/CaseInsensitiveMapFactory.java

@@ -0,0 +1,53 @@
+package com.jfinal.plugin.activerecord;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+@SuppressWarnings("unchecked")
+public class CaseInsensitiveMapFactory implements IMapFactory {
+	
+	public Map<String, Object> getAttrsMap() {
+		return new CaseInsensitiveMap();
+	}
+	
+	public Map<String, Object> getColumnsMap() {
+		return new CaseInsensitiveMap();
+	}
+}
+
+@SuppressWarnings({"rawtypes", "unchecked"})
+class CaseInsensitiveMap extends HashMap {
+	
+	private static final long serialVersionUID = -3415001825854442053L;
+	
+	@Override
+	public Object get(Object key) {
+		Object k = (key instanceof String ? ((String)key).toUpperCase() : key);
+		return super.get(k);
+	}
+	
+	@Override
+	public boolean containsKey(Object key) {
+		Object k = (key instanceof String ? ((String)key).toUpperCase() : key);
+		return super.containsKey(k);
+	}
+	
+	@Override
+	public Object put(Object key, Object value) {
+		Object k = (key instanceof String ? ((String)key).toUpperCase() : key);
+		return super.put(k, value);
+	}
+	
+	@Override
+	public void putAll(Map m) {
+		for (Map.Entry e : (Set<Map.Entry>)(m.entrySet()))
+            put(e.getKey(), e.getValue());
+	}
+	
+	@Override
+	public Object remove(Object key) {
+		Object k = (key instanceof String ? ((String)key).toUpperCase() : key);
+		return super.remove(k);
+	}
+}

+ 18 - 6
src/com/jfinal/plugin/activerecord/DbKit.java

@@ -20,6 +20,8 @@ import java.sql.Connection;
 import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.HashMap;
+import java.util.Map;
 import javax.sql.DataSource;
 import com.jfinal.plugin.activerecord.cache.EhCache;
 import com.jfinal.plugin.activerecord.cache.ICache;
@@ -42,6 +44,16 @@ public final class DbKit {
 	static boolean devMode = false;
 	static Dialect dialect = new MysqlDialect();
 	
+	static IMapFactory mapFactory = new IMapFactory(){
+		public Map<String, Object> getAttrsMap() {return new HashMap<String, Object>();}
+		public Map<String, Object> getColumnsMap() {return new HashMap<String, Object>();}
+	};
+	
+	static void setMapFactory(IMapFactory mapFactory) {
+		if (mapFactory != null)
+			DbKit.mapFactory = mapFactory;
+	}
+	
 	static void setDevMode(boolean devMode) {
 		DbKit.devMode = devMode;
 	}
@@ -50,11 +62,15 @@ public final class DbKit {
 		DbKit.showSql = showSql;
 	}
 	
-	public static void setDialect(Dialect dialect) {
+	static void setDialect(Dialect dialect) {
 		if (dialect != null)
 			DbKit.dialect = dialect;
 	}
 	
+	static void setCache(ICache cache) {
+		DbKit.cache = cache;
+	}
+	
 	public static Dialect getDialect() {
 		return dialect;
 	}
@@ -63,10 +79,6 @@ public final class DbKit {
 		return cache;
 	}
 	
-	public static void setCache(ICache cache) {
-		DbKit.cache = cache;
-	}
-	
 	// Prevent new DbKit()
 	private DbKit() {
 	}
@@ -180,7 +192,7 @@ public final class DbKit {
 		if (index > sql.toLowerCase().lastIndexOf(")")) {
 			String sql1 = sql.substring(0, index);
 			String sql2 = sql.substring(index);
-			sql2 = sql2.replaceAll("[oO][rR][dD][eE][rR] [bB][yY] [a-zA-Z0-9_.]+((\\s)+(([dD][eE][sS][cC])|([aA][sS][cC])))?(( )*,( )*[a-zA-Z0-9_.]+(( )+(([dD][eE][sS][cC])|([aA][sS][cC])))?)*", "");
+			sql2 = sql2.replaceAll("[oO][rR][dD][eE][rR] [bB][yY] [\u4e00-\u9fa5a-zA-Z0-9_.]+((\\s)+(([dD][eE][sS][cC])|([aA][sS][cC])))?(( )*,( )*[\u4e00-\u9fa5a-zA-Z0-9_.]+(( )+(([dD][eE][sS][cC])|([aA][sS][cC])))?)*", "");
 			return sql1 + sql2;
 		}
 		return sql;

+ 8 - 0
src/com/jfinal/plugin/activerecord/IMapFactory.java

@@ -0,0 +1,8 @@
+package com.jfinal.plugin.activerecord;
+
+import java.util.Map;
+
+public interface IMapFactory {
+	Map<String, Object> getAttrsMap();
+	Map<String, Object> getColumnsMap();
+}

+ 8 - 1
src/com/jfinal/plugin/activerecord/Model.java

@@ -44,7 +44,7 @@ public abstract class Model<M extends Model> implements Serializable {
 	/**
 	 * Attributes of this model
 	 */
-	private Map<String, Object> attrs = new HashMap<String, Object>();
+	private Map<String, Object> attrs = DbKit.mapFactory.getAttrsMap();	// new HashMap<String, Object>();
 	
 	/**
 	 * Flag of column has been modified. update need this flag
@@ -691,5 +691,12 @@ public abstract class Model<M extends Model> implements Serializable {
 		java.util.Collection<Object> attrValueCollection = attrs.values();
 		return attrValueCollection.toArray(new Object[attrValueCollection.size()]);
 	}
+	
+	/**
+	 * Return json string of this model.
+	 */
+	public String toJson() {
+		return com.jfinal.util.JsonBuilder.toJson(attrs, 4);
+	}
 }
 

+ 25 - 1
src/com/jfinal/plugin/activerecord/Record.java

@@ -19,6 +19,7 @@ package com.jfinal.plugin.activerecord;
 import java.io.Serializable;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.Map.Entry;
 
 /**
@@ -27,7 +28,7 @@ import java.util.Map.Entry;
 public class Record implements Serializable {
 	
 	private static final long serialVersionUID = -3254070837297655225L;
-	private Map<String, Object> columns = new HashMap<String, Object>();
+	private Map<String, Object> columns = DbKit.mapFactory.getColumnsMap();	// new HashMap<String, Object>();
 	
 	/**
 	 * Return columns map.
@@ -269,6 +270,29 @@ public class Record implements Serializable {
 	public int hashCode() {
 		return columns == null ? 0 : columns.hashCode();
 	}
+	
+	/**
+	 * Return column names of this record.
+	 */
+	public String[] getcolumnNames() {
+		Set<String> attrNameSet = columns.keySet();
+		return attrNameSet.toArray(new String[attrNameSet.size()]);
+	}
+	
+	/**
+	 * Return column values of this record.
+	 */
+	public Object[] getcolumnValues() {
+		java.util.Collection<Object> attrValueCollection = columns.values();
+		return attrValueCollection.toArray(new Object[attrValueCollection.size()]);
+	}
+	
+	/**
+	 * Return json string of this record.
+	 */
+	public String toJson() {
+		return com.jfinal.util.JsonBuilder.toJson(columns, 4);
+	}
 }
 
 

+ 7 - 0
src/com/jfinal/plugin/druid/DruidStatViewHandler.java

@@ -14,6 +14,7 @@ import com.alibaba.druid.support.logging.Log;
 import com.alibaba.druid.support.logging.LogFactory;
 import com.alibaba.druid.util.IOUtils;
 import com.jfinal.handler.Handler;
+import com.jfinal.util.HandlerKit;
 
 /**
  * 替代 StatViewServlet
@@ -41,6 +42,12 @@ public class DruidStatViewHandler extends Handler {
 	public void handle(String target, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
 		if (target.startsWith(visitPath)) {
 			isHandled[0] = true;
+			
+			if (target.equals(visitPath) && !target.endsWith("/index.html")) {
+				HandlerKit.redirect(target += "/index.html", request, response, isHandled);
+				return ;
+			}
+			
 			try {
 				servlet.service(request, response);
 			} catch (Exception e) {

+ 1 - 0
src/com/jfinal/render/FileRender.java

@@ -69,6 +69,7 @@ class FileRender extends Render {
 			return ;
         }
 		
+		response.addHeader("Content-disposition", "attachment; filename=" + file.getName());
         String contentType = servletContext.getMimeType(file.getName());
         if (contentType == null) {
         	contentType = DEFAULT_FILE_CONTENT_TYPE;		// "application/octet-stream";

+ 1 - 1
src/com/jfinal/render/RenderFactory.java

@@ -47,7 +47,7 @@ public class RenderFactory {
 		return me;
 	}
 	
-	public static void setmainRenderFactory(IMainRenderFactory mainRenderFactory) {
+	public static void setMainRenderFactory(IMainRenderFactory mainRenderFactory) {
 		if (mainRenderFactory != null)
 			RenderFactory.mainRenderFactory = mainRenderFactory;
 	}

+ 1 - 1
src/com/jfinal/upload/MultipartRequest.java

@@ -123,7 +123,7 @@ public class MultipartRequest extends HttpServletRequestWrapper {
 	}
 	
 	private boolean isSafeFile(UploadFile uploadFile) {
-		if (uploadFile.getFilesystemName().toLowerCase().endsWith(".jsp")) {
+		if (uploadFile.getFileName().toLowerCase().endsWith(".jsp")) {
 			uploadFile.getFile().delete();
 			return false;
 		}

+ 6 - 6
src/com/jfinal/upload/UploadFile.java

@@ -26,14 +26,14 @@ public class UploadFile {
 	private String parameterName;
 	
 	private String saveDirectory;
-	private String filesystemName;
+	private String fileName;
 	private String originalFileName;
 	private String contentType;
 	
 	public UploadFile(String parameterName, String saveDirectory, String filesystemName, String originalFileName, String contentType) {
 		this.parameterName = parameterName;
 		this.saveDirectory = saveDirectory;
-		this.filesystemName = filesystemName;
+		this.fileName = filesystemName;
 		this.originalFileName = originalFileName;
 		this.contentType = contentType;
 	}
@@ -42,8 +42,8 @@ public class UploadFile {
 		return parameterName;
 	}
 	
-	public String getFilesystemName() {
-		return filesystemName;
+	public String getFileName() {
+		return fileName;
 	}
 	
 	public String getOriginalFileName() {
@@ -59,10 +59,10 @@ public class UploadFile {
 	}
 	
 	public File getFile() {
-		if (saveDirectory == null || filesystemName == null) {
+		if (saveDirectory == null || fileName == null) {
 			return null;
 		} else {
-			return new File(saveDirectory + File.separator + filesystemName);
+			return new File(saveDirectory + File.separator + fileName);
 		}
 	}
 }

+ 64 - 0
src/com/jfinal/util/HandlerKit.java

@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2011-2012, James Zhan 詹波 (jfinal@126.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.jfinal.util;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import com.jfinal.render.RenderException;
+import com.jfinal.render.RenderFactory;
+
+/**
+ * HandlerKit.
+ */
+public class HandlerKit {
+	
+	private HandlerKit(){}
+	
+	public static void renderError404(String view, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
+		isHandled[0] = true;
+		
+		response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+		RenderFactory.me().getRender(view).setContext(request, response).render();
+	}
+	
+	public static void redirect301(String url, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
+		isHandled[0] = true;
+		
+		String queryString = request.getQueryString();
+		if (queryString != null)
+			url += "?" + queryString;
+		
+		response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
+		response.setHeader("Location", url);
+		response.setHeader("Connection", "close");
+	}
+	
+	public static void redirect(String url, HttpServletRequest request, HttpServletResponse response, boolean[] isHandled) {
+		isHandled[0] = true;
+		
+		String queryString = request.getQueryString();
+		if (queryString != null)
+			url = url + "?" + queryString;
+		
+		try {
+			response.sendRedirect(url);	// always 302
+		} catch (IOException e) {
+			throw new RenderException(e);
+		}
+	}
+}