com.runwaysdk.controller.URLConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for com.runwaysdk.controller.URLConfigurationManager.java

Source

/**
 * Copyright (c) 2015 TerraFrame, Inc. All rights reserved.
 *
 * This file is part of Runway SDK(tm).
 *
 * Runway SDK(tm) 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 3 of the
 * License, or (at your option) any later version.
 *
 * Runway SDK(tm) 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 Runway SDK(tm).  If not, see <http://www.gnu.org/licenses/>.
 */
package com.runwaysdk.controller;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.runwaysdk.configuration.ConfigurationManager;
import com.runwaysdk.configuration.ConfigurationManager.ConfigGroup;
import com.runwaysdk.configuration.RunwayConfigurationException;
import com.runwaysdk.controller.URLConfigurationManager.ControllerMapping.ActionMapping;
import com.runwaysdk.generation.loader.LoaderDecorator;

/**
 * This class reads the url config file searching for controller definitions and method->url mappings. Each method on the controller can map to many url regex strings.
 * The method name is assumed to also be regex, in which case it will loop over all methods matching the regex and create a new action mapping from method->url.
 * The actions are queried in top-down fashion, meaning that actions defined first in the xml will be the first to match a url.
 * 
 * This class also allows the definition of url forwards and redirects. Forwards happen within the same request, the request is simply forwarded to another url
 * and no filters are applied. Redirects actually redirect the user; the client sees the new url and filters are applied.
 */
public class URLConfigurationManager {
    // Always use the SLF4J logger.
    private static Logger log = LoggerFactory.getLogger(URLConfigurationManager.class);

    String fileName = "urlmap.xml";

    private static Object initializeLock = new Object();

    private static ArrayList<UriMapping> mappings;

    public URLConfigurationManager() {
        String exMsg = "An exception occurred while reading the xml servlet request mappings.";

        try {
            // The method returns immediately if the configuration file does not exist.
            readMappings();
        } catch (ParserConfigurationException e) {
            throw new RunwayConfigurationException(exMsg, e);
        } catch (SAXException e) {
            throw new RunwayConfigurationException(exMsg, e);
        } catch (IOException e) {
            throw new RunwayConfigurationException(exMsg, e);
        }
    }

    /**
     * Handles the uri request. If this url maps to something else it will be mapped first, then the action is performed.
     * 
     * @param url
     * @throws IOException 
     * @throws ServletException 
     */
    public static void handleUrl(String url, HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        URLConfigurationManager manager = new URLConfigurationManager();
        UriMapping mapping = manager.getMapping(url);
        if (mapping != null) {
            ServletDispatcher dispatcher = new ServletDispatcher();

            mapping.performRequest(req, resp, dispatcher);
        }
    }

    /**
     * Returns the UriMapping associated with the given uri. This URI is assumed to NOT include the application context path (if there is one).
     * 
     * @param uri
     * @return
     */
    public UriMapping getMapping(String uri) {

        if (mappings == null) {
            return null;
        }

        if (uri.startsWith("/")) {
            uri = uri.replaceFirst("/", "");
        }

        // If this is a ControllerMapping, calculate what action it would be referencing.
        ArrayList<String> uris = new ArrayList<String>(Arrays.asList(uri.split("/")));
        String actionUri = "";
        if (uris.size() > 1) {
            uris.remove(0);
            actionUri = StringUtils.join(uris, "/");
        }

        for (UriMapping mapping : mappings) {
            if (mapping.handlesUri(uri)) {
                if (mapping instanceof ControllerMapping) {
                    ActionMapping action = ((ControllerMapping) mapping).getActionAtUri(actionUri);
                    if (action != null) {
                        return action;
                    }
                } else {
                    return mapping;
                }
            }
        }

        return null;
    }

    private void readMappings() throws ParserConfigurationException, SAXException, IOException {
        if (!ConfigurationManager.checkExistence(ConfigGroup.CLIENT, fileName)) {
            return;
        }

        synchronized (initializeLock) {
            if (mappings != null) {
                return;
            }

            mappings = new ArrayList<UriMapping>();
        }

        InputStream stream = ConfigurationManager.getResourceAsStream(ConfigGroup.CLIENT, fileName);

        if (stream != null) {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
            Document doc = dBuilder.parse(stream);

            Element xmlMappings = doc.getDocumentElement();

            NodeList children = xmlMappings.getChildNodes();
            for (int i = 0; i < children.getLength(); ++i) {
                Node n = children.item(i);

                if (n.getNodeType() == Node.ELEMENT_NODE) {
                    Element el = (Element) n;

                    String uri = el.getAttribute("uri");
                    String controllerClassName = el.getAttribute("controller");
                    String redirect = el.getAttribute("redirect");
                    String forward = el.getAttribute("forward");

                    if (uri == null) {
                        String exMsg = "Request mapping requires a uri.";
                        throw new RunwayConfigurationException(exMsg);
                    }

                    if (controllerClassName != null && controllerClassName != "") {
                        readController(uri, controllerClassName, el);
                    } else if (forward != null && forward != "") {
                        mappings.add(new UriForwardMapping(uri, forward));
                    } else if (redirect != null && redirect != "") {
                        mappings.add(new UriRedirectMapping(uri, redirect));
                    } else {
                        String exMsg = "Request mapping requires either a controller class name or a redirect url.";
                        throw new RunwayConfigurationException(exMsg);
                    }
                }
            }

            String info = "URL mappings successfully read from " + fileName + ":\n" + this.toString();
            log.info(info);
        }
    }

    public void readController(String uri, String controllerClassName, Element el) {
        ControllerMapping controllerMapping = new ControllerMapping(uri, controllerClassName);

        ArrayList<Method> methods = null;
        try {
            Class<?> clazz = LoaderDecorator.load(controllerClassName);
            Class<?> clazzBase = LoaderDecorator.load(controllerClassName + "Base");
            methods = new ArrayList<Method>(Arrays.asList(clazz.getMethods()));
            Iterator<Method> it = methods.iterator();
            while (it.hasNext()) {
                Method m = it.next();
                if (!m.getDeclaringClass().equals(clazz) && !m.getDeclaringClass().equals(clazzBase)) {
                    it.remove();
                }
            }
        } catch (Throwable t) {
            String exMsg = "Exception loading controller class [" + controllerClassName + "].";
            throw new RunwayConfigurationException(exMsg, t);
        }

        NodeList actions = el.getChildNodes();
        for (int iAction = 0; iAction < actions.getLength(); ++iAction) {
            Node nodeAct = actions.item(iAction);

            if (nodeAct.getNodeType() == Node.ELEMENT_NODE) {
                Element elAction = (Element) nodeAct;

                String method = elAction.getAttribute("method");
                String actionUrl = elAction.getAttribute("uri");

                boolean didMatch = false;
                for (Method m : methods) {
                    if (m.getName().matches(method)) {
                        controllerMapping.add(m.getName(), actionUrl);
                        didMatch = true;
                    }
                }

                if (!didMatch) {
                    String exMsg = "The method regex [" + method + "] for action [" + actionUrl
                            + "] did not match any methods on the controller class definition ["
                            + controllerClassName + "].";
                    throw new RunwayConfigurationException(exMsg);
                }
            }
        }

        mappings.add(controllerMapping);
    }

    @Override
    public String toString() {
        String out = "";

        for (UriMapping mapping : mappings) {
            out = out + mapping.toString() + "\n";
        }

        return out;
    }

    /**
     * ABSTRACT URI MAPPING
     */
    public abstract class UriMapping {
        private String uri;

        public UriMapping() {
            this.uri = null;
        }

        public UriMapping(String uri) {
            this.setUri(uri);
        }

        public String getUri() {
            return this.uri;
        }

        public void setUri(String uri) {
            this.uri = uri;
        }

        public boolean handlesUri(String uri) {
            if (uri.matches(this.getUri())) {
                return true;
            }

            return false;
        }

        abstract void performRequest(HttpServletRequest req, HttpServletResponse resp, ServletDispatcher dispatcher)
                throws IOException;
    }

    /**
     * URI FORWARD MAPPING
     */
    public class UriForwardMapping extends UriMapping {
        private String uriEnd;

        public UriForwardMapping(String uriStart, String uriEnd) {
            super(uriStart);

            this.uriEnd = uriEnd;
        }

        /**
         * @return the uriEnd
         */
        public String getUriEnd() {
            return uriEnd;
        }

        @Override
        public void performRequest(HttpServletRequest req, HttpServletResponse resp, ServletDispatcher dispatcher) {
            try {
                req.getRequestDispatcher(this.getUriEnd()).forward(req, resp);
            } catch (ServletException e) {
                throw new RuntimeException(e);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public String toString() {
            return this.toString("");
        }

        public String toString(String indent) {
            return indent + "UriForwardMapping: \"" + this.getUri() + "\" = \"" + this.getUriEnd() + "\"";
        }
    }

    /**
     * URI REDIRECT MAPPING
     */
    public class UriRedirectMapping extends UriForwardMapping {
        public UriRedirectMapping(String uriStart, String uriEnd) {
            super(uriStart, uriEnd);
        }

        @Override
        public void performRequest(HttpServletRequest req, HttpServletResponse resp, ServletDispatcher dispatcher) {
            try {
                resp.sendRedirect(req.getContextPath() + "/" + this.getUriEnd());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

        public String toString(String indent) {
            return indent + "UriRedirectMapping: \"" + this.getUri() + "\" = \"" + this.getUriEnd() + "\"";
        }
    }

    /**
     * CONTROLLER MAPPING
     */
    public class ControllerMapping extends UriMapping {
        private String className;
        private ArrayList<ActionMapping> actions;

        public ControllerMapping(String controllerUri, String className) {
            super(controllerUri);

            this.className = className;
            actions = new ArrayList<ActionMapping>();
        }

        public String getControllerClassName() {
            return this.className;
        }

        @Override
        public boolean handlesUri(String uri) {
            String[] contPath = uri.split("/");
            return super.handlesUri(contPath[0]);
        }

        public ActionMapping getActionAtUri(String uri) {
            for (int i = actions.size() - 1; i >= 0; --i) {
                ActionMapping action = actions.get(i);
                if (action.handlesUri(uri)) {
                    return action;
                }
            }

            return null;
        }

        public void add(String actionName, String url) {
            //      Iterator<ActionMapping> it = actions.iterator();
            //      while (it.hasNext()) {
            //        ActionMapping action = it.next();
            //        if (action.getMethodName().equals(actionName)) {
            //          action.addUri(url);
            //          it.remove();
            //          actions.add(action);
            //          return;
            //        }
            //      }

            actions.add(new ActionMapping(this, actionName, url));
        }

        public String toString(String indent) {
            String innerIndent = indent;
            if (indent == null) {
                indent = "";
                innerIndent = "  ";
            }

            String out = indent + this.getControllerClassName() + " : \"" + this.getUri() + "\" = [\n";

            for (int i = actions.size() - 1; i >= 0; --i) {
                ActionMapping action = actions.get(i);
                out = out + indent + action.toString(innerIndent) + "\n";
            }

            out = out + indent + "]";

            return out;
        }

        @Override
        public String toString() {
            return this.toString(null);
        }

        /**
         * ACTION MAPPING
         */
        public class ActionMapping extends UriMapping {
            String actionName;
            ControllerMapping controller;

            public ActionMapping(ControllerMapping controller, String actionName, String uri) {
                super();

                this.actionName = actionName;
                this.controller = controller;

                // The Uri must be set after the actionName is set since we do property replacement in it.
                this.setUri(uri);
            }

            @Override
            public void setUri(String uri) {
                String replaced = uri.replace("${method}", actionName);
                super.setUri(replaced);
            }

            public void setMethodName(String val) {
                this.actionName = val;
            }

            public String getMethodName() {
                return actionName;
            }

            public ControllerMapping getControllerMapping() {
                return this.controller;
            }

            @Override
            public String toString() {
                return this.toString("");
            }

            public String toString(String indent) {
                return indent + "ActionMapping: " + actionName + " = \"" + this.getUri() + "\"";
            }

            @Override
            public void performRequest(HttpServletRequest req, HttpServletResponse resp,
                    ServletDispatcher dispatcher) throws IOException {
                String actionName = getMethodName();
                String controllerName = getControllerMapping().getControllerClassName();

                dispatcher.invokeControllerAction(controllerName, actionName, req, resp);
            }
        }

        @Override
        public void performRequest(HttpServletRequest req, HttpServletResponse resp, ServletDispatcher dispatcher) {
            throw new UnsupportedOperationException(
                    "You can't perform a request on a ControllerMapping, only an ActionMapping.");
        }
    }
}