Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@
* @return null
* if nothing was generated.
*/
// TODO apparently unused

Check warning on line 472 in groovy/src/main/java/org/kohsuke/stapler/jelly/groovy/JellyBuilder.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: apparently unused
public Element redirectToDom(Closure c) {
SAXContentHandler sc = new SAXContentHandler();
with(new XMLOutput(sc), c);
Expand Down
81 changes: 81 additions & 0 deletions html/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-parent</artifactId>
<version>${changelist}</version>
</parent>
<artifactId>stapler-html</artifactId>
<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>stapler-jelly</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>5.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>stapler</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-util</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-servlet</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-webapp</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-test-harness-htmlunit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.16</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
20 changes: 20 additions & 0 deletions html/src/main/java/org/kohsuke/stapler/html/HtmlInclude.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.kohsuke.stapler.html;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Denotes that a record field should include another view.
* The field type should be Stapler-dispatchable.
*/
@Target(ElementType.RECORD_COMPONENT)
@Retention(RetentionPolicy.RUNTIME)
public @interface HtmlInclude {
/**
* The name of the view to include.
* No file extension should be used, so for example use {@code index} or {@code config}.
*/
String value();
}
27 changes: 27 additions & 0 deletions html/src/main/java/org/kohsuke/stapler/html/HtmlView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.kohsuke.stapler.html;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;

/**
* A method which renders an HTML view.
* The method must be public, take no arguments, and return a {@link Record}.
* The fields of the record must align with the element {@code id}s
* with names starting with the {@code st.} prefix.
* The following field types are permitted:
* <ul>
* <li>{@link String}, to insert character data.
* <li>{@link boolean}, to conditionally include a static subtree.
* <li>Some other {@link Record} type, to include a nested structure, skipped if {@code null}.
* <li>{@link List} of some {@link Record} type, to repeat a nested structure zero or more times.
* </ul>
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface HtmlView {
/** View name, which should be {@code *.xhtml} base name. */
String value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.kohsuke.stapler.html.impl;

import java.net.URL;
import java.util.logging.Logger;
import org.dom4j.io.SAXReader;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.MetaClassLoader;
import org.kohsuke.stapler.html.HtmlView;

public final class HtmlClassLoaderTearOff {
private static final Logger LOGGER = Logger.getLogger(HtmlClassLoaderTearOff.class.getName());
private final MetaClassLoader owner;
private final SAXReader parser;

public HtmlClassLoaderTearOff(MetaClassLoader owner) throws Exception {
this.owner = owner;
parser = new SAXReader();
parser.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
parser.setFeature("http://xml.org/sax/features/external-general-entities", false);
parser.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
// TODO enable validation against XHTML 1.1 schema when in test mode (-ea)

Check warning on line 21 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlClassLoaderTearOff.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: enable validation against XHTML 1.1 schema when in test mode (-ea)
}

public HtmlJellyScript parse(URL script, MetaClass owner) throws Exception {
LOGGER.info(() -> "TODO parsing " + script + " from " + owner);

Check warning on line 25 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlClassLoaderTearOff.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: parsing " + script + " from " + owner);
Class<?> c = owner.klass.toJavaClass();
var base = c.getProtectionDomain().getCodeSource().getLocation()
+ c.getName().replace('.', '/').replace('$', '/') + "/";
for (var m : c.getMethods()) {
var ann = m.getAnnotation(HtmlView.class);
if (ann == null) {
continue;
}
if (m.getParameterCount() > 0) {
throw new Exception(m + " must not take arguments");
}
if (!m.getReturnType().isRecord()) {
throw new Exception(m + " must return a record");
}
// TODO verify that the Record fields & nesting match st.* DOM structure

Check warning on line 40 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlClassLoaderTearOff.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: verify that the Record fields & nesting match st.* DOM structure
// (or do this all at compile time using an annotation processor)
if (script.toString().equals(base + ann.value() + ".xhtml")) {
return new HtmlJellyScript(m, parser.read(script).getRootElement());
}
}
throw new Exception(c + " does not have a @HtmlView corresponding to " + script);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.kohsuke.stapler.html.impl;

import java.net.URL;
import java.util.logging.Logger;
import org.kohsuke.stapler.AbstractTearOff;
import org.kohsuke.stapler.MetaClass;

public final class HtmlClassTearOff extends AbstractTearOff<HtmlClassLoaderTearOff, HtmlJellyScript, Exception> {
private static final Logger LOGGER = Logger.getLogger(HtmlClassTearOff.class.getName());

public HtmlClassTearOff(MetaClass owner) {
super(owner, HtmlClassLoaderTearOff.class);
LOGGER.fine(() -> "initialized " + owner);
}

@Override
protected String getDefaultScriptExtension() {
return ".xhtml";
}

@Override
public HtmlJellyScript parseScript(URL res) throws Exception {
return classLoader.parse(res, owner);
}
}
84 changes: 84 additions & 0 deletions html/src/main/java/org/kohsuke/stapler/html/impl/HtmlFacet.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.kohsuke.stapler.html.impl;

import static org.kohsuke.stapler.Facet.LOGGER;

import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.jelly.JellyException;
import org.apache.commons.jelly.Script;
import org.kohsuke.MetaInfServices;
import org.kohsuke.stapler.AbstractTearOff;
import org.kohsuke.stapler.Dispatcher;
import org.kohsuke.stapler.Facet;
import org.kohsuke.stapler.MetaClass;
import org.kohsuke.stapler.RequestImpl;
import org.kohsuke.stapler.ResponseImpl;
import org.kohsuke.stapler.jelly.JellyClassTearOff;
import org.kohsuke.stapler.jelly.JellyCompatibleFacet;
import org.kohsuke.stapler.jelly.JellyFacet;
import org.kohsuke.stapler.lang.Klass;

@MetaInfServices(Facet.class)
public final class HtmlFacet extends Facet implements JellyCompatibleFacet {

private static final Logger LOGGER = Logger.getLogger(HtmlFacet.class.getName());

// TODO seems like there could be some default methods in JellyCompatibleFacet to avoid boilerplate

Check warning on line 32 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlFacet.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: seems like there could be some default methods in JellyCompatibleFacet to avoid boilerplate

@Override
public void buildViewDispatchers(MetaClass owner, List<Dispatcher> dispatchers) {
dispatchers.add(createValidatingDispatcher(
owner.loadTearOff(HtmlClassTearOff.class), owner.webApp.getFacet(JellyFacet.class).scriptInvoker));
}

@Override
public RequestDispatcher createRequestDispatcher(RequestImpl request, Klass<?> type, Object it, String viewName)
throws IOException {
// TODO is this actually used?

Check warning on line 43 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlFacet.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: is this actually used?
return createRequestDispatcher(
request.getWebApp().getMetaClass(type).loadTearOff(HtmlClassTearOff.class),
request.getWebApp().getFacet(JellyFacet.class).scriptInvoker,
it,
viewName);
}

@Override
public void buildIndexDispatchers(MetaClass owner, List<Dispatcher> dispatchers) {
try {
if (owner.loadTearOff(JellyClassTearOff.class).findScript("index") != null) {
super.buildIndexDispatchers(owner, dispatchers);
}
} catch (JellyException e) {
LOGGER.log(Level.WARNING, "Failed to parse index.xhtml for " + owner, e);
}
}

@Override
public boolean handleIndexRequest(RequestImpl req, ResponseImpl rsp, Object node, MetaClass nodeMetaClass)
throws IOException, ServletException {
return handleIndexRequest(
nodeMetaClass.loadTearOff(HtmlClassTearOff.class),
req.getWebApp().getFacet(JellyFacet.class).scriptInvoker,
req,
rsp,
node);
}

@Override
public Collection<? extends Class<? extends AbstractTearOff<?, ? extends Script, ?>>> getClassTearOffTypes() {
return Set.of(HtmlClassTearOff.class);
}

@Override
public Collection<String> getScriptExtensions() {
// TODO allow *.html if it can be parsed without external deps

Check warning on line 80 in html/src/main/java/org/kohsuke/stapler/html/impl/HtmlFacet.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: allow *.html if it can be parsed without external deps
// or use *.xml?
return Set.of(".xhtml");
}
}
Loading
Loading