Embedded Jetty to test the spring application startup - spring

What I am trying to do is, to have a test to startup the whole application to see if there's any error. But I want to use the applicationContext.xml from the /src/resource folder and not form test/resource. How can I do that in JUnit?
My application is big and a lot of people share the same codebase. So, I just wanted to have a quick test to see if the checkin can start up the application.
This is my simple code but it looks like it's missing some of the autowire stuff, that's why I want to use the xml files from /src/resource, so I don't have to maintain two locations.
My application is plain Spring MVC 3.0
#Test(enabled = false)
public void shouldStartupTheApp() throws Exception {
Server server = new Server();
SelectChannelConnector connector = new SelectChannelConnector();
connector.setPort(9999);
server.setConnectors(new Connector[] {connector});
Context context = new Context(server, "/", Context.SESSIONS);
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setContextConfigLocation("classpath:/test-applicationContext.xml");
ServletHolder servletHolder = new ServletHolder(dispatcherServlet);
context.addServlet(servletHolder, "/*");
server.start();
}

You can import your src/resources/filename.xml in other xml-file using
<import resource="classpath:/filename.xml" />

Related

Returning a different Tomcat session ID based on URI in Spring

I'm very new to Spring + Tomcat and trying to learn it while working on an existing spring boot (v. 2.6.x) web app running on a Tomcat server (tomcat-embed-core v. 9.0.x).
The application serves URLs of this type:
\{customer}\path\to\resource
\{customer}\newapp\path\to\resource
I can see this reported on the ModelAndView controller annotated with the likes of PostMapping and GetMapping.
Now, the service keeps tracks of sessions using JSESSION cookies and generating session IDs using tomcat's standard org.apache.catalina.SessionIdGenerator.
I would like to programmatically change the format of the session identifier generated by tomcat, depending on the request being served.
Given the paths above, for instance, I'd like to have:
request to 1. above, should be generating session identifiers like 905A6892CB2C12F84A331F58A6A2C382
requests to newapp, i.e. 2. above, should be generating session identifiers like NEWAPP_905A6892CB2C12F84A331F58A6A2C382
The format of the session is irrelevant, but they must have a different prefix.
It seems that one way to achieve this would be to have two different contexts, each one with a different catalina Manager, which can be set using org.apache.catalina.Context#setManager.
I'm not able to define two different contexts because the root in the path is the variable {customer}, nor I'm able to dynamically inject the context using an implementation of org.apache.catalina.valves.ValveBase, as there is no easy way to create a delegated context, where the only difference WRT the base tomcat context is the Manager.
I've tried changing the root of the newapp paths to be like newapp\{customer}\path\to\resource and creating a new Context for newapp, but this fails if I try to override org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer like
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
prepareContext(tomcat.getHost(), initializers);
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
final Context ctx = tomcat.addContext("/newapp", documentRoot);
ctx.setManager(new NewAppManager());
return getTomcatWebServer(tomcat);
}
returning a 404 error - it looks like the application is not able to find the mapping for the path, even if I successfully changed the GetMapping to newapp\{customer}\path\to\resource.
Can anyone suggest the best way to achieve this and if what I'm trying even makes sense?
Thank you so much!
I was able to achieve the required outcome by adding a few lines in the getWebServer method.
Adding the whole method below, with comments, for clarity.
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
customizeEngine(tomcat.getEngine());
prepareContext(tomcat.getHost(), initializers);
// This is the ROOT context generated by the embedded tomcat
final StandardContext rootCtx = (StandardContext) tomcat.getHost().findChild("");
// Both context will be sharing the same war file
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
// Creating a new context for the "newapp, serving the "/newapp" path
final StandardContext newappCtx = (StandardContext) tomcat.addContext("/newapp", documentRoot);
// This turned out to be very important, sets the classloader of the context to delegate to the application classloader, exactly as ROOT ctx does
newappCtx.setParentClassLoader(rootCtx.getParentClassLoader());
newappCtx.setDelegate(true);
// This was needed in our specific case, the default cookie path would be "/newapp"
newappCtx.setSessionCookiePath("/");
// And finally setting the session manager, generating a new session identifier
newappCtx.setManager(new NewappSessionManager());
final WebServer ws = getTomcatWebServer(tomcat);
// This is the default dispatcher created by the embedded tomcat
final Servlet dispatcherServlet = ((StandardWrapper) rootCtx.findChild("dispatcherServlet")).getServlet();
// We're now adding the default servlet to the "newappCtx"
Tomcat.addServlet(newappCtx, "dispatcherServlet", dispatcherServlet);
// it will serve all paths under "/newapp"
newappCtx.addServletMappingDecoded("/*", "dispatcherServlet");
return ws;
}
....
// Simple manager for the new context
private static final class NewappSessionManager extends StandardManager {
#Override
protected String getNextSessionId() {
return "NEWAPP_" + super.getNextSessionId();
}
}

Jersey on embedded Jetty with hk2 locator not loading ResourceConfig

I am using Jetty as an embedded server for Jersey.
ServletHolder jerseyServletHolder = new ServletHolder(ServletContainer.class);
jerseyServletHolder.setInitOrder(1);
jerseyServletHolder.setInitParameter(
org.glassfish.jersey.server.ServerProperties.PROVIDER_PACKAGES,
"com.my.package");
webAppContext.addServlet(jerseyServletHolder, "/rest/*");
I have a ResourceConfig implementation:
#ApplicationPath("/rest")
public class MyResourceConfig extends ResourceConfig {
static{
System.out.println("ResourceConfig loaded");
// this never gets calls
}
#Inject
public MyResourceConfig(ServiceLocator serviceLocator, Properties serverProps) {
packages("com.my.package");
}
}
The problem is that when I launch, the MyResourceConfig class is never loaded.
If I add:
jerseyServletHolder.setInitParameter(
ServletProperties.JAXRS_APPLICATION_CLASS,
MyResourceConfig.class.getName());
then the ResourceConfig does get loaded.
Why isn't MyResoureConfig getting picked up based on the #ApplicationPath annotation?
You just need annotation / bytecode scanning enabled.
Start by putting jetty-annotations-<version>.jar (and transitive dependencies) into your project.
Then, in your code, after you create your org.eclipse.jetty.server.Server object, do this.
Server server = new Server(8080);
// Enable parsing of jndi-related parts of web.xml and jetty-env.xml
Configuration.ClassList classlist = Configuration.ClassList
.setServerDefault(server);
classlist.addAfter("org.eclipse.jetty.webapp.FragmentConfiguration",
"org.eclipse.jetty.plus.webapp.EnvConfiguration",
"org.eclipse.jetty.plus.webapp.PlusConfiguration");
// Enable annotation/bytecode scanning and ServletContainerInitializer usages
classlist.addBefore(
"org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
"org.eclipse.jetty.annotations.AnnotationConfiguration");
WebAppContext webAppContext = createWebAppContext();
// ....
server.start();
That will enable the configurations needed to perform bytecode scanning and annotation scanning, along with enabling the ability to load any javax.servlet.ServletContainerInitializer found within your webapp, including the critical one from Jersey (org.glassfish.jersey.servlet.init.JerseyServletContainerInitializer)

Using a custom classloader with Spring's ComponentScan and PropertyPlaceholderConfigurer

I want to create multiple application contexts in my Tomcat application.
Some of these application contexts have the same package and class names, but they all refer to different jars.
For example:
application0 use service.jar, model.jar
application1 use service-a.jar, model-a.jar
application2 use service-b.jar, model-b.jar
application0 context is OK because is in orign project.
I reference some web page to custom application1, I use my custom classloader to start applicationContext.
File file0 = new File("D://git/project1/service-a.jar");
File file1 = new File("D://git/project1/modele-a.jar");
// convert the file to URL format
URL url0 = file0.toURI().toURL();
URL url1 = file1.toURI().toURL();
List<URL> urls = new LinkedList<>();
List<File> libs = listFilesForFolder(new File("D://protal//apache-tomcat-8.0.39//lib"));
for(File lib : libs) {
urls.add(lib.toURI().toURL());
}
urls.add(url1);
urls.add(url0);
final URLClassLoader customClassLoader = new URLClassLoader(urls.toArray(new URL[urls.size()]));
ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("applicationContext.xml") {
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader)
{
super.initBeanDefinitionReader(reader);
reader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE);
reader.setBeanClassLoader(customClassLoader);
}
};
allApplicationContexts.add(context1);
The Spring contexts start OK, but they fail to create the component-scan bean, and PropertyPlaceholderConfigurer isn't working. Everything else seems correct.
I sure my config is correct because it works without the custom classloader. Libs contains all spring lib.
Is it possible to get this working with multiple Spring contexts?

Behavior differ between running application in IntelliJ and from jar

I have written an application using Jetty as a server. I wish to package my application into a .jar, so I may put it in a Docker container later on.
When I run my application through IntelliJ, everything seems to be working as it should, and I can reach my index.html from localhost:2222. However, when I package my application (using mvn clean compile assembly:single) and run my jar by typing java -jar myapplication.jar in the shell, my index.html on localhost:2222 returns HTTP Error 404.
My App.java:
public class App {
public static void main( String[] args ) throws Exception {
ResourceConfig config = new ResourceConfig();
config.packages("mypackage");
ServletHolder servlet = new ServletHolder(new ServletContainer(config));
Server server = new Server(2222);
ResourceHandler handler = new ResourceHandler();
handler.setBaseResource(org.eclipse.jetty.util.resource.Resource
.newClassPathResource("index.html"));
ServletContextHandler context = new ServletContextHandler(server, "/*");
context.addServlet(servlet, "/*");
HandlerList handlers = new HandlerList();
handlers.addHandler(handler);
handlers.addHandler(context);
server.setHandler(handlers);
try {
server.start();
server.join();
} finally {
server.destroy();
}
}
}
When running the application in IntelliJ and as jar, I get the following output (may be of interest?):
2015-11-06 09:25:00.991:INFO::main: Logging initialized #279ms
2015-11-06 09:25:01.238:WARN:oejsh.ContextHandler:main: o.e.j.s.ServletContextHandler#490d6c15{/,null,null} contextPath ends with /*
2015-11-06 09:25:01.238:WARN:oejsh.ContextHandler:main: Empty contextPath
2015-11-06 09:25:01.250:INFO:oejs.Server:main: jetty-9.2.3.v20140905
Nov 06, 2015 9:25:01 AM org.glassfish.jersey.server.ApplicationHandler initialize
INFO: Initiating Jersey application, version Jersey: 2.7 2014-03-12 18:11:31...
2015-11-06 09:25:02.294:INFO:oejsh.ContextHandler:main: Started o.e.j.s.ServletContextHandler#490d6c15{/,null,AVAILABLE}
2015-11-06 09:25:02.306:INFO:oejs.ServerConnector:main: Started ServerConnector#268f106e{HTTP/1.1}{0.0.0.0:2222}
2015-11-06 09:25:02.306:INFO:oejs.Server:main: Started #1688ms
I am not posting my file of Jersey-methods, since I don't think it is relevant for my question.
So, what is the magic of packaging this up to a jar that can recognize my index.html at localhost:2222? Is it some environment settings in IntelliJ?
Cheers
First of all, don't mix ResourceHandler and ServletContextHandler, you are duplicating static content effort (3 times over with Jersey in play) and will get unreliable results.
Drop the ResourceHandler, set the required ServletContextHandler.setBaseResource(), and add a DefaultServlet to your context.
// Figure out what path to serve content from
ClassLoader cl = App.class.getClassLoader();
// We look for a file, as ClassLoader.getResource() is not
// designed to look for directories (we resolve the directory later)
URL f = cl.getResource("static-root/index.html");
if (f == null)
{
throw new RuntimeException("Unable to find resource directory");
}
// Resolve file to directory
URI webRootUri = f.toURI().resolve("./").normalize();
System.err.println("Main Base Resource is " + webRootUri);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
context.setBaseResource(Resource.newResource(webRootUri));
server.setHandler(context);
// Jersey setup
ResourceConfig config = new ResourceConfig();
config.packages("mypackage");
context.addServlet(new ServletHolder(new ServletContainer(config)), "/*");
// Lastly, the default servlet for root content (always needed, to satisfy servlet spec)
// It is important that this is last.
ServletHolder holderDef = new ServletHolder("default",DefaultServlet.class);
holderDef.setInitParameter("dirAllowed","true");
context.addServlet(holderDef,"/");
Note: since you setup Jersey at the url-pattern /* you should know that Jersey is responsible for serving all static content, not Jetty.

Spring mixed XML/annotation configuration with overrides

I'm working on setting up a utility that lets us load an annotation-based configuration that overrides an XML configuration (for testing). I have tried a number of different setups, but this is the only one that I've gotten to work:
GenericApplicationContext firstCtx = new GenericApplicationContext();
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(firstCtx );
xmlReader.loadBeanDefinitions("applicationContext.xml");
GenericApplicationContext ctx = new GenericApplicationContext();
AnnotatedBeanDefinitionReader annotatedReader = new AnnotatedBeanDefinitionReader(ctx);
annotatedReader.register(SomeConfigClass.class);
ctx.refresh();
for (String currBeanName : firstCtx.getBeanDefinitionNames())
{
if (!ctx.containsBeanDefinition(currBeanName))
{
ctx.registerBeanDefinition(currBeanName, firstCtx.getBeanDefinition(currBeanName));
}
}
While this technically does work, it seems like a really cumbersome way to do this. Is there a better way to load an annotation-based configuration over an XML-based configuration?
Thanks!
I think a simpler way is to simply declare SomeConfigClass as a bean within your application context and the configured beans in SomeConfigClass will be wired in.
<bean class="..SomeConfigClass"/>
Or <context:component-scan base-package="package of SomeConfigClass"/>
Or the other way round, in SomeClassClass, do #ImportResource("applicationContext.xml")

Resources