Skip to content
17 changes: 2 additions & 15 deletions src/main/java/net/apnic/whowas/App.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
package net.apnic.whowas;

import java.util.Properties;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

public static void main(String[] args)
{
SpringApplication app = new SpringApplication(App.class);
Properties defaultProps = new Properties();

defaultProps.setProperty(
"spring.mvc.throw-exception-if-no-handler-found", "true");
defaultProps.setProperty("spring.resources.add-mappings", "false");
defaultProps.setProperty("spring.mvc.favicon.enabled", "false");
defaultProps.setProperty("management.add-application-context-header", "false");
app.setDefaultProperties(defaultProps);
app.run(args);
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
6 changes: 4 additions & 2 deletions src/main/java/net/apnic/whowas/loaders/RipeDbLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,19 @@
import org.slf4j.LoggerFactory;

import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

@Component
public class RipeDbLoader implements Loader {
private static final Logger LOGGER = LoggerFactory.getLogger(RipeDbLoader.class);

private long lastSerial;
private final transient JdbcOperations operations;

public RipeDbLoader(JdbcOperations jdbcOperations, long serial) {
this.lastSerial = serial;
public RipeDbLoader(JdbcOperations jdbcOperations) {
this.lastSerial = -1;
this.operations = jdbcOperations;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
import java.time.temporal.ChronoUnit;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.*;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
import javax.annotation.PostConstruct;
Expand All @@ -33,9 +30,11 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.ApplicationContext;
import org.springframework.jdbc.core.JdbcOperations;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.transaction.PlatformTransactionManager;

@Configuration
@EnableScheduling
Expand All @@ -48,6 +47,8 @@ public class LoaderConfiguration

@Autowired
private ApplicationContext context;

@Autowired
private RipeDbLoader dbLoader;

@Value("${snapshot.file:#{null}}")
Expand All @@ -57,10 +58,14 @@ public class LoaderConfiguration
History history;

@Autowired
private JdbcOperations jdbcOperations;
SearchEngine searchEngine;

/*
This is autowired here to trigger Spring to initialise the transaction manager bean
before @PostConstruct is invoked. Without this, the initial data load will hang.
*/
@Autowired
SearchEngine searchEngine;
PlatformTransactionManager platformTransactionManager;

private void buildTree()
{
Expand Down Expand Up @@ -105,10 +110,19 @@ private void buildTree()
}

@PostConstruct

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

public void initialise()
{
dbLoader = new RipeDbLoader(jdbcOperations, -1L);
executorService.execute(this::buildTree);
public void initialise() throws ExecutionException, InterruptedException {
/*
Scoping the initial load of data to @PostConstruct
results in the loading completing before the servlet context is started,
preventing requests from being served from partially loaded data.
*/
Future initialDataLoaded = executorService.submit(this::buildTree);
initialDataLoaded.get();
}

@Bean
TaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}

@Scheduled(fixedRate = 15000L)
Expand Down
9 changes: 9 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,17 @@ spring:
username: "${database.username}"
password: "${database.password}"

mvc:
throw-exception-if-no-handler-found: true
favicon:
enabled: false
resources:
add-mappings: false


management:
port: 8081
add-application-context-header: false

info:
build:
Expand Down
58 changes: 58 additions & 0 deletions src/test/java/net/apnic/whowas/InitialisationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package net.apnic.whowas;

import net.apnic.whowas.loaders.RipeDbLoader;
import org.junit.Test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.embedded.EmbeddedServletContainerInitializedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.ArrayList;
import java.util.concurrent.ConcurrentLinkedQueue;

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

public class InitialisationTest {

@Test
public void initialDataIsLoadedBeforeServletIsStarted() {
ConcurrentLinkedQueue<String> events = new ConcurrentLinkedQueue<>();

System.setProperty("server.port", "0");
System.setProperty("management.port", "-1");

SpringApplication springApplication = new SpringApplication(App.class);
springApplication.addInitializers(configurableApplicationContext ->
configurableApplicationContext.addBeanFactoryPostProcessor(beanFactory ->
beanFactory.registerResolvableDependency(
RipeDbLoader.class, new RipeDbLoader(new JdbcTemplate()) {
@Override
public void loadWith(RevisionConsumer consumer) {
sleep(1000);
events.add("Data loaded");
}
}
)
)
);
springApplication.addListeners(
(ApplicationListener<EmbeddedServletContainerInitializedEvent>) applicationEvent ->
events.add("Servlet container started")
);

try(ConfigurableApplicationContext context = springApplication.run()) {
assertThat("Data was loaded before tomcat was initialised",
new ArrayList<>(events).get(0), is("Data loaded"));
}
}

private static void sleep(int millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}