package service;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
import org.springframework.hateoas.hal.Jackson2HalModule;
import service.config.GraphDatabaseConfiguration;
import service.data.domain.entity.Product;
import service.data.domain.entity.User;
import service.data.domain.rels.Rating;
import javax.annotation.PostConstruct;
@SpringBootApplication
@ComponentScan({ "service.data", "service.config" })
@EnableZuulProxy
@Slf4j
public class Application {
final Logger logger = LoggerFactory.getLogger(Application.class);
@Autowired
RepositoryRestMvcConfiguration restConfiguration;
// Used to bootstrap the Neo4j database with demo data
@Value("${aws.s3.url}")
String datasetUrl;
@Value("${neo4j.bootstrap}")
Boolean bootstrap;
public static void main(String[] args) {
System.setProperty("org.neo4j.rest.read_timeout", "250");
SpringApplication.run(Application.class, args);
}
@PostConstruct
public void postConstructConfiguration() {
// Expose ids for the domain entities having repositories
logger.info("Exposing IDs on repositories...");
restConfiguration.config().exposeIdsFor(User.class);
restConfiguration.config().exposeIdsFor(Product.class);
restConfiguration.config().exposeIdsFor(Rating.class);
// Register the ObjectMapper module for properly rendering HATEOAS REST repositories
logger.info("Registering Jackson2HalModule...");
restConfiguration.objectMapper().registerModule(new Jackson2HalModule());
}
/**
* Bootstrap the Neo4j database with demo dataset. This can run multiple times without
* duplicating data.
*
* @param graphDatabaseConfiguration is the graph database configuration to communicate with the Neo4j server
* @return a {@link CommandLineRunner} instance with the method delegate to execute
*/
@Bean
public CommandLineRunner commandLineRunner(GraphDatabaseConfiguration graphDatabaseConfiguration) {
return strings -> {
if(bootstrap) {
logger.info("Creating index on User(id) and Product(id)...");
graphDatabaseConfiguration.neo4jTemplate().query("CREATE INDEX ON :User(id)", null).finish();
graphDatabaseConfiguration.neo4jTemplate().query("CREATE INDEX ON :Product(id)", null).finish();
logger.info("Importing ratings data...");
// Import graph data for movie ratings
String userImport = String.format("USING PERIODIC COMMIT 20000\n" +
"LOAD CSV WITH HEADERS FROM \"%s/ratings.csv\" AS csvLine\n" +
"MERGE (user:User:_User { id: toInt(csvLine.userId) })\n" +
"ON CREATE SET user.__type__=\"User\", user.className=\"data.domain.nodes.User\", user.knownId = csvLine.userId\n" +
"MERGE (product:Product:_Product { id: toInt(csvLine.movieId) })\n" +
"ON CREATE SET product.__type__=\"Product\", product.className=\"data.domain.nodes.Product\", product.knownId = csvLine.movieId\n" +
"MERGE (user)-[r:Rating]->(product)\n" +
"ON CREATE SET r.timestamp = toInt(csvLine.timestamp), r.rating = toInt(csvLine.rating), r.knownId = csvLine.userId + \"_\" + csvLine.movieId, r.__type__ = \"Rating\", r.className = \"data.domain.rels.Rating\"", datasetUrl);
graphDatabaseConfiguration.neo4jTemplate().query(userImport, null).finish();
logger.info("Import complete");
}
};
}
}