diff --git a/api-tld/pom.xml b/api-tld/pom.xml
new file mode 100644
index 00000000..2acb5dbc
--- /dev/null
+++ b/api-tld/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+ io.neba.neba-api-tld
+ jar
+ NEBA API TLD
+
+
+ Annotations and annotation processor for generating JSP Tag Library Descriptors (TLD).
+
+
+
+ io.neba
+ io.neba.neba-parent
+ 5.2.4-SNAPSHOT
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ none
+
+
+
+
+
+
+
+
+
diff --git a/api-tld/src/main/java/io/neba/api/tags/Tag.java b/api-tld/src/main/java/io/neba/api/tags/Tag.java
new file mode 100644
index 00000000..de0cda26
--- /dev/null
+++ b/api-tld/src/main/java/io/neba/api/tags/Tag.java
@@ -0,0 +1,38 @@
+/*
+ Copyright 2013 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 the "License";
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package io.neba.api.tags;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares a JSP tag. Used on {@link javax.servlet.jsp.tagext.TagSupport} subclasses
+ * to generate the tag metadata in the TLD.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.TYPE)
+public @interface Tag {
+
+ /**
+ * Description of the tag.
+ */
+ String description();
+}
diff --git a/api-tld/src/main/java/io/neba/api/tags/TagAttribute.java b/api-tld/src/main/java/io/neba/api/tags/TagAttribute.java
new file mode 100644
index 00000000..0635b444
--- /dev/null
+++ b/api-tld/src/main/java/io/neba/api/tags/TagAttribute.java
@@ -0,0 +1,43 @@
+/*
+ Copyright 2013 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 the "License";
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package io.neba.api.tags;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares a JSP tag attribute. Used on setter methods of tag classes
+ * to generate the attribute metadata in the TLD.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.METHOD)
+public @interface TagAttribute {
+
+ /**
+ * Description of the attribute.
+ */
+ String description();
+
+ /**
+ * Whether the attribute supports runtime expressions (rtexprvalue).
+ */
+ boolean runtimeValueAllowed() default false;
+}
diff --git a/api-tld/src/main/java/io/neba/api/tags/TagLibrary.java b/api-tld/src/main/java/io/neba/api/tags/TagLibrary.java
new file mode 100644
index 00000000..969b6c61
--- /dev/null
+++ b/api-tld/src/main/java/io/neba/api/tags/TagLibrary.java
@@ -0,0 +1,52 @@
+/*
+ Copyright 2013 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 the "License";
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package io.neba.api.tags;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Declares a JSP tag library. Used on package-info.java to generate the TLD descriptor.
+ */
+@Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target(ElementType.PACKAGE)
+public @interface TagLibrary {
+
+ /**
+ * The tag library URI (e.g. {@code http://neba.io/1.0}).
+ */
+ String value();
+
+ /**
+ * The output TLD filename (e.g. {@code neba.tld}).
+ */
+ String descriptorFile();
+
+ /**
+ * Short name for the tag library (e.g. {@code neba}).
+ */
+ String shortName();
+
+ /**
+ * Description of the tag library.
+ */
+ String description();
+}
diff --git a/api-tld/src/main/java/io/neba/api/tags/processor/TldProcessor.java b/api-tld/src/main/java/io/neba/api/tags/processor/TldProcessor.java
new file mode 100644
index 00000000..97ec6a75
--- /dev/null
+++ b/api-tld/src/main/java/io/neba/api/tags/processor/TldProcessor.java
@@ -0,0 +1,237 @@
+/*
+ Copyright 2013 the original author or authors.
+
+ Licensed under the Apache License, Version 2.0 the "License";
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+package io.neba.api.tags.processor;
+
+import io.neba.api.tags.Tag;
+import io.neba.api.tags.TagAttribute;
+import io.neba.api.tags.TagLibrary;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.ExecutableElement;
+import javax.lang.model.element.PackageElement;
+import javax.lang.model.element.TypeElement;
+import javax.tools.Diagnostic;
+import javax.tools.FileObject;
+import javax.tools.StandardLocation;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Annotation processor that generates TLD (Tag Library Descriptor) files from
+ * {@link TagLibrary}, {@link Tag}, and {@link TagAttribute} annotations.
+ */
+@SupportedAnnotationTypes({
+ "io.neba.api.tags.TagLibrary",
+ "io.neba.api.tags.Tag",
+ "io.neba.api.tags.TagAttribute"
+})
+@SupportedSourceVersion(SourceVersion.RELEASE_8)
+public class TldProcessor extends AbstractProcessor {
+
+ @Override
+ public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
+ if (roundEnv.processingOver()) {
+ return false;
+ }
+
+ TagLibraryInfo libraryInfo = null;
+ for (Element element : roundEnv.getElementsAnnotatedWith(TagLibrary.class)) {
+ if (element.getKind() == ElementKind.PACKAGE) {
+ PackageElement pkg = (PackageElement) element;
+ TagLibrary ann = pkg.getAnnotation(TagLibrary.class);
+ if (ann != null) {
+ libraryInfo = new TagLibraryInfo(
+ ann.value(),
+ ann.descriptorFile(),
+ ann.shortName(),
+ ann.description()
+ );
+ break;
+ }
+ }
+ }
+
+ if (libraryInfo == null) {
+ return false;
+ }
+
+ List tags = new ArrayList<>();
+ for (Element element : roundEnv.getElementsAnnotatedWith(Tag.class)) {
+ if (element.getKind() == ElementKind.CLASS) {
+ TypeElement type = (TypeElement) element;
+ Tag ann = type.getAnnotation(Tag.class);
+ if (ann != null) {
+ String tagName = deriveTagName(type.getSimpleName().toString());
+ List attributes = new ArrayList<>();
+ for (Element member : type.getEnclosedElements()) {
+ if (member.getKind() == ElementKind.METHOD) {
+ TagAttribute attrAnn = member.getAnnotation(TagAttribute.class);
+ if (attrAnn != null) {
+ String attrName = deriveAttributeName(((ExecutableElement) member).getSimpleName().toString());
+ attributes.add(new AttributeInfo(attrName, attrAnn.description(), attrAnn.runtimeValueAllowed()));
+ }
+ }
+ }
+ tags.add(new TagInfo(
+ tagName,
+ type.getQualifiedName().toString(),
+ ann.description(),
+ attributes
+ ));
+ }
+ }
+ }
+
+ try {
+ writeTld(libraryInfo, tags);
+ } catch (IOException e) {
+ processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Failed to write TLD: " + e.getMessage());
+ }
+
+ return true;
+ }
+
+ private static String deriveTagName(String className) {
+ if (className.endsWith("Tag")) {
+ String base = className.substring(0, className.length() - 3);
+ return base.substring(0, 1).toLowerCase() + base.substring(1);
+ }
+ return className.substring(0, 1).toLowerCase() + className.substring(1);
+ }
+
+ private static String deriveAttributeName(String setterName) {
+ if (setterName.startsWith("set") && setterName.length() > 3) {
+ String base = setterName.substring(3);
+ return base.substring(0, 1).toLowerCase() + base.substring(1);
+ }
+ return setterName;
+ }
+
+ private void writeTld(TagLibraryInfo library, List tags) throws IOException {
+ FileObject file = processingEnv.getFiler().createResource(
+ StandardLocation.CLASS_OUTPUT,
+ "",
+ "META-INF/" + library.descriptorFile
+ );
+
+ try (Writer writer = file.openWriter()) {
+ writer.write("\n");
+ writer.write("\n");
+ writer.write(" ");
+ writer.write(escape(library.description));
+ writer.write("\n");
+ writer.write(" 1.0\n");
+ writer.write(" ");
+ writer.write(escape(library.shortName));
+ writer.write("\n");
+ writer.write(" ");
+ writer.write(escape(library.uri));
+ writer.write("\n");
+
+ for (TagInfo tag : tags) {
+ writer.write(" \n");
+ writer.write(" ");
+ writer.write(escape(tag.name));
+ writer.write("\n");
+ writer.write(" ");
+ writer.write(escape(tag.tagClass));
+ writer.write("\n");
+ writer.write(" ");
+ writer.write(escape(tag.description));
+ writer.write("\n");
+ for (AttributeInfo attr : tag.attributes) {
+ writer.write(" \n");
+ writer.write(" ");
+ writer.write(escape(attr.name));
+ writer.write("\n");
+ writer.write(" false\n");
+ writer.write(" ");
+ writer.write(attr.runtimeValueAllowed ? "true" : "false");
+ writer.write("\n");
+ writer.write(" ");
+ writer.write(escape(attr.description));
+ writer.write("\n");
+ writer.write(" \n");
+ }
+ writer.write(" \n");
+ }
+
+ writer.write("\n");
+ }
+ }
+
+ private static String escape(String s) {
+ if (s == null) return "";
+ return s.replace("&", "&")
+ .replace("<", "<")
+ .replace(">", ">")
+ .replace("\"", """)
+ .replace("'", "'");
+ }
+
+ private static final class TagLibraryInfo {
+ final String uri;
+ final String descriptorFile;
+ final String shortName;
+ final String description;
+
+ TagLibraryInfo(String uri, String descriptorFile, String shortName, String description) {
+ this.uri = uri;
+ this.descriptorFile = descriptorFile;
+ this.shortName = shortName;
+ this.description = description;
+ }
+ }
+
+ private static final class TagInfo {
+ final String name;
+ final String tagClass;
+ final String description;
+ final List attributes;
+
+ TagInfo(String name, String tagClass, String description, List attributes) {
+ this.name = name;
+ this.tagClass = tagClass;
+ this.description = description;
+ this.attributes = attributes;
+ }
+ }
+
+ private static final class AttributeInfo {
+ final String name;
+ final String description;
+ final boolean runtimeValueAllowed;
+
+ AttributeInfo(String name, String description, boolean runtimeValueAllowed) {
+ this.name = name;
+ this.description = description;
+ this.runtimeValueAllowed = runtimeValueAllowed;
+ }
+ }
+}
diff --git a/api-tld/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/api-tld/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 00000000..9bfa2514
--- /dev/null
+++ b/api-tld/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -0,0 +1 @@
+io.neba.api.tags.processor.TldProcessor
diff --git a/api/pom.xml b/api/pom.xml
index dbf0240d..3ae8eb4f 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -48,6 +48,19 @@
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+
+ io.neba
+ io.neba.neba-api-tld
+ ${project.version}
+
+
+
+
org.apache.maven.plugins
maven-checkstyle-plugin
@@ -60,6 +73,12 @@
+
+ io.neba
+ io.neba.neba-api-tld
+ ${project.version}
+ provided
+
javax.servlet.jsp
jsp-api
@@ -88,10 +107,6 @@
org.slf4j
slf4j-simple
-
- com.google.code.tld-generator
- tld-generator
-
org.apache.commons
commons-lang3
diff --git a/api/src/main/java/io/neba/api/tags/DefineObjectsTag.java b/api/src/main/java/io/neba/api/tags/DefineObjectsTag.java
index 77ee3acf..4c2b1834 100644
--- a/api/src/main/java/io/neba/api/tags/DefineObjectsTag.java
+++ b/api/src/main/java/io/neba/api/tags/DefineObjectsTag.java
@@ -21,10 +21,8 @@
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.api.scripting.SlingScriptHelper;
-import tldgen.Tag;
-import tldgen.TagAttribute;
-
import javax.annotation.CheckForNull;
+
import javax.servlet.jsp.tagext.TagSupport;
import static io.neba.api.Constants.MODEL;
diff --git a/api/src/main/java/io/neba/api/tags/package-info.java b/api/src/main/java/io/neba/api/tags/package-info.java
index 7a6007c8..d3a30f2b 100644
--- a/api/src/main/java/io/neba/api/tags/package-info.java
+++ b/api/src/main/java/io/neba/api/tags/package-info.java
@@ -28,5 +28,3 @@
description = "NEBA tag library"
)
package io.neba.api.tags;
-
-import tldgen.TagLibrary;
diff --git a/core/pom.xml b/core/pom.xml
index cc0616e0..95da08c5 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -46,6 +46,10 @@
javax.servlet
javax.servlet-api
+
+ javax.annotation
+ javax.annotation-api
+
org.apache.commons
commons-lang3
diff --git a/core/src/test/java/io/neba/core/resourcemodels/mapping/FieldValueMappingCallbackTest.java b/core/src/test/java/io/neba/core/resourcemodels/mapping/FieldValueMappingCallbackTest.java
index de94df60..60f262cc 100644
--- a/core/src/test/java/io/neba/core/resourcemodels/mapping/FieldValueMappingCallbackTest.java
+++ b/core/src/test/java/io/neba/core/resourcemodels/mapping/FieldValueMappingCallbackTest.java
@@ -1378,7 +1378,7 @@ public void testNullNonCollectionValuesAreNullWhenInvokingFieldMappers() {
/**
* A {@link AnnotatedFieldMapper} implementation must take
* care to return an assignment-compatible value as a mapping result. However,
- * there is no way to enforce this at compile time. This test verifies that a suitable exception
+ * there are no enforce this at compile time. This test verifies that a suitable exception
* is thrown in case a field mapper returns an incompatible value at runtime.
*/
@Test
diff --git a/core/src/test/java/io/neba/core/resourcemodels/metadata/ModelStatisticsConsolePluginTest.java b/core/src/test/java/io/neba/core/resourcemodels/metadata/ModelStatisticsConsolePluginTest.java
index bf701b0c..c4c43804 100644
--- a/core/src/test/java/io/neba/core/resourcemodels/metadata/ModelStatisticsConsolePluginTest.java
+++ b/core/src/test/java/io/neba/core/resourcemodels/metadata/ModelStatisticsConsolePluginTest.java
@@ -17,6 +17,7 @@
package io.neba.core.resourcemodels.metadata;
import org.apache.commons.io.IOUtils;
+import org.apache.felix.webconsole.servlet.RequestVariableResolver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,6 +77,11 @@ public void setUp() throws Exception {
doReturn(this.metadataList)
.when(this.registrar)
.get();
+
+ RequestVariableResolver variableResolver = new RequestVariableResolver();
+ variableResolver.put(RequestVariableResolver.KEY_APP_ROOT, "/system/console");
+ variableResolver.put(RequestVariableResolver.KEY_PLUGIN_ROOT, "/system/console/" + ModelStatisticsConsolePlugin.LABEL);
+ doReturn(variableResolver).when(this.request).getAttribute(RequestVariableResolver.REQUEST_ATTRIBUTE);
}
@Test
diff --git a/pom.xml b/pom.xml
index 6cfbeca1..d4d21b54 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,7 @@
https://neba.io
+ api-tld
api
core
spring
@@ -114,6 +115,12 @@
4.0.0
provided
+
+ javax.annotation
+ javax.annotation-api
+ 1.3.2
+ provided
+
javax.servlet.jsp
jsp-api
@@ -165,7 +172,7 @@
org.apache.commons
commons-lang3
- 3.2.1
+ 3.18.0
provided
@@ -189,7 +196,7 @@
commons-fileupload
commons-fileupload
- 1.5
+ 1.6.0
provided
@@ -308,7 +315,7 @@
org.apache.felix
org.apache.felix.webconsole
- 4.2.2
+ 4.9.10
provided
@@ -335,23 +342,6 @@
3.4.2
-
- com.google.code.tld-generator
- tld-generator
- 1.1
- provided
- true
-
-
- javax.servlet-api
- javax.servlet
-
-
- javax.servlet.jsp-api
- javax.servlet.jsp
-
-
-
com.google.code.findbugs
jsr305
@@ -399,7 +389,7 @@
org.assertj
assertj-core
- 3.14.0
+ 3.27.7
test
diff --git a/spring/pom.xml b/spring/pom.xml
index 58f2a7ad..eeca312c 100644
--- a/spring/pom.xml
+++ b/spring/pom.xml
@@ -85,6 +85,10 @@
javax.servlet
javax.servlet-api
+
+ javax.annotation
+ javax.annotation-api
+
org.apache.sling
org.apache.sling.bgservlets