cincheo / jsweet

A Java to JavaScript transpiler.
http://www.jsweet.org
Other
1.46k stars 160 forks source link

JaxRS client #101

Closed gege-fr closed 8 years ago

gege-fr commented 8 years ago

Hi, i just discovered jsweet and it seems promising. I have a question : is there a way to consume JaxRS services ?

Since one point of having java to code the web page is to also have java server-side. I don't know if i'm alone but I think it's worth thinking about.

Maybe by generating jsweet wrapper over a maven-generated js or ts client ? Or re-using the pojos and parsing the jaxrs service definition and pojos ? Or generating a client from wadl ?

Well, I wanted to raise this topic.

renaudpawlak commented 8 years ago

This is a typical JSweet use case. We have been developing a real-world application with JSweet and Jackson and it works really well. It requires some configuration though. One of our plan is to set up template projects on Github so that you can have some example configurations to start with.

gege-fr commented 8 years ago

Yes, that would be great,when I'll have time to try on jsweet it'll be one of my first concerns :-) I'm curious to see that example.

I'm kind of a javascript-incompatible-person, I hope I'll finally be able to do decent work on browsers. I was putting up with gwt and having hopes for j2cl + jsinteroped-definitelytyped... until I found JSweet :-)

renaudpawlak commented 8 years ago

This is some kind of secret, but I am working on forking GWT's JRE runtime so that JSweet can be as good in Java APIs as it is already in JS APIs... As a javascript-incompatible-person (like me), you should be happy about it. I am frenetic on this topic :D

pedjapesic commented 8 years ago

This would be realy great ! Do you have any idea when we will be able to try this out? This would make me very happy as well :-)

gege-fr commented 8 years ago

You bet I am ! That would be the best of both worlds. Jdk plus all modern and trendy js libs :-)

renaudpawlak commented 8 years ago

I am aiming at having a first available JRE emulation working with JSweet in a couple weeks. This will largely depend on the time I have and whether I hit difficult/fundamental issues or not. Could be sooner, could be later depending on that. So far so good though.

I am planning also to change the release number to JSweet 2.0 because it will really bring JSweet at another level of possibilities. So release 1.1.0 would become 2.0.0. We could do a 2.0.0-RC1 to make sure that everything is right before advertising.

gege-fr commented 8 years ago

To go back on the original topic; do you have an exemple on how you'd recommend to do it (consume a JAX-RS service) or at least some pointers ?

Thanks, and sorry to insist, but that's because I'm really interested in that project, i'd like to make an intern try it for me ;-)

renaudpawlak commented 8 years ago

In that IONIC client project, we use a JSweet Server class to access the server... This could be a start for you. We will provide more "raw" examples when we find the time.

renaudpawlak commented 8 years ago

Note that this Server class uses the ES6 promise API... it is not mandatory to use promises, but we found it convenient here.

gege-fr commented 8 years ago

OK I kind of see. I confess I was hoping something involving a lot less coding. Do you think it would be possible to automatically generate a candy from the jaxrs definition or WADL ?

I'm thinking that wrapping a typescript client (the same way you already do it with definitely-typed definitions) would be the natural way for JSweet. There are probably other (better) tools out there, but if I'm not wrong at least Swagger can generate a TypeScript client from JAXRS java definitions.

This would either generate code in the generate-sources phase, or, more probably, would require to have another module (or project) in the maven project (that module's artifact would be our candy, a client for our jax-rs service).

You would have to publish a template POM chaining the necessary steps. That POM would be the same for all kinds of services. I'm aware that that would duplicate the POJOs definitions, but since they would be automatically generated, I think that's OK.

Well that's only my external and naive opinion, it's easier said than done.

gege-fr commented 8 years ago

But it implies that you would make your candies generator available, and i remember reading that it's not ready yet. Well, maybe that, for now, "only" handwriting the jsweet wrapper for the ts client would be doable ?

renaudpawlak commented 8 years ago

Umm... I think that a good option for you would be to use https://github.com/raphaeljolivet/java2typescript. This project generates the client stub in JavaScript and the d.ts. The cool thing is that you can then quite easily package a JSweet candy with the Java + the TypeScript definitions + the JavaScript... and use it all from JSweet then. Still, we probably really would have to provide some Maven/Gradle project templates to make it easy for you to use... Unfortunately, we have other priorities these days... but this is important... so at some point we will focus on it.

gege-fr commented 8 years ago

Yes, i understand. I also saw that lib, but it didn't didn't seem very active nor mature so i didn't cite it :

renaudpawlak commented 8 years ago

Ok. I think I see what you mean now. Basically, once you have found a way to generate a TypeScript client stub lib with any tool (it looks like Swagger can do it indeed), on the paper it would be quite easy to translate the stub lib to JSweet and package it as a candy. Sounds great on paper, but we have not tried it yet.

You are right, it may require the TS -> Java API translator to be used in the process to make the stubs available in Java. Right now, the online available version (http://www.jsweet.org/online-typescript-to-java-api-translator/) is still beta. However, there might be a work around here... you could use a regular Java-generated stub to compile your JSweet program (as long as it provides the exact same API as the TS one, which is possible). (remember that the Java stubs will be used only for compiling because they will not be run in Java)

So, to sum up, you need:

Side note: when the Java JRE emulation will be available, there is a possibility that the Java-generated stubs work with it. So it would not even require the use of a TS JaxRS stub generator.

In any case, this scenario still requires a little bit of work/testing to be production-ready. Right now, we are not using at all such sophisticated tooling. We are performing the server (Jackson) calls through a simple JS AJAX/JSON API by sharing the DTO in Java between the client and the server. It fully does the job for us because it enables reference tracking and refactoring of DTO between client and the server (this is important to us). So I am sorry if I made you think that we were doing automatic stub generation here.

Yet, it is something that is definitely worth exploring and that could be a great use case for JSweet. In any case, I think that it is important that we first step through a better Java basic APIs support and that we provide clear examples on how to share DTOs through a standard AJAX API.

gege-fr commented 8 years ago

You went further than me, i initially forgot that js whas needed for the candy to work ;-)

I'm skeptic about running a java stub with gwt sdk, at least for the network layer.

If we're talking about re-using gwt stuff, maybe there is something do-able with restygwt ? We've been using it with success.

renaudpawlak commented 8 years ago

Well, taking advantage of GWT JRE emulation was just a side note. I think that the best approach is add the JSweet layer to a TypeScript lib. RestyGWT seems interesting though. Thanks for the pointer.

lgrignon commented 8 years ago

Hello guys, check out those two brand new examples for JAX-RS + Knockout: https://github.com/lgrignon/ionic-exercise-server

and a sample client using Ionic: https://github.com/lgrignon/ionic-exercise

Some details could not be working perfectly since it's only an example, please tell me if you have any question / suggestion

gege-fr commented 8 years ago

To continue onto the automatic stub generation path.

I've been playing a bit with maven and swagger this afternoon; I've managed, using official swagger maven tasks to generate a .yaml (or json) interface file, then use it with swagger codegen maven plugin.

I think t's better to rely on know and widely used tools.

The annoying point beeing that , to my (thin) knowledge, swagger tools only allow a runtime .yaml specs generation

1/ make a swagger-friendly jaxrs service (@Api annotation, jaxrs resources and bean config)

2/ create another maven project 2.1 / get the war 2.2 / deploy it in a jetty instance started by maven (they got a maven plugin for it) 2.3 / get the yaml (yay) 2.4 / stop jetty 2.5 / use yaml to produce a javascript / typescript-node / typescript-angular ..... client

That's the step I'm at. After that... What would be the most friendly output for a jsweet candy ? I'm thinking about compiling the ts to js. But it's a one file TS, not interface + code, I'm afraid I'll get a one-file js, not the best to plug typescript onto it.

Or would it be easier (or wiser) to have a swagger codegen for jsweet (trim down java codegen to jsweet's supported sdk) ? That would allow to interface easily with a lot of services. Since it looks like swagger's spec format could become a reference ? http://swagger.io/introducing-the-open-api-initiative/

gege-fr commented 8 years ago

It looks like the codegen relies on mustache templates, i don't know if it would be hard to make a JSweet template from the java one.

https://github.com/swagger-api/swagger-codegen/tree/master/modules/swagger-codegen/src/main/resources/Java

renaudpawlak commented 8 years ago

At first sight it looks quite cool. I don't understand why they don't have a pure TypeScript output (like the JavaScript one). Anyways, I bet that the best way to proceed is to take a TypeScript-based output (such as TypeScript-angular) and translate it to JSweet. Porting TypeScript code is usually very easy and the Angular candies are available in JSweet... (NOTE: it is probably not a good idea to start from the Java template because of all the Java API that are not available to JSweet yet)...

renaudpawlak commented 8 years ago

I don't really understand how the TypeScript angular version works though... I wish I had more time to play with it...

gege-fr commented 8 years ago

My typescript skills are almost non-existant, but at least I won't lose time trying to modify the Java template since you said that porting from typescript is easyer. I'll try to at least understand the differences and goals of each typescript.

gege-fr commented 8 years ago

Sorry, missclicked ...

gege-fr commented 8 years ago

On a general note, it seems that swagger codegen "flattens" all the resources into one client api (DefaultApi). The colliding methods are appended with a suffix. I guess a good practice would be to prefix the JaxRS method with the resource name, in the java-server code, so that the generated API is more readable.

typescript-node relies on nodejs' request() method so i guess it's a nogo.

typescript-angular seems pretty straight-forward to me, it uses angular's $http() to send requests and promises to handle responses asynchronously. Each POJO is in a dedicated ts file, and api.d.ts binds them all ?

model.mustache => used one time per exchanged messages (generates POJOs definitions)

api.mustache => The client api used to send and receive the messages. The template has a section for the constructor, and then a section called for each method.

api.d.mustache => file centralizing all the necessary references probably for the typescript compiler

As I said, it seems indeed pretty straighforward. If it was possible to translate it into JSweet then that would be perfect : use the stub generated in target/generated-sources add the angular's candy, let jsweet compile it ... then rock & roll :-)

There is not so much typescript code in the templates, no more than an 100 lines, so seems very doable from an external point of view !

renaudpawlak commented 8 years ago

Yes... sounds really promising to me. If you want to give it a try, I could definitely help. I wonder if you could contribute a JSweet-angular target to swagger ;)

gege-fr commented 8 years ago

I'll try to give it a try (...) ;-) That would be ideal; problem is getting the free time for that and that i'm totally new to jsweet so i'll probably need your help to iron some details.

renaudpawlak commented 8 years ago

I can help really easily if you tell me what TypeScript code to translate to JSweet. For more complicated issues (like build issues), the best is probably to set up a fork of the swagger's TypeScript-angular target and I can PR to it. I have little time, but hopefully enough since the project looks well scoped...

gege-fr commented 8 years ago

I'm gonna try a bit before forking anything. There's more to it than just mustache, there's a java part for mapping types etc ... I'll keep you updated

gege-fr commented 8 years ago

I'm slowly advancing, i have a question : where is angular's $http among jsweet candies ?

gege-fr commented 8 years ago

Well, since There is very little code maybe it would be better to use jquery's http api or even directly XHR if it has similar functionalities. The best would be to have no dependency at all.

The only usage of angular seems to be there : send the request and return a promise for the response.

        let httpRequestParams: any = {
            method: '{{httpMethod}}',
            url: localVarPath,
            json: {{#hasFormParams}}false{{/hasFormParams}}{{^hasFormParams}}true{{/hasFormParams}},
            {{#bodyParam}}data: {{paramName}},
            {{/bodyParam}}
            {{#hasFormParams}}data: this.$httpParamSerializer(formParams),
            {{/hasFormParams}}
            params: queryParameters,
            headers: headerParams
        };

        if (extraHttpRequestParams) {
            httpRequestParams = this.extendObj(httpRequestParams, extraHttpRequestParams);
        }

        return this.$http(httpRequestParams);
renaudpawlak commented 8 years ago

I have never used Angular to perform http invocations... but it seems to me that $http is an injected field and that's why it does not appear in the APIs... it is actually of the ng.IHttpService type... Your need to pass a $http field of this type to your constructor and set it to a field (if I understand it right). So it seems that your code is not complete.

I agree with you. If we can use a simpler lib it is better. You may want to check again the Server class I have pointed to you, which should allow the same sort of functions, but without using much libs. On the other hand, since you are having something working with Angular, I guess it would be simpler for you just to translate to JSweet... For instance, my first guess to translate the given code would be:

        IRequestConfig httpRequestParams = new IRequestConfig() {
          {
            method = "{{httpMethod}}";
            url = localVarPath;
            json = {{#hasFormParams}}false{{/hasFormParams}}{{^hasFormParams}}true{{/hasFormParams}};
            {{#bodyParam}}data = {{paramName}};
            {{/bodyParam}}
            {{#hasFormParams}}data = this.$httpParamSerializer(formParams);
            {{/hasFormParams}}
            params = queryParameters;
            headers = headerParams;
          }
        };

        if (extraHttpRequestParams != null) {
            httpRequestParams = this.extendObj(httpRequestParams, extraHttpRequestParams);
        }

        return this.$http.apply(httpRequestParams);
gege-fr commented 8 years ago

I've created two github projects (not familiar to github) :

A swagger codegen fork to work into : https://github.com/GegeFR/swagger-codegen A sample two modules backend / frontend : https://github.com/GegeFR/JSweetJAXRS

Normally you should only have to clone, build / install the swagger-codegen then build the "sample".

Nothing is functionnal yet. My current issue is the transpiler that does not seem to accept / find the generated classes. And soon there will be the war packaging. Then, at least, testing will begin.

The current error is :

ERROR: Cannot find name 'DefaultApi'. at com\ericsson\eritennis\jsweetjaxrs\frontend\module.ts(17)
ERROR: Cannot find name 'DefaultApi'. at com\ericsson\eritennis\jsweetjaxrs\frontend\module.ts(17)

Even though the classes are in the target/generated-sources/swagger folder. I tried to add them with includes or even copying them direcly in the main sources folder, i get errors. I don't know what is wrong but you probably will ! :-)

gege-fr commented 8 years ago

Oh, if everybody is in the same package it's ok, i should go and re-read the jsweet specific aspects :)

gege-fr commented 8 years ago

Slowly improving. My issue is now to either have the static main's transpiled code at the end of the bundle (so that all the necessary classes / functions can be found) or either to start working on an event that would ensure me that the js file has been completly loaded.

renaudpawlak commented 8 years ago

Is your project up-to-date? I have the following error when trying to build it: [ERROR] Plugin io.swagger:swagger-codegen-maven-plugin:2.1.6-SNAPSHOT or one of its dependencies could not be resolved: Failed to read artifact descriptor for io.swagger:swagger-codegen-maven-plugin:jar:2.1.6-SNAPSHOT: Could not transfer artifact io.swagger:swagger-codegen-maven-plugin:pom:2.1.6-SNAPSHOT from/to jsweet-plugins-release (http://repository.jsweet.org/artifactory/plugins-release-local): Failed to transfer file: http://repository.jsweet.org/artifactory/plugins-release-local/io/swagger/swagger-codegen-maven-plugin/2.1.6-SNAPSHOT/swagger-codegen-maven-plugin-2.1.6-SNAPSHOT.pom. Return code is: 409 , ReasonPhrase:Conflict. -> [Help 1]

About the bundles, from what you say I assume that you want to work directly in the browser w/o any modules:

  1. make sure that you use the snapshot version of JSweet for both transpiler and the maven plugin (1.1.0-SNAPSHOT)... That way you will get the latest improvements on the bundles :)
  2. once you switch to the snapshot version, use the bundle option without any module (unless you want to bundle for a given module system). If you remove the module option, you should get a single file with a call to main() at the end of it. JSweet automatically analyzes the dependencies to make sure that the classes are in the right order.
gege-fr commented 8 years ago

I hastily pushed before going home... However I'm suspecting something linked to your environment. At work i had to "unplug" my artyfactory in settings.xml ...

Another point : I tryed, at work, to set up a remote repository in our artifactory in order to access jsweet artifacts "gracefully" however once that was done jsxeet's artifactory was answer 401's for urls that were OK with my browser. Maybe it's identifying artifactory's user agent and filtering ?

gege-fr commented 8 years ago

OK just pushed; it's getting better and it is requesting the backend; Now what's left is to

I think I'll work on that tomorrow afternoon

Note : the bundlesDirectory parameter does not seem to work on 1.1.0-SNAPSHOT

gege-fr commented 8 years ago

okay, it's getting better now (just pushed);

what's left ?

lgrignon commented 8 years ago

Hello, On which kind of operation did you get a 401, was it a push or a pull? I think anonymous access is granted for read operations anyway, and I think artifactory uses standard maven communication. Please let me know if you have a detailed error trace and we will dig further into it ;)

Nice work on JSweet+Swagger btw, generated JSweet code seems ok to me.

lgrignon commented 8 years ago

Just renamed the example repositories for jaxrs: https://github.com/lgrignon/jsweet-jaxrs-server-example https://github.com/lgrignon/jsweet-cordova-ionic-example

gege-fr commented 8 years ago

What would be the best and safest way, in "pure" jsweet (no jquery dependency) to check if an Object is an array ?

lgrignon commented 8 years ago

You can use: import static jsweet.util.Globals.typeof;

;)

renaudpawlak commented 8 years ago

I don't think that typeof works on arrays (it looks like it returns object). Testing that an object is an array is quite painful in JavaScript because their is no short universal method. You may want to check out this page to see what I mean: http://www.shamasis.net/2011/08/infinite-ways-to-detect-array-in-javascript/.

Of course, what you can do it to port one of the given methods in JSweet. I would personally do the simplest one (it works for sure, I have tested it):

import jsweet.lang.Array;
...
if(obj instanceof Array) {
   ...
}

They say it does not work across frames, but I am not sure it is an actual problem nowadays...

All that being said, honestly, that's exactly why jQuery is so cool in JavaScript. I know I said that it would be better if we don't use any lib, but actually, jQuery makes it so much simpler for all kinds of simple things! I think I would use jQuery to avoid the pain of dealing with ES versions, cross-browser compatibility, etc... Of course, you will get a dependency, but the lib would be much more robust and the code much simplier.

gege-fr commented 8 years ago

I'm trying to support the arrays in queryParam and formParam before trying to send a merge request upwards. I'll trust you that jquery is the best way to do it.

Anyway jquery is used almost everywhere :)

Another topic : I'm not part of the jsweet team at all, and i'm trying to push something related to it back into swagger.

I made it work basically but it's far from complete, i didn't test a lot of use cases, there are probably a lot of missing things since I'm new to both swagger and jsweet ...

I wouldn't want it to have an impact on the (fresh) jsweet image and I don't feel confident to have time and skils to fix things and support that codegen module if it were needed.

What do you think would be the best way to go ?

renaudpawlak commented 8 years ago

Well, as I see things, it is mainly up to the swagger project to accept or not your PR. If your project is early stage, you have to tell them, and it depends probably on their policy to accept early stage projects or not.

I think that having a JaxRS project for JSweet, even at early stage, can only impact JSweet image positively. After all, all projects started early stage (we just need to make it clear to the users). We can advertise it on JSweet's web site when you feel that it is ready. If you want to, we can also discuss a tighter integration as a JSweet subproject, but I believe that it is premature at this point.

In any case, it is important that we play a little bit with your project before anything else. We have to make sure that there is a clear "getting started" and some basic examples and explanations so that we can easily try it out. So far, there is no README in your project so it is hard to see what to start with.

At the moment I have very little time, but if you add a README with a getting started section, I would be happy to try it on a toy project when I have some time.

gege-fr commented 8 years ago

Okay, noted :-)

I also have little time this week.

Before making some doc i'd like to simplify the maven coz it's actually too complicated. At least generate the yaml at the server build time instead of using jetty on the client build to deploy the war ....

Then yes i'll make a readme :-)

gege-fr commented 8 years ago

I made the readme, you're welcome to try it !

I also made the sample project a bit simple by adding a dedicated module for deploying the war / retrieving the json / attaching it as an artifact. That way the client is servlet/jax-rs free (only bound to swagger).

Now what's needed is a more complete / complex set of operations on the server in order to generate a more complex json def file and add support for more cases on the generated stub.

The sample project : https://github.com/GegeFR/JSweetJAXRS The swagger-codegen fork : https://github.com/GegeFR/swagger-codegen

gege-fr commented 8 years ago

In case you're testing , you should pull : Added a more complete example POJO Added support for Maps as jsweet Objects Better handling of Arrays. ByteArray handled as String coz jaxRS serialize them as as byte64 string

Made the callback/functionnal interfaces "static public" so that they're usable from other packages

gege-fr commented 8 years ago

About artifactory : Ours was sending a HEAD message because of the "list remote directories option" and yours was not liking it. Once the option is disable, the HEAD isn't sent anymore and everything's okay.

gege-fr commented 8 years ago

Also added enum support. One enum is created for each field, even if they're of the same enum type. But, seeing the json file, it seems there's no choice.

I think it's getting there... Now it needs to be played with a bit :)