Jetty web application support for Jakarta EE 10 with full servlet specification compliance
—
The Jetty EE10 WebApp metadata processing system handles comprehensive analysis and resolution of web application descriptors, annotations, and fragment information. It provides a unified view of all configuration sources including web.xml, web-fragment.xml files, and discovered annotations.
The central container for all web application metadata.
public class MetaData {
// Metadata management
public void clear();
// Descriptor management
public void setDefaultsDescriptor(DefaultsDescriptor descriptor)
throws Exception;
public void setWebDescriptor(WebDescriptor descriptor) throws Exception;
public void addOverrideDescriptor(OverrideDescriptor descriptor)
throws Exception;
public void addFragmentDescriptor(Resource jarResource,
FragmentDescriptor descriptor) throws Exception;
// Annotation management
public void addDiscoveredAnnotations(List<DiscoveredAnnotation> annotations);
public void addDiscoveredAnnotation(DiscoveredAnnotation annotation);
// Processor management
public void addDescriptorProcessor(DescriptorProcessor p);
public void removeDescriptorProcessor(DescriptorProcessor p);
// Resolution and validation
public void resolve(WebAppContext context) throws Exception;
public boolean isMetaDataComplete();
public boolean isDistributable();
// Descriptor access
public WebDescriptor getWebDescriptor();
public List<WebDescriptor> getOverrideDescriptors();
public WebDescriptor getDefaultsDescriptor();
// Fragment management
public boolean isOrdered();
public Ordering getOrdering();
public void setOrdering(Ordering o);
public FragmentDescriptor getFragmentDescriptor(String name);
public Resource getJarForFragmentName(String name);
public Map<String, FragmentDescriptor> getNamedFragmentDescriptors();
// Origin tracking
public Origin getOrigin(String name);
public OriginInfo getOriginInfo(String name);
public void setOrigin(String name, Descriptor d);
public void setOrigin(String name, Annotation annotation, Class<?> annotated);
public void setOriginAPI(String name);
// Resource management
public List<Resource> getWebInfResources(boolean withOrdering);
public List<Resource> getContainerResources();
// Configuration properties
public boolean isAllowDuplicateFragmentNames();
public void setAllowDuplicateFragmentNames(boolean allowDuplicateFragmentNames);
public boolean isValidateXml();
public void setValidateXml(boolean validateXml);
}Base class for all XML descriptor parsing.
public abstract class Descriptor {
// Constructor
public Descriptor(Resource resource);
// Parsing
public void parse(XmlParser parser) throws Exception;
// Access methods
public String getURI();
public Resource getResource();
public XmlParser.Node getRoot();
}Handles web.xml, web-defaults.xml, and web-overrides.xml parsing.
public class WebDescriptor extends Descriptor {
// Static utility methods
public static boolean isMetaDataComplete(WebDescriptor d);
public static XmlParser getParser(boolean validating);
public static XmlParser newParser(boolean validating);
// Metadata properties
public MetaData.Complete getMetaDataComplete();
public int getMajorVersion();
public int getMinorVersion();
// Class name management
public void addClassName(String className);
public ArrayList<String> getClassNames();
// Deployment properties
public boolean isDistributable();
// Ordering information
public boolean isOrdered();
public List<String> getOrdering();
}public class DefaultsDescriptor extends WebDescriptor {
// Handles web-defaults.xml parsing
}
public class OverrideDescriptor extends WebDescriptor {
// Handles web-overrides.xml parsing
}
public class FragmentDescriptor extends WebDescriptor {
// Handles web-fragment.xml parsing from JAR files
}public interface DescriptorProcessor {
void process(WebAppContext context, Descriptor descriptor) throws Exception;
}Processes standard servlet specification descriptors.
public class StandardDescriptorProcessor extends IterativeDescriptorProcessor {
// Processes web.xml, web-defaults.xml, web-overrides.xml,
// and web-fragment.xml descriptors
}Base class for iterative descriptor processing.
public abstract class IterativeDescriptorProcessor
implements DescriptorProcessor {
// Base functionality for iterating through descriptor elements
}Represents annotations discovered during classpath scanning.
public abstract class DiscoveredAnnotation {
// Constructors
public DiscoveredAnnotation(WebAppContext context, String className);
public DiscoveredAnnotation(WebAppContext context, String className,
Resource resource);
// Core method - must be implemented by subclasses
public abstract void apply();
// Access methods
public String getClassName();
public Resource getResource();
public Class<?> getTargetClass();
}public interface Ordering {
List<Resource> order(List<Resource> fragments);
}public class AbsoluteOrdering implements Ordering {
// Implements absolute ordering for fragments
public List<Resource> order(List<Resource> fragments);
}
public class RelativeOrdering implements Ordering {
// Implements relative ordering for fragments
public List<Resource> order(List<Resource> fragments);
}public enum Origin {
NotSet, WebXml, WebDefaults, WebOverride,
WebFragment, Annotation, API;
public static Origin of(Object o);
}public static class MetaData.OriginInfo {
// Contains detailed information about where metadata originated
}public static final String VALIDATE_XML =
"org.eclipse.jetty.ee10.webapp.validateXml";
public static final String ORDERED_LIBS =
"org.eclipse.jetty.ee10.webapp.orderedLibs";public enum MetaData.Complete {
NotSet, True, False
}import org.eclipse.jetty.ee10.webapp.*;
WebAppContext context = new WebAppContext();
MetaData metaData = new MetaData();
// Set defaults descriptor
DefaultsDescriptor defaults = new DefaultsDescriptor(
ResourceFactory.of(context).newResource("webdefault.xml"));
defaults.parse(XmlParser.getParser(true));
metaData.setDefaultsDescriptor(defaults);
// Set main web descriptor
WebDescriptor webXml = new WebDescriptor(
context.getWebInf().resolve("web.xml"));
webXml.parse(XmlParser.getParser(true));
metaData.setWebDescriptor(webXml);
context.setMetaData(metaData);import org.eclipse.jetty.ee10.webapp.*;
MetaData metaData = context.getMetaData();
// Add fragments from JAR files
Resource jarResource = ResourceFactory.of(context).newResource("lib/fragment.jar");
FragmentDescriptor fragment = new FragmentDescriptor(
jarResource.resolve("META-INF/web-fragment.xml"));
fragment.parse(XmlParser.getParser(true));
metaData.addFragmentDescriptor(jarResource, fragment);
// Set ordering if specified
if (metaData.isOrdered()) {
AbsoluteOrdering ordering = new AbsoluteOrdering();
metaData.setOrdering(ordering);
}import org.eclipse.jetty.ee10.webapp.*;
// Custom annotation implementation
public class WebServletAnnotation extends DiscoveredAnnotation {
private WebServlet annotation;
public WebServletAnnotation(WebAppContext context, String className,
Resource resource) {
super(context, className, resource);
// Extract annotation details
}
@Override
public void apply() {
// Apply the @WebServlet annotation to context
ServletHolder holder = new ServletHolder();
holder.setName(annotation.name());
holder.setClassName(getClassName());
// Add servlet mapping
for (String pattern : annotation.urlPatterns()) {
context.getServletHandler().addServletWithMapping(holder, pattern);
}
}
}
// Add discovered annotations
MetaData metaData = context.getMetaData();
List<DiscoveredAnnotation> annotations = new ArrayList<>();
annotations.add(new WebServletAnnotation(context, "com.example.MyServlet",
ResourceFactory.of(context).newResource("classes/com/example/MyServlet.class")));
metaData.addDiscoveredAnnotations(annotations);import org.eclipse.jetty.ee10.webapp.*;
public class CustomDescriptorProcessor implements DescriptorProcessor {
@Override
public void process(WebAppContext context, Descriptor descriptor)
throws Exception {
if (descriptor instanceof WebDescriptor) {
WebDescriptor webDesc = (WebDescriptor) descriptor;
XmlParser.Node root = webDesc.getRoot();
// Process custom elements
XmlParser.Node[] customNodes = root.getNodes("custom-config");
for (XmlParser.Node node : customNodes) {
String value = node.toString();
context.setAttribute("custom.config", value);
}
}
}
}
// Add custom processor
MetaData metaData = context.getMetaData();
metaData.addDescriptorProcessor(new CustomDescriptorProcessor());import org.eclipse.jetty.ee10.webapp.*;
MetaData metaData = context.getMetaData();
// Track origin of configuration elements
metaData.setOrigin("javax.servlet.Servlet", webXmlDescriptor);
metaData.setOrigin("com.example.MyServlet", annotation, MyServlet.class);
metaData.setOriginAPI("ServletContext.addServlet");
// Query origins
Origin servletOrigin = metaData.getOrigin("javax.servlet.Servlet");
OriginInfo info = metaData.getOriginInfo("com.example.MyServlet");
System.out.println("Servlet configuration came from: " + servletOrigin);import org.eclipse.jetty.ee10.webapp.*;
WebAppContext context = new WebAppContext();
MetaData metaData = context.getMetaData();
// Enable XML validation
metaData.setValidateXml(true);
// Allow duplicate fragment names if needed
metaData.setAllowDuplicateFragmentNames(false);
try {
// Resolve all metadata
metaData.resolve(context);
// Check completion status
if (metaData.isMetaDataComplete()) {
System.out.println("Metadata is complete - no annotation scanning needed");
} else {
System.out.println("Metadata incomplete - annotation scanning required");
}
// Check distributability
if (metaData.isDistributable()) {
System.out.println("Web application is distributable");
}
} catch (Exception e) {
System.err.println("Metadata resolution failed: " + e.getMessage());
}import org.eclipse.jetty.ee10.webapp.*;
MetaData metaData = context.getMetaData();
// Get WEB-INF resources with ordering
List<Resource> orderedResources = metaData.getWebInfResources(true);
List<Resource> containerResources = metaData.getContainerResources();
// Access fragment information
Map<String, FragmentDescriptor> fragments =
metaData.getNamedFragmentDescriptors();
for (Map.Entry<String, FragmentDescriptor> entry : fragments.entrySet()) {
String name = entry.getKey();
FragmentDescriptor fragment = entry.getValue();
Resource jar = metaData.getJarForFragmentName(name);
System.out.println("Fragment " + name + " from " + jar.getURI());
}import org.eclipse.jetty.ee10.webapp.*;
public class CustomOrdering implements Ordering {
private List<String> beforeOthers;
private List<String> afterOthers;
@Override
public List<Resource> order(List<Resource> fragments) {
// Custom ordering logic
List<Resource> ordered = new ArrayList<>(fragments);
// Sort based on custom criteria
ordered.sort((r1, r2) -> {
// Compare fragment names, priorities, etc.
return getFragmentName(r1).compareTo(getFragmentName(r2));
});
return ordered;
}
private String getFragmentName(Resource resource) {
// Extract fragment name from resource
return resource.getFileName();
}
}
// Use custom ordering
MetaData metaData = context.getMetaData();
metaData.setOrdering(new CustomOrdering());Metadata processing can encounter various exceptions:
Exception - General parsing or processing errorsIOException - I/O errors when reading descriptor filesSAXException - XML parsing errors in descriptorsClassNotFoundException - Missing classes referenced in annotationsIllegalStateException - Invalid metadata state during resolutionCommon error scenarios:
// Enable container metadata caching for better performance
System.setProperty(MetaInfConfiguration.USE_CONTAINER_METAINF_CACHE, "true");// Disable XML validation for faster parsing (development only)
MetaData metaData = context.getMetaData();
metaData.setValidateXml(false);// Allow duplicate fragment names to reduce validation overhead
MetaData metaData = context.getMetaData();
metaData.setAllowDuplicateFragmentNames(true);Install with Tessl CLI
npx tessl i tessl/maven-org-eclipse-jetty-ee10--jetty-ee10-webapp