All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ro.pippo.core.Request Maven / Gradle / Ivy

There is a newer version: 1.14.0
Show newest version
/*
 * Copyright (C) 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package ro.pippo.core;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.core.route.RouteContext;
import ro.pippo.core.route.RouteDispatcher;
import ro.pippo.core.util.ClassUtils;
import ro.pippo.core.util.CookieUtils;
import ro.pippo.core.util.IoUtils;
import ro.pippo.core.util.StringUtils;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/**
 * Represents a server-side HTTP request. An instance of this class is created
 * for each request.
 *
 * @author Decebal Suiu
 */
public final class Request {

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

    private HttpServletRequest httpServletRequest;
    private ContentTypeEngines contentTypeEngines;
    private Map parameters; // query&post parameters
    private Map pathParameters; // path parameters
    private Map allParameters; // parameters + pathParameters
    private Map files;
    private Session session;
    private String applicationPath;
    private String method;
    private String path;

    private String acceptType;
    private String contentType;
    private String body; // cache

    public Request(HttpServletRequest servletRequest, Application application) {
        this.httpServletRequest = servletRequest;
        this.contentTypeEngines = application.getContentTypeEngines();

        applicationPath = application.getRouter().getApplicationPath();

        // fill (query&post) parameters if any
        initParameters();

        // empty path parameters for now (see setPathParameters method)
        pathParameters = Collections.unmodifiableMap(new HashMap());

        // init all parameters
        initAllParameters();
    }

    /**
     * Returns all parameters (query, post, path).
     */
    public Map getParameters() {
        return allParameters;
    }

    /**
     * Returns one parameter value.
     */
    public ParameterValue getParameter(String name) {
        if (!getParameters().containsKey(name)) {
            return new ParameterValue();
        }

        return getParameters().get(name);
    }

    /**
     * Returns all query&post parameters.
     */
    public Map getQueryParameters() {
        return parameters;
    }

    /**
     * Returns one query parameter value.
     */
    public ParameterValue getQueryParameter(String name) {
        if (!getQueryParameters().containsKey(name)) {
            return new ParameterValue();
        }

        return getQueryParameters().get(name);
    }

    /**
     * Returns all path parameters.
     */
    public Map getPathParameters() {
        return pathParameters;
    }

    /**
     * Returns one path parameter.
     */
    public ParameterValue getPathParameter(String name) {
        if (!getPathParameters().containsKey(name)) {
            return new ParameterValue();
        }

        return getPathParameters().get(name);
    }

    private void initParameters() {
        Map> arrays = new HashMap<>();
        Map tmp = new HashMap<>();
        Enumeration names = httpServletRequest.getParameterNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();

            if (name.matches("(.+)\\[(\\d+)\\]")) {
                // support indexed parameter arrays e.g. setting[0], setting[1], setting[2]
                int brk = name.indexOf('[');
                String base = name.substring(0, brk);

                // try-catch for requests in the form //server/?a[123123123123123123123123123123]
                try {
                    int idx = Integer.parseInt(name.substring(brk + 1, name.length() - 1));
                    if (!arrays.containsKey(base)) {
                        // use an ordered map because we can not rely on parameter
                        // order from the servlet container nor from the request
                        arrays.put(base, new TreeMap<>());
                    }

                    Map map = arrays.get(base);
                    String value = httpServletRequest.getParameterValues(name)[0];
                    map.put(idx, value);
                } catch (NumberFormatException e) {
                    // add as a simple parameter
                    String[] values = httpServletRequest.getParameterValues(name);
                    tmp.put(name, new ParameterValue(values));
                }
            } else {
                String[] values = httpServletRequest.getParameterValues(name);
                tmp.put(name, new ParameterValue(values));
            }
        }

        for (Map.Entry> entry : arrays.entrySet()) {
            // identify maximum specified index
            int maxIndex = 0;
            for (int index : entry.getValue().keySet()) {
                if (index > maxIndex) {
                    maxIndex = index;
                }
            }

            // populate array & respect specified indexes
            // Note: this may not be linear but we must respect that design choice
            String[] values = new String[maxIndex + 1];
            for (Map.Entry indexedValue : entry.getValue().entrySet()) {
                values[indexedValue.getKey()] = indexedValue.getValue();
            }
            tmp.put(entry.getKey(), new ParameterValue(values));
        }

        parameters = Collections.unmodifiableMap(tmp);
    }

    private void initPathParameters(Map map) {
        Map tmp = new HashMap<>();
        if (map != null) {
            Set names = map.keySet();
            for (String name : names) {
                tmp.put(name, new ParameterValue(map.get(name)));
            }
        }

        pathParameters = Collections.unmodifiableMap(tmp);
    }

    private void initAllParameters() {
        Map tmp = new HashMap<>();

        // add query parameters
        tmp.putAll(parameters);

        // add path parameters
        tmp.putAll(pathParameters);

        allParameters = Collections.unmodifiableMap(tmp);
    }

    // INTERNAL, called in (Default)RouteContext.next()
    public void setPathParameters(Map pathParameters) {
        initPathParameters(pathParameters);
        initAllParameters();
    }

    public  T createEntityFromParameters(Class entityClass) {
        T entity;
        try {
            entity = entityClass.newInstance();
        } catch (Exception e) {
            log.error("Cannot create new instance of class '{}'", entityClass.getName(), e);
            return null;
        }

        updateEntityFromParameters(entity);

        return entity;
    }

    @SuppressWarnings("unchecked")
    public  T updateEntityFromParameters(T entity) {
        for (Field field : ClassUtils.getAllFields(entity.getClass())) {
            String parameterName = field.getName();
            Param parameter = field.getAnnotation(Param.class);
            if (parameter != null) {
                parameterName = parameter.value();
            }

            if (getParameters().containsKey(parameterName)) {
                if (!field.isAccessible()) {
                    field.setAccessible(true);
                }

                String pattern = null;
                ParamPattern parameterPattern = field.getAnnotation(ParamPattern.class);
                if (parameterPattern != null) {
                    pattern = parameterPattern.value();
                }

                try {
                    Class fieldClass = field.getType();
                    Object value;
                    if (Collection.class.isAssignableFrom(fieldClass)) {
                        Type parameterType = field.getGenericType();
                        if (!ParameterizedType.class.isAssignableFrom(parameterType.getClass())) {
                            throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}",
                                    field.getName(), fieldClass.getName());
                        }
                        ParameterizedType parameterizedType = (ParameterizedType) parameterType;
                        Class genericClass;
                        try {
                            genericClass = (Class) parameterizedType.getActualTypeArguments()[0];
                        } catch (ClassCastException e) {
                            throw new PippoRuntimeException("Please specify a generic parameter type for field '{}' {}",
                                    field.getName(), fieldClass.getName());
                        }

                        if (Set.class == fieldClass) {
                            value = getParameters().get(parameterName).toSet(genericClass, pattern);
                        } else if (List.class == fieldClass) {
                            value = getParameters().get(parameterName).toList(genericClass, pattern);
                        } else if (fieldClass.isInterface()) {
                            throw new PippoRuntimeException("Field '{}' collection '{}' is not a supported type!",
                                    field.getName(), fieldClass.getName());
                        } else {
                            Class collectionClass = (Class) fieldClass;
                            value = getParameters().get(parameterName).toCollection(collectionClass, genericClass, pattern);
                        }
                    } else {
                        value = getParameters().get(parameterName).to(fieldClass, pattern);
                    }
                    field.set(entity, value);
                } catch (IllegalAccessException e) {
                    log.error("Cannot set value for field '{}' from parameter '{}'", field.getName(), parameterName, e);
                } catch (PippoRuntimeException e) {
                    log.error(e.getMessage(), e);
                }
            }
        }

        return entity;
    }

    public  T createEntityFromBody(Class entityClass) {
        try {
            String body = getBody();
            if (StringUtils.isNullOrEmpty(body)) {
                log.warn("Can not create entity '{}' from null or empty request body!", entityClass.getName());
                return null;
            }

            // try to determine the body content-type
            String contentType = getContentType();
            if (StringUtils.isNullOrEmpty(contentType)) {
                // sloppy client, try the accept header
                contentType = getAcceptType();
            }

            if (StringUtils.isNullOrEmpty(contentType)) {
                throw new PippoRuntimeException(
                    "Failed to create entity '{}' from request body because 'content-type' is not specified!",
                    entityClass.getName());
            }

            ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(contentType);
            if (engine == null) {
                throw new PippoRuntimeException(
                    "Failed to create entity '{}' from request body because a content engine for '{}' could not be found!",
                    entityClass.getName(), contentType);
            }

            return engine.fromString(body, entityClass);
        } catch (PippoRuntimeException e) {
            // pass-through PippoRuntimeExceptions
            throw e;
        } catch (Exception e) {
            // capture and re-throw all other exceptions
            throw new PippoRuntimeException(e, "Failed to create entity '{}' from request body!", entityClass.getName());
        }
    }

    public String getHost() {
        return httpServletRequest.getHeader(HttpConstants.Header.HOST);
    }

    public String getUserAgent() {
        return httpServletRequest.getHeader(HttpConstants.Header.USER_AGENT);
    }

    public int getPort() {
        return httpServletRequest.getServerPort();
    }

    public String getClientIp() {
        return httpServletRequest.getRemoteAddr();
    }

    public int getContentLength() {
        return httpServletRequest.getContentLength();
    }

    public String getScheme() {
        return httpServletRequest.getScheme();
    }

    public String getAcceptType() {
        if (acceptType == null) {
            acceptType = httpServletRequest.getHeader(HttpConstants.Header.ACCEPT);
            // try to specify an AcceptType from an registered ContentType suffix
            String suffix = StringUtils.getFileExtension(getPath());
            if (!StringUtils.isNullOrEmpty(suffix)) {
                ContentTypeEngine engine = contentTypeEngines.getContentTypeEngine(suffix);
                if (engine != null) {
                    acceptType = engine.getContentType();
                }
            }
        }
        return acceptType;
    }

    public String getContentType() {
        if (contentType == null) {
            String httpServletRequestContentType = httpServletRequest.getHeader(HttpConstants.Header.CONTENT_TYPE);
            if (HttpConstants.Method.POST.equals(httpServletRequest.getMethod())
                && (HttpConstants.ContentType.APPLICATION_FORM_URLENCODED.equals(httpServletRequestContentType)
                || HttpConstants.ContentType.MULTIPART_FORM_DATA.equals(httpServletRequestContentType))) {
                // Allow forms to exercise RESTful API endpoints by POSTing content like 'application/json'.
                // This parameter is usually paired with '_method' and '_content' parameters.
                contentType = getParameter("_content_type").toString(httpServletRequestContentType);
            } else {
                contentType = httpServletRequestContentType;
            }
        }
        return contentType;
    }

    public String getBody() {
        if (body == null) {
            String httpServletRequestContentType = httpServletRequest.getHeader(HttpConstants.Header.CONTENT_TYPE);
            if (HttpConstants.Method.POST.equals(httpServletRequest.getMethod())
                && (HttpConstants.ContentType.APPLICATION_FORM_URLENCODED.equals(httpServletRequestContentType)
                || HttpConstants.ContentType.MULTIPART_FORM_DATA.equals(httpServletRequestContentType))) {
                // Allow forms to exercise RESTful API endpoints by POSTing content like 'application/json'.
                // This parameter is usually paired with '_method' and '_content_type' parameters.
                body = getParameter("_content").toString(null);
            } else {
                try {
                    body = IoUtils.toString(httpServletRequest.getInputStream());
                } catch (Exception e) {
                    throw new PippoRuntimeException(e, "Exception when reading the request body");
                }
            }
        }
        return body;
    }

    public String getHeader(String name) {
        return httpServletRequest.getHeader(name);
    }

    public boolean isSecure() {
        return httpServletRequest.isSecure();
    }

    /**
     * Returns the url with the protocol, application path, & resource path. The
     * query string is omitted.
     *
     * @return the url
     */
    public String getUrl() {
        return httpServletRequest.getRequestURL().toString();
    }

    /**
     * Returns the container uri with the application path & resource path. The
     * protocol and query string are omitted.
     *
     * @return the container uri
     */
    public String getUri() {
        return httpServletRequest.getRequestURI();
    }

    /**
     * Returns the query string component of the request.
     *
     * @return the query string
     */
    public String getQuery() {
        return httpServletRequest.getQueryString();
    }

    /**
     * Returns the uri relative to the application path.
     *
     * @return the uri relative to the application path
     */
    public String getApplicationUri() {
        if ("".equals(applicationPath)) {
            return getUri();
        } else {
            return getUri().substring(applicationPath.length());
        }
    }

    /**
     * Returns the uri with the query string relative to the application root path.
     *
     * @return the application-relative uri with the query
     */
    public String getApplicationUriWithQuery() {
        StringBuilder sb = new StringBuilder();
        sb.append(getApplicationUri());
        if (getQuery() != null) {
            sb.append('?').append(getQuery());
        }

        return sb.toString();
    }

    public String getContextPath() {
        return httpServletRequest.getContextPath();
    }

    public String getApplicationPath() {
        return applicationPath;
    }

    /**
     * Returns a request path relative to the application path. The returned value always start with "/".
     */
    public String getPath() {
        if (path == null) {
            // create a URI to automatically decode the path
            URI uri = URI.create(httpServletRequest.getRequestURL().toString());
            String requestUri = uri.getPath();

            path = applicationPath.isEmpty() ? requestUri : requestUri.substring(applicationPath.length());
            if (StringUtils.isNullOrEmpty(path)) {
                path = "/";
            }
        }

        return path;
    }

    public String getMethod() {
        if (method == null) {
            String httpServletRequestMethod = httpServletRequest.getMethod();
            String httpServletRequestContentType = httpServletRequest.getHeader(HttpConstants.Header.CONTENT_TYPE);
            if (HttpConstants.Method.POST.equals(httpServletRequestMethod)
                && (HttpConstants.ContentType.APPLICATION_FORM_URLENCODED.equals(httpServletRequestContentType)
                || HttpConstants.ContentType.MULTIPART_FORM_DATA.equals(httpServletRequestContentType))) {
                // Allow forms to more discretely control the Pippo form handler and encourages RESTful design.
                // This parameter may be paired with the '_content_type' and '_content' parameters.
                method = getParameter("_method").toString(httpServletRequestMethod).toUpperCase();
            } else {
                method = httpServletRequestMethod;
            }
        }
        return method;
    }

    public HttpServletRequest getHttpServletRequest() {
        return httpServletRequest;
    }

    public Session getSession() {
        return getSession(true);
    }

    public Session getSession(boolean create) {
        if (session == null) {
            HttpSession httpSession = httpServletRequest.getSession(create);
            if (httpSession != null) {
                session = new Session(httpSession);
            }
        }

        return session;
    }

    public void resetSession() {
        if (session == null) {
            return;
        }
        session.invalidate();
        session = null;
        getSession();
    }

    public void recreateSession() {
        if (session == null) {
            return;
        }

        // preserve the session data
        Map values = new HashMap<>();
        Enumeration names = getSession().getNames();
        while (names.hasMoreElements()) {
            String name = names.nextElement();
            values.put(name, getSession().get(name));
        }

        // preserve the flash data
        Flash flash = session.getFlash();

        // create a new session
        resetSession();

        // restore the session data
        for (Map.Entry entry : values.entrySet()) {
            getSession().put(entry.getKey(), entry.getValue());
        }

        // restore the flash instance
        if (flash != null) {
            getSession().put("flash", flash);
        }
    }

    public Map getFiles() {
        if (files == null) {
            files = new HashMap<>();
            try {
                Collection parts = httpServletRequest.getParts();
                for (Part part : parts) {
                    files.put(part.getName(), new FileItem(part));
                }
            } catch (Exception e) {
                throw new PippoRuntimeException(e, "Cannot get files");
            }
        }

        return files;
    }

    public FileItem getFile(String name) {
        return getFiles().get(name);
    }

    public List getCookies() {
        return CookieUtils.getCookies(httpServletRequest);
    }

    public Cookie getCookie(String name) {
        return CookieUtils.getCookie(httpServletRequest, name);
    }

    public static Request get() {
        RouteContext routeContext = RouteDispatcher.getRouteContext();

        return (routeContext != null) ? routeContext.getRequest() : null;
    }

    @Override
    public String toString() {
        return "Request{" +
            "requestMethod='" + getMethod() + '\'' +
            ", uriPattern='" + getApplicationUri() + '\'' +
            '}';
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy