Adding JNDI to embedded Tomcat server in Grails 3 - spring

When running test-app in grails 3.0, or run-app, grails runs its own version of the embedded Tomcat server. I was able to conclude this from the following link: https://roshandawrani.wordpress.com/2011/03/13/grails-tip-configuring-embedded-tomcat-instance-used-in-developmenttest-env/
However, the context.xml and server.xml files are precompiled with the pulled down libraries. When creating a grails app from scratch, I cannot find either of there two files. Same is true for config.groovy, as it is located within an external library.
I am trying to inject JNDI resources, into the container, so that I can invoke them. Something like this:
<Resource name="myDatasourceName" auth="Container" type="javax.sql.DataSource"
maxActive="100" maxIdle="30" maxWait="10000"
username="root" password="password" driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/my_db_name"/>
In the first link, the authors provide a way to do it in a scripts/_Events.groovy directory, but I do not have this either.
UPDATE 1: Non-working code
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration
import org.apache.catalina.Context
import org.apache.catalina.startup.Tomcat
import org.apache.tomcat.util.descriptor.web.ContextResource
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory
import org.springframework.context.annotation.Bean
#SpringBootApplication
class Application extends GrailsAutoConfiguration {
static void main(String[] args) {
GrailsApp.run(Application, args)
}
#Bean
public TomcatEmbeddedServletContainerFactory tomcatFactory() {
return new TomcatEmbeddedServletContainerFactory() {
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatEmbeddedServletContainer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
context.getNamingResources().addResource(preconfigureDbResource("oneSource", "127.0.0.1"))
context.getNamingResources().addResource(preconfigureDbResource("nextSource", "127.0.0.1"))
}
}
}
private ContextResource preconfigureDbResource(String name, String ip) {
ContextResource resource = new ContextResource()
resource.setType("javax.sql.DataSource")
resource.setName("jdbc/" + name)
resource.setProperty("url", "jdbc:oracle:thin:#" + ip + ":1521:ucop")
resource.setProperty("driverClassName", "oracle.jdbc.driver.OracleDriver")
resource.setProperty("username", "coolio")
resource.setProperty("password", "password")
resource.setProperty("auth", "Container")
resource.setProperty("maxTotal", "100")
resource.setProperty("maxIdle", "30")
resource.setProperty("maxWaitMillis", "10000")
return resource;
}
}
I am calling this source like this in my service file:
public DataSource getOneSource() {
Context context = (Context) new InitialContext().lookup("java:/comp/env")
oneSource= (DataSource) context.lookup("jdbc/oneSource")
return oneSource
}
But I am getting an error stating:
javax.naming.NameNotFoundException: Name [comp/env] is not bound in this Context. Unable to find [comp].
Has anyone done this before? I would not be surprised if there is an extra thread that is overwriting the context.

In Grails 3, you do it like this: SampleTomcatJndiApplication
Typically, in Grails web applications, this is in /grails-app/init/Application.groovy
(In my case, I commented out the jndiDataSource() part and just used postProcessContext().)
Source: Graeme Rocher

The solution to this issue is addressed in two steps. First, I had to use the child approach to setting the right context, found in this question. Setting the right context in embedded Tomcat
As imagined, The only change I then had to make was to the getTomcatEmbeddedServletContainer method. I have edited the original to look like this:
#Override
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(Tomcat tomcat) {
tomcat.enableNaming();
TomcatEmbeddedServletContainer container =
super.getTomcatEmbeddedServletContainer(tomcat);
for (Container child: container.getTomcat().getHost().findChildren()) {
if (child instanceof Context) {
ClassLoader contextClassLoader =((Context)child).getLoader().getClassLoader();
Thread.currentThread().setContextClassLoader(contextClassLoader);
break;
}
}
return container;
}
Next, I had to edit the gradle build file, to include the dbcp BasicDataSource Dependency. My gradle build file now contains:
dependencies {
// Embedded tomcat dependencies
compile "org.apache.tomcat:tomcat-dbcp:9.0.0.M1"
}

Related

How to set equivalent of web.xml JNDI <env-entry> in a Spring Boot project?

Referring to this SO answer, I'd like to setup the equivalent of this web.xml configuration in a JSF / JoinFaces / SpringBoot application (that doesn't have web.xml).
<env-entry>
<env-entry-name>jsf/ClientSideSecretKey</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>[AES key in Base64 format]</env-entry-value>
</env-entry>
Any pointers?
If you are using spring boot and embedded tomcat server, you can add <env-entry> programmatically with the following configuration.
#SpringBootApplication
public class DemoApplication extends SpringBootServletInitializer {
#Bean
public TomcatServletWebServerFactory tomcatFactory() {
return new TomcatServletWebServerFactory() {
#Override
protected TomcatWebServer getTomcatWebServer(org.apache.catalina.startup.Tomcat tomcat) {
tomcat.enableNaming();
return super.getTomcatWebServer(tomcat);
}
#Override
protected void postProcessContext(Context context) {
// adding <resource-ref>
ContextResource resource = new ContextResource();
resource.setName("jdbc/myJndiResource");
resource.setType(DataSource.class.getName());
resource.setProperty("driverClassName", "org.postgresql.Driver");
resource.setProperty("url", "jdbc:postgresql://hostname:port/dbname");
resource.setProperty("username", "username");
resource.setProperty("password", "password");
context.getNamingResources()
.addResource(resource);
// adding <env-entry>
ContextEnvironment ce = new ContextEnvironment();
ce.setName("jsf/ClientSideSecretKey");
ce.setType(String.class.getName());
ce.setValue("[AES key in Base64 format]");
context.getNamingResources().addEnvironment(ce);
}
};
}
public static void main(String[] args) throws NamingException {
SpringApplication.run(DemoApplication.class, args);
}
}
Once defined the jndi naming resources they can be accessed in your application using JndiTemplate of InitialContext.
JndiTemplate jndiTemplate = new JndiTemplate();
String str = (String) jndiTemplate.lookup("java:comp/env/jsf/ClientSideSecretKey");
Hope this helps you in resolving your problem.
Essentially <env-entry> declares a web application context attribute.
You can initialize your servlet context and provide the equivalent servlet context attributes in your Spring Boot application.
For that purpose, you can register a bean that implements the ServletContextInitializer interface (or WebApplicationInitializer if your app has to be deployed in a traditional servlet container). For example:
public class JsfServletContextInitializer implements ServletContextInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setAttribute("jsf/ClientSideSecretKey", "[AES key in Base64 format]");
}
}
Do not forget to register it as a bean in your configuration.

Spring boot - Embedded Tomcat - Connector Customizer - fail to add parseBodyMethods attributes

The original problem is when I sent a http request with method 'DELETE', the body part couldn't be sent to the server.
After googling, I found this article that suggests modifying the server.xml file and adding 'parseBodyMethods' to the Connector part can solve the problem:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
parseBodyMethods="POST,PUT,DELETE"
redirectPort="8443" />
However, because I'm using spring's embedded tomcat, I have to find a way to do the same in spring's way. So, I found this article that seems to allow me to add ConnectorCustomizer and add additional attribute to the Connector. The following is my code:
public class MyTomcatConnectorCustomizer implements EmbeddedServletContainerCustomizer {
#Override
public void customize(ConfigurableEmbeddedServletContainer factory) {
if(factory instanceof TomcatEmbeddedServletContainerFactory) {
customizeTomcat((TomcatEmbeddedServletContainerFactory) factory);
}
}
public void customizeTomcat(TomcatEmbeddedServletContainerFactory factory) {
TomcatEmbeddedServletContainerFactory tomcat = (TomcatEmbeddedServletContainerFactory) factory;
tomcat.addConnectorCustomizers(connector -> {
connector.setAttribute("parseBodyMethods", "POST,PUT,DELETE");
});
}
}
#Bean
MyTomcatConnectorCustomizer myTomcatConnectorCustomizer() {
MyTomcatConnectorCustomizer myTomcatConnectorCustomizer = new MyTomcatConnectorCustomizer();
return myTomcatConnectorCustomizer;
}
But still, the same issue exists. the body is still empty when I send a 'DELETE' request to the server. Does anyone have encountered the same issue before? Help appreciated!
change
connector.setAttribute("parseBodyMethods", "POST,PUT,DELETE");
to
connector.setParseBodyMethods("POST,PUT,DELETE")
or just
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory(){
#Override
protected void customizeConnector(Connector connector) {
super.customizeConnector(connector);
connector.setParseBodyMethods("POST,PUT,DELETE");
}
};
}
Yes, I know this is a bit old question but I stumbled upon this issue and found that SO don't have a solution for Spring Boot version 2.0.0 and later.
I had found this article on Baeldung which describes the changes in SB 2 versus SB 1.
Quoting the article (code emphasis is mine):
In Spring Boot 2, the EmbeddedServletContainerCustomizer interface is replaced by WebServerFactoryCustomizer, while the ConfigurableEmbeddedServletContainer class is replaced with ConfigurableServletWebServerFactory.
So to fix your issue you need to implement interface WebServerFactoryCustomizer from package org.springframework.boot.web.server and parametrize it with TomcatServletWebServerFactory and override it's customize method.
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;
#Component
public class MyTomcatConnectorCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
#Override
public void customize(TomcatServletWebServerFactory factory) {
TomcatConnectorCustomizer parseBodyMethodCustomizer = connector -> {
connector.setParseBodyMethods("POST,PUT,DELETE");
};
factory.addConnectorCustomizers(parseBodyMethodCustomizer);
}
}

Tomcat 8, Spring Boot, #Configurable LoadTimeWeaving without -javaagent?

I'm trying to setup a #Configurable domain object(not managed by the spring container).
I've got this working by adding the -javaagent:path/to/spring-instrument.jar as a JVM argument but it's not 100% clear to me whether or not this -javaagent MUST be in place. I'm running this on Tomcat 8. I may be misinterpreting the documentation but it seems I may be able to use a another mechanism to accomplish this, in particular this line:
Do not define TomcatInstrumentableClassLoader anymore on Tomcat 8.0 and higher. Instead, let Spring automatically use Tomcat’s new native InstrumentableClassLoader facility through the TomcatLoadTimeWeaver strategy.
Code Samples below:
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
public MyService myService(){
return new MyService();
}
}
#Configurable
public class MyDomainObject {
#Autowired
private MyService myService;
public MyService getMyService(){
return myService;
}
}
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void test(){
log.info("test");
}
}
So is there a way to get these #Configrable objects woven without specifying the -javaagent? I'd be interested in learning if I can accomplish this when deploying as WAR to a Standalone Tomcat 8 server and/or using the embedded Tomcat 8 server when launching as a 'fat' jar.
As it stands deploying to Stand alone Tomcat 8 server doesn't throw an error but the getMyService() method above returns null. Launching as a fat jar throws the following error during startup:
Caused by: java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar
I suppose the real question is how do I Specify a custom LoadTimeWeaver in Tomcat 8? Nothing seems to be automatically happening as the documentation states but again I may be misinterpreting what that means exactly.
you can try this :
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
or
there is a new library that just solves to dynamically setup spring InstrumentationLoadTimeWeaver to enable support for aspects without having to start the JVM with an explicit java agent
<dependency>
<groupId>de.invesdwin</groupId>
<artifactId>invesdwin-instrument</artifactId>
<version>1.0.2</version>
</dependency>
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication{
public static void main(final String[] args) {
DynamicInstrumentationLoader.waitForInitialized(); //dynamically attach java agent to jvm if not already present
DynamicInstrumentationLoader.initLoadTimeWeavingContext(); //weave all classes before they are loaded as beans
SpringApplication.run(TestApplication.class, args); //start application, load some classes
}
}
what about creating your own annotation #MyConfigurable ? so you can do what ever you like when it's methods are called.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.CONSTRUCTOR)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConfigurable
{}

Spring Boot: Web.xml and embedded server jar

I'm trying to convert a legacy spring-mvc app to Spring boot (in order to have a self contained JAR enabling easier upgrade to Java-8).
I see no reason to use replace my existing web.xml file with code as the code looks like configuration and web.xml is more established.
Is it possible to use my existing web.xml in a Spring Boot application (in embedded JAR mode)?
Edit: I also want to avoid using #EnableAutoConfiguration
Thanks
ok, thanks to Mecon, I'm slightly closer. I had to remove the ContextLoaderListener in the web.xml; also had to import the xml Spring config even though it was referenced in the contextConfigLocation.
#Configuration
#ComponentScan
#EnableAutoConfiguration
#ImportResource(value = {"classpath:/webapp-base.xml"})
public class WebApp {
#Autowired
private ServerProperties serverProperties;
#Autowired
private MediaConfiguration mediaConfig;
#Bean
public EmbeddedServletContainerFactory servletContainer() {
JettyEmbeddedServletContainerFactory factory = new JettyEmbeddedServletContainerFactory();
factory.setContextPath(serverProperties.getContextPath());
factory.addConfigurations(new WebXmlConfiguration());
factory.addServerCustomizers(server -> {
List<Handler> resourceHandlers = getResourceHandlers();
Handler original = server.getHandler();
HandlerList handlerList = new HandlerList();
Handler[] array = getHandlers(original, resourceHandlers);
handlerList.setHandlers(array);
server.setHandler(handlerList);
}
);
return factory;
}
private List<Handler> getResourceHandlers() {
return mediaConfig.getMappings().stream().map(m -> {
ContextHandler contextHandler = new ContextHandler(m.getUrlpath());
ResourceHandler resourceHandler = new ResourceHandler();
resourceHandler.setResourceBase(m.getFilepath());
contextHandler.setHandler(resourceHandler);
return contextHandler;
}).collect(Collectors.toList());
}
private Handler[] getHandlers(Handler original, List<Handler> resourceHandlers) {
ArrayList<Handler> handlers = new ArrayList<>();
handlers.add(original);
handlers.addAll(resourceHandlers);
return handlers.toArray(new Handler[resourceHandlers.size()+1]);
}
public static void main(String[] args) {
SpringApplication.run(WebApp.class, args);
}
}
You don't need Spring-Boot to have a self-contained JAR, all you really need is Embedded Tomcat, or Jetty.
Create a class with public static void main(String[] a), and this Class will be used when the Jar is "executed" by java -jar myWebapp.jar command.
In the main method, you can fire up the Embedded Tomcat or Jetty, and make it load your webapp by referring to existing web.xml.

META-INF/resources not works properly with #EnableWebMVC in Spring Boot

1.
I'm working with Spring Boot. My Main class very simple
#ComponentScan
#EnableAutoConfiguration
#Configuration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#2. Now I would like to make my static content externalised into a jar file. So, below is the jar project
/pom.xml
/src/main/resources/META-INF/resources/hello.json // here is my resource
I do maven install and put the dependency into the main app, run the app normally. Now I can invoke http://localhost:8080/hello.json to get my hello.json file
#3. Then, the next step is using the Apache Tiles for my main web project, so I create a #EnableWebMvc class to configure the tilesViewResolver
#Configuration
#EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
public #Bean TilesViewResolver tilesViewResolver() {
return new TilesViewResolver();
}
public #Bean TilesConfigurer tilesConfigurer() {
TilesConfigurer ret = new TilesConfigurer();
ret.setDefinitions(new String[] { "classpath:tiles.xml" });
return ret;
}
}
Then I started again the application and try the hello.json to ensure everything still works properly. But, the 404 page appear. Delete the WebMvcConfiguration give back my hello.json.
What configuration I should do to resolve this issue?
Thanks a lot.
In Spring MVC, using XML configuration, you have to have a tag like the following to service static content:
<mvc:resources mapping="/js/**" location="/js/"/>
This insinuates that Spring Boot is doing something to automatically guess that you have static content and properly setup the above example in META-INF/resources. It's not really "magic", but rather that they have a default Java Configuration using #EnableWebMvc that has some pretty reliable default values.
When you provide your own #EnableWebMvc, my guess is you are over-writting their "default" one. In order to add back a resource handler, you would do something like this:
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/js/**").addResourceLocations("/js/").setCachePeriod(31556926);
}
This is equivalent to the XML above.

Resources