src/java/de/uapcore/lightpit/AbstractLightPITServlet.java

Sun, 17 Dec 2017 01:45:28 +0100

author
Mike Becker <universe@uap-core.de>
date
Sun, 17 Dec 2017 01:45:28 +0100
changeset 11
737ab27e37b3
parent 10
89e3e6e28b69
child 12
005d27918b57
permissions
-rw-r--r--

implements simple request mapper

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 2017 Mike Becker. All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 */
package de.uapcore.lightpit;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiConsumer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * A special implementation of a HTTPServlet which is focused on implementing
 * the necessary functionality for {@link LightPITModule}s.
 */
public abstract class AbstractLightPITServlet extends HttpServlet {
    
    private static final Logger LOG = LoggerFactory.getLogger(AbstractLightPITServlet.class);
    
    /**
     * Store a reference to the annotation for quicker access.
     */
    private Optional<LightPITModule> moduleInfo = Optional.empty();

    /**
     * The EL proxy is necessary, because the EL resolver cannot handle annotation properties.
     */
    private Optional<LightPITModule.ELProxy> moduleInfoELProxy = Optional.empty();
    
    /**
     * Invocation mapping gathered from the {@link RequestMapping} annotations.
     */
    private final Map<HttpMethod, Map<String, BiConsumer<HttpServletRequest, HttpServletResponse>>> mappings = new HashMap<>();

    /**
     * Gives implementing modules access to the {@link ModuleManager}.
     * @return the module manager
     */
    protected final ModuleManager getModuleManager() {
        return (ModuleManager) getServletContext().getAttribute(ModuleManager.SC_ATTR_NAME);
    }
    
    private void invokeMapping(Method method, HttpServletRequest req, HttpServletResponse resp) {
        try {
            LOG.debug("invoke {}", method.getName());
            method.invoke(this, req, resp);
        } catch (ReflectiveOperationException ex) {
            LOG.error(String.format("invocation of method %s failed", method.getName()), ex);
        }
    }

    @Override
    public void init() throws ServletException {
        moduleInfo = Optional.ofNullable(this.getClass().getAnnotation(LightPITModule.class));
        moduleInfoELProxy = moduleInfo.map(LightPITModule.ELProxy::convert);
        
        if (moduleInfo.isPresent()) {
            Method[] methods = getClass().getDeclaredMethods();
            for (Method method : methods) {
                Optional<RequestMapping> mapping = Optional.ofNullable(method.getAnnotation(RequestMapping.class));
                if (mapping.isPresent()) {
                    if (!Modifier.isPublic(method.getModifiers())) {
                        LOG.warn("{} is annotated with {} but is not public",
                                method.getName(), RequestMapping.class.getSimpleName()
                        );
                        continue;
                    }
                    if (Modifier.isAbstract(method.getModifiers())) {
                        LOG.warn("{} is annotated with {} but is abstract",
                                method.getName(), RequestMapping.class.getSimpleName()
                        );
                        continue;
                    }
                    
                    Class<?>[] params = method.getParameterTypes();
                    if (params.length == 2
                            && HttpServletRequest.class.isAssignableFrom(params[0])
                            && HttpServletResponse.class.isAssignableFrom(params[1])) {
                        
                        if (mappings.computeIfAbsent(mapping.get().method(), k -> new HashMap<>()).
                                putIfAbsent(mapping.get().requestPath(),
                                        (req, resp) -> invokeMapping(method, req, resp)) != null) {
                            LOG.warn("{} {} has multiple mappings",
                                    mapping.get().method(),
                                    mapping.get().requestPath()
                            );
                        }
                        
                        LOG.info("{} {} maps to {}",
                                mapping.get().method(),
                                mapping.get().requestPath(),
                                method.getName()
                        );
                    } else {
                        LOG.warn("{} is annotated with {} but has the wrong signature - (HttpServletRequest,HttpServletResponse) required",
                                method.getName(), RequestMapping.class.getSimpleName()
                        );
                    }
                }
            }
        }
        
        LOG.trace("{} initialized", getServletName());
    }

    @Override
    public void destroy() {
        mappings.clear();
        LOG.trace("{} destroyed", getServletName());
    }
    
    
    /**
     * Sets several requests attributes, that can be used by the JSP.
     * 
     * @param req the servlet request object
     * @see Constants#REQ_ATTR_PATH
     * @see Constants#REQ_ATTR_MODULE_CLASSNAME
     * @see Constants#REQ_ATTR_MODULE_INFO
     */
    private void setGenericRequestAttributes(HttpServletRequest req) {
        req.setAttribute(Constants.REQ_ATTR_PATH, Functions.fullPath(req));

        req.setAttribute(Constants.REQ_ATTR_MODULE_CLASSNAME, this.getClass().getName());

        moduleInfoELProxy.ifPresent((proxy) -> req.setAttribute(Constants.REQ_ATTR_MODULE_INFO, proxy));
    }
    
    private void forwardToFullView(HttpServletRequest req, HttpServletResponse resp)
            throws IOException, ServletException {
        
        setGenericRequestAttributes(req);
        req.setAttribute(Constants.REQ_ATTR_MENU, getModuleManager().getMainMenu());
        req.getRequestDispatcher(Functions.jspPath("full.jsp")).forward(req, resp);
    }
    
    private Optional<BiConsumer<HttpServletRequest, HttpServletResponse>> findMapping(HttpMethod method, HttpServletRequest req) {
        return Optional.ofNullable(mappings.get(method)).map(
                (rm) -> rm.get(Optional.ofNullable(req.getPathInfo()).orElse(""))
        );
    }
    
    @Override
    protected final void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        
        findMapping(HttpMethod.GET, req).ifPresent((consumer) -> consumer.accept(req, resp));
        
        // TODO: let the invoked handler decide (signature must be changed from a BiConsumer to a BiFunction)
        // TODO: we should call a default handler, if no specific mapping could be found
        forwardToFullView(req, resp);
    }

    @Override
    protected final void doPost(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        
        findMapping(HttpMethod.POST, req).ifPresent((consumer) -> consumer.accept(req, resp));
        
        forwardToFullView(req, resp);
    }
}

mercurial