package io.oasp.module.rest.service.api;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;
/**
* This class helps to deal with {@link UriInfo} and {@link MultivaluedMap} from the JAX-RS API. E.g. if you have a REST
* query operation for a collection URI you can use {@link UriInfo} in case you want to support a mixture of optional
* and required parameters. The methods provided here throw according exceptions such as {@link BadRequestException} and
* already support conversion of values.
*
*/
public class RequestParameters {
private final MultivaluedMap<String, String> parameters;
/**
* The constructor.
*
* @param parameters is the {@link MultivaluedMap} containing the parameters to wrap.
*/
public RequestParameters(MultivaluedMap<String, String> parameters) {
super();
this.parameters = parameters;
}
/**
* Gets the single parameter in a generic and flexible way.
*
* @param <T> is the generic type of <code>targetType</code>.
* @param key is the {@link java.util.Map#get(Object) key} of the parameter to get.
* @param targetType is the {@link Class} reflecting the type to convert the value to. Supports common Java standard
* types such as {@link String}, {@link Long}, {@link Double}, {@link BigDecimal}, etc.
* @param required - {@code true} if the value is required and a {@link BadRequestException} is thrown if it is
* not present, {@code false} otherwise (if optional).
* @return the value for the given <code>key</code> converted to the given <code>targetType</code>. May be
* {@code null} if <code>required</code> is {@code false} .
* @throws WebApplicationException if an error occurred. E.g. {@link BadRequestException} if a required parameter is
* missing or {@link InternalServerErrorException} if the given <code>targetType</code> is not supported.
*/
@SuppressWarnings("unchecked")
public <T> T get(String key, Class<T> targetType, boolean required) throws WebApplicationException {
String value = get(key);
if (value == null) {
if (required) {
throw new BadRequestException("Missing parameter: " + key);
}
Object result = null;
if (targetType.isPrimitive()) {
if (targetType == boolean.class) {
result = Boolean.FALSE;
} else if (targetType == int.class) {
result = Integer.valueOf(0);
} else if (targetType == long.class) {
result = Long.valueOf(0);
} else if (targetType == double.class) {
result = Double.valueOf(0);
} else if (targetType == float.class) {
result = Float.valueOf(0);
} else if (targetType == byte.class) {
result = Byte.valueOf((byte) 0);
} else if (targetType == short.class) {
result = Short.valueOf((short) 0);
} else if (targetType == char.class) {
result = '\0';
}
}
return (T) result;
}
try {
return convertValue(value, targetType);
} catch (WebApplicationException e) {
throw e;
} catch (Exception e) {
throw new BadRequestException("Failed to convert '" + value + "' to type " + targetType);
}
}
/**
* Converts the given <code>value</code> to the given <code>targetType</code>.
*
* @param <T> is the generic type of <code>targetType</code>.
* @param value is the value to convert.
* @param targetType is the {@link Class} reflecting the type to convert the value to.
* @return the converted value.
* @throws ParseException if parsing of the given <code>value</code> failed while converting.
*/
@SuppressWarnings("unchecked")
protected <T> T convertValue(String value, Class<T> targetType) throws ParseException {
if (value == null) {
return null;
}
Object result;
if (targetType == String.class) {
result = value;
} else if (targetType.isEnum()) {
for (T instance : targetType.getEnumConstants()) {
Enum<?> e = (Enum<?>) instance;
if (e.name().equalsIgnoreCase(value)) {
return instance;
}
}
throw new IllegalArgumentException("Enum constant not found!");
} else if ((targetType == boolean.class) || (targetType == Boolean.class)) {
result = Boolean.parseBoolean(value);
} else if ((targetType == int.class) || (targetType == Integer.class)) {
result = Integer.valueOf(value);
} else if ((targetType == long.class) || (targetType == Long.class)) {
result = Long.valueOf(value);
} else if ((targetType == double.class) || (targetType == Double.class)) {
result = Double.valueOf(value);
} else if ((targetType == float.class) || (targetType == Float.class)) {
result = Float.valueOf(value);
} else if ((targetType == short.class) || (targetType == Short.class)) {
result = Short.valueOf(value);
} else if ((targetType == byte.class) || (targetType == Byte.class)) {
result = Byte.valueOf(value);
} else if (targetType == BigDecimal.class) {
result = new BigDecimal(value);
} else if (targetType == BigInteger.class) {
result = new BigInteger(value);
} else if (targetType == Date.class) {
result = new SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss").parseObject(value);
} else {
throw new InternalServerErrorException("Unsupported type " + targetType);
}
// do not use type.cast() as not working for primitive types.
return (T) result;
}
/**
* Gets the parameter as single value with the given <code>key</code> as {@link String}.
*
* @param key is the {@link java.util.Map#get(Object) key} of the parameter to get.
* @return the requested parameter. Will be {@code null} if the parameter is not present.
* @throws BadRequestException if the parameter is defined multiple times (see {@link #getList(String)}).
*/
public String get(String key) throws BadRequestException {
List<String> list = this.parameters.get(key);
if ((list == null) || (list.isEmpty())) {
return null;
}
if (list.size() > 1) {
throw new BadRequestException("Duplicate parameter: " + key);
}
return list.get(0);
}
/**
* Gets the parameter with the given <code>key</code> as {@link String}. Unlike {@link #get(String)} this method will
* not throw an exception if the parameter is multi-valued but just return the first value.
*
* @param key is the {@link java.util.Map#get(Object) key} of the parameter to get.
* @return the first value of the requested parameter. Will be {@code null} if the parameter is not present.
*/
public String getFirst(String key) {
return this.parameters.getFirst(key);
}
/**
* Gets the {@link List} of all value for the parameter with with the given <code>key</code>. In general you should
* avoid multi-valued parameters (e.g. http://host/path?query=a&query=b). The JAX-RS API supports this exotic case as
* first citizen so we expose it here but only use it if you know exactly what you are doing.
*
* @param key is the {@link java.util.Map#get(Object) key} of the parameter to get.
* @return the {@link List} with all values of the requested parameter. Will be an {@link Collections#emptyList()
* empty list} if the parameter is not present.
*/
public List<String> getList(String key) {
List<String> list = this.parameters.get(key);
if (list == null) {
list = Collections.emptyList();
}
return list;
}
/**
* @param uriInfo is the {@link UriInfo}.
* @return a new instance of {@link RequestParameters} for {@link UriInfo#getQueryParameters()}.
*/
public static RequestParameters fromQuery(UriInfo uriInfo) {
return new RequestParameters(uriInfo.getQueryParameters());
}
/**
* @param uriInfo is the {@link UriInfo}.
* @return a new instance of {@link RequestParameters} for {@link UriInfo#getPathParameters()}.
*/
public static RequestParameters fromPath(UriInfo uriInfo) {
return new RequestParameters(uriInfo.getPathParameters());
}
}