/******************************************************************************* * Copyright (c) 2012-2016 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.everrest.core.impl.provider; import org.everrest.core.ApplicationContext; import org.everrest.core.impl.MultivaluedMapImpl; import org.everrest.core.provider.EntityProvider; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.ext.Provider; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.List; import java.util.Map; @Provider @Consumes({MediaType.APPLICATION_FORM_URLENCODED}) @Produces({MediaType.APPLICATION_FORM_URLENCODED}) public class MultivaluedMapEntityProvider implements EntityProvider<MultivaluedMap<String, String>> { private HttpServletRequest httpRequest; public MultivaluedMapEntityProvider(@Context HttpServletRequest httpRequest) { this.httpRequest = httpRequest; } @Override public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { if (type == MultivaluedMap.class) { try { ParameterizedType parameterizedType = (ParameterizedType)genericType; Type[] typeArguments = parameterizedType.getActualTypeArguments(); return typeArguments.length == 2 && typeArguments[0] == String.class && typeArguments[1] == String.class; } catch (ClassCastException e) { return false; } } return false; } @Override @SuppressWarnings("unchecked") public MultivaluedMap<String, String> readFrom(Class<MultivaluedMap<String, String>> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException { ApplicationContext context = ApplicationContext.getCurrent(); Object decodedMap = context.getAttributes().get("org.everrest.provider.entity.decoded.form"); if (decodedMap != null) { return (MultivaluedMap<String, String>)decodedMap; } MultivaluedMap<String, String> encodedForm = new MultivaluedMapImpl(); MultivaluedMap<String, String> decodedForm = new MultivaluedMapImpl(); StringBuilder sb = new StringBuilder(); int r; while ((r = entityStream.read()) != -1) { if (r != '&') { sb.append((char)r); } else { parseKeyValuePair(sb.toString().trim(), encodedForm, decodedForm); sb.setLength(0); } } parseKeyValuePair(sb.toString().trim(), encodedForm, decodedForm); if (decodedForm.isEmpty() && httpRequest != null) { httpRequest.getParameterMap() .entrySet() .stream() .filter(e -> e.getValue() != null) .forEach(e -> decodedForm.addAll(e.getKey(), e.getValue())); } context.getAttributes().put("org.everrest.provider.entity.decoded.form", decodedForm); context.getAttributes().put("org.everrest.provider.entity.encoded.form", encodedForm); return decodedForm; } /** * Parse string and add key/value pair in the {@link MultivaluedMap}. * * @param pair * string for processing * @param encodedForm * {@link MultivaluedMap} to add encoded result of parsing * @param decodedForm * {@link MultivaluedMap} to add decoded result of parsing * @throws UnsupportedEncodingException * if supplied string can't be decoded */ private void parseKeyValuePair(String pair, MultivaluedMap<String, String> encodedForm, MultivaluedMap<String, String> decodedForm) throws UnsupportedEncodingException { if (pair.length() == 0) { return; } int eq = pair.indexOf('='); String encodedName; String encodedValue; String decodedName; String decodedValue; if (eq < 0) { encodedName = pair; encodedValue = ""; decodedName = URLDecoder.decode(encodedName, "UTF-8"); decodedValue = ""; } else { encodedName = pair.substring(0, eq); encodedValue = pair.substring(eq + 1); decodedName = URLDecoder.decode(encodedName, "UTF-8"); decodedValue = URLDecoder.decode(encodedValue, "UTF-8"); } encodedForm.add(encodedName, encodedValue); decodedForm.add(decodedName, decodedValue); } @Override public long getSize(MultivaluedMap<String, String> multivaluedMap, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } @Override public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return MultivaluedMap.class.isAssignableFrom(type); } @Override public void writeTo(MultivaluedMap<String, String> multivaluedMap, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException { int i = 0; for (Map.Entry<String, List<String>> e : multivaluedMap.entrySet()) { for (String value : e.getValue()) { if (i > 0) { entityStream.write('&'); } String name = URLEncoder.encode(e.getKey(), "UTF-8"); entityStream.write(name.getBytes()); i++; if (value != null) { entityStream.write('='); value = URLEncoder.encode(value, "UTF-8"); entityStream.write(value.getBytes()); } } } } }