com.medallia.spider.api.DynamicInputImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.medallia.spider.api.DynamicInputImpl.java

Source

/*
 * 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;
        }
    };

}