
net.virtalab.databazer.h2.H2DataSource Maven / Gradle / Ivy
Show all versions of databazer Show documentation
package net.virtalab.databazer.h2;
import net.virtalab.databazer.NamedDataSource;
import java.lang.reflect.Field;
import java.sql.Driver;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The H2DataSource represents implementation of DataSource interface {@link javax.sql.DataSource} for H2 Databases
*
* By default this class defines some default values such as:
*
* - Name of DataSource - {@code default}
* - Driver class - official H2 Driver: {@link org.h2.Driver}.
* - URL - {@code jdbc:h2:mem:} which is private (unnamed) in-memory DB
* - Username - {@code sa}
* - Password - empty password
*
*
*
* Those defaults can easily be overridden by using following methods:
*
* - Name of DataSource - {@link #setName(String) setName(String)}
* - Driver class - {@link #setDriverClass(Class) setDriverClass(Class)}
* - URL - {@link #setUrl(String) setUrl(String)}
* - Username - {@link #setUsername(String) setUsername(String)}
* - Password - {@link #setPassword(String) setPassword(String)}
*
*
*
* Alongside with default constructor there another way to create this DataSource.
* It can be done using Builder pattern. This DataSource provides inner class Creator
* For more information on Creator class see {@link H2DataSource.Creator its documentation}
*
* Creator class can be accessed via two approaches:
*
* -
* Creating new instance of Creator:
*
* H2DataSource.Creator creator = new H2DataSource.Creator();
*
*
* -
* Static method {@link #Creator()}
*
* H2DataSource.Creator = H2DataSource.Creator();
*
*
*
*
*
* @author Alexander Muravya
* @see net.virtalab.databazer.NamedDataSource
* @version 0.1
* @since 0.1
*/
public class H2DataSource extends NamedDataSource {
/**
* Constructor invoked by Spring when creating bean, sets default values.
*/
public H2DataSource(){
this.setName("default");
this.setDriverClass(org.h2.Driver.class);
this.setUrl("jdbc:h2:mem:");
this.setUsername("sa");
this.setPassword("");
}
/**
* Provides {@link Creator} instance in static way
*
* @return Creator instance
*/
public static Creator Creator(){
return new Creator();
}
/**
* Class that creates H2DataSource using fluent interface approach.
* It contains methods that allow to build JDBC URL step-by-step by setting each single component.
*
* For example:
*
* Instead of
*
* String url = "jdbc:h2:mem:dbName";
* H2DataSource ds = new H2DataSource();
* ds.setUrl(url);
*
* You can set each component method-by-method
*
* H2DataSource ds = H2DataSource.Creator().databaseName("dbName").create();
*
*
* There are some default values will be use (all of them can be overridden by Creator methods)
*
* - Name - "default"
* - Driver Class - {@link org.h2.Driver org.h2.Driver}
* - Path to DB - empty path
* - Database Name - empty
* - Host - "localhost"
* - Port - 9092
* - Username - "sa"
* - Password is empty
* - {@link DatabaseMode Database Mode} - Memory
* - {@link StorageType Storage Type} - Memory
*
*
* Please note that:
*
*
* - Empty Database name is accepted only for {@link StorageType#MEMORY in-memory database}
* - Path is ignored while building in-memory database
* - Host and port settings are ignored in {@link DatabaseMode#MEMORY in-memory} or {@link DatabaseMode#FILE file} database modes
* - If you try to set multiple {@link DatabaseMode modes} i.e. memory and then file, last set mode wins.
*
*
* Customization note
*
* Custom URL (set by using {@link #setUrl(String) setUrl(String)}) overrides all other URL-related settings
*
* @version 0.1
* @since 0.1
* @author Alex Muravya
*
*/
public static class Creator{
//defaults
private static final String DEFAULT_NAME = "default";
private static final Class extends Driver> DEFAULT_DRIVER = org.h2.Driver.class;
private static final String DEFAULT_PATH = "";
private static final String DEFAULT_DBNAME= "";
private static final String DEFAULT_HOST = "localhost";
private static final int DEFAULT_PORT = 9092;
private static final String DEFAULT_USER="sa";
private static final String DEFAULT_PASSWORD="";
private Map options = new HashMap();
//fields
String name = DEFAULT_NAME;
String path = DEFAULT_PATH;
String databaseName = DEFAULT_DBNAME;
String host = DEFAULT_HOST;
int port = DEFAULT_PORT;
String username = DEFAULT_USER;
String password = DEFAULT_PASSWORD;
DatabaseMode mode = DatabaseMode.MEMORY;
StorageType storageType = StorageType.MEMORY;
Driver driver;
Class extends Driver> driverClass = DEFAULT_DRIVER;
String url;
/**
* Constructs DataSource with defaults
*/
public Creator(){}
/**
* Constructs DataSource from provided connection URL.
* If used, overrides all URL-related settings.
*
* @param url JDBC URL for H2 (http://h2database.com for more info)
* @return {@link #Creator() Creator} instance
*/
public Creator url(String url){
this.url = url;
return this;
}
/**
* DataSource {@link NamedDataSource#setName(String) name} which overrides default value.
*
* @param name DataSource name. Should be not empty String.
* @return {@link #Creator() Creator} instance
*/
public Creator name(String name){
this.name = name;
return this;
}
/**
* In-Memory mode for database.
* Database will be located at memory.
* This is fastest mode, but no persistence provided.
*
* @return {@link #Creator() Creator} instance
*/
public Creator mem(){
this.mode = DatabaseMode.MEMORY;
this.storageType = StorageType.MEMORY;
return this;
}
/**
* File mode for database.
* Database stores at file and can be accessed only by one process at moment.
*
* @return {@link #Creator() Creator} instance
*/
public Creator file(){
this.mode = DatabaseMode.FILE;
this.storageType = StorageType.FILE;
return this;
}
/**
* TCP mode for database.
* Newly created DB can be accessed via TCP/IP.
* You can adjust {@link #server(String, int)} host and port} if needed.
*
* @return {@link #Creator() Creator} instance
*/
public Creator tcp(){
this.mode = DatabaseMode.TCP;
return this;
}
/**
* SSL Mode for Database.
* SSL Mode if same as TCP, but provides Secure transport over TCP/IP stack.
* Host and port are also {@link #server(String, int) adjustable} for this mode as well.
*
* @return {@link #Creator() Creator} instance
*/
public Creator ssl(){
this.mode = DatabaseMode.SSL;
return this;
}
/**
* Defines database name.
* For memory DB this is named in-memory db.
* For File and Server+file is just database name without path to file.
* If no {@link #path(String) path specified} H2 engine will search (or create) DB at current working directory
*
* @param databaseName database name. Should be 1 or more letters (without slashes and spaces).
* @return {@link #Creator() Creator} instance
*/
public Creator databaseName(String databaseName){
this.databaseName = databaseName;
return this;
}
/**
* Defines path to Database file.
*
* Path should not end with "/" (it added automatically)
*
* Path could be relative to running directory. (Example ../db. Interpreted as: goto one level above running directory and goto db there)
*
* Path could be relative to user home (*nix only) (Example: ~/db. Interpreted as: /home//db)
*
* Path could be absolute to / (*nix only) (Example: /opt/db. Interpreted as: /opt/db)
*
* Path could be absolute to drive (Windows only) (Example: C:/db . Attention: Use "/" here.)
*
* @param path filesystem path to database file.
* @return {@link #Creator() Creator} instance
*/
public Creator path(String path){
this.path = path;
return this;
}
/**
* Server is combination of hostname and port.
* If DatabaseMode is not equals to {@link DatabaseMode#TCP TCP} or {@link DatabaseMode#SSL SSL} this setting ignored.
*
* @param host hostname, which can be resolved to IP address or IP as String
* @param port Integer representing valid TCP port (from 1 to 65535)
* @return {@link #Creator() Creator} instance
*/
public Creator server(String host, int port){
this.host = host;
this.port = port;
//if server used - user need TCP or SSL
if(this.mode!=DatabaseMode.SSL){
this.mode = DatabaseMode.TCP;
}
return this;
}
/**
* {@link #server(String, int)} with default port
*
* @param host hostname, which can be resolved to IP address or IP as String
* @return {@link #Creator() Creator} instance
*/
public Creator server(String host){
this.server(host,DEFAULT_PORT);
return this;
}
/**
* Sets {@link StorageType Storage Type} of Server accessible (TCP or SSL) Database
*
* @param storageType valid Storage
* @return {@link #Creator() Creator} instance
*/
public Creator storageType(StorageType storageType){
this.storageType = storageType;
return this;
}
/**
* Username which be used as login for DataSource
*
* @param username username used to connect to DB. Must not be empty.
* @return {@link #Creator() Creator} instance
*/
public Creator username(String username){
this.username = username;
return this;
}
/**
* Password which corresponds with {@link Creator#username(java.lang.String) login}
*
* @param password password used to connect to DB. No limitation applied here.
* @return {@link #Creator() Creator} instance
*/
public Creator password(String password){
this.password = password;
return this;
}
/**
* Adds option to end of connection URL.
* List of options can be found at H2 Database site.
*
* @param key option name
* @param value option value
* @return {@link #Creator() Creator} instance
*/
public Creator option(String key,String value){
this.options.put(key,value);
return this;
}
/**
* Overrides driver setting with custom driver instance.
* Do not use this unless you know what you're doing.
*
* @param driver Object of class that implements {@link java.sql.Driver} for H2 Database
* @return {@link #Creator() Creator} instance
*/
public Creator driver(Driver driver){
this.driver = driver;
//null driverClass as we cannot use both at same time
this.driverClass = null;
return this;
}
/**
* Overrides driver class with custom driver class.
* Do not use this unless you know what you're doing.
*
* @param driverClass Class that implements {@link java.sql.Driver} and able to work with H2 Database
* @return Creator
*/
public Creator driver(Class extends Driver> driverClass){
this.driverClass = driverClass;
return this;
}
/**
* Triggers DataSource creation.
*
* @return generated {@link H2DataSource}
* @throws java.lang.IllegalArgumentException when argument value out of valid scope
* @throws java.lang.IllegalStateException when DB with settings provided settings cannot be created.
*
* For example:
*
* - At least one of compulsory fields is NULL
* - Empty database name for {@link StorageType#FILE File storage mode}
* - Empty datasource name
* - When setting {@link StorageType#FILE File storage mode} for {@link DatabaseMode#MEMORY in-memory Database} and vice-versa
*
*/
public H2DataSource create(){
//noinspection CaughtExceptionImmediatelyRethrown
try{
List optionalFields = new ArrayList();
optionalFields.add("driver");
optionalFields.add("driverClass");
optionalFields.add("url");
nullValidator(this,optionalFields);
}catch (IllegalArgumentException e){
throw e;
}
//check if we have all required params set
boolean isURLDefined = (this.url !=null);
boolean isDatabaseNameSet = (! this.databaseName.equals(DEFAULT_DBNAME));
//mode
boolean isInMemoryDb = (this.mode == DatabaseMode.MEMORY);
boolean isFileDb = (this.mode == DatabaseMode.FILE);
//storage mode
boolean isStoredInMemory = (this.storageType == StorageType.MEMORY);
boolean isStoredAtFile = (this.storageType == StorageType.FILE);
if(!isURLDefined){
//we have to create URL, so let's run some URL-related pre-checks
//Database name section
if( ! isDatabaseNameSet && ! isStoredInMemory){
throw new IllegalStateException("You didn't set database name. Noname DB is allowed only for memory mode or Server+Memory mode." +
"Use databaseName() or change mode to Memory by mem(), or storageType(StorageType.MEMORY) for server mode ");
}
//Host section
if(this.host.length()==0){
throw new IllegalArgumentException("Empty hostname is not allowed");
}
//Port section
int MIN_PORT=1;
int MAX_PORT=65535;
if(port < MIN_PORT || port > MAX_PORT){
throw new IllegalArgumentException("Port cannot be less then "+MIN_PORT+" and more then "+MAX_PORT);
}
// Mode/Storage match
if(isInMemoryDb && isStoredAtFile){
throw new IllegalStateException("It seems like you set in-memory database together with FILE Storage type. MEMORY is only valid Storage type for in-memory database");
}
if(isFileDb && isStoredInMemory){
throw new IllegalStateException("It seems like you set file database together with Memory storage type. FILE is only valid Storage type for file database");
}
}
if(this.name.length()==0){
throw new IllegalArgumentException("Empty name is not allowed");
}
return new H2DataSource(this);
}
/**
* Validates if creator instance has null values at fields
*
* @param creator Creator instance
* @param excludedFields field names that can be empty
* @throws java.lang.IllegalArgumentException when catches null-values field
*/
private void nullValidator(Creator creator,List excludedFields) throws IllegalArgumentException {
Class> c = creator.getClass();
Field[] fields = c.getDeclaredFields();
for(Field f: fields){
String name;
Object value;
try {
name = f.getName();
value = f.get(creator);
} catch (IllegalAccessException e) {
continue;
}
boolean nameExcluded = excludedFields.contains(name);
if(value==null && !nameExcluded){
String message = f.getName()+" cannot be NULL. Do not override default values even if you don't use it";
throw new IllegalArgumentException(message);
}
}
}
}
/**
* Private constructor which creates object from its builder.
*
* @param creator Creator instance
*/
private H2DataSource(Creator creator){
//connection name
this.setName(creator.name);
//drivers
if(creator.driver!=null){ this.setDriver(creator.driver); }
if(creator.driverClass!=null){ this.setDriverClass(creator.driverClass); }
//user and pass
this.setUsername(creator.username);
this.setPassword(creator.password);
//making connection url
StringBuilder URLBuilder = new StringBuilder();
if(creator.url!=null){
//we just use it
URLBuilder.append(creator.url);
//do we have options set?
if(creator.options.size() > 0){
this.addOptions(creator, URLBuilder);
}
} else {
//ok, we have to build URL step-by-step
URLBuilder.append("jdbc:h2:");
//further steps are highly dependent on DB mode
switch (creator.mode){
case MEMORY:
URLBuilder.append("mem:");
URLBuilder.append(createMemoryDB(creator));
break;
case FILE:
URLBuilder.append("file:");
URLBuilder.append(createFileDB(creator));
break;
case TCP:
URLBuilder.append("tcp://");
URLBuilder.append(createServerDB(creator));
break;
case SSL:
URLBuilder.append("ssl://");
URLBuilder.append(createServerDB(creator));
break;
default:
//hope we never be here due to pre-set defaults
//but if so - we scream and exit in panic
return;
}
//options
if(creator.options.size() > 0){
this.addOptions(creator, URLBuilder);
}
}
this.setUrl(URLBuilder.toString());
}
private String createMemoryDB(Creator creator){
StringBuilder builder = new StringBuilder();
//adding prefix "mem" (but not for MEMORY mode DB, because it already has it)
if(creator.mode != DatabaseMode.MEMORY){
//delimiter
builder.append("/");
String prefix = "mem:";
builder.append(prefix);
}
//check if have named or un-named memory DB
if(! creator.databaseName.equals(Creator.DEFAULT_DBNAME)){
builder.append(creator.databaseName);
}
return builder.toString();
}
private String createFileDB(Creator creator){
StringBuilder builder = new StringBuilder();
//path
builder.append(creator.path);
//delimiter
//we need it only when path is not already ends with / and path is not empty
if(! creator.path.endsWith("/") && ! creator.path.isEmpty() ){
builder.append("/");
}
//database name
builder.append(creator.databaseName);
return builder.toString();
}
private String createServerDB(Creator creator){
StringBuilder builder = new StringBuilder();
//host
builder.append(creator.host);
//port
if(creator.port!=Creator.DEFAULT_PORT){
builder.append(":").append(creator.port);
}
switch (creator.storageType){
case MEMORY:
builder.append(createMemoryDB(creator));
break;
case FILE:
builder.append(createFileDB(creator));
break;
default:
break;
}
return builder.toString();
}
private StringBuilder addOptions(Creator creator,StringBuilder URLBuilder){
URLBuilder.append(";"); //options delimiter
for (String key : creator.options.keySet()) {
String value = creator.options.get(key);
URLBuilder.append(key).append("=").append(value);
URLBuilder.append(";"); //delimiter
}
//remove trailing delimiter
URLBuilder.setLength(URLBuilder.length()-1);
return URLBuilder;
}
}