View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.juneau;
18  
19  import static org.apache.juneau.commons.utils.CollectionUtils.*;
20  import static org.apache.juneau.commons.utils.StringUtils.*;
21  import static org.apache.juneau.commons.utils.ThrowableUtils.*;
22  import static org.apache.juneau.commons.utils.Utils.*;
23  
24  import java.util.function.Supplier;
25  
26  import org.apache.juneau.annotation.*;
27  import org.apache.juneau.collections.*;
28  import org.apache.juneau.commons.collections.*;
29  import org.apache.juneau.parser.*;
30  
31  /**
32   * Represents a URL broken into authority/context-root/servlet-path/path-info parts.
33   *
34   * <p>
35   * A typical request against a URL takes the following form:
36   * <p class='bcode'>
37   * 	http://host:port/context-root/servlet-path/path-info
38   * 	|   authority   |  context   |  resource  |  path  |
39   * 	+--------------------------------------------------+
40   * </p>
41   *
42   * <p>
43   * This class allows you to convert URL strings to absolute (e.g. <js>"http://host:port/foo/bar"</js>) or root-relative
44   * (e.g. <js>"/foo/bar"</js>) URLs.
45   *
46   */
47  @Bean
48  public class UriContext {
49  
50  	/**
51  	 * Default URI context.
52  	 *
53  	 * <p>
54  	 * No information about authority, servlet-root, context-root, or path-info is known.
55  	 */
56  	public static final UriContext DEFAULT = new UriContext();
57  
58  	/**
59  	 * Static creator.
60  	 *
61  	 * @param s
62  	 * 	The input string.
63  	 * 	<br>Example: <js>{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}</js>
64  	 * @return A new {@link UriContext} object.
65  	 */
66  	public static UriContext of(String s) {
67  		try {
68  			return new UriContext(s);
69  		} catch (ParseException e) {
70  			throw toRex(e);
71  		}
72  	}
73  
74  	/**
75  	 * Static creator.
76  	 *
77  	 * @param authority
78  	 * 	The authority portion of URL (e.g. <js>"http://hostname:port"</js>)
79  	 * @param contextRoot
80  	 * 	The context root of the application (e.g. <js>"/context-root"</js>, or <js>"context-root"</js>)
81  	 * @param servletPath
82  	 * 	The servlet path (e.g. <js>"/servlet-path"</js>, or <js>"servlet-path"</js>)
83  	 * @param pathInfo
84  	 * 	The path info (e.g. <js>"/path-info"</js>, or <js>"path-info"</js>)
85  	 * @return A new {@link UriContext} object.
86  	 */
87  	public static UriContext of(String authority, String contextRoot, String servletPath, String pathInfo) {
88  		return new UriContext(authority, contextRoot, servletPath, pathInfo);
89  	}
90  
91  	private static String getParent(String uri) {
92  		var i = uri.lastIndexOf('/');
93  		if (i <= 1)
94  			return "/";
95  		return uri.substring(0, i);
96  	}
97  
98  	@SuppressWarnings("javadoc")
99  	public final String authority, contextRoot, servletPath, pathInfo, parentPath;
100 
101 	// Memoized suppliers.
102 	private final Supplier<String> aContextRoot, rContextRoot, aServletPath, rResource, aPathInfo, rPath;
103 
104 	/**
105 	 * Default constructor.
106 	 *
107 	 * <p>
108 	 * All <jk>null</jk> values.
109 	 */
110 	public UriContext() {
111 		this(null, null, null, null);
112 	}
113 
114 	/**
115 	 * String constructor.
116 	 *
117 	 * <p>
118 	 * Input string is a JSON object with the following format:
119 	 * <js>{authority:'xxx',contextRoot:'xxx',servletPath:'xxx',pathInfo:'xxx'}</js>
120 	 *
121 	 * @param s
122 	 * 	The input string.
123 	 * 	<br>Example: <js>{authority:'http://localhost:10000',contextRoot:'/myContext',servletPath:'/myServlet',pathInfo:'/foo'}</js>
124 	 * @throws ParseException
125 	 * 	If input string is not a valid JSON object.
126 	 */
127 	public UriContext(String s) throws ParseException {
128 		var m = JsonMap.ofJson(s);
129 		this.authority = nullIfEmpty(trimSlashes(m.getString("authority")));
130 		this.contextRoot = nullIfEmpty(trimSlashes(m.getString("contextRoot")));
131 		this.servletPath = nullIfEmpty(trimSlashes(m.getString("servletPath")));
132 		this.pathInfo = nullIfEmpty(trimSlashes(m.getString("pathInfo")));
133 		this.parentPath = this.pathInfo == null || this.pathInfo.indexOf('/') == -1 ? null : this.pathInfo.substring(0, this.pathInfo.lastIndexOf('/'));
134 		this.rContextRoot = mem(() -> findRContextRoot());
135 		this.rResource = mem(() -> findRResource());
136 		this.rPath = mem(() -> findRPath());
137 		this.aContextRoot = mem(() -> findAContextRoot());
138 		this.aServletPath = mem(() -> findAServletPath());
139 		this.aPathInfo = mem(() -> findAPathInfo());
140 	}
141 
142 	/**
143 	 * Constructor.
144 	 *
145 	 * <p>
146 	 * Leading and trailing slashes are trimmed of all parameters.
147 	 *
148 	 * <p>
149 	 * Any parameter can be <jk>null</jk>.  Blanks and nulls are equivalent.
150 	 *
151 	 * @param authority
152 	 * 	The authority portion of URL (e.g. <js>"http://hostname:port"</js>)
153 	 * @param contextRoot
154 	 * 	The context root of the application (e.g. <js>"/context-root"</js>, or <js>"context-root"</js>)
155 	 * @param servletPath
156 	 * 	The servlet path (e.g. <js>"/servlet-path"</js>, or <js>"servlet-path"</js>)
157 	 * @param pathInfo
158 	 * 	The path info (e.g. <js>"/path-info"</js>, or <js>"path-info"</js>)
159 	 */
160 	@Beanc
161 	public UriContext(@Name("authority") String authority, @Name("contextRoot") String contextRoot, @Name("servletPath") String servletPath, @Name("pathInfo") String pathInfo) {
162 		this.authority = nullIfEmpty(trimSlashes(authority));
163 		this.contextRoot = nullIfEmpty(trimSlashes(contextRoot));
164 		this.servletPath = nullIfEmpty(trimSlashes(servletPath));
165 		this.pathInfo = nullIfEmpty(trimSlashes(pathInfo));
166 		this.parentPath = this.pathInfo == null || this.pathInfo.indexOf('/') == -1 ? null : this.pathInfo.substring(0, this.pathInfo.lastIndexOf('/'));
167 		this.rContextRoot = mem(() -> findRContextRoot());
168 		this.rResource = mem(() -> findRResource());
169 		this.rPath = mem(() -> findRPath());
170 		this.aContextRoot = mem(() -> findAContextRoot());
171 		this.aServletPath = mem(() -> findAServletPath());
172 		this.aPathInfo = mem(() -> findAPathInfo());
173 	}
174 
175 	private String findRContextRoot() {
176 		return contextRoot == null ? "/" : ('/' + contextRoot);
177 	}
178 
179 	private String findRResource() {
180 		// @formatter:off
181 		if (contextRoot == null)
182 			return (
183 				servletPath == null
184 				? "/"
185 				: ('/' + servletPath)
186 			);
187 		return (
188 			servletPath == null
189 			? ('/' + contextRoot)
190 			: ('/' + contextRoot + '/' + servletPath)
191 		);
192 		// @formatter:on
193 	}
194 
195 	private String findRPath() {
196 		// @formatter:off
197 		if (contextRoot == null) {
198 			if (servletPath == null)
199 				return (
200 					pathInfo == null
201 					? "/"
202 					: ('/' + pathInfo)
203 				);
204 			return (
205 				pathInfo == null
206 				? ('/' + servletPath)
207 				: ('/' + servletPath + '/' + pathInfo)
208 			);
209 		}
210 		if (servletPath == null)
211 			return (
212 				pathInfo == null
213 				? ('/' + contextRoot)
214 				: ('/' + contextRoot + '/' + pathInfo)
215 			);
216 		return (
217 			pathInfo == null
218 			? ('/' + contextRoot + '/' + servletPath)
219 			: ('/' + contextRoot + '/' + servletPath + '/' + pathInfo)
220 		);
221 		// @formatter:on
222 	}
223 
224 	private String findAContextRoot() {
225 		// @formatter:off
226 		if (authority == null)
227 			return rContextRoot.get();
228 		return (
229 			contextRoot == null
230 			? authority
231 			: (authority + '/' + contextRoot)
232 		);
233 		// @formatter:on
234 	}
235 
236 	private String findAServletPath() {
237 		// @formatter:off
238 		if (authority == null)
239 			return rResource.get();
240 		if (contextRoot == null)
241 			return (
242 				servletPath == null
243 				? authority
244 				: authority + '/' + servletPath
245 			);
246 		return (
247 			servletPath == null
248 			? (authority + '/' + contextRoot)
249 			: (authority + '/' + contextRoot + '/' + servletPath)
250 		);
251 		// @formatter:on
252 	}
253 
254 	private String findAPathInfo() {
255 		// @formatter:off
256 		if (authority == null)
257 			return rPath.get();
258 		if (contextRoot == null) {
259 			if (servletPath == null)
260 				return (
261 					pathInfo == null
262 					? authority : (authority + '/' + pathInfo)
263 				);
264 			return (
265 				pathInfo == null
266 				? (authority + '/' + servletPath)
267 				: (authority + '/' + servletPath + '/' + pathInfo)
268 			);
269 		}
270 		if (servletPath == null)
271 			return (
272 				pathInfo == null
273 				? authority + '/' + contextRoot
274 				: (authority + '/' + contextRoot + '/' + pathInfo)
275 			);
276 		return (
277 			pathInfo == null
278 			? (authority + '/' + contextRoot + '/' + servletPath)
279 			: (authority + '/' + contextRoot + '/' + servletPath + '/' + pathInfo)
280 		);
281 		// @formatter:on
282 	}
283 
284 	/**
285 	 * Returns the absolute URI of just the authority portion of this URI context.
286 	 *
287 	 * <p>
288 	 * Example:  <js>"http://hostname:port"</js>
289 	 *
290 	 * <p>
291 	 * If the authority is null/empty, returns <js>"/"</js>.
292 	 *
293 	 * @return
294 	 * 	The absolute URI of just the authority portion of this URI context.
295 	 * 	Never <jk>null</jk>.
296 	 */
297 	public String getAbsoluteAuthority() { return authority == null ? "/" : authority; }
298 
299 	/**
300 	 * Returns the absolute URI of the context-root portion of this URI context.
301 	 *
302 	 * <p>
303 	 * Example:  <js>"http://hostname:port/context-root"</js>
304 	 *
305 	 * @return
306 	 * 	The absolute URI of the context-root portion of this URI context.
307 	 * 	Never <jk>null</jk>.
308 	 */
309 	public String getAbsoluteContextRoot() {
310 		return aContextRoot.get();
311 	}
312 
313 	/**
314 	 * Returns the absolute URI of the path portion of this URI context.
315 	 *
316 	 * <p>
317 	 * Example:  <js>"http://hostname:port/context-root/servlet-path/path-info"</js>
318 	 *
319 	 * @return
320 	 * 	The absolute URI of the path portion of this URI context.
321 	 * 	Never <jk>null</jk>.
322 	 */
323 	public String getAbsolutePathInfo() {
324 		return aPathInfo.get();
325 	}
326 
327 	/**
328 	 * Returns the parent of the URL returned by {@link #getAbsolutePathInfo()}.
329 	 *
330 	 * @return The parent of the URL returned by {@link #getAbsolutePathInfo()}.
331 	 */
332 	public String getAbsolutePathInfoParent() { return getParent(getAbsolutePathInfo()); }
333 
334 	/**
335 	 * Returns the absolute URI of the resource portion of this URI context.
336 	 *
337 	 * <p>
338 	 * Example:  <js>"http://hostname:port/context-root/servlet-path"</js>
339 	 *
340 	 * @return
341 	 * 	The absolute URI of the resource portion of this URI context.
342 	 * 	Never <jk>null</jk>.
343 	 */
344 	public String getAbsoluteServletPath() {
345 		return aServletPath.get();
346 	}
347 
348 	/**
349 	 * Returns the parent of the URL returned by {@link #getAbsoluteServletPath()}.
350 	 *
351 	 * @return The parent of the URL returned by {@link #getAbsoluteServletPath()}.
352 	 */
353 	public String getAbsoluteServletPathParent() { return getParent(getAbsoluteServletPath()); }
354 
355 	/**
356 	 * Returns the root-relative URI of the context portion of this URI context.
357 	 *
358 	 * <p>
359 	 * Example:  <js>"/context-root"</js>
360 	 *
361 	 * @return
362 	 * 	The root-relative URI of the context portion of this URI context.
363 	 * 	Never <jk>null</jk>.
364 	 */
365 	public String getRootRelativeContextRoot() {
366 		return rContextRoot.get();
367 	}
368 
369 	/**
370 	 * Returns the root-relative URI of the path portion of this URI context.
371 	 *
372 	 * <p>
373 	 * Example:  <js>"/context-root/servlet-path/path-info"</js>
374 	 *
375 	 * @return
376 	 * 	The root-relative URI of the path portion of this URI context.
377 	 * 	Never <jk>null</jk>.
378 	 */
379 	public String getRootRelativePathInfo() {
380 		return rPath.get();
381 	}
382 
383 	/**
384 	 * Returns the parent of the URL returned by {@link #getRootRelativePathInfo()}.
385 	 *
386 	 * @return The parent of the URL returned by {@link #getRootRelativePathInfo()}.
387 	 */
388 	public String getRootRelativePathInfoParent() { return getParent(getRootRelativePathInfo()); }
389 
390 	/**
391 	 * Returns the root-relative URI of the resource portion of this URI context.
392 	 *
393 	 * <p>
394 	 * Example:  <js>"/context-root/servlet-path"</js>
395 	 *
396 	 * @return
397 	 * 	The root-relative URI of the resource portion of this URI context.
398 	 * 	Never <jk>null</jk>.
399 	 */
400 	public String getRootRelativeServletPath() {
401 		return rResource.get();
402 	}
403 
404 	/**
405 	 * Returns the parent of the URL returned by {@link #getRootRelativeServletPath()}.
406 	 *
407 	 * @return The parent of the URL returned by {@link #getRootRelativeServletPath()}.
408 	 */
409 	public String getRootRelativeServletPathParent() { return getParent(getRootRelativeServletPath()); }
410 
411 	protected FluentMap<String,Object> properties() {
412 		// @formatter:off
413 		return filteredBeanPropertyMap()
414 			.a("aContextRoot", aContextRoot.get())
415 			.a("aPathInfo", aPathInfo.get())
416 			.a("aServletPath", aServletPath.get())
417 			.a("authority", authority)
418 			.a("contextRoot", contextRoot)
419 			.a("parentPath", parentPath)
420 			.a("pathInfo", pathInfo)
421 			.a("rContextRoot", rContextRoot.get())
422 			.a("rResource", rResource.get())
423 			.a("servletPath", servletPath)
424 			.a("rPath", rPath.get());
425 		// @formatter:on
426 	}
427 
428 	@Override /* Overridden from Object */
429 	public String toString() {
430 		return r(properties());
431 	}
432 }