Mounting a Vert.x sub-router on a path with path parameters - java

I want to create a URL structure for my Vert.x Web powered API that makes it clear how some entities are "contained" inside other entities and how you "traverse the entity path" to find child entities, so I'm thinking of using something like this to address a "grand child" (I don't expect anything deeper than a grand child):
GET /entity/:parent_id/sub-entity/:child_id/subsub-entity/:grandchild_id
So normally my Router configuration will look something like this:
router.get("/entity/:parent_id/sub-entity/:child_id/subsub-entity/:grandchild_id")
.handler(r -> {
HttpServerRequest req = r.request();
Minipart p = Entities.get(req.getParameter("parent_id"))
.getPart(req.getParameter("child_id"))
.getMinipart(req.getParameter("grandchild_id"));
// do something with p
});
When I add a lot of operations (each entity class at each level has catalog and create operations, and each level entity instance has get, update and delete operations, as well as a few other tidbits), my router class gets really large.
I was thinking of using sub-routers to offload the sub-entity management down the line, so the Entities Router configuration might do:
router.mountSubRouter("/entity/:parent_id/sub-entity", PartsRouter.init(vertx));
and then PartsRouter can do:
router.get("/:child_id").handler(r -> {
String parentEntityId = r.request().getParameter("parent_id");
Entity parent = Entities.get(parentEntityId);
String myid = r.request().getParameter("child_id");
Part myself = parent.getPart(myid);
// do something with myself
});
But when I do try that and try to access the sub-router operations, I get a 404 error from Vert.x...
Update:
Apparently Vert.x explicitly does not support this - it threw an exception that my wrapper code just logged and ignored, saying:
java.lang.IllegalArgumentException: Can't use patterns in subrouter mounts
So, is there another way to achieve what I'm trying to do (split a large router configuration into a proper class hierarchy)?

I can image 2 ways of solving your problem:
The first one would be to have a initial handler that processes the common part of the request and calls next so the following one will continue where the first stopped e.g.:
router.route("/entity/:parent_id/sub-entity", ctx -> {
// common part here...
ctx.next();
});
And then:
router.route("/entity/:parent_id/sub-entity/:child_id", ctx -> {
String parentEntityId = r.request().getParameter("parent_id");
Entity parent = Entities.get(parentEntityId);
String myid = r.request().getParameter("child_id");
Part myself = parent.getPart(myid);
// do something with myself
});
Alternatively you can use internal redirects, so you handle the initial code as before but instead of calling next() you redirect to another URL. In that case you should store in the context what you want to reuse since the request will be restarted in the new location.

Related

How can I properly set up this endpoint?

I'm making an URL shortener with the Javalin framework and have this endpoint set up:
app.routes(()->{
path("",()->{
get("/:id", ctx->{
//do stuff
ctx.redirect("somewhere.com");
});
});
});
Problem is when I need to serve a javascript file to load into my html files. It tries to load from http://localhost:7000/qrcode.min.js but ends up going to the endpoint mentioned above. From what I read in the documentation this is normal behaviour, Javalin first runs the endpoint handler and then (if it doesn't find an endpoint) runs the file handler.
So how can I fix this? should I define a GET request at "/qrcode.min.js"?, I dont think the javalin context handler has a function that lets me return a .js file.
As Matt already suggested in a comment, it would be way cleaner if you'd prefix either path. That way, you could have /r/:id (or /u/:id with "u" for "URL") and the static files would not get in your way, or you could prefix your static files with e.g. /static/, or even just /s/ for brevity, and your shortened URLs would not get in your way.
If you, however, prefer to stick with your current scheme, you can simply filter out JavaScript files (or any other non-id request) in the handler and instead provide the file (however, if you previously had auto-generated ETags, you'd lose caching if you don't want to handle that yourself).
The latter solution would look like so:
app.routes (() -> {
path ("", () -> {
get ("/:id", ctx -> {
String id = ctx.pathParam ("id");
if (id.endsWith (".js")) {
String resourcePath = "your/classpath/resources/folder/" + id;
try {
InputStream resultStream = Thread.currentThread ()
.getContextClassLoader ()
.getResourceAsStream (resourcePath);
if (resultStream == null)
throw new NullPointerException ("Script not found");
ctx.contentType ("application/javascript");
ctx.result (resultStream);
} catch (NullPointerException e) { // script does not exist
e.printStackTrace (); // for development only!
ctx.status (404);
}
return;
}
// do stuff
ctx.redirect ("somewhere.com");
});
});
});
Depending on your preference, you can also handle the resultStream == null case where my code is currently throwing an NPE to be caught by the outer try/catch and omit the try/catch completely.
Setting the Content-Type is essential so that the browser knows that you're actually responding with JavaScript code. Also, I'm typically using Thread.currentThread ().getContextClassLoader () because we'd want the resource to be resolved based upon the current HTTP handler thread, which could, in theory, have a different class path/class loader than the class we're currently in.
Please note that, as stated above, this will not support client-side caching as the handler simply ignores all ETag headers sent with the request* and instead respond with the complete file which, with many requests in a short amount of time and large scripts, will certainly put way more stress on your disks and CPUs.
Thus, I'd actually recommend to prefix the static files route and let Javalin/Jetty handle all the caching and files magic.
* Actually, the header sent by the client is If-None-Match most of the time. The server would respond with an ETag to allow for caching in the browser.

Routing template format for undertow

Is there any documentation about routing template format for undertow. I want to setup handlers like this:
/ or /index.html -> Use handler 1
Anything else -> Use handler 2
I tried this one, bu did not work:
Handlers.routing()
.add("GET", "/", handler1)
.add("GET", "/index.html", handler1)
.add("GET", "/*", handler2)
Any idea?
There are a couple of ways to achieve this:
1) Basic approach: PathHandler
Handlers.path()
.addExactPath("/path1", handler1)
.addPrefixPath("/path2", handler2);
The handler1 will match only on /path1 (or /path1/).
The handler2 will match on /path2, /path2/ and everything else that starts with /path2/.
2) Route approach: RoutingHandler
If you use a RoutingHandler, you have the option to easily extract variables from the paths. That's convenient for building REST APIs for example (note the usage of the convenience get method on the RoutingHandler).
Handlers.routing().get("/{test}/*", exchange -> {
PathTemplateMatch pathMatch = exchange.getAttachment(PathTemplateMatch.ATTACHMENT_KEY);
String itemId1 = pathMatch.getParameters().get("test"); // or exchange.getQueryParameters().get("test")
String itemId2 = pathMatch.getParameters().get("*"); // or exchange.getQueryParameters().get("*")
}))
The * parameter can match anything (like a path for instance a/b/c).
In order to use the * parameter, you need an actual named parameter defined before in the route template (test in my example).
Note that the parameters defined in your route template will be available together with the query parameters (exchange.getQueryParameters()). This is default behavior. If you do not want it, you can create your routing handler like this: Handlers.routing(false).get(...) and then retrieve the parameters from the exchange's attachments.
For any route that is not matched by your routing handler, you can use the fallbackHandler available in the RoutingHandler.
Handlers.routing()
.get("/", handler1)
.get("/index.html", handler1)
.setFallbackHandler(handler2);
By default the fallbackHandler simply returns an empty response body with a 404 status code. The handler2 will be matching any other requests, not only GET requests.
Comprehensive Example
You can of course combine PathHandler and RoutingHandler to fit your needs.
Here is a small example of a more realistic setup:
Undertow.builder().addHttpListener(8080, "0.0.0.0")
.setHandler(Handlers.path()
// REST API path
.addPrefixPath("/api", Handlers.routing()
.get("/customers", exchange -> {...})
.delete("/customers/{customerId}", exchange -> {...})
.setFallbackHandler(exchange -> {...}))
// Redirect root path to /static to serve the index.html by default
.addExactPath("/", Handlers.redirect("/static"))
// Serve all static files from a folder
.addPrefixPath("/static", new ResourceHandler(
new PathResourceManager(Paths.get("/path/to/www/"), 100))
.setWelcomeFiles("index.html"))
).build().start();
This application also serves static files from your file system. This is handy to serve a javascript application or static html files for instance.

How to get all OpenText Content Server categories in one call?

I'm currently on a project that involves OpenText Content Server 10.5 SP1 Update 2015-03.
I'm trying to find out if is possible to get all categories from the System Category Volume with one call using Java SOAP web services or REST.
On the web services side I found a couple of methods exposed by the DocumentManagement WSDL GetCategoryDefinition and GetCategoryDefinitions which require categoryIDs as argument.
On the REST side I managed to obtain access to categories but after a quite long trip:
call to otcs/cs.exe?func=search.GetCategoryVolume gives as a response an URL for the subsequent call
call to otcs/cs.exe?func=ll&ObjID=2005&objAction=XMLExport&scope=1 gives the id of the system category volume along with category IDs
call to otcs/cs.exe?func=ll&ObjID=21361&objAction=XMLExport&scope=1 gives the required info about the category.
I would like to have a single call returning all information about categories I need.
Is it possible to achieve that?
It's possible.
What you need to do:
1.) Find all IDs of the Categories, you want the definitions for
2.) call DocumentManagementWS.getCategoryDefinitions(IDs)
example
In my project we store all Categories in Folders, and not in the CategoryVolume of Content server.
// INFO: variable dm is an instance of the documentManagement-Webservice
// 1.) read the folder of the Categories
Node categoryRoot = dm.getNodeByPath(configRoot.getID(), Arrays.asList("Categories"));
// 2.) find all Ids of the categories
List<Node> categories = dm.listNodes(categoryRoot.getID(), false);
if (categories != null) {
for (Node category : categories) {
if (category.getType().equals("Category")) {
categoryIds.add(category.getID());
}
}
}
// 3.) Read all defintitions of the categories
List<AttributeGroupDefinition> categoryDefinitions = dm.getCategoryDefinitions(categoryIds);
Maybe not exactly program oriented but you know about the handler "cs.exe?func=attributes.dump" ? This is the UI version of what you are asking.

JavaScript scope resolve time

I'm writing an app that loads javascript dynamically using rhino(or a browser); I got 2 files:
// in a file called fooDefinition.js
var definition = {
foo: function(data){return bar(data)},
loadFile: "barLib.js"
}
now, bar() is defined like this:
// in a file called barLib.js
function bar(data){
return data + " -> bar!";
}
This is what I want to do:
load fooDefinition.js into the environment
read the value of loadFile (in this case: "barLib.js") and load the file (NOTE: load the file through external mechanism, not through javascript itself!)
call foo
external mechanism & example usage (Java pseudo code):
// assume engine is a statefull engine (rhino for example)
String str = /*content of fooDefinition.js*/;
engine.eval(str);
String fileToLoad = engine.eval("definition.loadFile");
engine.load(IOUtils.readFileToString(new File(fileToLoad)));
String result = engine.eval("definition.foo('myData')");
I've tried this in Google Chrome's JS console and no error was thrown
I wonder is this the correct way of accomplish such task?
TL;DR:
Are the attributes of an object loaded and checked when the object is defined?
If your engine is statefull that is it keeps track of defined variables, yes your approach is corrent and will work as expected
But if it is not, your way will fail, because when you call the following
String fileToLoad = engine.eval("definition.loadFile");
your engine haven't any info about definition object and as a result it return an exception (in JavaScript).
It seems your engine is statefull and all things will work correctly

Performing a distributed search inside a Solr component prepare method

I'm writing a custom Solr component. In the component's prepare method I'm executing a query given as a custom parameter (inside the req.params). I'm not running the q parameter query in the prepare method, but another input query defined in a custom parameter. I'm using the documents returned by that custom input query to do some preparations in the prepare method.
The problem is that since my index is distributed into several shards, the documents returned by the custom query are only the ones residing on one of the shards. In other words, the search performed in my prepare method is not distributed, and I'm getting partial results. This is more or less how I perform the search in my prepare method:
rb.req.getSearcher().getDocList(customQuery, null, null, offset, len, 0);
Is there a way to make a distributed search in the prepare method and get the matched documents from all the shards?
EDIT:
My current solution is to execute a query using Solrj roughly as follows:
SolrServer server = new HttpSolrServer(url);
SolrQuery request = new SolrQuery(customQuery);
NamedList queryResponse = server.query(request).getResponse();
Then I parse the response to get the content of the returned documents. I don't like my solution for several reasons. One of the reasons is that I have to parse the response. But the main reason is that I have to pass the Solr server url as a parameter. I put the url in the solrconfig.xml file. Is it possible to somehow construct a SolrServer instance without explicitly stating the Solr server url (perhaps through ZooKeeper)?
The Easy Way
Use CloudSolrServer to execute the distributed query. Feed it the Zookeeper url and the collection name (which are available in the response builder):
CoreDescriptor coreDescriptor = rb.req.getCore().getCoreDescriptor();
String collectionName = coreDescriptor.getCloudDescriptor().getCollectionName();
ZkController zkController = coreDescriptor.getCoreContainer().getZkController();
String zookeeperUrl = zkController.getZkServerAddress();
CloudSolrServer server = new CloudSolrServer(zookeeperUrl);
server.setDefaultCollection(collectionName);
server.connect();
SolrRequest request = ... //initialize the solr request to execute the query
NamedList<Object> solrResponse = server.request(solrRequest);
// do whatever you like with the returned response;
server.shutdown();
The Right Way
Do not perform a distributed search inside the prepare method. Don't query the index in the prepare method. What you have to do is first decide at which stage of the execution you want your distributed query to be executed. The stages are STAGE_START, STAGE_PARSE_QUERY, STAGE_TOP_GROUPS, STAGE_EXECUTE_QUERY, STAGE_GET_FIELDS and STAGE_DONE. If you need it to be executed between two of the stages, then create a new intermediate stage (such as EXECUTE_PREPARING_QUERY).
Override the distributedProcess method and implement it in such a way that if the current stage is your stage then set the right parameters for the shard request:
#Override public int distributedProcess(ResponseBuilder rb) {
...
if (rb.stage == MY_STAGE) {
ShardRequest sreq = new ShardRequest();
sreq.purpose = ShardRequest.PURPOSE_PRIVATE;
sreq.params = new ModifiableSolrParams();
// set the parameters for the shard request
rb.addRequest(this, sreq);
}
...
}
Now each shard is going to execute the request defined by the params you've set on its own core. That's going to happen on the stage MY_STAGE. You still have to handle the responses of the shards, combine them and use them. The right place to handle all those responses is inside the handleResponses method of the component. So override handleResponses and do whatever you need to do with the shard responses if you're in the right stage. You probably need to save them somewhere so you can reference them later in the finishStage method.
#Override public void handleResponses(ResponseBuilder rb, ShardRequest sreq) {
...
if (stage == MY_STAGE) {
List<ShardResponse> responses = sreq.responses;
for (ShardResponse response : responses) {
//do something with the response, maybe save it somewhere
rb.finished.remove(sreq);
}
}
...
}
Now you have to override the finishStage method and do whatever you need to do with the combined results.
#Override public void finishStage(ResponseBuilder rb) {
...
if (rb.stage == MY_STAGE) {
// do whatever you need to do with the results
}
...
}
The important message is to use the response builder stages to control the execution flow of the component with relation to the other components. You don't have to put the code in the prepare method if you want it to be executed before the execution of the actual query. You just have to create or use a stage that's intermediate to STAGE_START and STAGE_EXECUTE_QUERY.

Categories

Resources