| /* |
| * 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(); |
| } |
| |
| } |
| |
| } |