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

framework.ServletImpl Maven / Gradle / Ivy

package framework;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import app.config.Sys;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import framework.annotation.Config;
import framework.annotation.Route;
import framework.annotation.Route.Method;

/**
 * Servlet implementation
 */
@WebServlet("/")
@MultipartConfig
public class ServletImpl implements javax.servlet.Servlet {

    /**
     * first access
     */
    AtomicBoolean first = new AtomicBoolean(true);

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
     */
    @Override
    public void init(ServletConfig config) throws ServletException {
        Config.Injector.setup(Sys.class.getPackage().getName());
        Sys.context_path = Tool.suffix(config.getServletContext().getContextPath(), "/");
        new ApplicationImpl(config.getServletContext()).setup(ResponseImpl::new);
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#destroy()
     */
    @Override
    public void destroy() {
        Application.current().ifPresent(Application::shutdown);
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#getServletConfig()
     */
    @Override
    public ServletConfig getServletConfig() {
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#getServletInfo()
     */
    @Override
    public String getServletInfo() {
        return null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
     */
    @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        if (first.compareAndSet(true, false)) {
            Sys.http_port = Tool.of(req.getServerPort());
        }
        try (Defer request = new Defer<>(Tool.peek(new RequestImpl((HttpServletRequest) req, (HttpServletResponse) res), Request.CURRENT::set),
                r -> Request.CURRENT.remove());
                Defer session = new Defer<>(Tool.peek(new SessionImpl(((HttpServletRequest) req).getSession()), Session.CURRENT::set),
                        s -> Session.CURRENT.remove())) {
            Application.current().get().handle(request.get(), session.get());
        }
    }

    /**
     * Application implementation
     */
    @SuppressWarnings("serial")
    static class ApplicationImpl extends Application {

        /**
         * application scope object
         */
        transient final ServletContext context;

        /**
         * constructor
         * 
         * @param context application scope object
         */
        @SuppressFBWarnings("ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD")
        ApplicationImpl(ServletContext context) {
            this.context = context;
            CURRENT = this;
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#names()
         */
        @Override
        public Stream names() {
            return Tool.stream(context.getAttributeNames());
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#getAttr(java.lang.String)
         */
        @SuppressWarnings("unchecked")
        @Override
        public  Optional getAttr(String name) {
            return Tool.of(Reflector.getProperty(this, name, () -> (T) context.getAttribute(name), false));
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#setAttr(java.lang.String, java.lang.Object)
         */
        @Override
        public void setAttr(String name, Object value) {
            context.setAttribute(name, value);
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#removeAttr(java.lang.String)
         */
        @Override
        public void removeAttr(String name) {
            context.removeAttribute(name);
        }

        /**
         * @return context path
         */
        @Override
        public String getContextPath() {
            return Tool.suffix(context.getContextPath(), "/");
        }
    }

    /**
     * Session implementation
     */
    @SuppressWarnings("serial")
    static class SessionImpl extends Session {

        /**
         * http session
         */
        HttpSession session;

        /**
         * constructor
         *
         * @param session session
         */
        SessionImpl(HttpSession session) {
            this.session = session;
            session.setMaxInactiveInterval(Sys.session_timeout_minutes * 60);
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#hashCode()
         */
        @Override
        public int hashCode() {
            return session.getId().hashCode();
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#equals(java.lang.Object)
         */
        @Override
        @SuppressFBWarnings("EQ_UNUSUAL")
        public boolean equals(Object obj) {
            return obj != null && hashCode() == obj.hashCode();
        }

        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return session.getId();
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#clear()
         */
        @Override
        public void clear() {
            Tool.stream(session.getAttributeNames()).forEach(session::removeAttribute);
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#names()
         */
        @Override
        public Stream names() {
            return Tool.stream(session.getAttributeNames());
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#getAttr(java.lang.String)
         */
        @SuppressWarnings("unchecked")
        @Override
        public  Optional getAttr(String name) {
            return Tool.of(Reflector.getProperty(this, name, () -> (T) session.getAttribute(name), false));
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#setAttr(java.lang.String, java.lang.Object)
         */
        @Override
        public void setAttr(String name, Serializable value) {
            session.setAttribute(name, value);
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#removeAttr(java.lang.String)
         */
        @Override
        public void removeAttr(String name) {
            session.removeAttribute(name);
        }
    }

    /**
     * Request implementation
     */
    @SuppressWarnings("serial")
    static class RequestImpl extends Request {

        /**
         * http request
         */
        final HttpServletRequest request;

        /**
         * http response
         */
        final HttpServletResponse response;

        /**
         * parameters
         */
        final Map> parameters;

        /**
         * parameters
         */
        final Map> files;

        /**
         * request path
         */
        final String path;

        /**
         * Request method
         */
        final Method method;

        /**
         * constructor
         *
         * @param request servlet request
         * @param response servlet response
         */
        RequestImpl(HttpServletRequest request, HttpServletResponse response) {
            Try.r(() -> request.setCharacterEncoding(StandardCharsets.UTF_8.name())).run();
            this.request = request;
            this.response = response;
            String uri = request.getRequestURI();
            int rootLength = request.getContextPath().length() + 1;
            path = rootLength > uri.length() ? null : Tool.prefix(Tool.string(uri.substring(rootLength)).orElse("/"), "/");
            method = Try. f(m -> Method.valueOf(m.toUpperCase()), (e, m) -> {
                Log.info(e, () -> "Invalid method: " + m);
                return null;
            }).apply(Tool.of(request.getParameter(Sys.request_method_key)).orElseGet(request::getMethod));
            parameters = request.getParameterMap().entrySet().stream().collect(LinkedHashMap::new,
                    (m, e) -> m.put(e.getKey(), new ArrayList<>(Arrays.asList(e.getValue()))), Map::putAll);
            files = new LinkedHashMap<>();
            if (Tool.of(request.getHeader("Content-Type")).filter(i -> i.startsWith("multipart/")).isPresent()) {
                Try.s(request::getParts).get().forEach(part -> {
                    List values = parameters.computeIfAbsent(part.getName(), k -> new ArrayList<>());
                    Optional filename = Tool.of(part.getHeader("Content-Disposition"))
                            .flatMap(s -> Stream.of(s.split("\\s*;\\s*")).filter(i -> i.toLowerCase().startsWith("filename")).findFirst()
                                    .map(i -> i.substring(i.indexOf('=') + 1).trim().replace("\"", "")).filter(i -> !i.isEmpty()));
                    Tool.ifPresentOr(filename, i -> {
                        values.add(i);
                        files.put(i, Tuple.of(Tool.using(part::getInputStream, Tool::loadBytes), null));
                    }, () -> values.add(Tool.using(part::getInputStream, Tool::loadText)));
                });
            }
        }

        /**
         * @return http method
         */
        @Override
        public Route.Method getMethod() {
            return Enum.valueOf(Route.Method.class, request.getMethod());
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#names()
         */
        @Override
        public Stream names() {
            return Tool.stream(request.getAttributeNames());
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#getAttr(java.lang.String)
         */
        @SuppressWarnings("unchecked")
        @Override
        public  Optional getAttr(String name) {
            return Tool.of(Reflector.getProperty(this, name, () -> (T) request.getAttribute(name), false));
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#setAttr(java.lang.String, java.lang.Object)
         */
        @Override
        public void setAttr(String name, Object value) {
            request.setAttribute(name, value);
        }

        /*
         * (non-Javadoc)
         * 
         * @see framework.Attributes#removeAttr(java.lang.String)
         */
        @Override
        public void removeAttr(String name) {
            request.removeAttribute(name);
        }

        @Override
        public String getPath() {
            return path;
        }

        @Override
        public Map> getFiles() {
            return files;
        }

        @Override
        public Map> getHeaders() {
            return new Attributes>() {

                @Override
                public Stream names() {
                    return Tool.stream(request.getHeaderNames());
                }

                @SuppressWarnings("unchecked")
                @Override
                public > Optional getAttr(String name) {
                    return (Optional) Tool.of(request.getHeaders(name)).map(e -> Tool.stream(e).collect(Collectors.toList()));
                }

                @Override
                public void setAttr(String name, List value) {
                    throw new UnsupportedOperationException();
                }

                @Override
                public void removeAttr(String name) {
                    throw new UnsupportedOperationException();
                }
            };
        }

        @Override
        public Map> getParameters() {
            return parameters;
        }

        @Override
        protected String getRemoteAddr() {
            return request.getRemoteAddr() + ":" + request.getRemotePort();
        }

        @Override
        public String getQuery() {
            return request.getQueryString();
        }
    }

    /**
     * Response implementation
     */
    static class ResponseImpl extends Response {

        @Override
        public void writeResponse(Consumer> writeBody) {
            HttpServletResponse response = ((RequestImpl) Request.current().get()).response;
            Runnable action = () -> {
                headers.forEach((key, values) -> values.forEach(value -> response.addHeader(key, value)));
                charset.ifPresent(c -> response.setCharacterEncoding(c.name()));
                response.setStatus(status.code);
            };
            if (content == null) {
                action.run();
            } else {
                writeBody.accept(Try.s(() -> {
                    action.run();
                    return response.getOutputStream();
                }));
            }
        }

        @Override
        public String toString() {
            return Request.current().map(i -> (RequestImpl) i)
                    .map(i -> "-> " + i.request.getProtocol() + " "
                            + Status.of(i.response.getStatus()).map(Object::toString).orElseGet(() -> String.valueOf(i.response.getStatus()))
                            + Tool.string(i.response.getContentType()).map(type -> " (" + type + ")").orElse(""))
                    .orElse("");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy