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