package de.axone.web;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.axone.data.Label;
import de.axone.exception.Assert;
import de.axone.tools.Str;
import de.axone.web.SuperURL.Part;
import de.axone.web.SuperURL.Path;
import de.axone.web.SuperURL.Query;
import de.axone.web.SuperURL.Query.QueryPart;
import de.axone.web.SuperURL.UserInfo;
/*
* NOTE:
*
* http://www.axon-e.de/foo/bar?key=value
*
* request.getRequestURI() -> /foo/bar?key=value
* request.getRequestURL() -> http://www.axon-e.de/foo/bar (without query)
* request.getPathInfo() -> /foo/bar (including /)
* request.getQueryString() -> key=value (without ?)
*
*/
public class SuperURLBuilders {
private static final Logger log =
LoggerFactory.getLogger( SuperURL.class );
static final SuperURLBuilders INSTANCE = new SuperURLBuilders();
public static SuperURLBuilder_String fromString() {
return new SuperURLBuilder_String();
}
public static SuperURLBuilder_URI fromURI() {
return new SuperURLBuilder_URI();
}
public static SuperURLBuilder_Request fromRequest() {
return new SuperURLBuilder_Request();
}
public static SuperURLBuilder_Copy fromSuperURL() {
return new SuperURLBuilder_Copy();
}
public static abstract class SuperURLBuilder<T, S extends SuperURLBuilder<T,S>> {
protected Set<Part> using = EnumSet.allOf( Part.class );
protected S myself;
@SuppressWarnings( "unchecked" )
SuperURLBuilder() {
myself = (S)this;
}
public S using( Set<Part> parts ){
this.using = parts;
return myself;
}
public S using( Part first, Part ... rest ){
this.using( EnumSet.of( first, rest ) );
return myself;
}
public S ignoring( Part first, Part ... rest ){
return ignoring( EnumSet.of( first, rest ) );
}
public S ignoring( Collection<Part> parts ){
this.using.removeAll( parts );
return myself;
}
public S ignoringIf( boolean condition, Part first, Part ... rest ){
return ignoringIf( condition, EnumSet.of( first, rest ) );
}
public S ignoringIf( boolean condition, Collection<Part> parts ){
if( condition ) this.using.removeAll( parts );
return myself;
}
public SuperURL build( T from ) {
return update( new SuperURL(), from );
}
public abstract SuperURL update( SuperURL dst, T from );
}
public static class SuperURLBuilder_String extends SuperURLBuilder<CharSequence, SuperURLBuilder_String> {
public enum Method {
uri, url, friendly;
}
private Method method = Method.uri;
public SuperURLBuilder_String usingMethod( Method method ){
this.method = method;
return this;
}
@Override
public SuperURL update( SuperURL dst, CharSequence parseMe ){
Assert.notNull( parseMe, "parseMe" );
if( parseMe.length() == 0 ) return dst;
switch( method ){
case uri:
String fixed = applyFixes( parseMe );
return updateUrlUsingUri( dst, fixed );
case url:
return updateUrlUsingURL( dst, parseMe.toString() );
case friendly:
return updateUrlFriendly( dst, parseMe.toString() );
default:
throw new IllegalArgumentException( "Unknown method: " + method );
}
}
//http://flo:blah@www.axon-e.de:8080/a/b/c/?foo=bar&a=b#fragment
private SuperURL updateUrlFriendly( SuperURL dst, String parseMe ){
throw new UnsupportedOperationException( "Not implemented (yet)" );
}
private SuperURL updateUrlUsingURL( SuperURL dst, String parseMe ){
try {
URL url = new URL( parseMe );
SuperURL result = new SuperURLBuilder_URL().using( using ).update( dst, url );
return result;
} catch( MalformedURLException e ) {
throw new IllegalArgumentException( "Cannot parse: " + parseMe, e );
}
}
private SuperURL updateUrlUsingUri( SuperURL dst, String parseMe ){
try {
URI uri = new URI( parseMe );
return new SuperURLBuilder_URI().using( using ).update( dst, uri );
} catch( URISyntaxException e ){
throw new IllegalArgumentException( "Cannot parse: " + parseMe, e );
}
}
private String applyFixes( CharSequence old ){
log.trace( "Parse: {}", old );
String asString = old.toString();
asString = fixForChrome( asString );
asString = fixForNGinx( asString );
log.trace( "To: {}", asString );
return asString;
}
private static final char [] SQUARE_BRACETS = { '[', ']' };
private static final String [] SQUARE_BRACETS_ENCODED = { "%5B", "%5D" };
// Chrome passes '[' and ']' in places where they not belong
private static String fixForChrome( String old ){
if( ! Str.containsOneOf( old, SQUARE_BRACETS ) ) return old;
if( Str.contains( old, "http://[" ) ) return old;// Ipv6 quickfix.
return Str.translate( old, SQUARE_BRACETS, SQUARE_BRACETS_ENCODED );
}
private static final char DQUOTES = '"';
private static final String DQUOTES_ENCODED = "%22";
// Using NGinx as proxy you get '"' unencoded in the url
private static String fixForNGinx( String old ){
if( ! Str.contains( old, DQUOTES ) ) return old;
return Str.translate( old, DQUOTES, DQUOTES_ENCODED );
}
}
public static class SuperURLBuilder_URL extends SuperURLBuilder<URL, SuperURLBuilder_URL> {
@Override
public SuperURL update( SuperURL dst, URL url ) {
if( using.contains( Part.Scheme ) ){
dst.setScheme( url.getProtocol() );
}
if( using.contains( Part.UserInfo ) ){
if( url.getUserInfo() != null ){
dst.setUserInfo( UserInfo.parse( url.getUserInfo(), true ) );
} else {
dst.setUserInfo( null );
}
}
if( using.contains( Part.Host ) ){
if( url.getHost() != null ){
dst.setHost( Host().parse( url.getHost(), true ).build() );
} else {
dst.setHost( null );
}
}
if( using.contains( Part.Port ) ){
if( url.getPort() >= 0 ){
dst.setPort( url.getPort() );
} else {
dst.setPort( null );
}
}
if( using.contains( Part.Path ) ){
if( url.getPath() != null ){
dst.setPath( Path().parse( url.getPath(), true ).build() );
} else {
dst.setPath( null );
}
}
if( using.contains( Part.Query ) ){
if( url.getQuery() != null ){
dst.setQuery( Query().parse( url.getQuery(), true ).build() );
} else {
dst.setQuery( null );
}
}
if( using.contains( Part.Fragment ) ){
dst.setFragment( url.getRef() );
}
return dst;
}
}
public static class SuperURLBuilder_URI extends SuperURLBuilder<URI, SuperURLBuilder_URI> {
@Override
public SuperURL update( SuperURL dst, URI uri ){
if( using.contains( Part.Scheme ) ){
dst.setScheme( uri.getScheme() );
}
if( using.contains( Part.UserInfo ) ){
if( uri.getUserInfo() != null ){
dst.setUserInfo( UserInfo.parse( uri.getRawUserInfo(), true ) );
} else {
dst.setUserInfo( null );
}
}
if( using.contains( Part.Fragment ) ){
dst.setFragment( uri.getFragment() );
}
if( using.contains( Part.Host ) ){
if( uri.getHost() != null ){
dst.setHost( Host().parse( uri.getHost(), false ).build() );
} else {
dst.setHost( null );
}
}
if( using.contains( Part.Port ) ){
if( uri.getPort() >= 0 ){
dst.setPort( uri.getPort() );
} else {
dst.setPort( null );
}
}
if( using.contains( Part.Path ) ){
if( uri.getPath() != null ){
Path path = Path().parse( uri.getRawPath(), true ).build();
dst.setPath( path );
} else {
dst.setPath( null );
}
}
if( using.contains( Part.Query ) ){
if( uri.getQuery() != null ){
dst.setQuery( Query().parse( uri.getRawQuery(), true ).build() );
} else {
dst.setQuery( null );
}
}
return dst;
}
}
public static class SuperURLBuilder_Request extends SuperURLBuilder<HttpServletRequest,SuperURLBuilder_Request> {
public enum Method {
URI, URL, Parameters, QueryString, CombinedString;
}
private Method method = Method.CombinedString;
public SuperURLBuilder_Request usingMethod( Method method ){
Assert.notNull( method, "method" );
this.method = method;
return this;
}
@Override
public SuperURL update( SuperURL dst, HttpServletRequest request ) {
/*
E.rr( "URI: " + request.getRequestURI() );
E.rr( "URL: " + request.getRequestURL() );
E.rr( "QuS: " + request.getPathInfo() );
E.rr( "PIn: " + request.getQueryString() );
*/
switch( method ){
case Parameters:
return updateUsingRequestAndParameters( dst, request );
case QueryString:
return updateParsingRequest( dst, request );
case URI:
return updateUsingRequestURI( dst, request );
case URL:
return updateUsingRequestURL( dst, request );
case CombinedString:
return updateUsingCombinedString( dst, request );
default:
throw new IllegalArgumentException( "Unknown method: " + method );
}
}
private SuperURL updateUsingCombinedString( SuperURL dst,
HttpServletRequest request ) {
StringBuffer url = request.getRequestURL();
String query = request.getQueryString();
if( query != null )
url.append( '?' ).append( query );
return SuperURLBuilders.fromString().using( using ).build( url.toString() );
}
/**
* Construct a SuperURL using the HttpServletRequest itself.
*
* This uses the requests methods the get the needed paramters
* and includes GET AND POST parameters.
*
* This is not what you want if you only want to parse the
* URL. So use {@link #updateUsingRequestURI(SuperURL, HttpServletRequest)} or
* {@link #updateUsingRequestURL(SuperURL, HttpServletRequest)} instead.
*
*
* @param dst The url to be updated
* @param request the HttpServletRequest to use for updating
* @return the updated url
*/
public SuperURL updateParsingRequest( SuperURL dst, HttpServletRequest request ) {
if( using.contains( Part.Scheme ) ){
dst.setScheme( request.getScheme() );
}
if( using.contains( Part.Host ) ){
String serverName = request.getServerName();
if( serverName != null )
dst.setHost( Host().parse( serverName, false ).build() );
}
if( using.contains( Part.Port ) ){
dst.setPort( request.getServerPort() );
}
if( using.contains( Part.Path ) ){
String pathInfo = request.getPathInfo();
if( pathInfo != null )
dst.setPath( Path().parse( request.getPathInfo(), true ).build() );
}
if( using.contains( Part.Query ) ){
String queryString = request.getQueryString();
if( queryString != null )
dst.setQuery( Query().parse( queryString, true ).build() );
}
return dst;
}
/**
* Construct a SuperURL using the HttpServletRequest itself.
*
* This uses the requests methods the get the needed paramters
* and includes GET AND POST parameters.
*
* This is not what you want if you only want to parse the
* URL. So use {@link #updateUsingRequestURI(SuperURL, HttpServletRequest)} or
* {@link #updateUsingRequestURL(SuperURL, HttpServletRequest)} instead.
*
* @param dst The url to be updated
* @param request the HttpServletRequest to use for updating
* @return the updated url
*/
public SuperURL updateUsingRequestAndParameters( SuperURL dst, HttpServletRequest request ) {
if( using.contains( Part.Scheme ) ){
dst.setScheme( request.getScheme() );
}
if( using.contains( Part.Host ) ){
dst.setHost( Host().parse( request.getServerName(), false ).build() );
}
if( using.contains( Part.Port ) ){
dst.setPort( request.getServerPort() );
}
if( using.contains( Part.Path ) ){
dst.setPath( Path().parse( request.getPathInfo() ).build() );
}
if( using.contains( Part.Query ) ){
Query query = null;
Enumeration<?> names = request.getParameterNames();
while( names.hasMoreElements() ){
if( query == null ) query = new SuperURL.Query();
String name = (String)names.nextElement();
String[] values = request.getParameterValues( name ); // Buggy (warum?)
for( String v : values ){
query.addValue( name, trimQueryValue( v ) );
}
}
dst.setQuery( query );
}
return dst;
}
private static String trimQueryValue( String queryValue ){
if( queryValue.length() == 0 ) return null;
return queryValue;
}
public SuperURL updateUsingRequestURI( SuperURL dst, HttpServletRequest request ) {
try {
URI uri = new URI( request.getRequestURI() );
return new SuperURLBuilder_URI().using( using ).update( dst, uri );
} catch( URISyntaxException e ) {
throw new IllegalArgumentException( "Cannot parse URI: " + request.getRequestURI() );
}
}
public SuperURL updateUsingRequestURL( SuperURL dst, HttpServletRequest request ) {
try {
URL url = new URL( request.getRequestURL().toString() );
return new SuperURLBuilder_URL().using( using ).update( dst, url );
} catch( MalformedURLException e ) {
throw new IllegalArgumentException( "Cannot parse URL: " + request.getRequestURI() );
}
}
}
public static class SuperURLBuilder_Copy extends SuperURLBuilder<SuperURL, SuperURLBuilder_Copy> {
private boolean deep = true;
public SuperURLBuilder_Copy usingDeepCopy( boolean deep ){
this.deep = deep;
return this;
}
@Override
public SuperURL update( SuperURL dst, SuperURL from ) {
if( using.contains( Part.Scheme ) ) {
dst.setScheme( from.getScheme() );
dst.setIncludeScheme( from.isIncludeScheme() );
}
if( using.contains( Part.UserInfo ) ){
if( deep ){
dst.setUserInfo( from.getUserInfo().copy() );
} else {
dst.setUserInfo( from.getUserInfo() );
}
dst.setIncludeUserInfo( from.isIncludeUserInfo() );
}
if( using.contains( Part.Host ) ) {
if( deep ){
dst.setHost( from.getHost().copy() );
} else {
dst.setHost( from.getHost() );
}
dst.setIncludeHost( from.isIncludeHost() );
}
if( using.contains( Part.Port ) ) {
dst.setPort( from.getPort() );
dst.setIncludePort( from.isIncludePort() );
}
if( using.contains( Part.Path ) ) {
if( deep ){
dst.setPath( from.getPath().copy() );
} else {
dst.setPath( from.getPath() );
}
dst.setIncludePath( from.isIncludePath() );
}
if( using.contains( Part.Query ) ) {
if( deep ){
dst.setQuery( from.getQuery().copy() );
} else {
dst.setQuery( from.getQuery() );
}
dst.setIncludeQuery( from.isIncludeQuery() );
}
if( using.contains( Part.Fragment ) ) {
dst.setFragment( from.getFragment() );
dst.setIncludeFragment( from.isIncludeFragment() );
}
return dst;
}
}
public static HostBuilder Host(){
return new HostBuilder();
}
public static PathBuilder Path(){
return new PathBuilder();
}
public static QueryBuilder Query(){
return new QueryBuilder();
}
public static class HostBuilder {
private SuperURL.Host result = new SuperURL.Host();
public HostBuilder parse( String parseMe ){
return parse( parseMe, true );
}
public HostBuilder parse( String parseMe, boolean decode ){
String [] parts = Str.splitFast( parseMe, '.' );
if( decode ){
for( int i=0; i<parts.length; i++ ){
parts[ i ] = SuperURL.decode( parts[ i ] );
}
}
result.parts = new ArrayList<>( Arrays.asList( parts ) );
return this;
}
public SuperURL.Host build(){
return result;
}
}
public static class PathBuilder {
private SuperURL.Path result = new SuperURL.Path();
public PathBuilder of( String ... part ){
result.addAll( part );
return this;
}
public PathBuilder parse( String parseMe ){
return parse( parseMe, true );
}
public PathBuilder parse( String parseMe, boolean decode ){
if( parseMe == null || parseMe.length() == 0 ){
return this;
}
if( "/".equals( parseMe ) ){
result.setStartsWithSlash( true );
return this;
}
String[] parts = Str.splitFast( parseMe, '/' );
ArrayList<String> asList = new ArrayList<>( parts.length );
for( int i = 0; i < parts.length ; i++ ){
String part = parts[ i ];
if( i == 0 ){
if( part.length() == 0 ){
result.startsWithSlash = true;
continue;
}
}
if( i == parts.length -1 ){
if( part.length() == 0 ){
result.endsWithSlash = true;
continue;
}
}
if( decode ) part = SuperURL.decode( part );
asList.add( part );
}
result.path.addAll( asList );
return this;
}
public SuperURL.Path build(){
return result;
}
}
public static class QueryBuilder {
SuperURL.Query result = new SuperURL.Query();
public QueryBuilder part( QueryPart part ){
if( part != null ) result.add( part );
return this;
}
public QueryBuilder parts( Query parameters ) {
if( parameters != null ) result.addAll( parameters );
return this;
}
public QueryBuilder label( String key, Label value ){
return value( key, value != null ? value.label() : null );
}
public QueryBuilder value( String key, String value ){
return part( new QueryPart( key, value ) );
}
public SuperURL.Query build(){
return result;
}
public QueryBuilder parse( String parseMe ){
return parse( parseMe, true );
}
public QueryBuilder parse( String parseMe, boolean decode ){
String[] parts = Str.splitFast( parseMe, '&' );
for( String part : parts ){
QueryPart qPart = QueryPart.parse( part, decode );
result.add( qPart );
}
return this;
}
public QueryBuilder fromPlainMap( Map<String,String> parameters ){
for( Map.Entry<String,String> entry : parameters.entrySet() ){
result.addValue( entry.getKey(), entry.getValue() );
}
return this;
}
public QueryBuilder fromMultiMap( Map<String,String[]> parameters ){
for( Map.Entry<String,String[]> paras : parameters.entrySet() ){
for( String value : paras.getValue() ){
result.addValue( paras.getKey(), value );
}
}
return this;
}
public QueryBuilder fromArray( String ... parameters ){
if( parameters.length % 2 != 0 )
throw new IllegalArgumentException( "Only even parameter count allowed" );
for( int i=0; i<parameters.length; i+=2 ){
result.addValue( parameters[ i ], parameters[ i+1 ] );
}
return this;
}
}
}