Skip to content

Commit

Permalink
IT for MongoDB with Panache transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
loicmathieu committed Mar 16, 2021
1 parent 9113938 commit 87a72f9
Show file tree
Hide file tree
Showing 11 changed files with 428 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.quarkus.it.mongodb.panache.transaction;

import org.bson.codecs.pojo.annotations.BsonId;

import io.quarkus.mongodb.panache.MongoEntity;
import io.quarkus.mongodb.panache.PanacheMongoEntityBase;

@MongoEntity(database = "transaction-person")
public class TransactionPerson extends PanacheMongoEntityBase {
@BsonId
public Long id;
public String firstname;
public String lastname;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package io.quarkus.it.mongodb.panache.transaction;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Named;
import javax.transaction.Transactional;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.mongodb.client.MongoClient;

@Path("/transaction")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class TransactionPersonResource {
@Inject
@Named("cl2")
MongoClient mongoClient;

@PostConstruct
void initDb() {
// in case of transaction, the collection needs to exist prior to using it
if (!mongoClient.getDatabase("transaction-person").listCollectionNames().into(new ArrayList<>())
.contains("TransactionPerson")) {
mongoClient.getDatabase("transaction-person").createCollection("TransactionPerson");
}
}

@GET
@Transactional
public List<TransactionPerson> getPersons() {
return TransactionPerson.listAll();
}

@POST
@Transactional
public Response addPerson(TransactionPerson person) {
person.persist();
return Response.created(URI.create("/transaction/imperative/" + person.id.toString())).build();
}

@POST
@Path("/twice")
@Transactional
public void addPersonTwice(TransactionPerson person) {
person.persist();
person.persist();//this should throw an exception, and the first person should not have been created
}

@PUT
@Transactional
public Response updatePerson(TransactionPerson person) {
person.update();
return Response.accepted().build();
}

@DELETE
@Path("/{id}")
@Transactional
public void deletePerson(@PathParam("id") String id) {
TransactionPerson person = TransactionPerson.findById(Long.parseLong(id));
person.delete();
}

@GET
@Path("/{id}")
@Transactional
public TransactionPerson getPerson(@PathParam("id") String id) {
return TransactionPerson.findById(Long.parseLong(id));
}

@GET
@Path("/count")
@Transactional
public long countAll() {
return TransactionPerson.count();
}

@DELETE
@Transactional
public void deleteAll() {
TransactionPerson.deleteAll();
}

@POST
@Path("/rename")
@Transactional
public Response rename(@QueryParam("previousName") String previousName, @QueryParam("newName") String newName) {
TransactionPerson.update("lastname", newName).where("lastname", previousName);
return Response.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
quarkus.mongodb.connection-string=mongodb://localhost:27018
quarkus.mongodb.connection-string=mongodb://localhost:27018,localhost:27019
quarkus.mongodb.write-concern.journal=false
quarkus.mongodb.database=books

# fake a different MongoDB instance
quarkus.mongodb.cl2.connection-string=mongodb://localhost:27018
quarkus.mongodb.cl2.connection-string=mongodb://localhost:27018,localhost:27019
quarkus.mongodb.cl2.write-concern.journal=false

#quarkus.log.category."io.quarkus.mongodb.panache.runtime".level=DEBUG
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package io.quarkus.it.mongodb.panache;

import static org.awaitility.Awaitility.await;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import org.bson.Document;
import org.jboss.logging.Logger;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import com.mongodb.client.MongoDatabase;

import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongoCmdOptionsBuilder;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;
import de.flapdoodle.embed.process.runtime.Network;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;

public class MongoReplicaSetTestResource implements QuarkusTestResourceLifecycleManager {

private static final Logger LOGGER = Logger.getLogger(MongoReplicaSetTestResource.class);
private static List<MongodExecutable> MONGOS = new ArrayList<>();

@Override
public Map<String, String> start() {
try {
List<IMongodConfig> configs = new ArrayList<>();
for (int i = 0; i < 2; i++) {
int port = 27018 + i;
configs.add(buildMongodConfiguration("localhost", port, true));
}
configs.forEach(config -> {
MongodExecutable exec = MongodStarter.getDefaultInstance().prepare(config);
MONGOS.add(exec);
try {
exec.start();
} catch (IOException e) {
LOGGER.error("Unable to start the mongo instance", e);
}
});
initializeReplicaSet(configs);
return Collections.emptyMap();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

@Override
public void stop() {
MONGOS.forEach(mongod -> {
try {
mongod.stop();
} catch (Exception e) {
LOGGER.error("Unable to stop MongoDB", e);
}
});
}

private static void initializeReplicaSet(final List<IMongodConfig> mongodConfigList) throws UnknownHostException {
final String arbitrerAddress = "mongodb://" + mongodConfigList.get(0).net().getServerAddress().getHostName() + ":"
+ mongodConfigList.get(0).net().getPort();
final MongoClientSettings mo = MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(arbitrerAddress)).build();

try (MongoClient mongo = MongoClients.create(mo)) {
final MongoDatabase mongoAdminDB = mongo.getDatabase("admin");

Document cr = mongoAdminDB.runCommand(new Document("isMaster", 1));
LOGGER.infof("isMaster: %s", cr);

// Build replica set configuration settings
final Document rsConfiguration = buildReplicaSetConfiguration(mongodConfigList);
LOGGER.infof("replSetSettings: %s", rsConfiguration);

// Initialize replica set
cr = mongoAdminDB.runCommand(new Document("replSetInitiate", rsConfiguration));
LOGGER.infof("replSetInitiate: %s", cr);

// Check replica set status before to proceed
await()
.pollInterval(100, TimeUnit.MILLISECONDS)
.atMost(1, TimeUnit.MINUTES)
.until(() -> {
Document result = mongoAdminDB.runCommand(new Document("replSetGetStatus", 1));
LOGGER.infof("replSetGetStatus: %s", result);
return !isReplicaSetStarted(result);
});
}
}

private static Document buildReplicaSetConfiguration(final List<IMongodConfig> configList) throws UnknownHostException {
final Document replicaSetSetting = new Document();
replicaSetSetting.append("_id", "test001");

final List<Document> members = new ArrayList<>();
int i = 0;
for (final IMongodConfig mongoConfig : configList) {
members.add(new Document().append("_id", i++).append("host",
mongoConfig.net().getServerAddress().getHostName() + ":" + mongoConfig.net().getPort()));
}

replicaSetSetting.append("members", members);
return replicaSetSetting;
}

private static boolean isReplicaSetStarted(final Document setting) {
if (!setting.containsKey("members")) {
return false;
}

@SuppressWarnings("unchecked")
final List<Document> members = setting.get("members", List.class);
for (final Document member : members) {
LOGGER.infof("replica set member %s", member);
final int state = member.getInteger("state");
LOGGER.infof("state: %s", state);
// 1 - PRIMARY, 2 - SECONDARY, 7 - ARBITER
if (state != 1 && state != 2 && state != 7) {
return false;
}
}
return true;
}

private static IMongodConfig buildMongodConfiguration(String url, int port, final boolean configureReplicaSet)
throws IOException {
final MongodConfigBuilder builder = new MongodConfigBuilder()
.version(Version.Main.V4_0)
.net(new Net(url, port, Network.localhostIsIPv6()));
if (configureReplicaSet) {
builder.withLaunchArgument("--replSet", "test001");
builder.cmdOptions(new MongoCmdOptionsBuilder()
.syncDelay(5)
.useSmallFiles(true)
.useNoJournal(false)
.build());
}
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import io.quarkus.test.junit.mockito.InjectMock;

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
@QuarkusTestResource(MongoReplicaSetTestResource.class)
public class MongodbPanacheMockingTest {

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import io.restassured.response.Response;

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
@QuarkusTestResource(MongoReplicaSetTestResource.class)
class MongodbPanacheResourceTest {
private static final TypeRef<List<BookDTO>> LIST_OF_BOOK_TYPE_REF = new TypeRef<List<BookDTO>>() {
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
import io.quarkus.test.junit.NativeImageTest;

@NativeImageTest
class NativeMongodbPanacheResourceIT extends ReactiveMongodbPanacheResourceTest {
class NativeReactiveMongodbPanacheResourceIT extends ReactiveMongodbPanacheResourceTest {

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;

import io.quarkus.it.mongodb.panache.BookDTO;
import io.quarkus.it.mongodb.panache.MongoTestResource;
import io.quarkus.it.mongodb.panache.MongoReplicaSetTestResource;
import io.quarkus.it.mongodb.panache.book.BookDetail;
import io.quarkus.it.mongodb.panache.person.Person;
import io.quarkus.test.common.QuarkusTestResource;
Expand All @@ -41,7 +41,7 @@
import io.restassured.response.Response;

@QuarkusTest
@QuarkusTestResource(MongoTestResource.class)
@QuarkusTestResource(MongoReplicaSetTestResource.class)
class ReactiveMongodbPanacheResourceTest {
private static final TypeRef<List<BookDTO>> LIST_OF_BOOK_TYPE_REF = new TypeRef<List<BookDTO>>() {
};
Expand Down
Loading

0 comments on commit 87a72f9

Please sign in to comment.