Skip to content

Commit

Permalink
Feature: add support for custom taglib mappings for freemarker from t…
Browse files Browse the repository at this point in the history
…hird-party libraries
  • Loading branch information
dmarks2 committed Nov 20, 2024
1 parent 2b88a21 commit 817dfc4
Show file tree
Hide file tree
Showing 10 changed files with 220 additions and 2 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
- Feature: add documentation lookup and code completion for Liferay specific properties in `gradle.properties`
- Feature: add XPath expression support for Liferay specific helper classes like `SAXReaderUtil`
- Feature: add deprecation checks for Liferay 2024.Q3
- Feature: add support for custom taglib mappings for freemarker from third-party libraries
- Bugfix: Finally fixed gradle support for projects using Java 16 or below
- Bugfix: Exception occurred after deleting a fragment.json file
- Bugfix: Detecting servlet context imports in Freemarker files did not work for gradle projects (code completion, freemarker debugger)
Expand Down
4 changes: 3 additions & 1 deletion documentation/vtl_ftl_files.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ The following taglibs are available:
<@liferay_social_bookmarks />
<@liferay_trash />

Custom taglibs from third-party dependencies are included, too. The dependencies must have a `META-INF/taglib-mappings.properties` file.

*This feature works in IntelliJ Ultimate Edition only.*

Implicit Theme Settings variables
Expand Down Expand Up @@ -181,4 +183,4 @@ Depending on the Liferay version inspections are shown if deprecated or removed
Freemarker files. Depending on the type of deprecation or removal quickfixes are offered. Those may be

* Remove the attribute or tag
* Rename the attribute, tag or namespace to a replacement for the deprecation
* Rename the attribute, tag or namespace to a replacement for the deprecation
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package de.dm.intellij.liferay.language.freemarker;

import com.intellij.freemarker.psi.files.FtlXmlNamespaceType;
import com.intellij.freemarker.psi.variables.FtlVariable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.ModuleOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleSourceOrderEntry;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiManager;
import com.intellij.psi.xml.XmlDocument;
import com.intellij.psi.xml.XmlFile;
import com.intellij.xml.XmlNSDescriptor;
import de.dm.intellij.liferay.language.freemarker.custom.CustomFtlVariable;
import de.dm.intellij.liferay.util.LiferayFileUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.PropertyResourceBundle;

public class LiferayFreemarkerTaglibsUtil {

private final static Logger log = Logger.getInstance(LiferayFreemarkerTaglibsUtil.class);

public static void getCustomTaglibMappings(@NotNull Module module, Map<String, List<? extends FtlVariable>> customTaglibMappings) {
Project project = module.getProject();

ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);

OrderEnumerator orderEntries = moduleRootManager.orderEntries();

orderEntries.forEach(orderEntry -> {
if (
(orderEntry instanceof ModuleSourceOrderEntry) ||
(orderEntry instanceof ModuleOrderEntry)
) {
Module orderEntryModule;

if (orderEntry instanceof ModuleSourceOrderEntry moduleSourceOrderEntry) {
orderEntryModule = moduleSourceOrderEntry.getOwnerModule();
} else {
ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry) orderEntry;

orderEntryModule = moduleOrderEntry.getModule();
}

if (orderEntryModule != null) {
handleModule(orderEntryModule, customTaglibMappings);
}
} else if (orderEntry instanceof LibraryOrderEntry libraryOrderEntry) {
Library library = libraryOrderEntry.getLibrary();

if (library != null) {
handleLibrary(project, library, customTaglibMappings);
}
}

return true;
});
}

private static void handleModule(@NotNull Module module, @NotNull Map<String, List<? extends FtlVariable>> customTaglibMappings) {
ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);

VirtualFile[] sourceRoots = moduleRootManager.getSourceRoots();

for (VirtualFile contentRoot : sourceRoots) {
VirtualFile taglibMappingsProperties = LiferayFileUtil.getChild(contentRoot, "META-INF/taglib-mappings.properties");

if (taglibMappingsProperties != null) {
try {
Project project = module.getProject();

handleTaglibMappingsProperties(project, customTaglibMappings, taglibMappingsProperties, contentRoot);
} catch (IOException e) {
log.warn("Unable to add " + taglibMappingsProperties.getPath() + " as taglib: " + e.getMessage(), e);
}
}
}
}

private static void handleLibrary(@NotNull Project project, @NotNull Library library, @NotNull Map<String, List<? extends FtlVariable>> customTaglibMappings) {
VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);

for (VirtualFile file : files) {
VirtualFile root = LiferayFileUtil.getJarRoot(file);

if (root != null) {
VirtualFile taglibMappingsProperties = LiferayFileUtil.getChild(root, "META-INF/taglib-mappings.properties");

if (taglibMappingsProperties != null) {
try {
handleTaglibMappingsProperties(project, customTaglibMappings, taglibMappingsProperties, root);
} catch (IOException e) {
log.warn("Unable to add " + taglibMappingsProperties.getPath() + " as taglib: " + e.getMessage(), e);
}
}
}
}
}

private static void handleTaglibMappingsProperties(@NotNull Project project, @NotNull Map<String, List<? extends FtlVariable>> customTaglibMappings, @NotNull VirtualFile taglibMappingsProperties, VirtualFile root) throws IOException {
PropertyResourceBundle resourceBundle = new PropertyResourceBundle(taglibMappingsProperties.getInputStream());

for (Enumeration<String> enumeration = resourceBundle.getKeys(); enumeration.hasMoreElements(); ) {
String key = enumeration.nextElement();

String value = resourceBundle.getString(key);

if (StringUtil.isNotEmpty(value)) {
value = StringUtil.substringAfter(value, "/");

VirtualFile tldFile = LiferayFileUtil.getChild(root, value);

if (tldFile != null) {
if (! customTaglibMappings.containsKey(key)) {
if (log.isDebugEnabled()) {
log.debug("Adding " + tldFile.getPath() + " with taglib prefix " + key);
}

customTaglibMappings.put(key, getTaglibSupportVariables(project, key, tldFile));
}
}
}
}
}

private static List<? extends FtlVariable> getTaglibSupportVariables(Project project, @NonNls final String taglibPrefix, VirtualFile tldFile) {
XmlFile xmlFile = (XmlFile)PsiManager.getInstance(project).findFile(tldFile);

if (xmlFile == null) {
return Collections.emptyList();
}

final XmlDocument document = xmlFile.getDocument();
if (document == null) {
return Collections.emptyList();
}

final XmlNSDescriptor descriptor = (XmlNSDescriptor) document.getMetaData();
if (descriptor == null) {
return Collections.emptyList();
}

PsiElement declaration = descriptor.getDeclaration();
if (declaration == null) {
declaration = xmlFile;
}

return List.of(new CustomFtlVariable(taglibPrefix, declaration, new FtlXmlNamespaceType(descriptor)));
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,22 @@ public List<? extends FtlVariable> getGlobalVariables(FtlFile file) {
}

//Provide Liferay Taglibs as predefined variables in their corresponding Freemarker namespaces
Map<String, List<? extends FtlVariable>> customTaglibMappings = new HashMap<>();

LiferayFreemarkerTaglibsUtil.getCustomTaglibMappings(module, customTaglibMappings);

for (Map.Entry<String, String> taglibMapping : LiferayFreemarkerTaglibs.FTL_TAGLIB_MAPPINGS.entrySet()) {
result.addAll(getTaglibSupportVariables("/com/liferay/tld/" + liferayVersionPrefix + "/" + taglibMapping.getKey(), module, taglibMapping.getValue()));
if (! customTaglibMappings.containsKey(taglibMapping.getValue())) {
if (log.isDebugEnabled()) {
log.debug("Adding /com/liferay/tld/" + liferayVersionPrefix + "/" + taglibMapping.getKey() + " with taglib prefix " + taglibMapping.getValue());
}

customTaglibMappings.put(taglibMapping.getValue(), getTaglibSupportVariables("/com/liferay/tld/" + liferayVersionPrefix + "/" + taglibMapping.getKey(), module, taglibMapping.getValue()));
}
}

for (Map.Entry<String, List<? extends FtlVariable>> entry : customTaglibMappings.entrySet()) {
result.addAll(entry.getValue());
}
}

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
<li>Feature: add documentation lookup and code completion for Liferay specific properties in gradle.properties</li>
<li>Feature: add XPath expression support for Liferay specific helper classes like SAXReaderUtil</li>
<li>Feature: add deprecation checks for Liferay 2024.Q3</li>
<li>Feature: add support for custom taglib mappings for freemarker from third-party libraries</li>
<li>Bugfix: Exception occurred after deleting a fragment.json file</li>
<li>Bugfix: Finally fixed gradle support for projects using Java 16 or below</li>
<li>Bugfix: Detecting servlet context imports in Freemarker files did not work for gradle projects (code completion, freemarker debugger)</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ protected LightProjectDescriptor getProjectDescriptor() {
.themeSettings(LiferayLookAndFeelXmlParser.TEMPLATES_PATH, "/templates")
.themeSettings(LiferayLookAndFeelXmlParser.TEMPLATE_EXTENSION, "ftl")
.library("freemarker-other", TEST_DATA_PATH, "freemarker-other.jar")
.library("my-taglib", TEST_DATA_PATH, "my-taglib.jar")
.build();
}

Expand Down Expand Up @@ -457,6 +458,24 @@ public void testLiferayTaglibs() {
assertTrue(strings.contains("liferay_aui"));
}

public void testCustomTaglibFromDependencies() {
myFixture.configureByFiles("templates/custom_taglib.ftl");
myFixture.complete(CompletionType.BASIC, 1);
List<String> strings = myFixture.getLookupElementStrings();

assertNotNull(strings);
assertTrue(strings.contains("my_taglib"));
}

public void testCustomTaglibFromModule() {
myFixture.configureByFiles("templates/custom_taglib.ftl", "META-INF/taglib-mappings.properties", "META-INF/module-taglib.tld");
myFixture.complete(CompletionType.BASIC, 1);
List<String> strings = myFixture.getLookupElementStrings();

assertNotNull(strings);
assertTrue(strings.contains("module_taglib"));
}

public void testWorkflowDefinitionTemplateContextVariables() {
myFixture.configureByFiles("workflow-definition-freemarker.xml");
myFixture.complete(CompletionType.BASIC, 1);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0"?>

<taglib
version="2.1"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-jsptaglibrary_2_1.xsd"
>
<tlib-version>1.0</tlib-version>
<short-name>module-taglib</short-name>
<uri>http://sample.com/tld/module-taglib</uri>
</taglib>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module_taglib=/META-INF/module-taglib.tld
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<@<caret>

0 comments on commit 817dfc4

Please sign in to comment.