blob: 18bb1f8278bc61c88eb3565a424ec01b704d1182 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.batchee.spring.ui;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.catalina.WebResourceRoot;
import org.junit.Test;
import org.springframework.batch.core.BatchStatus;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.JobExecution;
import org.springframework.batch.core.JobParameters;
import org.springframework.batch.core.JobParametersInvalidException;
import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.explore.JobExplorer;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.listener.JobExecutionListenerSupport;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.embedded.tomcat.TomcatContextCustomizer;
import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestTemplate;
import javax.sql.DataSource;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.CountDownLatch;
import static java.lang.Thread.sleep;
import static org.apache.catalina.Lifecycle.CONFIGURE_START_EVENT;
import static org.apache.catalina.WebResourceRoot.ResourceSetType.RESOURCE_JAR;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class BatchEEUITest {
@Test
public void ensureUIRuns() throws InterruptedException {
try (final ConfigurableApplicationContext ctx = runBatch()) {
final int maxRetries = 10;
for (int i = 1; i <= maxRetries; i++) {
try {
assertUIIsThere(ctx);
break;
} catch (final HttpClientErrorException.NotFound ae) {
if (i == maxRetries) {
throw ae;
}
sleep(250);
}
}
}
}
private ConfigurableApplicationContext runBatch() {
final SpringApplication app = new SpringApplication(BatchApp.class);
final JobExecution done;
final ConfigurableApplicationContext ctx = app.run("--server.port=0", "--server.tomcat.basedir=target/tomcat");
try {
done = ctx.getBean(JobLauncher.class).run(ctx.getBean(Job.class), new JobParameters());
ctx.getBean(Awaiter.class).latch.await();
} catch (final JobExecutionAlreadyRunningException | JobRestartException |
JobInstanceAlreadyCompleteException | JobParametersInvalidException |
InterruptedException e) {
throw new IllegalStateException(e);
}
final JobExecution jobExecution = ctx.getBean(JobExplorer.class).getJobExecution(done.getJobId());
assertNotNull(jobExecution);
assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
return ctx;
}
// sanity checks to ensure the UI is properly rendered
private void assertUIIsThere(final ConfigurableApplicationContext ctx) {
final int port = ServletWebServerApplicationContext.class.cast(ctx).getWebServer().getPort();
assertHome(port);
assertDetails(port);
}
private void assertDetails(final int port) {
final ResponseEntity<String> home = new RestTemplate().getForEntity("http://localhost:" + port + "/batchee/executions/BatchEEUITestBatch", String.class);
assertEquals(200, home.getStatusCodeValue());
final String body = home.getBody();
assertNotNull(body);
assertTrue(body, body.contains("<td>COMPLETED</td>"));
assertTrue(body, body.contains("<td><a href=\"/batchee/step-executions/1\">1</a></td>"));
}
private void assertHome(final int port) {
final ResponseEntity<String> home = new RestTemplate().getForEntity("http://localhost:" + port + "/batchee/", String.class);
assertEquals(200, home.getStatusCodeValue());
final String body = home.getBody();
assertNotNull(body);
assertTrue(body, body.contains("<h3 class=\"text-muted\">Apache JBatch GUI</h3>"));
assertTrue(body, body.contains("<a href=\"/batchee/executions/BatchEEUITestBatch\">BatchEEUITestBatch</a>"));
}
@EnableBatchProcessing
@SpringBootApplication
public static class BatchApp {
@Bean // StaticResourceJars fails with surefire classloader so let's workaround it for the test
public TomcatContextCustomizer surefireStaticResourceJarsWorkaround() {
return context -> context.addLifecycleListener(event -> {
if (!event.getType().equals(CONFIGURE_START_EVENT)) {
return;
}
final URL url = Thread.currentThread().getContextClassLoader()
.getResource("META-INF/resources/internal/batchee/layout.jsp");
final WebResourceRoot resources = context.getResources();
try {
final URL cleanUrl = new URL(
url.getProtocol(), url.getHost(), url.getPort(),
url.getFile().substring(0, url.getFile().length() - "META-INF/resources/internal/batchee/layout.jsp".length()));
resources.createWebResourceSet(RESOURCE_JAR, "/", cleanUrl, "/META-INF/resources");
} catch (final MalformedURLException e) {
throw new IllegalStateException(e);
}
});
}
@Bean(destroyMethod = "close")
public DataSource dataSource() {
final HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:derby:memory:BatchEEUITest;create=true");
dataSource.setUsername("SA");
dataSource.setPassword("");
dataSource.setDriverClassName("org.apache.derby.jdbc.EmbeddedDriver");
return dataSource;
}
@Bean
public Awaiter awaiter() {
return new Awaiter();
}
@Bean
public Job job(final JobBuilderFactory jobBuilderFactory,
final StepBuilderFactory stepBuilderFactory,
final Awaiter awaiter) {
return jobBuilderFactory
.get("BatchEEUITestBatch")
.listener(new JobExecutionListenerSupport() {
@Override
public void afterJob(final JobExecution jobExecution) {
awaiter.latch.countDown();
}
})
.start(stepBuilderFactory.get("step1").tasklet((contribution, chunkContext) -> RepeatStatus.FINISHED).build())
.build();
}
}
public static class Awaiter {
private final CountDownLatch latch = new CountDownLatch(1);
}
}