/** * 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.hadoop.gateway.util.urltemplate; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; 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<String>( 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.size() > 0 ) { 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.size() > 0 ) { 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, "UTF-8")); } 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(); } } }