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 028cdd410bb..5f543823dff 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 438ec0b1b72..e71f1b30a63 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 fc747ae9f13..844267da96d 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 aec5c931706..e2251a3ece4 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 e76779366a7..b5ee2eba692 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 54406188a9f..7235609a8d7 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();