diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Address.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Address.java index fc23904d2..383252740 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Address.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Address.java @@ -15,6 +15,9 @@ */ package org.ocpsoft.urlbuilder; +import java.util.List; +import java.util.Map; + /** * Represents a valid web address, or valid web address fragment. * @@ -84,7 +87,8 @@ public interface Address String getSchemeSpecificPart(); /** - * Return true if this {@link Address} has a scheme specific part section, otherwise return false. + * Return true if this {@link Address} has a scheme specific part section, otherwise return + * false. */ boolean isSchemeSpecificPartSet(); @@ -93,6 +97,11 @@ public interface Address */ String getQuery(); + /** + * Get the query parameters of this {@link Address}, or null if no query is set. + */ + Map> getQueryParameters(); + /** * Return true if this {@link Address} contains a query section, otherwise return false. */ diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilder.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilder.java index c5a15777e..5419413f5 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilder.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilder.java @@ -18,6 +18,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -27,7 +28,8 @@ import org.ocpsoft.urlbuilder.util.Encoder; /** - * Representation of a uniform resource locator, or web address. Internal state is not encoded, plain UTF-8. + * Representation of a uniform resource locator, or web address. Internal state is stored as it is originally provided, + * and must be encoded or decoded as necessary. * * @author Lincoln Baxter, III */ @@ -58,6 +60,21 @@ public static AddressBuilderBase begin() * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. */ protected Address build() + { + if (address == null) + { + address = new ParameterizedAddressResult(this); + } + return address; + } + + /** + * Generate an {@link Address} representing the current literal state of this {@link AddressBuilder}. + *

+ * (Does not apply parameterization. E.g. The URL `/{foo}` will be treated as literal text, as opposed to calling + * {@link #build()}, which would result in `foo` being treated as a parameterized expression) + */ + protected Address buildLiteral() { if (address == null) { @@ -67,8 +84,8 @@ protected Address build() } /** - * Create a new {@link Address} from the given fully encoded URL. Improperly formatted or encoded URLs are not - * parse-able and will result in an exception. + * Create a new {@link Address} from the given URL. Improperly formatted or encoded URLs are not parse-able and will + * result in an exception. No builder parameterization is possible using this method. * * @see http://en.wikipedia.org/wiki/URI_scheme * @throws IllegalArgumentException when the input URL or URL fragment is not valid. @@ -79,16 +96,18 @@ public static Address create(String url) throws IllegalArgumentException URI u = new URI(url); String scheme = u.getScheme(); String host = u.getHost(); - if(scheme != null && host == null) - return AddressBuilder.begin().scheme(u.getScheme()).schemeSpecificPart(u.getRawSchemeSpecificPart()).build(); + if (scheme != null && host == null) + return AddressBuilder.begin().scheme(u.getScheme()).schemeSpecificPart(u.getRawSchemeSpecificPart()) + .buildLiteral(); else - return AddressBuilder.begin().scheme(scheme).domain(host).port(u.getPort()) - .pathEncoded(u.getRawPath()).queryLiteral(u.getRawQuery()).anchor(u.getRawFragment()).build(); + return AddressBuilder.begin().scheme(scheme).domain(host).port(u.getPort()) + .path(u.getRawPath()).queryLiteral(u.getRawQuery()).anchor(u.getRawFragment()).buildLiteral(); } catch (URISyntaxException e) { throw new IllegalArgumentException( "[" + url + "] is not a valid URL fragment. Consider encoding relevant portions of the URL with [" - + Encoder.class + "]", e); + + Encoder.class + + "], or use the provided builder pattern via this class to specify part encoding.", e); } } @@ -130,8 +149,8 @@ AddressBuilderPort port(int port) } /** - * Set the non-encoded path section of this {@link Address}. The given value will be stored without additional - * encoding or decoding. + * Set the path section of this {@link Address}. The given value will be stored without additional encoding or + * decoding. */ AddressBuilderPath path(CharSequence path) { @@ -140,31 +159,70 @@ AddressBuilderPath path(CharSequence path) } /** - * Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored. + * Set the path section of this {@link Address}. The given value will be decoded before it is stored. */ - AddressBuilderPath pathEncoded(CharSequence path) + AddressBuilderPath pathDecoded(CharSequence path) { this.path = Decoder.path(path); return new AddressBuilderPath(this); } /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set the path section of this {@link Address}. The given value will be encoded before it is stored. + */ + AddressBuilderPath pathEncoded(CharSequence path) + { + this.path = Encoder.path(path); + return new AddressBuilderPath(this); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ AddressBuilderQuery query(CharSequence name, Object... values) { - this.queries.put(Encoder.query(name.toString()), Parameter.create(name.toString(), true, values)); + this.queries.put(name.toString(), Parameter.create(name.toString(), values)); + return new AddressBuilderQuery(this); + } + + /** + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + if (values != null) + { + List encodedValues = new ArrayList(values.length); + for (Object value : values) + { + if (value == null) + encodedValues.add(value); + else + encodedValues.add(Decoder.query(value.toString())); + } + this.queries.put(Decoder.query(name.toString()), Parameter.create(name.toString(), encodedValues)); + } return new AddressBuilderQuery(this); } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { - this.queries.put(name.toString(), Parameter.create(name.toString(), false, values)); + if (values != null) + { + List encodedValues = new ArrayList(values.length); + for (Object value : values) + { + if (value == null) + encodedValues.add(value); + else + encodedValues.add(Encoder.query(value.toString())); + } + this.queries.put(Encoder.query(name.toString()), Parameter.create(name.toString(), encodedValues)); + } return new AddressBuilderQuery(this); } @@ -224,7 +282,7 @@ AddressBuilderQuery queryLiteral(String query) } for (Entry> entry : params.entrySet()) { - queryEncoded(entry.getKey(), entry.getValue().toArray()); + query(entry.getKey(), entry.getValue().toArray()); } } return new AddressBuilderQuery(this); @@ -253,26 +311,102 @@ AddressBuilderAnchor anchor(CharSequence anchor) } /** - * Set a parameter name and value or values. Any supplied values will be encoded appropriately for their location in - * the {@link Address}. + * Set a parameter name and value or values. The supplied values will be stored without additional encoding. */ void set(CharSequence name, Object... values) { - this.parameters.put(name.toString(), Parameter.create(name.toString(), true, values)); + this.parameters.put(name.toString(), Parameter.create(name.toString(), values)); } /** - * Set a pre-encoded parameter name and value or values. The values will be stored with no additional encoding or - * decoding. + * Set a parameter name and value or values. The values will be decoded before they are stored. + */ + void setDecoded(CharSequence name, Object... values) + { + if (values != null) + { + List encodedValues = new ArrayList(values.length); + for (Object value : values) + { + if (value == null) + encodedValues.add(value); + else + encodedValues.add(Decoder.path(value.toString())); + } + this.parameters.put(name.toString(), Parameter.create(name.toString(), encodedValues)); + } + } + + /** + * Set a parameter name and value or values. The values will be encoded before they are stored. */ void setEncoded(CharSequence name, Object... values) { - this.parameters.put(name.toString(), Parameter.create(name.toString(), false, values)); + if (values != null) + { + List encodedValues = new ArrayList(values.length); + for (Object value : values) + { + if (value == null) + encodedValues.add(value); + else + encodedValues.add(Encoder.path(value.toString())); + } + this.parameters.put(name.toString(), Parameter.create(name.toString(), encodedValues)); + } } @Override public String toString() { - return build().toString(); + return buildLiteral().toString(); + } + + /** + * Package private method for {@link Address} implementations to use for rendering. + */ + static StringBuilder toString(Address address) + { + StringBuilder result = new StringBuilder(); + + if (address.isSchemeSet()) + result.append(address.getScheme()).append(":"); + + if (address.isSchemeSpecificPartSet()) + { + result.append(address.getSchemeSpecificPart()); + } + else + { + if (address.isDomainSet()) + result.append("//").append(address.getDomain()); + + if (address.isPortSet()) + result.append(":").append(address.getPort()); + + if (address.isPathSet()) + result.append(address.getPath()); + + if (address.isQuerySet()) + { + if (address.isDomainSet() && !address.isPathSet()) + result.append("/"); + result.append('?').append(address.getQuery()); + } + + if (address.isAnchorSet()) + result.append('#').append(address.getAnchor()); + } + return result; + } + + Map> getQueries() + { + Map> result = new LinkedHashMap>(); + for (Entry entry : this.queries.entrySet()) { + CharSequence key = entry.getKey(); + result.put(key == null ? null : key.toString(), entry.getValue().getValues()); + } + return Collections.unmodifiableMap(result); } } diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderAnchor.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderAnchor.java index d39655d62..03d309822 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderAnchor.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderAnchor.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderAnchor +public class AddressBuilderAnchor implements BuildableAddress { private AddressBuilder parent; @@ -29,14 +29,18 @@ public class AddressBuilderAnchor this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + @Override public String toString() { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderBase.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderBase.java index 35a2c1139..e8701ba3c 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderBase.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderBase.java @@ -18,7 +18,7 @@ /** * @author Lincoln Baxter, III */ -public class AddressBuilderBase +public class AddressBuilderBase implements BuildableAddress { private AddressBuilder parent; @@ -27,14 +27,18 @@ public class AddressBuilderBase this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + /** * Set the scheme section of this {@link Address}. */ @@ -60,8 +64,8 @@ public AddressBuilderPort port(int port) } /** - * Set the non-encoded path section of this {@link Address}. The given value will be stored without additional - * encoding or decoding. + * Set the path section of this {@link Address}. The given value will be stored without additional encoding or + * decoding. */ public AddressBuilderPath path(CharSequence path) { @@ -69,7 +73,15 @@ public AddressBuilderPath path(CharSequence path) } /** - * Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored. + * Set the path section of this {@link Address}. The given value will be decoded before it is stored. + */ + public AddressBuilderPath pathDecoded(CharSequence path) + { + return parent.pathDecoded(path); + } + + /** + * Set the path section of this {@link Address}. The given value will be encoded before it is stored. */ public AddressBuilderPath pathEncoded(CharSequence path) { @@ -77,8 +89,8 @@ public AddressBuilderPath pathEncoded(CharSequence path) } /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object... values) { @@ -86,8 +98,15 @@ public AddressBuilderQuery query(CharSequence name, Object... values) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderDomain.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderDomain.java index 52502c91d..dc10b524f 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderDomain.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderDomain.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderDomain +public class AddressBuilderDomain implements BuildableAddress { private AddressBuilder parent; @@ -29,14 +29,45 @@ public class AddressBuilderDomain this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + + /** + * Set a parameter name and value or values. The supplied values will be stored without additional encoding. + */ + public AddressBuilderDomain set(CharSequence name, Object... values) + { + parent.set(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be decoded before they are stored. + */ + public AddressBuilderDomain setDecoded(CharSequence name, Object... values) + { + parent.setDecoded(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be encoded before they are stored. + */ + public AddressBuilderDomain setEncoded(CharSequence name, Object... values) + { + parent.setEncoded(name, values); + return this; + } + /** * Set the port section of this {@link Address}. */ @@ -46,8 +77,8 @@ public AddressBuilderPort port(int port) } /** - * Set the non-encoded path section of this {@link Address}. The given value will be stored without additional - * encoding or decoding. + * Set the path section of this {@link Address}. The given value will be stored without additional encoding or + * decoding. */ public AddressBuilderPath path(CharSequence path) { @@ -55,7 +86,15 @@ public AddressBuilderPath path(CharSequence path) } /** - * Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored. + * Set the path section of this {@link Address}. The given value will be decoded before it is stored. + */ + public AddressBuilderPath pathDecoded(CharSequence path) + { + return parent.pathDecoded(path); + } + + /** + * Set the path section of this {@link Address}. The given value will be encoded before it is stored. */ public AddressBuilderPath pathEncoded(CharSequence path) { @@ -63,8 +102,8 @@ public AddressBuilderPath pathEncoded(CharSequence path) } /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object... values) { @@ -72,8 +111,15 @@ public AddressBuilderQuery query(CharSequence name, Object... values) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPath.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPath.java index 58fcc411f..a138ad47a 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPath.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPath.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderPath +public class AddressBuilderPath implements BuildableAddress { private AddressBuilder parent; @@ -29,17 +29,21 @@ public class AddressBuilderPath this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object... values) { @@ -47,8 +51,15 @@ public AddressBuilderQuery query(CharSequence name, Object... values) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { @@ -73,8 +84,7 @@ public AddressBuilderAnchor anchor(String anchor) } /** - * Set a parameter name and value or values. Any supplied values will be encoded appropriately for their location in - * the {@link Address}. + * Set a parameter name and value or values. The supplied values will be stored without additional encoding. */ public AddressBuilderPath set(CharSequence name, Object... values) { @@ -83,8 +93,16 @@ public AddressBuilderPath set(CharSequence name, Object... values) } /** - * Set a pre-encoded parameter name and value or values. The values will be stored with no additional encoding or - * decoding. + * Set a parameter name and value or values. The values will be decoded before they are stored. + */ + public AddressBuilderPath setDecoded(CharSequence name, Object... values) + { + parent.setDecoded(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be encoded before they are stored. */ public AddressBuilderPath setEncoded(CharSequence name, Object... values) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPort.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPort.java index 344a7d909..9e2cd102b 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPort.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderPort.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderPort +public class AddressBuilderPort implements BuildableAddress { private AddressBuilder parent; @@ -29,16 +29,20 @@ public class AddressBuilderPort this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + /** - * Set the non-encoded path section of this {@link Address}. The given value will be stored without additional + * Set the path section of this {@link Address}. The given value will be stored without additional * encoding or decoding. */ public AddressBuilderPath path(CharSequence path) @@ -47,16 +51,24 @@ public AddressBuilderPath path(CharSequence path) } /** - * Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored. + * Set the path section of this {@link Address}. The given value will be decoded before it is stored. + */ + public AddressBuilderPath pathDecoded(CharSequence path) + { + return parent.pathDecoded(path); + } + + /** + * Set the path section of this {@link Address}. The given value will be encoded before it is stored. */ public AddressBuilderPath pathEncoded(CharSequence path) { - return parent.pathEncoded(path); + return parent.pathDecoded(path); } /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object... values) { @@ -64,8 +76,15 @@ public AddressBuilderQuery query(CharSequence name, Object... values) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderQuery.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderQuery.java index 790cb9dc6..5833930ab 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderQuery.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderQuery.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderQuery +public class AddressBuilderQuery implements BuildableAddress { private AddressBuilder parent; @@ -29,17 +29,21 @@ public class AddressBuilderQuery this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object value) { @@ -47,8 +51,15 @@ public AddressBuilderQuery query(CharSequence name, Object value) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object value) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderScheme.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderScheme.java index 9a030184d..45d971c86 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderScheme.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderScheme.java @@ -20,7 +20,7 @@ * * @author Lincoln Baxter, III */ -public class AddressBuilderScheme +public class AddressBuilderScheme implements BuildableAddress { private AddressBuilder parent; @@ -29,14 +29,18 @@ public class AddressBuilderScheme this.parent = parent; } - /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. - */ + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + /** * Set the domain section of this {@link Address}. */ @@ -52,7 +56,34 @@ public AddressBuilderSchemeSpecificPart schemeSpecificPart(CharSequence schemeSp { return parent.schemeSpecificPart(schemeSpecificPart); } - + + /** + * Set a parameter name and value or values. The supplied values will be stored without additional encoding. + */ + public AddressBuilderScheme set(CharSequence name, Object... values) + { + parent.set(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be decoded before they are stored. + */ + public AddressBuilderScheme setDecoded(CharSequence name, Object... values) + { + parent.setDecoded(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be encoded before they are stored. + */ + public AddressBuilderScheme setEncoded(CharSequence name, Object... values) + { + parent.setEncoded(name, values); + return this; + } + /** * Set the port section of this {@link Address}. */ @@ -71,16 +102,24 @@ public AddressBuilderPath path(CharSequence path) } /** - * Set the encoded path section of this {@link Address}. The given value will be decoded before it is stored. + * Set the path section of this {@link Address}. The given value will be decoded before it is stored. + */ + public AddressBuilderPath pathDecoded(CharSequence path) + { + return parent.pathDecoded(path); + } + + /** + * Set the path section of this {@link Address}. The given value will be encoded before it is stored. */ public AddressBuilderPath pathEncoded(CharSequence path) { - return parent.pathEncoded(path); + return parent.pathDecoded(path); } /** - * Set a query-parameter to a value or multiple values. The given name and values will be encoded before they are - * stored. + * Set a query-parameter to a value or multiple values. The given name and values will be stored without additional + * encoding or decoding. */ public AddressBuilderQuery query(CharSequence name, Object... values) { @@ -88,8 +127,15 @@ public AddressBuilderQuery query(CharSequence name, Object... values) } /** - * Set a pre-encoded query-parameter to a pre-encoded value or multiple values. The given name and values be stored - * without additional encoding or decoding. + * Set a query-parameter value or multiple values. The given name and values be decoded before they are stored. + */ + public AddressBuilderQuery queryDecoded(CharSequence name, Object... values) + { + return parent.queryDecoded(name, values); + } + + /** + * Set a query-parameter to a value or multiple values. The given name and values be encoded before they are stored. */ public AddressBuilderQuery queryEncoded(CharSequence name, Object... values) { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderSchemeSpecificPart.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderSchemeSpecificPart.java index 5657a25d0..28440dc24 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderSchemeSpecificPart.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressBuilderSchemeSpecificPart.java @@ -20,7 +20,7 @@ * * @author Fabien Marsaud */ -public class AddressBuilderSchemeSpecificPart +public class AddressBuilderSchemeSpecificPart implements BuildableAddress { private AddressBuilder parent; @@ -30,13 +30,44 @@ public class AddressBuilderSchemeSpecificPart } /** - * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. + * Set a parameter name and value or values. The supplied values will be stored without additional encoding. */ + public AddressBuilderSchemeSpecificPart set(CharSequence name, Object... values) + { + parent.set(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be decoded before they are stored. + */ + public AddressBuilderSchemeSpecificPart setDecoded(CharSequence name, Object... values) + { + parent.setDecoded(name, values); + return this; + } + + /** + * Set a parameter name and value or values. The values will be encoded before they are stored. + */ + public AddressBuilderSchemeSpecificPart setEncoded(CharSequence name, Object... values) + { + parent.setEncoded(name, values); + return this; + } + + @Override public Address build() { return parent.build(); } + @Override + public Address buildLiteral() + { + return parent.buildLiteral(); + } + @Override public String toString() { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressResult.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressResult.java index 60af7ff20..8b3ed30e7 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressResult.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/AddressResult.java @@ -15,13 +15,10 @@ */ package org.ocpsoft.urlbuilder; +import java.util.Collections; +import java.util.List; import java.util.Map; -import org.ocpsoft.urlbuilder.util.CaptureType; -import org.ocpsoft.urlbuilder.util.CapturingGroup; -import org.ocpsoft.urlbuilder.util.Encoder; -import org.ocpsoft.urlbuilder.util.ParseTools; - /** * Implementation of {@link Address} created by {@link AddressBuilder}. * @@ -37,21 +34,22 @@ class AddressResult implements Address private final String query; private final String anchor; private CharSequence result; + private Map> queries = Collections.emptyMap(); public AddressResult(AddressBuilder parent) { if (isSet(parent.scheme)) - protocol = parameterize(parent.parameters, parent.scheme).toString(); + protocol = parent.scheme.toString(); else protocol = null; if (isSet(parent.schemeSpecificPart)) - schemeSpecificPart = parameterize(parent.parameters, parent.schemeSpecificPart, false).toString(); + schemeSpecificPart = parent.schemeSpecificPart.toString(); else schemeSpecificPart = null; if (isSet(parent.domain)) - host = parameterize(parent.parameters, parent.domain).toString(); + host = parent.domain.toString(); else host = null; @@ -62,7 +60,7 @@ public AddressResult(AddressBuilder parent) if (isSet(parent.path)) { - CharSequence path = parameterize(parent.parameters, parent.path); + CharSequence path = parent.path; if (path.charAt(0) != '/') path = new StringBuilder('/').append(path); this.path = path.toString(); @@ -70,13 +68,15 @@ public AddressResult(AddressBuilder parent) else path = null; - if (isSet(parent.queries)) + if (isSet(parent.queries)) { + this.queries = Collections.unmodifiableMap(parent.getQueries()); query = toQuery(parent.queries).toString(); + } else query = null; if (isSetOrEmpty(parent.anchor)) - anchor = parameterize(parent.parameters, parent.anchor).toString(); + anchor = parent.anchor.toString(); else anchor = null; } @@ -98,7 +98,7 @@ private CharSequence toQuery(Map queries) if (parameter.getValueCount() > 0) { for (int i = 0; i < parameter.getValueCount(); i++) { - String value = parameter.getValueAsQueryParam(i); + String value = parameter.getValue(i); if (value != null) result.append('=').append(value); @@ -118,95 +118,13 @@ public String toString() { if (this.result == null) { - StringBuilder result = new StringBuilder(); - - if (isSchemeSet()) - result.append(getScheme()).append(":"); - - if (isSchemeSpecificPartSet()) - { - result.append(getSchemeSpecificPart()); - } - else - { - if (isDomainSet()) - result.append("//").append(getDomain()); - - if (isPortSet()) - result.append(":").append(getPort()); - - if (isPathSet()) - result.append(getPath()); - - if (isQuerySet()) - result.append('?').append(getQuery()); - - if (isAnchorSet()) - result.append('#').append(getAnchor()); - } - + StringBuilder result = AddressBuilder.toString(this); this.result = result; } return this.result.toString(); } - private CharSequence parameterize(Map parameters, CharSequence sequence) - { - return parameterize(parameters, sequence, true); - } - - private CharSequence parameterize(Map parameters, CharSequence sequence, boolean encodeSequence) - { - StringBuilder result = new StringBuilder(); - int cursor = 0; - int lastEnd = 0; - while (cursor < sequence.length()) - { - switch (sequence.charAt(cursor)) - { - case '{': - CharSequence subSequence = sequence.subSequence(lastEnd, cursor); - if(encodeSequence) - subSequence = Encoder.path(subSequence); - - result.append(subSequence); - - int startPos = cursor; - CapturingGroup group = ParseTools.balancedCapture(sequence, startPos, sequence.length() - 1, - CaptureType.BRACE); - cursor = group.getEnd(); - lastEnd = group.getEnd() + 1; - - String name = group.getCaptured().toString(); - - Parameter parameter = parameters.get(name); - if (parameter == null || !parameter.hasValues()) - throw new IllegalStateException("No parameter [" + name + "] was set in the pattern [" + sequence - + "]. Call address.set(\"" + name + "\", value); or remove the parameter from the pattern."); - - result.append(parameter.getValueAsPathParam(0)); - - break; - - default: - break; - } - - cursor++; - } - - if (cursor >= lastEnd) - { - CharSequence subSequence = sequence.subSequence(lastEnd, cursor); - if(encodeSequence) - subSequence = Encoder.path(subSequence); - - result.append(subSequence); - } - return result; - } - private boolean isSet(Integer port) { return port != null; @@ -307,19 +225,25 @@ public String getSchemeSpecificPart() { return schemeSpecificPart; } - + @Override public boolean isSchemeSpecificPartSet() { return isSet(schemeSpecificPart); } - + @Override public String getQuery() { return query; } + @Override + public Map> getQueryParameters() + { + return queries; + } + @Override public boolean isQuerySet() { diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/BuildableAddress.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/BuildableAddress.java new file mode 100644 index 000000000..40acf0177 --- /dev/null +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/BuildableAddress.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates. + * + * Licensed under the Eclipse Public License version 1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package org.ocpsoft.urlbuilder; + +/** + * Represents an object that can build and return an {@link Address} as a result. + * + * @author Lincoln Baxter, III + */ +public interface BuildableAddress +{ + /** + * Generate an {@link Address} representing the current state of this {@link AddressBuilder}. + */ + Address build(); + + /** + * Generate an {@link Address} representing the current literal state of this {@link AddressBuilder}. + *

+ * (Does not apply parameterization. E.g. The URL `/{foo}` will be treated as literal text, as opposed to calling + * {@link #build()}, which would result in `foo` being treated as a parameterized expression) + */ + public Address buildLiteral(); +} diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Parameter.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Parameter.java index 57723e5f5..827280ed4 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Parameter.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/Parameter.java @@ -18,35 +18,31 @@ import java.util.Arrays; import java.util.List; -import org.ocpsoft.urlbuilder.util.Encoder; - /** - * Internal state object used in {@link AddressBuilder} to perform parameterization of {@link String} based values. + * Immutable internal state object used in {@link AddressBuilder} to perform parameterization of {@link String} based + * values. * * @author Lincoln Baxter, III */ class Parameter { - private final CharSequence name; private final List values; - private final boolean encode; - private Parameter(CharSequence name, boolean encode, List values) + private Parameter(CharSequence name, List values) { this.name = name; - this.encode = encode; this.values = values; } - public static Parameter create(CharSequence name, Object... values) + public static Parameter create(CharSequence name, List values) { - return new Parameter(name, true, Arrays.asList(values)); + return new Parameter(name, values); } - public static Parameter create(CharSequence name, boolean encode, Object... values) + public static Parameter create(CharSequence name, Object... values) { - return new Parameter(name, encode, Arrays.asList(values)); + return new Parameter(name, Arrays.asList(values)); } public CharSequence getName() @@ -54,11 +50,6 @@ public CharSequence getName() return name; } - public boolean isEncode() - { - return encode; - } - public boolean hasValues() { return !values.isEmpty(); @@ -69,25 +60,15 @@ public int getValueCount() return values.size(); } - public String getValueAsPathParam(int index) + public String getValue(int index) { - if (encode) { - return Encoder.path(values.get(index).toString()); - } - else { - return values.get(index).toString(); - } + Object value = values.get(index); + return value == null ? null : value.toString(); } - public String getValueAsQueryParam(int index) + public List getValues() { - Object value = values.get(index); - if (encode) { - return Encoder.query(value == null ? null : value.toString()); - } - else { - return value == null ? null : value.toString(); - } + return values; } } diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/ParameterizedAddressResult.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/ParameterizedAddressResult.java new file mode 100644 index 000000000..b561a568b --- /dev/null +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/ParameterizedAddressResult.java @@ -0,0 +1,301 @@ +/* + * Copyright 2013 Lincoln Baxter, III + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.ocpsoft.urlbuilder; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.ocpsoft.urlbuilder.util.CaptureType; +import org.ocpsoft.urlbuilder.util.CapturingGroup; +import org.ocpsoft.urlbuilder.util.ParseTools; + +/** + * Parameterized implementation of {@link Address} created by {@link AddressBuilder}. (Applies parameterization to all + * parts of the URL.) + * + * @author Lincoln Baxter, III + */ +class ParameterizedAddressResult implements Address +{ + private final String protocol; + private final String schemeSpecificPart; + private final String host; + private final Integer port; + private final String path; + private final String query; + private final String anchor; + private CharSequence result; + private Map> queries = Collections.emptyMap(); + + public ParameterizedAddressResult(AddressBuilder parent) + { + if (isSet(parent.scheme)) + protocol = parameterize(parent.parameters, parent.scheme).toString(); + else + protocol = null; + + if (isSet(parent.schemeSpecificPart)) + schemeSpecificPart = parameterize(parent.parameters, parent.schemeSpecificPart).toString(); + else + schemeSpecificPart = null; + + if (isSet(parent.domain)) + host = parameterize(parent.parameters, parent.domain).toString(); + else + host = null; + + if (isSet(parent.port)) + port = parent.port; + else + port = null; + + if (isSet(parent.path)) + { + CharSequence path = parameterize(parent.parameters, parent.path); + if (path.charAt(0) != '/') + path = new StringBuilder('/').append(path); + this.path = path.toString(); + } + else + path = null; + + if (isSet(parent.queries)) + { + this.queries = Collections.unmodifiableMap(parent.getQueries()); + query = toQuery(parent.queries).toString(); + } + else + query = null; + + if (isSetOrEmpty(parent.anchor)) + anchor = parameterize(parent.parameters, parent.anchor).toString(); + else + anchor = null; + } + + private CharSequence toQuery(Map queries) + { + StringBuilder result = new StringBuilder(); + boolean first = true; + for (CharSequence name : queries.keySet()) { + Parameter parameter = queries.get(name); + + if (!first) + result.append('&'); + else + first = false; + + result.append(name); + + if (parameter.getValueCount() > 0) + { + for (int i = 0; i < parameter.getValueCount(); i++) { + String value = parameter.getValue(i); + + if (value != null) + result.append('=').append(value); + + if (i < parameter.getValueCount() - 1) + { + result.append('&').append(name); + } + } + } + } + return result; + } + + @Override + public String toString() + { + if (this.result == null) + { + StringBuilder result = AddressBuilder.toString(this); + this.result = result; + } + + return this.result.toString(); + } + + private CharSequence parameterize(Map parameters, CharSequence sequence) + { + StringBuilder result = new StringBuilder(); + int cursor = 0; + int lastEnd = 0; + while (cursor < sequence.length()) + { + switch (sequence.charAt(cursor)) + { + case '{': + result.append(sequence.subSequence(lastEnd, cursor)); + + int startPos = cursor; + CapturingGroup group = ParseTools.balancedCapture(sequence, startPos, sequence.length() - 1, + CaptureType.BRACE); + cursor = group.getEnd(); + lastEnd = group.getEnd() + 1; + + String name = group.getCaptured().toString(); + + Parameter parameter = parameters.get(name); + if (parameter == null || !parameter.hasValues()) + throw new IllegalStateException("No parameter [" + name + "] was set in the pattern [" + sequence + + "]. Call address.set(\"" + name + "\", value); or remove the parameter from the pattern."); + + result.append(parameter.getValue(0)); + + break; + + default: + break; + } + + cursor++; + } + + if (cursor >= lastEnd) + { + result.append(sequence.subSequence(lastEnd, cursor)); + } + return result; + } + + private boolean isSet(Integer port) + { + return port != null; + } + + private boolean isSet(Map map) + { + return map != null && !map.isEmpty(); + } + + private boolean isSet(CharSequence value) + { + return value != null && value.length() > 0; + } + + private boolean isSetOrEmpty(CharSequence value) + { + return value != null; + } + + /* + * Inspectors + */ + + @Override + public String getAnchor() + { + return anchor; + } + + @Override + public boolean isAnchorSet() + { + return isSetOrEmpty(anchor); + } + + @Override + public String getPath() + { + return path; + } + + @Override + public String getPathAndQuery() + { + StringBuilder result = new StringBuilder(); + if (isPathSet()) + result.append(getPath()); + if (isQuerySet()) + result.append('?').append(getQuery()); + return result.toString(); + } + + @Override + public boolean isPathSet() + { + return isSet(path); + } + + @Override + public Integer getPort() + { + return port; + } + + @Override + public boolean isPortSet() + { + return isSet(port); + } + + @Override + public String getDomain() + { + return host; + } + + @Override + public boolean isDomainSet() + { + return isSet(host); + } + + @Override + public String getScheme() + { + return protocol; + } + + @Override + public boolean isSchemeSet() + { + return isSet(protocol); + } + + @Override + public String getSchemeSpecificPart() + { + return schemeSpecificPart; + } + + @Override + public boolean isSchemeSpecificPartSet() + { + return isSet(schemeSpecificPart); + } + + @Override + public String getQuery() + { + return query; + } + + @Override + public Map> getQueryParameters() + { + return queries; + } + + @Override + public boolean isQuerySet() + { + return isSet(query); + } +} diff --git a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/util/Encoder.java b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/util/Encoder.java index 2411bee47..86c2f28dd 100644 --- a/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/util/Encoder.java +++ b/addressbuilder/src/main/java/org/ocpsoft/urlbuilder/util/Encoder.java @@ -6,15 +6,22 @@ import java.net.URLEncoder; import java.nio.charset.Charset; +/** + * Utility class to encode URL path and query parts. + * + * @author Lincoln Baxter, III + */ public class Encoder { private static final Charset UTF8 = Charset.forName("UTF-8"); /** - * Encodes the given string as described in RFC 2396 + * Encodes the given string using HTML form encoding as described in RFC 2396. + * + * @throws IllegalArgumentException when illegal URI syntax is attempted. */ - public static String path(CharSequence s) + public static String path(CharSequence s) throws IllegalArgumentException { try { @@ -28,15 +35,17 @@ public static String path(CharSequence s) } /** - * Encodes the given string using HTML form encoding + * Encodes the given string using HTML form encoding as described in RFC 2396. + * + * @throws IllegalArgumentException when illegal URI syntax is attempted. */ - public static String query(CharSequence s) + public static String query(CharSequence s) throws IllegalArgumentException { try { return URLEncoder.encode(s.toString(), UTF8.name()); } catch (UnsupportedEncodingException e) { - throw new IllegalStateException(e); + throw new IllegalArgumentException(e); } } diff --git a/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderEncodingTest.java b/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderEncodingTest.java index 7d9a34dd8..1478bff63 100644 --- a/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderEncodingTest.java +++ b/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderEncodingTest.java @@ -25,7 +25,7 @@ public void testCreateImproperlyEncodedQuery() @Test public void testPathEncoded() { - Assert.assertEquals("foo%20bar", AddressBuilder.begin().pathEncoded("foo%20bar").build().toString()); + Assert.assertEquals("foo%20bar", AddressBuilder.begin().path("foo%20bar").build().toString()); } @Test @@ -50,15 +50,29 @@ public void testCreateUnencodedAmpersandInQuery() throws Exception } @Test - public void testParameterEncoding() + public void testParameterEncodingDomainWithQuery() + { + Assert.assertEquals("http://a%20b/?q=a+b", + AddressBuilder.begin() + .scheme("http") + .domain("{p}") + .setEncoded("p", "a b") + .queryEncoded("q", "a b") + .build() + .toString()); + } + + @Test + public void testParameterEncodingPathWithQuery() { Assert.assertEquals("http://localhost/a%20b?q=a+b", AddressBuilder.begin() .scheme("http") .domain("localhost") .path("/{p}") - .set("p", "a b") - .query("q", "a b") + .setEncoded("p", "a b") + .queryEncoded("q", "a b") + .build() .toString()); } @@ -70,8 +84,8 @@ public void testParameterEncodingResult() .scheme("http") .domain("localhost") .path("/{p}") - .set("p", "a b") - .query("q", "a b") + .setEncoded("p", "a b") + .queryEncoded("q", "a b") .build() .toString()); } @@ -84,21 +98,27 @@ public void testParametersWithoutEncoding() .scheme("http") .domain("localhost") .path("/{p}") - .setEncoded("p", "a%20b") - .queryEncoded("q", "a+b") + .set("p", "a%20b") + .query("q", "a+b") + .build() .toString()); } @Test public void testParametersWithoutEncodingResult() { - Assert.assertEquals("http://localhost/a%20b?q=a+b", + /* + * This is actually an erroneous resultant URL because the space ' ' character should be encoded, + * but since we are just testing behavior of the builder, this is fine. + * Just don't use this as a "good example". + */ + Assert.assertEquals("http://localhost/a b?q=a b", AddressBuilder.begin() .scheme("http") .domain("localhost") .path("/{p}") - .setEncoded("p", "a%20b") - .queryEncoded("q", "a+b") + .set("p", "a b") + .query("q", "a b") .build() .toString()); } @@ -107,27 +127,27 @@ public void testParametersWithoutEncodingResult() public void testBuildQueryWithAmpersandInName() { Assert.assertEquals("?q%26q=200", - AddressBuilder.begin().query("q&q", 200).toString()); + AddressBuilder.begin().queryEncoded("q&q", 200).toString()); } @Test public void testBuildQueryWithAmpersandInValue() { Assert.assertEquals("?q=%26200", - AddressBuilder.begin().query("q", "&200").toString()); + AddressBuilder.begin().queryEncoded("q", "&200").toString()); } @Test public void testBuildQueryWithQuestionMarkInName() { Assert.assertEquals("??q=200", - AddressBuilder.begin().queryEncoded("?q=200").toString()); + AddressBuilder.begin().query("?q=200").toString()); } @Test public void testBuildQueryWithQuestionMarkInValue() { Assert.assertEquals("?q=?200", - AddressBuilder.begin().queryEncoded("q", "?200").toString()); + AddressBuilder.begin().query("q", "?200").toString()); } } diff --git a/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderTest.java b/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderTest.java index 90885dbab..88360ea85 100644 --- a/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderTest.java +++ b/addressbuilder/src/test/java/org/ocpsoft/urlbuilder/AddressBuilderTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertEquals; import org.junit.Assert; -import org.junit.Ignore; import org.junit.Test; public class AddressBuilderTest @@ -21,8 +20,9 @@ public void testBuildEverything() .path("/{s}/{t}") .set("s", "search") .set("t", "table") - .query("q", "query string") + .queryEncoded("q", "query string") .anchor("foo") + .build() .toString()); } @@ -38,7 +38,7 @@ public void testBuildEverythingResult() .path("/{s}/{t}") .set("s", "search") .set("t", "table") - .query("q", "query string") + .queryEncoded("q", "query string") .anchor("foo") .build() .toString()); @@ -146,7 +146,7 @@ public void testBuildPathSimpleResult() public void testBuildPathWithOneParameter() { Assert.assertEquals("/store/23", - AddressBuilder.begin().path("/store/{item}").set("item", 23).toString()); + AddressBuilder.begin().path("/store/{item}").set("item", 23).build().toString()); } @Test @@ -160,7 +160,8 @@ public void testBuildPathWithOneParameterResult() public void testBuildPathWithParameters() { Assert.assertEquals("/store/23/buy", - AddressBuilder.begin().path("/store/{item}/{action}").set("item", 23).set("action", "buy").toString()); + AddressBuilder.begin().path("/store/{item}/{action}").set("item", 23).set("action", "buy").build() + .toString()); } @Test @@ -177,7 +178,16 @@ public void testBuildHostAndPath() Assert.assertEquals("//ocpsoft.org/store/23/buy", AddressBuilder.begin() .domain("ocpsoft.org") - .path("/store/{item}/{action}").set("item", 23).set("action", "buy").toString()); + .path("/store/{item}/{action}").set("item", 23).set("action", "buy").build().toString()); + } + + @Test + public void testBuildHostAndQuery() + { + Assert.assertEquals("//ocpsoft.org/?buy=23", + AddressBuilder.begin() + .domain("ocpsoft.org") + .query("buy", "23").build().toString()); } @Test @@ -238,6 +248,18 @@ public void testFromStringOnlyWithPathAndQuery() assertEquals("/search?q=foobar", address.getPathAndQuery()); } + @Test + public void testFromStringOnlyWithPathAndQuery2() + { + Address address = AddressBuilder.create("search?q=foobar"); + assertEquals(null, address.getScheme()); + assertEquals(null, address.getDomain()); + assertEquals(null, address.getPort()); + assertEquals("search", address.getPath()); + assertEquals("q=foobar", address.getQuery()); + assertEquals("search?q=foobar", address.getPathAndQuery()); + } + @Test public void testCreateSchemalessUrl() { @@ -257,7 +279,7 @@ public void testBuildSchemeSpecificPart() { Assert.assertEquals("mailto:contact@ocpsoft.org?subject=Howdy Lincoln!", AddressBuilder.begin() - .scheme("mailto") + .scheme("mailto") .schemeSpecificPart("contact@ocpsoft.org?subject=Howdy Lincoln!") .toString()); } @@ -267,9 +289,9 @@ public void testBuildSchemeSpecificPartResult() { Assert.assertEquals("mailto:contact@ocpsoft.org?subject=Howdy Lincoln!", AddressBuilder.begin() - .scheme("mailto") - .schemeSpecificPart("contact@ocpsoft.org?subject=Howdy Lincoln!") - .build().toString()); + .scheme("mailto") + .schemeSpecificPart("contact@ocpsoft.org?subject=Howdy Lincoln!") + .build().toString()); } @Test @@ -285,11 +307,11 @@ public void testEmptyAnchorOnly() } @Test - @Ignore // see: https://github.com/ocpsoft/rewrite/issues/195 public void shouldCreateAddressFromUrlWithCurlyBrace() { Address address = AddressBuilder.create("http://localhost/somepath/%7Bsomething%7D"); - assertEquals("/somepath/{something}", address.getPath()); + assertEquals("/somepath/%7Bsomething%7D", address.getPath()); + assertEquals("http://localhost/somepath/%7Bsomething%7D", address.toString()); } } diff --git a/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/http/event/HttpServletRewrite.java b/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/http/event/HttpServletRewrite.java index 9b4d72207..aed545dfc 100644 --- a/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/http/event/HttpServletRewrite.java +++ b/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/http/event/HttpServletRewrite.java @@ -34,7 +34,8 @@ public interface HttpServletRewrite extends public String getContextPath(); /** - * Get the full {@link Address} of the current request. + * Get the full {@link Address} of the current request. This is the original URL of the request and has not been + * encoded or decoded. */ Address getInboundAddress(); diff --git a/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/util/URLBuilder.java b/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/util/URLBuilder.java index fe2d63700..5db8a6a11 100644 --- a/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/util/URLBuilder.java +++ b/api-servlet/src/main/java/org/ocpsoft/rewrite/servlet/util/URLBuilder.java @@ -24,6 +24,8 @@ import org.ocpsoft.common.util.Strings; import org.ocpsoft.urlbuilder.AddressBuilder; +import org.ocpsoft.urlbuilder.util.Decoder; +import org.ocpsoft.urlbuilder.util.Encoder; /** * Utility for building URL strings. Also manages the URL query string with the help of {@link QueryStringBuilder}. @@ -170,26 +172,6 @@ public URLBuilder decode() return new URLBuilder(getDecodedSegments(), metadata, query.decode()); } - /** - * Decodes a segment using the {@link URI} class. - * - * @param segment The segment to decode - * @return the decoded segment - */ - private String decodeSegment(final String segment) - { - try - { - String prepared = ("http://localhost/" + segment); - final URI uri = new URI(prepared); - return uri.getPath().substring(1); - } - catch (URISyntaxException e) - { - throw new IllegalArgumentException(e); - } - } - /** * Return this {@link URLBuilder} after path segments and query parameters have been encoded. */ @@ -198,25 +180,6 @@ public URLBuilder encode() return new URLBuilder(getEncodedSegments(), metadata, query.encode()); } - /** - * Encodes a segment using the {@link URI} class. - * - * @param segment The segment to encode - * @return the encoded segment - */ - private String encodeSegment(final String segment) - { - try - { - final URI uri = new URI("http", "localhost", "/" + segment, null); - return uri.toASCIIString().substring(17); - } - catch (URISyntaxException e) - { - throw new IllegalArgumentException(e); - } - } - private List getDecodedSegments() { /* @@ -224,7 +187,7 @@ private List getDecodedSegments() */ List result = new ArrayList(); for (int i = 0; i < segments.size(); i++) { - result.add(decodeSegment(segments.get(i))); + result.add(Decoder.path(segments.get(i))); } return result; } @@ -236,7 +199,7 @@ private List getEncodedSegments() */ List result = new ArrayList(); for (int i = 0; i < segments.size(); i++) { - result.add(encodeSegment(segments.get(i))); + result.add(Encoder.path(segments.get(i))); } return result; } diff --git a/config-prettyfaces-tests/src/test/java/org/ocpsoft/rewrite/prettyfaces/encoding/URLEncodingTest.java b/config-prettyfaces-tests/src/test/java/org/ocpsoft/rewrite/prettyfaces/encoding/URLEncodingTest.java index a5eb9d17b..80eb85741 100644 --- a/config-prettyfaces-tests/src/test/java/org/ocpsoft/rewrite/prettyfaces/encoding/URLEncodingTest.java +++ b/config-prettyfaces-tests/src/test/java/org/ocpsoft/rewrite/prettyfaces/encoding/URLEncodingTest.java @@ -16,7 +16,6 @@ */ package org.ocpsoft.rewrite.prettyfaces.encoding; - import org.apache.http.client.methods.HttpGet; import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.drone.api.annotation.Drone; @@ -123,14 +122,35 @@ public void testURLDecoding() throws Exception Assert.assertTrue(browser.getPageSource().contains("beanQueryText=Fooo Bar")); } + @Test + public void testURLDecodingWithPoundSignEncoded() throws Exception + { + browser.get(getBaseURL() + getContextPath() + "/encoding/V%23r?dis=gt%23%232206"); + String pageSource = browser.getPageSource(); + Assert.assertTrue(pageSource.contains("/encoding/V%23r?dis=gt%23%232206")); + Assert.assertTrue(pageSource.contains("beanPathText=V#r")); + Assert.assertTrue(pageSource.contains("beanQueryText=gt##2206")); + } + + @Test + public void testURLDecodingWithPoundSign() throws Exception + { + browser.get(getBaseURL() + getContextPath() + "/encoding/V%23r?dis=gt##2206"); + String pageSource = browser.getPageSource(); + Assert.assertTrue(pageSource.contains("/encoding/V%23r?dis=gt")); + Assert.assertTrue(pageSource.contains("beanPathText=V#r")); + Assert.assertTrue(pageSource.contains("beanQueryText=gt")); + } + @Test public void testQueryDecoding() throws Exception { HttpAction action = get("/encoding/Vračar?dis=Fooo%20Bar"); Assert.assertTrue(action.getCurrentURL().endsWith("/encoding/Vračar?dis=Fooo%20Bar")); - Assert.assertTrue(action.getResponseContent().contains("/encoding/Vra%C4%8Dar?dis=Fooo+Bar")); - Assert.assertTrue(action.getResponseContent().contains("beanQueryText=Fooo Bar")); + String responseContent = action.getResponseContent(); + Assert.assertTrue(responseContent.contains("/encoding/Vra%C4%8Dar?dis=Fooo+Bar")); + Assert.assertTrue(responseContent.contains("beanQueryText=Fooo Bar")); } @Test @@ -141,7 +161,6 @@ public void testEncodedPathDecoding() throws Exception Assert.assertTrue(action.getCurrentURL().endsWith("/encoding/Vračar?dis=Fooo%20Bar")); Assert.assertTrue(action.getResponseContent().contains("/encoding/Vra%C4%8Dar?dis=Fooo+Bar")); Assert.assertTrue(action.getResponseContent().contains("beanPathText=Vračar")); - Assert.assertTrue(action.getResponseContent().contains("beanQueryText=Fooo Bar")); } @Test @@ -178,4 +197,18 @@ public void testNoDecodeOnSubmitDoesNotCrash() throws Exception browser.findElement(By.id("submit")).click(); Assert.assertTrue(browser.getPageSource().contains("viewId=/encoding.xhtml")); } + + @Test + public void testBracesAndBracketsInURL() throws Exception + { + browser.get(getBaseURL() + getContextPath() + "/basic/[]{}"); + Assert.assertNotNull(browser.findElement(By.id("form"))); + } + + @Test + public void testBracesAndBracketsInURLEncoded() throws Exception + { + browser.get(getBaseURL() + getContextPath() + "/basic/%5B%5D%7B%7D"); + Assert.assertNotNull(browser.findElement(By.id("form"))); + } } diff --git a/config-prettyfaces-tests/src/test/resources/encoding/encoding-pretty-config.xml b/config-prettyfaces-tests/src/test/resources/encoding/encoding-pretty-config.xml index f3368d599..f1c303b3b 100644 --- a/config-prettyfaces-tests/src/test/resources/encoding/encoding-pretty-config.xml +++ b/config-prettyfaces-tests/src/test/resources/encoding/encoding-pretty-config.xml @@ -41,5 +41,9 @@ + + + + \ No newline at end of file diff --git a/config-prettyfaces/src/main/java/com/ocpsoft/pretty/faces/url/URL.java b/config-prettyfaces/src/main/java/com/ocpsoft/pretty/faces/url/URL.java index 186763139..2f9210928 100644 --- a/config-prettyfaces/src/main/java/com/ocpsoft/pretty/faces/url/URL.java +++ b/config-prettyfaces/src/main/java/com/ocpsoft/pretty/faces/url/URL.java @@ -15,8 +15,6 @@ */ package com.ocpsoft.pretty.faces.url; -import java.net.URI; -import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -24,8 +22,16 @@ import java.util.List; import java.util.Map; +import org.ocpsoft.urlbuilder.util.Decoder; +import org.ocpsoft.urlbuilder.util.Encoder; + import com.ocpsoft.pretty.faces.util.StringUtils; +/** + * Utility class providing common functionality required when interacting with and asking questions of URLs. + * + * @author Lincoln Baxter, III + */ public class URL { private Metadata metadata = new Metadata(); @@ -96,7 +102,7 @@ public List getDecodedSegments() List result = new ArrayList(); for (String segment : segments) { - result.add(decodeSegment(segment)); + result.add(Decoder.path(segment)); } decodedSegments.put(encoding, Collections.unmodifiableList(result)); } @@ -111,65 +117,11 @@ public List getEncodedSegments() List resultSegments = new ArrayList(); for (String segment : segments) { - resultSegments.add(encodeSegment(segment)); + resultSegments.add(Encoder.path(segment)); } return resultSegments; } - /** - * Encodes a segment using the {@link URI} class. - * - * @param segment The segment to encode - * @return the encoded segment - */ - private static String encodeSegment(final String segment) - { - try - { - final URI uri = new URI("http", "localhost", "/" + segment, null); - return uri.toASCIIString().substring(17); - } - catch (URISyntaxException e) - { - throw new IllegalArgumentException(e); - } - } - - /** - * Decodes a segment using the {@link URI} class. - * - * @param segment The segment to decode - * @return the decoded segment - */ - private static String decodeSegment(final String segment) - { - try - { - - /* - * Note: The replacement allows to call decode() on an already decoded URL - * and supports decoding of strings that contain not encoded characters. - * IHMO this is only needed because there seem to be situation in PrettyFaces - * where decoded URLs are decoded again. - */ - - final URI uri = new URI(("http://localhost/" + segment) - .replace(" ", "%20") - .replace("\"", "%22") - .replace("[", "%5B") - .replace("]", "%5D") - .replace("<", "%3C") - .replace(">", "%3E") - .replace("|", "%7C") - ); - return uri.getPath().substring(1); - } - catch (URISyntaxException e) - { - throw new IllegalArgumentException(e); - } - } - /** * Return a decoded form of this URL. */ diff --git a/config-prettyfaces/src/main/java/org/ocpsoft/rewrite/prettyfaces/UrlMappingRuleAdaptor.java b/config-prettyfaces/src/main/java/org/ocpsoft/rewrite/prettyfaces/UrlMappingRuleAdaptor.java index 161a57d34..b58a48cc4 100644 --- a/config-prettyfaces/src/main/java/org/ocpsoft/rewrite/prettyfaces/UrlMappingRuleAdaptor.java +++ b/config-prettyfaces/src/main/java/org/ocpsoft/rewrite/prettyfaces/UrlMappingRuleAdaptor.java @@ -121,7 +121,8 @@ private String rewritePrettyMappings(final PrettyConfig config, final String con return result; } - private boolean evaluateOutbound(String outboundURL) { + private boolean evaluateOutbound(String outboundURL) + { QueryString outboundQueryString = new QueryString(); if (outboundURL.contains("?")) { outboundQueryString.addParameters(outboundURL); @@ -169,7 +170,7 @@ private boolean evaluateOutbound(String outboundURL) { for (String outboundParamValue : outboundParam.getValue()) { if ((mappingViewParamValue == outboundParamValue) - || (mappingViewParamValue != null && mappingViewParamValue.equals(outboundParamValue))) + || (mappingViewParamValue != null && mappingViewParamValue.equals(outboundParamValue))) { found = true; break; @@ -217,11 +218,12 @@ else if ((event instanceof HttpOutboundServletRewrite) @Override public void perform(final Rewrite event, final EvaluationContext ec) { - PrettyContext context = PrettyContext.getCurrentInstance(((HttpServletRewrite) event).getRequest()); + HttpServletRewrite httpServletRewrite = (HttpServletRewrite) event; + PrettyContext context = PrettyContext.getCurrentInstance(httpServletRewrite.getRequest()); if (event instanceof HttpInboundServletRewrite) { - ((HttpServletRewrite) event).getRequest().setAttribute(REWRITE_MAPPING_ID_KEY, + httpServletRewrite.getRequest().setAttribute(REWRITE_MAPPING_ID_KEY, REWRITE_MAPPING_ID_KEY + ":" + mapping.getId()); URL url = context.getRequestURL(); @@ -234,9 +236,9 @@ public void perform(final Rewrite event, final EvaluationContext ec) { String viewId = mapping.getViewId(); log.trace("Forwarding mapped request [" + url.toURL() + "] to resource [" + viewId + "]"); - if (url.decode().toURL().equals(viewId)) + if (url.toURL().equals(viewId)) { - ((HttpServletRewrite) event).proceed(); + httpServletRewrite.proceed(); } else { @@ -247,7 +249,7 @@ public void perform(final Rewrite event, final EvaluationContext ec) else if ((event instanceof HttpOutboundServletRewrite) && mapping.isOutbound()) { HttpOutboundServletRewrite outboundRewrite = (HttpOutboundServletRewrite) event; - String newUrl = rewritePrettyMappings(context.getConfig(), ((HttpServletRewrite) event).getContextPath(), + String newUrl = rewritePrettyMappings(context.getConfig(), httpServletRewrite.getContextPath(), outboundRewrite.getOutboundAddress().toString()); outboundRewrite.setOutboundAddress(AddressBuilder.create(newUrl)); } diff --git a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/Path.java b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/Path.java index e8a78beee..9929bedad 100644 --- a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/Path.java +++ b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/Path.java @@ -36,15 +36,14 @@ import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite; import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite; import org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider; -import org.ocpsoft.rewrite.servlet.util.URLBuilder; import org.ocpsoft.urlbuilder.Address; +import org.ocpsoft.urlbuilder.AddressBuilder; /** * A {@link Condition} that inspects the value of {@link HttpServletRewrite#getRequestPath()} * * @author Lincoln Baxter, III */ -@SuppressWarnings("deprecation") public abstract class Path extends HttpCondition implements Parameterized { private final ParameterizedPatternParser expression; @@ -129,7 +128,7 @@ public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationCont return false; } else - url = URLBuilder.createFrom(event.getInboundAddress().getPath()).decode().toPath(); + url = AddressBuilder.begin().pathDecoded(event.getInboundAddress().getPath()).buildLiteral().toString(); String contextPath = event.getContextPath(); if (!contextPath.equals("/") && url.startsWith(contextPath)) diff --git a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/PathAndQuery.java b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/PathAndQuery.java index e4935ea98..3378f4fce 100644 --- a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/PathAndQuery.java +++ b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/PathAndQuery.java @@ -32,20 +32,18 @@ import org.ocpsoft.rewrite.param.ParameterizedPatternParser; import org.ocpsoft.rewrite.param.RegexConstraint; import org.ocpsoft.rewrite.param.RegexParameterizedPatternParser; -import org.ocpsoft.rewrite.servlet.config.HttpCondition; import org.ocpsoft.rewrite.servlet.config.bind.RequestBinding; import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite; import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite; import org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider; -import org.ocpsoft.rewrite.servlet.util.URLBuilder; import org.ocpsoft.urlbuilder.Address; +import org.ocpsoft.urlbuilder.AddressBuilder; /** * A {@link Condition} that inspects the value of {@link HttpServletRewrite#getAddress()} path and query string. * * @author Lincoln Baxter, III */ -@SuppressWarnings("deprecation") public abstract class PathAndQuery extends HttpCondition implements Parameterized { private final ParameterizedPatternParser expression; @@ -132,7 +130,8 @@ public boolean evaluateHttp(final HttpServletRewrite event, final EvaluationCont return false; } else - url = URLBuilder.createFrom(event.getInboundAddress().getPathAndQuery()).decode().toURL(); + url = AddressBuilder.begin().pathDecoded(event.getInboundAddress().getPath()) + .queryDecoded(event.getInboundAddress().getQuery()).build().toString(); String contextPath = event.getContextPath(); if (!contextPath.equals("/") && url.startsWith(contextPath)) diff --git a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/rule/Join.java b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/rule/Join.java index 3c12b4d14..ee4db4f9d 100644 --- a/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/rule/Join.java +++ b/config-servlet/src/main/java/org/ocpsoft/rewrite/servlet/config/rule/Join.java @@ -15,8 +15,11 @@ */ package org.ocpsoft.rewrite.servlet.config.rule; +import java.util.ArrayList; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; import javax.servlet.http.HttpServletRequest; @@ -46,8 +49,9 @@ import org.ocpsoft.rewrite.servlet.http.event.HttpOutboundServletRewrite; import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite; import org.ocpsoft.rewrite.servlet.spi.RequestParameterProvider; -import org.ocpsoft.rewrite.servlet.util.QueryStringBuilder; import org.ocpsoft.urlbuilder.Address; +import org.ocpsoft.urlbuilder.AddressBuilder; +import org.ocpsoft.urlbuilder.AddressBuilderBase; /** * {@link Rule} that creates a bi-directional rewrite rule between an externally facing {@link Address} and an internal @@ -56,7 +60,6 @@ * * @author Lincoln Baxter, III */ -@SuppressWarnings("deprecation") public class Join implements Rule, JoinPath, Parameterized { private static final String JOIN_DISABLED_KEY = Join.class.getName() + "_DISABLED"; @@ -268,25 +271,36 @@ public void perform(final Rewrite event, final EvaluationContext context) else if (event instanceof HttpOutboundServletRewrite) { - Set parameters = getPathRequestParameters(); + Address outboundAddress = ((HttpOutboundServletRewrite) event).getOutboundAddress(); - String outboundURL = ((HttpOutboundServletRewrite) event).getOutboundAddress().toString(); - QueryStringBuilder query = QueryStringBuilder.createNew(); - if (outboundURL.contains("?")) + /* + * Remove known path parameters from query parameter list to be included in new URL + */ + Set pathParameters = getPathRequestParameters(); + AddressBuilderBase queryBuilder = AddressBuilder.begin(); + if (outboundAddress.isQuerySet()) { - query.addParameters(outboundURL); - for (String string : parameters) { - List values = query.removeParameter(string); - if (values.size() > 1) + // Create a new query string without the parameters that were used in building the path + Map> queryParameters = outboundAddress.getQueryParameters(); + for (Entry> queryParameter : queryParameters.entrySet()) { + String parameterName = queryParameter.getKey(); + List parameterValues = queryParameter.getValue(); + if (pathParameters.contains(parameterName)) + { + List newParameterValues = new ArrayList(parameterValues); + newParameterValues.remove(0); + if (!newParameterValues.isEmpty()) + queryBuilder.query(parameterName, newParameterValues.toArray()); + } + else { - query.addParameter(string, values.subList(1, values.size()).toArray(new String[] {})); + queryBuilder.query(parameterName, parameterValues.toArray()); } } } + Address queryResult = queryBuilder.buildLiteral(); - Address outboundAddress = ((HttpOutboundServletRewrite) event).getOutboundAddress(); - - Substitute substitute = Substitute.with(requestPattern + query.toQueryString()); + Substitute substitute = Substitute.with(requestPattern + queryResult.toString()); substitute.setParameterStore(store); substitute.perform(event, context); diff --git a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/CommittedResponseTest.java b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/CommittedResponseTest.java index d91c5c673..259ae82b6 100644 --- a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/CommittedResponseTest.java +++ b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/CommittedResponseTest.java @@ -23,7 +23,6 @@ import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.asset.StringAsset; import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Ignore; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; diff --git a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/ForwardEncodingTest.java b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/ForwardEncodingTest.java index e4d135cf7..9c5c0f828 100644 --- a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/ForwardEncodingTest.java +++ b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/ForwardEncodingTest.java @@ -105,7 +105,6 @@ public void requestUrlsDirect() throws Exception Matchers.containsString("getRequestURI: [/rewrite-test/direct/debug/foo%20bar.dyn]")); assertThat(action.getResponseContent(), Matchers.containsString("inboundAddressPath: [/rewrite-test/direct/debug/foo%20bar.dyn]")); - } @Test @@ -116,13 +115,14 @@ public void requestUrlsForward() throws Exception assertEquals(200, action.getResponse().getStatusLine().getStatusCode()); // Not really sure if this is the expected result + // Lincoln: This is the behavior of the underlying HttpServletRequest, so we've really not changed/modified any + // behavior here. Recommend leaving this alone. assertThat(action.getResponseContent(), Matchers.containsString("getRequestURI: [/rewrite-test/direct/debug/foo bar.dyn]")); // IMHO this should be the result as it is consistent with the non-forwarded case assertThat(action.getResponseContent(), Matchers.containsString("inboundAddressPath: [/rewrite-test/direct/debug/foo%20bar.dyn]")); - } } \ No newline at end of file diff --git a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/JoinEncodingConfigurationTest.java b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/JoinEncodingConfigurationTest.java index 25ac3b41b..cf631b973 100644 --- a/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/JoinEncodingConfigurationTest.java +++ b/config-servlet/src/test/java/org/ocpsoft/rewrite/servlet/config/JoinEncodingConfigurationTest.java @@ -33,7 +33,6 @@ import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.spec.WebArchive; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.ocpsoft.common.util.Streams; @@ -85,7 +84,6 @@ public void testJoinEncodingSpaceCharacter() throws Exception } @Test - @Ignore // see: https://github.com/ocpsoft/rewrite/issues/195 public void testJoinSupportsSingleCurlyBrace() throws Exception { HttpAction action = get("/encoding/foo%7Bbar"); @@ -97,7 +95,17 @@ public void testJoinSupportsSingleCurlyBrace() throws Exception } @Test - @Ignore // see: https://github.com/ocpsoft/rewrite/issues/195 + public void testJoinSupportsCurlyBracketGroup() throws Exception + { + HttpAction action = get("/encoding/foo%5B%5D"); + assertEquals(200, action.getResponse().getStatusLine().getStatusCode()); + + String responseContent = action.getResponseContent(); + assertThat(responseContent, containsString("getRequestPath() = " + getContextPath() + "/encoding.html")); + assertThat(responseContent, containsString("getParameter('param') = foo[]")); + } + + @Test public void testJoinSupportsCurlyBraceGroup() throws Exception { HttpAction action = get("/encoding/foo%7Bbar%7D"); @@ -174,7 +182,8 @@ public void testOutboundRewritingSimpleString() throws Exception /** * Make sure that an URL containing a space in the query string (encoded as '+') is rewritten to a path containing - * the space encoded as %20. + * the space encoded as %20. This effectively tests if proper encoding/decoding is occurring throughout the entire + * life-cycle of the parameter. */ @Test public void testOutboundRewritingSpaceCharacter() throws Exception diff --git a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/BaseHttpRewrite.java b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/BaseHttpRewrite.java index 1a120c0b3..f95626132 100644 --- a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/BaseHttpRewrite.java +++ b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/BaseHttpRewrite.java @@ -89,8 +89,8 @@ public Address getInboundAddress() .scheme(getRequest().getScheme()) .domain(getRequest().getServerName()) .port(getRequest().getServerPort()) - .pathEncoded(requestURI) - .queryLiteral(getRequest().getQueryString()).build(); + .path(requestURI) + .queryLiteral(getRequest().getQueryString()).buildLiteral(); } return this.address; } diff --git a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/DefaultHttpRewriteProvider.java b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/DefaultHttpRewriteProvider.java index 91b9ca337..d9ec680fc 100644 --- a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/DefaultHttpRewriteProvider.java +++ b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/DefaultHttpRewriteProvider.java @@ -246,7 +246,7 @@ private void rewriteInbound(final HttpServletRewrite event) } } catch (Exception e) { - throw new RewriteException("Error during [" + event + "] while executing rule [" + rule + "]"); + throw new RewriteException("Error during [" + event + "] while executing rule [" + rule + "]", e); } } diff --git a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/HttpRewriteWrappedResponse.java b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/HttpRewriteWrappedResponse.java index 4ba4bfbab..d362e65a8 100644 --- a/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/HttpRewriteWrappedResponse.java +++ b/impl-servlet/src/main/java/org/ocpsoft/rewrite/servlet/impl/HttpRewriteWrappedResponse.java @@ -49,7 +49,6 @@ import org.ocpsoft.rewrite.servlet.http.event.HttpServletRewrite; import org.ocpsoft.rewrite.servlet.spi.OutboundRewriteProducer; import org.ocpsoft.rewrite.servlet.spi.RewriteLifecycleListener; -import org.ocpsoft.rewrite.servlet.util.URLBuilder; import org.ocpsoft.rewrite.spi.RewriteProvider; import org.ocpsoft.urlbuilder.Address; import org.ocpsoft.urlbuilder.AddressBuilder; @@ -57,7 +56,6 @@ /** * @author Lincoln Baxter, III */ -@SuppressWarnings("deprecation") public class HttpRewriteWrappedResponse extends RewriteWrappedResponse { private final HttpServletRequest request; @@ -378,7 +376,7 @@ public String encodeUrl(final String url) @Override public String encodeRedirectURL(final String url) { - Address address = AddressBuilder.create(URLBuilder.createFrom(url).toURL()); + Address address = AddressBuilder.create(url); OutboundServletRewrite event = rewrite(address); if (event.getFlow().is(ServletRewriteFlow.ABORT_REQUEST)) @@ -391,7 +389,7 @@ public String encodeRedirectURL(final String url) @Override public String encodeURL(final String url) { - Address address = AddressBuilder.create(URLBuilder.createFrom(url).toURL()); + Address address = AddressBuilder.create(url); OutboundServletRewrite event = rewrite(address); if (event.getFlow().is(ServletRewriteFlow.ABORT_REQUEST)) diff --git a/integration-faces/src/main/java/org/ocpsoft/rewrite/faces/navigate/Navigate.java b/integration-faces/src/main/java/org/ocpsoft/rewrite/faces/navigate/Navigate.java index 1f929ee1c..fa4991ae1 100644 --- a/integration-faces/src/main/java/org/ocpsoft/rewrite/faces/navigate/Navigate.java +++ b/integration-faces/src/main/java/org/ocpsoft/rewrite/faces/navigate/Navigate.java @@ -126,10 +126,10 @@ public String build() */ private String buildRedirectOutcome() { - AddressBuilderPath builderPath = AddressBuilder.begin().path(viewId); + AddressBuilderPath builderPath = AddressBuilder.begin().pathEncoded(viewId); for (Entry> param : parameters.entrySet()) { String[] values = param.getValue().toArray(new String[param.getValue().size()]); - builderPath.query(param.getKey(), (Object[]) values); + builderPath.queryEncoded(param.getKey(), (Object[]) values); } String url = builderPath.toString(); return RewriteNavigationHandler.REDIRECT_PREFIX + url;