Grails 3 static compilation of taglib - java

I'm trying to apply #GrailsCompileStatic to taglib and geting the following error:
Error:(19, 16) Groovyc: [Static type checking] - Cannot find matching
method com.tempvs.image.MyTagLib#render(java.util.LinkedHashMap
). Please check if the
declared type is right and if the method exists.
Code example:
#GrailsCompileStatic
class MyTagLib {
...
String myTag = { Map attrs ->
...
out << render(template: '/templates/myTemplate', model: [...])
}
}
What am I doing wrong and how can I solve the problem?

You are using some dynamic features, because taglibs in general do use them. Even just calling "render" is in a sense dynamic.
If you really want to, you can work around this by injecting a PageRenderer and using that to render your page, and then outputting the resulting HTML. I'm not sure it would be worth it, but I certainly don't know your performance requirements!
Example:
import grails.compiler.GrailsCompileStatic
import grails.gsp.PageRenderer
#GrailsCompileStatic
class StaticTestTagLib {
static namespace = "staticTest"
PageRenderer groovyPageRenderer
String myTag = { Map attrs ->
out << groovyPageRenderer.render(template: '/templates/myTemplate', model: [...])
}
}
This works, but you will have some work ahead of you if your taglib is doing anything remotely complex, because EVERY call to render, or any other tag is going to have to be replaced by a statically compiled call. It's possible, but possibly not hugely beneficial.

I am not sure what is the point in creating a new tag that just renders a template.
Grails already has a tag library that renders a template.
<g:render template="displaybook" model="['book':book,'author':author]" />
https://docs.grails.org/latest/ref/Tags/render.html

Related

Is it possible to create a Freemarker macro programmatically?

Freemarker is used as the default template engine in the ninja web framework. The framework assigns some default values to a template which are globaly available when using the ninja web framework. I have created an extension for the template which does enbales CSRF-Protection. The extension offers a function which can be used in a template, e.g.
${foo(bar)}
At the moment the function needs to be called with specific parameters, which is not very intuitiv. Using a macro I could simplify this call to
#{foo}
and the user doesn't need to worry about passing the correct (e.g. "bar") parameter. But to make this available in the ninja web framework I have to define a macro programmatically. Is that possible?
UPDATE
Sorry for the confusion. Meant <#foo/> instead of #{foo} ...
Looking at the Freemarker documentation I maybe can make more clear what I want to achieve: http://freemarker.org/docs/ref_directive_macro.html
Like I explained above I am passing a custom function to the template, enabling me to call
${foo("bar")}
What I want to do, is call this via a macro like
#<myMacro/>
But the defined macro like
<#macro myMacro>
${foo("bar")}
</#macro>
should not be defined in the template but programmatically. Hope that makes it more clear.
UPDATE2 / SOLUTION
I ended up using the recommended TemplateDirectiveModel.
public class TemplateEngineFreemarkerAuthenticityTokenDirective implements TemplateDirectiveModel {
private String authenticityToken;
public TemplateEngineFreemarkerAuthenticityTokenDirective(Context context) {
this.authenticityToken = context.getSession().getAuthenticityToken();
}
#Override
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
if (!params.isEmpty()) {
throw new TemplateException("This directive doesn't allow parameters.", env);
}
if (loopVars.length != 0) {
throw new TemplateException("This directive doesn't allow loop variables.", env);
}
Writer out = env.getOut();
out.append(this.authenticityToken);
}
}
FreeMarker macro invocations doesn't look like #{...}. Is that some kind of Ninja-specific extension?
Anyway, if you know that there's a bar in the data-model, then your method can get it like Environment.getCurrentEnvironment().getDataModel().get("bar"), so it need not be passed in.
Also, it's maybe useful to know that FTL has two kind of "subroutines", the function-like ones, and the directive-like ones. Both can be implement both in FTL (#function, #macro) and in Java (plain Java methods, TemplateMethodModelEx, TemplateDirectiveModel). The real difference is that the function-like ones are for calculating values, and the directive-like ones are for printing values directly to the output (hence bypassing auto-escaping) and for side-effects. But all of these can reach the Environment, so there's no difference there.
You can call a macro "dynamically". Let's say you had a macro:
<#macro myMacro>
${foo("bar")}
</#macro>
You can call it like this:
<#myMacro />
OR
<#.vars["myMacro"] />
So then you can do...
<#assign someVar = "myMacro" />
<#.vars[someVar] />

Unified Expression Language, method call list argument

i have next method:
public int countEvents(List<EventTypeEnum> eventTypes)
now how to call this method from unified EL ?
With non collection enum arguments works fine but not with collection
Tried
${countEvents("ALERT")}
${countEvents(["ALERT"])}
both crashes
If the values are fixed, you might just do this:
public int countEvents(List<EventTypeEnum> eventTypes) { ... }
public int countAlerts() {
return countEvents(Arrays.asList(EventTypeEnum.ALERT));
}
${countAlerts()}
This is usually the simplest and clearest solution.
Enums could be treated as strings in jstl. But there is no direct way by which you could create a list in jstl. You need to have a list of evenTypes in scope, say ${eventTypes}, which is a List<EventTypeEnum> then you could directly the method in you bean like ${bean.countEvents(eventTypes)}.
However from your code i presume you need to create this on the fly. In that case you'll have to rely on standard jsp. Say you have an enum
public enum EventTypeEnum {
ALERT, ONCLICK, ONBLUR
}
And a pojo which has the count method like
import java.util.List;
public class CountBean {
public int countEvents(List<EventTypeEnum> eventTypes){
return eventTypes.size();
}
}
So if you have an instance of CountBean in scope, say ${myBean} then in you jsp you could do like
<jsp:useBean id="events" class="java.util.ArrayList">
<%
events.add("ALERT");
events.add("ONCLICK");
%>
</jsp:useBean>
Length : <c:out value="${myBean.countEvents(events)}"/>
This would work fine. If you dont want to use <jsp:useBean> approach you could use MicroNova YUZU tags. It is an open-source EL-based JSP tag libary designed to augment JSTL. More details could be found on http://www.micronova.com/yuzu.jsp.
If you dont want to create bean instances to invoke this method and wish to use it as static you could create custom jstl methods as explained here or here.
Hope this helps.

Add Freemarker support to customized JSP tag

I have a customized JSP tag library with a Java class (extending TagSupport) that generates the output for my web application. It has some parameters that are formed into HTML code using a StringBuilder.
Now the generated HTML is becoming more complex and hard to handle with calls of StringBuilder.append, so I'd like to replace the code generation with a Freemarker template.
I already found out that I could use a generic Struts component tag instead, because the Struts tags already use Freemarker template files, so I could write a tag like:
<s:component template="/components/myStruct.ftl">
<s:param name="myParam" value="%{'myParam'}" />
</s:component>
Then writing the specified template file myStruct.ftl would probably solve my problem. I actually did not try if Struts really finds and uses that file correctly, but I optimistically expect it to work.
My question is, if it's also possible to retain the current code with the customized tag
<my:struct param="myParam" />
and only change the Java class linked to that tag.
I've found code that reads a Freemarker template:
Configuration config = FreemarkerManager.getInstance().getConfiguration(pageContext.getServletContext());
config.setServletContextForTemplateLoading(pageContext.getServletContext(), "/components");
Template templ = config.getTemplate("myStruct.ftl");
templ.process(params, pageContext.getOut());
but it seems very circuitously to me and I wondered what would be the "standard" way to do it. Additionally it seemed that you cannot use tags from the Struts tag library in a template used like this. (I ran into an ArrayIndexOutOfBoundException, caused by Sitemesh... I did not analyze it yet.)
My intention was to keep the Java class as some kind of wrapper around the Struts component tag. Maybe somthing like:
OgnlValueStack stack = TagUtils.getStack(pageContext);
Component c = new Component(stack);
c.addParameter("param", param);
But I don't know how to continue this code stub. It may be crap anyway.
Is there an easy/"standard" way to do this or do I simply have to get rid of the customized tag?
Thanks in advance.
A friend of mine sent me this link:
http://cppoon.wordpress.com/2013/02/27/how-to-create-a-struts-2-component-with-freemarker/
This is what I was looking for. The gist is to change the customized tag to not extend TagSupportbut AbstractUITag which makes it a Struts tag instead of a JSP tag, roughly speaking.
This enables the automatic linkage (by name and path conventions) to my Freemarker template. I basically followed the instructions on that page. I only added the methods that are abstract in the super class, so they had to be implemented.
IMO the site lacks of a description of how the UI bean class is linked to the tag class. But as the IDE forces you to implement the getBean method inside the tag class, you quickly get to this code (using the classes described on that site):
#Override
public Component getBean(OgnlValueStack stack, HttpServletRequest request, HttpServletResponse response)
{
Pagination pagination = new Pagination(stack, request, response);
pagination.setList(list);
return pagination;
}
This might not be completely correct for the recent Struts, but it worked for the ancient version I've got to use.
Thanks again to the guy who sent me the link :)

Using String array "constant" in Spring #RequestMapping value

I should probably point out that Spring is not in and of itself necessarily crucial to this question, but I encountered this behavior while using Spring, so the question uses the situation in Spring in which I encountered this.
I have a controller class that maps requests for GET and POST requests to the same set of URLs for a particular form. This form has different URLs for different locales, but there is only one method for the GET request, and one for the POST, since the logic at the controller level for the form is identical for each locale site (but things deeper in the logic, like locale-specific validation, may be different). Example:
#Controller
public class MyFormController {
// GET request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value={"/us-form.html", "/de-form.html", "/fr-form.html"},
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The form GET and POST works just fine when written like this. You'll notice that the String arrays used for the #RequestMapping values are identical. What I want to do is put those URLs into one spot (ideally a static final field in the controller) so that when we add new URLs (which correspond to the form in future localized sites), we can just add them in one spot. So I tried this modification to the controller:
#Controller
public class MyFormController {
// Moved URLs up here, with references in #RequestMappings
private static final String[] MY_URLS =
{"/us-form.html", "/de-form.html", "/fr-form.html"};
// GET request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.GET})
public String showMyForm() {
// Do some stuff like adding values to the model
return "my-form-view";
}
// POST request
#RequestMapping(value=MY_URLS, // <-- considered non-constant
method={RequestMethod.POST})
public String submitMyForm() {
// Do stuff like validation and error marking in the model
return "my-form-view"; // Same as GET
}
}
The problem here is that the compiler complains about the value attribute no longer being a constant. I am aware that Spring requires that value must be a constant, but I had thought that using a final field (or static final in my case) with an Array literal containing String literals would have passed as "constant". My suspicion here is that the array literal has to be constructed on the fly in such a way that it is uninitialized when the value attribute is parsed.
I feel like this shouldn't be a hard thing to figure out with a basic Java knowledge, but something is escaping me that I haven't been able to find any answers for after some research. Can someone confirm my suspicion and give a citation or good explanation for why that may be so, or deny my suspicion and explain what the actual issue is?
Note: I cannot simply combine the URLs into a Path Pattern, as each form URL is in its localized site's language, and matching on that would be impossible. I merely give the "/{locale}-form.html" strings above as my URLs for example's sake.
You're right, this is nothing to do with Spring, all Annotation parameters must be compile-time constants. That's a basic java language rule.
Marking the array reference as final doesn't cut it because this is still perfectly legal:
MY_URLS[0] = "es-form.html";
Also, how locked in are you into embedding locale into the url like that in the first place? Are you emulating legacy links? Spring has plenty of built in support for using the browser's actual locale.

JSF: navigation

I have to warn you: the question may be rather silly, but I can't seem to wrap my head around it right now.
I have two managed beans, let's say A and B:
class A
{
private Date d8; // ...getters & setters
public String search()
{
// search by d8
}
}
class B
{
private Date d9; //...getters & setters
public String insert()
{
// insert a new item for date d9
}
}
and then I have two JSP pages, pageA.jsp (the search page) and pageB.jsp (the input page).
What I would like to do is placing a commandbutton in pageB so to open the search page pageA passing the parameter d9 somehow, or navigating to pageA directly after b.insert(). What I would like to do is showing the search result after the insertion.
Maybe it's just that I can't see the clear, simple solution, but I'd like to know what the best practice might be here, also...
I though of these possible solutions:
including **A** in **B** and linking the command button with **b.a.search**
passing **d9** as a **hiddenInput** and adding a new method **searchFromB** in **A** (ugly!)
collapsing the two beans into one
JSF 1.1/1.2 raw doesn't provide an easy way to do this. Seam/Spring both have ways around this and there are a couple of things you can do. JSF 2 should also have solutions to this once it is released.
Probably the easiest and most expedient would be to collapse the two beans into one and make it session scoped. The worry, of course, is that this bean will not get removed and stay in session until the session times out. Yay Memory leaks!
The other solution would be to pass the date on as a GET parameter. For instance, you action method could call the
FacesContext.getCurrentInstance().getExternalContext().redirect("pageB?d9=" + convertDateToLong(d9));
and then get the parameter on the other side.
You should configure the navigation flow in faces-config.xml. In ideal scenario you would return a "status" message which would decide the flow. Read more at following link:
http://www.horstmann.com/corejsf/faces-config.html
http://publib.boulder.ibm.com/infocenter/rtnlhelp/v6r0m0/index.jsp?topic=/com.businessobjects.integration.eclipse.doc.devtools/developer/JSF_Walkthrough8.html
As far as passing the values from one page to another is concerned you can use backing beans. More about backing beans here:
http://www.netbeans.org/kb/articles/jAstrologer-intro.html
http://www.coderanch.com/t/214065/JSF/java/backing-beans-vs-managed-beans
Hope i have understood and answered correctly to your question
Way to share values between beans
FacesContext facesContext = FacesContext.getCurrentInstance();
Application app = facesContext.getApplication();
ExpressionFactory elFactory = app.getExpressionFactory();
ELContext elContext = facesContext.getELContext();
ValueExpression valueExp = elFactory.createValueExpression(elContext, expression, Object.class);
return valueExp.getValue(elContext);
In above code "expression" would be something like #{xyzBean.beanProperty}
Since JSF uses singleton instances, you should be able to access the values from other beans. If you find more details on this technique, I am sure you'll get what you are looking for.
Add commandButton action attribute referencing to B'insert method
<h:commandLink action="#{b.insert}" value="insert"/>
In B'insert method,add d9 parameter as request parameter. Then return an arbitrary string from insert method.
FacesContext fc = FacesContext.getCurrentInstance();
fc.getExternalContext().getRequestMap().put("d9", d9);
Then go to faces context and add navigation from B to A with "from-outcome" as the arbitrary String you returned from insert method. But don't add redirect tag to navigation tags as it will destroy the request coming from B and the parameter you added (d9) will be cleared.
<from-outcome>return string of insert method</from-outcome>
<to-view-id>address of A</to-view-id>
Then you might get the "d9" in A class by fetching it from request map at its constructor or in a place where its more appropriate (getters). You might add it into a session scope or place it to a hidden variable if you want to keep track of it later.
in class A, when page is navigated, A should be initialized as it will be referenced.
FacesContext fc = FacesContext.getCurrentInstance();
fc.getExternalContext().getRequestMap().get("d9", d9);
Sorry i cant give full code, as i have no ide at here, its internet machine at work. I could not give details therefore.
In my opinion, the simplest way is 3-rd option - have both query and insert methods in same class. And you can do something like that:
public String query () {
//...
}
public String Insert() {
//insert
return Query(); }
If your classes are managed Beans you can load class A from class B and call A.query() in your insert method at the end. Also class A can have
<managed-bean-scope>session</managed-bean-scope>
parameter in faces-config.xml and it wouldn't be instantiated again when loaded.

Categories