Java - Better way to parse a RESTful resource URL
Asked Answered
E

6

6

I'm new to developing web services in Java (previously I've done them in PHP and Ruby). I'm writing a resource that is of the following format:

<URL>/myService/<domain>/<app_name>/<system_name>

As you can see, I've got a three-level resource identifier, and I'm trying to figure out the best way to parse it. The application I'm adding this new service to doesn't make use of Jersey or any RESTful frameworks like that. Instead, it's just extending HttpServlet.

Currently they're following an algorithm like this:

  • Call request.getPathInfo()
  • Replace the "/" characters in the path info with "." characters
  • Use String.substring methods to extract individual pieces of information for this resource from the pathInfo string.

This doesn't seem very elegant to me, and I'm looking for a better way. I know that using the javax.ws.rs package makes this very easy (using @Path and @PathParam annotations), but using Jersey is probably not an option.

Using only the base HttpServletRequest object and standard Java libraries, is there a better way to parse this information than the method described above?

Entelechy answered 24/7, 2013 at 17:4 Comment(1)
You can just have multiple servlets mapped to different urls. Maybe one for each resource.Chalcopyrite
S
10

How about jersey UriTemplate?

import com.sun.jersey.api.uri.UriTemplate;

...

String path = "/foos/foo/bars/bar";

Map<String, String> map = new HashMap<String, String>();
UriTemplate template = new UriTemplate("/foos/{foo}/bars/{bar}");
if( template.match(path, map) ) {
    System.out.println("Matched, " + map);
} else {
    System.out.println("Not matched, " + map);
}       
Sloe answered 5/2, 2014 at 2:48 Comment(1)
same as matching can we use it to create urls by replacing the variable values ( like these {foo})Aviculture
P
2

I had the same problem as you and, as I didn't find any suitable library, I decided to write URL-RESTify. You may use it or just take a look to write your own solution, it's a small project.

Perennial answered 29/9, 2013 at 20:35 Comment(0)
C
2

I've recently solved this issue in one of my applications. My URLs look like this.

/categories/{category}/subcategories/{subcategory}

My problem was that I wanted to map each url pattern with a Java class, so that I could call upon the correct class to render the data.

My application uses Netty, but the URL resolver doesn't use any third party libraries.

What this allows me to do is to parse the URL that is coming in from the browser, generate a map that has key-value pairs (in this case category, and subcategory), as well as instantiate the correct handler for each unique URL pattern. All in all only about 150 lines of Java code for the parsing, the application setup and the definition of the unique URL patterns.

You can view the code for the resolver in GitHub: https://github.com/joachimhs/Contentice/blob/master/Contentice.api/src/main/java/no/haagensoftware/contentice/util/URLResolver.java

UrlResolver.getValueForUrl will return a URLData with the information that you require about your URL: https://github.com/joachimhs/Contentice/blob/master/Contentice.api/src/main/java/no/haagensoftware/contentice/data/URLData.java

Once this is setup, I can associate URLs with Netty Handlers:

 this.urlResolver.addUrlPattern("/categories", CategoriesHandler.class);
 this.urlResolver.addUrlPattern("/categories/{category}", CategoryHandler.class);
 this.urlResolver.addUrlPattern("/categories/{category}/subcategories", SubCategoriesHandler.class);
 this.urlResolver.addUrlPattern("/categories/{category}/subcategories/{subcategory}", SubCategoryHandler.class);

Inside my Handlers I can simply get the parameter map:

String category = null;
logger.info("parameterMap: " + getParameterMap());
if (getParameterMap() != null) {
    category = getParameterMap().get("category");
}

I hope that helps :)

Crackleware answered 17/11, 2013 at 9:52 Comment(2)
Your urls are deadInness
I think this is the new location of Joachim's code: github.com/joachimhs/Conticious/blob/master/Conticious.cms/src/…Wrote
B
1

Jersey's UriTemplate mentioned in other answers is good, but it's a big library and it also includes many other dependency libraries.

Tiny solution with no dependency: https://github.com/xitrum-framework/jauter

Benzocaine answered 21/10, 2014 at 22:41 Comment(0)
B
0

I believe first you need to create a framework for storing the REST method and class+method mappings in a property file or in memory data structue. Then write a top level servlet accepting all of your REST request. Depending on the URL starting from your context, you can try to fetch the mapping from your property file/in memory data structure to find out which class and which of its method need to be called. Then making use of reflection you can call the desired method. Take the method response and marshal it into the desired content-type format and send back to the servlet response output stream.

Byandby answered 24/7, 2013 at 17:14 Comment(0)
R
0

Implemented it myself (check the main method for example), just in case if you would want a custom implementation:

import lombok.AllArgsConstructor;
import lombok.NonNull;

import java.util.*;

public class Template {
    final List<TemplateElement> templateElements = new ArrayList<>();

    public static void main(String[] args) {
        final Template template = new Template("/hello/{who}");

        final Map<String, String> attributes = template.parse("/hello/world").get();
        System.out.println(attributes.get("who")); // world
    }

    public Template(@NonNull final String template) {
        validate(template);

        final String[] pathElements = template.split("/");

        for (final String element : pathElements) {
            if (isAttribute(element)) {
                final String elementName = element.substring(1, element.length() - 1); // exclude { and }
                templateElements.add(new TemplateElement(ElementType.ATTRIBUTE, elementName));
            } else {
                templateElements.add(new TemplateElement(ElementType.FIXED, element));
            }
        }
    }

    public Optional<Map<String, String>> parse(@NonNull final String path) {
        validate(path);
        final String[] pathElements = path.split("/");
        if (pathElements.length != templateElements.size()) return Optional.empty();

        final Map<String, String> attributes = new HashMap<>();

        // ignore the 0th element, it'll always be empty
        for (int i = 1; i < templateElements.size(); i++) {
            final String element = pathElements[i];
            final TemplateElement templateElement = templateElements.get(i);

            switch (templateElement.type) {
                case FIXED:
                    if (!element.equals(templateElement.name)) return Optional.empty();
                    break;

                case ATTRIBUTE:
                    attributes.put(templateElement.name, element);
                    break;
            }
        }

        return Optional.of(attributes);
    }

    private void validate(@NonNull final String path) {
        if (!path.startsWith("/"))
            throw new RuntimeException("A template must start with /"); // a template must start with /
    }

    private boolean isAttribute(@NonNull final String str) {
        return str.startsWith("{") && str.endsWith("}");
    }

    @AllArgsConstructor
    class TemplateElement {
        final ElementType type;
        final String name;
    }

    enum ElementType {
        FIXED, ATTRIBUTE
    }
}

Please point out mistakes if any. Thanks.

Rundell answered 16/6, 2019 at 0:58 Comment(3)
path.split("/") is already pointing towards issues: path parameters can have RegEx in order to restrict values. If such a RegEx contains the slash, you lost.Marciemarcile
Hmm interesting, will a slash not be url encoded to %2F in that case (if it's a part of some regex element in the url)?Rundell
No, the RegEx is NOT part of the path itself. Only the VALUES of the path parameters will be URL-encoded. However, the path given in JAX-RS / WADL is not an URL itself.Marciemarcile

© 2022 - 2025 — McMap. All rights reserved.