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 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