View Javadoc

1   /*
2    * $Id: AbstractURLActionHandler.java 216 2010-12-30 12:39:20Z roland $
3    * Copyright (C) 2007 - 2010 Roland Krueger
4    * Created on 11.02.2010
5    *
6    * Author: Roland Krueger (www.rolandkrueger.info)
7    *
8    * This file is part of RoKlib.
9    *
10   * This library is free software; you can redistribute it and/or
11   * modify it under the terms of the GNU Lesser General Public License
12   * as published by the Free Software Foundation; either version 2.1 of
13   * the License, or (at your option) any later version.
14   *
15   * This library is distributed in the hope that it will be useful, but
16   * WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18   * Lesser General Public License for more details.
19   *
20   * You should have received a copy of the GNU Lesser General Public
21   * License along with this library; if not, write to the Free Software
22   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23   * USA
24   */
25  package info.rolandkrueger.roklib.webapps.urldispatching;
26  
27  import info.rolandkrueger.roklib.util.LoggingManager;
28  import info.rolandkrueger.roklib.util.conditionalengine.AbstractCondition;
29  import info.rolandkrueger.roklib.util.helper.CheckForNull;
30  import info.rolandkrueger.roklib.util.net.IURLProvider;
31  import info.rolandkrueger.roklib.webapps.urldispatching.urlparameters.EnumURLParameterErrors;
32  import info.rolandkrueger.roklib.webapps.urldispatching.urlparameters.IURLParameter;
33  
34  import java.io.Serializable;
35  import java.io.UnsupportedEncodingException;
36  import java.net.MalformedURLException;
37  import java.net.URL;
38  import java.net.URLDecoder;
39  import java.net.URLEncoder;
40  import java.util.HashMap;
41  import java.util.Iterator;
42  import java.util.LinkedList;
43  import java.util.List;
44  import java.util.Map;
45  import java.util.TreeMap;
46  
47  import org.apache.log4j.Logger;
48  
49  public abstract class AbstractURLActionHandler implements IURLActionHandler
50  {
51    private static final long serialVersionUID = 8450975393827044559L;
52    private static final String[] STRING_ARRAY_PROTOTYPE = new String[] {};
53    private static final Logger LOG = LoggingManager.getInstance ().getLogger (AbstractURLActionHandler.class);
54    
55    private List<CommandForCondition> mCommandsForCondition;
56    private List<IURLParameter<?>> mURLParameters;
57    private List<String> mActionArgumentOrder;
58    protected List<IURLActionHandler> mHandlerChain;
59    private Map<String, AbstractURLActionHandler> mSubHandlers;
60    private Map<String, List<Serializable>> mActionArgumentMap;
61    private AbstractURLActionHandler mParentHandler;
62    private AbstractURLActionCommand mDefaultCommand;
63    protected String mActionName;
64    private String mActionURI;
65    private boolean mCaseSensitive = false;
66    
67    public AbstractURLActionHandler (String actionName)
68    {
69      CheckForNull.check (actionName);
70      mActionName = actionName;
71      mActionURI  = actionName;
72      mDefaultCommand = null;
73    }
74    
75    protected void setCaseSensitive (boolean caseSensitive)
76    {
77      mCaseSensitive = caseSensitive;
78      if (mCaseSensitive & mSubHandlers != null)
79      {
80        Map<String, AbstractURLActionHandler> subHandlers = mSubHandlers;
81        mSubHandlers = new HashMap<String, AbstractURLActionHandler> (4);
82        mSubHandlers.putAll (subHandlers);
83      }
84    }
85    
86    private Map<String, AbstractURLActionHandler> getSubHandlerMap ()
87    {
88      if (mSubHandlers == null)
89      {
90        if (! mCaseSensitive)
91        {
92          mSubHandlers = new TreeMap<String, AbstractURLActionHandler> (String.CASE_INSENSITIVE_ORDER);
93        }
94        else
95        {
96          mSubHandlers = new HashMap<String, AbstractURLActionHandler> (4);
97        }
98      }
99      return mSubHandlers;
100   }
101   
102   public boolean isCaseSensitive ()
103   {
104     return mCaseSensitive;
105   }
106 
107   public String getActionName ()
108   {
109     return mActionName;
110   }
111   
112   public void setDefaultActionCommand (AbstractURLActionCommand command)
113   {
114     mDefaultCommand = command;
115   }
116   
117   protected void registerURLParameter (IURLParameter<?> parameter)
118   {
119     if (parameter == null) return;
120     if (mURLParameters == null)
121       mURLParameters = new LinkedList<IURLParameter<?>> ();
122     if ( ! mURLParameters.contains (parameter))
123       mURLParameters.add (parameter);
124   }
125   
126   protected void registerURLParameter (IURLParameter<?> parameter, boolean isOptional)
127   {
128     registerURLParameter (parameter);
129     parameter.setOptional (isOptional);
130   }
131   
132   protected boolean haveRegisteredURLParametersErrors ()
133   {
134     if (mURLParameters == null) return false;
135     boolean result = false;
136     
137     for (IURLParameter<?> parameter : mURLParameters)
138     {
139       result |= parameter.getError () != EnumURLParameterErrors.NO_ERROR;
140     }
141     
142     return result;
143   }
144   
145   public final AbstractURLActionCommand handleURL (List<String> pUriTokens,
146       Map<String, List<String>> pParameters, ParameterMode pParameterMode)
147   {
148     if (mCommandsForCondition != null)
149     {
150       for (CommandForCondition cfc : mCommandsForCondition)
151       {
152         if (cfc.mCondition.getBooleanValue () == true)
153           return cfc.mDefaultCommandForCondition;
154       }
155     }
156     if (mURLParameters != null)
157     {
158       if (pParameterMode == ParameterMode.QUERY)
159       {
160         for (IURLParameter<?> parameter : mURLParameters)
161         {
162           parameter.clearValue ();
163           parameter.consume (pParameters);
164         }
165       } else
166       {
167         List<String> parameterNames = new LinkedList <String> ();
168         for (IURLParameter<?> parameter : mURLParameters)
169         {
170           parameterNames.addAll (parameter.getParameterNames ());
171         }
172         if (pParameterMode == ParameterMode.DIRECTORY_WITH_NAMES)
173         {
174           Map<String, List<String>> parameters = new HashMap<String, List<String>> (4);
175           String parameterName;
176           String value;
177           for (Iterator<String> it = pUriTokens.iterator (); it.hasNext ();)
178           {
179             parameterName = it.next ();
180             try
181             {
182               parameterName = URLDecoder.decode (parameterName, "UTF-8");
183             } catch (UnsupportedEncodingException e)
184             {
185               // nothing to do, parameterName stays encoded
186             }
187             value = "";
188             if (parameterNames.contains (parameterName))
189             {
190               it.remove ();
191               if (it.hasNext ())
192               {
193                 value = it.next ();
194                 try
195                 {
196                   value = URLDecoder.decode (value, "UTF-8");
197                 } catch (UnsupportedEncodingException e)
198                 {
199                   // nothing to do, value stays encoded
200                 }
201                 it.remove ();
202               }
203               List<String> values = parameters.get (parameterName);
204               if (values == null)
205               {
206                 values = new LinkedList<String> ();
207                 parameters.put (parameterName, values);
208               }
209               values.add (value);
210             }
211           }
212           for (IURLParameter<?> parameter : mURLParameters)
213           {
214             parameter.clearValue ();
215             parameter.consume (parameters);
216           }
217         } else
218         {
219           List<String> valueList = new LinkedList<String> ();
220           for (IURLParameter<?> parameter : mURLParameters)
221           {
222             parameter.clearValue ();
223             if (pUriTokens.isEmpty ()) continue;
224             valueList.clear ();
225             int singleValueCount = parameter.getSingleValueCount ();
226             int i = 0;
227             while ( ! pUriTokens.isEmpty () && i < singleValueCount)
228             {
229               String token = pUriTokens.remove (0);
230               try
231               {
232                 token = URLDecoder.decode (token, "UTF-8");
233               } catch (UnsupportedEncodingException e)
234               {
235                 // nothing to do, token stays encoded
236               }
237               valueList.add (token);
238               ++i;
239             }
240             parameter.consumeList (valueList.toArray (STRING_ARRAY_PROTOTYPE));
241           }
242         }
243       }
244     }
245 
246     if (mHandlerChain != null)
247     {
248       for (IURLActionHandler chainedHandler : mHandlerChain)
249       {
250         if (LOG.isTraceEnabled ())
251         {
252           LOG.trace ("Executing chained handler " + chainedHandler + " (" + mHandlerChain.size () + " chained handler(s) in list)");
253         }
254         AbstractURLActionCommand commandFromChain = chainedHandler.handleURL (pUriTokens, pParameters, pParameterMode);
255         if (commandFromChain != null) return commandFromChain;
256       }
257     }
258     
259     return handleURLImpl (pUriTokens, pParameters, pParameterMode);
260   }
261       
262   protected abstract AbstractURLActionCommand handleURLImpl (List<String> uriTokens, 
263                                                              Map<String, List<String>> parameters, 
264                                                              ParameterMode parameterMode);
265   
266   private String urlEncode (String term)
267   {
268     try
269     {
270       return URLEncoder.encode (term, "UTF-8");
271     } catch (UnsupportedEncodingException e)
272     {
273       // this should not happen
274       return term;
275     }
276   }
277   
278   public final void addSubHandler (AbstractURLActionHandler subHandler)
279   {
280     CheckForNull.check (subHandler);
281     if (subHandler.mParentHandler != null)
282       throw new IllegalArgumentException (String.format ("This sub-handler instance has " +
283       		"already been added to another action handler. This handler = '%s'; sub-handler = '%s'", 
284       		mActionName, subHandler.mActionName));
285     subHandler.mParentHandler = this;
286     subHandler.mActionURI = String.format ("%s%s%s", getActionURI (), "/", urlEncode (subHandler.mActionName));
287     getSubHandlerMap ().put (subHandler.mActionName, subHandler);
288     subHandler.setCaseSensitive (mCaseSensitive);
289   }
290   
291   public String getActionURI ()
292   {
293     return mActionURI;
294   }
295   
296   protected final void setParent (AbstractURLActionHandler parent)
297   {
298     mParentHandler = parent;
299   }
300   
301   protected IURLProvider getContextURLProvider ()
302   {
303     if (mParentHandler != null)
304     {
305       return mParentHandler.getContextURLProvider ();
306     } else
307     {
308       throw new IllegalStateException ("Unable to provide IURLProvider object: this URL " +
309       		"action handler or one of its ancestors hasn't yet been added to a " + URLActionDispatcher.class.getName ());
310     }
311   }
312   
313   public URL getParameterizedActionURL (boolean clearAfterwards)
314   {
315     return getParameterizedActionURL (clearAfterwards, ParameterMode.QUERY);
316   }
317   
318   public URL getParameterizedActionURL (boolean clearAfterwards, ParameterMode parameterMode)
319   {
320     return getParameterizedActionURL (clearAfterwards, parameterMode, false);
321   }
322   
323   public URL getParameterizedActionURL (boolean clearAfterwards, ParameterMode parameterMode, boolean addHashMark)
324   {
325     return getParameterizedActionURL (clearAfterwards, parameterMode, addHashMark, null);
326   }
327   
328   public URL getParameterizedActionURL (boolean clearAfterwards, ParameterMode parameterMode, boolean addHashMark, URL baseURL)
329   {
330     StringBuilder buf = new StringBuilder ();
331     if (addHashMark) 
332     {
333       buf.append ('#');
334       buf.append (getActionURI ().substring (1));
335     } else
336     {
337       buf.append (getActionURI ());
338     }
339     
340     boolean removeLastCharacter = false;
341     if (mActionArgumentMap != null && ! mActionArgumentMap.isEmpty ())
342     {
343       if (parameterMode == ParameterMode.QUERY)
344       {
345         buf.append ('?');
346         for (String argument : mActionArgumentOrder)
347         {
348           for (Serializable value : mActionArgumentMap.get (argument))
349           {
350             buf.append (urlEncode (argument)).append ('=').append (urlEncode (value.toString ()));
351             buf.append ('&');
352             removeLastCharacter = true;
353           }
354         }
355       } else
356       {
357         buf.append ('/');
358         for (String argument : mActionArgumentOrder)
359         {
360           for (Serializable value : mActionArgumentMap.get (argument))
361           {
362             if (parameterMode == ParameterMode.DIRECTORY_WITH_NAMES)
363             {
364               buf.append (urlEncode (argument)).append ('/');
365             }
366             buf.append (urlEncode (value.toString ()));
367             buf.append ('/');
368             removeLastCharacter = true;
369           }
370         }
371       }
372     }
373     
374     if (removeLastCharacter) buf.setLength (buf.length () - 1);
375     
376     try
377     {
378       URL url = baseURL == null ? getContextURLProvider ().getURL () : baseURL;
379       return new URL (url, buf.toString ());
380     } catch (MalformedURLException e)
381     {
382       throw new RuntimeException ("Unable to create URL object.", e);
383     } finally
384     {
385       if (clearAfterwards)
386       {
387         clearActionArguments ();
388       }
389     }
390   }
391   
392   public final void addDefaultCommandForCondition (AbstractURLActionCommand command, AbstractCondition condition)
393   {
394     CheckForNull.check (command, condition);
395     if (mCommandsForCondition == null)  
396       mCommandsForCondition = new LinkedList<CommandForCondition> ();
397     CommandForCondition cfc = new CommandForCondition ();
398     cfc.mDefaultCommandForCondition = command;
399     cfc.mCondition = condition;
400     mCommandsForCondition.add (cfc);
401   }
402   
403   public void addToHandlerChain (IURLActionHandler handler)
404   {
405     CheckForNull.check (handler);
406     if (mHandlerChain == null)
407     {
408       mHandlerChain = new LinkedList<IURLActionHandler> ();
409     }
410     mHandlerChain.add (handler);
411   }
412   
413   /**
414    * <code>null</code> argument values are ignored.
415    * @param argumentName
416    * @param argumentValues
417    */
418   public void addActionArgument (String argumentName, Serializable ... argumentValues)
419   {
420     CheckForNull.check (argumentName);
421     if (mActionArgumentMap == null)
422     {
423       mActionArgumentMap = new HashMap<String, List<Serializable>> (4);
424       mActionArgumentOrder = new LinkedList<String> ();
425     }
426 
427     List<Serializable> valueList = mActionArgumentMap.get (argumentName);
428     if (valueList == null)
429     {
430       valueList = new LinkedList<Serializable> ();
431       mActionArgumentMap.put (argumentName, valueList);
432     }
433     for (Serializable value : argumentValues)
434     {
435       if (value != null)
436         valueList.add (value);
437     }
438     if (valueList.isEmpty ())
439     {
440       mActionArgumentMap.remove (argumentName);
441     } else if (!mActionArgumentOrder.contains (argumentName))
442     {
443       mActionArgumentOrder.add (argumentName);
444     }
445   }
446 
447   public void clearActionArguments ()
448   {
449     if (mActionArgumentMap != null)
450     {
451       mActionArgumentMap.clear ();
452       mActionArgumentOrder.clear ();
453     }
454   }
455   
456   public final void clearDefaultCommands ()
457   {
458     mCommandsForCondition.clear ();
459   }
460   
461   protected final AbstractURLActionCommand forwardToSubHandler (String handlerID, List<String> uriTokens, 
462                                    Map<String, List<String>> parameters, ParameterMode parameterMode)
463   {
464     
465     AbstractURLActionHandler subHandler = getSubHandlerMap ().get (handlerID);
466     if (subHandler == null) return mDefaultCommand;
467     
468     return subHandler.handleURL (uriTokens, parameters, parameterMode);
469   }
470   
471   private static class CommandForCondition implements Serializable
472   {
473     private static final long serialVersionUID = 2090692709855753816L;
474 
475     private AbstractURLActionCommand mDefaultCommandForCondition;
476     private AbstractCondition        mCondition;
477   }
478   
479   public void getActionURLOverview (List<String> targetList)
480   {
481     StringBuilder buf = new StringBuilder ();
482     buf.append (getActionURI ());
483     
484     if (mURLParameters != null && mURLParameters.size () > 0)
485     {
486       buf.append (" ? ");
487       for (IURLParameter<?> parameter : mURLParameters)
488       {
489         buf.append (parameter).append (", ");
490       }
491       buf.setLength (buf.length () - 2);
492     }
493     if (buf.length () > 0)
494       targetList.add (buf.toString ());
495     for (AbstractURLActionHandler subHandler : getSubHandlerMap ().values ())
496     {
497       subHandler.getActionURLOverview (targetList);
498     }
499   }
500   
501   @Override
502   public String toString ()
503   {
504     return String.format ("%s='%s'", getClass ().getSimpleName (), mActionName);
505   }
506 }