Java static method call from freemarker template

3,270 views
Skip to first unread message

Daniel SAWAN

unread,
Dec 10, 2015, 11:14:37 AM12/10/15
to ninja-framework

Hi there,


I want to call a specific java method from a static class and I was wondering if this can be done from a freemarker template.

I just don’t want to pass the object to all my templates because this method should be call on the website main template or layout. So the basic solution to get the java object in all my controller and send them to all templates using .render(…) is not an acceptable solution.


Thanks for your help

Pierre N

unread,
Dec 11, 2015, 2:20:58 AM12/11/15
to ninja-framework
This is how I did in one of my projects : 

public class Ninja extends NinjaDefault {
   
   
@Inject
   
TemplateEngineFreemarker tpl;


   
@Override
   
public void onFrameworkStart() {
       
BeansWrapper wrapper = BeansWrapper.getDefaultInstance();
       
TemplateHashModel staticModels = wrapper.getStaticModels();
       
TemplateHashModel tplHashModelStringUtils = null;
       
try {
            tplHashModelStringUtils
= (TemplateHashModel) staticModels.get("com.serphacker.javafixutil.StringUtils");
       
} catch (TemplateModelException e) {
            e
.printStackTrace();
           
System.exit(1);
       
}
        tpl
.getConfiguration().setSharedVariable("StringUtils", tplHashModelStringUtils);
 
}
 
}

Daniel SAWAN

unread,
Dec 11, 2015, 4:27:50 AM12/11/15
to ninja-framework
Thanks for your help ! This is working !
Btw you should add super.onFrameworkStart(); and the end of onFrameworkStart().

Daniel SAWAN

unread,
Dec 11, 2015, 5:19:05 AM12/11/15
to ninja-framework

Now i get another problem:/


Calling a static method work. But my Static Method should be able to call some database data and return them to the template.

So I write some : 


@Inject

UserDao userDao;


But the injection doesn’t work because when we call staticModels.get("tools.Test") it just reference it in order to call the method and do not instantiate it with all the injection stuff.

So does someone know if there is a way to get all the injection stuff working outside of a controller ? (in a static method). How to get my DAO working in a static method ? Or maybe I am doing these in a wrong way ?


I am completely lost here because in Play framework it was so easy to make this ex : @MyStatic.staticMethod() and that’s it… staticMethod could have transactions etc and still work.


Le vendredi 11 décembre 2015 08:20:58 UTC+1, Pierre N a écrit :

Raphael André Bauer

unread,
Dec 11, 2015, 5:25:27 AM12/11/15
to ninja-f...@googlegroups.com
Hi Daniel,


you are working against the MVC pattern here. Ninja (and the MVC
pattern) is designed to clearly separate the view from the data. Data
(and hence database calls) should be solely done inside the controller
and the ready-to-use data should then be rendered via the view.

If you don't do this (like accessing a database inside the view) you
will very likely end up with unmaintainable code (spaghetti code) and
code that is very very hard to test and verify.

There are of course ways how you can do this (it's software after
all), but that does not mean that these ways make any sense...


Best,


Raphael
> --
> You received this message because you are subscribed to the Google Groups
> "ninja-framework" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to ninja-framewo...@googlegroups.com.
> To post to this group, send email to ninja-f...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/ninja-framework/036458c1-c3bd-4d79-a301-e16b95363513%40googlegroups.com.
>
> For more options, visit https://groups.google.com/d/optout.

Daniel SAWAN

unread,
Dec 11, 2015, 5:38:33 AM12/11/15
to ninja-framework

I completely agree with you Raphael until I found that I have this use case :


I need to show all the user last actions on the website on the left menu. The left menu is part of the template and this left menu is showed in almost all my website.

In order to get this “last actions” I have to call the database. So as you can see the use case is very simple.


So the question now is : how to get those last action without having to retrive them and render them in every single controller method of my website (big refactoring).

So the cleanest solution I found is to let the template manage this.


How would you Raphael solve this use case ?

Francisco Leite de Castro

unread,
Dec 11, 2015, 6:48:48 AM12/11/15
to ninja-f...@googlegroups.com
In sure that hacking the view, right now, looks like the solution that will write the less amount of code, but it's definitely not the cleanest.

I'm not familiar with ninja but from a quick glance over the docs "filters" seem the way to go.

Someone more familiar with the platform will sure be able to point you in a possibly even more elegant solution.


Francisco

Sent from my phone

Raphael André Bauer

unread,
Dec 11, 2015, 7:21:32 AM12/11/15
to ninja-f...@googlegroups.com
Yea! Thats totally right. Filters are perfect for that. Simply inject
the Dto into your filter and render a special variable at the end
after your controller returns the result. Then add the filter to each
method (or class to simplify things) that should be supplied with the
variable.

Let us know if that helps!


Cheers,

Raphael
> https://groups.google.com/d/msgid/ninja-framework/EC7A72FE-9FCF-405C-B6BF-A87B83C461B3%40gmail.com.

Daniel SAWAN

unread,
Dec 11, 2015, 7:57:59 AM12/11/15
to ninja-framework
    @Inject
    UserDao userDao;
    
   
@Inject
   
TemplateEngineFreemarker tpl;
 
   
@Override
   
public Result filter(FilterChain chain, Context context) {
   
       
if (context.getSession() != null && context.getSession().get(USERID) != null) {
       
long userid = Long.parseLong(context.getSession().get(USERID));
       
try {
 tpl
.getConfiguration().setSharedVariable("userActions", userDao.getLastsUserAction(userid, 5));
 
} catch (TemplateModelException e) {
 e
.printStackTrace();
 
}
       
}
       
return chain.next(context);


   
}


I made a Filter and add it the the class and it work perfectly well.
This respect MVC and is a way better solution then mine (template calling DAO).
The only problem with this sort of solution is that each new created Class or Method should be annotated. So it is not as "transparent" as i want it to be. But i think in a good coder point of view this is the best way to go and in a lazy coder point of view it is like "damn i have to remembter to put this anotation everywhere". So i decided to put this directly in my TestLoggedInFilter which is already everywhere to make it transparent.

Thanks for your helps guys ! I hope this will help other people with same problem (add to cart management etc...) :)

ufstzv...@googlemail.com

unread,
Dec 11, 2015, 8:25:34 AM12/11/15
to ninja-framework
Yes, filters could be a good solution.
But with filters you have to think about every time creating a new action-method, which filters to add or not to add. With many developers, this could be some kind of messy to figure out, which filters on which methods to add. Or think about you have a controller method, that is responsible for to solve a specific problem (like getting the "lastActions of a user"), then maybe sometimes you want that to be annotated with that specific filter, and sometimes you dont want the filter to be applied.

I have another idea, but (maybe) not for FreeMarker.

Just take a look at the rocker-template-engine.
(https://github.com/fizzed/ninja-rocker)

Consider having the following 3 files:
1.) ApplicationController.java
2.) index.rocker.html
3.) lastActions.rocker.html

->

1.) ApplicationController.java:
public class ApplicationController {
    public Result index() {
        return Results.ok().render(
            views.index.template()
        );
    }
    public static String renderLastActions() {
    //do some heavy DB-calls to get the lastActions
        return views.lastActions.template(dbResults).render().toString();
    }
}

2.) index.rocker.html:
@import controllers.ApplicationController
<html>
<body>
    Lets see how the rendered template of "renderLastActions" looks like:
    <br>
    @controllers.ApplicationController.renderLastActions()
</body>
</html>


3.) lastActions.rocker.html
@import controllers.ApplicationController
@args(LastAction dbResults)
<div>
    //loop over and print the lastActions:
    @dbResults
</div>


So, with this solution you are absolutely flexible where (in which templates) you want to print out the "lastActions".
In addition to that, all the DB-stuff is done within a method in the controller.
And all your templates wont need any arguments/parameters (exempt the only one template, that is responsible for to render the "lastActions").
So that would solve your main problem "i-don’t-want-to-pass-the-object-to-all-my-templates".

What would you all say about my idea (my solution), concerning
- avoid breaking-the-MVC-pattern
- avoid spaghetti-code
- avoid createing unmaintainable-code
in conjunction with
- provide a less-amount-of-code solution
- provide a clean solution?

Daniel SAWAN

unread,
Dec 11, 2015, 9:29:14 AM12/11/15
to ninja-framework
Wow i just noticed that tpl.getConfiguration().setSharedVariable change the freemarker setting for all users.
Putting a variable in that method would impact all the users of your website... You definitly can't put user specific variable in that :/
Do you know a way to add variable only on the current rendered template ?

Le vendredi 11 décembre 2015 08:20:58 UTC+1, Pierre N a écrit :

Daniel SAWAN

unread,
Dec 11, 2015, 9:53:24 AM12/11/15
to ninja-framework
I think your idea doesn't solve the spaghetti-code and is the same exact thing i was trying to do.
Instead of calling the dao you call the controller which call the dao but it still the same problem. It is the template calling the Dao (not directly this time) But in my example the "static" class is the "controller" class in your example.

I tried using Ninja framework to make a static method in a controller instead of a random Class and still have the same problem.
In order to use your Dao you have to inject it. In order to call the controller from the template then your method should be static. But when you change the controller method to static then you have to change the Dao declaration to static and then the injection fail.

I tried everything to make template calling static method with db requests but as your go out of the ninja normal request workflow then you don't have transactions, injections etc...

So i think there is 2 solutions :

1) is there a way to get dao working in a static method ? but then it goes spaghetti
2) Is there a way to give a specific object to template using filters ? Because the code i tried "tpl.getConfiguration().setSharedVariable" set the variable for all the freemarker template configuration which is shared and not for the actual requested template

Raphael André Bauer

unread,
Dec 11, 2015, 10:00:28 AM12/11/15
to ninja-f...@googlegroups.com
Hi Daniel,


there is a misunderstanding here... Don't use the static stuff anywhere.

Use the following in your filter:
"""
return chain.next(context).render("userActions",
userDao.getLastsUserAction(userid, 5)).
"""

Then you can access $userActions in all your templates...

Cheers,

Raphael
> https://groups.google.com/d/msgid/ninja-framework/cde766fc-573f-444c-9e0b-5d3e20791440%40googlegroups.com.

Daniel SAWAN

unread,
Dec 11, 2015, 10:10:46 AM12/11/15
to ninja-framework
It would be a nice solution Raphael and i didn't know we could access .render() this way but i got this error trying your solution :

java.lang.IllegalArgumentException: You already want to render a Renderable class. Adding more items to render is not supported.
        at ninja.Result.assertObjectNoRenderableOrThrowException(Result.java:753)
        at ninja.Result.render(Result.java:270)
        at ninja.Result.render(Result.java:331)
        at filters.LoggedInFilter.filter(LoggedInFilter.java:79)
        at ninja.FilterChainImpl.next(FilterChainImpl.java:35)
        at ninja.NinjaDefault.onRouteRequest(NinjaDefault.java:102)
        at conf.Ninja.onRouteRequest(Ninja.java:45)
        at ninja.servlet.NinjaServletDispatcher.service(NinjaServletDispatcher.java:86)
        at com.google.inject.servlet.ServletDefinition.doServiceImpl(ServletDefinition.java:287)
        at com.google.inject.servlet.ServletDefinition.doService(ServletDefinition.java:277)
        at com.google.inject.servlet.ServletDefinition.service(ServletDefinition.java:182)
        at com.google.inject.servlet.ManagedServletPipeline.service(ManagedServletPipeline.java:91)
        at com.google.inject.servlet.FilterChainInvocation.doFilter(FilterChainInvocation.java:85)
        at com.google.inject.servlet.ManagedFilterPipeline.dispatch(ManagedFilterPipeline.java:119)
        at com.google.inject.servlet.GuiceFilter$1.call(GuiceFilter.java:133)
        at com.google.inject.servlet.GuiceFilter$1.call(GuiceFilter.java:130)
        at com.google.inject.servlet.GuiceFilter$Context.call(GuiceFilter.java:203)
        at com.google.inject.servlet.GuiceFilter.doFilter(GuiceFilter.java:130)
        at org.eclipse.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1652)
        at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:585)
        at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1127)
        at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:515)
        at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1061)
        at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
        at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:97)
        at org.eclipse.jetty.server.Server.handle(Server.java:497)
        at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:310)
        at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:257)
        at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:540)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:635)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:555)
        at java.lang.Thread.run(Thread.java:744)

Daniel SAWAN

unread,
Dec 11, 2015, 10:22:46 AM12/11/15
to ninja-framework
I changed my test page and don't got this error anymore but the template still don't manage to get this variable.

ronny...@gmail.com

unread,
Dec 11, 2015, 10:28:37 AM12/11/15
to ninja-framework
@Raphael

thanks! that works for me.

Raphael André Bauer

unread,
Dec 11, 2015, 10:35:41 AM12/11/15
to ninja-f...@googlegroups.com
How does your code look like inside your controllers? What do you put
inside your .render(..) methods?

Raphael
> https://groups.google.com/d/msgid/ninja-framework/4d545fbe-a073-49a6-b23c-0e7bf2a40c4a%40googlegroups.com.

Daniel SAWAN

unread,
Dec 11, 2015, 10:35:47 AM12/11/15
to ninja-framework
oh i am such a dumb i didn't returned the result directly;

chain.next(contexte).render ("userActions", userDao.getLastsUserAction (userid, 5)). 

return chain.next(context);

My bad this is working. Thanks ! :D

Daniel SAWAN

unread,
Dec 11, 2015, 12:28:55 PM12/11/15
to ninja-framework
By the way you should really take care when you use this sort of filter globally.

I have many controllers, some was returning  Results.html().render("xxx", xxx) and others  Results.text().render("mytext")

if in your global filter you do something like : 

return chain.next(contexte).render ("userActions", userDao.getLastsUserAction (userid, 5));

Then if your controller call :

Results.text().render("mytext")

A bug is gonna happen and the rendered object will but be "mytext" but List<UserActions>

everything is ok is your controller return a Results.html().render("xxx", xxx)

I don't know if it is a bug or if it is normal.
Reply all
Reply to author
Forward
0 new messages