Spring Boot multiple WAR files in 1 Tomcat - spring-boot

we are developing a multi-mandator shop solution for multiple countries, like Sweden, Netherlands, Germany etc. We aim to have 1 WAR file for each mandator and would like to have all of them running in 1 tomcat. Is it possible to have this integrated into Spring-Boot's embedded tomcat?

If the Mandators are different webapps/war files then you can add wars/web apps to the EmbeddedServletContainer(Tomcat), using the tomcat.addWebapp method.
In your spring-boot main class add the following bean.
#Bean
public EmbeddedServletContainerFactory servletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
// Ensure that the webapps directory exists
new File(tomcat.getServer().getCatalinaBase(), "webapps").mkdirs();
try {
Context context = tomcat.addWebapp("/Sweden","Sweden.war");
tomcat.addWebapp("/Netherlands","Netherlands.war");
tomcat.addWebapp("/Germany","Germany.war");
context.setParentClassLoader(getClass().getClassLoader());
} catch (ServletException ex) {
throw new IllegalStateException("Failed to add webapp", ex);
}
return super.getTomcatEmbeddedServletContainer(tomcat);
}
};
}

Related

Spring Boot 2.0.0.RC2 KafkaHealthIndicator, Actuator {“status”:“DOWN”}

I ported my application from Spring Boot 2.0.0.M6 to Spring Boot 2.0.0.RC2 and ran into the issue with KafkaHealthIndicator that thinks right now, that my Kafka status is DOWN.
kafka":{
"status":"DOWN",
"details":{
"clusterId":"wpAKGc_DQBWy9YfPTLNctQ",
"brokerId":"0",
"nodes":1
}
}
org.springframework.boot.actuate.kafka.KafkaHealthIndicator uses the following logic in order to determine the status:
Status status = nodes >= replicationFactor ? Status.UP : Status.DOWN;
where replication factor is retrieved by the following property: transaction.state.log.replication.factor
I have added the following properties to my Kafka server.properties:
offsets.topic.replication.factor=1
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
but it doesn't help.
What am I doing wrong and how to fix it ?
Right now, I use the temporal solution with disabling healthcheck for Kafka:
management.health.kafka.enabled=false
but I don't like it and I want to fix it.
This worked fine for me...
transaction.state.log.replication.factor=1
transaction.state.log.min.isr=1
with
#SpringBootApplication
public class So48965775Application {
public static void main(String[] args) {
SpringApplication.run(So48965775Application.class, args);
}
#Bean
public ApplicationRunner runner(KafkaHealthIndicator health) {
return args -> {
Executors.newSingleThreadExecutor().execute(() -> {
while (true) {
System.out.println(health.health());
try {
Thread.sleep(5000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
};
}
}
and
UP {clusterId=ZR4GdILXSFSIGI1wDiKNLg, brokerId=0, nodes=1}
Did you restart the broker after changing the properties?

java.io.FileNotFoundException:/BOOT-inf/classes!/templates/

i am useing springboot create a project ,it's no error run idea,but, run app.jar file ,it's run Exception like this
java.io.FileNotFoundException: class path resource [templates/] cannot be resol
ed to absolute file path because it does not reside in the file system: jar:fil
:/E:/projects/help/target/zhx-help-0.0.1-SNAPSHOT.jar!/BOOT-INF/classes!/templa
es/
at org.springframework.util.ResourceUtils.getFile(ResourceUtils.java:21
)
at org.springframework.core.io.AbstractFileResolvingResource.getFile(Ab
tractFileResolvingResource.java:52)
at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.get
emplateLoaderForPath(FreeMarkerConfigurationFactory.java:338)
at org.springframework.ui.freemarker.FreeMarkerConfigurationFactory.cre
teConfiguration(FreeMarkerConfigurationFactory.java:290)
at org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer
afterPropertiesSet(FreeMarkerConfigurer.java:116)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1687)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.initializeBean(AbstractAutowireCapableBeanFactory.java:1624)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.doCreateBean(AbstractAutowireCapableBeanFactory.java:555)
at org.springframework.beans.factory.support.AbstractAutowireCapableBea
Factory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getO
ject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegist
y.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetB
an(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBea
(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory
springboot version :1.5.2
useing spring-data-jpa
I see you use freemarker. In spring boot you cannot use the normal File approach to get your templates because they are not accessible when you run an executable JAR (File cannot be loaded as a resource when inside the JAR)
Use the following approach to load your templates folder:
cfg.setTemplateLoader(new ClassTemplateLoader(getClass().getClassLoader(), "templates"));
Full example:
#Configuration
public class FreemarkerConfiguration {
#Bean
public freemarker.template.Configuration freemarkerConfig() throws IOException {
freemarker.template.Configuration cfg = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_23);
cfg.setTemplateLoader(new ClassTemplateLoader(getClass().getClassLoader(), "templates"));
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
return cfg;
}
}
If you are using jersey with spring boot, Jersey doesn't work well with Spring Boot. The reason for this error is, jersey is not able to auto discover all the rest resources. To fix this, register all the resources explicitly, registering package doesn't work. Hope this issue will be fixed in future versions of jersey.
public class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(Resource1.class);
register(Resource2.class);
register(Resource3.class);
}
}
FreeMarkerConfigurationFactory.class
protected TemplateLoader getTemplateLoaderForPath(String templateLoaderPath) {
if(this.isPreferFileSystemAccess()) {
try {
Resource ex = this.getResourceLoader().getResource(templateLoaderPath);
File file = ex.getFile();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Template loader path [" + ex + "] resolved to file path [" + file.getAbsolutePath() + "]");
}
return new FileTemplateLoader(file);
} catch (IOException var4) {
if(this.logger.isDebugEnabled()) {
this.logger.debug("Cannot resolve template loader path [" + templateLoaderPath + "] to [java.io.File]: using SpringTemplateLoader as fallback", var4);
}
return new SpringTemplateLoader(this.getResourceLoader(), templateLoaderPath);
}
} else {
this.logger.debug("File system access not preferred: using SpringTemplateLoader");
return new SpringTemplateLoader(this.getResourceLoader(), templateLoaderPath);
}
}
so make log lever info
<logger name="org.springframework.web" level="INFO"/>

One Tomcat two spring application (war) two seperate logging configurations

As mentioned in the title I have two applications with two different logging configurations. As soon as I use springs logging.file setting I can not seperate the configurations of both apps.
The problem worsens because one app is using logback.xml and one app is using log4j.properties.
I tried to introduce a new configuration parameter in one application where I can set the path to the logback.xml but I am unable to make the new setting work for all logging in the application.
public static void main(String[] args) {
reconfigureLogging();
SpringApplication.run(IndexerApplication.class, args);
}
private static void reconfigureLogging() {
if (System.getProperty("IndexerLogging") != null && !System.getProperty("IndexerLogging").isEmpty()) {
try {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(context);
// Call context.reset() to clear any previous configuration, e.g. default
// configuration. For multi-step configuration, omit calling context.reset().
System.out.println("SETTING: " + System.getProperty("IndexerLogging"));
System.out.println("SETTING: " + System.getProperty("INDEXER_LOG_FILE"));
context.reset();
configurator.doConfigure(System.getProperty("IndexerLogging"));
} catch (JoranException je) {
System.out.println("FEHLER IN CONFIG");
}
logger.info("Entering application.");
}
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
reconfigureLogging();
return application.sources(applicationClass);
}
The above code works somehow. But the only log entry which is written to the logfile specified in the configuration, which ${IndexerLogging} points to, is the entry from logger.info("Entering application."); :(
I don't really like to attach that code to every class which does some logging in the application.
The application has to be runnable as tomcat deployment but also as spring boot application with integrated tomcat use.
Any idea how I can set the path from ${IndexerLogging} as the path to read the configuration file when first configuring logging in that application?
Take a look at https://github.com/qos-ch/logback-extensions/wiki/Spring you can configure the logback config file to use.

Jetty - stand alone WebSocket server

in these days I try to implement a WebSocket server by using Jetty.
I've created a Jetty project called "WebSocketServer" as demo-base in the distribution of Jetty 9.2.0.v20140526.
After that, I write some codes to implement the WebSocket mechanism and export all codes to a war file to push it to the webapps folder of "WebSocketServer". When I java -jar ..<jetty.home>/start.jar, it is all workable. But, after I create a new connection to this WebSocket project, there are some error codes happened.
java.lang.ClassCastException: org.eclipse.jetty.server.HttpConnection cannot be cast to org.eclipse.jetty.server.HttpConnection
at org.eclipse.jetty.websocket.server.WebSocketServerFactory.acceptWebSocket(WebSocketServerFactory.java:175)
at org.eclipse.jetty.websocket.server.WebSocketServerFactory.acceptWebSocket(WebSocketServerFactory.java:148)
at org.eclipse.jetty.websocket.servlet.WebSocketServlet.service(WebSocketServlet.java:151)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:751)
at org.eclipse.jetty.servlet.ServletHandler.doHandle(ServletHandler.java:566)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:143)
at org.eclipse.jetty.security.SecurityHandler.handle(SecurityHandler.java:578)
at org.eclipse.jetty.server.session.SessionHandler.doHandle(SessionHandler.java:221)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:1111)
at org.eclipse.jetty.servlet.ServletHandler.doScope(ServletHandler.java:498)
at org.eclipse.jetty.server.session.SessionHandler.doScope(SessionHandler.java:183)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:1045)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:141)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:199)
at org.eclipse.jetty.server.handler.HandlerCollection.handle(HandlerCollection.java:109)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:98)
at org.eclipse.jetty.server.Server.handle(Server.java:461)
at org.eclipse.jetty.server.HttpChannel.handle(HttpChannel.java:284)
at org.eclipse.jetty.server.HttpConnection.onFillable(HttpConnection.java:244)
at org.eclipse.jetty.io.AbstractConnection$2.run(AbstractConnection.java:534)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:607)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:536)
at java.lang.Thread.run(Thread.java:744)
I have no idea what's going on? The following codes are what I write to build a simple WebSocket server.
Servlet:
#SuppressWarnings("serial")
public class XYZWebSocketServlet extends WebSocketServlet{
#Override
public void configure(WebSocketServletFactory factory) {
factory.getPolicy().setIdleTimeout(600000);
factory.register(XYZWebSocketEvent.class);
}
}
Event:
#WebSocket
public class XYZWebSocketEvent {
private Session session;
#OnWebSocketConnect
public void onConnect(Session sess) {
session = sess;
// Get parameters while client connect to server
Map<String,List<String>> parameters = session.getUpgradeRequest().getParameterMap();
String encyptedID = parameters.get("ID").get(0);
System.out.println("Connect: " + session.getRemoteAddress().getPort());
try {
session.setIdleTimeout(600000);
session.getRemote().sendString("Hello!");
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
#OnWebSocketMessage
public void onMessage(String message) {
try {
session.getRemote().sendString("Message: " + message);
}
catch (Exception ex) {
}
}
#OnWebSocketClose
public void onClose(int statusCode, String reason) {
try {
session.getRemote().sendString("Close: statusCode=" + statusCode + ", reason=" +reason);
}
catch (Exception ex) {
}
}
#OnWebSocketError
public void onError(Throwable t) {
System.out.println("Error: " + t.getMessage());
}
public Session getSession() {
return this.session;
}
}
web.xml:
<?xml version="1.0" encoding="UTF-8"?>
<web-app>
<display-name>WebSocket application</display-name>
<servlet>
<servlet-name>XYZWebSocketServlet</servlet-name>
<servlet-class>com.newKinpo.servlet.XYZWebSocketServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>XYZWebSocketServlet</servlet-name>
<url-pattern>/events/*</url-pattern>
</servlet-mapping>
</web-app>
Is there something wrong? Thanks for your attention.
I had similiar problem and I have found the cause and the solution. Embeded jetty server is loaded by SUN class loader (will call it system class loader later) after webapp directory is scanned my app is loaded by WebApp class loader and when it comes to WebSocketServerFactory it is loaded by WebApp. However org.eclipse.jetty.server.HttpConnection object obtained from the request is loaded by the system class loader.
According to https://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading jetty websocket package is treated as system class package and shouldn't be loaded by the WebApp if already loaded by the system.
The solution is to force loading of the package org.eclipse.jetty.websocket at the time jetty server is initialized.
I just create dummy instance of WebSocketHandler for example. There are many options to force package loading but they are irrelevant to this question.

Spring Annotated Controllers not working with Tomcated Embedded on Heroku

I have spring annotated controllers that work fine when I am using my WAR, but when I try to run embedded, locally and on Heroku, none of the annotated controllers are working. I have some pages setup using mvc:view-controller and those work, but none of the component-scan controllers work.
package com.myapp.launch;
import java.io.File;
import javax.servlet.ServletException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;
public class Main {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
String webappDirLocation = "src/main/webapp/";
Tomcat tomcat = new Tomcat();
//The port that we should run on can be set into an environment variable
//Look for that variable and default to 8080 if it isn't there.
String webPort = System.getenv("PORT");
if(webPort == null || webPort.isEmpty()) {
webPort = "8080";
}
tomcat.setPort(Integer.valueOf(webPort));
try {
tomcat.addWebapp("/", new File(webappDirLocation).getAbsolutePath());
} catch (ServletException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("configuring app with basedir: " + new File("./" + webappDirLocation).getAbsolutePath());
try {
tomcat.start();
} catch (LifecycleException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tomcat.getServer().await();
}
}
Here is part of my spring config.
<mvc:view-controller path="/" view-name="home"/>
<mvc:view-controller path="/terms" view-name="terms"/>
<mvc:view-controller path="/privacy" view-name="privacy"/>
<context:component-scan base-package="com.myapp.controllers"/>
I found out that this was due to my controllers being groovy and those controllers were being compiled as part of a make step when I was running tomcat locally, but that same process was not being run when I launched tomcat embedded. After adding an execution goal to my gmaven plugin I was able to get this working without issue.
Since the classes were compiled by gmaven tomcat was able to pick them up.

Resources