/url-parameter-mapping

Flexible URL parameter mapper for Vaadin Flow

Primary LanguageJavaOtherNOASSERTION

Published on Vaadin  Directory Latest published version Stars on vaadin.com/directory

URL Parameter Mapping for Vaadin Flow

While vaadin/flow#2740 and vaadin/flow#4213 are still in the works, the need for flexible parametrized routes still exist. This helper implementation lives on top of built in HasUrlParameter and provides support for named parameters.

Installing with Maven:

<repository>
   <id>vaadin-addons</id>
   <url>http://maven.vaadin.com/vaadin-addons</url>
</repository>
<dependency>
   <groupId>org.vaadin.helper</groupId>
   <artifactId>url-parameter-mapping</artifactId>
   <version>1.0.0-beta2</version>
</dependency>

Usage example:

import org.vaadin.flow.helper.*;

...

@Route("example")
@UrlParameterMapping(":exampleId/:orderId")
// Will match /example/12345/ORD223434, set exampleId = 12345 and
// call setOrder("ORD223434")
// Otherwise user will be rerouted to default NotFoundException view
class MyView extends Div implements HasUrlParameterMapping {
    // Note: parameter fields/setters should be public    
    @UrlParameter
    public Integer exampleId;
    
    @UrlParameter(name = "orderId", regEx = "ORD[0-9]{6}") 
    public setOrder(String order) { ... }
    ...
}

Optional parameters are supported:

@Route("example")
@UrlParameterMapping(":exampleId[/:version]")
// Will match /example/12345 and /example/12345/678
// version property will receive null when missing 

Static segments are supported:

@Route("example")
@UrlParameterMapping("edit/:exampleId")
// Will match /example/edit/12345

Parameters can be anywhere:

@Route("order")
@UrlParameterMapping("detail/:orderId/edit")
// Will match /order/detail/12345/edit

Those could be also optional:

@Route("order")
@UrlParameterMapping("detail[/:rowId]/edit")
// Will match /order/detail/12345/edit and /order/detail/edit

Full route paths can also be used for matching:

import org.vaadin.flow.helper.*;

...

@Route("example")
@UrlParameterMapping("example/:exampleId")
// Will match /example/12345 and call setExampleId(12345)
class MyView extends Div implements HasAbsoluteUrlParameterMapping {
    //                         note ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    @UrlParameter
    public Integer exampleId;
    
    ...
}

Regular expressions are supported:

@Route("new-message")
@UrlParameterMapping(":userId")
...
@UrlParameter(regEx="[0-9]{1,6}")
Integer userId;
// Will match /new-message/123456, but not /new-message/1234567

Regular expressions could be also dynamic:

import org.vaadin.flow.helper.*;

...

@Route("example")
@UrlParameterMapping(":selectedTab")
// Will match /example/12345 and call setExampleId(12345)
class MyView extends Div implements HasUrlParameterMapping {
    static {
        UrlParameterMappingHelper.setDynamicRegex(MyView.class, "selectedTab", MyView::getSelectedTabRegex);
    }

    @UrlParameter(dynamicRegEx = true)
    public String selectedTab;
   
    public String getSelectedTabRegex() {
        return String.join("|", backendService.getAvailableTabs());
    }    
    ...
}

Multiple mappings are supported:

@Route("forum/thread")
@RouteAlias("forum/message")
@UrlParameterMapping("forum/thread/:threadId[/:urlTitle]")
@UrlParameterMapping("forum/thread/:threadId/:messageId")
@UrlParameterMapping("forum/message/:messageId")
// Will match (with HasAbsoluteUrlParameterMapping)
// - /forum/thread/12345
// - /forum/thread/12345/forum-post-title
// - /forum/thread/12345/67890
// - /forum/message/67890

It is also possible to check which of patterns matched:

@Route(...)
@UrlParameterMapping(SomeView.ORDER_VIEW)
@UrlParameterMapping(SomeView.ORDER_EDIT)
class SomeView extends Div implements HasUrlParameterMapping {
    final static String ORDER_VIEW = ":orderId[/view]";
    final static String ORDER_EDIT = ":orderId/edit";

    @UrlMatchedPatternParameter()
    public String matchedPattern;

    @UrlParameter(name = "orderId")
    public void setOrder(Integer orderId) { ... }

    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if ( ORDER_VIEW.equals(matchedPattern) ) {
            ...
        } else {
            ...
        }
    }

    ...
}

Query parameter handling

import org.vaadin.flow.helper.*;

...

@Route("example")
@UrlParameterMapping(queryParameters = { "exampleId=:exampleId", "mode=:mode" })
@UrlParameterMapping(path = ":exampleId", queryParameters = { "mode=:mode" })
// Will match /example/12345 and set exampleId to 12345, mode to null
// Will also match /example?exampleId=34567&mode=edit and set exampleId to 34567 and mode to "edit"
class MyView extends Div implements HasUrlParameterMapping {
    @UrlParameter
    public Integer exampleId;
    
    @UrlParameter(regEx = "edit|preview")
    public String mode;
    ...
}

URL formatting

@UrlParameter
public Integer orderId = 12345;

@UrlParameter
public Integer orderRowId = null;

...

String url = UrlParameterMappingHelper.format(this,"order/:orderId[/:orderRowId]/:1", "edit");
// url = "order/12345/edit"

orderRowId = 78;
String url2 = UrlParameterMappingHelper.format(this,"order/:orderId[/:orderRowId]/:1", "edit");
// url = "order/12345/78/edit"

URL parameter matching could also be used for Vaadin RequestHandler:

class DownloadRequestHandler implements RequestHandler {
    @UrlParameterMapping("download/:uuid")
    public class ParameterMapping {
        @UrlParameter()
        public UUID uuid;
    }

    boolean handleRequest(VaadinSession session, VaadinRequest request,
                VaadinResponse response) throws IOException {
		VaadinServletRequest servletRequest = (VaadinServletRequest) request;
        ParameterMapping mapping = new ParameterMapping();
		
        if ( !UrlParameterMappingHelper.match(mapping, servletRequest.getRequestURI())) {
            return false;
        }
        
        ...
    }
}

If no matches are detected, automatic rerouteToError(NotFoundException.class) will be performed. It's possible to use custom exception or view using @RerouteIfNotMatched(...) annotation, or disable this feature completely using @IgnoreIfNotMatched annotation. In this case you can check if there were any matches using @UrlMatchedPatternParameter annotated field/setter. Note: query parameters are always optional and do not trigger rerouting.

When no custom regular expression is specified, it is automatically derived from field/method type:

  • String: [^/^?^&]+ -- anything up to next /, ? or &
  • Integer: -?[0-1]?[0-9]{1,9} -- -1999999999 to 1999999999
  • Long: -?[0-8]?[0-9]{1,18} -- -8999999999999999999 to 8999999999999999999
  • Boolean: true|false
  • UUID: [0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}

Development instructions

Starting the test/demo server:

mvn jetty:run

This deploys demo at http://localhost:8080