001package com.hfg.webapp; 002 003import javax.servlet.http.HttpServletRequest; 004import javax.servlet.http.HttpServletResponse; 005import java.lang.reflect.Constructor; 006import java.util.HashMap; 007import java.util.Map; 008 009import com.hfg.exception.ProgrammingException; 010import com.hfg.util.StringUtil; 011import com.hfg.util.collection.CollectionUtil; 012 013//------------------------------------------------------------------------------ 014/** 015 Dispatcher for dealing with servlet requests. 016 <div> 017 Example servlet code: 018 <pre> 019 020 private ServletRequestDispatcher mRequestDispatcher = new ServletRequestDispatcher(); 021 022 //--------------------------------------------------------------------------- 023 public void init() 024 { 025 mRequestDispatcher.registerDefaultHandler(MainPage.class); // Handler invoked if there are no params 026 mRequestDispatcher.registerHandler(PAGE, MAIN_PAGE, MainPage.class); 027 } 028 029 //--------------------------------------------------------------------------- 030 public void doGet(HttpServletRequest inRequest, HttpServletResponse inResponse) 031 { 032 handleRequest(inRequest, inResponse); 033 } 034 035 //--------------------------------------------------------------------------- 036 public void doPost(HttpServletRequest inRequest, HttpServletResponse inResponse) 037 { 038 handleRequest(inRequest, inResponse); 039 } 040 041 //--------------------------------------------------------------------------- 042 private void handleRequest(HttpServletRequest inRequest, HttpServletResponse inResponse) 043 { 044 try 045 { 046 mRequestDispatcher.dispatch(inRequest, inResponse); 047 } 048 catch (Throwable e) 049 { 050 e.printStackTrace(); 051 } 052 } 053 </pre> 054 </div> 055 <div> 056 @author J. Alex Taylor, hairyfatguy.com 057 </div> 058 */ 059//------------------------------------------------------------------------------ 060// com.hfg XML/HTML Coding Library 061// 062// This library is free software; you can redistribute it and/or 063// modify it under the terms of the GNU Lesser General Public 064// License as published by the Free Software Foundation; either 065// version 2.1 of the License, or (at your option) any later version. 066// 067// This library is distributed in the hope that it will be useful, 068// but WITHOUT ANY WARRANTY; without even the implied warranty of 069// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 070// Lesser General Public License for more details. 071// 072// You should have received a copy of the GNU Lesser General Public 073// License along with this library; if not, write to the Free Software 074// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 075// 076// J. Alex Taylor, President, Founder, CEO, COO, CFO, OOPS hairyfatguy.com 077// jataylor@hairyfatguy.com 078//------------------------------------------------------------------------------ 079 080public class ServletRequestDispatcher 081{ 082 private Class mDefaultHandlerClass; 083 private Map<String, Class> mPathHandlerMap; 084 private Map<String, Map<String, Class>> mParamHandlerMap = new HashMap<>(25); 085 private boolean mAllowPartialPathMatches = true; 086 087 //########################################################################### 088 // PUBLIC METHODS 089 //########################################################################### 090 091 //--------------------------------------------------------------------------- 092 public ServletRequestDispatcher registerDefaultHandler(Class inValue) 093 { 094 mDefaultHandlerClass = inValue; 095 return this; 096 } 097 098 //--------------------------------------------------------------------------- 099 public ServletRequestDispatcher registerPathHandler(String inPath, Class inHandlerClass) 100 { 101 if (! ServletRequestHandler.class.isAssignableFrom(inHandlerClass)) 102 { 103 throw new ProgrammingException("The specified handler class " + StringUtil.singleQuote(inHandlerClass.getSimpleName()) 104 + " does not implement " + ServletRequestHandler.class.getSimpleName() + "!"); 105 } 106 107 if (null == mPathHandlerMap) 108 { 109 mPathHandlerMap = new HashMap<>(10); 110 } 111 112 mPathHandlerMap.put(inPath, inHandlerClass); 113 114 return this; 115 } 116 117 //--------------------------------------------------------------------------- 118 public ServletRequestDispatcher registerParamHandler(String inParam, String inValue, Class inHandlerClass) 119 { 120 if (! ServletRequestHandler.class.isAssignableFrom(inHandlerClass)) 121 { 122 throw new ProgrammingException("The specified handler class " + StringUtil.singleQuote(inHandlerClass.getSimpleName()) 123 + " does not implement " + ServletRequestHandler.class.getSimpleName() + "!"); 124 } 125 126 Map<String, Class> paramMap = mParamHandlerMap.get(inParam); 127 if (null == paramMap) 128 { 129 paramMap = new HashMap<>(25); 130 mParamHandlerMap.put(inParam, paramMap); 131 } 132 133 paramMap.put(inValue, inHandlerClass); 134 135 return this; 136 } 137 138 //--------------------------------------------------------------------------- 139 /** 140 Specifies whether or not registered path values can be matched to the start of 141 requested paths as broken by the path separator '/'. 142 If true, '/foo/bar' would be routed to the registered path '/foo' 143 but '/food' would not. 144 * @param inValue whether or not registered path values can be matched to the start of 145 requested paths as broken by the path separator '/'. 146 * @return this servlet request dispatcher object to enable method chaining 147 */ 148 public ServletRequestDispatcher setAllowPartialPathMatches(boolean inValue) 149 { 150 mAllowPartialPathMatches = inValue; 151 return this; 152 } 153 154 //--------------------------------------------------------------------------- 155 public boolean getAllowPartialPathMatches() 156 { 157 return mAllowPartialPathMatches; 158 } 159 160 //--------------------------------------------------------------------------- 161 public void dispatch(HttpServletRequest inRequest, HttpServletResponse inResponse) 162 throws Exception 163 { 164 Class handlerClass; 165 166 String path = inRequest.getPathInfo(); 167 if (path != null 168 && path.endsWith("/")) // Remove trailing slashes 169 { 170 path = path.substring(0, path.length() - 1); 171 } 172 173 // Does the request map to a path handler? 174 handlerClass = getPathHandler(path); 175 if (null == handlerClass 176 && CollectionUtil.hasValues(inRequest.getParameterMap())) 177 { 178 // Does the request map to a param handler? 179 for (String param : mParamHandlerMap.keySet()) 180 { 181 String[] values = inRequest.getParameterValues(param); 182 if (values != null) 183 { 184 Map<String, Class> paramMap = mParamHandlerMap.get(param); 185 if (1 == paramMap.size() 186 && null == paramMap.keySet().iterator().next()) 187 { 188 // null indicates all requests w/ the specified param use the same handler 189 handlerClass = paramMap.get(null); 190 } 191 else 192 { 193 for (String value : values) 194 { 195 handlerClass = paramMap.get(value); 196 if (handlerClass != null) 197 { 198 break; 199 } 200 } 201 } 202 } 203 204 if (handlerClass != null) 205 { 206 break; 207 } 208 } 209 } 210 211 if (null == handlerClass) 212 { 213 handlerClass = mDefaultHandlerClass; 214 } 215 216 if (handlerClass != null) 217 { 218 ServletRequestHandler handler = instantiateHandler(handlerClass); 219 220 handler.handleRequest(inRequest, inResponse); 221 } 222 } 223 224 //########################################################################### 225 // PRIVATE METHODS 226 //########################################################################### 227 228 //--------------------------------------------------------------------------- 229 private Class getPathHandler(String inPath) 230 { 231 Class handlerClass = null; 232 233 if (mPathHandlerMap != null) 234 { 235 handlerClass = mPathHandlerMap.get(inPath); 236 237 if (null == handlerClass 238 && mAllowPartialPathMatches 239 && inPath != null) 240 { 241 // Does a registered path match the beginning of the requested path? 242 int bestPathLength = 0; 243 for (String key : mPathHandlerMap.keySet()) 244 { 245 if (inPath.startsWith(key + "/")) 246 { 247 if (key.length() > bestPathLength) 248 { 249 bestPathLength = key.length(); 250 handlerClass = mPathHandlerMap.get(key); 251 } 252 } 253 } 254 } 255 } 256 257 return handlerClass; 258 } 259 260 //--------------------------------------------------------------------------- 261 private ServletRequestHandler instantiateHandler(Class inHandlerClass) 262 { 263 ServletRequestHandler handler; 264 try 265 { 266 Constructor constructor = inHandlerClass.getConstructor(); 267 268 handler = (ServletRequestHandler) constructor.newInstance(); 269 } 270 catch (NoSuchMethodException e) 271 { 272 throw new ProgrammingException("Handler class " + StringUtil.singleQuote(inHandlerClass.getSimpleName()) + " doesn't have a default Constructor!", e); 273 } 274 catch (Exception e) 275 { 276 throw new ProgrammingException("Problem initializing handler class " + StringUtil.singleQuote(inHandlerClass.getSimpleName()) + "!", e); 277 } 278 279 return handler; 280 } 281}