Error 406 on Spring 3+Google App Engine on Android Studio - spring

I am using Google App Engine to develop a backend module provided by Android Studio. I am also using Spring 3 to map URL to class.Although,I have successfully used the combo on Eclipse.But on Android Studio getting a peculiar error(May be because of gradle error).Consider the following files-
build.gradle
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.google.appengine:gradle-appengine-plugin:1.9.18'
}
}
repositories {
jcenter();
}
apply plugin: 'java'
apply plugin: 'war'
apply plugin: 'appengine'
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
dependencies {
appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.18'
compile 'javax.servlet:servlet-api:2.5'
compile 'org.springframework:spring-webmvc:4.0.0.RELEASE'
compile 'com.google.code.gson:gson:2.5'
}
appengine {
downloadSdk = true
appcfg {
oauth2 = true
}
}
MyServlet.java . When I am accessing conventional MyServlet the Gson get successfully loaded and returns the required JSON.
public class MyServlet extends HttpServlet {
#Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("application/json");
Gson gson = new Gson();
Shop sh = new Shop();
sh.setName("Gufran Kurshid");
sh.setAddress("Pitampura new Delhi");
sh.setId(5235);
resp.getWriter().println(gson.toJson(sh));
}
}
MyController.java - A normal controller URL /hello is also easily accessible.But /getDummyJSON which is made to return JSON is giving Error 406.
#Controller
#RequestMapping("/greet")
public class MyController {
#RequestMapping("/hello")
public ModelAndView helloDost() {
ModelAndView modelAndView = new ModelAndView("rat");
modelAndView.addObject("message", "Hello Dost");
return modelAndView;
}
#RequestMapping(value = "/getDummyJSON", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.GET)
public
#ResponseBody
Map getDummyJSON() {
Map map = new HashMap<String, Object>();
map.put("status", true);
map.put("message", "Hello Friends");
return map;
}
}
My study says the Spring is not able map and load Gson classes.Is there any thing wrong with Android Studio or my Gradle file ?

Related

NoSuchMethodException QueryDSL with Spring Boot & Spring Data Mongo

I am trying to implement Query DSL on my Spring Boot 2.0.4.RELEASE app that uses Spring Data Mongo 2.0.4.RELEASE & Gradle 4.10.
I am using Spring Tool Suite for running it locally.
Did the following steps which I found from multiple sources including Spring data documentation:
created gradle/querydsl.gradle which has below content to generate Q classes
apply plugin: "com.ewerk.gradle.plugins.querydsl"
sourceSets {
main {
java {
srcDir "$buildDir/generated/source/apt/main"
}
}
}
querydsl {
springDataMongo = true
querydslSourcesDir = "$buildDir/generated/source/apt/main"
}
dependencies {
compile "com.querydsl:querydsl-mongodb:4.1.4"
compileOnly "com.querydsl:querydsl-apt:4.1.4"
}
sourceSets.main.java.srcDirs = ['src/main/java']
Calling above gradle file from main build.gradle as shown below
buildscript {
ext { springBootVersion = "2.0.4.RELEASE" }
repositories { mavenCentral() }
dependencies {
classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}"
classpath "gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.9"
}
}
plugins {
id "java"
id "eclipse"
id "org.springframework.boot" version "2.0.4.RELEASE"
id "io.spring.dependency-management" version "1.0.6.RELEASE"
}
sourceCompatibility = 1.8
repositories { mavenCentral() }
dependencies {
...
compile("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
compile("org.springframework.boot:spring-boot-starter-data-mongodb:${springBootVersion}")
...
}
apply from: 'gradle/querydsl.gradle'
/* Added this because Eclipse was not able to find generated classes */
sourceSets.main.java.srcDirs = ['build/generated/source/apt/main','src/main/java']
compileJava.dependsOn processResources
processResources.dependsOn cleanResources
After this updated the Repository annotated interface as below. Note: I also use Fragment Repository FragmentOrderRepository for some custom queries.
public interface OrderRepository<D extends OrderDAO>
extends EntityRepository<D>, PagingAndSortingRepository<D, String>, FragmentOrderRepository<D>, QuerydslPredicateExecutor<D> {}
Then in controller created a GET mapping as shown here
#RestController
public class OrderController {
#GetMapping(value="/orders/dsl", produces = { "application/json" })
public ResponseEntity<List> getOrdersDSL(#QuerydslPredicate(root = OrderDAO.class) Predicate predicate, Pageable pageable, #RequestParam final MultiValueMap<String, String> parameters) {
return (ResponseEntity<List>) orderService.getTools().getRepository().findAll(predicate, pageable);
}
}
Then in my runner class I added EnableSpringDataWebSupport annotation
#SpringBootApplication
#EnableSpringDataWebSupport
public class SampleApp {
public static void main(String[] args) {
SpringApplication.run(SampleApp.class, args);
}
}
With this my app starts up without any errors but when I try hitting the path http://localhost:5057/orders/dsl?email=test#test.com
I get a NoSuchMethodException with message No primary or default constructor found for interface com.querydsl.core.types.Predicate.
Can anyone please help with some pointers to solve this issue?
It seems that parameters are not getting resolved to a type.
---- UPDATE 09/19/19 ----
While debugging I found that a class HandlerMethodArgumentResolverComposite which finds ArgumentResolver for given MethodParameter from a List of argumentResolvers(of type HandlerMethodArgumentResolver). This list does not contain QuerydslPredicateArgumentResolver. Hence it is not able to resolve the arguments.
This means QuerydslWebConfiguration which adds above resolver for Predicate type is not getting called, which in turn indicates that some AutoConfiguration is not happening.
Probably I am missing some annotation here.
Found the mistake I was doing, was missing EnableWebMvc annotation on my Configuration annotated class.
Details are in this documentation.

java.net.ProtocolException: unexpected end of stream (implementation 'com.squareup.okhttp3:okhttp:4.2.0')

I am getting this exception:
java.net.ProtocolException: unexpected end of stream W/System.err: at okhttp3.internal.http1.Http1ExchangeCodec$FixedLengthSource.read(Http1ExchangeCodec.kt:392)
I am trying to do SOAP Request. I'm not always getting this exception. Sometimes I get the right answer from the web server.
I changed the parser library from SimpleXML to TikXML but that I am still getting the same problem. At the onResponse method I am not doing anything but I had the same the problem.
Here is how I make the call:
final Call<ResponseEnvelope> consumeWS = RetrofitGenerator
.getConsultarTipoDeDocumentoApi()
.getTiposDeDocumento(this.USERNAME_ENCRYPT, this.USERTOKEN_ENCRYPT,
this.MESSAGEID_ENCRYPT, "close", this.requestEnvelope);
consumeWS.enqueue(new Callback<ResponseEnvelope>() {
#Override
public void onResponse(Call<ResponseEnvelope> call, Response<ResponseEnvelope> response) {
ResponseEnvelope responseEnvelope = new ResponseEnvelope(response.body().getBody());
if(responseEnvelope != null && response.isSuccessful())
dataTipoDeDocumento.setValue(responseEnvelope.getBody().getMtrtipdoccResponse()
.getReturn().getLISTAREGISTROS().getLIST());
}
#Override
public void onFailure(Call<ResponseEnvelope> call, Throwable t) {
t.printStackTrace();
dataTipoDeDocumento.setValue(null);
Here is my RetrofitGenerator:
private static OkHttpClient.Builder okHttpClient = new OkHttpClient
.Builder();
private static Retrofit.Builder retrofitBuilder = new Retrofit.Builder()
.addConverterFactory(SimpleXmlConverterFactory.create(serializer));
public RetrofitGenerator() {
}
public static <S> S createService(Class<S> serviceClass, String baseUrl) {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
logging.level(HttpLoggingInterceptor.Level.BODY);
OkHttpClient client = okHttpClient
.connectTimeout(5, TimeUnit.SECONDS)
.writeTimeout(5, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.SECONDS)
.retryOnConnectionFailure(true)
.addInterceptor(logging)
.build();
Retrofit retrofit = retrofitBuilder.baseUrl(baseUrl).client(client).build();
return retrofit.create(serviceClass);
}
Here is the problem: (Debugging the request)
override fun read(sink: Buffer, byteCount: Long): Long {
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
check(!closed) { "closed" }
if (bytesRemaining == 0L) return -1
val read = super.read(sink, minOf(bytesRemaining, byteCount))
if (read == -1L) {
realConnection!!.noNewExchanges() // The server didn't supply the promised content length.
val e = ProtocolException("unexpected end of stream")
responseBodyComplete()
throw e
}
I noticed that when it fails there is a variable named bytesRemaining that get the value 1 (when it fails it is always 1). The content-length of the response is always 933. I don't know what is happening.
Here is my gradle:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.app.multired"
minSdkVersion 22
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
dataBinding {
enabled = true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.jakewharton:butterknife:10.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
/* dagger dependency for DI*/
implementation "com.google.dagger:dagger:2.16"
annotationProcessor "com.google.dagger:dagger-compiler:2.16"
compileOnly 'javax.annotation:jsr250-api:1.0'
implementation 'javax.inject:javax.inject:1'
/*Retrofit lib*/
testImplementation 'com.squareup.okhttp3:mockwebserver:4.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.0'
//implementation 'com.squareup.okhttp3:okhttp:3.12.0'
implementation 'com.squareup.retrofit2:converter-simplexml:2.6.1'
/*RxJava lib*/
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation "io.reactivex.rxjava2:rxjava:2.1.8"
implementation 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
/* LiveData lib*/
implementation "android.arch.lifecycle:extensions:1.1.1"
implementation "android.arch.lifecycle:runtime:1.1.1"
annotationProcessor "android.arch.lifecycle:compiler:1.1.1"
implementation 'androidx.recyclerview:recyclerview:1.0.0'
/* Biometric Authentication */
implementation 'androidx.biometric:biometric:1.0.0-alpha03'
implementation 'org.jetbrains:annotations:15.0'
}
How I solved it:
public abstract class CallbackWithRetry<T> implements Callback<T> {
private static final String TAG = CallbackWithRetry.class.getSimpleName();
#Override
public void onFailure(Call<T> call, Throwable t){
Log.e(TAG, t.getLocalizedMessage());
retry(call);
}
private void retry(Call<T> call){
call.clone().enqueue(this);
}
}
The edited Callback:
final Call<ConsultarTipoDeDocumentoResponseEnvelope> consumeWS = RetrofitGenerator
.getConsultarTipoDeDocumentoApi()
.getTiposDeDocumento(this.USERNAME_ENCRYPT, this.USERTOKEN_ENCRYPT,
this.MESSAGEID_ENCRYPT, this.consultarTipoDeDocumentoRequestEnvelope);
consumeWS.enqueue(new CallbackWithRetry<ConsultarTipoDeDocumentoResponseEnvelope>() {
#Override
public void onResponse(Call<ConsultarTipoDeDocumentoResponseEnvelope> call, Response<ConsultarTipoDeDocumentoResponseEnvelope> response) {
ConsultarTipoDeDocumentoResponseEnvelope consultarTipoDeDocumentoResponseEnvelope = new ConsultarTipoDeDocumentoResponseEnvelope(response.body().getBody());
if(consultarTipoDeDocumentoResponseEnvelope != null && response.isSuccessful())
dataTipoDeDocumento.setValue(consultarTipoDeDocumentoResponseEnvelope.getBody().getMtrtipdoccResponse()
.getReturn().getLISTAREGISTROS().getLIST());
}
#Override
public void onFailure(Call<ConsultarTipoDeDocumentoResponseEnvelope> call, Throwable t) {
super.onFailure(call, t);
}
});
This retry is very useful since Retrofit2 included the call variable as a parameter of the onFailure method when you do the enqueue Call.
My guess is that the server measured the response length using String.length() (or similar) but the content was not all ASCII and so the encoded length in bytes was greater.
You can confirm this by seeing if there are any non-ASCII characters in the broken responses.

Circular view path [error] while json output expected in springboot

newbie to Springboot with gradle, I am creating a restful service which queries the db2 database and returns the result in json.
The desired output
{
resource: {
results: [
{
currencyCode: "JPY",
conversionRateToUSD: "0.010286580",
conversionRateFromUSD: "97.214040040",
startDate: "2011-01-01",
endDate: "2011-01-29"
}
]
}
}
The api i am trying to build is http://localhost:8080/apis/exchange-rates/referenceDate=2015-01-01&currencyCode=JPY
I have created the below controller class
#RestController
#Slf4j
#RequestMapping("/apis")
public class IndividualExchangeRateController {
#Autowired
private IndividualExchangeRateService individualExchangeRateService;
public IndividualExchangeRateController(IndividualExchangeRateService individualExchangeRateService) {
this.individualExchangeRateService = individualExchangeRateService;
}
#RequestMapping(value = "/exchange-rates", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public #ResponseBody IndividualResource getIndividual(#RequestParam("referenceDate") #DateTimeFormat(pattern = "YYYY-MM-DD")Date referenceDate,
#RequestParam(value = "currencyCode", required = false) String currencyCode){
try {
System.out.println("Inside Controller");
return individualExchangeRateService.getIndividualExchangeRate(referenceDate, currencyCode);
}
catch (HttpClientErrorException e){
throw new InvalidRequestException(e.getMessage());
}
}
}
I am getting the below error when i call the api
javax.servlet.ServletException: Circular view path [error]: would dispatch back to the current handler URL [/error] again. Check your ViewResolver setup! (Hint: This may be the result of an unspecified view, due to default view name generation.)
Can anybody help out on this ?
As the output is json i do not have thymeleaf dependencies on my application
Below is the gradle build file
plugins {
id 'org.springframework.boot' version '2.1.7.RELEASE'
id 'java'
}
apply plugin: 'io.spring.dependency-management'
group = 'com.abc.service'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url "http://artifactory.abcinc.dev/artifactory/maven-repos" }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
compileOnly 'org.projectlombok:lombok:1.18.8'
annotationProcessor 'org.projectlombok:lombok:1.18.8'
runtime 'com.ibm.db2.jcc:db2jcc4:4.19.49'
compile group: 'org.springframework', name: 'spring-jdbc', version: '5.1.9.RELEASE'
compile group: 'com.zaxxer', name: 'HikariCP', version: '3.3.1'
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.9.9'
}
public class IndividualExchangeRate {
private String currencyCode;
private double conversionRateFromUSD;
private double conversionRateToUSD;
}
public class IndividualResource {
private List<IndividualExchangeRate> individualExchangeRates;
}
All the classes are annotated with lombok.
Spring Boot uses a default Whitelabel error page in case server error.So there might be some code snippets which is breaking your appliaction flow.
Add server.error.whitelabel.enabled=false in your application.properties file .
OR add the following code snippets :
#Controller
public class AppErrorController implements ErrorController{
private static final String PATH = "/error";
#RequestMapping(value = PATH)
public String error() {
return "Error handling";
}
#Override
public String getErrorPath() {
return PATH;
}
}

Why Spring #Service methods appear with 0% code coverage in JaCoCo?

These libraries are loaded:
JUnit 5.3.2
JaCoCo 0.8.2
Mockito 2.10.0
Only element "static {...}" appears with 100% coverage. All the rest is at 0%:
The unit test class has annotations #ExtendWith(SpringExtension.class) and #AutoConfigureMockMvc. The service is injected with #Mock.
doReturn(actual).when(service).get(param);
when(service.get(param)).thenReturn(actual);
expected = service.get(param);
verify(service, times(1)).get(param);
assertEquals(expected, actual);
assertEquals(actual, expected);
My ServiceImpl class is red when I click any method. It extends an abstract class. Jackson's ObjectMapper is red, and also the entire lines within the methods. For example:
public CustomReturnObject get(final CustomParamObject paramObject) {
try {
return retryTemplate.execute(status -> {
String json = repository.get(paramObject);
CustomReturnObject returnObject = json2CustomObject(json, paramObject);
if (returnObject == null) {
returnObject = new CustomReturnObject();
returnObject.setId(paramObject.getId());
}
return returnObject;
});
} catch (Exception ex) {
log.error(ex.getMessage(), ex);
return null;
}
}
Similarly to https://stackoverflow.com/a/46614216/244993 let's put aside Spring, because there is IMO clearly something wrong with your expectations/understanding about core thing here - mocking.
By
doReturn(actual).when(service).get(param);
expected = service.get(param);
verify(service, times(1)).get(param);
assertEquals(expected, actual);
you are not testing get method, you are testing something that always returns actual, no matter what is actually written in get, because in this case it is not executed.
Here is complete example as a proof:
src/main/java/hello/GreetingService.java:
package hello;
class GreetingService {
Object get(Object param) {
throw new UnsupportedOperationException();
}
}
src/test/java/hello/GreetingServiceTest.java:
package hello;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
#ExtendWith(MockitoExtension.class)
public class GreetingServiceTest {
#Mock
public GreetingService service;
#Test
void test() {
Object param = new Object();
Object actual = new Object();
doReturn(actual).when(service).get(param);
Object expected = service.get(param);
verify(service, Mockito.times(1)).get(param);
assertEquals(expected, actual);
}
}
build.gradle :
apply plugin: 'java'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile 'org.mockito:mockito-junit-jupiter:2.23.4'
}
Real method get throws UnsupportedOperationException, however above test succeeds, so real method was not executed. As another way to proof that get not executed: put a breakpoint into it and execute test in debug mode from IDE - breakpoint won't be reached.
Coverage shows what was really executed and hence absolutely correct that it is zero for methods that are not executed.

Spring 4 + Embedded Tomcat 7

I try to build a web app using Spring Web MVC 4.3.2 and embedded Tomcat 7.0.64.
I did not manage to write the correct main method to start embedded Tomcat. It works for Spring Controller sending #ResponseBody content (JSON) but failed for JSP views.
public static void main(String[] args) throws Exception {
String appBase = ".";// What to put here ?
Tomcat tomcat = new Tomcat();
String contextPath = "";
String port = System.getProperty("server.port");
tomcat.setPort(port == null ? 8080 : Integer.valueOf(port));
tomcat.getHost().setAppBase(appBase);
Context context = tomcat.addWebapp(contextPath, appBase);
// So that it works when in it's launched from IntelliJ or Eclipse
// Also need that a folder named "META-INF" exists in build/classes/main
// https://bz.apache.org/bugzilla/show_bug.cgi?id=52853#c19
((StandardJarScanner) context.getJarScanner()).setScanAllDirectories(true);
tomcat.start();
tomcat.getServer().await();
}
For JSP view it says : The requested resource is not available (WEB-INF/views/home.jsp) HTTP 404
If I set the appBase variable to the absolute path where the JSPs are, it works. But, of course, it is not a solution as it would not work on another machine. I need a relative path.
If I set appBase varibale to "src/main/webapp", then Tomcat fails to start with the following error : java.lang.IllegalArgumentException: Document base C:\blabla\spring-jsp-embedded-tomcat\tomcat.8080\src\main\webapp\src\main\webapp does not exist or is not a readable directory.
Morevover, the jar that is built with Gradle fat jar technique does not contain the WEB-INF dir.
How can I do to make a simple Spring MVC app working with an embedded Tomcat and JSPs (to be launched with java -cp path/to/my/jar com.app.Launcher) ?
build.gradle :
apply plugin: 'java'
sourceCompatibility = 1.7
version = '1.0'
jar {
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
repositories {
maven { url "http://repo1.maven.org/maven2" }
}
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.6.2'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.6.2'
compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: '2.6.2'
compile 'org.springframework:spring-webmvc:4.3.2.RELEASE'
compile 'com.fasterxml.jackson.core:jackson-databind:2.7.0'
compile 'javax.servlet:javax.servlet-api:3.0.1'
compile 'javax.servlet.jsp:jsp-api:2.2'
compile 'javax.servlet:jstl:1.2'
// Embedded Tomcat
// 2 mandatory libs
compile 'org.apache.tomcat.embed:tomcat-embed-core:7.0.64'
compile 'org.apache.tomcat.embed:tomcat-embed-logging-juli:7.0.64'
// To enable JSPs
compile 'org.apache.tomcat.embed:tomcat-embed-jasper:7.0.64'
testCompile group: 'junit', name: 'junit', version: '4.+'
}
Tomcat launcher :
public class Launcher {
public static void main(String[] args) throws Exception {
String contextPath = "";
// String appBase = "C:/absolute/path/to/webapp/dir"; // It works but of course I need a relative path
// String appBase = "."; // Works only for Controller sending back ResponseBody (JSON) but fail to find jsp files
String appBase = "src/main/webapp"; // Tomcat does not start properly
Tomcat tomcat = new Tomcat();
String port = System.getProperty("server.port");
tomcat.setPort(port == null ? 8080 : Integer.valueOf(port));
tomcat.getHost().setAppBase(appBase);
Context context = tomcat.addWebapp(contextPath, appBase);
// So that it works when in it's launched from IntelliJ or Eclipse
// Also need that a folder named "META-INF" exists in build/classes/main
// https://bz.apache.org/bugzilla/show_bug.cgi?id=52853#c19
((StandardJarScanner) context.getJarScanner()).setScanAllDirectories(true);
tomcat.start();
tomcat.getServer().await();
}
}
Spring web app initializer :
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
}
WebConfig :
#Configuration
#EnableWebMvc
#ComponentScan("com.app")
public class WebConfig extends WebMvcConfigurerAdapter {
#Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
Folder structure :
Apparently, embedded Tomcat expects static resources to be in a META-INF/resources directory. I followed this : tutorial and I checked how the final jar was structured.
So I modified the Gradle build script to put the JSPs there.
sourceSets {
main {
resources.srcDirs = ["src/main/webapp"]
output.resourcesDir = "$buildDir/classes/main/META-INF/resources"
}
}
And now it works. However, I have the feeling that it's a makeshift job. If someone has a more satisfying and educational answer, I would be pleased to get it.

Resources