Adding applicationproperties in Jhipster - java

I'm using jhipster microservices app for my development. Based on jhipster documentation for adding application-specific is here:
application-dev.yml and
ApplicationProperties.java
I did this by adding this
application:
mycom:
sgADIpAddress: 172.x.x.xxx
and this my applicationconfig class
package com.mbb.ias.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* Properties specific to JHipster.
*
* <p>
* Properties are configured in the application.yml file.
* </p>
*/
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
private final Mycom mycom= new Mycom();
public Mycom getMycom () {
return mycom;
}
public static class Mycom {
String sgADIpAddress ="";
public String getSgADIpAddress() {
return sgADIpAddress;
}
public void setSgADIpAddress(String sgADIpAddress) {
this.sgADIpAddress = sgADIpAddress;
}
}
}
I've call this by using same like jhipster properties which are
#Inject
private ApplicationProperties applicationProperties;
in classes which are need this AD IP address.
it will throw null value
java.lang.NumberFormatException: null
at java.lang.Integer.parseInt(Unknown Source)
at java.lang.Integer.parseInt(Unknown Source)
please help me guys, SIT going to be started, I need to create a profile for maven build like jhipster created

I have the same problem and spent a couple of hours to figure it out...Jhipster has its preconfigured property class that users can customize their own properteis:
Quote from Jhipster website:
Your generated application can also have its own Spring Boot properties. This is highly recommended, as it allows type-safe configuration of the application, as well as auto-completion and documentation within an IDE.
JHipster has generated a ApplicationProperties class in the config package, which is already preconfigured, and it is already documented at the bottom the application.yml, application-dev.yml and application-prod.yml files. All you need to do is code your own specific properties.
In my case, I have set the properties in all yml files.
application:
redis:
host: vnode1
pool:
max-active: 8
max-idle: 8
max-wait: -1
min-idle: 0
port: 6379
In ApplicationProperties class:
#ConfigurationProperties(prefix = "application", ignoreUnknownFields = false)
public class ApplicationProperties {
public final Redis redis = new Redis();
public Redis getRedis() {
return redis;
}
public static class Redis {
private String host = "127.0.0.1";
private int port = 0;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
private Pool pool = new Pool();
public void setPool(Pool pool) {
this.pool = pool;
}
public Pool getPool() {
return this.pool;
}
public static class Pool {
private int maxActive = 8;
private int maxWait = -1;
private int maxIdle = 8;
private int minIdle = 0;
public int getMaxIdle() {
return maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxActive() {
return maxActive;
}
public int getMinIdle() {
return minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getMaxWait() {
return maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
}
}
}
Then I use it as:
private final ApplicationProperties.Redis redis;
public RedisConfiguration(ApplicationProperties applicationProperties){
redis = applicationProperties.getRedis();
}
For instance use max-wait and host:
this.redis.getPool().getMaxWait();
this.redis.getHost();

refering to this thread
Spring annotation #Inject doesn't work
i remove my new operator for all classes which is calling my applicationproperties.java
#Service
public class ADAuthenticatorService {
private static final Logger log = LoggerFactory.getLogger(ADAuthenticatorService.class);
private final static long DIFF_NET_JAVA_FOR_DATE_AND_TIMES = 11644473600000L;
#Inject
ADContext adContext;
/**
* AD authentication
*
* #param UserID,
* AD User ID
* #param Password,
* AD Password
* #return ADProfile
*/
#Inject
ApplicationProperties applicationProperties;
public ADProfile authenticate(String UserID, String Password) throws Exception {
ADContext context = adContext.getDefaultContext(applicationProperties);
return authenticate(context, UserID, Password);
}
in my ADContext i put #component on the top of my Class name, and added #Sevice annotation on the top of ADAuthenticatorService
then my
#Inject
ApplicationProperties applicationProperties;
is working flawlessly
just posting this answer so any noob like me at outside can benefit this lol

Related

Why PostConstruct method does not work to keep bean init order when using in springboot?

I have some config information in the database, when the springboot starts, it will load this information only one time. So I write the code like this:
#Configuration
public class CommonConfig {
private final DbConfigDao dbConfigDao;
#Autowired
public CommonConfig (DbConfigDao dbConfigDao) {
this.dbConfigDao = dbConfigDao;
}
private int redisStoreDays; //omit get,set method
#PostConstruct
public void init() {
redisStoreDays = dbConfigDao.getValueByKey("redisStoreDays");
//... continue to load the other config from db
}
}
In the other bean, I try to get the redisStoreDays value, it returns 0, but the real value is 1.
#Configuration
public class AutoConfiguration {
#Conditional(RedisClusterConditional.class)
#Bean(name = "redisDao", initMethod = "init")
public RedisClusterDao getRedisClusterDao() {
return new RedisClusterDaoImpl();
}
#Conditional(RedisSentryConditional.class)
#Bean(name = "redisDao", initMethod = "init")
public RedisSentryConditional getRedisSentryDao() {
return new RedisSentryDaoImpl();
}
}
public class RedisClusterDaoImpl implements RedisDao {
#Autowired
private CommonConfig commonConfig;
private int storeDays = 7;
#Override
public void init() {
storeDays = 60 * 60 * 24 * commonConfig.getRedisStoreDays();
//commonConfig.getRedisStoreDays is 0 but in fact is 1
}
}
How to keep the bean init order?
I try to add PostConstruct in my redis bean, but it still does not work.
I debug and find the commonConfig is not null, but commonConfig.getRedisStoreDays() returns 0.
After executing init method in RedisClusterDaoImpl, commonConfig.getRedisStoreDays() changes to 1.
I also try to add #AutoConfigureBefore(RedisClusterDao.class),but storeDays still gets 0 in RedisClusterDaoImpl class.
Use Spring's #DependsOn, i.e.:
#DependsOn("CommonConfig")
#Configuration
public class AutoConfiguration {
...
}
Why not
#Configuration
public class CommonConfig {
private final DbConfigDao dbConfigDao;
#Autowired
public CommonConfig (DbConfigDao dbConfigDao) {
this.dbConfigDao = dbConfigDao;
// why in PostConstruct? You have the bean right here, it should be initialized
redisStoreDays = dbConfigDao.getValueByKey("redisStoreDays");
}
private int redisStoreDays; //omit get,set method
}
or just
public class RedisClusterDaoImpl implements RedisDao {
#Autowired
private DbConfigDao commonConfig;
private int storeDays = 7;
#Override
public void init() {
storeDays = 60 * 60 * 24 * dbConfigDao.getValueByKey("redisStoreDays");
}
}
After all, this is the redis bean, I don't see why it shouldn't know the name of the config setting it needs.

Initialisation of Spring ConfigurationProperties

I have the following class mapping parts of the properties from the application.properties:
#Component
#ConfigurationProperties(prefix = "city")
#Getter
#Setter
public class CityProperties {
private int populationAmountWorkshop;
private double productionInefficientFactor;
private Loaner loaner = new Loaner();
private Tax tax = new Tax();
private Guard pikeman = new Guard();
private Guard bowman = new Guard();
private Guard crossbowman = new Guard();
private Guard musketeer = new Guard();
#Getter
#Setter
public static class Loaner {
private int maxRequest;
private int maxAgeRequest;
private int maxNbLoans;
}
#Getter
#Setter
public static class Tax {
private double poor;
private double middle;
private double rich;
private int baseHeadTax;
private int basePropertyTax;
}
#Getter
#Setter
public static class Guard {
private int weeklySalary;
}
}
A portion of application.properties:
#City
# Amount of inhabitants to warrant the city to have one workshop
city.populationAmountWorkshop=2500
# Factor that is applied on the efficient production to get the inefficient production
city.productionInefficientFactor=0.6
# Maximum requests per loaner
city.loaner.maxRequest=6
# Maximum age of loan request in weeks
city.loaner.maxAgeRequest=4
# Maximum loan offers per loaner
city.loaner.maxNbLoans=3
# Weekly tax value factor for the various population per 100 citizens
city.tax.poor=0
city.tax.middle=0.6
city.tax.rich=2.0
city.tax.baseHeadTax=4
city.tax.basePropertyTax=280
city.pikeman.weeklySalary=3
city.bowman.weeklySalary=3
city.crossbowman.weeklySalary=4
city.musketeer.weeklySalary=6
Then this is the application for the test setup:
#SpringBootApplication
#Import({ServerTestConfiguration.class})
#ActiveProfiles("server")
#EnableConfigurationProperties
#PropertySource(value = {"application.properties", "server.properties", "bean-test.properties"})
public class SavegameTestApplication {
}
These are annotations on the ServerTestConfiguration class all other imported confugrations are the same as I use in the production case as well:
#Configuration
#EnableAutoConfiguration
#Import(value = {ClientServerInterfaceServerConfiguration.class, ServerConfiguration.class, ImageConfiguration.class})
public class ServerTestConfiguration {
...
}
And finally the constructor of my test class that initializes the Spring-Boot application:
public CityWallSerializationTest() {
SpringApplicationBuilder builder = new SpringApplicationBuilder(SavegameTestApplication.class);
DependentAnnotationConfigApplicationContext context = (DependentAnnotationConfigApplicationContext)
builder.contextClass(DependentAnnotationConfigApplicationContext.class).profiles("server").run();
setContext(context);
setClientServerEventBus((AsyncEventBus) context.getBean("clientServerEventBus"));
IConverterProvider converterProvider = context.getBean(IConverterProvider.class);
BuildProperties buildProperties = context.getBean(BuildProperties.class);
Archiver archiver = context.getBean(Archiver.class);
IDatabaseDumpAndRestore databaseService = context.getBean(IDatabaseDumpAndRestore.class);
TestableLoadAndSaveService loadAndSaveService = new TestableLoadAndSaveService(context, converterProvider,
buildProperties, archiver, databaseService);
setLoadAndSaveService(loadAndSaveService);
}
This works fine in my production code, however when I want to write some tests using a spring boot application the values are not initialized.
Printing out the CityProperties at the end of the constructor results in this output:
CityProperties(populationAmountWorkshop=0, productionInefficientFactor=0.0, loaner=CityProperties.Loaner(maxRequest=0, maxAgeRequest=0, maxNbLoans=0), tax=CityProperties.Tax(poor=0.0, middle=0.0, rich=0.0, baseHeadTax=0, basePropertyTax=0), pikeman=CityProperties.Guard(weeklySalary=0), bowman=CityProperties.Guard(weeklySalary=0), crossbowman=CityProperties.Guard(weeklySalary=0), musketeer=CityProperties.Guard(weeklySalary=0))
I would like to understand how Spring handles the initialization of these ConfigurationProperties annotated classes, how the magic happens so to speak. I want to know this in order to properly debug the application to figure out where it goes wrong.
The productive code is a JavaFX application, that makes the whole initialization a bit more complicated:
#Slf4j
#SpringBootApplication
#Import(StandaloneConfiguration.class)
#PropertySource(value = {"application.properties", "server.properties"})
public class OpenPatricianApplication extends Application implements IOpenPatricianApplicationWindow {
private StartupService startupService;
private GamePropertyUtility gamePropertyUtility;
private int width;
private int height;
private boolean fullscreen;
private Stage primaryStage;
private final AggregateEventHandler<KeyEvent> keyEventHandlerAggregate;
private final MouseClickLocationEventHandler mouseClickEventHandler;
private ApplicationContext context;
public OpenPatricianApplication() {
width = MIN_WIDTH;
height = MIN_HEIGHT;
this.fullscreen = false;
keyEventHandlerAggregate = new AggregateEventHandler<>();
CloseApplicationEventHandler closeEventHandler = new CloseApplicationEventHandler();
mouseClickEventHandler = new MouseClickLocationEventHandler();
EventHandler<KeyEvent> fullScreenEventHandler = event -> {
try {
if (event.getCode().equals(KeyCode.F) && event.isControlDown()) {
updateFullscreenMode();
}
} catch (RuntimeException e) {
log.error("Failed to switch to/from fullscreen mode", e);
}
};
EventHandler<KeyEvent> closeEventWindowKeyHandler = event -> {
if (event.getCode().equals(KeyCode.ESCAPE)) {
log.info("Pressed ESC");
context.getBean(MainGameView.class).closeEventView();
}
};
addKeyEventHandler(closeEventHandler);
addKeyEventHandler(fullScreenEventHandler);
addKeyEventHandler(closeEventWindowKeyHandler);
}
/**
* Add a key event handler to the application.
* #param eventHandler to be added.
*/
private void addKeyEventHandler(EventHandler<KeyEvent> eventHandler) {
keyEventHandlerAggregate.addEventHandler(eventHandler);
}
public static void main(String[] args) {
launch(args);
}
#Override
public void init() {
SpringApplicationBuilder builder = new SpringApplicationBuilder(OpenPatricianApplication.class);
context = builder.contextClass(DependentAnnotationConfigApplicationContext.class).profiles("standalone")
.run(getParameters().getRaw().toArray(new String[0]));
this.startupService = context.getBean(StartupService.class);
this.gamePropertyUtility = context.getBean(GamePropertyUtility.class);
if (startupService.checkVersion()) {
startupService.logEnvironment();
CommandLineArguments cmdHelper = new CommandLineArguments();
Options opts = cmdHelper.createCommandLineOptions();
CommandLine cmdLine = cmdHelper.parseCommandLine(opts, getParameters().getRaw().toArray(new String[getParameters().getRaw().size()]));
if (cmdLine.hasOption(CommandLineArguments.HELP_OPTION)){
cmdHelper.printHelp(opts);
System.exit(0);
}
if (cmdLine.hasOption(CommandLineArguments.VERSION_OPTION)) {
System.out.println("OpenPatrician version: "+OpenPatricianApplication.class.getPackage().getImplementationVersion());
System.exit(0);
}
cmdHelper.persistAsPropertyFile(cmdLine);
}
}
#Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setMinWidth(MIN_WIDTH);
this.primaryStage.setMinHeight(MIN_HEIGHT);
primaryStage.getIcons().add(new Image(getClass().getResourceAsStream("/icons/trade-icon.png")));
UIFactory uiFactory = context.getBean(UIFactory.class);
uiFactory.setApplicationWindow(this);
BaseStartupScene startupS = uiFactory.getStartupScene();
Scene defaultScene = new Scene(startupS.getRoot(), width, height);
defaultScene.getStylesheets().add("/styles/font.css");
this.fullscreen = Boolean.valueOf((String) gamePropertyUtility.getProperties().get("window.fullscreen"));
startupS.setSceneChangeable(this);
defaultScene.setOnMousePressed(mouseClickEventHandler);
defaultScene.setOnKeyPressed(keyEventHandlerAggregate);
try {
CheatKeyEventListener cheatListener = context.getBean(CheatKeyEventListener.class);
if (cheatListener != null) {
addKeyEventHandler(cheatListener);
}
} catch (Exception e) {
// the cheat listener is no defined for the context.
e.printStackTrace();
}
setCursor(defaultScene);
primaryStage.setFullScreen(fullscreen);
primaryStage.setFullScreenExitHint("");
primaryStage.setTitle("OpenPatrician");
primaryStage.setScene(defaultScene);
primaryStage.show();
}
private void setCursor(Scene scene) {
URL url = getClass().getResource("/icons/64/cursor.png");
try {
Image img = new Image(url.openStream());
scene.setCursor(new ImageCursor(img));
} catch (IOException e) {
log.warn("Failed to load cursor icon from {}", url);
}
}
/**
* #see SceneChangeable#changeScene(OpenPatricianScene)
*/
#Override
public void changeScene(final OpenPatricianScene scene) {
primaryStage.getScene().setOnMousePressed(mouseClickEventHandler);
primaryStage.getScene().setOnKeyPressed(keyEventHandlerAggregate);
primaryStage.getScene().setRoot(scene.getRoot());
}
/**
* Toggle between full screen and non full screen mode.
*/
public void updateFullscreenMode() {
fullscreen = !fullscreen;
primaryStage.setFullScreen(fullscreen);
}
#Override
public double getSceneWidth() {
return primaryStage.getScene().getWidth();
}
#Override
public double getSceneHeight() {
return primaryStage.getScene().getHeight();
}
#Override
public void stop() throws Exception {
System.out.println("Stopping the UI Application");
stopUIApplicationContext();
super.stop();
}
/**
* Closing the application context for the user interface.
*/
private void stopUIApplicationContext() {
AsyncEventBus eventBus = (AsyncEventBus) context.getBean("clientServerEventBus");
eventBus.post(new GameStateChange(EGameStatusChange.SHUTDOWN));
((AbstractApplicationContext)context).close();
}
}
It seems like you are mismatching test code and production code. Let me explain:
#SpringBootApplication is intented to run your class with the main. Usually, this looks like:
#SpringBootApplication
public class MyApplication {
public static void main(String[] args] {
SpringApplication.run(MyApplication .class, args);
}
}
If you want to test an application, then your test class has to be annotated with #SpringBootTest. This annotation will automatically detect a class annotated by #SpringBootConfiguration (which is included by #SpringBootApplication).
Within your #SpringBootTest class, you can also use #Import to load other configuration classes.
#ActiveProfiles can only be used in a test context... You are not using any profile when running your "test" class, as this annotation is test purpose only. It will be if you switch to #SpringBootTest.
Still in a Test context, if you want to load properties files (other than application.properties), you have to use #TestPropertySource instead of #PropertySource.
Your "Main" test class is supposed to look something like that:
#SpringBootTest
#ActiveProfiles("server")
#Import({ServerTestConfiguration.class})
#TestPropertySource(locations = {"classpath:/server.properties, "classpath:/bean-test.properties"}})
public class SavegameTestApplication {
}
Fore more information on Spring Boot Application Test, check this: https://www.baeldung.com/spring-boot-testing#integration-testing-with-springboottest
Once you are done with test class, you should be removing your CityWallSerializationTest which is basically a re-implementation of #SpringBootTest. Please note that you can use #Autowired in your test classes as well.
The class that handles the binding of ConfigurationProperties is ConfigurationPropertiesBindingPostProcessor.
In this particular case it turned out that the only properties file loaded was the application.properties that was present on the class path from the test project instead of the application.properties that actually contains the proper key value pairs.
This can be seen at startup when running the application with -Dlogging.level.org.springframework=DEBUG and then look in the output for:
2019-12-31 10:54:49,884 [main] DEBUG o.s.b.SpringApplication : Loading source class ch.sahits.game.savegame.SavegameTestApplication
2019-12-31 10:54:49,908 [main] DEBUG o.s.b.c.c.ConfigFileApplicationListener : Loaded config file 'file:<path>/OpenPatrician/OpenPatricianModel/target/classes/application.properties' (classpath:/application.properties)
2
The second line specifies the location of the application.properties that is loaded.

In Spring Boot which class would be initialize first #Component or #Configuration

I have a class annotated with #Component which is use to initialze application.yml config properties. Service classe is using configuration property. But sometime my Service class instance created before the Configuration class and I get null property value in service class, Its random not specific pattern.
Configuration Initializer class..
#Component
public class ConfigInitializer implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(ConfigInitializer.class);
#Autowired
ProxyConfig proxyConfig;
/*#PostConstruct
public void postConstruct(){
setProperties();
}
*/
#Override
public void afterPropertiesSet() {
setProperties();
}
private void setSystemProperties(){
log.debug("Setting properties...");
Properties props = new Properties();
props.put("PROXY_URL", proxyConfig.getProxyUrl());
props.put("PROXY_PORT", proxyConfig.getProxyPort());
System.getProperties().putAll(props);
}
}
#Component
#ConfigurationProperties(prefix = "proxy-config")
public static class ProxyConfig {
private String proxyUrl;
private String proxyPort;
public String getProxyUrl() {
return proxyUrl;
}
public void setProxyUrl(String proxyUrl) {
this.proxyUrl = proxyUrl;
}
public String getProxyPort() {
return proxyPort;
}
public void setProxyPort(String proxyPort) {
this.proxyPort = proxyPort;
}
}
Service Class..
#Service("receiverService")
public class ReceiverService {
private static final Logger logger = LoggerFactory.getLogger(ReceiverService.class);
private ExecutorService executorService = Executors.newSingleThreadExecutor();
#Autowired
public ReceiverService() {
initClient();
}
private void initClient() {
Future future = executorService.submit(new Callable(){
public Object call() throws Exception {
String value = System.getProperty("PROXY_URL"); **//Here I am getting null**
logger.info("Values : " + value);
}
});
System.out.println("future.get() = " + future.get());
}
}
Above Service class get null values String value = System.getProperty("PROXY_URL")
When I use #DependsOn annotation on Service class, it works fine.
In my little knowledge, I know Spring does not have specific order of bean creation.
I want to know If I use #Configuration instead of #Component on ConfigInitializer class like below, Will spring initialize ConfigInitializer
class before other beans ?.
#Configuration
public class ConfigInitializer implements InitializingBean {
//code here
}

Dynamically use different database based on previous lookup with AbstractRoutingDataSource

We will face high data volume on our mariadb databases. To overcome problems with backups and ddl operations, we had the idea to store the data into multiple databases. Basically we will have a database with short term data (e.g. last 30 days, named short_term) and another one with the rest of the data (named long_term). Obviously the data needs to be moved from short_term to long_term but that should be achievable.
The problem I'm currently facing on a prototype is that I can connect to short_term but am not able to switch to long_term if for example want to query both of them (e.g. get() where I can't find it in the short_term database).
I have set it up like this (both work independently but not with switch database context):
HistoryAwareRoutingSource:
public class HistoryAwareRoutingSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return ThreadLocalStorage.getDatabaseType();
}
}
ThreadLocalStorage
public class ThreadLocalStorage {
private static ThreadLocal<String> databaseType = new ThreadLocal<>();
public static void setDatabaseType(String databaseTypeName) {
databaseType.set(databaseTypeName);
}
public static String getDatabaseType() {
return databaseType.get();
}
}
DatasourceConfiguration
#Configuration
public class DatasourceConfiguration {
#Value("${spring.datasource.url}")
private String db1Url;
#Value("${spring.datasource.username}")
private String db1Username;
#Value("${spring.datasource.password}")
private String db1Password;
#Value("${spring.datasource.driver-class-name}")
private String db1DriverClassName;
#Value("${spring.datasource.connectionProperties}")
private String db1ConnectionProps;
#Value("${spring.datasource.sqlScriptEncoding}")
private String db1Encoding;
#Value("${spring.datasource2.url}")
private String db2Url;
#Value("${spring.datasource2.username}")
private String db2Username;
#Value("${spring.datasource2.password}")
private String db2Password;
#Value("${spring.datasource2.driver-class-name}")
private String db2DriverClassName;
#Value("${spring.datasource2.connectionProperties}")
private String db2ConnectionProps;
#Value("${spring.datasource2.sqlScriptEncoding}")
private String db2Encoding;
#Bean
public DataSource dataSource() {
HistoryAwareRoutingSource historyAwareRoutingSource = new HistoryAwareRoutingSource();
Map<Object, Object> dataSourceMap = new HashMap<>();
dataSourceMap.put("PRIMARY", dataSource1());
dataSourceMap.put("SECONDARY", dataSource2());
historyAwareRoutingSource.setDefaultTargetDataSource(dataSource1());
historyAwareRoutingSource.setTargetDataSources(dataSourceMap);
historyAwareRoutingSource.afterPropertiesSet();
return historyAwareRoutingSource;
}
private DataSource dataSource1() {
HikariDataSource primary = new HikariDataSource();
primary.setInitializationFailTimeout(0);
primary.setMaximumPoolSize(5);
primary.setDriverClassName(db1DriverClassName);
primary.setJdbcUrl(db1Url);
primary.setUsername(db1Username);
primary.setPassword(db1Password);
primary.addDataSourceProperty("connectionProperties", db1ConnectionProps);
primary.addDataSourceProperty("sqlScriptEncoding", db1Encoding);
return primary;
}
private DataSource dataSource2() {
HikariDataSource secondary = new HikariDataSource();
secondary.setInitializationFailTimeout(0);
secondary.setMaximumPoolSize(5);
secondary.setDriverClassName(db2DriverClassName);
secondary.setJdbcUrl(db2Url);
secondary.setUsername(db2Username);
secondary.setPassword(db2Password);
secondary.addDataSourceProperty("connectionProperties", db2ConnectionProps);
secondary.addDataSourceProperty("sqlScriptEncoding", db2Encoding);
return secondary;
}
}
Then I have a RestController class like this:
#RestController
#RequestMapping(value = "/story")
#RequiredArgsConstructor
public class MultiDBController {
#Autowired
private StoryService storyService;
#GetMapping("/create")
#UsePrimaryStorage
public ResponseEntity<StoryDTO> createEntity() {
setPrimaryDB();
return ResponseEntity.ok(storyService.createOne());
}
private void setPrimaryDB() {
// TODO destroy the current db connection or hand it back to the pool so the next time a connect is taken it is the PRIMARY Datasource?
ThreadLocalStorage.setDatabaseType("PRIMARY");
}
private void setSecondaryDB() {
// TODO destroy the current db connection or hand it back to the pool so the next time a connect is taken it is the PRIMARY Datasource?
ThreadLocalStorage.setDatabaseType("SECONDARY");
}
#GetMapping("/{storyId}")
public ResponseEntity<StoryDTO> get(#PathVariable UUID storyId) {
// try to find in primary db
setPrimaryDB();
Optional<StoryDTO> storyOptional = storyService.findOne(storyId);
if (!storyOptional.isPresent()) {
setSecondaryDB();
Optional<StoryDTO> storyOptionalSecondary = storyService.findOne(storyId);
if(storyOptionalSecondary.isPresent()) {
return ResponseEntity.ok(storyOptionalSecondary.get());
} else {
return ResponseEntity.notFound().build();
}
}
return ResponseEntity.ok(storyOptional.get());
}
}
So the question is, how do I implement the TODO's

Use Dropwizard configuration in a method that establishes a connection to a MongoDB database

I am coding Dropwizard micro-services that fetch data in a MongoDB database. The micro-services work fine but I'm struggling to use in my DAO the configuration coming from my Dropwizard configuration Java class. Currently I have
public class XDAO implements IXDAO {
protected DB db;
protected DBCollection collection;
/* singleton */
private static XDAO instance;
/* Get singleton */
public static synchronized XDAO getSingleton(){
if (instance == null){
instance = new XDAO();
}
return instance;
}
/* constructor */
public XDAO(){
initDatabase();
initDatabaseIndexes();
}
private void initDatabase(){
MongoClient client = null;
try {
client = new Mongo("10.126.80.192",27017);
db = client.getDB("terre");
//then some other code
}
catch (final MongoException e){
...
}
catch (UnknownHostException e){
...
}
}
}
I want to unhard-code the three arguments in these two lines :
client = new Mongo("10.126.80.192", 27017);
db = client.getDB("terre");
My MongoConfiguration Java class is :
public class MongoConfiguration extends Configuration {
#JsonProperty
#NotEmpty
public String host;
#JsonProperty
public int port = 27017;
#JsonProperty
#NotEmpty
public String db_name;
public String getMongohost() {
return host;
}
public void setMongohost(String host) {
this.host = host;
}
public int getMongoport() {
return port;
}
public void setMongoport(int port) {
this.port = port;
}
public String getDb_name() {
return db_name;
}
public void setDb_name(String db_name) {
this.db_name = db_name;
}
}
My Resource class that uses the DAO is :
#Path("/mongo")
#Produces(MediaType.APPLICATION_JSON)
public class MyResource {
private XDAO xDAO = XDAO.getSingleton();
private String mongohost;
private String db_name;
private int mongoport;
public MyResource(String db_name, String mongohost, int mongoport) {
this.db_name = db_name;
this.mongohost = mongohost;
this.mongoport = mongoport;
}
public MyResource() {
}
#GET
#Path("/findByUUID")
#Produces(value = MediaType.APPLICATION_JSON)
#Timed
public Entity findByUUID(#QueryParam("uuid") String uuid) {
return xDAO.findByUUid(uuid);
}
}
And in my application class there is
#Override
public void run(final MongoConfiguration configuration, final Environment environment) {
final MyResource resource = new MyResource(configuration.getDb_name(), configuration.getMongohost(), configuration.getMongoport());
environment.jersey().register(resource);
}
To solve my problem I tried many things. The last thing I tried was to add these four fields in my XDAO
private String mongohost;
private String db_name;
private int mongoport;
private static final MongoConfiguration configuration = new MongoConfiguration();
Coming with this piece of code in the constructor of the XDAO:
public XDAO(){
instance.mongohost = configuration.getMongohost();
instance.mongoport = configuration.getMongoport();
instance.db_name = configuration.getDb_name();
/* then like before */
initDatabase();
initDatabaseIndexes();
}
When I try this I have a null pointer exception when my initDatabase method is invoked : mongoHost and db_name are null
The problem is that you are creating a new configuration in your XDAO with private static final MongoConfiguration configuration = new MongoConfiguration(); instead of using the config from Dropwizard's run method.
When you do this, the fields host and db_name in the new configuration are null, which is why you are getting the NPE when instantiating XDAO
You need to pass the instance of MongoConfiguration that you get from Dropwizard in your application class to your XDAO, ideally when the singleton XDAO is created so it has non-null values for db_name and host
This code below part of the problem - you are creating the singleton without giving XDAO the MongoConfiguration configuration instance.
public class XDAO implements IXDAO {
//... snip
/* Get singleton */
public static synchronized XDAO getSingleton(){
if (instance == null){
instance = new XDAO(); // no configuration information is included!
}
return instance;
}
/* constructor */
public XDAO(){
initDatabase(); // this call needs db_name & host but you haven't set those yet!!
initDatabaseIndexes();
}
I recommend you modify your application class to create XDAO along the lines of this:
#Override
public void run(final MongoConfiguration configuration, final Environment environment) {
XDAO XDAOsingleton = new XDAO(configuration);
XDAO.setSingletonInstance(XDAOsingleton); // You need to create this static method.
final MyResource resource = new MyResource(configuration.getDb_name(), configuration.getMongohost(), configuration.getMongoport()); // MyResource depends on XDAO so must be created after XAO's singleton is set
environment.jersey().register(resource);
}
You may also need to take initDatabase() etc out of XDAO's constructor depending on if you keep public static synchronized XDAO getSingleton()
I also recommend you change the constructor of MyResource to public MyResource(XDAO xdao). The resource class doesn't appear to need the configuration information, and it is better to make the dependency on an XDAO explicit (you then also don't need to keep the XDAO singleton in a static field inside XDAO's class).
To get MongoDB integrated in a simple way to Dropwizard, please try and use MongoDB Managed Object. I will explain this in 3 simple steps:
Step 1: Create a simple MongoManged class:
import com.mongodb.Mongo;
import io.dropwizard.lifecycle.Managed;
public class MongoManaged implements Managed {
private Mongo mongo;
public MongoManaged(Mongo mongo) {
this.mongo = mongo;
}
#Override
public void start() throws Exception {
}
#Override
public void stop() throws Exception {
mongo.close();
}
}
Step 2: Mention MongoDB Host, Port, DB Name in a config yml file:
mongoHost : localhost
mongoPort : 27017
mongoDB : softwaredevelopercentral
Step 3: Bind everything together in the Application Class:
public class DropwizardMongoDBApplication extends Application<DropwizardMongoDBConfiguration> {
private static final Logger logger = LoggerFactory.getLogger(DropwizardMongoDBApplication.class);
public static void main(String[] args) throws Exception {
new DropwizardMongoDBApplication().run("server", args[0]);
}
#Override
public void initialize(Bootstrap<DropwizardMongoDBConfiguration> b) {
}
#Override
public void run(DropwizardMongoDBConfiguration config, Environment env)
throws Exception {
MongoClient mongoClient = new MongoClient(config.getMongoHost(), config.getMongoPort());
MongoManaged mongoManaged = new MongoManaged(mongoClient);
env.lifecycle().manage(mongoManaged);
MongoDatabase db = mongoClient.getDatabase(config.getMongoDB());
MongoCollection<Document> collection = db.getCollection(config.getCollectionName());
logger.info("Registering RESTful API resources");
env.jersey().register(new PingResource());
env.jersey().register(new EmployeeResource(collection, new MongoService()));
env.healthChecks().register("DropwizardMongoDBHealthCheck",
new DropwizardMongoDBHealthCheckResource(mongoClient));
}
}
I have used these steps and written a blog post and a sample working application code is available on GitHub. Please check: http://softwaredevelopercentral.blogspot.com/2017/09/dropwizard-mongodb-tutorial.html

Categories