From 0e7eecfe348428efa9e6ab4c2cda1c83d2ca7201 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 20 Mar 2015 12:11:42 -0400 Subject: [PATCH] Add copyToUriComponentsBuilder method After this change UriComponentsBuilder#uriComponents method no longer no longer copies from the given UriComponents but rather lets the UriComponents instance copy itself to the UriComponentsBuilder. This avoids the need for instanceof checks and also makes it possible to distinguish between path and path segments, which otherwise is internal knowledge of UriComponentsBuilder. Issue: SPR-12742 --- .../web/util/HierarchicalUriComponents.java | 37 +++++++++++++++ .../web/util/OpaqueUriComponents.java | 9 +++- .../web/util/UriComponents.java | 8 +++- .../web/util/UriComponentsBuilder.java | 47 +++++-------------- .../web/util/UriComponentsBuilderTests.java | 9 ++++ .../web/util/UriComponentsTests.java | 13 ++++- 6 files changed, 86 insertions(+), 37 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index 028cdd410b..5f543823df 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -417,6 +417,19 @@ final class HierarchicalUriComponents extends UriComponents { } } + @Override + protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + builder.scheme(getScheme()); + builder.userInfo(getUserInfo()); + builder.host(getHost()); + builder.port(getPort()); + builder.replacePath(""); + this.path.copyToUriComponentsBuilder(builder); + builder.replaceQueryParams(getQueryParams()); + builder.fragment(getFragment()); + } + + @Override public boolean equals(Object obj) { if (this == obj) { @@ -608,6 +621,8 @@ final class HierarchicalUriComponents extends UriComponents { void verify(); PathComponent expand(UriTemplateVariables uriVariables); + + void copyToUriComponentsBuilder(UriComponentsBuilder builder); } @@ -651,6 +666,11 @@ final class HierarchicalUriComponents extends UriComponents { return new FullPathComponent(expandedPath); } + @Override + public void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + builder.path(getPath()); + } + @Override public boolean equals(Object obj) { return (this == obj || (obj instanceof FullPathComponent && @@ -672,6 +692,7 @@ final class HierarchicalUriComponents extends UriComponents { private final List pathSegments; public PathSegmentComponent(List pathSegments) { + Assert.notNull(pathSegments); this.pathSegments = Collections.unmodifiableList(new ArrayList(pathSegments)); } @@ -723,6 +744,11 @@ final class HierarchicalUriComponents extends UriComponents { return new PathSegmentComponent(expandedPathSegments); } + @Override + public void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + builder.pathSegment(getPathSegments().toArray(new String[getPathSegments().size()])); + } + @Override public boolean equals(Object obj) { return (this == obj || (obj instanceof PathSegmentComponent && @@ -744,6 +770,7 @@ final class HierarchicalUriComponents extends UriComponents { private final List pathComponents; public PathComponentComposite(List pathComponents) { + Assert.notNull(pathComponents); this.pathComponents = pathComponents; } @@ -789,6 +816,13 @@ final class HierarchicalUriComponents extends UriComponents { } return new PathComponentComposite(expandedComponents); } + + @Override + public void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + for (PathComponent pathComponent : this.pathComponents) { + pathComponent.copyToUriComponentsBuilder(builder); + } + } } @@ -816,6 +850,9 @@ final class HierarchicalUriComponents extends UriComponents { return this; } @Override + public void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + } + @Override public boolean equals(Object obj) { return (this == obj); } diff --git a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java index 438ec0b1b7..e71f1b30a6 100644 --- a/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/OpaqueUriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -135,6 +135,13 @@ final class OpaqueUriComponents extends UriComponents { } } + @Override + protected void copyToUriComponentsBuilder(UriComponentsBuilder builder) { + builder.scheme(getScheme()); + builder.schemeSpecificPart(getSchemeSpecificPart()); + builder.fragment(getFragment()); + } + @Override public boolean equals(Object obj) { diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java index fc747ae9f1..844267da96 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -203,6 +203,12 @@ public abstract class UriComponents implements Serializable { return toUriString(); } + /** + * Set all components of the given UriComponentsBuilder. + * @since 4.2 + */ + protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder); + // static expansion helpers diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index aec5c93170..e2251a3ece 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -447,40 +447,7 @@ public class UriComponentsBuilder implements Cloneable { */ public UriComponentsBuilder uriComponents(UriComponents uriComponents) { Assert.notNull(uriComponents, "'uriComponents' must not be null"); - this.scheme = uriComponents.getScheme(); - if (uriComponents instanceof OpaqueUriComponents) { - this.ssp = uriComponents.getSchemeSpecificPart(); - resetHierarchicalComponents(); - } - else { - if (uriComponents.getUserInfo() != null) { - this.userInfo = uriComponents.getUserInfo(); - } - if (uriComponents.getHost() != null) { - this.host = uriComponents.getHost(); - } - if (uriComponents.getPort() != -1) { - this.port = String.valueOf(uriComponents.getPort()); - } - if (StringUtils.hasLength(uriComponents.getPath())) { - List segments = uriComponents.getPathSegments(); - if (segments.isEmpty()) { - // Perhaps "/" - this.pathBuilder.addPath(uriComponents.getPath()); - } - else { - this.pathBuilder.addPathSegments(segments.toArray(new String[segments.size()])); - } - } - if (!uriComponents.getQueryParams().isEmpty()) { - this.queryParams.clear(); - this.queryParams.putAll(uriComponents.getQueryParams()); - } - resetSchemeSpecificPart(); - } - if (uriComponents.getFragment() != null) { - this.fragment = uriComponents.getFragment(); - } + uriComponents.copyToUriComponentsBuilder(this); return this; } @@ -679,6 +646,18 @@ public class UriComponentsBuilder implements Cloneable { return this; } + /** + * Set the query parameter values overriding all existing query values. + * @param params the query parameter name + * @return this UriComponentsBuilder + */ + public UriComponentsBuilder replaceQueryParams(MultiValueMap params) { + Assert.notNull(params, "'params' must not be null"); + this.queryParams.clear(); + this.queryParams.putAll(params); + return this; + } + /** * Set the URI fragment. The given fragment may contain URI template variables, * and may also be {@code null} to clear the fragment of this builder. diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index e76779366a..b5ee2eba69 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -436,6 +436,15 @@ public class UriComponentsBuilderTests { assertEquals("https://a.example.org/mvc-showcase", result.toString()); } + // SPR-12742 + + @Test + public void fromHttpRequestWithTrailingSlash() throws Exception { + UriComponents before = UriComponentsBuilder.fromPath("/foo/").build(); + UriComponents after = UriComponentsBuilder.newInstance().uriComponents(before).build(); + assertEquals("/foo/", after.getPath()); + } + @Test public void path() throws URISyntaxException { UriComponentsBuilder builder = UriComponentsBuilder.fromPath("/foo/bar"); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 54406188a9..7235609a8d 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.URI; import java.net.URISyntaxException; +import java.util.Arrays; import org.junit.Test; @@ -132,6 +133,16 @@ public class UriComponentsTests { assertThat(uriComponents.toString(), equalTo(readObject.toString())); } + @Test + public void copyToUriComponentsBuilder() { + UriComponents source = UriComponentsBuilder.fromPath("/foo/bar").pathSegment("ba/z").build(); + UriComponentsBuilder targetBuilder = UriComponentsBuilder.newInstance(); + source.copyToUriComponentsBuilder(targetBuilder); + UriComponents result = targetBuilder.build().encode(); + assertEquals("/foo/bar/ba%2Fz", result.getPath()); + assertEquals(Arrays.asList("foo", "bar", "ba%2Fz"), result.getPathSegments()); + } + @Test public void equalsHierarchicalUriComponents() throws Exception { UriComponents uriComponents1 = UriComponentsBuilder.fromUriString("http://example.com").path("/{foo}").query("bar={baz}").build();