How to enable journal in embedded MongoDB from Spring Boot test - java

I am trying to write a Spring Boot test that uses embedded MondoDB 4.0.2; the code to test requires Mongo ChangeStreams which requires MongoDB start as a replica set. MongoDB as a replica set at MongoDB v4 requires journaling enabled. I was not able to find a way to start with journaling enabled so posted this here looking for answers. I subsequently found out how to do it - below.
I have spring-boot 2.1.3.RELEASE. Spring-data-mongodb 2.1.5.RELEASE
This is what I'd been trying:
#RunWith(SpringRunner.class)
#DataMongoTest(properties= {
"spring.mongodb.embedded.version= 4.0.2",
"spring.mongodb.embedded.storage.repl-set-name = r_0",
"spring.mongodb.embedded.storage.journal.enabled=true"
})
public class MyStreamWatcherTest {
#SpringBootApplication
#ComponentScan(basePackages = {"my.package.with.dao.classes"})
#EnableMongoRepositories( { "my.package.with.dao.repository" })
static public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
#Before
public void startup() {
MongoDatabase adminDb = mongoClient.getDatabase("admin");
Document config = new Document("_id", "rs0");
BasicDBList members = new BasicDBList();
members.add(new Document("_id", 0).append("host",
mongoClient.getConnectPoint()));
config.put("members", members);
adminDb.runCommand(new Document("replSetInitiate", config));
However, when the test starts the options used to start mongo did not include enabling journal.
The fix was to add this class:
#Configuration
public class MyEmbeddedMongoConfiguration {
private int localPort = 0;
public int getLocalPort() {
return localPort;
}
#Bean
public IMongodConfig mongodConfig(EmbeddedMongoProperties embeddedProperties) throws IOException {
MongodConfigBuilder builder = new MongodConfigBuilder()
.version(Version.V4_0_2)
.cmdOptions(new MongoCmdOptionsBuilder().useNoJournal(false).build());
// Save the local port so the replica set initializer can come get it.
this.localPort = Network.getFreeServerPort();
builder.net(new Net("127.0.0.1", this.getLocalPort(), Network.localhostIsIPv6()));
EmbeddedMongoProperties.Storage storage = embeddedProperties.getStorage();
if (storage != null) {
String databaseDir = storage.getDatabaseDir();
String replSetName = "rs0"; // Should be able to: storage.getReplSetName();
int oplogSize = (storage.getOplogSize() != null)
? (int) storage.getOplogSize().toMegabytes() : 0;
builder.replication(new Storage(databaseDir, replSetName, oplogSize));
}
return builder.build();
}
This got journal enabled and the mongod started with replica set enabled. Then I added another class to initialize the replica set:
#Configuration
public class EmbeddedMongoReplicaSetInitializer {
#Autowired
MyEmbeddedMongoConfiguration myEmbeddedMongoConfiguration;
MongoClient mongoClient;
// We don't use this MongoClient as it will try to wait for the replica set to stabilize
// before address-fetching methods will return. It is specified here to order this class's
// creation after MongoClient, so we can be sure mongod is running.
EmbeddedMongoReplicaSetInitializer(MongoClient mongoClient) {
this.mongoClient = mongoClient;
}
#PostConstruct
public void initReplicaSet() {
//List<ServerAddress> serverAddresses = mongoClient.getServerAddressList();
MongoClient mongo = new MongoClient(new ServerAddress("127.0.0.1", myEmbeddedMongoConfiguration.getLocalPort()));
MongoDatabase adminDb = mongo.getDatabase("admin");
Document config = new Document("_id", "rs0");
BasicDBList members = new BasicDBList();
members.add(new Document("_id", 0).append("host", String.format("127.0.0.1:%d", myEmbeddedMongoConfiguration.getLocalPort())));
config.put("members", members);
adminDb.runCommand(new Document("replSetInitiate", config));
mongo.close();
}
}
That's getting the job done. If anyone has tips to make this easier, please post here.

Related

APPLICATION FAILED TO START when I try to work with mongoDB and SpringBoot

I have implemented a spring boot application to retrieve file data from files and save it in separate collections. When I run the application it gives the following error. I couldn't resolve it. Can anyone help me to do this?
Error
Description:
Parameter 2 of constructor in com.bezkoder.spring.jwt.mongodb.SpringBootSecurityJwtMongodbApplication required a bean of type 'com.bezkoder.spring.jwt.mongodb.models.LogRecordCollection' that could not be found.
Action:
Consider defining a bean of type 'com.bezkoder.spring.jwt.mongodb.models.LogRecordCollection' in your configuration.
Disconnected from the target VM, address: '127.0.0.1:55297', transport: 'socket'
LogRecordController.java
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth/log")
public class LogRecordController {
#Autowired
LogRecordRepository logRecordRepository;
#GetMapping("")
public ResponseEntity<?> getAllLogRecordsByLogFileId(#RequestParam("fileId") String fileId) {
try{
LogRecordCollection logRecordCollection = new LogRecordCollection();
logRecordCollection.setCollectionName(fileId);
// List<LogRecord> logRecords = logRecordRepository.findAll(PageRequest.of(1, 10, Sort.by(Sort.Direction.ASC, "no"))).getContent();
List<LogRecord> logRecords = logRecordRepository.findAll();
return ResponseEntity.ok().body(logRecords);
}catch (Exception e){
return ResponseEntity.status(HttpStatus.EXPECTATION_FAILED).body(e.getMessage());
}
}
}
SpringBootSecurityJwtMongodbApplication.java
#SpringBootApplication
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api/auth/logFile")
public class SpringBootSecurityJwtMongodbApplication {
public SpringBootSecurityJwtMongodbApplication(LogFileRepository logfileRepo, LogRecordRepository logrecordRepo, LogRecordCollection logrecordColl) {
this.logfileRepo = logfileRepo;
this.logrecordRepo = logrecordRepo;
this.logrecordColl = logrecordColl;
}
public static void main(String[] args) {
SpringApplication.run(SpringBootSecurityJwtMongodbApplication.class, args);
}
#Bean
public ApplicationRunner runner(FTPConfiguration.GateFile gateFile) {
return args -> {
List<File> files = gateFile.mget(".");
for (File file : files) {
JSONArray arr = new JSONArray();
System.out.println("Result:" + file.getAbsolutePath());
run(file, arr);
}
};
}
void run(File file, JSONArray arr) throws IOException {
SimpleDateFormat formatter = new SimpleDateFormat("hh:mm:ss");
Pcap pcap = Pcap.openStream(file);
JSONObject obj = new JSONObject();
String fileName = file.getName();
pcap.loop(
packet -> {
String Time = null;
String Source = null;
String Destination = null;
String dataProtocol = null;
Long Length = null;
if (packet.hasProtocol(Protocol.TCP)) {
TCPPacket packet1 = (TCPPacket) packet.getPacket(Protocol.TCP);
Time = formatter.format(new Date(packet1.getArrivalTime() / 1000));
Source = packet1.getSourceIP();
Destination = packet1.getDestinationIP();
dataProtocol = packet1.getProtocol().toString();
Length = packet1.getTotalLength();
} else if (packet.hasProtocol(Protocol.UDP)) {
UDPPacket packet1 = (UDPPacket) packet.getPacket(Protocol.UDP);
Time = formatter.format(new Date(packet1.getArrivalTime() / 1000));
Source = packet1.getSourceIP();
Destination = packet1.getDestinationIP();
dataProtocol = packet1.getProtocol().toString();
Length = packet1.getTotalLength();
} else {
System.out.println("Not found protocol. | " + packet.getProtocol());
}
obj.put("Time", Time);
obj.put("Source", Source);
obj.put("Destination", Destination);
obj.put("Protocol", dataProtocol);
obj.put("Length", Length);
arr.add(obj);
return packet.getNextPacket() != null;
}
);
System.out.println(arr);
System.out.println(fileName);
Calendar calendar = Calendar.getInstance();
String now = String.valueOf(calendar.getTime());
LogFile data =logfileRepo.save(new LogFile("", fileName, now));
String collectionName = data.getFileName();
System.out.println(collectionName);
//Converting jsonData string into JSON object
//Creating an empty ArrayList of type Object
ArrayList<Object> listdata = new ArrayList<>();
//Checking whether the JSON array has some value or not
if (arr != null) {
//Iterating JSON array
for (int i=0;i<arr.size();i++){
//Adding each element of JSON array into ArrayList
listdata.add(arr.get(i));
}
}
logrecordColl.setCollectionName(collectionName);
listdata.addAll(logrecordRepo.findAll());
}
private final LogFileRepository logfileRepo;
private final LogRecordRepository logrecordRepo;
private final LogRecordCollection logrecordColl;
}
LogRecordRepository.java
import com.bezkoder.spring.jwt.mongodb.models.LogRecord;
import org.springframework.data.mongodb.repository.MongoRepository;
public interface LogRecordRepository extends MongoRepository<LogRecord, String>{
}
LogRecordCollection.java
public class LogRecordCollection {
private static String collectionName = "undefined";
public static String getCollectionName(){
return collectionName;
}
public void setCollectionName(String collectionName){
this.collectionName = collectionName;
}
}
Parameter 2 of constructor in com.bezkoder.spring.jwt.mongodb.SpringBootSecurityJwtMongodbApplication required a bean of type 'com.bezkoder.spring.jwt.mongodb.models.LogRecordCollection' that could not be found.
In an nutshell, the exception like this is self-explanatory. It means that Spring could not find a bean to be injected into your class
In your case the class SpringBootSecurityJwtMongodbApplication has a constructor:
public SpringBootSecurityJwtMongodbApplication(LogFileRepository logfileRepo, LogRecordRepository logrecordRepo, LogRecordCollection logrecordColl) {
this.logfileRepo = logfileRepo;
this.logrecordRepo = logrecordRepo;
this.logrecordColl = logrecordColl;
}
Now, LogRecordCollection has to be a bean (annotated with #Component for example, or defined in via java configuration (#Configuration marked classes and method annotated with #Bean that creates this class). Otherwise spring won't "recognize" this class a bean.
So strictly speaking this is your issue.
Now, having said that - the code you've presented in the question looks extremely messy - you mix #SpringBootApplication which is an entry point to the application, the rest controller and what not. I really recommend you to separate all this to different files to improve the code clarity and avoid unexpected exceptions that can be tricky to fix.
add below annotations in SpringBootSecurityJwtMongodbApplication
#SpringBootApplication
#ComponentScan("com.bezkoder.spring.jwt.mongodb") //to scan packages mentioned
#EnableMongoRepositories("com.bezkoder.spring.jwt.mongodb") //to activate MongoDB repositories
public class SpringBootSecurityJwtMongodbApplication { ... }

flapdoodle.embed.mongo gets always started with Spring Boot Main application in Eclipse, how to remove

I have got a problem. A simple spring boot application works fine with existing MongoDB configuration.
For integration test, I added required configuration for embeded mongodb with flapdoodle configuration. All the unit tests are getting executed properly. When I run the main Spring Boot application, it by default considers the flapdoodle embeded mongodb configuration. As a result, the embeded mongodb never exits and while running the junit test cases, it still runs. I provide below the code snippet.
Whenever I start Spring Boot main application, it still runs the embeded mongodb. I see always the following lines in the console.
Download PRODUCTION:Windows:B64 START
Download PRODUCTION:Windows:B64 DownloadSize: 231162327
Download PRODUCTION:Windows:B64 0% 1% 2% 3% 4% 5% 6% 7% 8%
I provide the code for Mongodb configuration which should be picked up when running the main Spring Boot application.
#Slf4j
#Configuration
public class NoSQLAutoConfiguration {
#Autowired
private NoSQLEnvConfigProperties configProperties;
/**
* Morphia.
*
* #return the morphia
*/
private Morphia morphia() {
final Morphia morphia = new Morphia();
morphia.mapPackage(DS_ENTITY_PKG_NAME);
return morphia;
}
#Bean
public Datastore datastore(#Autowired #Qualifier("dev") MongoClient mongoClient) {
String dbName = configProperties.getDatabase();
final Datastore datastore = morphia().createDatastore(mongoClient, dbName);
datastore.ensureIndexes();
return datastore;
}
/**
* Mongo client.
*
* #return the mongo client
*/
#Primary
#Bean(name = "dev")
public MongoClient mongoClient() {
MongoClient mongoClient = null;
String dbHost = configProperties.getHost();
int dbPort = configProperties.getPort();
String database = configProperties.getDatabase();
log.debug("MongDB Host: {} - MongoDB Port: {}", dbHost, dbPort);
List<ServerAddress> serverAddresses = new ArrayList<>();
serverAddresses.add(new ServerAddress(dbHost, dbPort));
MongoClientOptions options = getMongoOptions();
String dbUserName = configProperties.getMongodbUsername();
String encRawPwd = configProperties.getMongodbPassword();
char[] dbPwd = null;
try {
dbPwd = Util.decode(encRawPwd).toCharArray();
} catch (Exception ex) {
// Ignore exception
dbPwd = null;
}
Optional<String> userName = Optional.ofNullable(dbUserName);
Optional<char[]> password = Optional.ofNullable(dbPwd);
if (userName.isPresent() && password.isPresent()) {
MongoCredential credential = MongoCredential.createCredential(dbUserName, database, dbPwd);
List<MongoCredential> credentialList = new ArrayList<>();
credentialList.add(credential);
mongoClient = new MongoClient(serverAddresses, credentialList, options);
} else {
log.debug("Connecting to local Mongo DB");
mongoClient = new MongoClient(dbHost, dbPort);
}
return mongoClient;
}
private MongoClientOptions getMongoOptions() {
MongoClientOptions.Builder builder = MongoClientOptions.builder();
builder.maxConnectionIdleTime(configProperties.getMongodbIdleConnection());
builder.minConnectionsPerHost(configProperties.getMongodbMinConnection());
builder.connectTimeout(configProperties.getMongodbConnectionTimeout());
return builder.build();
}
}
For integration testing, I have the configuration for embeded mongodb which is part of src/test.
#TestConfiguration
public class MongoConfiguration implements InitializingBean, DisposableBean {
MongodExecutable executable;
private static final String DBNAME = "embeded";
private static final String DBHOST = "localhost";
private static final int DBPORT = 27019;
#Override
public void afterPropertiesSet() throws Exception {
IMongodConfig mongodConfig = new MongodConfigBuilder().version(Version.Main.PRODUCTION)
.net(new Net(DBHOST, DBPORT, Network.localhostIsIPv6())).build();
MongodStarter starter = MongodStarter.getDefaultInstance();
executable = starter.prepare(mongodConfig);
executable.start();
}
private Morphia morphia() {
final Morphia morphia = new Morphia();
morphia.mapPackage(DS_ENTITY_PKG_NAME);
return morphia;
}
#Bean
public Datastore datastore(#Autowired #Qualifier("test") MongoClient mongoClient) {
final Datastore datastore = morphia().createDatastore(mongoClient, DBNAME);
datastore.ensureIndexes();
return datastore;
}
#Bean(name = "test")
public MongoClient mongoClient() {
return new MongoClient(DBHOST, DBPORT);
}
#Override
public void destroy() throws Exception {
executable.stop();
}
}
Please help me how to remove this embeded mongo configuration while running Spring Boot main application in eclipse.
I also provide below my main application below.
#EnableAspectJAutoProxy
#EnableSwagger2
#SpringBootApplication(scanBasePackages = { "com.blr.app" })
public class ValidationApplication {
/**
* The main method. f
*
* #param args the arguments
*/
public static void main(String[] args) {
SpringApplication.run(ValidationApplication.class, args);
}
}
I see the code that you have not added any profile to MongoConfiguration class. During eclipse build, this class is also picked up by Spring framework. Add the below lines to this class so that while running Spring Boot test this class will be picked up and while running main Spring Boot app, the actual Mongo Configuration file will be picked up. That is why Spring comes up with concept Profile. Add the profile appropriately for different environment.
#Profile("test")
#ActiveProfiles("test")
So final code will look like this.
#Profile("test")
#ActiveProfiles("test")
#TestConfiguration
public class MongoConfiguration implements InitializingBean, DisposableBean {
...
...
}

How to dynamically inject property values from a configuration file into Hystrix's annotations

I am learning Spring Cloud now.
We know that If the remote configuration file is updated, by accessing the “localhost:10000/refresh”, we can dynamically obtain the values of custom properties of the remote configuration file on the configuration server. I would like to ask, how do I dynamically inject these values into Hystrix's annotations.
My remote configuration file “licenseservice.yml”, I put it in a git repository:
example.property: I am the default. I am from git now.
example.timeoutInMilliseconds: 12000
server:
port: 10000
eureka:
instance:
prefer-ip-address: true
...
The java class I used to get the values in the configuration file:
#Component
#RefreshScope
public class ServiceConfig {
#Value("${example.property}")
private String exampleProperty;
#Value("${example.timeoutInMilliseconds}")
private String timeoutInMilliseconds;
public String getExampleProperty() {
return exampleProperty;
}
public String getTimeoutInMilliseconds(){
return timeoutInMilliseconds;
}
}
The java method that uses the Hystrix annotations:
#HystrixCommand(fallbackMethod = "buildFallbackLicenseList",
threadPoolKey = "licenseByOrgThreadPool",
threadPoolProperties = {
#HystrixProperty(name = "coreSize",value = "30"),
#HystrixProperty(name = "maxQueueSize", value = "10")
},
commandProperties = {
#HystrixProperty(name =
"circuitBreaker.requestVolumeThreshold",value = "10"),
#HystrixProperty(name =
"circuitBreaker.errorThresholdPercentage",value = "75"),
#HystrixProperty(name =
"circuitBreaker.sleepWindowInMilliseconds",value = "7000"),
#HystrixProperty(name =
"metrics.rollingStats.timeInMilliseconds",value = "15000"),
#HystrixProperty(name =
"metrics.rollingStats.numBuckets",value = "5"),
#HystrixProperty(name =
"execution.isolation.thread.timeoutInMilliseconds",value = ???)
})
public List<License> getLicensesByOrg(String organizationId){
System.out.println("SERVICE CONFIG TEST
"+serviceConfig.getExampleProperty());
System.out.println("SERVICE CONFIG TEST
"+serviceConfig.getTimeoutInMilliseconds());
randomlyRunLong();
return licenseRepository.findByOrganizationId(organizationId);
}
My problem is, I want to dynamically change the value of the variable "execution.isolation.thread.timeoutInMilliseconds"(by using the value of "example.timeoutInMilliseconds" in the .yml file). You can see that I wrote three question marks in that place, because I don't know what to write, can someone please? Tell me, thank you.

Creating mongodb database user in Java using spring data mongodb

I need to create a mongodb database user in a Spring boot application using spring data mongodb. I will be creating this user as part of application startup.
I could not find any reference for doing this using spring data mongodb.
Is that possible by using Spring data mongodb?
I had the same issue in the past and I end up by creating the user before the context load, like this:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class Application extends SpringBootServletInitializer {
#SuppressWarnings("resource")
public static void main(final String[] args) {
createMongoDbUser();
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
}
private void createMongoDbUser() {
MongoClient mongo = new MongoClient(HOST, PORT);
MongoDatabase db = mongo.getDatabase(DB);
Map<String, Object> commandArguments = new BasicDBObject();
commandArguments.put("createUser", USER_NAME);
commandArguments.put("pwd", USER_PWD);
String[] roles = { "readWrite" };
commandArguments.put("roles", roles);
BasicDBObject command = new BasicDBObject(commandArguments);
db.runCommand(command);
}
}
Spring-data-mongodb will create the db all by itself if it can't find it, when declaring your mongo-db factory.
For instance, I declare my db-factory in xml using the following:
<mongo:db-factory id="mongofactory" dbname="dbNameHere" mongo-ref="mongo" />
I did not have to create it myself, it was created by spring-data-mongodb upon firing may app the first time.

How can I read in a list of objects from yaml using Spring's PropertiesConfigurationFactory?

If I have a set of properties, I understand that Springboot's relaxed data binder will read in a list of properties (or yaml) and populate the matching object. Like so:
Properties props = new Properties();
props.put("devices.imports[0]","imp1");
props.put("devices.imports[1]","imp2");
props.put("devices.definitions[0].id","first");
props.put("devices.definitions[1].id", "second");
DeviceConfig conf = new DeviceConfig();
PropertiesConfigurationFactory<DeviceConfig> pcf = new PropertiesConfigurationFactory<>(conf);
pcf.setProperties(props);
conf = pcf.getObject();
assertThat(conf.getDefinitions()).hasSize(2); //Definitions is coming in as 0 instead of the expected 2
DeviceConfig looks like this:
#ConfigurationProperties(prefix="devices")
public class DeviceConfig {
private List<String> imports = new ArrayList<>();
private List<DeviceDetailsProperties> definitions = new ArrayList<>();
public List<String> getImports() {
return this.imports;
}
public List<DeviceDetailsProperties> getDefinitions() {
return definitions;
}
public void setImports(List<String> imports) {
this.imports = imports;
}
public void setDefinitions(List<DeviceDetailsProperties> definitions) {
this.definitions = definitions;
}
}
DeviceDetailsProperties just has an id field with getters/setters.
Strangely neither the definitions (objects) or imports (Strings) are getting populated.
Using SpringBoot 1.2.0.RELEASE
When using the PropertiesConfigurationFactory in a manual way like this, it won't automatically use the prefix value in the annotation.
Add a targetName like so:
pcf.setTargetName("devices");
The corrected code would be:
Properties props = new Properties();
props.put("devices.imports[0]","imp1");
props.put("devices.imports[1]","imp2");
props.put("devices.definitions[0].id","first");
props.put("devices.definitions[1].id", "second");
DeviceConfig conf = new DeviceConfig();
PropertiesConfigurationFactory<DeviceConfig> pcf = new PropertiesConfigurationFactory<>(conf);
pcf.setProperties(props);
pcf.setTargetName("devices"); // <--- Add this line
conf = pcf.getObject();
assertThat(conf.getDefinitions()).hasSize(2);

Categories

Resources