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-2010 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  
 
 17  
 package org.seasar.cubby.routing.impl;
 18  
 
 19  
 import static org.seasar.cubby.internal.util.LogMessages.format;
 20  
 
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.lang.reflect.Method;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Collection;
 25  
 import java.util.HashMap;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 import java.util.Map;
 29  
 import java.util.TreeMap;
 30  
 import java.util.Map.Entry;
 31  
 import java.util.regex.Matcher;
 32  
 import java.util.regex.Pattern;
 33  
 
 34  
 import org.seasar.cubby.action.Path;
 35  
 import org.seasar.cubby.action.RequestMethod;
 36  
 import org.seasar.cubby.internal.util.MetaUtils;
 37  
 import org.seasar.cubby.internal.util.QueryStringBuilder;
 38  
 import org.seasar.cubby.internal.util.URLBodyEncoder;
 39  
 import org.seasar.cubby.routing.PathInfo;
 40  
 import org.seasar.cubby.routing.PathResolver;
 41  
 import org.seasar.cubby.routing.PathTemplateParser;
 42  
 import org.seasar.cubby.routing.Routing;
 43  
 import org.seasar.cubby.routing.RoutingException;
 44  
 import org.seasar.cubby.util.ActionUtils;
 45  
 import org.slf4j.Logger;
 46  
 import org.slf4j.LoggerFactory;
 47  
 
 48  
 /**
 49  
  * パスに対応するアクションメソッドを解決するためのクラスの実装です。
 50  
  * 
 51  
  * @author baba
 52  
  */
 53  471
 public class PathResolverImpl implements PathResolver {
 54  
 
 55  
         /** ロガー */
 56  1
         private static final Logger logger = LoggerFactory
 57  
                         .getLogger(PathResolverImpl.class);
 58  
 
 59  
         /** 登録されたルーティングのマップ。 */
 60  59
         private final Map<RoutingKey, Routing> routings = new TreeMap<RoutingKey, Routing>();
 61  
 
 62  
         /** パステンプレートのパーサー。 */
 63  
         private final PathTemplateParser pathTemplateParser;
 64  
 
 65  
         /**
 66  
          * インスタンス化します。
 67  
          * 
 68  
          * @param pathTemplateParser
 69  
          *            パステンプレートのパーサー
 70  
          */
 71  59
         public PathResolverImpl(final PathTemplateParser pathTemplateParser) {
 72  59
                 this.pathTemplateParser = pathTemplateParser;
 73  59
         }
 74  
 
 75  
         /**
 76  
          * ルーティング情報を取得します。
 77  
          * 
 78  
          * @return ルーティング情報
 79  
          */
 80  
         public Collection<Routing> getRoutings() {
 81  45
                 return routings.values();
 82  
         }
 83  
 
 84  
         /**
 85  
          * {@inheritDoc}
 86  
          */
 87  
         public void add(final Class<?> actionClass) {
 88  1738
                 for (final Method method : actionClass.getMethods()) {
 89  1655
                         if (ActionUtils.isActionMethod(method)) {
 90  348
                                 final String actionPath = MetaUtils.getActionPath(actionClass,
 91  
                                                 method);
 92  348
                                 final RequestMethod[] acceptableRequestMethods = MetaUtils
 93  
                                                 .getAcceptableRequestMethods(actionClass, method);
 94  1010
                                 for (final RequestMethod requestMethod : acceptableRequestMethods) {
 95  662
                                         final String onSubmit = MetaUtils.getOnSubmit(method);
 96  662
                                         final int priority = MetaUtils.getPriority(method);
 97  662
                                         this.add(actionPath, actionClass, method, requestMethod,
 98  
                                                         onSubmit, priority);
 99  
                                 }
 100  
                         }
 101  
                 }
 102  83
         }
 103  
 
 104  
         /**
 105  
          * {@inheritDoc}
 106  
          */
 107  
         public void addAll(final Collection<Class<?>> actionClasses) {
 108  46
                 for (final Class<?> actionClass : actionClasses) {
 109  80
                         add(actionClass);
 110  
                 }
 111  46
         }
 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  686
                 if (!ActionUtils.isActionMethod(method)) {
 155  0
                         throw new RoutingException(format("ECUB0003", method));
 156  
                 }
 157  
 
 158  686
                 final List<String> uriParameterNames = new ArrayList<String>();
 159  686
                 final String uriRegex = pathTemplateParser.parse(actionPath,
 160  686
                                 new PathTemplateParser.Handler() {
 161  
 
 162  
                                         public String handle(final String name, final String regex) {
 163  453
                                                 uriParameterNames.add(name);
 164  453
                                                 return regexGroup(regex);
 165  
                                         }
 166  
 
 167  
                                 });
 168  686
                 final Pattern pattern = Pattern.compile("^" + uriRegex + "$");
 169  
 
 170  686
                 final Routing routing = new RoutingImpl(actionClass, method,
 171  
                                 actionPath, uriParameterNames, pattern, requestMethod,
 172  
                                 onSubmit, priority);
 173  686
                 final RoutingKey key = new RoutingKey(routing);
 174  
 
 175  686
                 if (logger.isDebugEnabled()) {
 176  686
                         logger.debug(format("DCUB0007", routing));
 177  
                 }
 178  686
                 if (routings.containsKey(key)) {
 179  0
                         final Routing duplication = routings.get(key);
 180  0
                         throw new RoutingException(format("ECUB0001", routing, duplication));
 181  
                 }
 182  686
                 routings.put(key, routing);
 183  686
         }
 184  
 
 185  
         /**
 186  
          * {@inheritDoc}
 187  
          */
 188  
         public PathInfo getPathInfo(final String path, final String requestMethod,
 189  
                         final String characterEncoding) {
 190  15
                 final Iterator<Routing> iterator = getRoutings().iterator();
 191  234
                 while (iterator.hasNext()) {
 192  232
                         final Routing routing = iterator.next();
 193  232
                         final Matcher matcher = routing.getPattern().matcher(path);
 194  232
                         if (matcher.find()) {
 195  13
                                 if (routing.isAcceptable(requestMethod)) {
 196  13
                                         final Map<String, Routing> onSubmitRoutings = new HashMap<String, Routing>();
 197  13
                                         onSubmitRoutings.put(routing.getOnSubmit(), routing);
 198  177
                                         while (iterator.hasNext()) {
 199  164
                                                 final Routing anotherRouting = iterator.next();
 200  164
                                                 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  164
                                         }
 208  
 
 209  13
                                         final Map<String, String[]> uriParameters = new HashMap<String, String[]>();
 210  22
                                         for (int i = 0; i < matcher.groupCount(); i++) {
 211  9
                                                 final String name = routing.getUriParameterNames().get(
 212  
                                                                 i);
 213  9
                                                 final String value = matcher.group(i + 1);
 214  9
                                                 uriParameters.put(name, new String[] { value });
 215  
                                         }
 216  
 
 217  13
                                         final PathInfo pathInfo = new ResolvedPathInfo(
 218  
                                                         uriParameters, onSubmitRoutings);
 219  
 
 220  13
                                         return pathInfo;
 221  
                                 }
 222  
                         }
 223  219
                 }
 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  453
                 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  26
                 final Collection<Routing> routings = getRoutings();
 246  26
                 final Routing routing = findRouting(routings, actionClass, methodName);
 247  25
                 final String actionPath = routing.getActionPath();
 248  25
                 final Map<String, String[]> copyOfParameters = new HashMap<String, String[]>(
 249  
                                 parameters);
 250  25
                 final StringBuilder path = new StringBuilder(100);
 251  25
                 path.append(pathTemplateParser.parse(actionPath,
 252  25
                                 new PathTemplateParser.Handler() {
 253  
 
 254  
                                         public String handle(final String name, final String regex) {
 255  20
                                                 if (!copyOfParameters.containsKey(name)) {
 256  1
                                                         throw new RoutingException(format("ECUB0104",
 257  
                                                                         actionPath, name));
 258  
                                                 }
 259  19
                                                 final String value = copyOfParameters.remove(name)[0];
 260  19
                                                 if (!value.matches(regex)) {
 261  1
                                                         throw new RoutingException(format("ECUB0105",
 262  
                                                                         actionPath, name, value, regex));
 263  
                                                 }
 264  18
                                                 return encode(value, characterEncoding);
 265  
                                         }
 266  
 
 267  
                                 }));
 268  
 
 269  23
                 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  23
                 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  26
                 for (final Routing routing : routings) {
 303  79
                         if (actionClass.getCanonicalName().equals(
 304  
                                         routing.getActionClass().getCanonicalName())) {
 305  79
                                 if (methodName.equals(routing.getActionMethod().getName())) {
 306  25
                                         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  18
                 if (characterEncoding == null) {
 325  0
                         return str;
 326  
                 }
 327  
                 try {
 328  18
                         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  
          */
 339  4655
         static class RoutingKey implements Comparable<RoutingKey> {
 340  
 
 341  
                 private final int priority;
 342  
 
 343  
                 private final List<String> uriParameterNames;
 344  
 
 345  
                 private final Pattern pattern;
 346  
 
 347  
                 private final RequestMethod requestMethod;
 348  
 
 349  
                 private final String onSubmit;
 350  
 
 351  702
                 public RoutingKey(final Routing routing) {
 352  702
                         this.priority = routing.getPriority();
 353  702
                         this.uriParameterNames = routing.getUriParameterNames();
 354  702
                         this.pattern = routing.getPattern();
 355  702
                         this.requestMethod = routing.getRequestMethod();
 356  702
                         this.onSubmit = routing.getOnSubmit();
 357  702
                 }
 358  
 
 359  
                 /**
 360  
                  * このキーと指定されたキーを比較します。
 361  
                  * <p>
 362  
                  * 正規表現パターンと HTTP メソッドが同じ場合は同値とみなします。
 363  
                  * </p>
 364  
                  * <p>
 365  
                  * また、大小関係は以下のようになります。
 366  
                  * <ul>
 367  
                  * <li>優先度(@link {@link Path#priority()})が小さい順</li>
 368  
                  * <li>URI 埋め込みパラメータが少ない順</li>
 369  
                  * <li>正規表現の順(@link {@link String#compareTo(String)})</li>
 370  
                  * </ul>
 371  
                  * </p>
 372  
                  * 
 373  
                  * @param another
 374  
                  *            比較対象のキー
 375  
                  * @return 比較結果
 376  
                  */
 377  
                 public int compareTo(final RoutingKey another) {
 378  4657
                         int compare = this.priority - another.priority;
 379  4657
                         if (compare != 0) {
 380  45
                                 return compare;
 381  
                         }
 382  4612
                         compare = this.uriParameterNames.size()
 383  
                                         - another.uriParameterNames.size();
 384  4612
                         if (compare != 0) {
 385  1506
                                 return compare;
 386  
                         }
 387  3106
                         compare = this.pattern.pattern().compareTo(
 388  
                                         another.pattern.pattern());
 389  3106
                         if (compare != 0) {
 390  2304
                                 return compare;
 391  
                         }
 392  802
                         compare = this.requestMethod.compareTo(another.requestMethod);
 393  802
                         if (compare != 0) {
 394  800
                                 return compare;
 395  
                         }
 396  2
                         if (this.onSubmit == another.onSubmit) {
 397  2
                                 compare = 0;
 398  0
                         } else if (this.onSubmit == null) {
 399  0
                                 compare = -1;
 400  0
                         } else if (another.onSubmit == null) {
 401  0
                                 compare = 1;
 402  
                         } else {
 403  0
                                 compare = this.onSubmit.compareTo(another.onSubmit);
 404  
                         }
 405  2
                         return compare;
 406  
                 }
 407  
 
 408  
                 /**
 409  
                  * {@inheritDoc}
 410  
                  */
 411  
                 @Override
 412  
                 public int hashCode() {
 413  0
                         final int prime = 31;
 414  0
                         int result = 1;
 415  0
                         result = prime * result
 416  
                                         + ((onSubmit == null) ? 0 : onSubmit.hashCode());
 417  0
                         result = prime
 418  
                                         * result
 419  
                                         + ((pattern.pattern() == null) ? 0 : pattern.pattern()
 420  
                                                         .hashCode());
 421  0
                         result = prime * result + priority;
 422  0
                         result = prime * result
 423  
                                         + ((requestMethod == null) ? 0 : requestMethod.hashCode());
 424  0
                         result = prime
 425  
                                         * result
 426  
                                         + ((uriParameterNames == null) ? 0 : uriParameterNames
 427  
                                                         .hashCode());
 428  0
                         return result;
 429  
                 }
 430  
 
 431  
                 /**
 432  
                  * {@inheritDoc}
 433  
                  */
 434  
                 @Override
 435  
                 public boolean equals(final Object obj) {
 436  0
                         if (this == obj) {
 437  0
                                 return true;
 438  
                         }
 439  0
                         if (obj == null) {
 440  0
                                 return false;
 441  
                         }
 442  0
                         if (getClass() != obj.getClass()) {
 443  0
                                 return false;
 444  
                         }
 445  0
                         final RoutingKey other = (RoutingKey) obj;
 446  0
                         if (onSubmit == null) {
 447  0
                                 if (other.onSubmit != null) {
 448  0
                                         return false;
 449  
                                 }
 450  0
                         } else if (!onSubmit.equals(other.onSubmit)) {
 451  0
                                 return false;
 452  
                         }
 453  0
                         if (pattern == null) {
 454  0
                                 if (other.pattern != null) {
 455  0
                                         return false;
 456  
                                 }
 457  0
                         } else if (!pattern.pattern().equals(other.pattern.pattern())) {
 458  0
                                 return false;
 459  
                         }
 460  0
                         if (priority != other.priority) {
 461  0
                                 return false;
 462  
                         }
 463  0
                         if (requestMethod == null) {
 464  0
                                 if (other.requestMethod != null) {
 465  0
                                         return false;
 466  
                                 }
 467  0
                         } else if (!requestMethod.equals(other.requestMethod)) {
 468  0
                                 return false;
 469  
                         }
 470  0
                         if (uriParameterNames == null) {
 471  0
                                 if (other.uriParameterNames != null) {
 472  0
                                         return false;
 473  
                                 }
 474  0
                         } else if (!uriParameterNames.equals(other.uriParameterNames)) {
 475  0
                                 return false;
 476  
                         }
 477  0
                         return true;
 478  
                 }
 479  
 
 480  
         }
 481  
 
 482  
         /**
 483  
          * パスから取得した情報の実装です。
 484  
          * 
 485  
          * @author baba
 486  
          */
 487  
         static class ResolvedPathInfo implements PathInfo {
 488  
 
 489  
                 /** URI から抽出したパラメータ。 */
 490  
                 private final Map<String, String[]> uriParameters;
 491  
 
 492  
                 /** 要求パラメータ名と対応するルーティングのマッピング。 */
 493  
                 private final Map<String, Routing> routings;
 494  
 
 495  
                 /**
 496  
                  * インスタンス化します。
 497  
                  * 
 498  
                  * @param uriParameters
 499  
                  *            URI から抽出したパラメータ
 500  
                  * @param routings
 501  
                  *            要求パラメータ名とルーティングのマッピング
 502  
                  */
 503  
                 public ResolvedPathInfo(final Map<String, String[]> uriParameters,
 504  13
                                 final Map<String, Routing> routings) {
 505  13
                         this.uriParameters = uriParameters;
 506  13
                         this.routings = routings;
 507  13
                 }
 508  
 
 509  
                 /**
 510  
                  * {@inheritDoc}
 511  
                  */
 512  
                 public Map<String, String[]> getURIParameters() {
 513  12
                         return uriParameters;
 514  
                 }
 515  
 
 516  
                 /**
 517  
                  * {@inheritDoc}
 518  
                  */
 519  
                 public Routing dispatch(final Map<String, Object[]> parameterMap) {
 520  13
                         for (final Entry<String, Routing> entry : routings.entrySet()) {
 521  13
                                 if (parameterMap.containsKey(entry.getKey())) {
 522  0
                                         return entry.getValue();
 523  
                                 }
 524  
                         }
 525  13
                         return routings.get(null);
 526  
                 }
 527  
 
 528  
         }
 529  
 
 530  
 }