[MAGNOLIA-3092] freemarker: support for loading taglibs from classpath resources (jars and folders) instead of WEB-INF/lib only Created: 18/Feb/10  Updated: 01/Mar/15  Resolved: 11/Dec/11

Status: Closed
Project: Magnolia
Component/s: None
Affects Version/s: None
Fix Version/s: 4.5

Type: Improvement Priority: Major
Reporter: Manuel Molaschi Assignee: Fabrizio Giustina
Resolution: Fixed Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: Text File taglibs.patch    
Issue Links:
causality
is causing MAGNOLIA-5651 Using JSP Taglibs with Freemarker on ... Closed
duplicate
is duplicated by MAGNOLIA-2608 Tag library support in freemarker doe... Closed
relation
is related to MAGNOLIA-2608 Tag library support in freemarker doe... Closed
is related to MAGNOLIA-3744 Upgrade to FreeMarker 2.3.18 Closed
Template:
Acceptance criteria:
Empty
Task DoD:
[ ]* Doc/release notes changes? Comment present?
[ ]* Downstream builds green?
[ ]* Solution information and context easily available?
[ ]* Tests
[ ]* FixVersion filled and not yet released
[ ]  Architecture Decision Record (ADR)
Date of First Response:

 Description   

it would be useful to have freemarker loading taglibs from classpath resources (jars and folders) instead of WEB-INF/lib only.
I created the MagnoliaTaglibFactory class that, using some code inspired by ClasspathResourceUtil, loads tlds searching in jars and folders derived from classpath. Then, in method FreemarkerHelper.checkTaglibFactory, i changed line
taglibFactory = new TaglibFactory(servletContext);
to
taglibFactory = new MagnoliaTaglibFactory(servletContext);

If it's fine for you, i can commit it.

PS: i've submitted the patch to freemarker (http://sourceforge.net/tracker/index.php?func=detail&aid=2954132&group_id=794&atid=100794#)



 Comments   
Comment by Philipp Bärfuss [ 04/Aug/11 ]

The methods we would like to override are private and I don't like to copy bigger parts of the code.

But we followed a different approach in the AbstractRenderTestCase. We fake the existence of the jars in WEB-INF/lib by mocking the servlet context. We could do the same thing in the FreemarkerHelper.addTaglibSupportData(Map<String, Object>, WebContext)

Comment by Philipp Bärfuss [ 04/Aug/11 ]

see AbstractRenderTestCase.createFreemarkerFriendlyServletContext()

the following changes had to be done:

  • the code has to consequently delegate to the original context first
  • the fake URL has to point to the WEB-INF/lib folder and not to the original jar
Comment by Danilo Ghirardelli [ 07/Sep/11 ]

There is an updated patch for the same problem, hoping that freemarkers developer would consider it someday:
https://sourceforge.net/tracker/?func=detail&aid=3405654&group_id=794&atid=300794

Anyway, the idea of "tricking" freemarker with a wrapped context is good, but I don't exactly like it, first because the problem is within the library, and second because I'm not really sure you can "trick" freemarker with only the wrapped context for every case, but that's just a sensation...

Comment by Danilo Ghirardelli [ 09/Sep/11 ]

I'm sorry, I attached the patch and commented the linked issue by mistake, I'll re-attach everything on this...

Just to refute myself, I attached a patch to do what Philipp suggested. The attached patch wraps freemarker and tricks it to load jars from all the classpath when they are requested under WEB-INF/lib.
The counterpart getResourceAsStream has been changed as well to do load files with absolute paths. This is a potentail security problem, but given that the method is called only by freemarker class and only with the data provided by the previous method, there should be no risk of loading random files on filesystem.

This make possible working with freemarker in Magnolia directly under eclipse, without deploying.

Just to be clear, this patch is inspired by the freemarker patch linked above, so credits goes to the original creator.

Comment by Fabrizio Giustina [ 11/Dec/11 ]

After playing a bit with the patch I realized it doesn't completely solve the issue described in the original MAGNOLIA-2608

There are actually 2 problems with the freemarker handling of tlds:
1) tlds are not loaded from jar files if jars are not in the WEB-INF/lib directory
2) tlds are not loaded if not stored in jars, never.

The FreemarkerServletContextWrapper implemented here only solves the first issue, but this is not enough. While working with the Magnolia community code, for example, all the taglibs are not inside a jar but expanded in the "generated-resources/xdoclet" directory.

Unfortunately it looks like the second issue can't be solved without extending freemarker TagLibFactory: looking at the original code it's clear that is just throws away any path that doesn't refer to a jar (and so also most of the resouce-scanning code in FreemarkerServletContextWrapper is actually useless):

    private void getLocationsFromLibJars() throws ParserConfigurationException, IOException, SAXException
    {
        Set libs = ctx.getResourcePaths("/WEB-INF/lib");
        for (Iterator iter = libs.iterator(); iter.hasNext();) {
            String path = (String) iter.next(); 
            if(path.endsWith(".jar") || path.endsWith(".zip")) {
               ... // check for tlds

Although according to jsp specs also tlds expanded into WEB-INF and subdirectories should be loaded automatically freemarker does't seem to support this, so we cannot either use this to trick it (the "load resources from workspaces" in the eclipse tomcat adapter works by faking WEB-INF/xxx.tld paths for any tld found in classpath and outside jars).

So, although I totally agree with Philipp and I wouldn't go for replacing taglibfactory copying a large part of the code due to lack of extensibility, that looks the only way. I don't really like the solution but the issue is pretty important for any eclipse user out there and it is here unsolved for nearly 2 years... speaking about myself I realized that until now I copied the patched taglibfactory over a dozen of projects, always running into this issue.

Said so, I'll try to cleanup the taglibfactory-based patch as more as possible so that it can be included, I can't see any other working solution...

Comment by Danilo Ghirardelli [ 11/Dec/11 ]

The patch was written with freemarker 2.3.18, in which they slightly refactored their TaglibFactory. The little changes they did are not enough to solve our problem directly, but are enough to make this patch works as intended.

Comment by Fabrizio Giustina [ 11/Dec/11 ]

wow, looks like part of the issue has been actually solved in freemarker 2.3.18 (trunk is using 2.3.16 at the moment):
http://sourceforge.net/tracker/index.php?func=detail&aid=3151085&group_id=794&atid=100794#

while it doesn't actually fix this issue it should help in solving the problem by wrapping the servlet context instead of patching everything as explained in the last comment... upgrading could be a better solution, I'll try to get through this path.

Comment by Fabrizio Giustina [ 11/Dec/11 ]

Done!
freemarker 2.3.18 + the patch works fine! Many thanks to Danilo, we can now finally close this long-standing issue.

Comment by Danilo Ghirardelli [ 11/Jul/12 ]

Is there any chance to have this backported also in the 4.4.x branch?

Comment by Daniel Dekany [ 01/Mar/15 ]

You don't need to patch FreeMarker anymore to have this feature. Since 2.3.22, the requested behavior can be activated with an init-param to FreemarkerServlet:

<init-param>
  <param-name>MetaInfTldSources</param-name>
  <param-value>classpath</param-value>
</init-param>  

Or, as this behavior is probably only desirable when running the application from under Eclipse without deploying it, you can set this system property in the Eclipse run configuration:

-Dorg.freemarker.jsp.metaInfTldSources=classpath

Or, if you want this to be the default (but beware, this behavior differs from what the JSP specification requests), you can extend FreemarkerServlet and override createDefaultMetaInfTldSources like:

    @Override
    protected List<MetaInfTldSource> createDefaultMetaInfTldSources() {
        return Collections.singletonList(
                new ClasspathMetaInfTldSource(Pattern.compile(".*", Pattern.DOTALL)));
    }
Generated at Mon Feb 12 03:43:06 CET 2024 using Jira 9.4.2#940002-sha1:46d1a51de284217efdcb32434eab47a99af2938b.