Why are GORM Data Services are not injected into ordinary Grails services when deployed to Tomcat?
We have a couple of domain/service/dataservice sets like the following:
//grails-app/services
class BeerService {
BeerDataService beerDataService
List<BeerEntity> list() {
beerDataService.list() << throws NPE
}
}
//grails-app/services
#grails.gorm.services.Service(BeerEntity)
interface BeerDataService {
List<BeerEntity> list()
}
//grails-app/domain
class BeerEntity {
String type
}
This works as expected using bootRun or as a runnable jar (in any environment), but when deployed in Tomcat (8 or 9) the data services are not registered as beans (aka. not present in applicationContext).
Using them therefore results in NPE’s.
Fetching them from the applicationContext directly (via #Autowired or Holders) results in NoSuchBeanDefinitionException being thrown.
I’ve verified the problem in a sample project (using generate-all) deployed to a local Tomcat 8 using different Java 8 versions.
Data Services are not injected when running in Tomcat, neither in controllers nor other services.
Any thoughts on why?
I’m completely lost!
Environment:
Java: 8.0.322-tem and 8.0.302-open
Tomcat: 9.0.31 and 8.5.78
OS: Ubuntu 20.04
From gradle.properties:
grailsVersion=5.1.7
groovyVersion=3.0.7
gorm.version=7.2.1
Related
I am using Kotlin + Gradle and trying to build a war file to deploy on Tomcat. My application is from the https://start.spring.io plus a simple controller and build the war file using ./gradlew bootWar
#SpringBootApplication
class ServletInitializer : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
return application.sources(DemoApplication::class.java)
}
}
#RestController
class TomcatController {
#GetMapping("/hello")
fun sayHello(): Collection<String> {
return IntStream.range(0, 10)
.mapToObj { i: Int -> "Hello number $i" }
.collect(Collectors.toList())
}
}
when I try to access it I get
Type Status Report
Message The requested resource [/demo-0.0.1-SNAPSHOT/hello] is not available
Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
I am super stuck. What am I doing wrong? If I add a html file to the src/main/webapp/index.html it shows up for some reason only the rest api can't be reached.
Spring Boot applications come with a built in Servlet. You are probably already using this feature when launching the application inside your IDE.
This basically means that you can just run your .jar file on any web server and it will be ready to go without setting up an extra tomcat instance.
However, if you want to build a Spring Boot application as a war file and deploy it to an external tomcat, you need to follow some extra steps as explained in this article.
Assuming from what you posted so far: the path that is returned shows another route before your actual controller route "/demo-0.0.1-SNAPSHOT/hello" is this "/demo-0.0.1-SNAPSHOT" the path that your application runs on ? If not it should be included in your controller (assuming you havent set it elsewhere for e.g. in your application.properties).
for e.g. http://localhost:8080/ would be the basepath and either http://localhost:8080/demo-0.0.1-SNAPSHOT/hello or http://localhost:8080/hello would point to your controller. Also your startup logs (for Tomcat and Spring) might give away more about the issue.
Am developing application using Maven it has EJB layer. I configured datasource in WebSphere Liberty server. All transaction can be handle by the server. Am using Jenkins to build a application. I would like to create CI/CD implementation. For that I tried add Junit test in application. but am unable to connect database while doing Jenkins build. Because there is no communication b/w server and Jenkins while doing build. How can I create Junit that handle database connection and EJB without Mock.?
One possible solution you could use here is an integration testing library called MicroShed Testing. It is created for integration tests with JavaEE/MicroProfile app servers such as Liberty, and can be used to test your application with external resources running such as DBs.
MicroShed Testing is ideal if you are running your Liberty application inside of a Docker container. If you are running in Docker, you can easily write an integration test that looks something like this:
#MicroShedTest
#SharedContainerConfig(AppContainerConfig.class)
public class DatabaseTest {
#Inject
public static MyJAXRSEndpoint personSvc;
#Test
public void testGetPerson() {
Long bobId = personSvc.createPerson("Bob", 24);
Person bob = personSvc.getPerson(bobId);
assertEquals("Bob", bob.name);
assertEquals(24, bob.age);
}
}
To get this working, you can define your application topology in the class referenced by the #SharedContainerConfig annotation like this:
public class AppContainerConfig implements SharedContainerConfiguration {
#Container
public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>()
.withNetworkAliases("testpostgres")
.withDatabaseName("testdb");
#Container
public static MicroProfileApplication app = new MicroProfileApplication()
.withEnv("POSTGRES_HOSTNAME", "testpostgres")
.withEnv("POSTGRES_PORT", "5432")
.withAppContextRoot("/myservice");
#Override
public void startContainers() {
postgres.start();
app.start();
}
}
The above code will do the following steps:
1) Build your application into a Docker container, using the Dockerfile in your repo
2) Start up the docker container for your app AND the postgresql database (or any other DB container you may need)
3) Wait for containers to be ready, then run tests that invoke HTTP requests on the running containers
I find this approach nice because it works the same way anywhere Docker is installed -- either locally on your machine or in your CI/CD pipeline.
For more info on MicroShed Testing, I recommend checking out the website here:
https://microshed.org/microshed-testing/
and especially the examples, which include a Liberty+Database example:
https://microshed.org/microshed-testing/features/99_Examples.html
Disclaimer: I work on Liberty and MicroShed Testing
I have application written in Spring Boot 2 and REST API. When I run this app on embeded Tomcat server via bootRun gradle task everything is fine.
The problem is that when this application is deployed on standalone Tomcat 8.5 server response is truncated to 8kB. Why is that?
My REST controller:
#RestController
public class ApiController {
public ResponseEntity<Mono<ResultData>> get(String param) {
// generating data
return ResponseEntity.ok(Mono.just(ResultData.builder()
.data(data)
.build()));
}
}
Solved. I have not extended SpringBootServletInitializer (https://docs.spring.io/spring-boot/docs/current/reference/html/howto-traditional-deployment.html) - when you would like to run Spring Boot app as deployable war you have to do this.
My WAR application use non-Spring library (for JSF). The library initializes using servletContext.getResource("page.html"). page.html is within a JAR in WEB-INF/lib, packaged as META-INF/resources/page.html
That works excellent when I deploy WAR on servlet container. But when I run application as executable WAR it does not work because embedded servlet container does not scan classpath META-INF/resources.
For example for Undertow classpath resource manager is not used:
private ResourceManager getDocumentRootResourceManager() {
File root = getCanonicalDocumentRoot();
if (root.isDirectory()) {
return new FileResourceManager(root, 0);
}
if (root.isFile()) {
return new JarResourceManager(root);
}
return ResourceManager.EMPTY_RESOURCE_MANAGER;
}
https://github.com/spring-projects/spring-boot/blob/master/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java#L466
Time for question: why embedded servlet container ignores META-INF/resources? It is a problem to make executable Servlet 3.0 applications.
Similar issues:
Embedded Tomcat, executable jar, ServletContext.getRealPath()
https://github.com/spring-projects/spring-boot/issues/4218
I solved my problem with the following configuration:
#Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
UndertowEmbeddedServletContainerFactory factory =
new UndertowEmbeddedServletContainerFactory();
factory.addDeploymentInfoCustomizers(new UndertowDeploymentInfoCustomizer() {
#Override
public void customize(DeploymentInfo deploymentInfo) {
deploymentInfo.setResourceManager(
new ClassPathResourceManager(deploymentInfo.getClassLoader(),
"META-INF/resources"));
});
return factory;
}
Generally, I think that it is underdocumented how embedded web container behaves. Spring Boot developers are happy if embdedded web container offers some subset of features to application, people who migrate existing application expects embedded container provides all features of regular container. For example ServletContainerInitializers are ignored in embedded: https://github.com/spring-projects/spring-boot/issues/321
I am trying to use Spring MVC 4's Rest templates to support google protocol buffers as message format. I have am following this post on Spring framework blog
spring-mvc-google-protocol-buffers
I checked out the sourceCode trying to implement it in my environment.
I have two issues- I cannot get it to compile when I turn Java.version to 1.6 and i cannot get it to work as a webapp (don't know what
will be the context-root of the converted war file)
-Details-
I have a requirement to make this code work as a web-app and deploy on java6 container (weblogic 10.3.6 -servlet 2.5 compliant)
So i changed the java 8 features from the codebase to make it Java 6 compatible.
The only problem is when I change the pom.xml's following section
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.DemoApplication</start-class>
<java.version>1.8</java.version>
</properties>
to change the java.version to 1.6 value, then try to do mvn clean install , the DemoApplicationTests class fails to compile with this error.
-google-protocol-buffers-master\src\test\java\demo\DemoApplicationTests.java:28: cannot find symbol
[ERROR] symbol : constructor RestTemplate(java.util.List<org.springframework.http.converter.protobuf.ProtobufHttpMessageConverter>) is not defined
[ERROR] location: class org.springframework.web.client.RestTemplate
The following link shows that Spring codebase normally doesn't have any Java 8 specific source code so not sure why this code only compiles in Java 8
https://spring.io/blog/2015/04/03/how-spring-achieves-compatibility-with-java-6-7-and-8
--------------------------------------------------------------------------------
The following link shows how to convert a spring boot application to a WAR app.
I did change the pom.xml packaging option to war.
The code gets build by mvn clean install without issues and the .war file gets generated.
But there's no web.xml - so i cannot tell what will be the context-root of the deployed web app.
I either way deployed the webapp on weblogic 10.3.6 ( which is java 6 compatible)
and it deployed fine.
But when I run the DemoApplicationTests (that I have changed to point straight to the URL
using this call (got the context-root from the weblogic console by clicking on the deployed web app)
ResponseEntity<CustomerProtos.Customer> customer = restTemplate.getForEntity(
"http://127.0.0.1:7001/demo-0.0.1-SNAPSHOT/customers/2", CustomerProtos.Customer.class);
I keep getting 404 not found error.
I have put up my changed code here.
https://github.com/robinbajaj123/spring-and-google-protocol-buffers
Your feedback will be appreciated.
You'd need to convert the Spring Boot app to also be a valid Servlet application. If you were using Servlet 3 or later and chose a .war-based deployment from start.spring.io you'd get a ServletIntializer which is a Java class that is the programmatic equivalent of web.xml. Since you're using 2.5, not 3.0, you need an explicit web.xml. You might check out this sample on how to get a Boot app hoisted up in a Servlet 2.5 environment, though using Servlet 2.5 is not recommended!. It's worth mentioning that Servlet 3.0 support was introduced in 2009..
Finally, this code uses Java 8 lambdas. You'll need to replace the lambdas with Java 6-equivalent code. One example I see is:
#Bean
CustomerRepository customerRepository() {
...
The last line in the #Bean definition returns a lambda: customers::get. Replace it with:
final Map<Integer, CustomerProtos.Customer> customers =
new ConcurrentHashMap<Integer, CustomerProtos.Customer>();
return new CustomerRepository() {
public CustomerProtos.Customer findById(int id) {
return customers.get( id) ;
}
};
Similarly, replace the forEach method in the List w/ an old-school for-in loop:
for (CustomerProtos.Customer c : Arrays.asList( ... )) {
customers.put(c.getId(), c);
}