How are IntelliJ 13, Tomcat 7 (Servlet 3), Spring 4 and context/servlet mappings related - spring

If you take a empty IntelliJ 13 project setup for Spring 4 and Tomcat, configured entirely with Java (no XML) you could initialize your dispatcher servelet and Tomcat with something like this:
1
public class WebAppInit extends AbstractAnnotationConfigDispatcherServletInitializer
{
#Override
protected Class<?>[] getRootConfigClasses()
{
return new Class<?>[]{SpringConfig.class};
}
#Override
protected Class<?>[] getServletConfigClasses()
{
return new Class<?>[]{SpringWebMVCConfig.class};
}
#Override
protected String[] getServletMappings()
{
return new String[]{"/url"};
}
#Override
protected Filter[] getServletFilters()
{
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
return new Filter[]{characterEncodingFilter};
}
}
2
Then there is this in Run Configuration in IntelliJ13:
3
Finally there is the mapping of the rest controller:
#RestController
public class RootController
{
#Autowired
private SomeDAO someDAO;
#RequestMapping(value = "/")
public String root()
{
return someDAO.getStuff();
}
}
The question I have is what controls what?
A) If I change the mapping in 1 the request mappings (3) no longer work.
B) If I set 1 and 3 to "/" and change 2 to .../url then I hit the controller with .../url in the browser.
C) But if I set 1 and 2 to .../url2 then I get 404 when I go to .../url2 in the browser.
I always thought that 1 sets the mapping for the Spring dispatcher servlet, 2 sets the app context as far as the IDE is concerned (similarly to if you named your war file "url" and then dropped into webapps) and 3 is just a url mapping relative to the servlet context. If that's the case then I don't understand why case C results in 404.

In IntelliJ, you're configuring the context path of the web application to /url2. That means that all the requests to URLs starting by /url2/ are routed by Tomcat to a component of the webapp, whereas all the other requests are routed by Tomcat to another webapp (or result in a 404 if no webapp is mapped to the URL)
In the Spring webapp configuration, you're configuring the path, inside the webapp, to which the Spring servlet is mapped. And you're configuring it to /url2. So, when you remove the context path from the URL (which is used to choose the webapp), the resulting path is then inspected and, if it is /url2, then it goes to the Spring servlet. Othewise, it goes to another resource in the webapp.
The end result is that, to hit the Spring servlet, you need a path like
http://localhost:8080/url2/url2
Note that mapping the Spring servlet to a path like /url2 doesn't make much sense, because only this path would lead to the servlet, which is supposed to be used for many different paths, to which controllers are mapped. You probably want to mapp it to /url2/* which would mean that all the requests for a path, within the webapp (so after the context path), starting with /url2/, would go to the Spring servlet.

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();
}
}

Amazon Web Services - Deploy Spring Boot Application

I have a spring boot application that I am deploying onto EB (Elastic Beanstalk) AWS. The app works fine locally, however, I get a 404 on all pages I try to access on the deployed version. Also, I can't access any of the static content under the static folder either. All REST endpoints work fine.
My project structure is as follows
-- src
-- main
-- kotlin
-- resources
-- static
-- css
-- fonts
-- images
-- js
-- templates (contains html files)
I've tried defining option_settings in a .config file under a .ebextensions folder
option_settings
aws:elasticbeanstalk:container:java:staticfiles:
/public/: WEB-INF/classes/static
/static/: WEB-INF/classes/staticenter code here
I've also tried adding the following to my spring configuration class
#Configuration open class MvcConfig : WebMvcConfigurerAdapter() {
override fun addViewControllers(registry: ViewControllerRegistry) {
registry.addViewController("/").setViewName("homepage")
registry.addViewController("/index").setViewName("homepage")
registry.addViewController("/home").setViewName("homepage")
registry.addViewController("/homepage").setViewName("homepage")
registry.addViewController("/login").setViewName("login")
registry.addViewController("/products").setViewName("productsList")
registry.addViewController("/productdetail").setViewName("productDetail")
}
#Bean
open fun viewResolver(): ViewResolver {
val bean = InternalResourceViewResolver()
bean.setPrefix("/templates/")
bean.setSuffix(".html")
return bean
}
override fun addResourceHandlers(registry: ResourceHandlerRegistry?) {
// Register resource handler for all static folders
registry!!.addResourceHandler("/resources/**").addResourceLocations("classpath:/statics/")
.setCacheControl(CacheControl.maxAge(2, TimeUnit.HOURS).cachePublic())
}
#Bean
open fun errorPageFilter(): ErrorPageFilter {
return ErrorPageFilter()
}
#Bean
open fun disableSpringBootErrorFilter(filter: ErrorPageFilter): FilterRegistrationBean {
val filterRegistrationBean = FilterRegistrationBean()
filterRegistrationBean.filter = filter
filterRegistrationBean.isEnabled = false
return filterRegistrationBean
}
I am deploying onto tomcat8 that sits behind an Apache proxy server.
My EB settings are correct as I tried to deploy a simpler application onto the instance, which worked fine.
Please let me know if there are any further details I need to provide
The issue was related to the resourceHandler, the classpath it self was never added to the resource handler, even though servlet logging was indicating it was.
Spring by default maps the following paths
classpath:META-INF/resources/
classpath:resources/
classpath:static/
classpath:public/
needed to add the /** to the mapping. The solution was to modify the code to the following
override fun addResourceHandlers(registry: ResourceHandlerRegistry?) {
registry!!.addResourceHandler("/**", "/static/**")
.addResourceLocations("classpath:/templates/", "classpath:/static/").setCacheControl(CacheControl.maxAge(2,TimeUnit.HOURS).cachePublic())
}
#Bean
open fun viewResolver(): ViewResolver {
val bean = InternalResourceViewResolver()
bean.setSuffix(".html")
return bean
}

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.

Camel: use datasource configured by spring-boot

I have a project and in it I'm using spring-boot-jdbc-starter and it automatically configures a DataSource for me.
Now I added camel-spring-boot to project and I was able to successfully create routes from Beans of type RouteBuilder.
But when I'm using sql component of camel it can not find datasource. Is there any simple way to add Spring configured datasource to CamelContext? In samples of camel project they use spring xml for datasource configuration but I'm looking for a way with java config. This is what I tried:
#Configuration
public class SqlRouteBuilder extends RouteBuilder {
#Bean
public SqlComponent sqlComponent(DataSource dataSource) {
SqlComponent sqlComponent = new SqlComponent();
sqlComponent.setDataSource(dataSource);
return sqlComponent;
}
#Override
public void configure() throws Exception {
from("sql:SELECT * FROM tasks WHERE STATUS NOT LIKE 'completed'")
.to("mock:sql");
}
}
I have to publish it because although the answer is in the commentary, you may not notice it, and in my case such a configuration was necessary to run the process.
The use of the SQL component should look like this:
from("timer://dbQueryTimer?period=10s")
.routeId("DATABASE_QUERY_TIMER_ROUTE")
.to("sql:SELECT * FROM event_queue?dataSource=#dataSource")
.process(xchg -> {
List<Map<String, Object>> row = xchg.getIn().getBody(List.class);
row.stream()
.map((x) -> {
EventQueue eventQueue = new EventQueue();
eventQueue.setId((Long)x.get("id"));
eventQueue.setData((String)x.get("data"));
return eventQueue;
}).collect(Collectors.toList());
})
.log(LoggingLevel.INFO,"******Database query executed - body:${body}******");
Note the use of ?dataSource=#dataSource. The dataSource name points to the DataSource object configured by Spring, it can be changed to another one and thus use different DataSource in different routes.
Here is the sample/example code (Java DSL). For this I used
Spring boot
H2 embedded Database
Camel
on startup spring-boot, creates table and loads data. Then camel route, runs "select" to pull the data.
Here is the code:
public void configure() throws Exception {
from("timer://timer1?period=1000")
.setBody(constant("select * from Employee"))
.to("jdbc:dataSource")
.split().simple("${body}")
.log("process row ${body}")
full example in github

Compatibility of ContainerResponseFilter in jersey 1.17

Can i run my CustomFilter extended with ContainerResponseFilter in jersey1.17.
I am using GrizzlyWebServer. Please suggest . Given below is my sample server code to add the filter.
GrizzlyWebServer webServer = new GrizzlyWebServer(.............);
....
....
ServletAdapter adapter3 = new ServletAdapter();
adapter3.addInitParameter("com.sun.jersey.config.property.packages", "com.motilink.server.services");
adapter3.setContextPath("/");
adapter3.setServletInstance(new ServletContainer());
adapter3.addContextParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, PoweredbyResponseFilter.class.getName());
webServer.addGrizzlyAdapter(adapter3, new String[]{"/"});
...
.....
MY Filter:
#FrontierResponse
#Provider
public class PoweredbyResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
System.out.println("hell");
responseContext.getHeaders().add("X-Powered-By", "Jersey :-)");
}
}
Resource Class:
#NameBinding
#Retention(value = RetentionPolicy.RUNTIME)
public #interface FrontierResponse {
}
#GET
#Produces("text/plain")
#Path("plain")
//#FrontierResponse
public String getMessage() {
System.out.println("hello world called");
return "Hello World";
}
and finally i call it from a browser
http:// localhost:4464/plain
Add the ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS property as a init-param and not as a context-param:
...
adapter3.addInitParameter(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS, PoweredbyResponseFilter.class.getName());
...
EDIT 1
From your answer it seems that you're actually trying to use Jersey 1.x (1.17) runtime with implemented JAX-RS 2.0 providers (ContainerRequestContext and ContainerResponseContext have been introduced in JAX-RS 2.0 and Jersey 1.x doesn't know how to use them).
So my advice would be - drop all your Jersey 1.17 dependencies and replace them with Jersey 2.x dependencies. Take a look at our helloworld-webapp example (particularly at App class) to see how to create a Grizzly server instance with JAX-RS application.
Note that it is sufficient to add just ServerProperties.PROVIDER_PACKAGES property to init-params and your Resources and Providers (incl. response filters) will be scanned and registered in the application.

Resources