Coverage Report - org.seasar.cubby.routing.impl.PathResolverImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
PathResolverImpl
90%
81/90
81%
36/44
4.952
PathResolverImpl$1
100%
3/3
N/A
4.952
PathResolverImpl$2
100%
7/7
100%
4/4
4.952
PathResolverImpl$ResolvedPathInfo
88%
8/9
75%
3/4
4.952
PathResolverImpl$RoutingKey
34%
23/66
16%
9/54
4.952
 
 1  
 /*
 2  
  * Copyright 2004-2009 the Seasar Foundation and the Others.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 13  
  * either express or implied. See the License for the specific language
 14  
  * governing permissions and limitations under the License.
 15  
  */
 16  
 package org.seasar.cubby.routing.impl;
 17  
 
 18  
 import static org.seasar.cubby.internal.util.LogMessages.format;
 19  
 
 20  
 import java.io.UnsupportedEncodingException;
 21  
 import java.lang.reflect.Method;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Collection;
 24  
 import java.util.HashMap;
 25  
 import java.util.Iterator;
 26  
 import java.util.List;
 27  
 import java.util.Map;
 28  
 import java.util.TreeMap;
 29  
 import java.util.Map.Entry;
 30  
 import java.util.regex.Matcher;
 31  
 import java.util.regex.Pattern;
 32  
 
 33  
 import org.seasar.cubby.action.Path;
 34  
 import org.seasar.cubby.action.RequestMethod;
 35  
 import org.seasar.cubby.internal.util.MetaUtils;
 36  
 import org.seasar.cubby.internal.util.QueryStringBuilder;
 37  
 import org.seasar.cubby.internal.util.URLBodyEncoder;
 38  
 import org.seasar.cubby.routing.PathInfo;
 39  
 import org.seasar.cubby.routing.PathResolver;
 40  
 import org.seasar.cubby.routing.PathTemplateParser;
 41  
 import org.seasar.cubby.routing.Routing;
 42  
 import org.seasar.cubby.routing.RoutingException;
 43  
 import org.seasar.cubby.util.ActionUtils;
 44  
 import org.slf4j.Logger;
 45  
 import org.slf4j.LoggerFactory;
 46  
 
 47  
 /**
 48  
  * パスに対応するアクションメソッドを解決するためのクラスの実装です。
 49  
  * 
 50  
  * @author baba
 51  
  */
 52  403
 public class PathResolverImpl implements PathResolver {
 53  
 
 54  
         /** ロガー */
 55  1
         private static final Logger logger = LoggerFactory
 56  
                         .getLogger(PathResolverImpl.class);
 57  
 
 58  
         /** 登録されたルーティングのマップ。 */
 59  57
         private final Map<RoutingKey, Routing> routings = new TreeMap<RoutingKey, Routing>();
 60  
 
 61  
         /** パステンプレートのパーサー。 */
 62  
         private final PathTemplateParser pathTemplateParser;
 63  
 
 64  
         /**
 65  
          * インスタンス化します。
 66  
          * 
 67  
          * @param pathTemplateParser
 68  
          *            パステンプレートのパーサー
 69  
          */
 70  57
         public PathResolverImpl(final PathTemplateParser pathTemplateParser) {
 71  57
                 this.pathTemplateParser = pathTemplateParser;
 72  57
         }
 73  
 
 74  
         /**
 75  
          * ルーティング情報を取得します。
 76  
          * 
 77  
          * @return ルーティング情報
 78  
          */
 79  
         public Collection<Routing> getRoutings() {
 80  43
                 return routings.values();
 81  
         }
 82  
 
 83  
         /**
 84  
          * {@inheritDoc}
 85  
          */
 86  
         public void add(final Class<?> actionClass) {
 87  1576
                 for (final Method method : actionClass.getMethods()) {
 88  1499
                         if (ActionUtils.isActionMethod(method)) {
 89  288
                                 final String actionPath = MetaUtils.getActionPath(actionClass,
 90  
                                                 method);
 91  288
                                 final RequestMethod[] acceptableRequestMethods = MetaUtils
 92  
                                                 .getAcceptableRequestMethods(actionClass, method);
 93  834
                                 for (final RequestMethod requestMethod : acceptableRequestMethods) {
 94  546
                                         final String onSubmit = MetaUtils.getOnSubmit(method);
 95  546
                                         final int priority = MetaUtils.getPriority(method);
 96  546
                                         this.add(actionPath, actionClass, method, requestMethod,
 97  
                                                         onSubmit, priority);
 98  
                                 }
 99  
                         }
 100  
                 }
 101  77
         }
 102  
 
 103  
         /**
 104  
          * {@inheritDoc}
 105  
          */
 106  
         public void addAll(final Collection<Class<?>> actionClasses) {
 107  44
                 for (final Class<?> actionClass : actionClasses) {
 108  74
                         add(actionClass);
 109  
                 }
 110  44
         }
 111  
 
 112  
         /**
 113  
          * {@inheritDoc}
 114  
          */
 115  
         public void clear() {
 116  1
                 routings.clear();
 117  1
         }
 118  
 
 119  
         /**
 120  
          * {@inheritDoc}
 121  
          */
 122  
         public void add(final String actionPath, final Class<?> actionClass,
 123  
                         final String methodName, final RequestMethod requestMethod,
 124  
                         final String onSubmit, final int priority) {
 125  
                 try {
 126  24
                         final Method method = actionClass.getMethod(methodName);
 127  24
                         this.add(actionPath, actionClass, method, requestMethod, onSubmit,
 128  
                                         priority);
 129  0
                 } catch (final NoSuchMethodException e) {
 130  0
                         throw new RoutingException(e);
 131  24
                 }
 132  24
         }
 133  
 
 134  
         /**
 135  
          * ルーティング情報を登録します。
 136  
          * 
 137  
          * @param actionPath
 138  
          *            アクションのパス
 139  
          * @param actionClass
 140  
          *            アクションクラス
 141  
          * @param method
 142  
          *            アクションメソッド
 143  
          * @param requestMethods
 144  
          *            要求メソッド
 145  
          * @param onSubmit
 146  
          *            アクションメソッドへ振り分けるための要求パラメータ名
 147  
          * @param priority
 148  
          *            プライオリティ
 149  
          */
 150  
         private void add(final String actionPath, final Class<?> actionClass,
 151  
                         final Method method, final RequestMethod requestMethod,
 152  
                         final String onSubmit, final int priority) {
 153  570
                 if (!ActionUtils.isActionMethod(method)) {
 154  0
                         throw new RoutingException(format("ECUB0003", method));
 155  
                 }
 156  
 
 157  570
                 final List<String> uriParameterNames = new ArrayList<String>();
 158  570
                 final String uriRegex = pathTemplateParser.parse(actionPath,
 159  570
                                 new PathTemplateParser.Handler() {
 160  
 
 161  
                                         public String handle(final String name, final String regex) {
 162  385
                                                 uriParameterNames.add(name);
 163  385
                                                 return regexGroup(regex);
 164  
                                         }
 165  
 
 166  
                                 });
 167  570
                 final Pattern pattern = Pattern.compile("^" + uriRegex + "$");
 168  
 
 169  570
                 final Routing routing = new RoutingImpl(actionClass, method,
 170  
                                 actionPath, uriParameterNames, pattern, requestMethod,
 171  
                                 onSubmit, priority);
 172  570
                 final RoutingKey key = new RoutingKey(routing);
 173  
 
 174  570
                 if (logger.isDebugEnabled()) {
 175  570
                         logger.debug(format("DCUB0007", routing));
 176  
                 }
 177  570
                 if (routings.containsKey(key)) {
 178  0
                         final Routing duplication = routings.get(key);
 179  0
                         throw new RoutingException(format("ECUB0001", routing, duplication));
 180  
                 }
 181  570
                 routings.put(key, routing);
 182  570
         }
 183  
 
 184  
         /**
 185  
          * {@inheritDoc}
 186  
          */
 187  
         public PathInfo getPathInfo(final String path, final String requestMethod,
 188  
                         final String characterEncoding) {
 189  13
                 final Iterator<Routing> iterator = getRoutings().iterator();
 190  177
                 while (iterator.hasNext()) {
 191  175
                         final Routing routing = iterator.next();
 192  175
                         final Matcher matcher = routing.getPattern().matcher(path);
 193  175
                         if (matcher.find()) {
 194  11
                                 if (routing.isAcceptable(requestMethod)) {
 195  11
                                         final Map<String, Routing> onSubmitRoutings = new HashMap<String, Routing>();
 196  11
                                         onSubmitRoutings.put(routing.getOnSubmit(), routing);
 197  128
                                         while (iterator.hasNext()) {
 198  117
                                                 final Routing anotherRouting = iterator.next();
 199  117
                                                 if (routing.getPattern().pattern().equals(
 200  
                                                                 anotherRouting.getPattern().pattern())
 201  
                                                                 && routing.getRequestMethod().equals(
 202  
                                                                                 anotherRouting.getRequestMethod())) {
 203  0
                                                         onSubmitRoutings.put(anotherRouting.getOnSubmit(),
 204  
                                                                         anotherRouting);
 205  
                                                 }
 206  117
                                         }
 207  
 
 208  11
                                         final Map<String, String[]> uriParameters = new HashMap<String, String[]>();
 209  19
                                         for (int i = 0; i < matcher.groupCount(); i++) {
 210  8
                                                 final String name = routing.getUriParameterNames().get(
 211  
                                                                 i);
 212  8
                                                 final String value = matcher.group(i + 1);
 213  8
                                                 uriParameters.put(name, new String[] { value });
 214  
                                         }
 215  
 
 216  11
                                         final PathInfo pathInfo = new ResolvedPathInfo(
 217  
                                                         uriParameters, onSubmitRoutings);
 218  
 
 219  11
                                         return pathInfo;
 220  
                                 }
 221  
                         }
 222  164
                 }
 223  
 
 224  2
                 return null;
 225  
         }
 226  
 
 227  
         /**
 228  
          * 指定された正規表現を括弧「()」で囲んで正規表現のグループにします。
 229  
          * 
 230  
          * @param regex
 231  
          *            正規表現
 232  
          * @return 正規表現のグループ
 233  
          */
 234  
         private static String regexGroup(final String regex) {
 235  385
                 return "(" + regex + ")";
 236  
         }
 237  
 
 238  
         /**
 239  
          * {@inheritDoc}
 240  
          */
 241  
         public String reverseLookup(final Class<?> actionClass,
 242  
                         final String methodName, final Map<String, String[]> parameters,
 243  
                         final String characterEncoding) {
 244  26
                 final Collection<Routing> routings = getRoutings();
 245  26
                 final Routing routing = findRouting(routings, actionClass, methodName);
 246  25
                 final String actionPath = routing.getActionPath();
 247  25
                 final Map<String, String[]> copyOfParameters = new HashMap<String, String[]>(
 248  
                                 parameters);
 249  25
                 final StringBuilder path = new StringBuilder(100);
 250  25
                 path.append(pathTemplateParser.parse(actionPath,
 251  25
                                 new PathTemplateParser.Handler() {
 252  
 
 253  
                                         public String handle(final String name, final String regex) {
 254  20
                                                 if (!copyOfParameters.containsKey(name)) {
 255  1
                                                         throw new RoutingException(format("ECUB0104",
 256  
                                                                         actionPath, name));
 257  
                                                 }
 258  19
                                                 final String value = copyOfParameters.remove(name)[0];
 259  19
                                                 if (!value.matches(regex)) {
 260  1
                                                         throw new RoutingException(format("ECUB0105",
 261  
                                                                         actionPath, name, value, regex));
 262  
                                                 }
 263  18
                                                 return encode(value, characterEncoding);
 264  
                                         }
 265  
 
 266  
                                 }));
 267  
 
 268  23
                 if (!copyOfParameters.isEmpty()) {
 269  8
                         final QueryStringBuilder builder = new QueryStringBuilder();
 270  8
                         if (characterEncoding != null) {
 271  8
                                 builder.setEncode(characterEncoding);
 272  
                         }
 273  8
                         for (final Entry<String, String[]> entry : copyOfParameters
 274  
                                         .entrySet()) {
 275  22
                                 for (final String value : entry.getValue()) {
 276  11
                                         builder.addParam(entry.getKey(), value);
 277  
                                 }
 278  
                         }
 279  8
                         path.append('?');
 280  8
                         path.append(builder.toString());
 281  
                 }
 282  
 
 283  23
                 return path.toString();
 284  
         }
 285  
 
 286  
         /**
 287  
          * 指定されたクラス、メソッドに対応するルーティング情報を検索します。
 288  
          * 
 289  
          * @param routings
 290  
          *            ルーティング情報
 291  
          * @param actionClass
 292  
          *            クラス
 293  
          * @param methodName
 294  
          *            メソッド
 295  
          * @return ルーティング情報
 296  
          * @throws RoutingException
 297  
          *             ルーティング情報が見つからなかった場合
 298  
          */
 299  
         private static Routing findRouting(final Collection<Routing> routings,
 300  
                         final Class<?> actionClass, final String methodName) {
 301  26
                 for (final Routing routing : routings) {
 302  79
                         if (actionClass.getCanonicalName().equals(
 303  
                                         routing.getActionClass().getCanonicalName())) {
 304  79
                                 if (methodName.equals(routing.getActionMethod().getName())) {
 305  25
                                         return routing;
 306  
                                 }
 307  
                         }
 308  
                 }
 309  1
                 throw new RoutingException(format("ECUB0103", actionClass, methodName));
 310  
         }
 311  
 
 312  
         /**
 313  
          * 指定された文字列を URL エンコードします。
 314  
          * 
 315  
          * @param str
 316  
          *            文字列
 317  
          * @param characterEncoding
 318  
          *            エンコーディング
 319  
          * @return エンコードされた文字列
 320  
          */
 321  
         private static String encode(final String str,
 322  
                         final String characterEncoding) {
 323  18
                 if (characterEncoding == null) {
 324  0
                         return str;
 325  
                 }
 326  
                 try {
 327  18
                         return URLBodyEncoder.encode(str, characterEncoding);
 328  0
                 } catch (final UnsupportedEncodingException e) {
 329  0
                         throw new RoutingException(e);
 330  
                 }
 331  
         }
 332  
 
 333  
         /**
 334  
          * ルーティングのキーです。
 335  
          * 
 336  
          * @author baba
 337  
          */
 338  3535
         static class RoutingKey implements Comparable<RoutingKey> {
 339  
 
 340  
                 private final int priority;
 341  
 
 342  
                 private final List<String> uriParameterNames;
 343  
 
 344  
                 private final Pattern pattern;
 345  
 
 346  
                 private final RequestMethod requestMethod;
 347  
 
 348  
                 private final String onSubmit;
 349  
 
 350  586
                 public RoutingKey(final Routing routing) {
 351  586
                         this.priority = routing.getPriority();
 352  586
                         this.uriParameterNames = routing.getUriParameterNames();
 353  586
                         this.pattern = routing.getPattern();
 354  586
                         this.requestMethod = routing.getRequestMethod();
 355  586
                         this.onSubmit = routing.getOnSubmit();
 356  586
                 }
 357  
 
 358  
                 /**
 359  
                  * このキーと指定されたキーを比較します。
 360  
                  * <p>
 361  
                  * 正規表現パターンと HTTP メソッドが同じ場合は同値とみなします。
 362  
                  * </p>
 363  
                  * <p>
 364  
                  * また、大小関係は以下のようになります。
 365  
                  * <ul>
 366  
                  * <li>優先度(@link {@link Path#priority()})が小さい順</li>
 367  
                  * <li>URI 埋め込みパラメータが少ない順</li>
 368  
                  * <li>正規表現の順(@link {@link String#compareTo(String)})</li>
 369  
                  * </ul>
 370  
                  * </p>
 371  
                  * 
 372  
                  * @param another
 373  
                  *            比較対象のキー
 374  
                  * @return 比較結果
 375  
                  */
 376  
                 public int compareTo(final RoutingKey another) {
 377  3537
                         int compare = this.priority - another.priority;
 378  3537
                         if (compare != 0) {
 379  45
                                 return compare;
 380  
                         }
 381  3492
                         compare = this.uriParameterNames.size()
 382  
                                         - another.uriParameterNames.size();
 383  3492
                         if (compare != 0) {
 384  1186
                                 return compare;
 385  
                         }
 386  2306
                         compare = this.pattern.pattern().compareTo(
 387  
                                         another.pattern.pattern());
 388  2306
                         if (compare != 0) {
 389  1636
                                 return compare;
 390  
                         }
 391  670
                         compare = this.requestMethod.compareTo(another.requestMethod);
 392  670
                         if (compare != 0) {
 393  668
                                 return compare;
 394  
                         }
 395  2
                         if (this.onSubmit == another.onSubmit) {
 396  2
                                 compare = 0;
 397  0
                         } else if (this.onSubmit == null) {
 398  0
                                 compare = -1;
 399  0
                         } else if (another.onSubmit == null) {
 400  0
                                 compare = 1;
 401  
                         } else {
 402  0
                                 compare = this.onSubmit.compareTo(another.onSubmit);
 403  
                         }
 404  2
                         return compare;
 405  
                 }
 406  
 
 407  
                 /**
 408  
                  * {@inheritDoc}
 409  
                  */
 410  
                 @Override
 411  
                 public int hashCode() {
 412  0
                         final int prime = 31;
 413  0
                         int result = 1;
 414  0
                         result = prime * result
 415  
                                         + ((onSubmit == null) ? 0 : onSubmit.hashCode());
 416  0
                         result = prime
 417  
                                         * result
 418  
                                         + ((pattern.pattern() == null) ? 0 : pattern.pattern()
 419  
                                                         .hashCode());
 420  0
                         result = prime * result + priority;
 421  0
                         result = prime * result
 422  
                                         + ((requestMethod == null) ? 0 : requestMethod.hashCode());
 423  0
                         result = prime
 424  
                                         * result
 425  
                                         + ((uriParameterNames == null) ? 0 : uriParameterNames
 426  
                                                         .hashCode());
 427  0
                         return result;
 428  
                 }
 429  
 
 430  
                 /**
 431  
                  * {@inheritDoc}
 432  
                  */
 433  
                 @Override
 434  
                 public boolean equals(final Object obj) {
 435  0
                         if (this == obj) {
 436  0
                                 return true;
 437  
                         }
 438  0
                         if (obj == null) {
 439  0
                                 return false;
 440  
                         }
 441  0
                         if (getClass() != obj.getClass()) {
 442  0
                                 return false;
 443  
                         }
 444  0
                         final RoutingKey other = (RoutingKey) obj;
 445  0
                         if (onSubmit == null) {
 446  0
                                 if (other.onSubmit != null) {
 447  0
                                         return false;
 448  
                                 }
 449  0
                         } else if (!onSubmit.equals(other.onSubmit)) {
 450  0
                                 return false;
 451  
                         }
 452  0
                         if (pattern == null) {
 453  0
                                 if (other.pattern != null) {
 454  0
                                         return false;
 455  
                                 }
 456  0
                         } else if (!pattern.pattern().equals(other.pattern.pattern())) {
 457  0
                                 return false;
 458  
                         }
 459  0
                         if (priority != other.priority) {
 460  0
                                 return false;
 461  
                         }
 462  0
                         if (requestMethod == null) {
 463  0
                                 if (other.requestMethod != null) {
 464  0
                                         return false;
 465  
                                 }
 466  0
                         } else if (!requestMethod.equals(other.requestMethod)) {
 467  0
                                 return false;
 468  
                         }
 469  0
                         if (uriParameterNames == null) {
 470  0
                                 if (other.uriParameterNames != null) {
 471  0
                                         return false;
 472  
                                 }
 473  0
                         } else if (!uriParameterNames.equals(other.uriParameterNames)) {
 474  0
                                 return false;
 475  
                         }
 476  0
                         return true;
 477  
                 }
 478  
 
 479  
         }
 480  
 
 481  
         /**
 482  
          * パスから取得した情報の実装です。
 483  
          * 
 484  
          * @author baba
 485  
          */
 486  
         static class ResolvedPathInfo implements PathInfo {
 487  
 
 488  
                 /** URI から抽出したパラメータ。 */
 489  
                 private final Map<String, String[]> uriParameters;
 490  
 
 491  
                 /** 要求パラメータ名と対応するルーティングのマッピング。 */
 492  
                 private final Map<String, Routing> routings;
 493  
 
 494  
                 /**
 495  
                  * インスタンス化します。
 496  
                  * 
 497  
                  * @param uriParameters
 498  
                  *            URI から抽出したパラメータ
 499  
                  * @param routings
 500  
                  *            要求パラメータ名とルーティングのマッピング
 501  
                  */
 502  
                 public ResolvedPathInfo(final Map<String, String[]> uriParameters,
 503  11
                                 final Map<String, Routing> routings) {
 504  11
                         this.uriParameters = uriParameters;
 505  11
                         this.routings = routings;
 506  11
                 }
 507  
 
 508  
                 /**
 509  
                  * {@inheritDoc}
 510  
                  */
 511  
                 public Map<String, String[]> getURIParameters() {
 512  10
                         return uriParameters;
 513  
                 }
 514  
 
 515  
                 /**
 516  
                  * {@inheritDoc}
 517  
                  */
 518  
                 public Routing dispatch(final Map<String, Object[]> parameterMap) {
 519  11
                         for (final Entry<String, Routing> entry : routings.entrySet()) {
 520  11
                                 if (parameterMap.containsKey(entry.getKey())) {
 521  0
                                         return entry.getValue();
 522  
                                 }
 523  
                         }
 524  11
                         return routings.get(null);
 525  
                 }
 526  
 
 527  
         }
 528  
 
 529  
 }