Package org.grails.web.mapping

Source Code of org.grails.web.mapping.DefaultUrlMappingEvaluator

/*
* Copyright 2004-2005 Graeme Rocher
*
* 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 org.grails.web.mapping;

import grails.core.GrailsApplication;
import grails.core.GrailsControllerClass;
import grails.core.support.ClassLoaderAware;
import grails.io.IOUtils;
import grails.plugins.GrailsPluginManager;
import grails.plugins.PluginManagerAware;
import grails.util.GrailsMetaClassUtils;
import grails.validation.ConstrainedProperty;
import grails.web.mapping.UrlMapping;
import grails.web.mapping.UrlMappingData;
import grails.web.mapping.UrlMappingEvaluator;
import grails.web.mapping.UrlMappingParser;
import grails.web.mapping.exceptions.UrlMappingException;
import groovy.lang.Binding;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyObject;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.Script;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.groovy.grails.web.metaclass.ControllerDynamicMethods;
import org.codehaus.groovy.runtime.IOGroovyMethods;
import org.grails.validation.ConstrainedPropertyBuilder;
import org.springframework.beans.BeanUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

/**
* <p>A UrlMapping evaluator that evaluates Groovy scripts that are in the form:</p>
* <p/>
* <pre>
* <code>
* mappings {
*    /$post/$year?/$month?/$day?" {
*       controller = "blog"
*       action = "show"
*       constraints {
*           year(matches:/\d{4}/)
*           month(matches:/\d{2}/)
*       }
*    }
* }
* </code>
* </pre>
*
* @author Graeme Rocher
* @since 0.5
*/
public class DefaultUrlMappingEvaluator implements UrlMappingEvaluator, ClassLoaderAware, PluginManagerAware {

    public static final String ACTION_CREATE = "create";
    public static final String ACTION_INDEX = "index";
    public static final String ACTION_SHOW = "show";
    public static final String ACTION_EDIT = "edit";
    public static final String ACTION_UPDATE = "update";
    public static final String ACTION_PATCH = "patch";
    public static final String ACTION_DELETE = "delete";
    public static final String ACTION_SAVE = "save";
    public static final List<String> DEFAULT_RESOURCES_INCLUDES = Arrays.asList(ACTION_INDEX, ACTION_CREATE, ACTION_SAVE,ACTION_SHOW,ACTION_EDIT, ACTION_UPDATE, ACTION_PATCH, ACTION_DELETE);
    public static final List<String> DEFAULT_RESOURCE_INCLUDES = Arrays.asList(ACTION_CREATE,ACTION_SAVE,ACTION_SHOW, ACTION_EDIT, ACTION_UPDATE, ACTION_PATCH, ACTION_DELETE);
    private static final Log LOG = LogFactory.getLog(UrlMappingBuilder.class);
    private GroovyClassLoader classLoader = new GroovyClassLoader();
    private UrlMappingParser urlParser = new DefaultUrlMappingParser();
    private ServletContext servletContext;
    private static final String EXCEPTION = "exception";
    private static final String PARSE_REQUEST = "parseRequest";
    private static final String RESOURCE = "resource";
    private static final String RESOURCES = "resources";

    private GrailsPluginManager pluginManager;
    private ApplicationContext applicationContext;


    /**
     * @deprecated Used DefaultUrLMappingsEvaluator(ApplicationContext) instead
     * @param servletContext The servlet context
     */
    @Deprecated
    public DefaultUrlMappingEvaluator(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public DefaultUrlMappingEvaluator(ApplicationContext applicationContext) {
        if (applicationContext instanceof WebApplicationContext) {
            this.servletContext = ((WebApplicationContext)applicationContext).getServletContext();
        }
        this.applicationContext = applicationContext;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List evaluateMappings(Resource resource) {
        InputStream inputStream = null;
        try {
            inputStream = resource.getInputStream();
            return evaluateMappings(classLoader.parseClass(IOGroovyMethods.getText(inputStream, "UTF-8")));
        }
        catch (IOException e) {
            throw new UrlMappingException("Unable to read mapping file [" + resource.getFilename() + "]: " + e.getMessage(), e);
        }
        finally {
            IOUtils.closeQuietly(inputStream);
        }
    }

    @SuppressWarnings({"unchecked","rawtypes"})
    public List<UrlMapping> evaluateMappings(Class theClass) {
        GroovyObject obj = (GroovyObject) BeanUtils.instantiateClass(theClass);

        if (obj instanceof Script) {
            Script script = (Script) obj;
            Binding b = new Binding();

            MappingCapturingClosure closure = new MappingCapturingClosure(script);
            b.setVariable("mappings", closure);
            script.setBinding(b);

            script.run();

            Closure mappings = closure.getMappings();

            Binding binding = script.getBinding();
            return evaluateMappings(script, mappings, binding);
        }

        throw new UrlMappingException("Unable to configure URL mappings for class [" + theClass +
                "]. A URL mapping must be an instance of groovy.lang.Script.");
    }

    private List<UrlMapping> evaluateMappings(GroovyObject go, Closure<?> mappings, Binding binding) {
        UrlMappingBuilder builder = new UrlMappingBuilder(binding, servletContext);
        mappings.setDelegate(builder);
        mappings.call();
        builder.urlDefiningMode = false;

        configureUrlMappingDynamicObjects(go);

        return builder.getUrlMappings();
    }

    @SuppressWarnings("rawtypes")
    public List<UrlMapping> evaluateMappings(Closure closure) {
        UrlMappingBuilder builder = new UrlMappingBuilder(null, servletContext);
        closure.setDelegate(builder);
        closure.setResolveStrategy(Closure.DELEGATE_FIRST);
        if (closure.getParameterTypes().length == 0) {
            closure.call();
        }
        else {
            closure.call(applicationContext);
        }
        builder.urlDefiningMode = false;
        configureUrlMappingDynamicObjects(closure);
        return builder.getUrlMappings();
    }

    private void configureUrlMappingDynamicObjects(Object object) {
        if (pluginManager != null) {
            ControllerDynamicMethods.registerCommonWebProperties(GrailsMetaClassUtils.getExpandoMetaClass(object.getClass()), null);
        }
    }

    public void setClassLoader(ClassLoader classLoader) {
        Assert.isInstanceOf(GroovyClassLoader.class, classLoader,
               "Property [classLoader] must be an instance of GroovyClassLoader");
        this.classLoader = (GroovyClassLoader) classLoader;
    }

    public void setPluginManager(GrailsPluginManager pluginManager) {
        this.pluginManager = pluginManager;
    }

    /**
     * A Closure that captures a call to a method that accepts a single closure
     */
    @SuppressWarnings("rawtypes")
    class MappingCapturingClosure extends Closure {

        private static final long serialVersionUID = 2108155626252742722L;
        private Closure<?> mappings;

        public Closure<?> getMappings() {
            return mappings;
        }

        public MappingCapturingClosure(Object o) {
            super(o);
        }

        @Override
        public Object call(Object... args) {
            if (args.length > 0 && (args[0] instanceof Closure)) {
                mappings = (Closure<?>) args[0];
            }
            return null;
        }
    }

    /**
     * <p>A modal builder that constructs a UrlMapping instances by executing a closure. The class overrides
     * getProperty(name) and allows the substitution of GString values with the * wildcard.
     * <p/>
     * <p>invokeMethod(methodName, args) is also overriden for the creation of each UrlMapping instance
     */
    @SuppressWarnings("rawtypes")
    class UrlMappingBuilder extends GroovyObjectSupport {
        private static final String CAPTURING_WILD_CARD = UrlMapping.CAPTURED_WILDCARD;
        private static final String SLASH = "/";
        private static final String CONSTRAINTS = "constraints";



        private boolean urlDefiningMode = true;
        private List<ConstrainedProperty> previousConstraints = new ArrayList<ConstrainedProperty>();
        private List<UrlMapping> urlMappings = new ArrayList<UrlMapping>();
        private Map<String, Object> parameterValues = new HashMap<String, Object>();
        private Binding binding;
        private ServletContext sc;
        private Object exception;
        private Object parseRequest;
        private Deque<ParentResource> parentResources = new ArrayDeque<ParentResource>();
        private Deque<MetaMappingInfo> mappingInfoDeque = new ArrayDeque<MetaMappingInfo>();

        public UrlMappingBuilder(Binding binding, ServletContext servletContext) {
            this.binding = binding;
            sc = servletContext;
        }

        public List<UrlMapping> getUrlMappings() {
            return urlMappings;
        }

        public ServletContext getServletContext() {
            return sc;
        }

        public ApplicationContext getApplicationContext() {
            return WebApplicationContextUtils.getRequiredWebApplicationContext(sc);
        }

        public GrailsApplication getGrailsApplication() {
            return getApplicationContext().getBean(GrailsApplication.APPLICATION_ID, GrailsApplication.class);
        }

        @Override
        public Object getProperty(String name) {
            if (urlDefiningMode) {
                final ConstrainedProperty newConstrained = new ConstrainedProperty(UrlMapping.class, name, String.class);
                previousConstraints.add(newConstrained);
                return CAPTURING_WILD_CARD;
            }
            return super.getProperty(name);
        }

        public Object getException() {
            return exception;
        }

        public void setException(Object exception) {
            this.exception = exception;
        }

        public Object getUri() {
            return getMetaMappingInfo().getUri();
        }

        public void setUri(Object uri) {
            getMetaMappingInfo().setUri(uri);
        }

        public void setAction(Object action) {
            getMetaMappingInfo().setAction(action);
        }

        public Object getAction() {
            return getMetaMappingInfo().getAction();
        }

        public void setController(Object controller) {
            getMetaMappingInfo().setController(controller);
        }

        public Object getController() {
            return getMetaMappingInfo().getController();
        }

        public void setRedirectInfo(Object redirectInfo) {
            getMetaMappingInfo().setRedirectInfo(redirectInfo);
        }

        public Object getRedirectInfo() {
            return getMetaMappingInfo().getRedirectInfo();
        }

        public void setPlugin(Object plugin) {
            getMetaMappingInfo().setPlugin(plugin);
        }

        public Object getPlugin() {
            return getMetaMappingInfo().getPlugin();
        }

        public void setNamespace(Object namespace) {
            getMetaMappingInfo().setNamespace(namespace);
        }

        public Object getNamespace() {
            return getMetaMappingInfo().getNamespace();
        }

        public Object getView() {
            return getMetaMappingInfo().getView();
        }

        public void setView(String viewName) {
            getMetaMappingInfo().setView(viewName);
        }

        public void name(Map<String, UrlMapping> m) {
            for (Map.Entry<String, UrlMapping> entry: m.entrySet()) {
                entry.getValue().setMappingName(entry.getKey());
            }
        }

        /**
         * Define a group
         *
         * @param uri The URI
         * @param mappings The mappings
         */
        public void group(String uri, Closure mappings) {

            try {
                parentResources.push(new ParentResource(null, uri, true));
                mappings.call();
            } finally {
                parentResources.pop();
            }
        }

        @Override
        public Object invokeMethod(String methodName, Object arg) {
            if (binding == null) {
                return invokeMethodClosure(methodName, arg);
            }
            return invokeMethodScript(methodName, arg);
        }

        private Object invokeMethodScript(String methodName, Object arg) {
            return _invoke(methodName, arg, null);
        }

        private Object invokeMethodClosure(String methodName, Object arg) {
            return _invoke(methodName, arg, this);
        }

        void propertyMissing(String name, Object value) {
            parameterValues.put(name, value);
        }

        Object propertyMissing(String name) {
            return parameterValues.get(name);
        }

        private Object _invoke(String methodName, Object arg, Object delegate) {
            try {
                MetaMappingInfo mappingInfo = pushNewMetaMappingInfo();
                List<ConstrainedProperty> currentConstraints = mappingInfo.getConstraints();
                Object[] args = (Object[]) arg;
                String mappedURI = establishFullURI(methodName, currentConstraints);
                final boolean isResponseCode = isResponseCode(mappedURI);
                if (mappedURI.startsWith(SLASH) || isResponseCode) {
                    // Create a new parameter map for this mapping.
                    parameterValues = new HashMap<String, Object>();
                    Map variables = binding != null ? binding.getVariables() : null;
                    try {
                        if( parentResources.isEmpty() ) {
                            urlDefiningMode = false;
                        }
                        args = args != null && args.length > 0 ? args : new Object[]{Collections.EMPTY_MAP};
                        if (args[0] instanceof Closure) {
                            UrlMappingData urlData = createUrlMappingData(mappedURI, isResponseCode);

                            Closure callable = (Closure) args[0];
                            if (delegate != null) {
                                callable.setDelegate(delegate);
                            }
                            callable.call();

                            if (binding != null) {
                                mappingInfo.setController(variables.get(GrailsControllerClass.CONTROLLER));
                                mappingInfo.setAction(variables.get(GrailsControllerClass.ACTION));
                                mappingInfo.setView(variables.get(GrailsControllerClass.VIEW));
                                mappingInfo.setUri(variables.get(UrlMapping.URI));
                                mappingInfo.setPlugin(variables.get(UrlMapping.PLUGIN));
                                mappingInfo.setNamespace(variables.get(UrlMapping.NAMESPACE));
                                if (variables.containsKey(UrlMapping.HTTP_METHOD)) {
                                    mappingInfo.setHttpMethod(variables.get(UrlMapping.HTTP_METHOD).toString());
                                }
                            }

                            ConstrainedProperty[] constraints = currentConstraints.toArray(new ConstrainedProperty[currentConstraints.size()]);
                            UrlMapping urlMapping;
                            if (mappingInfo.getUri() != null) {
                                try {
                                    urlMapping = new RegexUrlMapping(urlData, new URI(mappingInfo.getUri().toString()), constraints, sc);
                                } catch (URISyntaxException e) {
                                    throw new UrlMappingException("Cannot map to invalid URI: " + e.getMessage(), e);
                                }
                            } else {
                                urlMapping = createURLMapping(urlData, isResponseCode, mappingInfo.getRedirectInfo(), mappingInfo.getController(), mappingInfo.getAction(), mappingInfo.getNamespace(), mappingInfo.getPlugin(), mappingInfo.getView(), mappingInfo.getHttpMethod(), null, constraints);
                            }
                           
                            if (binding != null) {
                                Map bindingVariables = variables;
                                Object parse = getParseRequest(Collections.EMPTY_MAP, bindingVariables);
                                if (parse instanceof Boolean) {
                                    urlMapping.setParseRequest((Boolean) parse);
                                }
                            }
                            configureUrlMapping(urlMapping);
                            return urlMapping;
                        }

                        if (args[0] instanceof Map) {
                            Map namedArguments = (Map) args[0];
                            String uri = mappedURI;
                            String version = null;

                            if (namedArguments.containsKey(UrlMapping.VERSION)) {
                                version = namedArguments.get(UrlMapping.VERSION).toString();
                            }
                            if (namedArguments.containsKey(UrlMapping.NAMESPACE)) {
                                mappingInfo.setNamespace(namedArguments.get(UrlMapping.NAMESPACE).toString());
                            }

                            if (namedArguments.containsKey(UrlMapping.PLUGIN)) {
                                mappingInfo.setPlugin(namedArguments.get(UrlMapping.PLUGIN).toString());
                            }
                           
                            UrlMappingData urlData = createUrlMappingData(uri, isResponseCode);

                            if (namedArguments.containsKey(RESOURCE)) {
                                Object controller = namedArguments.get(RESOURCE);
                                String controllerName = controller.toString();
                                mappingInfo.setController(controllerName);
                                parentResources.push(new ParentResource(controllerName, uri, true));
                                try {
                                    invokeLastArgumentIfClosure(args);
                                } finally {
                                    parentResources.pop();
                                }
                                if (controller != null) {

                                    createSingleResourceRestfulMappings(controllerName, mappingInfo.getPlugin(), mappingInfo.getNamespace(), version, urlData, currentConstraints, calculateIncludes(namedArguments, DEFAULT_RESOURCE_INCLUDES));
                                }
                            } else if (namedArguments.containsKey(RESOURCES)) {
                                Object controller = namedArguments.get(RESOURCES);
                                String controllerName = controller.toString();
                                mappingInfo.setController(controllerName);
                                parentResources.push(new ParentResource(controllerName, uri, false));
                                try {
                                    invokeLastArgumentIfClosure(args);
                                } finally {
                                    parentResources.pop();
                                }
                                if (controller != null) {
                                    createResourceRestfulMappings(controllerName, mappingInfo.getPlugin(), mappingInfo.getNamespace(), version,urlData, currentConstraints, calculateIncludes(namedArguments, DEFAULT_RESOURCES_INCLUDES));
                                }
                            } else {

                                invokeLastArgumentIfClosure(args);
                                UrlMapping urlMapping = getURLMappingForNamedArgs(namedArguments, urlData, mappedURI, isResponseCode, currentConstraints);
                                configureUrlMapping(urlMapping);
                                return urlMapping;
                            }
                        }
                        return null;
                    } finally {
                        if (binding != null) {
                            variables.clear();
                        }
                        if (parentResources.isEmpty()) {
                            urlDefiningMode = true;
                        }
                    }
                } else if (!urlDefiningMode && CONSTRAINTS.equals(mappedURI)) {
                    ConstrainedPropertyBuilder builder = new ConstrainedPropertyBuilder(this);
                    if (args.length > 0 && (args[0] instanceof Closure)) {

                        Closure callable = (Closure) args[0];
                        callable.setDelegate(builder);
                        for (ConstrainedProperty constrainedProperty : currentConstraints) {
                            builder.getConstrainedProperties().put(constrainedProperty.getPropertyName(), constrainedProperty);
                        }
                        callable.call();
                    }
                    return builder.getConstrainedProperties();
                } else {
                    return super.invokeMethod(mappedURI, arg);
                }
            } finally {
                mappingInfoDeque.pop();
            }
        }

        private List<String> calculateIncludes(Map namedArguments, List<String> defaultResourcesIncludes) {
            List<String> includes = new ArrayList<String>(defaultResourcesIncludes);

            Object excludesObject = namedArguments.get("excludes");
            if (excludesObject != null ) {
                if (excludesObject instanceof List) {

                    List excludeList = (List) excludesObject;
                    for (Object exc : excludeList) {
                        if (exc != null) {
                            String excStr = exc.toString().toLowerCase();
                            includes.remove(excStr);
                        }
                    }
                }
                else {
                    includes.remove(excludesObject.toString());
                }
            }
            Object includesObject = namedArguments.get("includes");
            if (includesObject != null) {
                if (includesObject instanceof List) {

                    List includeList = (List) includesObject;
                    includes.clear();
                    for (Object inc : includeList) {
                        if (inc != null) {
                            String incStr = inc.toString().toLowerCase();
                            includes.add(incStr);
                        }
                    }
                }
                else {
                    includes.clear();
                    includes.add(includesObject.toString());
                }
            }
            return includes;
        }

        private String establishFullURI(String uri, List<ConstrainedProperty> constrainedList) {
            if (parentResources.isEmpty()) {
                return uri;
            }

            StringBuilder uriBuilder = new StringBuilder();
            ParentResource parentResource = parentResources.peek();
            if (parentResource.isSingle) {
                uriBuilder.append(parentResource.uri);
            }
            else {
                if (parentResource.controllerName != null) {
                    uriBuilder.append(parentResource.uri).append(SLASH).append(CAPTURING_WILD_CARD);
                    constrainedList.add(new ConstrainedProperty(UrlMapping.class, parentResource.controllerName + "Id", String.class));
                }
            }

            uriBuilder.append(uri);
            return uriBuilder.toString();
        }

        private void invokeLastArgumentIfClosure(Object[] args) {
            if (args.length > 1 && args[1] instanceof Closure) {
                ((Closure) args[1]).call();
            }
        }

        protected void createResourceRestfulMappings(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList, List<String> includes) {
            ConstrainedProperty[] constraintArray = constrainedList.toArray(new ConstrainedProperty[constrainedList.size()]);

            if (includes.contains(ACTION_INDEX)) {
                // GET /$controller -> action:'index'
                UrlMapping listUrlMapping = createIndexActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(listUrlMapping);
            }

            if (includes.contains(ACTION_CREATE)) {
                // GET /$controller/create -> action:'create'
                UrlMapping createUrlMapping = createCreateActionResourcesRestfulMapping(controllerName, pluginName,namespace, version, urlData, constraintArray);
                configureUrlMapping(createUrlMapping);
            }

            if (includes.contains(ACTION_SAVE)) {
                // POST /$controller -> action:'save'
                UrlMapping saveUrlMapping = createSaveActionResourcesRestfulMapping(controllerName, pluginName, namespace,version, urlData, constrainedList);
                configureUrlMapping(saveUrlMapping);
            }

            if (includes.contains(ACTION_SHOW)) {
                // GET /$controller/$id -> action:'show'
                UrlMapping showUrlMapping = createShowActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(showUrlMapping);
            }

            if (includes.contains(ACTION_EDIT)) {
                // GET /$controller/$id/edit -> action:'edit'
                UrlMapping editUrlMapping = createEditActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(editUrlMapping);
            }

            if (includes.contains(ACTION_UPDATE)) {
                // PUT /$controller/$id -> action:'update'
                UrlMapping updateUrlMapping = createUpdateActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(updateUrlMapping);
            }

            if (includes.contains(ACTION_PATCH)) {
                // PATCH /$controller/$id -> action:'patch'
                UrlMapping patchUrlMapping = createPatchActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(patchUrlMapping);
            }

            if (includes.contains(ACTION_DELETE)) {
                // DELETE /$controller/$id -> action:'delete'
                UrlMapping deleteUrlMapping = createDeleteActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(deleteUrlMapping);
            }
        }

        protected UrlMapping createDeleteActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData deleteUrlMappingData = createRelativeUrlDataWithIdAndFormat(urlData);
            List<ConstrainedProperty> deleteUrlMappingConstraints = createConstraintsWithIdAndFormat(constrainedList);

            return new RegexUrlMapping(deleteUrlMappingData,controllerName, ACTION_DELETE, namespace, pluginName, null, HttpMethod.DELETE.toString(), version,deleteUrlMappingConstraints.toArray(new ConstrainedProperty[deleteUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createUpdateActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData updateUrlMappingData = createRelativeUrlDataWithIdAndFormat(urlData);
            List<ConstrainedProperty> updateUrlMappingConstraints = createConstraintsWithIdAndFormat(constrainedList);

            return new RegexUrlMapping(updateUrlMappingData,controllerName, ACTION_UPDATE,  namespace, pluginName, null, HttpMethod.PUT.toString(),version,updateUrlMappingConstraints.toArray(new ConstrainedProperty[updateUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createPatchActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData patchUrlMappingData = createRelativeUrlDataWithIdAndFormat(urlData);
            List<ConstrainedProperty> patchUrlMappingConstraints = createConstraintsWithIdAndFormat(constrainedList);

            return new RegexUrlMapping(patchUrlMappingData,controllerName, ACTION_PATCH,  namespace, pluginName, null, HttpMethod.PATCH.toString(),version,patchUrlMappingConstraints.toArray(new ConstrainedProperty[patchUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createEditActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData editUrlMappingData = urlData.createRelative('/' + CAPTURING_WILD_CARD + "/edit");
            List<ConstrainedProperty> editUrlMappingConstraints = new ArrayList<ConstrainedProperty>(constrainedList);
            editUrlMappingConstraints.add(new ConstrainedProperty(UrlMapping.class, "id", String.class));

            return new RegexUrlMapping(editUrlMappingData,controllerName, ACTION_EDIT, namespace, pluginName, null, HttpMethod.GET.toString(), version,editUrlMappingConstraints.toArray(new ConstrainedProperty[editUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createShowActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData showUrlMappingData = createRelativeUrlDataWithIdAndFormat(urlData);
            List<ConstrainedProperty> showUrlMappingConstraints = createConstraintsWithIdAndFormat(constrainedList);

            return new RegexUrlMapping(showUrlMappingData,controllerName, ACTION_SHOW, namespace, pluginName, null, HttpMethod.GET.toString(), version,showUrlMappingConstraints.toArray(new ConstrainedProperty[showUrlMappingConstraints.size()]) , servletContext);
        }

        private List<ConstrainedProperty> createConstraintsWithIdAndFormat(List<ConstrainedProperty> constrainedList) {
            List<ConstrainedProperty> showUrlMappingConstraints = new ArrayList<ConstrainedProperty>(constrainedList);
            showUrlMappingConstraints.add(new ConstrainedProperty(UrlMapping.class, "id", String.class));
            ConstrainedProperty cp = new ConstrainedProperty(UrlMapping.class, "format", String.class);
            cp.setNullable(true);
            showUrlMappingConstraints.add(cp);
            return showUrlMappingConstraints;
        }

        private UrlMappingData createRelativeUrlDataWithIdAndFormat(UrlMappingData urlData) {
            return urlData.createRelative('/' + CAPTURING_WILD_CARD + UrlMapping.OPTIONAL_EXTENSION_WILDCARD + UrlMapping.QUESTION_MARK);
        }
        private UrlMappingData createFormatOnlyUrlMappingData(UrlMappingData urlData) {
            return urlData.createRelative(UrlMapping.OPTIONAL_EXTENSION_WILDCARD + UrlMapping.QUESTION_MARK);
        }


        protected UrlMapping createSaveActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData saveActionUrlMappingData = urlData.createRelative(UrlMapping.OPTIONAL_EXTENSION_WILDCARD + UrlMapping.QUESTION_MARK);
            List<ConstrainedProperty> saveUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(saveActionUrlMappingData,controllerName, ACTION_SAVE, namespace, pluginName, null, HttpMethod.POST.toString(),version,saveUrlMappingConstraints.toArray(new ConstrainedProperty[saveUrlMappingConstraints.size()]), servletContext);
        }

        protected UrlMapping createCreateActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, ConstrainedProperty[] constraintArray) {
            UrlMappingData createMappingData = urlData.createRelative("/create");
            return new RegexUrlMapping(createMappingData,controllerName, ACTION_CREATE, namespace, pluginName, null, HttpMethod.GET.toString(), version,constraintArray, servletContext);
        }

        protected UrlMapping createIndexActionResourcesRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData indexActionUrlMappingData = urlData.createRelative(UrlMapping.OPTIONAL_EXTENSION_WILDCARD + UrlMapping.QUESTION_MARK);
            List<ConstrainedProperty> indexUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(indexActionUrlMappingData, controllerName, ACTION_INDEX, namespace, pluginName, null, HttpMethod.GET.toString(), version,indexUrlMappingConstraints.toArray(new ConstrainedProperty[indexUrlMappingConstraints.size()]), servletContext);
        }

        private List<ConstrainedProperty> createFormatOnlyConstraints(List<ConstrainedProperty> constrainedList) {
            List<ConstrainedProperty> indexUrlMappingConstraints = new ArrayList<ConstrainedProperty>(constrainedList);
            ConstrainedProperty cp = new ConstrainedProperty(UrlMapping.class, "format", String.class);
            cp.setNullable(true);
            indexUrlMappingConstraints.add(cp);
            return indexUrlMappingConstraints;
        }

        /**
         * Takes a controller and creates the necessary URL mappings for a singular RESTful resource
         *
         * @param controllerName The controller name
         * @param pluginName The name of the plugin
         * @param namespace
         * @param version
         * @param urlData   The urlData instance
         * @param includes
         */
        protected void createSingleResourceRestfulMappings(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList, List<String> includes) {

            ConstrainedProperty[] constraintArray = constrainedList.toArray(new ConstrainedProperty[constrainedList.size()]);

            if (includes.contains(ACTION_CREATE)) {
                // GET /$controller/create -> action: 'create'
                UrlMapping createUrlMapping = createCreateActionResourcesRestfulMapping(controllerName, pluginName, namespace,version, urlData, constraintArray);
                configureUrlMapping(createUrlMapping);
            }

            if (includes.contains(ACTION_SAVE)) {
                // POST /$controller -> action:'save'
                UrlMapping saveUrlMapping = createSaveActionResourcesRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(saveUrlMapping);
            }

            if (includes.contains(ACTION_SHOW)) {
                // GET /$controller -> action:'show'
                UrlMapping showUrlMapping = createShowActionResourceRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(showUrlMapping);
            }

            if (includes.contains(ACTION_EDIT)) {
                // GET /$controller/edit -> action:'edit'
                UrlMapping editUrlMapping = createEditActionResourceRestfulMapping(controllerName, pluginName, namespace,version,urlData, constraintArray);
                configureUrlMapping(editUrlMapping);
            }

            if (includes.contains(ACTION_UPDATE)) {
                // PUT /$controller -> action:'update'
                UrlMapping updateUrlMapping = createUpdateActionResourceRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(updateUrlMapping);
            }

            if (includes.contains(ACTION_PATCH)) {
                // PATCH /$controller -> action:'patch'
                UrlMapping patchUrlMapping = createPatchActionResourceRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(patchUrlMapping);
            }

            if (includes.contains(ACTION_DELETE)) {
                // DELETE /$controller -> action:'delete'
                UrlMapping deleteUrlMapping = createDeleteActionResourceRestfulMapping(controllerName, pluginName, namespace,version,urlData, constrainedList);
                configureUrlMapping(deleteUrlMapping);
            }
        }

        protected UrlMapping createDeleteActionResourceRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData deleteUrlMappingData = createFormatOnlyUrlMappingData(urlData);
            List<ConstrainedProperty> deleteUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(deleteUrlMappingData,controllerName, ACTION_DELETE, namespace, pluginName, null, HttpMethod.DELETE.toString(), version, deleteUrlMappingConstraints.toArray(new ConstrainedProperty[deleteUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createUpdateActionResourceRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData updateUrlMappingData = createFormatOnlyUrlMappingData(urlData);
            List<ConstrainedProperty> updateUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(updateUrlMappingData,controllerName, ACTION_UPDATE, namespace, pluginName, null, HttpMethod.PUT.toString(),version, updateUrlMappingConstraints.toArray(new ConstrainedProperty[updateUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createPatchActionResourceRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData patchUrlMappingData = createFormatOnlyUrlMappingData(urlData);
            List<ConstrainedProperty> patchUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(patchUrlMappingData,controllerName, ACTION_PATCH, namespace, pluginName, null, HttpMethod.PATCH.toString(),version, patchUrlMappingConstraints.toArray(new ConstrainedProperty[patchUrlMappingConstraints.size()]) , servletContext);
        }

        protected UrlMapping createEditActionResourceRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, ConstrainedProperty[] constraintArray) {
            UrlMappingData editMappingData = urlData.createRelative("/edit");
            return new RegexUrlMapping(editMappingData,controllerName,ACTION_EDIT, namespace, pluginName, null, HttpMethod.GET.toString(),version, constraintArray, servletContext);
        }

        protected UrlMapping createShowActionResourceRestfulMapping(String controllerName, Object pluginName, Object namespace, String version, UrlMappingData urlData, List<ConstrainedProperty> constrainedList) {
            UrlMappingData showUrlMappingData = createFormatOnlyUrlMappingData(urlData);
            List<ConstrainedProperty> showUrlMappingConstraints = createFormatOnlyConstraints(constrainedList);

            return new RegexUrlMapping(showUrlMappingData,controllerName, ACTION_SHOW, namespace, pluginName, null, HttpMethod.GET.toString(), version, showUrlMappingConstraints.toArray(new ConstrainedProperty[showUrlMappingConstraints.size()]) , servletContext);
        }

        @SuppressWarnings("unchecked")
        private void configureUrlMapping(UrlMapping urlMapping) {
            if (binding != null) {
                Map<String, Object> vars = binding.getVariables();
                for (String key : vars.keySet()) {
                    if (isNotCoreMappingKey(key)) {
                        parameterValues.put(key, vars.get(key));
                    }
                }

                binding.getVariables().clear();
            }

            // Add the controller and action to the params map if
            // they are set. This ensures consistency of behaviour
            // for the application, i.e. "controller" and "action"
            // parameters will always be available to it.
            if (urlMapping.getControllerName() != null) {
                parameterValues.put("controller", urlMapping.getControllerName());
            }
            if (urlMapping.getActionName() != null) {
                parameterValues.put("action", urlMapping.getActionName());
            }

            urlMapping.setParameterValues(new LinkedHashMap(parameterValues));
            urlMappings.add(urlMapping);
        }

        private boolean isNotCoreMappingKey(Object key) {
            return !GrailsControllerClass.ACTION.equals(key) &&
                   !GrailsControllerClass.CONTROLLER.equals(key) &&
                   !GrailsControllerClass.VIEW.equals(key);
        }

        private UrlMappingData createUrlMappingData(String methodName, boolean responseCode) {
            if (!responseCode) {
                return urlParser.parse(methodName);
            }

            return new ResponseCodeMappingData(methodName);
        }

        private boolean isResponseCode(String s) {
            for (int count = s.length(), i = 0; i < count; i++) {
                if (!Character.isDigit(s.charAt(i))) return false;
            }

            return true;
        }

        private UrlMapping getURLMappingForNamedArgs(Map namedArguments,
                UrlMappingData urlData, String mapping, boolean isResponseCode, List<ConstrainedProperty> constrainedList) {
            final Map bindingVariables = binding != null ? binding.getVariables() : null;
            Object controllerName = getControllerName(namedArguments, bindingVariables);
            Object actionName = getActionName(namedArguments, bindingVariables);
            Object pluginName = getPluginName(namedArguments, bindingVariables);
            Object httpMethod = getHttpMethod(namedArguments, bindingVariables);
            Object version = getVersion(namedArguments, bindingVariables);
            Object namespace = getNamespace(namedArguments, bindingVariables);
            Object redirectInfo = getRedirectInfo(namedArguments, bindingVariables);

            Object viewName = getViewName(namedArguments, bindingVariables);
            if (actionName != null && viewName != null) {
                viewName = null;
                LOG.warn("Both [action] and [view] specified in URL mapping [" + mapping + "]. The action takes precendence!");
            }

            Object uri = getURI(namedArguments, bindingVariables);
            ConstrainedProperty[] constraints = constrainedList.toArray(new ConstrainedProperty[constrainedList.size()]);

            UrlMapping urlMapping;
            if (uri != null) {
                try {
                    urlMapping = new RegexUrlMapping(urlData, new URI(uri.toString()), constraints, sc);
                }
                catch (URISyntaxException e) {
                    throw new UrlMappingException("Cannot map to invalid URI: " + e.getMessage(), e);
                }
            }
            else {
                urlMapping = createURLMapping(urlData, isResponseCode, redirectInfo, controllerName, actionName, namespace, pluginName, viewName, httpMethod != null ? httpMethod.toString() : null, version != null ? version.toString() : null, constraints);
            }

            Object exceptionArg = getException(namedArguments, bindingVariables);

            if (isResponseCode && exceptionArg != null) {
                if (exceptionArg instanceof Class) {
                    Class exClass = (Class) exceptionArg;
                    if (Throwable.class.isAssignableFrom(exClass)) {
                        ((ResponseCodeUrlMapping)urlMapping).setExceptionType(exClass);
                    }
                    else {
                        LOG.error("URL mapping argument [exception] with value ["+ exceptionArg +"] must be a subclass of java.lang.Throwable");
                    }
                }
                else {
                    LOG.error("URL mapping argument [exception] with value [" + exceptionArg + "] must be a valid class");
                }
            }

            Object parseRequest = getParseRequest(namedArguments,bindingVariables);
            if (parseRequest instanceof Boolean) {
                urlMapping.setParseRequest((Boolean) parseRequest);
            }


            return urlMapping;
        }

        private Object getVariableFromNamedArgsOrBinding(Map namedArguments, Map bindingVariables, String variableName, Object defaultValue) {
            Object returnValue;
            returnValue = namedArguments.get(variableName);
            if (returnValue == null) {
                returnValue = binding != null ? bindingVariables.get(variableName) : defaultValue;
            }
            return returnValue;
        }

        private Object getActionName(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables,GrailsControllerClass.ACTION, getMetaMappingInfo().getAction());
        }

        private Object getParseRequest(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables,PARSE_REQUEST, parseRequest);
        }

        private Object getControllerName(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables,GrailsControllerClass.CONTROLLER, getMetaMappingInfo().getController());
        }

        private Object getPluginName(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.PLUGIN, getMetaMappingInfo().getPlugin());
        }

        private Object getHttpMethod(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.HTTP_METHOD, getMetaMappingInfo().getHttpMethod());
        }

        private Object getRedirectInfo(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.REDIRECT_INFO, getMetaMappingInfo().getRedirectInfo());
        }

        private Object getVersion(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.VERSION, getMetaMappingInfo().getView());
        }

        private Object getNamespace(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.NAMESPACE, getMetaMappingInfo().getNamespace());
        }

        private Object getViewName(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables,GrailsControllerClass.VIEW, getMetaMappingInfo().getView());
        }

        private Object getURI(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables, UrlMapping.URI, getMetaMappingInfo().getUri());
        }

        private Object getException(Map namedArguments, Map bindingVariables) {
            return getVariableFromNamedArgsOrBinding(namedArguments, bindingVariables,EXCEPTION, exception);
        }

        private UrlMapping createURLMapping(UrlMappingData urlData, boolean isResponseCode, Object redirectInfo,
                                            Object controllerName, Object actionName, Object namespace, Object pluginName,
                                            Object viewName, String httpMethod, String version, ConstrainedProperty[] constraints) {
            if (!isResponseCode) {
                return new RegexUrlMapping(redirectInfo, urlData, controllerName, actionName, namespace, pluginName, viewName, httpMethod,  version, constraints, sc);
            }

            return new ResponseCodeUrlMapping(urlData, controllerName, actionName, namespace, pluginName, viewName,
                    null, sc);
        }
       
        protected MetaMappingInfo pushNewMetaMappingInfo() {
            MetaMappingInfo mappingInfo = new MetaMappingInfo();
            MetaMappingInfo parentMappingInfo = mappingInfoDeque.peek();
            if(parentMappingInfo != null) {
                List<ConstrainedProperty> parentMappingConstraints = parentMappingInfo.getConstraints();
                if(parentMappingConstraints != null) {
                    mappingInfo.getConstraints().addAll(parentMappingConstraints);
                }
            }
            if(previousConstraints.size() > 0) {
                mappingInfo.getConstraints().addAll(previousConstraints);
                previousConstraints.clear();
            }
           
            mappingInfoDeque.push(mappingInfo);
            return mappingInfo;
        }

        protected MetaMappingInfo getMetaMappingInfo() {
            return mappingInfoDeque.peek();
        }
       
        class ParentResource {
            String controllerName;
            String uri;
            boolean isSingle;

            ParentResource(String controllerName, String uri, boolean single) {
                this.controllerName = controllerName;
                this.uri = uri;
                isSingle = single;
            }
        }
    }
}
TOP

Related Classes of org.grails.web.mapping.DefaultUrlMappingEvaluator

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.