Package dk.brics.jwig.server

Source Code of dk.brics.jwig.server.RequestManager

package dk.brics.jwig.server;

import dk.brics.jwig.*;
import dk.brics.jwig.persistence.Persistable;
import dk.brics.jwig.persistence.Querier;
import dk.brics.jwig.util.ParameterNamer;
import dk.brics.xact.XML;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;

/**
* Request manager for a web application.
*/
public class RequestManager {

    private static final Logger log = Logger.getLogger(RequestManager.class);

    private WebApp webapp;

    private PatternMatcher patternmatcher;

    private List<RegisteredMethod> webmethods;

    private ParamNameGetter paramnamegetter;

    private Map<Class<? extends WebContext>, RegisteredMethod> handlermethods;

    private Map<Class<? extends WebContext>, RegisteredMethod> handlerValidatorMethods;

    private javax.mail.Session emailsession;

    private String[] webapp_paramnames;

    /**
     * Constructs an uninitialized request manager object.
     */
    public RequestManager() {
    }

    /**
     * Constructs a new request manager for the given web app. Finds the web
     * methods and creates pattern matchers.
     */
    public RequestManager(WebApp webapp) {
        this.webapp = webapp;

        // configuration
        handlermethods = new HashMap<Class<? extends WebContext>, RegisteredMethod>();
        handlerValidatorMethods = new HashMap<Class<? extends WebContext>, RegisteredMethod>();
        Class<? extends WebApp> webapp_class = webapp.getClass();
        log.info("Initializing web app: " + webapp_class.toString());

        // find web methods
        introspectWebAppClass(webapp_class);

        // initialize javamail session
        Properties props = new Properties();
        for (Map.Entry<String, Object> e : webapp.getProperties().entrySet()) {
            if (e.getKey().startsWith("mail.")) {
                props.put(e.getKey(), e.getValue());
            }
        }
        if (!props.isEmpty()) {
            log.debug("Initializing email session " + props);
            emailsession = javax.mail.Session.getInstance(props, null); // need
                                                                        // context.xml
                                                                        // for
                                                                        // Tomcat
                                                                        // auto-deploy
        }
    }

    /**
     * Finds the web methods for the given web app class. Also creates a pattern
     * matcher for the web app and finds the web app parameter names.
     */
    public void introspectWebAppClass(Class<? extends WebApp> webapp_class) {
        String urlpattern;
        URLPattern up = webapp_class.getAnnotation(URLPattern.class);
        if (up != null) {
            urlpattern = up.value();
        } else {
            urlpattern = webapp_class.getCanonicalName().replace('.', '/');
        }
        webapp_paramnames = getWebAppParameterNames(
                getSuperClasses(webapp_class)).toArray(new String[0]);
        try {
            patternmatcher = new PatternMatcher(urlpattern, false, true);
        } catch (IllegalArgumentException e) {
            throw new JWIGException("Unable to initialize "
                    + webapp_class.getCanonicalName(), e);
        }
        paramnamegetter = new ParamNameGetter();
        webmethods = new ArrayList<RegisteredMethod>();
        for (Method m : webapp_class.getMethods()) {
            Class<?> returntype = m.getReturnType();
            if (Modifier.isPublic(m.getModifiers())
                    && m.getAnnotation(IgnoreWebMethod.class) == null
                    && !Modifier.isStatic(m.getModifiers())
                    && (returntype.equals(Void.TYPE)
                            || returntype.equals(String.class)
                            || returntype.equals(URL.class)
                            || returntype.equals(XML.class) || m
                            .getAnnotation(URLPattern.class) != null)
                    && m.getDeclaringClass() != Object.class) {
                String p = getMethodURLPattern(m);
                try {
                    PatternMatcher matcher = new PatternMatcher(p, true, false);
                    float priority = getMethodPriority(m, matcher);
                    log.debug("Found web method: " + m.toGenericString()
                            + ", priority=" + priority);
                    Method previouslyRegistered = findWebMethodByName(m
                            .getName());
                    if (previouslyRegistered == null) {
                        webmethods.add(new RegisteredMethod(this, m,
                                paramnamegetter.getParameterNames(m), matcher,
                                priority, getSupportedHttpMethods(m)));
                    } else {
                        throw new JWIGException(
                                String.format(
                                        "Web method %s is overloaded (%s was previously found). This is not allowed in JWIG. If you need two web methods"
                                                + "to match the same URL, you must use a @URLPattern annotation instead",
                                        m.getDeclaringClass()
                                                .getCanonicalName()
                                                + "#"
                                                + m.getName(),
                                        previouslyRegistered
                                                .getDeclaringClass()
                                                .getCanonicalName()
                                                + "#"
                                                + previouslyRegistered
                                                        .getName()));
                    }
                } catch (IllegalArgumentException e) {
                    throw new JWIGException("Unable to initialize "
                            + m.toGenericString(), e);
                }
            }
        }
        Collections.sort(webmethods);
    }

    private Method findWebMethodByName(String methodName) {
        for (RegisteredMethod m : getWebMethods()) {
            if (m.getMethod().getName().equals(methodName))
                return m.getMethod();
        }
        return null;
    }

    private Set<String> getSupportedHttpMethods(Method m) {
        boolean none = true;
        Set<String> methods = new HashSet<String>();
        if (m.getAnnotation(GET.class) != null) {
            methods.add("GET");
            none = false;
        }
        if (m.getAnnotation(POST.class) != null) {
            methods.add("POST");
            none = false;
        }
        if (m.getAnnotation(HEAD.class) != null) {
            methods.add("HEAD");
            none = false;
        }
        if (m.getAnnotation(TRACE.class) != null) {
            methods.add("TRACE");
            none = false;
        }
        if (m.getAnnotation(HEAD.class) != null) {
            methods.add("HEAD");
            none = false;
        }
        if (m.getAnnotation(PUT.class) != null) {
            methods.add("PUT");
            none = false;
        }
        if (m.getAnnotation(DELETE.class) != null) {
            methods.add("DELETE");
            none = false;
        }
        if (m.getAnnotation(OPTIONS.class) != null) {
            methods.add("OPTIONS");
            none = false;
        }
        if (none) {
            methods.add("GET");
            methods.add("POST"); // TODO: only GET?
        }
        return methods;
    }

    /**
     * Finds the web app parameter names.
     */
    private List<String> getWebAppParameterNames(
            List<Class<? extends WebApp>> webapp_classes) {
        LinkedList<String> names = new LinkedList<String>();
        for (Class<? extends WebApp> webapp_class : webapp_classes) {
            String[] paramnames;
            URLPattern pn = webapp_class.getAnnotation(URLPattern.class);
            if (pn != null) {
                PatternMatcher pm = new PatternMatcher(pn.value(), false, true);
                List<String> strings = pm.getParameters();
                paramnames = strings.toArray(new String[strings.size()]);
            } else {
                paramnames = new String[0];
            }
            names.addAll(Arrays.asList(paramnames));
        }
        return names;
    }

    /**
     * Finds the URL pattern string of the given web method (using the method
     * name by default).
     */
    private String getMethodURLPattern(Method m) {
        URLPattern a = m.getAnnotation(URLPattern.class);
        if (a != null) {
            return a.value();
        } else {
            return m.getName();
        }
    }

    /**
     * Finds the priority of the given web method (or computes the default value
     * from the URL pattern and return type).
     */
    private float getMethodPriority(Method m, PatternMatcher pm) {
        float pri;
        Priority a = m.getAnnotation(Priority.class);
        if (a != null) {
            pri = a.value();
        } else {
            pri = pm.computeDefaultPriority();
            if (m.getReturnType().equals(Void.TYPE)) {
                pri += WebContext.PRE_CACHE;
            }
        }
        return pri;
    }

    /**
     * Returns the web app of this web app request manager.
     */
    public WebApp getWebApp() {
        return webapp;
    }

    /**
     * Returns the list of web methods.
     */
    public List<RegisteredMethod> getWebMethods() {
        return webmethods;
    }

    /**
     * Returns the email session.
     */
    public javax.mail.Session getEmailSession() {
        return emailsession;
    }

    /**
     * Returns true if this web app may match requests to the given url
     */
    public boolean matches(String path) {
        HashMap<String, String> webapp_params = new HashMap<String, String>();
        int i = patternmatcher.matchPrefix(path, webapp_params);
        boolean matched = i != -1;
        if (matched) {
            ThreadContext.get().createInvocationContext(this, webapp,
                    webapp_params, i);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Invokes the handler <code>run</code> method.
     */
    public Object invokeHandlerMethod(AbstractHandler handler, String referer) {
        ThreadContext c = ThreadContext.get();
        Class<? extends WebContext> handlerclass = handler.getClass();
        RegisteredMethod rm1 = handlermethods.get(handlerclass);
        if (rm1 == null) {
            Method m = null;
            for (Method dm : handlerclass.getDeclaredMethods()) {
                if (dm.getName().equals("run")) {
                    m = dm;
                    break;
                }
            }
            rm1 = m != null ? new RegisteredMethod(this, m,
                    paramnamegetter.getParameterNames(m),
                    getSupportedHttpMethods(m)) : null;
            handlermethods.put(handlerclass, rm1);
        }
        RegisteredMethod rm = rm1;
        if (rm == null) {
            throw new JWIGException("Handler method not found");
        }
        Object[] args = match(rm, null);
        if (args == null) {
            throw new JWIGException("Handler method arguments mismatch");
        }
        for (Session s : c.getResponse().getSessions()) {
            s.refresh();
        }
        Method method = rm.getMethod();
        invoke(method, handler, args);
        ThreadContext threadContext = ThreadContext.get();
        Object result = threadContext.getCurrentResult();
        threadContext.setReferer(referer);
        if (result == null) {
            try {
                result = new URL(referer);
            } catch (MalformedURLException e) {
                // Won't happen
            }
        }
        return result;
    }

    public Object invokeHandlerValidationMethod(SubmitHandler handler) {
        ThreadContext c = ThreadContext.get();
        Class<? extends WebContext> handlerclass = handler.getClass();
        RegisteredMethod registeredValidatorMethod = handlerValidatorMethods.get(handlerclass);
        if (registeredValidatorMethod == null) {
            Method validator = null;
            for (Method method : handlerclass.getDeclaredMethods()) {
                if (method.getName().equals("validate")) {
                    validator = method;
                    break;
                }
            }
            //It is OK to not have a validator
            if (validator == null) {
                return null;
            }
            registeredValidatorMethod = new RegisteredMethod(this, validator,
                                                             paramnamegetter.getParameterNames(validator),
                                                             getSupportedHttpMethods(validator));
            handlerValidatorMethods.put(handlerclass, registeredValidatorMethod);
        }
        Object[] args = match(registeredValidatorMethod, null);
        if (args == null) {
            throw new JWIGException("Handler method arguments mismatch");
        }
        for (Session s : c.getResponse().getSessions()) {
            s.refresh();
        }
        invoke(registeredValidatorMethod.getMethod(), handler, args);
        Object currentResult = c.getCurrentResult();
        if (currentResult == null) return null;
        return XML.parseTemplate("<div><[C]></div>").plug("C", currentResult);
    }

    /**
     * Invokes the given web/handler method.
     */
    void invoke(Method m, Object thisobj, Object[] args) {
        try {
            log.debug("Invoking " + m.toString());
            /*
             * log.debug("  this-type: " + thisobj.getClass()); for (Object o :
             * args) log.debug("  arg-type: " + (o == null ? "null" :
             * o.getClass()));
             */
            m.setAccessible(true);
            Object result = m.invoke(thisobj, args);
            if (thisobj instanceof EventHandler) {
                ((EventHandler) thisobj)
                        .setLatestReturnValue(result != null ? XML
                                .toXML(result) : null);
            }
            if (result != null) {
                ThreadContext.get().setCurrentResult(result);
                if (isCacheAugmented(m)) {
                    ThreadContext.get().setCacheAugmented(true);
                }
            }
        } catch (IllegalArgumentException e) {
            ThreadContext.get().setThrowable(new JWIGException(e));
        } catch (IllegalAccessException e) {
            ThreadContext.get().setThrowable(new JWIGException(e));
        } catch (InvocationTargetException e) {
            Throwable t = e;
            while ((t instanceof InvocationTargetException || t instanceof JWIGException)
                    && t.getCause() != null) {
                t = t.getCause();
            }
            RuntimeException r;
            if (t instanceof JWIGException) {
                r = (JWIGException) t;
            } else if (t instanceof RuntimeException) {
                r = (RuntimeException) t;
            } else {
                r = new JWIGException(t);
            }
            ThreadContext.get().setThrowable(r);
        } catch (SecurityException e) {
            ThreadContext.get().setThrowable(new JWIGException(e));
        }
    }

    private boolean isCacheAugmented(Method m) {
        return m.getAnnotation(AugmentedCache.class) != null
                || m.getDeclaringClass().getAnnotation(AugmentedCache.class) != null;
    }

    /**
     * Checks whether the given method matches the given path and builds
     * argument array (combined from the path parameters and ordinary servlet
     * parameters). Returns null if no match.
     */
    Object[] match(RegisteredMethod rm, String path) {
        Map<String, String> path_params = new HashMap<String, String>();
        if (path != null && !rm.getPatternMatcher().match(path, path_params)) {
            return null;
        }
        Map<String, Object[]> servletparams = new HashMap<String, Object[]>(ThreadContext.get().getServletParams());
        //The 'submit' parameter is called 'jwig_submit' on the client side to avoid a naming clash
        if (servletparams.containsKey("jwig_submit"))
            servletparams.put("submit",servletparams.remove("jwig_submit"));
        if (!matchHttpMethod(rm)) {
            return null;
        }
        Map<String, Object[]> params;
        if (path_params.isEmpty()) {
            params = servletparams;
        } else {
            params = new HashMap<String, Object[]>();
            Set<String> remaining = new HashSet<String>(path_params.keySet());
            for (Map.Entry<String, Object[]> entry : servletparams.entrySet()) {
                if (!path_params.containsKey(entry.getKey())) {
                    params.put(entry.getKey(), entry.getValue()); // throwing
                                                                  // away
                                                                  // servlet
                                                                  // parameters
                                                                  // whose names
                                                                  // also appear
                                                                  // as path
                                                                  // parameters
                }
            }
            for (String n : remaining) {
                Object[] v = new Object[1];
                v[0] = path_params.get(n);
                params.put(n, v);
            }
        }
        return buildMethodArgs(rm, params);
    }

    private boolean matchHttpMethod(RegisteredMethod rm) {
        String method = ThreadContext.get().getServletRequest().getMethod();
        return rm.getHttpMethods().contains(method);
    }

    /**
     * Builds argument array for a web method and actuals.
     */
    private Object[] buildMethodArgs(RegisteredMethod rm,
            Map<String, Object[]> actuals) {
        Method m = rm.getMethod();
        Class<?>[] formal_types = m.getParameterTypes();
        String[] formal_names = rm.getParameterNames();
        Object[] args = new Object[formal_names.length];
        Map<String, Object[]> actuals2 = new HashMap<String, Object[]>(actuals);
        for (int i = 0; i < formal_names.length; i++) {
            Object arg;
            if (formal_types[i].equals(Parameters.class)) {
                arg = buildParameters(actuals2); // TODO: avoid rebuilding all
                                                 // params multiple times per
                                                 // request
            } else {
                Annotation[] parameterAnnotations = m.getParameterAnnotations()[i];
                boolean required = false;
                for (Annotation a : parameterAnnotations) {
                    if (a.annotationType().equals(RequiredParameter.class)) {
                        required = true;
                    }
                }
                arg = buildMethodArg(actuals2.remove(formal_names[i]),
                        formal_types[i], formal_names[i], m, i, required);
            }
            args[i] = arg;
        }
        if (!actuals2.isEmpty()) {
            log.debug("Ignoring parameters: " + actuals2.keySet());
        }
        return args;
    }

    /**
     * Builds <code>Parameters</code> object from all remaining actuals.
     */
    private Parameters buildParameters(Map<String, Object[]> actuals) {
        LinkedHashMap<String, List<FormField>> argmap = new LinkedHashMap<String, List<FormField>>();
        for (Map.Entry<String, Object[]> e : actuals.entrySet()) {
            List<FormField> list = new ArrayList<FormField>();
            for (Object o : actuals.get(e.getKey())) {
                if (o instanceof String) {
                    list.add(new TextField((String) o));
                } else if (o instanceof FileField) {
                    list.add((FileField) o);
                } else {
                    throw new JWIGException("Unexpected object type");
                }
            }
            argmap.put(e.getKey(), list);
        }
        actuals.clear();
        return new Parameters(argmap);
    }

    /**
     * Builds a single web method argument given a type and actuals.
     */
    @SuppressWarnings("unchecked")
    private Object buildMethodArg(Object[] actuals, Class<?> type, String name,
            Method method, int param, boolean required) {
        final Class<?> retyped = retype(type);
        if (retyped != null)
            type = retyped;

        boolean valueMissing = actuals == null || actuals.length == 0;
        if (name.equals("submit") && valueMissing) {
            throw new JavascriptDisabledException();
        } else if (required && valueMissing) {
            throw new MissingParameterException(name);
        }
        boolean is_array = type.isArray();
        boolean is_collection = Collection.class.isAssignableFrom(type);
        Class<? extends Collection<?>> collection_type = null;
        if (is_array) {
            type = type.getComponentType();
        } else if (is_collection) {
            collection_type = (Class<? extends Collection<?>>) type
                    .asSubclass(Collection.class);
            type = ParameterNamer.getListType(method, param);
        }
        return deserializeArgument(actuals, type, name, is_array,
                is_collection, collection_type);
    }

    private Class<?> retype(Class<?> type) {
        WebApp context = WebApp.get();
        try {
            Method m = context.getClass().getMethod("retype", type);
            if (m == null) {
                // this will never happen, a NoSuchMethodException will be
                // thrown instead!
                return null;
            }
            Class<?> returnType = m.getReturnType();
            if (returnType == Class.class) {
                // invoke a method called "retype" with a single formal
                // parameter of type `type`. The method is invoked with null as
                // concrete argument! This makes sense as the method acts as a
                // map between two types (and thus does not need the value of
                // the actual input), but we want it to look like the access
                // methods.

                // if the method returns null, it has the effect of ignoring the
                // retyping rule
                return (Class<?>) m.invoke(context, new Object[] { null });
            } else {
                return null;
            }

        } catch (NoSuchMethodException e) {
            return null; // on purpose!
        } catch (Exception e) {
            throw new JWIGException(e);
        }
    }

    @SuppressWarnings("unchecked")
    public <E> E deserializeArgument(Object[] actuals, Class<E> type,
            String name) {
        return (E) deserializeArgument(actuals, type, name, false, false, null);
    }

    /**
     * Deserializes an argument, i.e. converts it from string to object.
     */
    public Object deserializeArgument(Object[] actuals, Class<?> type,
            String name, boolean is_array, boolean is_collection,
            Class<? extends Collection<?>> collection_type) {
        boolean is_compound = is_array || is_collection;
        boolean is_primitive = type.isPrimitive();
        String t = type.getName();
        if (is_primitive) {
            if (t.equals("float")) {
                type = Float.class;
            } else if (t.equals("double")) {
                type = Double.class;
            } else if (t.equals("byte")) {
                type = Byte.class;
            } else if (t.equals("short")) {
                type = Short.class;
            } else if (t.equals("int")) {
                type = Integer.class;
            } else if (t.equals("long")) {
                type = Long.class;
            } else if (t.equals("char")) {
                type = Character.class;
            } else if (t.equals("boolean")) {
                type = Boolean.class;
            }
        }
        if (actuals == null) {
            actuals = (Object[]) Array.newInstance(type, 0);
        }
        if (!is_compound) {
            if (actuals.length < 1) {
                if (is_primitive || Session.class.isAssignableFrom(type)) { // TODO:
                                                                            // other
                                                                            // argument
                                                                            // types
                                                                            // require
                                                                            // NonNull?
                    if (type == Boolean.class) {
                        return false; // if a checkbox is unchecked, no value is
                                      // sent
                    }
                    throw new BadRequestException("missing parameter");
                } else {
                    return null;
                }
            }
            if (actuals.length > 1) {
                log.debug("Ignoring additional parameters: " + name);
            }
        }
        if (type.equals(String.class) || FileField.class.isAssignableFrom(type)) {
            if (is_array) {
                Object[] actuals_correct_type = (Object[]) Array.newInstance(
                        type, actuals.length);
                System.arraycopy(actuals, 0, actuals_correct_type, 0,
                        actuals.length);
                return actuals_correct_type;
            } else if (is_collection) {
                return createCollection(collection_type, actuals);
            } else {
                Object actual = actuals[0];
                if (actual instanceof FileField
                        && String.class.isAssignableFrom(type)) {
                    throw new BadRequestException(
                            "You sent a binary field as the "
                                    + name
                                    + " parameter in the request, but a string field was expected");
                }
                if (actual instanceof String
                        && FileField.class.isAssignableFrom(type)) {
                    throw new BadRequestException(
                            "You sent a string field as the "
                                    + name
                                    + " parameter in the request, but a binary field was expected");
                }
                return actual;
            }
        }
        if (!is_compound) {
            if (Persistable.class.isAssignableFrom(type)) {
                return getPersistable(actuals[0],
                        type.asSubclass(Persistable.class),name);
            }
            if (Boolean.class.isAssignableFrom(type)) {
                String value = (String) actuals[0];
                if (value.equals("on")) { // for checkboxes
                    return true;
                }
            }
        }
        try {
            if (Persistable.class.isAssignableFrom(type)) {
                Object[] arg = (Object[]) Array.newInstance(type,
                        actuals.length);
                for (int i = 0; i < actuals.length; i++) {
                    arg[i] = getPersistable(actuals[i],
                            type.asSubclass(Persistable.class),name);
                }
                if (is_array) {
                    return arg;
                } else if (is_collection) {
                    return createCollection(collection_type, arg);
                } else {
                    return arg[0];
                }
            } else if (JSONObject.class.isAssignableFrom(type)) { // For web
                                                                  // services we
                                                                  // want to
                                                                  // support
                                                                  // JSON, the
                                                                  // official
                                                                  // implementation
                                                                  // does not
                                                                  // have a
                                                                  // valueOf
                                                                  // method
                String value = (String) actuals[0];
                try {
                    return new JSONObject(value);
                } catch (JSONException e) {
                    throw new BadRequestException(e.getMessage(), e);
                }
            } else if (URL.class.isAssignableFrom(type)) {
                try {
                    return new URL((String) actuals[0]);
                } catch (MalformedURLException e) {
                    throw new BadRequestException(e.getMessage(), e);
                }
            } else {
                Method valueOf = type.getMethod("valueOf", String.class);
                if (is_array) {
                    Object[] arg = createFromValueOf(actuals, type, valueOf);
                    if (is_primitive) {
                        return setPrimitive(t, arg);
                    } else {
                        return arg;
                    }
                } else if (is_collection) {
                    Object[] arg = createFromValueOf(actuals, type, valueOf);
                    return createCollection(collection_type, arg);
                } else {
                    Object invoke;
                    try {
                        invoke = valueOf.invoke(null, actuals[0]);
                    } catch (InvocationTargetException e) {
                        if (e.getCause() instanceof IllegalArgumentException) {
                            throw new BadRequestException(String.format("Argument %s has incorrect format: %s", name, actuals[0]));
                        } else {
                            throw e;
                        }
                    }
                    return invoke;
                }
            }
        } catch (NoSuchMethodException e) {
            throw new JWIGException("valueOf not found", e); // unreachable,
                                                             // detected during
                                                             // init
        } catch (IllegalAccessException e) {
            throw new JWIGException(e);
        } catch (InvocationTargetException e) {
            Throwable te = e.getCause();
            if (te instanceof JWIGException) {
                throw (JWIGException) te;
            }
            throw new BadRequestException(te.toString(), te);
        }
    }

    /**
     * Creates objects by invoking <code>valueOf</code> on the given actuals.
     */
    private Object[] createFromValueOf(Object[] actuals, Class<?> type,
            Method valueOf) throws IllegalAccessException,
            InvocationTargetException {
        Object[] arg = (Object[]) Array.newInstance(type, actuals.length);
        for (int i = 0; i < actuals.length; i++) {
            arg[i] = valueOf.invoke(null, actuals[i]);
        }
        return arg;
    }

    /**
     * Creates a collection for the given objects.
     */
    private Collection<?> createCollection(
            Class<? extends Collection<?>> collection_type, Object[] args) {
        try {
            collection_type = getDefaultImplementation(collection_type);
            Constructor<? extends Collection<?>> constructor = collection_type
                    .getConstructor(Collection.class); // all collections must
                                                       // declare such a
                                                       // constructor
            return constructor.newInstance(Arrays.asList(args));
        } catch (Exception e) {
            throw new JWIGException(e);
        }
    }

    /**
     * Converts boxed primitives to unboxed.
     */
    private Object setPrimitive(String t, Object[] arg) {
        Object res = null;
        if (t.equals("float")) {
            res = new float[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setFloat(res, i, (Float) arg[i]);
            }
        } else if (t.equals("double")) {
            res = new double[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setDouble(res, i, (Double) arg[i]);
            }
        } else if (t.equals("byte")) {
            res = new byte[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setByte(res, i, (Byte) arg[i]);
            }
        } else if (t.equals("short")) {
            res = new short[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setShort(res, i, (Short) arg[i]);
            }
        } else if (t.equals("int")) {
            res = new int[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setInt(res, i, (Integer) arg[i]);
            }
        } else if (t.equals("long")) {
            res = new long[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setLong(res, i, (Long) arg[i]);
            }
        } else if (t.equals("char")) {
            res = new char[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setChar(res, i, (Character) arg[i]);
            }
        } else if (t.equals("boolean")) {
            res = new boolean[arg.length];
            for (int i = 0; i < arg.length; i++) {
                Array.setBoolean(res, i, (Boolean) arg[i]);
            }
        }
        return res;
    }

    /**
     * Gets default implementations for some interfaces so they may be used
     * directly as a formal parameter. The implementations are as follows:
     * <table>
     * <tr>
     * <th>Interface</th>
     * <th>Default implementation</th>
     * </tr>
     * <tr>
     * <td>java.util.List</td>
     * <td>java.util.ArrayList</td>
     * </tr>
     * <tr>
     * <td>java.util.Set</td>
     * <td>java.util.HashSet</td>
     * </tr>
     * <tr>
     * <td>java.util.Queue</td>
     * <td>java.util.LinkedList</td>
     * </tr>
     * <tr>
     * <td>java.util.Deque</td>
     * <td>java.util.LinkedList</td>
     * </tr>
     * <tr>
     * <td>java.util.SortedSet</td>
     * <td>java.util.TreeSet</td>
     * </tr>
     * </table>
     */
    @SuppressWarnings("unchecked")
    private Class<? extends Collection<?>> getDefaultImplementation(
            Class<? extends Collection<?>> type) {
        Class<?> impl;
        if (type.equals(List.class)) {
            impl = ArrayList.class;
        } else if (type.equals(Set.class)) {
            impl = HashSet.class;
        } else if (type.equals(Deque.class)) {
            impl = LinkedList.class;
        } else if (type.equals(SortedSet.class)) {
            impl = TreeSet.class;
        } else if (type.equals(Queue.class)) {
            impl = LinkedList.class;
        } else {
            impl = type;
        }
        return (Class<? extends Collection<?>>) impl;
    }

    /**
     * Finds a persistable object via the persistable querier.
     */
    private Object getPersistable(Object actual,
            Class<? extends Persistable> type, String parameterName) {
        Class<? extends Persistable> ptype = type.asSubclass(Persistable.class);
        Querier querier = ThreadContext.getWebSite().getQuerier();
        int id = -1;
        Method p = findQueryMethod(ptype);
        if ("".equals(actual)) {
            return null;
        }
        if (p != null) {
            String name = p.getName();
            name = name.substring(3, 4).toLowerCase() + name.substring(4);
            id = querier.getIdFromProperty(ptype, name, (String) actual);
        } else {
            try {
                id = Integer.parseInt(String.valueOf(actual));
            } catch (NumberFormatException e) {
                throw new BadRequestException(String.format("Expected '%s' to be an integer, but it was %s", parameterName, actual));
            }
        }
        return querier.getObject(ptype, id);
    }

    private static Method findQueryMethod(Class<? extends Persistable> ptype) {
        Method p = null;
        for (Method m : ptype.getMethods()) {
            if (m.getAnnotation(QueryProperty.class) != null) {
                p = m;
            }
        }
        return p;
    }

    /**
     * Generates a URL for the given web method within the same web site. The
     * URL uses <code>https</code> if the current request is on a secure
     * connection.
     */
    public URL makeURL(Map<String, ?> webapp_params, String method,
            Object... args) {
        return makeURL(ThreadContext.get().getServletRequest().isSecure(),
                webapp_params, method, args);
    }

    /**
     * Generates a URL for the given web method within the same web site. The
     * URL uses <code>https</code> if the current request is on a secure
     * connection.
     */
    public URL makeURL(String method, Object... args) {
        return makeURL(ThreadContext.get().getServletRequest().isSecure(),
                new HashMap<String, Object>(), method, args);
    }

    /**
     * Generates a URL for the given web method within the same web site.
     */
    public URL makeURL(boolean secure, Map<String, ?> webapp_params,
            String method, Object... args) {
        RequestManager request_manager = null;
        int i = method.lastIndexOf('.');
        String methodname;
        if (i != -1) { // FIXME: drop qualifying class name for a class
                       // constant?
            String classname = method.substring(0, i);
            methodname = method.substring(i + 1);
            for (RequestManager h : ThreadContext.getRequestManagers()) {
                if (classname.equals(h.webapp.getClass().getCanonicalName())) {
                    request_manager = h;
                    break;
                }
            }
            if (request_manager == null) {
                throw new JWIGException("web app not found: " + classname);
            }
        } else {
            request_manager = this;
            methodname = method;
        }
        Map<String, String> exisiting_webapp_params;
        if (webapp_params != null) {
            exisiting_webapp_params = ThreadContext.get().getWebAppParams();
        } else {
            exisiting_webapp_params = new HashMap<String, String>();
            webapp_params = new HashMap<String, Object>();
        }
        for (RegisteredMethod rm : request_manager.webmethods) {
            if (rm.getMethod().getName().equals(methodname)) {
                Map<String, String[]> argmap = new HashMap<String, String[]>();
                String[] paramnames = rm.getParameterNames();
                for (int j = 0; j < args.length; j++) {
                    if (args[j] instanceof Parameters) {
                        Parameters ps = (Parameters) args[j];
                        Map<String, List<FormField>> psmap = ps.getMap();
                        for (Map.Entry<String, List<FormField>> me : psmap
                                .entrySet()) {
                            String[] values = new String[me.getValue().size()];
                            int k = 0;
                            for (FormField ff : me.getValue()) {
                                if (!(ff instanceof TextField)) {
                                    throw new JWIGException(
                                            "non-TextField in Parameters arguments for makeURL");
                                }
                                values[k++] = ff.getValue();
                            }
                            if (argmap.containsKey(me.getKey())) {
                                throw new JWIGException(
                                        "argument map already contains "
                                                + me.getKey());
                            }
                            argmap.put(me.getKey(), values);
                        }
                    } else {
                        String[] values;
                        if (args[j] instanceof Object[]) {
                            Object[] as = (Object[]) args[j];
                            values = new String[as.length];
                            for (int k = 0; k < as.length; k++) {
                                values[k] = makeURLArg(as[k]);
                            }
                        } else {
                            values = new String[1];
                            values[0] = makeURLArg(args[j]);
                        }
                        if (j >= paramnames.length) {
                            throw new JWIGException(
                                    "too many arguments. Expected "
                                            + paramnames.length + " got " + j
                                            + ". At: " + method + "(" + args
                                            + ")");
                        } else {
                            if (argmap.containsKey(paramnames[j])) {
                                throw new JWIGException(
                                        "argument map already contains "
                                                + paramnames[j]);
                            }
                            argmap.put(paramnames[j], values);
                        }
                    }
                }
                try {
                    WebApp containing_app = request_manager.getWebApp();
                    WebApp this_webapp = getWebApp();
                    List<Class<? extends WebApp>> common_super_classes = getSuperClasses(containing_app
                            .getClass());
                    common_super_classes.retainAll(getSuperClasses(this_webapp
                            .getClass()));
                    List<String> commonParameters = getWebAppParameterNames(common_super_classes);
                    for (Map.Entry<String, String> e : exisiting_webapp_params
                            .entrySet()) {
                        String key = e.getKey();
                        if (commonParameters.contains(key)) {
                            argmap.put(key, new String[] { e.getValue() });
                        }
                    }
                    List<String> list = getWebAppParameterNames(getSuperClasses(containing_app
                            .getClass()));
                    for (Map.Entry<String, ?> e : webapp_params.entrySet()) {
                        String paramName = e.getKey();
                        if (!list.contains(paramName)) {
                            throw new JWIGException("Web app " + containing_app
                                    + " does not take parameter " + paramName);
                        }
                        argmap.put(paramName,
                                new String[] { makeURLArg(e.getValue()) });
                    }
                    return new URL(request_manager.getWebAppURL(secure, argmap)
                            + rm.getPatternMatcher().makeURL(argmap, true));
                } catch (MalformedURLException e) {
                    throw new JWIGException(e);
                }
            }
        }
        throw new JWIGException("web method not found: " + methodname);
    }

    /**
     * Returns the super classes of the given web app class.
     */
    @SuppressWarnings("unchecked")
    private List<Class<? extends WebApp>> getSuperClasses(
            Class<? extends WebApp> webapp) {
        if (webapp.isAssignableFrom(WebApp.class)) {
            return new LinkedList<Class<? extends WebApp>>();
        } else {
            List<Class<? extends WebApp>> list = getSuperClasses((Class<? extends WebApp>) webapp
                    .getSuperclass());
            list.add(webapp);
            return list;
        }
    }

    /**
     * Converts a single web method argument into its string representation.
     */
    public static String makeURLArg(Object arg) { // TODO: Move method?
        Map<Object, String> map = ThreadContext.get().getCachedPropertyValues();
        String s = "";
        if (arg instanceof Persistable) {
            Persistable persistable = (Persistable) arg;
            if (map.containsKey(persistable)) {
                s = map.get(persistable);
            } else {
                Class<? extends Persistable> persistableClass = ThreadContext
                        .getWebSite().getQuerier().getBaseType(persistable);
                Method method = findQueryMethod(persistableClass);
                if (method != null) {
                    String name = method.getName();
                    try {
                        // Prefer calling the getter method to use cache if
                        // possible
                        name = name.substring(3);
                        String getterName = "get" + name;
                        Method getterMethod = persistableClass
                                .getMethod(getterName);
                        s = getterMethod.invoke(persistable).toString();
                    } catch (Exception e) {
                        name = name.substring(3, 4).toLowerCase()
                                + name.substring(4);
                        s = ThreadContext
                                .getWebSite()
                                .getQuerier()
                                .getPropertyFromId(persistableClass, name,
                                        persistable.getId());
                    }
                    map.put(persistable, s);
                } else {
                    s = persistable.getId().toString();
                }
            }
        } else if (arg != null) {
            s = arg.toString();
        } else {
            s = null;
        }
        return s;
    }

    /**
     * Returns the URL prefix for this web app. Uses SSL/TLS if the current
     * request uses SSL/TLS.
     */
    public String getWebAppURL(Map<String, String[]> argMap) {
        return getWebAppURL(ThreadContext.get().getServletRequest().isSecure(),
                argMap);
    }

    /**
     * Returns the URL prefix for the web site.
     *
     * @param secure
     *            if true, use <code>https</code> in the URL otherwise use
     *            <code>http</code>
     */
    public String getWebSiteURL(boolean secure) {
        HttpServletRequest request = ThreadContext.get().getServletRequest();
        StringBuilder b = new StringBuilder();
        String baseurl = ThreadContext.getBaseURL(secure);
        if (baseurl.length() > 0) {
            b.append(baseurl);
        } else { // no jwig.base_url/jwig.base_url_secure set, guess from
                 // request
            String scheme;
            int port;
            if (secure) {
                scheme = "https";
            } else {
                scheme = "http";
            }
            if (scheme.equals(request.getScheme())) {
                port = request.getServerPort();
            } else {
                if (secure) {
                    port = 443;
                } else {
                    port = 80;
                }
            }
            b.append(scheme).append("://").append(request.getServerName());
            if (!((port == 80 && scheme.equals("http")) || (port == 443 && scheme
                    .equals("https")))) {
                b.append(':').append(port);
            }
        }
        b.append(request.getContextPath()).append('/');
        return b.toString();
    }

    /**
     * Returns the URL prefix for this web app.
     *
     * @param secure
     *            if true, use <code>https</code> in the URL, otherwise use
     *            <code>http</code>
     */
    public String getWebAppURL(boolean secure, Map<String, String[]> argMap) {
        return getWebSiteURL(secure) + getRelativeWebAppURL(argMap);
    }

    /**
     * Returns the URL prefix for this web app, relative to the web site URL.
     */
    private String getRelativeWebAppURL(Map<String, String[]> argmap) {
        return patternmatcher.makeURL(argmap, false);
    }

    public PatternMatcher getPatternmatcher() {
        return patternmatcher;
    }
}
TOP

Related Classes of dk.brics.jwig.server.RequestManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.