Java - Better way to parse a RESTful resource URL - java

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?

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);
}

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 :)

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.

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

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.

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.

Related

Can I get the Field value in String into custom TokenFilter in Apache Solr?

I need to write a custom LemmaTokenFilter, which replaces and indexes the words with their lemmatized(base) form. The problem is, that I get the base forms from an external API, meaning I need to call the API, send my text, parse the response and send it as a Map<String, String> to my LemmaTokenFilter. The map contains pairs of <originalWord, baseFormOfWord>. However, I cannot figure out how can I access the full value of the text field, which is being proccessed by the TokenFilters.
One idea is to go through the tokenStream one by one when the LemmaTokenFilter is being created by the LemmaTokenFilterFactory, however I would need to watch out to not edit anything in the tokenStream, somehow reset the current token(since I would need to call the .increment() method on it to get all the tokens), but most importantly this seems unnecessary, since the field value is already there somewhere and I don't want to spend time trying to put it together again from the tokens. This implementation would probably be too slow.
Another idea would be to just process every token separately, however calling an external API with only one word and then parsing the response is definitely too inefficient.
I have found something on using the ResourceLoaderAware interface, however I don't really understand how could I use this to my advantage. I could probably save the map in a text file before every indexing, but writing to a file, opening it and reading from it before every document indexing seems too slow as well.
So the best way would be to just pass the value of the field as a String to the constructor of LemmaTokenFilter, however I don't know how to access it from the create() method of the LemmaTokenFilterFactory.
I could not find any help googling it, so any ideas are welcome.
Here's what I have so far:
public final class LemmaTokenFilter extends TokenFilter {
private final CharTermAttribute termAtt = addAttribute(CharTermAttribute.class);
private Map<String, String> lemmaMap;
protected LemmaTokenFilter(TokenStream input, Map<String, String> lemmaMap) {
super(input);
this.lemmaMap = lemmaMap;
}
#Override
public boolean incrementToken() throws IOException {
if (input.incrementToken()) {
String term = termAtt.toString();
String lemma;
if ((lemma = lemmaMap.get(term)) != null) {
termAtt.setEmpty();
termAtt.copyBuffer(lemma.toCharArray(), 0, lemma.length());
}
return true;
} else {
return false;
}
}
}
public class LemmaTokenFilterFactory extends TokenFilterFactory implements ResourceLoaderAware {
public LemmaTokenFilterFactory(Map<String, String> args) {
super(args);
if (!args.isEmpty()) {
throw new IllegalArgumentException("Unknown parameters: " + args);
}
}
#Override
public TokenStream create(TokenStream input) {
return new LemmaTokenFilter(input, getLemmaMap(getFieldValue(input)));
}
private String getFieldValue(TokenStream input) {
//TODO: how?
return "Šach je desková hra pro dva hráče, v dnešní soutěžní podobě zároveň považovaná i za odvětví sportu.";
}
private Map<String, String> getLemmaMap(String data) {
return UdPipeService.getLemma(data);
}
#Override
public void inform(ResourceLoader loader) throws IOException {
}
}
1. API based approach:
You can create an Analysis Chain with the Custom lemmatizer on top. To design this lemmatizer, I guess you can look at the implementation of the Keyword Tokenizer;
Such that you can read everything whatever is there inside the input and then call your API;
Replace all your tokens from the API response in the input text;
After that in Analysis Chain, use standard or white space tokenizer to tokenized your data.
2. File-Based Approach
It will follow all the same steps, except calling the API it can use the hashmap, from the files mentioned while defining the TokenStream
Now coming to the ResourceLoaderAware:
It is required when you need to indicate your Tokenstream that resource has changed it has inform method which takes care of that. For reference, you can look into StemmerOverrideFilter
Keyword Tokenizer: Emits the entire input as a single token.
So I think I found the answer, or actually two answers.
One would be to write my client application in a way, that incoming requests are first processed - the field value is sent to the external API and the response is stored into some global variable, which can then be accessed from the custom TokenFilters.
Another one would be to use custom UpdateRequestProcessors, which allow us to modify the content of the incoming document, calling the external API and again saving the response so it's somehow globally accessible from custom TokenFilters. Here Erik Hatcher talks about the use of the ScriptUpdateProcessor, which I believe can be used in my case too.
Hope this helps to anyone stumbling upon a similar problem, because I had a hard time looking for a solution to this(could not find any similar threads on SO)

How to pass non-primitive Object from Java to JS by Android addJavascriptInterface?

I've had a trouble on this topic (for days).
For instance, basically, I want to pass a Java WebSocketClient object to Android Webview JS on demand, so wrote a code referring to:
http://foretribe.blogspot.com.au/2013/08/how-to-make-android-webview-support.html
https://github.com/thinksource/vw_websocket/blob/master/src/com/strumsoft/websocket/phonegap/WebSocketFactory.java
JAVA
wv.addJavascriptInterface(new WebSocketFactory(), "factoryJ");
public class WebSocketFactory {
//.............
public WebSocket getInstance(String url) {
socket = new WebSocket(new URI(url));
return socket;
}
}
JS
var url = "ws:someaddress";
var ws = factoryJ.getInstance(url);
console.log(ws.toString()) // object pointer displayed
console.log(ws.getReadyState()); //Uncaught Error: Error calling method on NPObject!
Uncaught Error: Error calling method on NPObject!
This concept does not work at least for me avove Android4.2+.,
because addJavascriptInterface() only works with Java primitive types and Strings.
cf)
Passing a JavaScript object using addJavascriptInterface() on Android
Error calling method on NPObject! in Android 2.2
As far as I know, the only way to pass JAVA object to JS is :
wv.addJavascriptInterface(JavaObject, "JsObject");
Sure, this should work fine as long as a passing JavaObject is pre-determined, but since WebSocket Object(s) is on-demand, I need to hack somehow for this.
So, I prepare JavaObject as some Array of WebSocket .
WebSocketNew[] ws = new WebSocketNew[99999];
wv.addJavascriptInterface(ws, "wsJ");
Unfortunately, the JS treats wsJ[n] as undefined ; appears it's also not allowed to pass ArrayObject.
I've read JSON or JSON array of Java can be passed, but it's string after all and cannot be this solution, am I correct?
Probably, back in old days, Android Java-JS interaction is implemented more freely, but under security issues, they restrict more (the annotation #JavascriptInterface is the one, and not only this but also other factors ) Android 4.2.1, WebView and javascript interface breaks
How to pass non-primitive Object from Java to JS by Android addJavascriptInterface?
Any thought?
Thanks.
You are correct that Javascript Interface methods can only return strings and primitive types. If you need to return a more complex object, you can try serializing the object to JSON. But, that only works for model-level objects. If you need to return a class which contains functionality, the only way I know of to do this is to wrap the class, and expose each of its methods as a #JavascriptInterface. For instance:
class Foo {
private WrappedClass myWrappedClass;
public Foo(WrappedClass classToWrap) {
myWrappedClass = classToWrap;
}
#JavascriptInterface
public String doSomething1() {
return myWrappedClass.doSomething1();
}
#JavascriptInterface
public int doSomething2() {
return myWrappedClass.doSomething2();
}
// Etc.
}
This is pretty tedious.
But, it looks like you're trying to use websockets. Have you considered Socket.io? It would allow you to use web sockets in Javascript, rather than relying on a JavascriptInterface.
http://socket.io
This has been one of the most hectic hack for me.
The major problem occurs from Android UI-thread and non-UI-thread bind with addJavascriptInterface
Firstly, my mention:
As far as I know, the only way to pass JAVA object to JS is :
wv.addJavascriptInterface(JavaObject, "JsObject");
is wrong.
In fact, I could pass WebSocketNew= my custom Java object properly to JS as return value of getInstance.
However, the object intended to pass/return to JS must be in scope of Android UI-thread.
If we just do return new WebSocketNew(new URI(url))), it's not found by JS, probably because JS runs in UI-thread, and the origin runs on non-UI-thread of addJavascriptInterface.
As I mentioned earlier, since WebScoket instance is on-demand, so I decided to create Object pool in UI-thread first.
This can be done with HashMap with the requested url String.
Java
final HashMap<String, WebSocketNew> ws = new HashMap<>();
//runs on non-UI-thread
class WebSocketFactory
{
public WebSocketFactory()
{
}
#JavascriptInterface
public WebSocketNew getInstance(String url)
{
System.out.println("============WebSocketFactory============getInstance " + url);
try
{
ws.put(url, new WebSocketNew(new URI(url)));
ws.get(url).connect();
System.out.println("=====WebSocketNew=====" + url + " " + ws.get(url).getReadyState());
return ws.get(url);
}
catch (Exception e)
{
System.out.println("==========ERROR");
return null;
}
}
}
wv.addJavascriptInterface(new WebSocketFactory(), "factoryJ");
wv.loadUrl("file:///android_asset/Content/app.html");
JS
window.WebSocket = function(url)
{
console.log('####################creating new WebScoketInstance JS ' + url);
var p = {
url: null
};
p.url = url;
var ws = factoryJ.getInstance(p.url);
var obj = {
send: function(data)
{
console.log('--- send: function(data)----- ws.send1(data);------');
ws.send1(data);
},
//.......
//.......
}
return obj;
};
The JS code is related to topic : JavaScript Event implementation to Closure based Object

Play Framework 2 + java does not return URL dynamic part in getQueryString call

This may be a duplicate question but I was not able to find a solution. As a result, I'm posting my own one.
My URL looks like this "/customer/www.bakeryx.com" where www.bakeryx.com is the URL dynamic part and maps to "/customer/:domain".
I was hoping that when I call ctx.request().getQueryString("domain") I would get the www.bakeryxcom. Otherwise, I get a null response and there is no way to get this value from the action.
Please find bellow my work around for this task. I had to get the ROUTE_PATTERN from the context args.
public class DomainVerifierAction extends Action<DomainVerifierFilter> {
#Override
public Result call(Http.Context ctx) throws Throwable {
//how to get the domain here??
//work around is to get the route_pattern
String routePatternPlay = (String) ctx.args.get("ROUTE_PATTERN");
String path = ctx.request().path();
//added logic to extract domain from the PATH using ROUTE_PATTERN.
}
}
Question: Is there any solution for this problem?
I think the problem you are having is that the getQueryString method you are using is looking for the "?" operator in the URL, as in a traditional GET request (e.g. ?id=1). Instead, try passing the domain as a parameter in the controller method. For example:
In your routes file:
GET /customer/:domain controllers.Application.function(domain: String)
Then in your Play controller (assuming Play Framework 2.x):
public static Result function(String domain){
//Do something with the passed domain string here
return ok(...);
}

can jQuery serialize() use other separator than ampersand?

I am looking for some nice solution. I've got a couple of textfields on my page and I am sending these via Ajax using jQuery serialize method. This serialized string is parsed in my java method to hashmap with key = 'nameOfTextfield' nad value = 'valueInTextfield'
For example, I've got this String stdSel=value1&stdNamText=value2&stdRevText=value3 and everything works fine.
String[] sForm = serializedForm.split("&");
Map<String, String> fForm = new HashMap<String, String>();
for (String part : sForm) {
String key = null;
String value = null;
try {
key = part.split("=")[0];
value = part.split("=",2)[1];
fForm.put(key, value);
//if textfield is empty
} catch(IndexOutOfBoundsException e) {
fForm.put(key, "");
}
}
But this method will break down when ampersand in some textfield appears, for example this stdSel=value1&stdNamText=value2&stdRevText=val&&ue3. My thought was that I'll replace ampersand as separator in searialized string for some other character or maybe more characters. Is it possible and good idea or is there any better way?
Regards
Ondrej
Ampersands are escaped by the serialize function, so they don't break the URL.
What you need to unescape a field you got from an URL is
value = URLDecoder.decode(value,"UTF-8");
But, as was pointed by... Pointy, if you're using a web framework and not using only vanilla java.net java you probably don't have to do this.

Optional path variables in Spring-MVC RequestMapping URITemplate

I have the following mapping:
#RequestMapping(value = "/{first}/**/{last}", method = RequestMethod.GET)
public String test(#PathVariable("first") String first, #PathVariable("last")
String last) {}
Which for the following URIs:
foo/a/b/c/d/e/f/g/h/bar
foo/a/bar
foo/bar
maps foo to first and bar to last and works fine.
What I would like is something that maps everything between foo and bar into a single path param, or null if there is no middle (as in the last URI example):
#RequestMapping(value = "/{first}/{middle:[some regex here?]}/{last}",
method = RequestMethod.GET)
public String test(#PathVariable("first") String first, #PathVariable("middle")
String middle, #PathVariable("last") String last) {}
Pretty stuck on the regex since I was hoping that something simple like {middle:.*}, which only maps to /foo/a/bar, or {middle:(.*/)*}, which seems to map to nothing.
Does the AntPathStringMatcher tokenize upon "/" prior to applying regex patterns? (making patterns that cross a / impossible) or is there a solution?
FYI this is in Spring 3.1M2
This seem similar to #RequestMapping controllers and dynamic URLs but I didn't see a solution there.
In my project, I use inner variable in the springframework:
#RequestMapping(value = { "/trip/", // /trip/
"/trip/{tab:doa|poa}/",// /trip/doa/,/trip/poa/
"/trip/page{page:\\d+}/",// /trip/page1/
"/trip/{tab:doa|poa}/page{page:\\d+}/",// /trip/doa/page1/,/trip/poa/page1/
"/trip/{tab:trip|doa|poa}-place-{location}/",// /trip/trip-place-beijing/,/trip/doa-place-shanghai/,/trip/poa-place-newyork/,
"/trip/{tab:trip|doa|poa}-place-{location}/page{page:\\d+}/"// /trip/trip-place-beijing/page1/
}, method = RequestMethod.GET)
public String tripPark(Model model, HttpServletRequest request) throws Exception {
int page = 1;
String location = "";
String tab = "trip";
//
Map pathVariables = (Map) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
if (pathVariables != null) {
if (pathVariables.containsKey("page")) {
page = NumberUtils.toInt("" + pathVariables.get("page"), page);
}
if (pathVariables.containsKey("tab")) {
tab = "" + pathVariables.get("tab");
}
if (pathVariables.containsKey("location")) {
location = "" + pathVariables.get("location");
}
}
page = Math.max(1, Math.min(50, page));
final int pagesize = "poa".equals(tab) ? 40 : 30;
return _processTripPark(location, tab, pagesize, page, model, request);
}
See HandlerMapping.html#URI_TEMPLATE_VARIABLES_ATTRIBUTE
Can't be done as far as I know. Just as you stated, the regular expression is being applied to the path element after splitting up the path at each slash, so the regular expression can never match a '/'.
You could manually inspect the url and parse it yourself from the request object.
It could be done by writing a custom path matcher and configuring Spring to use it. For example, such a solution is documented here: http://java.dzone.com/articles/spring-3-webmvc-optional-path
The link provides a custom path matcher and shows how to configure spring to use it. That should solve your need if you don't mind writing a custom component.
Also, this is a duplicate of With Spring 3.0, can I make an optional path variable?
try to use this
#RequestMapping(value = {"some mapped address","some mapped address with path variable","some mapped address with another path variable"})
array list of available url for specific method
But be careful on creating list of url when you are using #PathVariable in your method signature it cant be null.
hope this help

Categories

Resources