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}