Package org.jtalks.jcommune.plugin.api.web

Source Code of org.jtalks.jcommune.plugin.api.web.PluginHandlerMapping$MethodAwareKey

/**
* Copyright (C) 2011  JTalks.org Team
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
* Lesser General Public License for more details.
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/
package org.jtalks.jcommune.plugin.api.web;

import com.google.common.annotations.VisibleForTesting;
import org.jtalks.jcommune.plugin.api.PluginLoader;
import org.jtalks.jcommune.plugin.api.core.WebControllerPlugin;
import org.jtalks.jcommune.plugin.api.filters.TypeFilter;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.HandlerMethodSelector;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.ArrayList;

/**
* Custom handler mapping. Needed to map plugin handlers separately from application handlers. It's necessary to allow
* update handlers without application restart.
*
* @author Mikhail Stryzhonok
*/
public class PluginHandlerMapping extends RequestMappingHandlerMapping {

    private static final PluginHandlerMapping INSTANCE = new PluginHandlerMapping();
    private final Map<MethodAwareKey, HandlerMethod> pluginHandlerMethods = new HashMap<>();
    private PluginLoader pluginLoader;

    private PluginHandlerMapping() {

    }

    public static PluginHandlerMapping getInstance() {
        return INSTANCE;
    }

    // need to run tests without context
    @Override
    protected boolean isContextRequired() {
        return false;
    }

    /**
     *  {@inheritDoc}
     */
    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        if (PluginController.class.isAssignableFrom(method.getDeclaringClass())) {
            registerPluginHandlerMethod((PluginController)handler, method, mapping);
        } else {
            super.registerHandlerMethod(handler, method, mapping);
        }
    }

    /**
     * Registers new plugin handler or updates existence
     *
     * @param handler controller object
     * @param method method to be registered
     * @param mapping information about request
     */
    @VisibleForTesting
    void registerPluginHandlerMethod(PluginController handler, Method method, RequestMappingInfo mapping) {
        handler.setApiPath(this.getClass().getProtectionDomain().getCodeSource().getLocation().getPath());
        Set<String> patterns = getMappingPathPatterns(mapping);
        if (patterns.size() != 1) {
            throw new IllegalStateException("Controller method " + method.getName() + " mapped to " + patterns.size()
                    + " urls. Expected 1 url");
        }
        Set<RequestMethod> methods = mapping.getMethodsCondition().getMethods();
        if (methods.size() != 1) {
            throw new IllegalStateException("Controller method " + method.getName() + " mapped to " + methods.size()
                    + " methods. Expected 1 method");
        }
        pluginHandlerMethods.put(new MethodAwareKey(methods.iterator().next(), getUniformUrl(patterns.iterator().next())),
                createHandlerMethod(handler, method));
    }


    /**
     * Adds handlers from controller to handler mapping
     * Note: class should be annotated with {@link org.springframework.stereotype.Controller} annotation and all
     * handler method should be annotated with {@link org.springframework.web.bind.annotation.RequestMapping} annotation
     *
     * @param controller controller object to map
     */
    public void addController(PluginController controller) {
        INSTANCE.detectHandlerMethods(controller);
        if (controller instanceof ApplicationContextAware) {
            ((ApplicationContextAware) controller).setApplicationContext(getApplicationContext());
        }
    }

    /**
     * Disables handlers from specified controller
     *
     * @param controller controller bean to disable handlers
     */
    public void deactivateController(PluginController controller) {
        List<MethodAwareKey> keys = getPluginControllerUrls(controller);
        for (MethodAwareKey key : keys) {
            pluginHandlerMethods.remove(key);
        }
    }

    /**
     * Get list of URLs which can be handled by controller
     *
     * @param controller controller object
     * @return list of URLs
     */
    private List<MethodAwareKey> getPluginControllerUrls(PluginController controller) {
        List<MethodAwareKey> keys = new ArrayList<>();
        final Class controllerType = controller.getClass();
        Set<Method> methods = HandlerMethodSelector.selectMethods(controllerType, new ReflectionUtils.MethodFilter() {
            public boolean matches(Method method) {
                return getMappingForMethod(method, controllerType) != null;
            }
        });

        for (Method method : methods) {
            RequestMappingInfo mapping = getMappingForMethod(method, controllerType);
            //Method have {@link RequestMapping} annotation
            if (mapping != null) {
                Set<String> patterns = getMappingPathPatterns(mapping);
                if (patterns.size() != 1) {
                    throw new IllegalStateException("Controller method " + method.getName() + " mapped to "
                            + patterns.size() + " urls. Expected 1 url");
                }
                Set<RequestMethod> requestMethods = mapping.getMethodsCondition().getMethods();
                if (requestMethods.size() != 1) {
                    throw new IllegalStateException("Controller method " + method.getName() + " mapped to " + methods.size()
                            + " methods. Expected 1 method");
                }
                keys.add(new MethodAwareKey(requestMethods.iterator().next(), getUniformUrl(patterns.iterator().next())));
            }
        }

        return keys;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
        //We should clear map in case if plugin version was changed
        pluginHandlerMethods.clear();
        //We should update Web plugins before resolving handler
        pluginLoader.reloadPlugins(new TypeFilter(WebControllerPlugin.class));
        String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
        MethodAwareKey key = new MethodAwareKey(RequestMethod.valueOf(request.getMethod()), getUniformUrl(lookupPath));
        HandlerMethod handlerMethod = pluginHandlerMethods.get(key);
        if (handlerMethod != null) {
            return handlerMethod;
        } else {
            return super.getHandlerInternal(request);
        }
    }

    /**
     * Adds "/" to the end of url if it necessary. Needed to support optional "/" at the end
     */
    private String getUniformUrl(String url) {
        if (url.endsWith("/")) {
            return url;
        }
        return url + "/";
    }

    //Needed for tests only
    @VisibleForTesting
    Map<MethodAwareKey, HandlerMethod> getPluginHandlerMethods() {
        return pluginHandlerMethods;
    }

    public PluginLoader getPluginLoader() {
        return pluginLoader;
    }

    public void setPluginLoader(PluginLoader pluginLoader) {
        this.pluginLoader = pluginLoader;
    }

    static class MethodAwareKey {
        private RequestMethod method;
        private String url;

        public MethodAwareKey(RequestMethod method, String url) {
            this.method = method;
            this.url = url;
        }

        public RequestMethod getMethod() {
            return method;
        }

        public String getUrl() {
            return url;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()){
                return false;
            }

            MethodAwareKey that = (MethodAwareKey) o;

            if (method != that.method) {
                return false;
            }
            if (url != null ? !url.equals(that.url) : that.url != null) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result = method != null ? method.hashCode() : 0;
            result = 31 * result + (url != null ? url.hashCode() : 0);
            return result;
        }
    }
}
TOP

Related Classes of org.jtalks.jcommune.plugin.api.web.PluginHandlerMapping$MethodAwareKey

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.