I have a large Spring application that is set up without XML using only annotations. I have made some changes to this application and have a separate project with what should be almost all the same code. However, in this separate project, Togglz seems to be using some sort of default config instead of the TogglzConfig file I've set up.
The first sign that something was wrong was when I couldn't access the Togglz console. I get a 403 Forbidden error despite my config being set to allow anyone to use it (as shown on the Togglz site). I then did some tests and tried to see a list of features and the list is empty when I call FeatureContext.getFeatureManager().getFeatures() despite my Feature class having several features included. This is why I think it's using some sort of default.
TogglzConfiguration.java
public enum Features implements Feature {
FEATURE1,
FEATURE2,
FEATURE3,
FEATURE4,
FEATURE5;
public boolean isActive() {
return FeatureContext.getFeatureManager().isActive(this);
}
}
TogglzConfiguration.java
#Component
public class TogglzConfiguration implements TogglzConfig {
public Class<? extends Feature> getFeatureClass() {
return Features.class;
}
public StateRepository getStateRepository() {
File properties = [internal call to property file];
try {
return new FileBasedStateRepository(properties);
} catch (Exception e) {
throw new TogglzConfigException("Error getting Togglz configuration from " + properties + ".", e);
}
}
#Override
public UserProvider getUserProvider() {
return new UserProvider() {
#Override
public FeatureUser getCurrentUser() {
return new SimpleFeatureUser("admin", true);
}
};
}
}
SpringConfiguration.java
#EnableTransactionManagement
#Configuration
#ComponentScan(basePackages = { "root package for the entire project" }, excludeFilters =
#ComponentScan.Filter(type=FilterType.ANNOTATION, value=Controller.class))
public class SpringConfiguration {
#Bean
public TransformerFactory transformerFactory() {
return TransformerFactory.newInstance();
}
#Bean
public DocumentBuilderFactory documentBuilderfactory() {
return DocumentBuilderFactory.newInstance();
}
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
My project finds a bunch of other beans set up with the #Component annotation. I don't know if the problem is that this component isn't being picked up at all or if Togglz simply isn't using it for some reason. I tried printing the name of the FeatureManager returned by FeatureContext.getFeaturemanager() and it is FallbackTestFeatureManager so this seems to confirm my suspicion that it's just not using my config at all.
Anyone have any ideas on what I can check? I'm flat out of ideas, especially since this is working with an almost completely the same IntelliJ project on my machine right now. I just can't find out what's different about the Togglz setup or the Spring configurations. Thanks in advance for your help.
I finally had my light bulb moment and solved this problem. In case anyone else has a similar issue, it seems my mistake was having the Togglz testing and JUnit dependencies added to my project but not limiting them to the test scope. I overlooked that part of the site.
<!-- Togglz testing support -->
<dependency>
<groupId>org.togglz</groupId>
<artifactId>togglz-testing</artifactId>
<version>2.5.0.Final</version>
<scope>test</scope>
</dependency>
Without that scope, I assume these were overriding the Togglz configuration I created with a default test configuration and that was causing my issue.
Issue : When running integration tests from maven (mvn verify) the spring application context is not initialized properly, it doesn't take in consideration my custom ApplicationContextInitializer class.
Test Class :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {MainApplication.class}, initializers = CustomContextInitializer.class)
#WebIntegrationTest
public class ApplicationIT {
// Running a SOAPUI suite as a JUnit Test
#Test
public void TestGateway() throws Exception {
SoapUITestCaseRunner runner = new SoapUITestCaseRunner();
runner.setProjectFile("../gateway/src/test/resources/soapui/gateway-soapui.xml");
runner.run();
}
}
MainApplication class :
#Configuration
#ComponentScan(basePackages = {
// different packages here (not relevant)
})
#EnableAutoConfiguration
public class MainApplication {
public static void main(String[] args) throws Exception {
new SpringApplicationBuilder(MainApplication.class)
.initializers(new CustomContextInitializer())
.run(args);
}
}
CustomContextInitiliazer class (for adding custom .properties files to the spring environment application context) :
public class CustomContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment env = applicationContext.getEnvironment();
try {
Resource[] res = new PathMatchingResourcePatternResolver().getResources("classpath*:/*.properties");
for (Resource re : res) {
env.getPropertySources().addFirst(new ResourcePropertySource(re));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
Results :
1) Everything works on when I start and run the application (either from IDE or by invoking mvn exec).
2) Integration tests run ok when started from IDE.
3) Integration tests throw error when invoked via maven verify because the custom properties files are not loaded into spring context environment. The result is the same as if I wouldn't have written initializers = CustomContextInitializer.class in the test class and tried to run the tests from IDE.
I think your code is correct, but your .properties files may be at the wrong place. Make sure they are under <project>/src/main/resources or that you have configured a custom resource folder in maven. If they reside under <project>/src/main/java they will not be part of the classpath as far as maven is concerned.
I'm using Spring-Data-Neo4j 4.0.0.M1, and trying to connect to the server. I'm getting an exception:
Caused by: org.apache.http.client.HttpResponseException: Unauthorized
I have a password on the server interface, but I'm not sure how to tell Spring about it.
#Configuration
#EnableNeo4jRepositories(basePackages = "com.noxgroup.nitro.persistence")
#EnableTransactionManagement
public class MyConfiguration extends Neo4jConfiguration {
#Bean
public Neo4jServer neo4jServer () {
/*** I was quite surprised not to see an overloaded parameter here ***/
return new RemoteServer("http://localhost:7474");
}
#Bean
public SessionFactory getSessionFactory() {
return new SessionFactory("org.my.software.domain");
}
#Bean
ApplicationListener<BeforeSaveEvent> beforeSaveEventApplicationListener() {
return new ApplicationListener<BeforeSaveEvent>() {
#Override
public void onApplicationEvent(BeforeSaveEvent event) {
if (event.getEntity() instanceof User) {
User user = (User) event.getEntity();
user.encodePassword();
}
}
};
}
}
Side Note
4.0.0 Milestone 1 is absolutely fantastic. If anyone is using 3.x.x, I'd recommend checking it out!
The username and password are passed currently via system properties
e.g.
-Drun.jvmArguments="-Dusername=<usr> -Dpassword=<pwd>"
or
System.setProperty("username", "neo4j");
System.setProperty("password", "password");
https://jira.spring.io/browse/DATAGRAPH-627 is open (not targeted for 4.0 RC1 though), please feel free to add comments/vote it up
I am trying to use dropwizard-sundial and am having trouble with a resource. I'm not sure if it's a classpath issue or if I am failing to register resources properly.
This is my application class' run method:
public void run(DataLoaderApplicationConfiguration configuration, Environment environment) throws Exception {
logger.info("Started DataLoader Application");
final String template = configuration.getTemplate();
environment.healthChecks().register("TemplateHealth", new TemplateHealthCheck(template));
// JOBS
environment.jersey().packages("com.tradier.dataloader.jobs");
}
I get the following error at runtime:
INFO [2015-04-07 15:00:19,737] com.xeiam.sundial.plugins.AnnotationJobTriggerPlugin: Loading annotated jobs from com.tradier.dataloader.jobs.
[WARNING]
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
at java.lang.Thread.run(Thread.java:722)
Caused by: java.lang.RuntimeException: Unexpected problem: No resource for com/tradier/dataloader/jobs
at org.quartz.classloading.CascadingClassLoadHelper.getJobClasses(CascadingClassLoadHelper.java:217)
at com.xeiam.sundial.plugins.AnnotationJobTriggerPlugin.start(AnnotationJobTriggerPlugin.java:72)
at org.quartz.QuartzScheduler.startPlugins(QuartzScheduler.java:1102)
at org.quartz.QuartzScheduler.start(QuartzScheduler.java:211)
at com.xeiam.sundial.SundialJobScheduler.startScheduler(SundialJobScheduler.java:102)
Check out a working example at https://github.com/timmolter/XDropWizard. It uses annotated jobs. You need to add the package name conatining the annotated jobs in your config.yaml file like this:
sundial:
thread-pool-size: 5
shutdown-on-unload: true
wait-on-shutdown: false
start-delay-seconds: 0
start-scheduler-on-load: true
global-lock-on-load: false
annotated-jobs-package-name: org.knowm.xdropwizard.jobs
If you still are getting an exception, leave a report at: https://github.com/timmolter/dropwizard-sundial/issues.
#Jeyashree Narayanan, the jobs package should not be configured in the application class as you have shown, it can be easily done in the yml file. Here is the explanation in simple steps:
Step 1: Configuration in yml file and the Configuration class
sundial:
thread-pool-size: 10
shutdown-on-unload: true
start-delay-seconds: 0
start-scheduler-on-load: true
global-lock-on-load: false
annotated-jobs-package-name: com.tradier.dataloader.jobs
tasks: [startjob, stopjob]
Configuration Class:
#JsonIgnoreProperties(ignoreUnknown = true)
public class DropwizardSundialConfiguration extends Configuration {
#Valid
#NotNull
public SundialConfiguration sundialConfiguration = new SundialConfiguration();
#JsonProperty("sundial")
public SundialConfiguration getSundialConfiguration() {
return sundialConfiguration;
}
}
Step 2: Add and configure the dropwizard-sundial bundle in the application class.
public class DropwizardSundialApplication extends Application<DropwizardSundialConfiguration> {
private static final Logger logger = LoggerFactory.getLogger(DropwizardSundialApplication.class);
public static void main(String[] args) throws Exception {
new DropwizardSundialApplication().run("server", args[0]);
}
#Override
public void initialize(Bootstrap<DropwizardSundialConfiguration> b) {
b.addBundle(new SundialBundle<DropwizardSundialConfiguration>() {
#Override
public SundialConfiguration getSundialConfiguration(DropwizardSundialConfiguration configuration) {
return configuration.getSundialConfiguration();
}
});
}
}
Step 3: Add the required job classes.
Here is a sample Cron job class:
#CronTrigger(cron = "0 19 13 * * ?")
public class CronJob extends Job {
private static final Logger logger = LoggerFactory.getLogger(CronJob.class);
#Override
public void doRun() throws JobInterruptException {
logger.info("Hello from Cron Job");
}
}
I have also written a blog post and a working application which is available on GitHub with these steps. Please check: http://softwaredevelopercentral.blogspot.com/2019/05/dropwizard-sundial-scheduler-tutorial.html
It appears to be a classpath issue.
From https://github.com/timmolter/Sundial/blob/develop/src/main/java/com/xeiam/sundial/SundialJobScheduler.java#L102:
public static void startScheduler(int threadPoolSize, String annotatedJobsPackageName) {
try {
createScheduler(threadPoolSize, annotatedJobsPackageName);
getScheduler().start(); // ---> Line 102
} catch (SchedulerException e) {
logger.error("COULD NOT START SUNDIAL SCHEDULER!!!", e);
throw new SchedulerStartupException(e);
}
I'm also using Sundial in my dropwizard project, I have all my jobs defined in jobs.xml, Sundial config defined in the .yaml file, and start it as follows:
SundialJobScheduler.startScheduler();
SundialManager sm = new SundialManager(config.getSundialConfiguration(),environment);
environment.lifecycle().manage(sm);
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.