Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to specify Servlets in web.xml (Issue #55) #507

Merged
merged 1 commit into from Dec 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -20,6 +20,7 @@ Import-Package: com.ibm.commons.util,
javax.faces.context,
javax.servlet;version="2.5.0",
javax.servlet.http;version="2.5.0",
org.apache.tomcat.util.descriptor.web;version="9.0.84",
org.openntf.xsp.cdi;version="2.5.0",
org.osgi.framework;version="1.8.0"
Export-Package: org.openntf.xsp.jakarta.servlet.webapp;version="2.15.0"
Expand Up @@ -16,11 +16,15 @@
package org.openntf.xsp.jakarta.servlet.nsf;

import java.io.UncheckedIOException;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.servlet.ServletException;

Expand All @@ -29,13 +33,16 @@
import jakarta.servlet.annotation.WebInitParam;
import jakarta.servlet.annotation.WebServlet;

import org.apache.tomcat.util.descriptor.web.ServletDef;
import org.apache.tomcat.util.descriptor.web.WebXml;
import org.openntf.xsp.cdi.CDILibrary;
import org.openntf.xsp.jakarta.servlet.ServletLibrary;
import org.openntf.xsp.jakartaee.servlet.ServletUtil;
import org.openntf.xsp.jakartaee.util.LibraryUtil;
import org.openntf.xsp.jakartaee.util.ModuleUtil;

import com.ibm.commons.util.PathUtil;
import com.ibm.commons.util.StringUtil;
import com.ibm.designer.runtime.domino.adapter.ComponentModule;
import com.ibm.designer.runtime.domino.adapter.IServletFactory;
import com.ibm.designer.runtime.domino.adapter.ServletMatch;
Expand All @@ -47,8 +54,10 @@
* @since 2.5.0
*/
public class ServletServletFactory implements IServletFactory {
private static final Logger log = Logger.getLogger(ServletServletFactory.class.getName());

private ComponentModule module;
private Map<WebServlet, Class<? extends Servlet>> servletClasses;
private Map<ServletInfo, Class<? extends Servlet>> servletClasses;
private Map<Class<? extends Servlet>, javax.servlet.Servlet> servlets;
private long lastUpdate;

Expand All @@ -61,7 +70,7 @@ public void init(ComponentModule module) {
public final ServletMatch getServletMatch(String contextPath, String path) throws javax.servlet.ServletException {
try {
if(LibraryUtil.usesLibrary(ServletLibrary.LIBRARY_ID, module)) {
for(Map.Entry<WebServlet, Class<? extends Servlet>> entry : getModuleServlets().entrySet()) {
for(Map.Entry<ServletInfo, Class<? extends Servlet>> entry : getModuleServlets().entrySet()) {
String match = matches(entry.getKey(), path);
if(match != null) {
javax.servlet.Servlet servlet = getExecutorServlet(entry.getKey(), entry.getValue());
Expand All @@ -77,18 +86,15 @@ public final ServletMatch getServletMatch(String contextPath, String path) throw
return null;
}

private String matches(WebServlet mapping, String path) {
private String matches(ServletInfo mapping, String path) {
// Context path is like /some/db.nsf
// Path is like /xsp/someservlet (no query string)

if(path == null || path.length() < 5) {
return null;
}

String[] patterns = mapping.value();
if(patterns == null || patterns.length == 0) {
patterns = mapping.urlPatterns();
}
List<String> patterns = mapping.patterns;

if(patterns != null) {
for(String pattern : patterns) {
Expand Down Expand Up @@ -142,7 +148,7 @@ private String findPathInfo(String path, String pattern) {
return null;
}

private javax.servlet.Servlet getExecutorServlet(WebServlet mapping, Class<? extends Servlet> c) {
private javax.servlet.Servlet getExecutorServlet(ServletInfo mapping, Class<? extends Servlet> c) {
checkInvalidate();
return this.servlets.computeIfAbsent(c, key -> {
try {
Expand All @@ -154,31 +160,52 @@ private javax.servlet.Servlet getExecutorServlet(WebServlet mapping, Class<? ext
}
Servlet wrapper = new XspServletWrapper(module, delegate);

Map<String, String> params = Arrays.stream(mapping.initParams())
.collect(Collectors.toMap(
WebInitParam::name,
WebInitParam::value
));
Map<String, String> params = mapping.def.getParameterMap();

return module.createServlet(ServletUtil.newToOld(wrapper), mapping.name(), params);
return module.createServlet(ServletUtil.newToOld(wrapper), mapping.def.getServletName(), params);
} catch (InstantiationException | IllegalAccessException | ServletException e) {
throw new RuntimeException(e);
}
});
}

@SuppressWarnings("unchecked")
private synchronized Map<WebServlet, Class<? extends Servlet>> getModuleServlets() {
private synchronized Map<ServletInfo, Class<? extends Servlet>> getModuleServlets() {
checkInvalidate();

if(this.servletClasses == null) {
this.servletClasses = ModuleUtil.getClasses(this.module)
Map<ServletInfo, Class<? extends Servlet>> result = new HashMap<>();
ModuleUtil.getClasses(this.module)
.filter(c -> c.isAnnotationPresent(WebServlet.class))
.filter(Servlet.class::isAssignableFrom)
.collect(Collectors.toMap(
c -> c.getAnnotation(WebServlet.class),
c -> (Class<? extends Servlet>)c
));
.forEach(c -> result.put(toServletDef(c), (Class<? extends Servlet>)c));

ClassLoader cl = module.getModuleClassLoader();
if(cl != null) {
WebXml webXml = ServletUtil.getWebXml(module);

// mappings is pattern -> name, so reverse for our needs
Map<String, String> mappings = webXml.getServletMappings();
Map<String, List<String>> nameToPattern = new HashMap<>();
mappings.forEach((pattern, name) -> nameToPattern.computeIfAbsent(name, k -> new ArrayList<>()).add(pattern));

Map<String, ServletDef> defs = webXml.getServlets();
nameToPattern.forEach((name, patterns) -> {
ServletInfo info = new ServletInfo();
info.def = defs.get(name);
info.patterns = patterns;

try {
result.put(info, (Class<? extends Servlet>)cl.loadClass(info.def.getServletClass()));
} catch (ClassNotFoundException e) {
if(log.isLoggable(Level.SEVERE)) {
log.log(Level.SEVERE, MessageFormat.format("Encountered exception loading Servlet class {0}", info.def.getServletClass()), e);
}
}
});
}

this.servletClasses = result;
}
return this.servletClasses;
}
Expand All @@ -192,5 +219,45 @@ private void checkInvalidate() {
this.servletClasses = null;
}
}

private ServletInfo toServletDef(Class<?> c) {
WebServlet annotation = c.getAnnotation(WebServlet.class);

ServletDef def = new ServletDef();

def.setAsyncSupported(Boolean.toString(annotation.asyncSupported()));
def.setDescription(annotation.description());
def.setDisplayName(annotation.displayName());
def.setEnabled(Boolean.toString(true));
def.setLargeIcon(annotation.largeIcon());
def.setLoadOnStartup(Integer.toString(annotation.loadOnStartup()));
def.setSmallIcon(annotation.smallIcon());
def.setServletClass(c.getName());

String name = annotation.name();
if(StringUtil.isEmpty(name)) {
name = c.getName();
}
def.setServletName(name);


for(WebInitParam param : annotation.initParams()) {
def.addInitParameter(param.name(), param.value());
}

ServletInfo info = new ServletInfo();
info.def = def;
String[] patterns = annotation.value();
if(patterns == null || patterns.length == 0) {
patterns = annotation.urlPatterns();
}
info.patterns = new ArrayList<>(Arrays.asList(patterns));
return info;
}

private static class ServletInfo {
private ServletDef def;
private List<String> patterns;
}

}
Expand Up @@ -25,6 +25,7 @@
import java.util.Enumeration;
import java.util.EventListener;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

Expand Down Expand Up @@ -55,6 +56,8 @@
public enum ServletUtil {
;

public static final String KEY_WEBXML = ServletUtil.class.getPackage().getName() + "_webXml"; //$NON-NLS-1$

private static final Logger log = Logger.getLogger(ServletUtil.class.getName());

public static javax.servlet.Servlet newToOld(jakarta.servlet.Servlet servlet) {
Expand Down Expand Up @@ -507,48 +510,65 @@ public static boolean isClosedConnection(Throwable t) {
*/
public static void populateWebXmlParams(ComponentModule module, jakarta.servlet.ServletContext context) {
if(module != null) {
WebXml result = AccessController.doPrivileged((PrivilegedAction<WebXml>)() -> {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ServletUtil.class.getClassLoader());
WebXml webXml = new WebXml();
try {
WebXmlParser parser = new WebXmlParser(false, false, false);
WebXml result = getWebXml(module);
// Now populate resolved params
result.getContextParams().forEach(context::setInitParameter);
}
}

/**
* Process any WEB-INF/web.xml and META-INF/web-fragment.xml files in the module
* into a deployment descriptor.
*
* @param module the module to load resources from
* @return a {@link WebXml} descriptor object for the module
* @since 2.15.0
*/
public static WebXml getWebXml(ComponentModule module) {
if(module == null) {
return new WebXml();
}

Map<String, Object> attrs = module.getAttributes();
return (WebXml)attrs.computeIfAbsent(KEY_WEBXML, key -> AccessController.doPrivileged((PrivilegedAction<WebXml>)() -> {
ClassLoader tccl = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(ServletUtil.class.getClassLoader());
WebXml webXml = new WebXml();
try {
WebXmlParser parser = new WebXmlParser(false, false, false);

InputStream is = module.getResourceAsStream("/WEB-INF/web.xml"); //$NON-NLS-1$
if(is != null) {
if(log.isLoggable(Level.FINE)) {
log.fine(MessageFormat.format("Processing web.xml in {0}", module.getModuleName()));
}
InputSource source = new InputSource(is);
parser.parseWebXml(source, webXml, false);
}

InputStream is = module.getResourceAsStream("/WEB-INF/web.xml"); //$NON-NLS-1$
if(is != null) {
// Look for META-INF/web-fragment.xml files
ClassLoader cl = module.getModuleClassLoader();
if(cl != null) {
Enumeration<URL> fragments = cl.getResources("META-INF/web-fragment.xml"); //$NON-NLS-1$
for(URL url : Collections.list(fragments)) {
if(log.isLoggable(Level.FINE)) {
log.fine(MessageFormat.format("Processing web.xml in {0}", module.getModuleName()));
log.fine(MessageFormat.format("Processing web-fragment.xml {0} in {1}", url, module.getModuleName()));
}
InputSource source = new InputSource(is);
parser.parseWebXml(source, webXml, false);
}

// Look for META-INF/web-fragment.xml files
ClassLoader cl = module.getModuleClassLoader();
if(cl != null) {
Enumeration<URL> fragments = cl.getResources("META-INF/web-fragment.xml"); //$NON-NLS-1$
for(URL url : Collections.list(fragments)) {
if(log.isLoggable(Level.FINE)) {
log.fine(MessageFormat.format("Processing web-fragment.xml {0} in {1}", url, module.getModuleName()));
}
InputStream fis = url.openStream();
if(fis != null) {
InputSource source = new InputSource(fis);
parser.parseWebXml(source, webXml, true);
}
InputStream fis = url.openStream();
if(fis != null) {
InputSource source = new InputSource(fis);
parser.parseWebXml(source, webXml, true);
}
}
} catch(Exception e) {
if(log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, MessageFormat.format("Encountered exception processing web.xml in {0}", module.getModuleName()), e);
}
} finally {
Thread.currentThread().setContextClassLoader(tccl);
}
return webXml;
});
// Now populate resolved params
result.getContextParams().forEach(context::setInitParameter);
}
} catch(Exception e) {
if(log.isLoggable(Level.WARNING)) {
log.log(Level.WARNING, MessageFormat.format("Encountered exception processing web.xml in {0}", module.getModuleName()), e);
}
} finally {
Thread.currentThread().setContextClassLoader(tccl);
}
return webXml;
}));
}
}
@@ -0,0 +1,23 @@
package servlet;

import java.io.IOException;
import java.io.PrintWriter;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class WebXmlServlet extends HttpServlet {
private static final long serialVersionUID = 1L;

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String param = getInitParameter("initGuy");

resp.setContentType("text/plain");
try(PrintWriter w = resp.getWriter()) {
w.print("I am the web.xml Servlet, and I was initialized with " + param);
}
}
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<note xmlns="http://www.lotus.com/dxl" class="form">
<item name="$TITLE">
<text>servlet/WebXmlServlet.java</text>
</item>
<item name="$Flags">
<text>34567Cg~[</text>
</item>
<item name="$FileNames">
<text>servlet/WebXmlServlet.java</text>
</item>
</note>
13 changes: 13 additions & 0 deletions eclipse/nsfs/nsf-jakartaee-example/odp/WebContent/WEB-INF/web.xml
Expand Up @@ -13,4 +13,17 @@
<param-name>jakarta.faces.FACELETS_LIBRARIES</param-name>
<param-value>/WEB-INF/exampleFacelets.taglib.xml</param-value>
</context-param>

<servlet>
<servlet-name>webxml-servlet</servlet-name>
<servlet-class>servlet.WebXmlServlet</servlet-class>
<init-param>
<param-name>initGuy</param-name>
<param-value>I was set by web.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>webxml-servlet</servlet-name>
<url-pattern>/webXmlServlet</url-pattern>
</servlet-mapping>
</web-app>
Expand Up @@ -152,7 +152,7 @@ public void testNotFound() {
WebTarget target = client.target(getRootUrl(null, TestDatabase.MAIN) + "/somefakepage.xhtml");
Response response = target.request().get();

assertEquals(404, response.getStatus());
checkResponse(404, response);

String content = response.readEntity(String.class);
assertFalse(StringUtil.isEmpty(content));
Expand Down