All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commons.http.WwwFormUrlEncodedCodec Maven / Gradle / Ivy

The newest version!
/* 
 * Copyright (c) 2017 Georgi Pavlov ([email protected]).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the MIT license which accompanies 
 * this distribution, and is available at 
 * https://github.com/tengia/oauth-2/blob/master/LICENSE
 */
package commons.http;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Encoder/decoder for the x-www-form-urlencoded data format. 
 *
 */
public class WwwFormUrlEncodedCodec {

	@SuppressWarnings("rawtypes")
	private Map globalSerializerMappings = new HashMap<>();
	@SuppressWarnings("rawtypes")
	private Map globalDeseralizerMappings = new HashMap<>();

	public static interface Serializer {
		String serialize(T value); 
	}

	public static interface Deserializer {
		T deserialize(String input);
	}

	public  WwwFormUrlEncodedCodec with(String mappedName, Serializer serializer) {
		this.globalSerializerMappings.put(mappedName, serializer);
		return this;
	}

	@SuppressWarnings("rawtypes")
	protected Map resolveSerializerMappings(Map serializersMappings) {
		//resolve mappings to use. We start with globals if any, then override them with specifically provided as argument in this method if any
		Map mappings = this.globalSerializerMappings;
		if(serializersMappings != null){
			mappings = Stream.of(serializersMappings)
				        .map(Map::entrySet)
				        .flatMap(Set::stream)
				        .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
				        						  (v1, v2) -> v2,
				                				  () -> new HashMap<>(this.globalSerializerMappings))); 
		}
		return mappings;
	}

	@SuppressWarnings("unchecked")
	protected Entry encodeEntry(final Entry entry, @SuppressWarnings("rawtypes") final Map mappings) {
		String encodedValue = null;
		//encode values with mapped serializers if any
		encodedValue = mappings.entrySet().stream()
						.filter(serializerMapping -> entry.getKey().equals(serializerMapping.getKey()))
						.map(Map.Entry::getValue)
						.findFirst()
						.flatMap((serializer)->{
							return Optional.ofNullable((String)serializer.serialize(entry.getValue()));
						})
	                    .orElseGet(()-> null);
		if(encodedValue == null) {
			//no special serializers were used. We will use toString() if the value is not a string already 
			if(!String.class.isAssignableFrom(entry.getValue().getClass()))
	    		encodedValue = entry.getValue().toString();
			else
				encodedValue = (String) entry.getValue();//TODO: refactor to avoid unnecessary change of the entry
		}
		entry.setValue(encodedValue);
		return entry;
	}

	/**
	 * Transform a stream of key-value pairs into a www.form-encoded string.
	 * @param stream
	 * @return
	 */
	public String encodeStream(final Stream> stream, @SuppressWarnings("rawtypes") Map serializersMappings) {
		@SuppressWarnings("rawtypes")
		final Map mappings = resolveSerializerMappings(serializersMappings); 
		String encoded = stream.filter(parameterEntry -> parameterEntry.getValue() != null)
		.map(parameterEntry -> {
			return encodeEntry(parameterEntry, mappings);
		})
		.map((entry) -> {
			return entry.getKey() + "=" + entry.getValue();
		})
		.collect(Collectors.joining("&"));		
		return encoded;
	}

	public WwwFormUrlEncodedCodec() {
		super();
	}

	public  WwwFormUrlEncodedCodec with(String mappedName, Deserializer deserializer) {
		globalDeseralizerMappings.put(mappedName, deserializer);
		return this;
	}
	
	public final class Tuple implements Map.Entry {
		String k, v;
		Tuple(String key, String value){
			this.k = key;
			this.v = value;
		}
		@Override
		public String getKey() {
			return this.k;
		}
		@Override
		public String getValue() {
			return this.v;
		}
		@Override
		public String setValue(String value) {
			String old = this.v;
			this.v = value;
			return old;
		}		
	}

	public Stream decodeStream(final String encodedString) {
		if(encodedString == null)
			throw new IllegalArgumentException();
		String decodedString = null;
		try {
			decodedString = URLDecoder.decode(encodedString, StandardCharsets.UTF_8.name());
		} catch (UnsupportedEncodingException e) { throw new RuntimeException(e);}
		return Stream.of(decodedString.split("&"))
		.map((entry)->{ 
			String[] kv = entry.split("=");
			return new Tuple(kv[0], kv[1]);
		});
	}

	@SuppressWarnings("rawtypes")
	protected Map resolveDeserializerMappings(Map deserializersMappings) {
		//resolve mappings to use. We start with globals if any, then override them with specifically provided as argument in this method if any
		Map mappings = this.globalDeseralizerMappings;
		if(deserializersMappings != null){
			mappings = Stream.of(deserializersMappings)
			        .map(Map::entrySet)
			        .flatMap(Set::stream)
			        .collect(Collectors.toMap(Entry::getKey, Entry::getValue,
			        						  (v1, v2) -> v2,
			                				  () -> new HashMap<>(this.globalDeseralizerMappings)));
		}
		return mappings;
	}

	protected Object decodeEntry(Tuple tuple, @SuppressWarnings("rawtypes") final Map deserializersMappings) {
		Object result = deserializersMappings.entrySet().stream()
							.filter(entry -> tuple.getKey().equals(entry.getKey()))
							.map(Map.Entry::getValue)
		                    .findFirst()
		                    .flatMap((deser)->{
		                    	return Optional.ofNullable(deser.deserialize(tuple.getValue()));
		                    })
		                    .orElseGet(()->null);
		if(result == null)
			result = tuple.getValue();
		return result;
	}

	public Map from(final String encodedString, @SuppressWarnings("rawtypes") final Map deserializersMappings) {
		@SuppressWarnings("rawtypes")
		final Map mappings = resolveDeserializerMappings(deserializersMappings);
		Map parameters = this.decodeStream(encodedString)
		.collect(Collectors.toMap(Tuple::getKey, (tuple)-> {
			return decodeEntry(tuple, mappings);
		}));
		return parameters;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy