I'm trying to create immutable configuration by creating an interface and its implementation that is of package private. Only the interface is exposed to the client; the implementation is hidden and cannot be called outside of the package. Interface has accessors and mutators and a static method that instantiates its implementation and returns itself. Whenever mutator is called a new instance is being created passing all the fields in the constructor in order not to change the original values of the first object.
Here are my codes:
package com.example.api;
public interface Config {
static Config newConfig() {
return new ConfigImpl();
}
String host();
Config host(String host);
int port();
Config port(int port);
String database();
Config database(String database);
String user();
Config user(String user);
String password();
Config password(String password);
}
package com.example.api;
class ConfigImpl implements Config {
private final String host;
private final int port;
private final String database;
private final String user;
private final String password;
public ConfigImpl() {
this(null, -1, null, null, null);
}
public ConfigImpl(String host, int port, String database, String user, String password) {
this.host = host;
this.port = port;
this.database = database;
this.user = user;
this.password = password;
}
#Override
public String host() {
return host;
}
#Override
public Config host(String host) {
return new ConfigImpl(host, port, database, user, password);
}
#Override
public int port() {
return port;
}
#Override
public Config port(int port) {
return new ConfigImpl(host, port, database, user, password);
}
#Override
public String database() {
return database;
}
#Override
public Config database(String database) {
return new ConfigImpl(host, port, database, user, password);
}
#Override
public String user() {
return user;
}
#Override
public Config user(String user) {
return new ConfigImpl(host, port, database, user, password);
}
#Override
public String password() {
return password;
}
#Override
public Config password(String password) {
return new ConfigImpl(host, port, database, user, password);
}
}
Sample program that uses the API:
import com.example.api.Config;
public class App {
public static void main(String[] args) {
Config config = Config.newConfig()
.host("localhost")
.port(7000)
.database("mydb")
.user("admin")
.password("pwd");
config.database("mydb2"); // won't change 'mydb'
Config config2 = config.database("mydb2"); // needs to be assigned to new instance
System.out.println(config.host() + "|" + config.port() + "|" + config.database() + "|" + config.user() + "|" + config.password());
System.out.println(config2.host() + "|" + config2.port() + "|" + config2.database() + "|" + config2.user() + "|" + config2.password());
}
}
This is working as expected:
localhost|7000|mydb|admin|pwd
localhost|7000|mydb2|admin|pwd
My concern is, is this a good design? Does it affect memory and performance since every mutator/setter creates a news instance?
I would prefer this over builder pattern if there's nothing wrong with my current design.
Thanks.
A builder should probably have a separate build() method to perform final set of operations e.g. validations on multiple fields that can't be addressed with just method signatures. It feels cleaner:
Config config = Config.newConfig()
.host("localhost")
.build();
The only concerning thing about your design is:
The amount of boilerplate code that has to be written by hand to make this work. Perhaps it's worth it in the end but if you plan to implement this for multiple types you should consider something to auto-generate the code e.g. Lombok #Builder.
Since you are not using regular getXXX() and setXXX() methods some frameworks that depend on the property method notation won't be able to process your class. Might be completely irrelevant in your case.
Related
I'm new in Spring, and want to figure out how to inject value from properties file.
I have component with Value annotation:
#Component
public class ConfigProp {
private final String login;
public ConfigProp(#Value("${login}") String login) {
this.login = login;
}
#Override
public String toString() {
return "ConfigProp{" +
"login='" + login + '\'' +
'}';
}
}
then, I have main class:
public class App {
public static void main(String[] args) {
var context = new AnnotationConfigApplicationContext(ConfigProp.class);
context.addBeanFactoryPostProcessor(new PropertySourcesPlaceholderConfigurer());
var cp = context.getBean(ConfigProp.class);
System.out.println(cp);
}
}
Additionally I have application.properties file
with content:
login=some_login
And when I launching my app I'm getting ConfigProp{login='${login}'}
Yes, I know, I may annotate ConfigProp with #PropertySource(path-to-properties) annotation, but it's not a good solution, because in the Prod I may want to use another properties file,
How can I resolve this issue?
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
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
I have to classes, and I need to input two values into one of the classes in my program.
import java.io.IOException;
import java.sql.*;
import static java.lang.System.out;
public class login{
private String username;
private String password;
public void logon() throws IOException {
System.out.println("Write your Name");
username = System.console().readLine();
System.out.println(username);
System.out.println("Write your Password");
password = System.console().readLine();
System.out.println(password);
try {
// Does stuff
}
catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
}
}
Mainly after username and password have been verified in the try catch. I need to transfer those two strings to another class. Seen Below.
public class ClientHandler {
protected Socket client;
protected PrintWriter out;
protected String Username;
protected String Password;
public ClientHandler(Socket client) {
//Does Stuff
}
}
I would like some pointers to how I can achieve this, either through constructors, or any other method that would be efficient.
I need to transfer those two strings to another class.
You can do this by creating a setter method (or more than one).
You need to add these two methods to your ClientHandler class.
public void setPassword(String password) {
this.Password = password;
}
public void setUsername(String username) {
this.Username = username;
}
Then, you set username and password variables by using these setter method
ClientHandler handler = ... //Create this object somehow
handler.setUsername(username);
handler.setPassword(password);
In your case this should go in you 'try' block
I have an issue/problem with CDI in the next scenario:
Initializator is injected in the ServletContextListener. But after some other "steps" the method startup is invoked:
#WebListener
public class ContextListener implements ServletContextListener {
#Inject
private Initializator initializator;
public void contextInitialized(ServletContextEvent event) {
ServletContext servletContext = (ServletContext) event.getSource();
String contextPath = ((ServletContext) event.getSource()).getContextPath();
String serverName = servletContext.getInitParameter("SERVER_NAME");
initializator.startup(serverName);
System.out.println("ServletContext " + contextPath + " stated.");
}
public void contextDestroyed(ServletContextEvent event) {
String contextPath = ((ServletContext) event.getSource()).getContextPath();
System.out.println("ServletContext " + contextPath + " stopped.");
}
}
The repository is successful injected in the initializator:
public class Initializator {
#Inject
private ChannelRepository repo;
public String serverName;
public void startup(String aServerName) {
this.serverName = aServerName;
initAll();
}
private void initAll() {
List<Channel> channels = repo.getChannels();
for (Channel channel : channels) {
channel.start();
}
}
}
The repository retrieves the data and instantiates channels:
public class ChannelRepository {
public List<Channel> getChannels() {
List<Channel> channels = new ArrayList<Channel>();
// ...some db access via jdbc (or jpa)
channels.add(new Channel("dummy", 8080));
return channels;
}
}
The channel needs a Logger:
public class Channel extends Thread {
#Inject
private Logger logger;
public String name;
public int port;
public Channel(String aName, int aPort) {
this.name = aName;
this.port = aPort;
}
#Override
public void run() {
logger.log("Channel " + name + " is running in port " + port);
// ...some other things to do
}
}
How to avoid the manual creation of Channel instances?
The problem is done because the startup method in Initializator is invoked after the instance construction.
How to manage this type of "deferred" injections?
Avoiding the manual creation of instances of Channel with
new Channel()
is fairly easy.
First we need a default constructor in class Channel and setters for channels attributes.
Then you must add this to your Channel Repo
#Inject
Instances<Channel> channelInstances;
and in your repo method change from
channels.add(new Channel("dummy", 8080));
to
Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(channel);
A small hint:
If it is possible to do, don't let Channel extend Thread, but do the following
final Channel channel = channelInstances.get();
channel.setPort(8080);
channel.setName("dummy");
channels.add(new Thread() {
#Override
public void run() {
channel.doTheChannelStuff();
channelInstances.destroy(channel);
}
}
Why should you do this:
Under some circumstances a memory leak will be introduced when doing it in the way you are trying to use it. (Had a similiar issue at work) This has something to do with dependent scoped (default) dependencies and "manual" creation of new instances.