20

As an example, take subdomain mapping.

This article: Managing multiple Domain and Sub Domain on Google App Engine for Same Application recommends to resolve subdomain on Filter and assign variable to ServletRequest headers.

Then the mapping will look like this:

@RequestMapping(value = "/path", headers="subdomain=www")
 public String subsiteIndexPage(Model model,HttpServletRequest request) { ... }

If we'd like to create custom @RequestMapping property, such as subdomain, eg. to create mapping like this:

@RequestMapping(value = "/some/action", subdomain = "www")
public String handlerFunction(){ ... }

we should override @RequestMapping @interface definition and override RequestMappingHandlerMapping protected methods, with our own implementation
(as stated on JIRA: "Allow custom request mapping conditions SPR-7812").

Is it right? Can anybody provide a hint, how to achieve this functionality?


Idea 1:
As suggested on original jira thread, is to create own implementation of RequestCondition

There is an project which uses this solution available on github: https://github.com/rstoyanchev/spring-mvc-31-demo/

And related SO question: Adding custom RequestCondition's in Spring mvc 3.1

Maybe mapping like @Subdomain("www") for both Type and Method, is possible solution?


Link to same question on forum.springsource.com

Community
  • 1
  • 1
Marek Sebera
  • 39,650
  • 37
  • 158
  • 244

2 Answers2

34

I've created solution based on referenced spring-mvc-31-demo

This solution can be used to map only single RequestCondition as of now. I've created two Issues to notify, this should be changed:
https://github.com/rstoyanchev/spring-mvc-31-demo/issues/5
https://jira.springsource.org/browse/SPR-9350

This solution uses custom @RequestCondition feature of Spring 3.1.1.RELEASE platform

USAGE

Example 1:

@Controller
@SubdomainMapping(value = "subdomain", tld = ".mydomain.com")
class MyController1 {
    // Code here will be executed only on address match:
    // subdomain.mydomain.com
}

Example 2:

@Controller
class MyController2 {

    @RequestMapping("/index.html")
    @SubdomainMapping("www")
    public function index_www(Map<Object, String> map){
        // on www.domain.com
        // where ".domain.com" is defined in SubdomainMapping.java
    }

    @RequestMapping("/index.html")
    @SubdomainMapping("custom")
    public function index_custom(Map<Object, String> map){
        // on custom.domain.com
        // where ".domain.com" is defined in SubdomainMapping.java
    }
}

We need three files

  • SubdomainMapping.java
  • SubdomainRequestCondition.java
  • SubdomainRequestMappingHandlerMapping.java

SubdomainMapping.java

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SubdomainMapping {

    /**
    * This param defines single or multiple subdomain
    * Where the Method/Type is valid to be called
    */
    String[] value() default {};
    /**
    * This param defines site domain and tld
    * It's important to put the leading dot
    * Not an array, so cannot be used for mapping multiple domains/tld
    */
    String tld() default ".custom.tld";
}

SubdomainRequestCondition.java

import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

public class SubdomainRequestCondition implements
        RequestCondition<SubdomainRequestCondition> {

    private final Set<String> subdomains;
    private final String tld;

    public SubdomainRequestCondition(String tld, String... subdomains) {
        this(tld, Arrays.asList(subdomains));
    }

    public SubdomainRequestCondition(String tld, Collection<String> subdomains) {
        this.subdomains = Collections.unmodifiableSet(new HashSet<String>(
                subdomains));
        this.tld = tld;
    }

    @Override
    public SubdomainRequestCondition combine(SubdomainRequestCondition other) {
        Set<String> allRoles = new LinkedHashSet<String>(this.subdomains);
        allRoles.addAll(other.subdomains);
        return new SubdomainRequestCondition(tld, allRoles);
    }

    @Override
    public SubdomainRequestCondition getMatchingCondition(
            HttpServletRequest request) {
        try {
            URL uri = new URL(request.getRequestURL().toString());
            String[] parts = uri.getHost().split(this.tld);
            if (parts.length == 1) {
                for (String s : this.subdomains) {
                    if (s.equalsIgnoreCase(parts[0])) {
                        return this;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace(System.err);
        }
        return null;
    }

    @Override
    public int compareTo(SubdomainRequestCondition other,
            HttpServletRequest request) {
        return org.apache.commons.collections.CollectionUtils.removeAll(other.subdomains, this.subdomains).size();
    }

}

SubdomainRequestMappingHandlerMapping.java

import java.lang.reflect.Method;

import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

public class CustomRequestMappingHandlerMapping extends
        RequestMappingHandlerMapping {

    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        SubdomainMapping typeAnnotation = AnnotationUtils.findAnnotation(
                handlerType, SubdomainMapping.class);
        return createCondition(typeAnnotation);
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        SubdomainMapping methodAnnotation = AnnotationUtils.findAnnotation(
                method, SubdomainMapping.class);
        return createCondition(methodAnnotation);
    }

    private RequestCondition<?> createCondition(SubdomainMapping accessMapping) {
        return (accessMapping != null) ? new SubdomainRequestCondition(
                accessMapping.tld(), accessMapping.value()) : null;
    }

}

Instalation

IMPORTANT: So far, it is not possible to use this solution with XML element
<mvc:annotation-driven />, see JIRA https://jira.springsource.org/browse/SPR-9344 for explanation

  • You have to register custom MappingHandler bean, pointing at this custom implementation SubdomainRequestMappingHandlerMapping class
  • You have to set it's order to be lower than default RequestMappingHandlerMapping
    OR
    Replace the registered RequestMappingHandlerMapping (possibly on order=0)

For more wide explanation on implementing this solution, see the related github project

Marek Sebera
  • 39,650
  • 37
  • 158
  • 244
  • 1
    To solve the problem with , you can replace it with java based config by extending WebMvcConfigurationSupport (read the javadoc in @EnableWebMvc, jump straight to the last case) – Victor Basso Mar 04 '15 at 14:55
  • For a xml based alternative to , check this SO question: http://stackoverflow.com/questions/3693397/howto-get-rid-of-mvcannotation-driven – Victor Basso Mar 04 '15 at 14:56
  • Could I use this approach for request mapping depends on condition from request body? – Waka Waka Jan 13 '19 at 12:19
  • @WakaWaka you should be able to use similar approach – Marek Sebera Jan 13 '19 at 16:47
  • I have implemented the same thing, @Vituel do you have any example to achieve the annotation thing using XML. current defining the annotation directly in class works well. – Touqeer Shafi Dec 03 '19 at 11:16
1

That's correct, but that would be too complicated. You'd better check the Host header, whether it contains a given subdomain.

But you should not really need this more than once or twice, so you can also do it manually in the method body. If you really need it in many places, it would be an odd requirement.

Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • odd in which way? It gives me probably better control, than many Url Rewrite rules. Like If you have a platform, and you need to differentiate actions based on authenticated role (admin, manager, user) and you can have mappings such as `/admin/*`, `/manager/*`, etc.. or you can have `admin.domain.tld/*`, `manager.domain.tld/*`, ... – Marek Sebera Apr 25 '12 at 10:09
  • Please see suggestion 1 in question. Maybe this could be elegant solution – Marek Sebera Apr 25 '12 at 10:19
  • Can you think about some nicer implementation of @RequestCondition than extending RequestMappingHandlerMapping ? – Marek Sebera Apr 25 '12 at 10:48
  • 1
    as far as I see, no. Though I would assume you should be able to plug your implementation in some list somehwre. But I can't find it – Bozho Apr 25 '12 at 11:43
  • I've put my own solution to this question, please consider making any updates and/or improvements to my code. Thanks – Marek Sebera Apr 26 '12 at 15:45