Mongo Morphia mapPackage doesn't map classes in package - mongodb

I am using Morphia mapper for MongoDB/Java. I have successfully used the web application on GlassFish server. I am migrating my project to WildFly8.2.Final/JBoss. I am having issues with Morphia mapping packages. Morphia mapping/scanning packages doesnt work. It worked fine on GlassFish but doesnt work on WilfFly.
I thought that it was a classpath issue and did a small test.
I experimented by individually mapping a class and it worked fine. Its just mapping a package doesn't work. I have the following code for Morphia.
Code :
public class MongoDataSource {
private static final String IP = XXXXXX;
private static final Integer PORT = XXXXXX;
private static final String DB_NAME = XXXXXX;
private static final String USERNAME = XXXXXX;
private static final String PWD = XXXXXX;
private static Morphia m;
private static Datastore ds;
private static DB db;
private static MongoClient client;
private static MongoDataSource INSTANCE = new MongoDataSource();
private MongoDataSource() {
m = new Morphia();
m.mapPackage("xxxx.model.user");//Works on Glassfish but doesnt work on WildFly/JBoss
m.map(xxxx.model.user.User.class);//My Experiment with loading a specific class in the package
try {
List<MongoCredential> credentials = new ArrayList<>();
credentials.add(MongoCredential.createMongoCRCredential(USERNAME, DB_NAME, PWD.toCharArray()));
ServerAddress servAddr = new ServerAddress(IP, PORT);
client = new MongoClient(servAddr, credentials);
db = client.getDB(DB_NAME);
ds = m.createDatastore(client, DB_NAME);
} catch (Exception e) {
//Log
}
}
public static Morphia getMorphia() {
return m;
}
public static Datastore getDatastore() {
return ds;
}
public static DB getDataBase() throws Exception {
return db;
}
}
What I don't understand is, if the code was not able to find package, how is it able to find a class in a package. Is this is a bug in Morphia API or some classpath issue when running the application on WildFly/Jboss. I cannot convince myself that its a classpath issue.

There have been several bugs related with mapPackage in morphia. Two days ago, using the version 0.110 I have experienced the an error with that method and I added to a existing issue in their GitHub
Check the related issues in GitHub with mapPackage and as a workaround you can just provide the classes directly using: morphia.map(ClassA.class, ClassB.class, ClassC.class);

Related

Use multiple mongo DBs in same application for same model & same Repository

I need to implement Spring boot - MongoDb application where There are 2 mongo DBs which have exact same database name & collections. Based on User making a request, i need to choose whether to fetch data from DB1 or DB2 (only difference in mongo URI host - IP).
E.g. I need some way to create 2 mongoTemplates like mTempA & mTempB in my Repository & based on some condition, use either of the template to execute query as below:
#Repository
public class MyCustomRepository {
private Logger logger = LoggerFactory.getLogger(MyCustomRepository.class);
#Autowired
private MongoTemplateA mongoTemplateA;// Need to know if this is possible & how
#Autowired
private MongoTemplateB mongoTemplateB;// Need to know if this is possible & how
public List<MyModel> findByCriteria(MyRequest request) {
List<MyModel> result;
//Query query = <build query based on request>
if (request.getUserType().equals("A")) {
result = mongoTemplateA.find(query, MyModel.class);
} else {
result = mongoTemplateB.find(query, MyModel.class);
}
logger.debug("Result fetched with {} records", result.size());
return result;
}
}
I don't want to have 2 separate Repo (Class or Interfaces) or different models to be used. Just want to have 2 different mongoTemplates to be injected in single repo.
Is this possible? If yes, please give some example code.
I have followed below tutorial:
https://dzone.com/articles/multiple-mongodb-connectors-with-spring-boot
As rightly pointed out by #Lucia, below is how it can be done:
Have 2 different configuration placeholders
#Configuration
#EnableMongoRepositories(basePackages = "com.snk.repository", mongoTemplateRef = "mongoTemplateA")
public class MongoConfigA {
// Configuration class for DB 1 access
}
#Configuration
#EnableMongoRepositories(basePackages = "com.snk.repository", mongoTemplateRef = "mongoTemplateB")
public class MongoConfigB {
// Configuration class for DB 2 access
}
Get one class which will help in reading custom properties for mongo db properties in application.properties:
#ConfigurationProperties(prefix = "mongodb")
public class MultipleMongoProperties {
private MongoProperties adb = new MongoProperties();
private MongoProperties bdb = new MongoProperties();
public MongoProperties getAdb() {
return adb;
}
public MongoProperties getBdb() {
return bdb;
}
}
Add a configuration class to create mongoTemplates:
#Configuration
#EnableConfigurationProperties(MultipleMongoProperties.class)
public class MultipleMongoConfig {
#Autowired
private MultipleMongoProperties mongoProperties = new MultipleMongoProperties();
#Bean(name = "mongoTemplateA")
#Primary
public MongoTemplate mongoTemplateA() {
return new MongoTemplate(aDbFactory(this.mongoProperties.getAdb()));
}
#Bean(name = "mongoTemplateB")
public MongoTemplate mongoTemplateB() {
return new MongoTemplate(bDbFactory(this.mongoProperties.getBdb()));
}
#Bean
#Primary
public MongoDbFactory aDbFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClientURI(mongo.getUri()));
}
#Bean
public MongoDbFactory bDbFactory(final MongoProperties mongo) {
return new SimpleMongoDbFactory(new MongoClientURI(mongo.getUri()));
}
}
Add below decelerations to your service/repository:
#Autowired
#Qualifier("mongoTemplateA")
private MongoTemplate mongoTemplateA;
#Autowired
#Qualifier("MongoTemplateB")
private MongoTemplate MongoTemplateB;
Add below properties in your application.properties:
mongodb.adb.uri=mongodb://user:pass#myhost1:27017/adb
mongodb.bdb.uri=mongodb://user:pass#myhost2:27017/bdb
If you have mongo rplica set, URL can be set as:
mongodb.adb.uri=mongodb://user:pass#myhost1,myhost2,myhost13/adb?replicaSet=rsName
mongodb.bdb.uri=mongodb://user:pass#myhost1,myhost2,myhost13/bdb?replicaSet=rsName
Based on your logic, use either of the template.
Thought, there are few catches:
Notice the #Primary annotation, one bean needs to be marked as primary. I haven't find any solution if no template is marked primary.
If any of the mongo DB is down & application is started/restarted, application will not start/deploy. to avoid this, #Autowired needs to be changed to #Autowired(required = false).
If any of the mongo DB is down & application is already running, it automatically uses 2nd mongo BD (which is not down). So, even if you want to use A DB, if it's down, requests are processed with B DB & vice-versa.

Connecting to MongoDB from spring boot app using ssl

im trying to connect my spring boot app to mongodb using ssl. I followed the steps described here, but they dont work for me.
https://www.compose.com/articles/how-to-connecting-to-compose-mongodb-with-java-and-ssl/
any idea?
Thanks Alem
I would suggest that you look at Accessing Data with MongoDB available here https://spring.io/guides/gs/accessing-data-mongodb/ for basic usage examples. spring-boot-starter-data-mongodb will get you a long way, what you need to do is configure a MongoClientOptions bean like this
#Bean
public MongoClientOptions mongoClientOptions(){
System.setProperty ("javax.net.ssl.keyStore","<<PATH TO KEYSTOR >>");
System.setProperty ("javax.net.ssl.keyStorePassword","PASSWORD");
MongoClientOptions.Builder builder = MongoClientOptions.builder();
MongoClientOptions options=builder.sslEnabled(true).build();
return options;
}
and pass the mongo client options to MongoClient instance as an argument as follows
public MongoClient(ServerAddress addr, MongoClientOptions options) {
super(addr, options);
}
Adding further, when mongo processs is started with
mongo --ssl --sslAllowInvalidCertificates --host --port
clients connecting to the mongo process dont have to set any options to support this.
I used this post Spring data mongodb, how to set SSL? and this spring.io guide as reference.
Hope that it helps
If you just want to connect your spring boot app with mongodb, you can use the keyStore and trustStore with java code. So you dont have to add your certificate via command line. If you are using cloud foundry you can connect your app with mongodbServices and then you have all the credentials you need in System.getEnv("VCAP_SERVICES").
#Configuration
public class MongoConfiguration extends AbstractMongoConfiguration {
private static Log logger = LogFactory.getLog(MongoConfiguration.class);
#Value("${spring.data.mongodb.database}")
private String defaultDatabase; //database you want to connect
private String host;
private int port;
private String authenticationDb; //usually admin
private String username;
private char[] password;
private String certificateDecoded; //your CA Certifcate decoded (starts with BEGIN CERTIFICATE)
public MongoConfiguration() {
//method for credentials initialization
}
//you can't set replicaset=replset in mongooptions so if you want set replicaset, you have to use
// customEditorConfigurer in combintaion with class that implementsPropertyEditorRegistrar
#Bean
public static CustomEditorConfigurer customEditorConfigurer(){
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setPropertyEditorRegistrars(
new PropertyEditorRegistrar[]{new ServerAddressPropertyEditorRegistrar()});
return configurer;
}
#Override
protected String getDatabaseName() {
return authenticationDb;
}
#Override
#Bean
public MongoClient mongoClient() {
MongoClient mongoClient = new MongoClient(Arrays.asList(new ServerAddress(host, port)), mongoCredentials(), mongoClientOptions());
return mongoClient;
}
#Bean
public MongoClientOptions mongoClientOptions() {
MongoClientOptions.Builder mongoClientOptions = MongoClientOptions.builder().sslInvalidHostNameAllowed(true).sslEnabled(true);
try {
InputStream inputStream = new ByteArrayInputStream(certificateDecoded.getBytes(StandardCharsets.UTF_8));
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
X509Certificate caCert = (X509Certificate) certificateFactory.generateCertificate(inputStream);
TrustManagerFactory trustManagerFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null); // You don't need the KeyStore instance to come from a file.
keyStore.setCertificateEntry("caCert", caCert);
trustManagerFactory.init(keyStore);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
mongoClientOptions.sslContext(sslContext);
mongoClientOptions.sslInvalidHostNameAllowed(true);
} catch (Exception e) {
throw new IllegalStateException(e);
}
return mongoClientOptions.build();
}
private MongoCredential mongoCredentials() {
return MongoCredential.createCredential(username, authenticationDb, password);
}
//With MongoTemplate you have access to db.
#Bean
public MongoTemplate mongoTemplate() {
SimpleMongoDbFactory factory = new SimpleMongoDbFactory(mongoClient(), defaultDatabase);
return new MongoClient(factory);
}
}
public final class ServerAddressPropertyEditorRegistrar implements PropertyEditorRegistrar {
#Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(ServerAddress[].class, new ServerAddressPropertyEditor());
}
}

Unable to insert collection in mongo database: Can't find a codec

Mongo runtime throws following error when trying to insert a new document in the collection. Note that the database and collection does NOT exist yet (and my assumption is that mongo runtime will create the database, followed by collection and then insert my first document by converting my POJO to BSON using a default codec). Any suggestions?
Error: 2015-12-13 18:46:46,384 - application-akka.actor.default-dispatcher-3 - [error] - application - Can't find a codec for class models.User.
/* Model Class */
package models;
import javax.persistence.Id;
import org.mongojack.ObjectId;
public class User {
#ObjectId #Id public String _id;
public String firstname;
public String lastname;
public String email;
public String phone;
public String address;
}
/* Controller Class */
public class Users extends Controller {
#BodyParser.Of(BodyParser.Json.class)
public Result create() {
Logger.info("Enter - Users::create()");
try {
Form<User> user = Form.form(User.class).bindFromRequest();
if (user.hasErrors()) {
Logger.info("User: " + user.toString());
Logger.info(user.errorsAsJson().toString());
return badRequest(user.errorsAsJson());
}
else {
User oneUser = user.get();
MongoClient mongoClient= new MongoClient();
MongoDatabase db = mongoClient.getDatabase("marketplace");
MongoCollection<User> col = db.getCollection("users", User.class);
col.insertOne(oneUser);
mongoClient.close();
return ok();
}
}
catch (Exception e) {
Logger.error(e.getLocalizedMessage());
return internalServerError(e.getMessage());
}
finally {
Logger.info("Exit - Users::create()");
}
}
}
You will have to wrap the MongoCollection with a JacksonDBCollection to enable all the MongoJack features.
Something like this:
JacksonDBCollection<User, String> userColl = JacksonDBCollection.wrap(col, User.class, String.class);
And then use userColl to insert your new object.
It seems that this the only way supported at this time. This requires using the getDB() method, which is deprecated since the mongo driver 3.0.
See the relevant issue on github here:
https://github.com/mongojack/mongojack/issues/105
Yup - this works. However I had to use deprecated getDB since it has a getter that returns collection of type DBCollection which "wrap" function expects. I was hoping to use getDatabase instead but gets me MongoCollection which Jackson's "Wrap" won't accept. Any suggestions?
User oneUser = user.get();
MongoClient mongoClient= new MongoClient();
DB db = mongoClient.getDB("marketplace");
DBCollection col = db.getCollection("marketplace");
JacksonDBCollection<User, String> userCol = JacksonDBCollection.wrap(col,User.class, String.class);
userCol.insert(oneUser);

Whether Replication Possible With Spring Data Couchbase?

HI i just want to know whether the XDCR replication is possible with spring data Couchbase. If possible how can i achieve that .please help .
My Code Sample
//configuration class
#Configuration
public class ApplicationConfig {
#Bean
public CouchbaseClient couchbaseClient() throws IOException {
return new CouchbaseClient(Arrays.asList(URI
.create("http://localhost:8091/pools")), "xxxw", "");
}
#Bean
public CouchbaseTemplate couchbaseTemplate() throws IOException {
return new CouchbaseTemplate(couchbaseClient());
}
}
#Document
public class Person {
#Field
String name;
#Id
String id;
public Person(final String personId, final String personname,
final int personIdAge, final JSONObject personData) {
this.id = personId;
this.name = personname;
this.age = personIdAge;
this.body = personData;
}
}
//main Test class
public class Test {
public static void main(String s[]) {
try {
ApplicationContext context = new AnnotationConfigApplicationContext(
ApplicationConfig.class);
CouchbaseTemplate template = context.getBean("couchbaseTemplate",
CouchbaseTemplate.class);
} catch (Exception e) {
e.printStackTrace();
}
}
How can i achieve repltcaion to elastic search index through spring data couchbase . with this sample classes ..??
Using elastic search in Couchbase is not dependent on what you client application looks like or whether or not it uses Spring. As long as you are storing data in Couchbase in JSON format things should work fine.
Setting up elastic search is more of an operations task than a development task. Take a look at the instructions at the link below and then run you application code as is. If you have configured everything properly then your data should end up in elastic search.
http://docs.couchbase.com/couchbase-elastic-search/

MonoDb creates connection every single operation on collection

I'm using mongodb-java-driver, and nothing else except that. I created singleten EJB with connection to Mongo.
#Singleton
public class MongoConnection {
private DB db = null;
private MongoClient mongoClient = null;
#PostConstruct
public void init() {
try {
mongoClient = new MongoClient("localhost", 27017);
db = mongoClient.getDB("mydb");
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
public MongoConnection() {
}
public DB getDb() {
return db;
}
public DBCollection getCollectionInDatabase(String collection) {
DBCollection coll;
coll = db.getCollection(collection);
return coll;
}
}
I get this ejb in ApplicationScoped bean (JSF) (just to be sure, that I will have only ONE instance of DB connection).
#Named("appMongo")
#ApplicationScoped
public class MongoApplicationScope implements Serializable{
private static final long serialVersionUID = 1L;
#EJB MongoConnection mu;
public MongoConnection getMu() {
return mu;
}
public void setMu(MongoConnection mu) {
this.mu = mu;
}
}
Then in request scoped bean I get data from db
#Named("mongoBean")
#SessionScoped
public class MongoBean implements Serializable {
private static final long serialVersionUID = 1L;
#Inject MongoApplicationScope mongoAccess;
public void mongoDzialanie() {
DBCollection coll = mongoAccess.getMu().getDb().getCollection("oko"); //at this step everything is correct
System.out.println(coll.getCount()); //new connection is created text from mongoDB console -> connection accepted from 127.0.0.1:57700 #2 (2 connections now open)
}
Why even if I have the same "db" object instance I can't get data without creating new connection, why I can't share this connection as it should be due to pooling?
}
MongoDB drivers open new connections on the background each time you work with a collection. Drivers decide when to open a new one. I believe it depends on the driver's implementation.
You can control the max number of connections opened by setting poolSize value (default is 5 for Node.JS MongoDb driver http://mongodb.github.io/node-mongodb-native/2.2/api/MongoClient.html#connect) It could be different for Java or other languages. Check your documentation.
If you are going to have more than one db object, each object will have its own connection pool. In my case I have mainDb and logsDb. Each have pool of 10. Therefore, up to 20 concurrent connections will be opened.
Finally, if you are using node.js driver, make sure to pass Number as value and not string (i.e poolSize:10). This will save you hours/days of troubleshooting :)