View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  
28  package org.apache.hc.core5.http.protocol;
29  
30  import java.util.HashSet;
31  import java.util.LinkedHashMap;
32  import java.util.Map;
33  import java.util.Map.Entry;
34  import java.util.Set;
35  import java.util.concurrent.locks.ReentrantLock;
36  
37  import org.apache.hc.core5.annotation.Contract;
38  import org.apache.hc.core5.annotation.ThreadingBehavior;
39  import org.apache.hc.core5.util.Args;
40  
41  /**
42   * Maintains a map of objects keyed by a request URI pattern.
43   * <p>
44   * Patterns may have three formats:
45   * </p>
46   * <ul>
47   * <li>{@code *}</li>
48   * <li>{@code *<uri>}</li>
49   * <li>{@code <uri>*}</li>
50   * </ul>
51   * <p>
52   * This class can be used to resolve an object matching a particular request
53   * URI.
54   * </p>
55   *
56   * @param <T> The type of registered objects.
57   * @since 4.0
58   */
59  @Contract(threading = ThreadingBehavior.SAFE)
60  public class UriPatternMatcher<T> implements LookupRegistry<T> {
61  
62      private final Map<String, T> map;
63  
64      private final ReentrantLock lock;
65  
66      public UriPatternMatcher() {
67          super();
68          this.map = new LinkedHashMap<>();
69          this.lock = new ReentrantLock();
70      }
71  
72      /**
73       * Returns a {@link Set} view of the mappings contained in this matcher.
74       *
75       * @return  a set view of the mappings contained in this matcher.
76       *
77       * @see Map#entrySet()
78       * @since 4.4.9
79       */
80      public Set<Entry<String, T>> entrySet() {
81          lock.lock();
82          try {
83              return new HashSet<>(map.entrySet());
84          } finally {
85              lock.unlock();
86          }
87      }
88  
89      /**
90       * Registers the given object for URIs matching the given pattern.
91       *
92       * @param pattern
93       *            the pattern to register the handler for.
94       * @param obj
95       *            the object.
96       */
97      @Override
98      public void register(final String pattern, final T obj) {
99          lock.lock();
100         try {
101             Args.notNull(pattern, "URI request pattern");
102             this.map.put(pattern, obj);
103         } finally {
104             lock.unlock();
105         }
106     }
107 
108     /**
109      * Removes registered object, if exists, for the given pattern.
110      *
111      * @param pattern
112      *            the pattern to unregister.
113      */
114     @Override
115     public void unregister(final String pattern) {
116         lock.lock();
117         try {
118             if (pattern == null) {
119                 return;
120             }
121             this.map.remove(pattern);
122         } finally {
123             lock.unlock();
124         }
125     }
126 
127     /**
128      * Looks up an object matching the given request path.
129      *
130      * @param path
131      *            the request path
132      * @return object or {@code null} if no match is found.
133      */
134     @Override
135     public T lookup(final String path) {
136         lock.lock();
137         try {
138             Args.notNull(path, "Request path");
139             // direct match?
140             T obj = this.map.get(path);
141             if (obj == null) {
142                 // pattern match?
143                 String bestMatch = null;
144                 for (final String pattern : this.map.keySet()) {
145                     if (matchUriRequestPattern(pattern, path)) {
146                         // we have a match. is it any better?
147                         if (bestMatch == null || (bestMatch.length() < pattern.length())
148                                 || (bestMatch.length() == pattern.length() && pattern.endsWith("*"))) {
149                             obj = this.map.get(pattern);
150                             bestMatch = pattern;
151                         }
152                     }
153                 }
154             }
155             return obj;
156         } finally {
157             lock.unlock();
158         }
159     }
160 
161     /**
162      * Tests if the given request path matches the given pattern.
163      *
164      * @param pattern
165      *            the pattern
166      * @param path
167      *            the request path
168      * @return {@code true} if the request URI matches the pattern, {@code false} otherwise.
169      */
170     protected boolean matchUriRequestPattern(final String pattern, final String path) {
171         if (pattern.equals("*")) {
172             return true;
173         }
174         return (pattern.endsWith("*") && path.startsWith(pattern.substring(0, pattern.length() - 1)))
175                 || (pattern.startsWith("*") && path.endsWith(pattern.substring(1)));
176     }
177 
178     @Override
179     public String toString() {
180         return this.map.toString();
181     }
182 
183 }