Java tutorial
/* * This file is part of the Spider Web Framework. * * The Spider Web Framework is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The Spider Web Framework is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with the Spider Web Framework. If not, see <http://www.gnu.org/licenses/>. */ package com.medallia.spider.api; import java.io.IOException; import java.io.InputStream; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.Map.Entry; import javax.servlet.http.HttpServletRequest; import com.google.common.base.Charsets; import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemStream; import org.apache.commons.fileupload.FileUploadException; import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.fileupload.util.Streams; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.io.ByteStreams; import com.medallia.spider.api.StRenderable.DynamicInput; import com.medallia.spider.api.StRenderable.Input; import com.medallia.spider.api.StRenderable.UploadedFile; import com.medallia.spider.api.StRenderer.InputArgHandler; import com.medallia.spider.api.StRenderer.InputArgParser; import com.medallia.tiny.Strings; /** * Implementation of {@link DynamicInput} that is also used to parse the * values for the static input variables. */ public class DynamicInputImpl implements DynamicInput, InputArgHandler { private final Map<String, String[]> inputParams; private final Map<String, UploadedFile> fileUploads; private final Map<Class<?>, InputArgParser<?>> inputArgParsers = Maps.newHashMap(); /** * Creates a new {@link DynamicInputImpl} * @param request from which to read the request parameters */ public DynamicInputImpl(HttpServletRequest request) { @SuppressWarnings("unchecked") Map<String, String[]> reqParams = Maps.newHashMap(request.getParameterMap()); this.inputParams = reqParams; if (ServletFileUpload.isMultipartContent(request)) { this.fileUploads = Maps.newHashMap(); Multimap<String, String> inputParamsWithList = ArrayListMultimap.create(); ServletFileUpload upload = new ServletFileUpload(); try { FileItemIterator iter = upload.getItemIterator(request); while (iter.hasNext()) { FileItemStream item = iter.next(); String fieldName = item.getFieldName(); InputStream stream = item.openStream(); if (item.isFormField()) { inputParamsWithList.put(fieldName, Streams.asString(stream, Charsets.UTF_8.name())); } else { final String filename = item.getName(); final byte[] bytes = ByteStreams.toByteArray(stream); fileUploads.put(fieldName, new UploadedFile() { @Override public String getFilename() { return filename; } @Override public byte[] getBytes() { return bytes; } @Override public int getSize() { return bytes.length; } }); } } for (Entry<String, Collection<String>> entry : inputParamsWithList.asMap().entrySet()) { inputParams.put(entry.getKey(), entry.getValue().toArray(new String[0])); } } catch (IOException | FileUploadException e) { throw new IllegalArgumentException("Failed to parse multipart", e); } } else { this.fileUploads = Collections.emptyMap(); } } @Override public <X> void registerArgParser(Class<X> type, InputArgParser<X> parser) { inputArgParsers.put(type, parser); } @Override public <X> X getInput(String name, Class<X> type) { return getInput(name, type, emptyAnnotatedElement); } public Map<String, String[]> getInputParams() { return Collections.unmodifiableMap(inputParams); } public Map<String, UploadedFile> getFileUploads() { return Collections.unmodifiableMap(fileUploads); } /** * Method used to parse values for the methods declared in {@link Input}. */ public <X> X getInput(String name, Class<X> type, AnnotatedElement anno) { // Special case for file uploads if (type.isAssignableFrom(UploadedFile.class)) return type.cast(fileUploads.get(name)); // Also support just getting the bytes from a file without the name if (type.isArray() && type.getComponentType() == Byte.TYPE) { UploadedFile uploadedFile = fileUploads.get(name); return uploadedFile != null ? type.cast(uploadedFile.getBytes()) : null; } if (type.isArray() && anno.isAnnotationPresent(Input.MultiValued.class)) { // return type is an array; grab all Object o = inputParams.get(name); return type.cast(parseMultiValue(type, o, anno)); } String v = Strings.extract(inputParams.get(name)); // boolean is used for checkboxes, and false is encoded as a missing value if (type == Boolean.class || type == Boolean.TYPE) { @SuppressWarnings("unchecked") X x = (X) Boolean.valueOf(v != null && !"false".equalsIgnoreCase(v)); return x; } // the remaining types have proper null values if (v == null) return null; return cast(parseSingleValue(type, v, anno, inputArgParsers)); } /** @return the given value parsed as the given {@link Class} type */ public <X> X parsePrimitiveValue(String v, Class<X> type) { return cast(parseSingleValue(type, v, null, null)); } @SuppressWarnings("unchecked") private <X> X cast(Object o) { // Do not use Class.cast here since it does not work on primitive types return (X) o; } /** * @param rt some kind of array class * @param data null, String or String[] * @return parsed data as per parseSingleValue * @throws AssertionError if parseSingleValue does */ private Object parseMultiValue(Class<?> rt, Object data, AnnotatedElement anno) throws AssertionError { String[] xs; // normalize the zero-and-one cases if (data == null) { xs = new String[0]; } else if (data instanceof String[]) { xs = (String[]) data; } else { xs = new String[] { data.toString() }; } Class<?> comp = rt.getComponentType(); Object arr = Array.newInstance(rt.getComponentType(), xs.length); for (int i = 0; i < xs.length; i++) { Array.set(arr, i, parseSingleValue(comp, xs[i], anno, inputArgParsers)); } return arr; } private static Object parseSingleValue(Class<?> rt, String v, AnnotatedElement anno, Map<Class<?>, InputArgParser<?>> inputArgParsers) { if (rt.isEnum()) { String vlow = v.toLowerCase(); for (Enum e : rt.asSubclass(Enum.class).getEnumConstants()) { if (e.name().toLowerCase().equals(vlow)) return e; } throw new AssertionError("Enum constant not found: " + v); } else if (rt == Integer.class) { // map blank strings to null return Strings.hasContent(v) ? Integer.valueOf(v) : null; } else if (rt == Integer.TYPE) { // primitive int must have a value return Integer.valueOf(v); } else if (rt == Long.class) { // map blank strings to null return Strings.hasContent(v) ? Long.valueOf(v) : null; } else if (rt == Long.TYPE) { // primitive long must have a value return Long.valueOf(v); } else if (rt == Double.class) { // map blank strings to null return Strings.hasContent(v) ? Double.valueOf(v) : null; } else if (rt == Double.TYPE) { // primitive double must have a value return Double.valueOf(v); } else if (rt == String.class) { return v; } else if (rt.isArray()) { Input.List ann = anno.getAnnotation(Input.List.class); if (ann == null) throw new AssertionError("Array type but no annotation (see " + Input.class + "): " + anno); String separator = ann.separator(); String[] strVals = v.split(separator, -1); Class<?> arrayType = rt.getComponentType(); Object a = Array.newInstance(arrayType, strVals.length); for (int i = 0; i < strVals.length; i++) { Array.set(a, i, parseSingleValue(arrayType, strVals[i], anno, inputArgParsers)); } return a; } else if (inputArgParsers != null) { InputArgParser<?> argParser = inputArgParsers.get(rt); if (argParser != null) { return argParser.parse(v); } } throw new AssertionError("Unknown return type " + rt + " (val: " + v + ")"); } private static final AnnotatedElement emptyAnnotatedElement = new AnnotatedElement() { @Override public <T extends Annotation> T getAnnotation(Class<T> annotationType) { return null; } @Override public Annotation[] getAnnotations() { return null; } @Override public Annotation[] getDeclaredAnnotations() { return null; } @Override public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) { return false; } }; }