blob: ad143219841f4995947e983291e49c8b024f6480 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.knox.gateway.util.urltemplate;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
public class Expander {
private static Params EMPTY_PARAMS = new EmptyParams();
public static URI expand( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
return Expander.expandToUri( template, params, evaluator );
}
public static URI expandToUri( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
return new URI( expandToString( template, params, evaluator ) );
}
public static Template expandToTemplate( Template template, Params params, Evaluator evaluator ) throws URISyntaxException {
//TODO: This could be much more efficient if it didn't create and then parse a string.
return Parser.parseLiteral( expandToString( template, params, evaluator ) );
}
public static String expandToString( Template template, Params params, Evaluator evaluator ) {
StringBuilder builder = new StringBuilder();
if( params == null ) {
params = EMPTY_PARAMS;
}
Set<String> names = new HashSet<>( params.getNames() );
expandScheme( template, names, params, evaluator, builder );
expandAuthority( template, names, params, evaluator, builder );
expandPath( template, names, params, evaluator, builder );
if( template.hasFragment() ) {
StringBuilder fragment = new StringBuilder();
expandFragment( template, names, params, evaluator, fragment );
expandQuery( template, names, params, evaluator, builder );
builder.append( fragment );
} else {
expandQuery( template, names, params, evaluator, builder );
}
return builder.toString();
}
private static void expandScheme( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
Segment segment = template.getScheme();
if( segment != null ) {
expandSingleValue( template.getScheme(), names, params, evaluator, builder );
builder.append( ":" );
}
}
private static void expandAuthority( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
if( template.hasAuthority() ) {
if( !template.isAuthorityOnly() ) {
builder.append( "//" );
}
Segment username = template.getUsername();
Segment password = template.getPassword();
Segment host = template.getHost();
Segment port = template.getPort();
expandSingleValue( username, names, params, evaluator, builder );
if( password != null ) {
builder.append( ":" );
expandSingleValue( password, names, params, evaluator, builder );
}
if( username != null || password != null ) {
builder.append( "@" );
}
if( host != null ) {
expandSingleValue( host, names, params, evaluator, builder );
}
if( port != null ) {
builder.append( ":" );
expandSingleValue( port, names, params, evaluator, builder );
}
}
}
private static void expandPath( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
if( template.isAbsolute() ) {
builder.append( "/" );
}
List<Path> path = template.getPath();
for( int i=0, n=path.size(); i<n; i++ ) {
if( i > 0 ) {
builder.append( "/" );
}
Path segment = path.get( i );
String name = segment.getParamName();
Function function = new Function( name );
names.remove( function.getParameterName() );
Segment.Value value = segment.getFirstValue();
switch( value.getType() ) {
case( Segment.STATIC ):
String pattern = value.getOriginalPattern();
builder.append( pattern );
break;
case( Segment.DEFAULT ):
case( Segment.STAR ):
case( Segment.GLOB ):
case( Segment.REGEX ):
List<String> values = function.evaluate( params, evaluator );
expandPathValues( segment, values, builder );
break;
}
}
if( template.isDirectory() && !path.isEmpty() ) {
builder.append( "/" );
}
}
//TODO: This needs to handle multiple values but only to the limit of the segment.
private static void expandPathValues( Path segment, List<String> values, StringBuilder builder ) {
if( values != null && !values.isEmpty() ) {
int type = segment.getFirstValue().getType();
if( type == Segment.GLOB || type == Segment.DEFAULT ) {
for( int i=0, n=values.size(); i<n; i++ ) {
if( i > 0 ) {
builder.append( "/" );
}
builder.append( values.get( i ) );
}
} else {
builder.append( values.get( 0 ) );
}
} else {
builder.append( segment.getFirstValue().getOriginalPattern() );
}
}
private static void expandQuery( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
AtomicInteger index = new AtomicInteger( 0 );
expandExplicitQuery( template, names, params, evaluator, builder, index );
expandExtraQuery( template, names, params, builder, index );
//Kevin: I took this out because it causes '?' to be added to expanded templates when there are not query params.
// if( template.hasQuery() && index.get() == 0 ) {
// builder.append( '?' );
// }
}
private static void expandExplicitQuery( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder, AtomicInteger index ) {
Collection<Query> query = template.getQuery().values();
if( !query.isEmpty() ) {
Iterator<Query> iterator = query.iterator();
while( iterator.hasNext() ) {
int i = index.incrementAndGet();
if( i == 1 ) {
builder.append( "?" );
} else {
builder.append( "&" );
}
Query segment = iterator.next();
String queryName = segment.getQueryName();
String paramName = segment.getParamName();
Function function = new Function( paramName );
names.remove( function.getParameterName() );
for( Segment.Value value: segment.getValues() ) {
switch( value.getType() ) {
case( Segment.STATIC ):
builder.append( queryName );
String pattern = value.getOriginalPattern();
if( pattern != null ) {
builder.append( "=" );
builder.append( pattern );
}
break;
case( Segment.DEFAULT ):
case( Segment.GLOB ):
case( Segment.STAR ):
case( Segment.REGEX ):
List<String> values = function.evaluate( params, evaluator );
expandQueryValues( segment, queryName, values, builder );
break;
default:
}
}
}
}
}
private static void expandExtraQuery( Template template, Set<String> names, Params params, StringBuilder builder, AtomicInteger index ) {
Query extra = template.getExtra();
if( extra != null ) {
// Need to copy to an array because we are going to modify the set while iterating.
String[] array = new String[ names.size() ];
names.toArray( array );
for( String name: array ) {
names.remove( name );
List<String> values = params.resolve( name );
if( values != null ) {
for( String value: values ) {
int i = index.incrementAndGet();
if( i == 1 ) {
builder.append( "?" );
} else {
builder.append( "&" );
}
appendQueryPart(name, builder);
if( value != null ) {
builder.append( "=" );
appendQueryPart(value, builder);
}
}
}
}
}
}
private static void expandQueryValues( Query segment, String queryName, List<String> values, StringBuilder builder ) {
String value;
if( values == null || values.size() == 0 ) {
builder.append( queryName );
} else {
int type = segment.getFirstValue().getType();
if( type == Segment.GLOB || type == Segment.DEFAULT ) {
for( int i=0, n=values.size(); i<n; i++ ) {
if( i > 0 ) {
builder.append( "&" );
}
appendQueryPart(queryName, builder);
value = values.get( i );
if( value != null ) {
builder.append( "=" );
appendQueryPart(value, builder);
}
}
} else {
appendQueryPart(queryName, builder);
value = values.get( 0 );
if( value != null ) {
builder.append( "=" );
appendQueryPart(value, builder);
}
}
}
}
private static void appendQueryPart(String part, StringBuilder builder) {
try {
builder.append(URLEncoder.encode(part, StandardCharsets.UTF_8.name()));
} catch ( UnsupportedEncodingException e ) {
builder.append(part);
}
}
private static void expandFragment( Template template, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
if( template.hasFragment() ) {
builder.append( "#" );
}
expandSingleValue( template.getFragment(), names, params, evaluator, builder );
}
private static void expandSingleValue( Segment segment, Set<String> names, Params params, Evaluator evaluator, StringBuilder builder ) {
if( segment != null ) {
String paramName = segment.getParamName();
Function function = new Function( paramName );
names.remove( function.getParameterName() );
Segment.Value value = segment.getFirstValue();
String str;
switch( value.getType() ) {
case Segment.DEFAULT:
case Segment.STAR:
case Segment.GLOB:
case Segment.REGEX:
List<String> values = function.evaluate( params, evaluator );
if( values != null && !values.isEmpty() ) {
str = values.get( 0 );
} else if( function.getFunctionName() != null ) {
str = paramName;
} else {
str = value.getOriginalPattern();
}
break;
default:
str = value.getOriginalPattern();
break;
}
builder.append( str );
}
}
private static class EmptyParams implements Params {
@Override
public Set<String> getNames() {
return Collections.emptySet();
}
@Override
public List<String> resolve( String name ) {
return Collections.emptyList();
}
}
}