How do I read the war-file manifest property in its jar-dependency?
UPD: servlets are not used here (it's spring-bean initialization code).
Since a .war file’s purpose is to handle servlets, I assume all of your code is called from servlets, or from a technology built on servlets, like JSP, JSF, or even Spring.
Call the current request’s getServletContext() method, and use the getResource or getResourceAsStream method of ServletContext. Those methods work just like the same methods of java.lang.Class, only they look in the .war file itself before searching the web application’s classpath for a matching path.
For example:
public Optional<Manifest> getWarManifest(ServletRequest request)
throws IOException {
InputStream manifest =
request.getServletContext().getResourceAsStream(
"/META-INF/MANIFEST.MF");
if (manifest == null) {
return Optional.empty();
}
try (InputStream stream = new BufferedInputStream(manifest)) {
return Optional.of(new Manifest(stream));
}
}
Update:
Since you want to read the manifest when preparing a Spring bean, it appears you can autowire a ServletContext object:
#Configuration
public class MyAppConfig {
#Bean
public MyBean createMyBean(#Autowired ServletContext context)
throws IOException {
Optional<Manifest> manifest;
InputStream source =
context.getResourceAsStream("/META-INF/MANIFEST.MF");
if (source == null) {
manifest = Optional.empty();
} else {
try (InputStream stream = new BufferedInputStream(source)) {
manifest = Optional.of(new Manifest(stream));
}
}
return new MyBean(manifest);
}
}
Related
Let say I have the follow code.
private static String configFile = null;
File cf = new File(configFile);
Configuration c = new Configuration();
if (cf.exists() && cf.isFile()) {
c.configure(cf);
} else {
c.configure(configFile);
}
I am wondering what is the difference between c.configure(cf) and c.configure(configFile). In my code,configFile is repsented as resource and cf is the the configFile object.
I found these two from this (api).
public Configuration configure(String resource)
throws HibernateException
public Configuration configure(File configFile)
throws HibernateException
The documentation of the API isn't explicitly clear, is it?
I tracked it as far as this class before getting fed up:
https://github.com/hibernate/hibernate-orm/blob/master/hibernate-core/src/main/java/org/hibernate/boot/cfgxml/internal/ConfigLoader.java
But it looks like in case of configure(String resource), it is the name of a resource as would be passed to the Java class loader to get a resource as a stream, i.e.:
http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html#getResourceAsStream(java.lang.String)
Whereas, configure(File configFile), it uses a FileInputStream.
In either case Hibernate is still expecting the same XML format for the configuration.
I am using the following method to get a resource from WAR file in WildFly:
this.getClass().getResource(relativePath)
It works when the application is deployed as exploded WAR. It used to work with compressed WAR, too. Yesterday, I did a clean and rebuild of project in Eclipse, and it just stopped working.
When I check the resource root:
logger.info(this.getClass().getResource("/").toExternalForm());
I get this:
file:/C:/JBoss/wildfly8.1.0.CR1/modules/system/layers/base/org/jboss/as/ejb3/main/timers/
So, no wonder it doesn't work. It probably has something to do with JBoss module loading, but I don't know if this is a bug or normal behavior.
I found various similar problems on StackOverflow, but no applicable solution. One of the suggestions is to use ServletContext like so:
#Resource
private WebServiceContext wsContext;
...
ServletContext servletContext = (ServletContext)this.wsContext.getMessageContext()
.get(MessageContext.SERVLET_CONTEXT);
servletContext.getResource(resourcePath);
But, when I try to obtain MessageContext in this manner, I get an IllegalStateException. So I am basically stuck. Any ideas?
I ran into this same problem, and rather than define the resource as a shared module, I ended up working around this by using a ServletContextListener in my WAR.
In the contextInitialized method, I got the ServletContext from the ServletContextEvent and used its getResource("/WEB-INF/myResource") to get the URL to the resource inside my WAR file. It appears that in the ServletContextListener, the .getResource() method resolves as expected rather than to the "/modules/system/layers/base/org/jboss/as/ejb3/main/timers/" url. That URL can then be stored in the ServletContext for later use by your servlets or in an injected ApplicationScoped CDI bean.
#WebListener
public class ServletInitializer implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
try {
final ServletContext context = sce.getServletContext();
final URL resourceUrl = context.getResource("/WEB-INF/myResource");
context.setAttribute("myResourceURL", resourceUrl);
} catch (final MalformedURLException e) {
throw new AssertionError("Resource not available in WAR file", e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {}
}
or
#WebListener
public class ServletInitializer implements ServletContextListener {
#Inject
private SomeApplicationScopedBean myBean;
#Override
public void contextInitialized(ServletContextEvent sce) {
try {
final ServletContext context = sce.getServletContext();
final URL resourceUrl = context.getResource("/WEB-INF/myResource");
myBean.setResourceUrl(resourceUrl);
} catch (final MalformedURLException e) {
throw new AssertionError("Resource not available in WAR file", e);
}
}
#Override
public void contextDestroyed(ServletContextEvent sce) {}
}
We had a similar problem and our fault was that we tried to access the static resource through the raw path instead of using the input stream the resource is providing - the following code works for us even when deploying a non-exploded .war-file.
final URL resource = this.getClass().getResource(FILE);
try (final InputStream inputStream = resource.openStream();
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
// Use bufferedReader to read the content
} catch (IOException e) {
// ...
}
I finally gave up and put my resource files in a new JBoss module, as described in this link.
https://community.jboss.org/wiki/HowToPutAnExternalFileInTheClasspath
It works, but the downside is that there are two deployment targets so things are more complicated. On the upside, the size of the WAR file is reduced, and I don't have to redeploy the application if only some of the resources have changed.
I was recently trying to figure out how to access a file within my own war in Java. The following is how the java classes and resources are packaged in the war file:
WAR
`-- WEB-INF
`-- classes (where all the java classes are)
`-- resourcefiles
`-- resourceFile1
My target file was resourceFile1. To get that file, I just did the following in code:
InputStream inStream = this.class.getClassLoader().getResourceAsStream("resourcefiles/resourceFile1");
In this case the resource files would need to be in the same folder as the classes folder containing the java classes. Hopefully others find this helpful.
This sample code works for wildfly deployed and tested on openshift.
I think it is a wildfly problem I downland wildfly and tried on local I also get the error.
Check sample project on github
import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLConnection;
#Controller
#RequestMapping
public class FileDownloadController {
private static final Logger logger = LoggerFactory.getLogger(FileDownloadController.class);
private static final String DOC_FILE = "file/ibrahim-karayel.docx";
private static final String PDF_FILE = "file/ibrahim-karayel.pdf";
#RequestMapping(value = "/download/{type}", method = RequestMethod.GET)
public void downloadFile(HttpServletRequest request, HttpServletResponse response,
#PathVariable("type") String type) throws IOException {
File file = null;
InputStream inputStream;
if (type.equalsIgnoreCase("doc")) {
inputStream = getClass().getClassLoader().getResourceAsStream(DOC_FILE);
file = new File(Thread.currentThread().getContextClassLoader().getResource(DOC_FILE).getFile());
} else if (type.equalsIgnoreCase("pdf")) {
inputStream = getClass().getClassLoader().getResourceAsStream(PDF_FILE);
file = new File(Thread.currentThread().getContextClassLoader().getResource(PDF_FILE).getFile());
} else{
throw new FileNotFoundException();
}
if (file == null && file.getName() == null) {
logger.error("File Not Found -> " + file);
throw new FileNotFoundException();
}
String mimeType = URLConnection.guessContentTypeFromName(file.getName());
if (mimeType == null) {
System.out.println("mimetype is not detectable, will take default");
mimeType = "application/octet-stream";
}
System.out.println("mimetype : " + mimeType);
response.setContentType(mimeType);
/* "Content-Disposition : inline" will show viewable types [like images/text/pdf/anything viewable by browser] right on browser
while others(zip e.g) will be directly downloaded [may provide save as popup, based on your browser setting.]*/
response.setHeader("Content-Disposition", String.format("inline; filename=\"" + file.getName() + "\""));
/* "Content-Disposition : attachment" will be directly download, may provide save as popup, based on your browser setting*/
//response.setHeader("Content-Disposition", String.format("attachment; filename=\"%s\"", file.getName()));
response.setContentLength(inputStream.available());
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
}
}
Had the same issue with Wildfly and not-exploded WAR and using Spring and ServletContextResource I have got around it like this:
[org.springframework.core.io.]Resource resource = new ServletContextResource(servletContext, "WEB-INF/classes/resource.png");
In the same #Service class I also had:
#Inject
private ServletContext servletContext;
I decided so:
#Autowired
private final ApplicationContext ctx;
private final Path path = Paths.get("testfiles/load")
ctx.getRosource("classpath:" + path);
I read this solution, that lead us to use getResourceAsStream(...) instead of getResource() inside Wildfly. I just test it on Wildfly 19 with myApp.ear deployed from console.
I was planning to unit test my Servlet through ServletUnit and ran across some problems :
- As a starting point, we are supposed to create a ServletRunner object. One of the constructors expects File object with web.xml file. I provide the full path of my web.xml file but somehow it ignores the path provided and searches at the top level folder. The code-snippet and error message is below:
Code Snippet
ServletRunner sr = new ServletRunner(new File("* C:/eclipse-workspaces/pocs/lms-csd/src/main/webapp/WEB-INF/web.xml*"));
ServletUnitClient sc = sr.newClient();
WebRequest request = new PostMethodWebRequest("path to be specified" ); request.setParameter( "userId", "test" );
request.setParameter( "password", "csd" );
WebResponse response = sc.getResponse(request);
String text = response.getText();
Assert.assertTrue(text.contains("Welcome to Leave Management System"));
stack trace
com.meterware.httpunit.HttpInternalErrorException:
Error on HTTP request: 500 org.apache.jasper.JasperException: java.io.FileNotFoundException: * C:\eclipse-workspaces\pocs\lms-csd\WEB-INF\web.xml*
(The system cannot find the path specified)
[http://localhost/login] - Why does the system keep on looking at the folder structure to be .../WEB-INF/web.xml.
Mine is a maven project and I would not like to change the structure of the project to adapt this way. How can I make ServletRunner class to read from a specified folder ? - In the Servlet code,
I use the following code :
String result = null if (someCondition) result = "/welcome.jsp"; } else { logger.warn("Password Validation failed"); request.setAttribute("failedlogin", new Boolean(true)); result = "/index.jsp"; } } RequestDispatcher requestDispatcher = getServletContext().getRequestDispatcher(result); requestDispatcher.forward(request, response);
Again ServletUnit expects welcome.jsp to be at root foler, though jsp files are present at .../src/main/webapp/ folder. Again how can ServletUnit be told about the target folder location ?
Many thanks in advance.
Best Regards
M.SuriNaidu
This is the sort of thing I do. This is a facsimile of the base class of my servlet tests. In this case I pass the relative path of the web.xml file as it exists in my source tree. I run these tests from ant and eclipse.
abstract public class ServletTestCase {
protected ServletRunner m_runner;
protected ServletUnitClient m_client;
protected String m_userAgent = "something/1.0";
#Override
protected void setUp() throws Exception {
super.setUp();
initHttpUnit();
}
#Override
protected void tearDown() throws Exception {
shutdownHttpUnit();
super.tearDown();
}
protected void initHttpUnit() throws IOException, SAXException {
shutdownHttpUnit();
// We are expecting UTF-8 character handling in URLs, make it the default
HttpUnitOptions.setDefaultCharacterSet("UTF-8");
// Find the servlet's web.xml file and use it to init servletunit
File file = new File("tests/web.xml"));
m_runner = new ServletRunner(file);
m_client = m_runner.newClient();
m_client.getClientProperties().setUserAgent(m_userAgent);
}
protected void shutdownHttpUnit() {
if (m_runner != null) {
m_runner.shutDown();
}
m_client = null;
m_runner = null;
}
}
I'm trying to create a simple webapp without any XML configuration using Spring 3.1 and an embedded Jetty 8 server.
However, I'm struggling to get Jetty to recognise my implementaton of the Spring WebApplicationInitializer interface.
Project structure:
src
+- main
+- java
| +- JettyServer.java
| +- Initializer.java
|
+- webapp
+- web.xml (objective is to remove this - see below).
The Initializer class above is a simple implementation of WebApplicationInitializer:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import org.springframework.web.WebApplicationInitializer;
public class Initializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("onStartup");
}
}
Likewise JettyServer is a simple implementation of an embedded Jetty server:
import org.eclipse.jetty.annotations.AnnotationConfiguration;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.Configuration;
import org.eclipse.jetty.webapp.WebAppContext;
public class JettyServer {
public static void main(String[] args) throws Exception {
Server server = new Server(8080);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setResourceBase("src/main/webapp");
webAppContext.setContextPath("/");
webAppContext.setConfigurations(new Configuration[] { new AnnotationConfiguration() });
webAppContext.setParentLoaderPriority(true);
server.setHandler(webAppContext);
server.start();
server.join();
}
}
My understanding is that on startup Jetty will use AnnotationConfiguration to scan for
annotated implementations of ServletContainerInitializer; it should find Initializer and wire it in...
However, when I start the Jetty server (from within Eclipse) I see the following on the command-line:
2012-11-04 16:59:04.552:INFO:oejs.Server:jetty-8.1.7.v20120910
2012-11-04 16:59:05.046:INFO:/:No Spring WebApplicationInitializer types detected on classpath
2012-11-04 16:59:05.046:INFO:oejsh.ContextHandler:started o.e.j.w.WebAppContext{/,file:/Users/duncan/Coding/spring-mvc-embedded-jetty-test/src/main/webapp/}
2012-11-04 16:59:05.117:INFO:oejs.AbstractConnector:Started SelectChannelConnector#0.0.0.0:8080
The important bit is this:
No Spring WebApplicationInitializer types detected on classpath
Note that src/main/java is defined as a source folder in Eclipse, so should be on the classpath. Also note that the Dynamic Web Module Facet is set to 3.0.
I'm sure there's a simple explanation, but I'm struggling to see the wood for the trees! I suspect the key is with the following line:
...
webAppContext.setResourceBase("src/main/webapp");
...
This makes sense with a 2.5 servlet using web.xml (see below), but what should it be when using AnnotationConfiguration?
NB: Everything fires up correctly if I change the Configurations to the following:
...
webAppContext.setConfigurations(new Configuration[] { new WebXmlConfiguration() });
...
In this case it finds the web.xml under src/main/webapp and uses it to wire the servlet using DispatcherServlet and AnnotationConfigWebApplicationContext in the usual way (completely bypassing the WebApplicationInitializer implementation above).
This feels very much like a classpath problem, but I'm struggling to understand quite how Jetty associates itself with implementations of WebApplicationInitializer - any suggestions would be most appreciated!
For info, I'm using the following:
Spring 3.1.1
Jetty 8.1.7
STS 3.1.0
The problem is that Jetty's AnnotationConfiguration class does not scan non-jar resources on the classpath (except under WEB-INF/classes).
It finds my WebApplicationInitializer's if I register a subclass of AnnotationConfiguration which overrides configure(WebAppContext) to scan the host classpath in addition to the container and web-inf locations.
Most of the sub-class is (sadly) copy-paste from the parent. It includes:
an extra parse call (parseHostClassPath) at the end of the configure method;
the parseHostClassPath method which is largely copy-paste from
AnnotationConfiguration's parseWebInfClasses;
the getHostClassPathResource method which grabs the first non-jar URL
from the classloader (which, for me at least, is the file url to my
classpath in eclipse).
I am using slightly different versions of Jetty (8.1.7.v20120910) and Spring (3.1.2_RELEASE), but I imagine the same solution will work.
Edit: I created a working sample project in github with some modifications (the code below works fine from Eclipse but not when running in a shaded jar) - https://github.com/steveliles/jetty-embedded-spring-mvc-noxml
In the OP's JettyServer class the necessary change would replace line 15 with:
webAppContext.setConfigurations (new Configuration []
{
new AnnotationConfiguration()
{
#Override
public void configure(WebAppContext context) throws Exception
{
boolean metadataComplete = context.getMetaData().isMetaDataComplete();
context.addDecorator(new AnnotationDecorator(context));
AnnotationParser parser = null;
if (!metadataComplete)
{
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
parser = createAnnotationParser();
parser.registerAnnotationHandler("javax.servlet.annotation.WebServlet", new WebServletAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebFilter", new WebFilterAnnotationHandler(context));
parser.registerAnnotationHandler("javax.servlet.annotation.WebListener", new WebListenerAnnotationHandler(context));
}
}
List<ServletContainerInitializer> nonExcludedInitializers = getNonExcludedInitializers(context);
parser = registerServletContainerInitializerAnnotationHandlers(context, parser, nonExcludedInitializers);
if (parser != null)
{
parseContainerPath(context, parser);
parseWebInfClasses(context, parser);
parseWebInfLib (context, parser);
parseHostClassPath(context, parser);
}
}
private void parseHostClassPath(final WebAppContext context, AnnotationParser parser) throws Exception
{
clearAnnotationList(parser.getAnnotationHandlers());
Resource resource = getHostClassPathResource(getClass().getClassLoader());
if (resource == null)
return;
parser.parse(resource, new ClassNameResolver()
{
public boolean isExcluded (String name)
{
if (context.isSystemClass(name)) return true;
if (context.isServerClass(name)) return false;
return false;
}
public boolean shouldOverride (String name)
{
//looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
if (context.isParentLoaderPriority())
return false;
return true;
}
});
//TODO - where to set the annotations discovered from WEB-INF/classes?
List<DiscoveredAnnotation> annotations = new ArrayList<DiscoveredAnnotation>();
gatherAnnotations(annotations, parser.getAnnotationHandlers());
context.getMetaData().addDiscoveredAnnotations (annotations);
}
private Resource getHostClassPathResource(ClassLoader loader) throws IOException
{
if (loader instanceof URLClassLoader)
{
URL[] urls = ((URLClassLoader)loader).getURLs();
for (URL url : urls)
if (url.getProtocol().startsWith("file"))
return Resource.newResource(url);
}
return null;
}
},
});
Update: Jetty 8.1.8 introduces internal changes that are incompatible with the code above. For 8.1.8 the following seems to work:
webAppContext.setConfigurations (new Configuration []
{
// This is necessary because Jetty out-of-the-box does not scan
// the classpath of your project in Eclipse, so it doesn't find
// your WebAppInitializer.
new AnnotationConfiguration()
{
#Override
public void configure(WebAppContext context) throws Exception {
boolean metadataComplete = context.getMetaData().isMetaDataComplete();
context.addDecorator(new AnnotationDecorator(context));
//Even if metadata is complete, we still need to scan for ServletContainerInitializers - if there are any
AnnotationParser parser = null;
if (!metadataComplete)
{
//If metadata isn't complete, if this is a servlet 3 webapp or isConfigDiscovered is true, we need to search for annotations
if (context.getServletContext().getEffectiveMajorVersion() >= 3 || context.isConfigurationDiscovered())
{
_discoverableAnnotationHandlers.add(new WebServletAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebFilterAnnotationHandler(context));
_discoverableAnnotationHandlers.add(new WebListenerAnnotationHandler(context));
}
}
//Regardless of metadata, if there are any ServletContainerInitializers with #HandlesTypes, then we need to scan all the
//classes so we can call their onStartup() methods correctly
createServletContainerInitializerAnnotationHandlers(context, getNonExcludedInitializers(context));
if (!_discoverableAnnotationHandlers.isEmpty() || _classInheritanceHandler != null || !_containerInitializerAnnotationHandlers.isEmpty())
{
parser = createAnnotationParser();
parse(context, parser);
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
context.getMetaData().addDiscoveredAnnotations(((AbstractDiscoverableAnnotationHandler)h).getAnnotationList());
}
}
private void parse(final WebAppContext context, AnnotationParser parser) throws Exception
{
List<Resource> _resources = getResources(getClass().getClassLoader());
for (Resource _resource : _resources)
{
if (_resource == null)
return;
parser.clearHandlers();
for (DiscoverableAnnotationHandler h:_discoverableAnnotationHandlers)
{
if (h instanceof AbstractDiscoverableAnnotationHandler)
((AbstractDiscoverableAnnotationHandler)h).setResource(null); //
}
parser.registerHandlers(_discoverableAnnotationHandlers);
parser.registerHandler(_classInheritanceHandler);
parser.registerHandlers(_containerInitializerAnnotationHandlers);
parser.parse(_resource,
new ClassNameResolver()
{
public boolean isExcluded (String name)
{
if (context.isSystemClass(name)) return true;
if (context.isServerClass(name)) return false;
return false;
}
public boolean shouldOverride (String name)
{
//looking at webapp classpath, found already-parsed class of same name - did it come from system or duplicate in webapp?
if (context.isParentLoaderPriority())
return false;
return true;
}
});
}
}
private List<Resource> getResources(ClassLoader aLoader) throws IOException
{
if (aLoader instanceof URLClassLoader)
{
List<Resource> _result = new ArrayList<Resource>();
URL[] _urls = ((URLClassLoader)aLoader).getURLs();
for (URL _url : _urls)
_result.add(Resource.newResource(_url));
return _result;
}
return Collections.emptyList();
}
}
});
I was able to resolve in an easier but more limited way by just providing explicitly to the AnnotationConfiguration the implementation class (MyWebApplicationInitializerImpl in this example) that I want to be loaded like this:
webAppContext.setConfigurations(new Configuration[] {
new WebXmlConfiguration(),
new AnnotationConfiguration() {
#Override
public void preConfigure(WebAppContext context) throws Exception {
MultiMap<String> map = new MultiMap<String>();
map.add(WebApplicationInitializer.class.getName(), MyWebApplicationInitializerImpl.class.getName());
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
}
});
Jetty 9.0.1 contains an enhancement which allows for scanning of annotations of non-jar resources (ie classes) on the container classpath. See comment #5 on the following issue for how to use it:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=404176#c5
Jan
The code below did the trick in my maven project:
public static void main(String[] args) throws Exception {
Server server = new Server();
ServerConnector scc = new ServerConnector(server);
scc.setPort(Integer.parseInt(System.getProperty("jetty.port", "8080")));
server.setConnectors(new Connector[] { scc });
WebAppContext context = new WebAppContext();
context.setServer(server);
context.setContextPath("/");
context.setWar("src/main/webapp");
context.getMetaData().addContainerResource(new FileResource(new File("./target/classes").toURI()));
context.setConfigurations(new Configuration[]{
new WebXmlConfiguration(),
new AnnotationConfiguration()
});
server.setHandler(context);
try {
System.out.println(">>> STARTING EMBEDDED JETTY SERVER, PRESS ANY KEY TO STOP");
System.out.println(String.format(">>> open http://localhost:%s/", scc.getPort()));
server.start();
while (System.in.available() == 0) {
Thread.sleep(5000);
}
server.stop();
server.join();
} catch (Throwable t) {
t.printStackTrace();
System.exit(100);
}
}
Based on my testing and this thread http://forum.springsource.org/showthread.php?127152-WebApplicationInitializer-not-loaded-with-embedded-Jetty I don't think it works at the moment. If you look in AnnotationConfiguration.configure:
parseContainerPath(context, parser);
// snip comment
parseWebInfClasses(context, parser);
parseWebInfLib (context, parser);
it seems coupled to a war-like deployment rather than embedded.
Here is an example using Spring MVC and embedded Jetty that might be more useful:
http://www.jamesward.com/2012/08/13/containerless-spring-mvc
It creates the Spring servlet directly rather then relying on annotations.
To those experiencing this lately, it appears this gets around the issue:
#Component
public class Initializer implements WebApplicationInitializer {
private ServletContext servletContext;
#Autowired
public WebInitializer(ServletContext servletContext) {
this.servletContext = servletContext;
}
#PostConstruct
public void onStartup() throws ServletException {
onStartup(servletContext);
}
public void onStartup(ServletContext servletContext) throws ServletException {
System.out.println("onStartup");
}
}
To make it work on Jetty 9 set attribute AnnotationConfiguration.CLASS_INHERITANCE_MAP on WebAppContext
webAppContext.setAttribute(AnnotationConfiguration.CLASS_INHERITANCE_MAP, createClassMap());
And here is how to create this map:
private ClassInheritanceMap createClassMap() {
ClassInheritanceMap classMap = new ClassInheritanceMap();
ConcurrentHashSet<String> impl = new ConcurrentHashSet<>();
impl.add(MyWebAppInitializer.class.getName());
classMap.put(WebApplicationInitializer.class.getName(), impl);
return classMap;
}
I placed that solution on gitHub
What about just setting the context attribute that tells the scanner which things belong on the container classpath that need to be scanned?
context attribute:
org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern
./servlet-api-[^/].jar$
It is designed to be used with jar names, but you could just match everything.
You'd need to use the WebInfConfiguration as well as the AnnotationConfiguration classes.
cheers
Jan
In our case these lines helped in Jetty startup code:
ClassList cl = Configuration.ClassList.setServerDefault(server);
cl.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration", "org.eclipse.jetty.annotations.AnnotationConfiguration");
Jetty 9 version of "magomarcelo" answer:
context.setConfigurations(
new org.eclipse.jetty.webapp.Configuration[] { new WebXmlConfiguration(), new AnnotationConfiguration() {
#Override
public void preConfigure(WebAppContext context) throws Exception {
final ClassInheritanceMap map = new ClassInheritanceMap();
final ConcurrentHashSet<String> set = new ConcurrentHashSet<>();
set.add(MyWebAppInitializer.class.getName());
map.put(WebApplicationInitializer.class.getName(), set);
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
} });
For Jetty 9, if you have webjars, the solution provided does not work straight away as those Jars need to be on the classpath and the JAR contents need to be available as resources for your webapp. So, for that to work together with webjars, the config would have to be:
context.setExtraClasspath(pathsToWebJarsCommaSeparated);
context.setAttribute(WebInfConfiguration.WEBINF_JAR_PATTERN, ".*\\.jar$");
context.setAttribute(WebInfConfiguration.CONTAINER_JAR_PATTERN, ".*\\.jar$");
context.setConfigurations(
new org.eclipse.jetty.webapp.Configuration[] {
new WebInfConfiguration(), new MetaInfConfiguration(),
new AnnotationConfiguration() {
#Override
public void preConfigure(WebAppContext context) throws Exception {
final ClassInheritanceMap map = new ClassInheritanceMap();
final ConcurrentHashSet<String> set = new ConcurrentHashSet<>();
set.add(MyWebAppInitializer.class.getName());
map.put(WebApplicationInitializer.class.getName(), set);
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
} });
The order here is important (WebInfConfiguration has to come before MetaInf).
Solution that worked for me and does not involve scanning, but uses WebApplicationInitializer class that you provide. Jetty version: 9.2.20
public class Main {
public static void main(String... args) throws Exception {
Properties properties = new Properties();
InputStream stream = Main.class.getResourceAsStream("/WEB-INF/application.properties");
properties.load(stream);
stream.close();
PropertyConfigurator.configure(properties);
WebAppContext webAppContext = new WebAppContext();
webAppContext.setResourceBase("resource");
webAppContext.setContextPath(properties.getProperty("base.url"));
webAppContext.setConfigurations(new Configuration[] {
new WebXmlConfiguration(),
new AnnotationConfiguration() {
#Override
public void preConfigure(WebAppContext context) {
ClassInheritanceMap map = new ClassInheritanceMap();
map.put(WebApplicationInitializer.class.getName(), new ConcurrentHashSet<String>() {{
add(WebInitializer.class.getName());
add(SecurityWebInitializer.class.getName());
}});
context.setAttribute(CLASS_INHERITANCE_MAP, map);
_classInheritanceHandler = new ClassInheritanceHandler(map);
}
}
});
Server server = new Server(Integer.parseInt(properties.getProperty("base.port")));
server.setHandler(webAppContext);
server.start();
server.join();
}
}
The source (in russian) of this code snippet is here: https://habrahabr.ru/post/255773/
did a simple maven project to demonstrate how it can be done cleanly.
public class Console {
public static void main(String[] args) {
try {
Server server = new Server(8080);
//Set a handler to handle requests.
server.setHandler(getWebAppContext());
//starts to listen at 0.0.0.0:8080
server.start();
server.join();
} catch (Exception e) {
log.error("server exited with exception", e);
}
}
private static WebAppContext getWebAppContext() {
final WebAppContext webAppContext = new WebAppContext();
//route all requests via this web-app.
webAppContext.setContextPath("/");
/*
* point to location where the jar into which this class gets packaged into resides.
* this could very well be the target directory in a maven development build.
*/
webAppContext.setResourceBase("directory_where_the_application_jar_exists");
//no web inf for us - so let the scanning know about location of our libraries / classes.
webAppContext.getMetaData().setWebInfClassesDirs(Arrays.asList(webAppContext.getBaseResource()));
//Scan for annotations (servlet 3+)
final AnnotationConfiguration configuration = new AnnotationConfiguration();
webAppContext.setConfigurations(new Configuration[]{configuration});
return webAppContext;
}
}
and that's all - the spring WebApplicationInitializer that you use will get detected without explicitly letting jetty server know about the existence of such an app initializer.
Background:
One of the components of our project operates using spring. Some SQL code is dynamically generated, based on a given XML spring configuration.
At first it was fine to store all the XML configurations in the same package on the classpath, (and then load it as a resource when the service is called) but over time we ended up with a large number of configurations. It came time to separate the configurations into different namespaces.
The Goal
What I want is, given a starting package on the classpath, to recursively walk the directory structure and discover any spring XML files dynamically. (So that as new configurations / packages are added, the files will still be found by the service).
The Problem
I was able to accomplish my goal fine when running outside an EJB container by using Thread.getContextClassloader().getResource(myBasePackage), then getting a File object and using it to walk the tree on the filesystem. Clunky, I know, but it was still classpath relative and it worked.
However, you cannot do this inside an EJB container (you can't interact with the filesystem at all), so I had to use the rather annoying workaround in which I maintain a list of hardcoded packages to search.
The Question
Is there a way (running inside an EJB container) to dynamically walk the classpath (from a given starting location) searching for arbitrary resources?
Short answer: Not while staying in compliance with the EJB spec. Because the spec envisions containers running in all kinds of non-standard situations, it does not make this possible.
Longer answer: Since you are not creating these resources dynamically, I would write a routine that gives you a list of all of the resources at build time and puts them in a dynamically generated file that your EJB knows how to reference. So you basically create a directory listing of packages and files that you can load in the EJB that are referenced in one master file.
Spring answer: Spring supports finding resources on the classpath, although I have no idea how well this works in the EJB context (and I doubt its EJB compliant, but I haven't checked). Some details here.
DISCLAIMER: As already pointed out, creating resources in the classpath is not recommended and depending on the EJB container explicitly forbidden. This may cause you a lot of problems because containers may explode your resources into another folder or even replicate the resources throughout the cluster (if thats the case). In order to create resources dynamically you have to create a custom classloader. So, I would never do it. It is better to access the filesystem directly than the classpath. It is less ugly and eventually cluster-safe if you use a remote filesystem + file locks.
If even after all I explained you still want to play with the classpath, you can try to do something like: get the classloader via
ClassLoader cld = Thread.currentThread().getContextClassLoader();
Starting from a base package enumerate all occurrences
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
Each URL is generally either a file link (file:///home/scott/.../MyResource.properties) or a jar link (file:///lib.jar!/com/domain/MyResource.properties). You have to check the pattern in the URL. Using that, enumerate the contents of the folder using the normal java API and find the subpackages. Proceed until you have scanned all packages.
See the class below (will be released with an open-source project of mine soon). It implemens a classpath scanner that you can pass in a selector. It works like a visitor. It my work for you, if not, get ideas from it. See the sample annotation selector at the end.
public class ClasspathScanner
{
private static final Log log = LogFactory.getLog(ClasspathScanner.class);
private static final String JAR_FILE_PATTERN = ".jar!";
private ClassSelector selector;
private Set<Class<?>> classes;
// PUBLIC METHODS ------------------------------------------------------------------------------
public synchronized Set<Class<?>> scanPackage(String basePackage, ClassSelector selector)
throws Exception
{
if (selector == null)
{
throw new NullPointerException("Selector cannot be NULL");
}
this.selector = selector;
this.classes = new HashSet<Class<?>>();
Set<Class<?>> aux;
try
{
scanClasses0(basePackage);
aux = this.classes;
}
finally
{
this.selector = null;
this.classes = null;
}
return aux;
}
// HELPER CLASSES ------------------------------------------------------------------------------
private void scanClasses0(String basePackage)
throws IOException, ClassNotFoundException, FileNotFoundException
{
File packageDirectory = null;
ClassLoader cld = getLoader();
String basePackagePath = basePackage.replace('.', '/');
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
if (basePackageUrls == null || !basePackageUrls.hasMoreElements())
{
throw new ClassNotFoundException("Base package path not found: [" + basePackagePath
+ "]");
}
while (basePackageUrls.hasMoreElements())
{
String packagePath = basePackageUrls.nextElement().getFile();
if (packagePath.contains(JAR_FILE_PATTERN))
{
scanJarFile(basePackagePath, packagePath);
}
else
{
packageDirectory = new File(packagePath);
scanDirectory(basePackage, packageDirectory);
}
}
}
private void scanDirectory(String packageName, File packagePath)
throws ClassNotFoundException, FileNotFoundException
{
if (packagePath.exists())
{
File[] packageFiles = packagePath.listFiles();
for (File file : packageFiles)
{
if (file.isFile() && file.getName().endsWith(".class"))
{
String fullFileName = packageName + '.' + file.getName();
checkClass(fullFileName);
}
else if (file.isDirectory())
{
scanDirectory(packageName + "." + file.getName(), file);
}
}
}
else
{
throw new FileNotFoundException(packagePath.getPath());
}
}
private void scanJarFile(String basePackagePath, String jarFileUrl)
throws IOException, ClassNotFoundException
{
String jarFilePath = jarFileUrl.substring("file:".length(), jarFileUrl
.indexOf(JAR_FILE_PATTERN)
+ JAR_FILE_PATTERN.length() - 1);
log.debug("URL JAR file path: [" + jarFilePath + "]");
jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
log.debug("Decoded JAR file path: [" + jarFilePath + "]");
JarFile jar = new JarFile(new File(jarFilePath));
for (Enumeration<JarEntry> jarFiles = jar.entries(); jarFiles.hasMoreElements();)
{
JarEntry file = jarFiles.nextElement();
String fileName = file.getName();
if (!file.isDirectory() && fileName.endsWith(".class")
&& fileName.startsWith(basePackagePath))
{
String className = fileName.replace('/', '.');
checkClass(className);
}
}
}
private void checkClass(String fullFilePath) throws ClassNotFoundException
{
String className = fullFilePath.substring(0, fullFilePath.length() - 6);
Class<?> c = getLoader().loadClass(className);
if (selector.select(c))
{
classes.add(c);
}
}
private ClassLoader getLoader()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
{
loader = getClass().getClassLoader();
}
return loader;
}
// INNER CLASSES -------------------------------------------------------------------------------
public interface ClassSelector
{
boolean select(Class<?> clazz);
}
public static class AnnotatedClassSelector implements ClassSelector
{
private final Class<? extends Annotation>[] annotations;
public AnnotatedClassSelector(Class<? extends Annotation>... annotations)
{
this.annotations = annotations;
}
public boolean select(Class<?> clazz)
{
for (Class<? extends Annotation> ac : annotations)
{
if (clazz.isAnnotationPresent(ac))
{
return true;
}
}
return false;
}
}
}