View Javadoc
1   package com.atlassian.plugin.refimpl.db;
2   
3   import com.atlassian.refapp.api.ConnectionProvider;
4   import org.h2.tools.Server;
5   import org.slf4j.Logger;
6   import org.slf4j.LoggerFactory;
7   import org.vibur.dbcp.ViburDBCPDataSource;
8   
9   import javax.sql.DataSource;
10  import java.net.InetAddress;
11  import java.sql.Connection;
12  import java.sql.SQLException;
13  import java.util.Optional;
14  
15  public class ConnectionProviderImpl implements ConnectionProvider {
16      private static final Logger log = LoggerFactory.getLogger(ConnectionProviderImpl.class);
17  
18      private static final String DB_DIR = System.getProperty("refapp.home", System.getProperty("java.io.tmpdir"));
19      private static final String LOCAL_HOST = InetAddress.getLoopbackAddress().getHostAddress();
20  
21      private static final String SYS_PROP_EXTERNAL = "refapp.jdbc.external";
22      private static final String SYS_PROP_DRIVER_CLASS = "refapp.jdbc.driver.class.name";
23      private static final String SYS_PROP_URL = "refapp.jdbc.app.url";
24      private static final String SYS_PROP_SCHEMA = "refapp.jdbc.app.schema";
25      private static final String SYS_PROP_USER = "refapp.jdbc.app.user";
26      private static final String SYS_PROP_PASSWORD = "refapp.jdbc.app.pass";
27      private static final String SYS_PROP_VAL_QUERY = "refapp.jdbc.validation.query";
28  
29      private static final String EMBEDDED_DRIVER_CLASS = "org.h2.Driver";
30      private static final String EMBEDDED_URL_PREFIX = "jdbc:h2:tcp://" + LOCAL_HOST + ":";
31      private static final String EMBEDDED_URL_SUFFIX = "/" + DB_DIR + "/h2db";
32      private static final Optional<String> EMBEDDED_SCHEMA = Optional.of("PUBLIC");
33      private static final String EMBEDDED_USER = "sa";
34      private static final String EMBEDDED_PASSWORD = "";
35      private static final Optional<String> EMBEDDED_VAL_QUERY = Optional.empty();
36  
37      private final String driverClassName;
38      private final Optional<String> schema;
39  
40      private final ViburDBCPDataSource dataSource;
41  
42      /**
43       * Construct based on system properties, defaulting if no properties given.
44       *
45       * @throws SQLException on failure to start the (default) H2 server instance
46       */
47      public ConnectionProviderImpl() throws SQLException {
48  
49          // default to false
50          final boolean externalDb = Boolean.getBoolean(SYS_PROP_EXTERNAL);
51  
52          final String url;
53          final String user;
54          final String password;
55          final Optional<String> validationQuery;
56          if (externalDb) {
57  
58              // use the external database specified by the user, via (some mandatory) system properties
59              this.driverClassName = mandatoryPropertyValue(SYS_PROP_DRIVER_CLASS);
60              url = mandatoryPropertyValue(SYS_PROP_URL);
61              this.schema = Optional.ofNullable(System.getProperty(SYS_PROP_SCHEMA));
62              user = mandatoryPropertyValue(SYS_PROP_USER);
63              password = mandatoryPropertyValue(SYS_PROP_PASSWORD);
64              validationQuery = Optional.ofNullable(System.getProperty(SYS_PROP_VAL_QUERY));
65          } else {
66  
67              // use an embedded database
68              this.driverClassName = EMBEDDED_DRIVER_CLASS;
69              url = embeddedUrl();
70              this.schema = EMBEDDED_SCHEMA;
71              user = EMBEDDED_USER;
72              password = EMBEDDED_PASSWORD;
73              validationQuery = EMBEDDED_VAL_QUERY;
74          }
75  
76          log.info("starting database driverClassName='{}' url='{}' schema='{}' user='{}' password='{}' validationQuery='{}'",
77                  driverClassName, url, schema, user, password, validationQuery);
78  
79          // create a vibur pooled datasource
80          final ViburDBCPDataSource viburDS = new ViburDBCPDataSource();
81          viburDS.setDriverClassName(driverClassName);
82          viburDS.setJdbcUrl(url);
83          viburDS.setUsername(user);
84          viburDS.setPassword(password);
85          validationQuery.ifPresent(q -> viburDS.setTestConnectionQuery(q));
86          viburDS.setCloseConnectionHook(rawConnection -> {
87              boolean autocommit = rawConnection.getAutoCommit();
88              if (!autocommit) {
89                  rawConnection.commit();
90              }
91          });
92          viburDS.start();
93  
94          this.dataSource = viburDS;
95      }
96  
97      public void terminate() {
98          dataSource.terminate();
99      }
100 
101     @Override
102     public Connection connection() throws SQLException {
103         return dataSource.getConnection();
104     }
105 
106     @Override
107     public DataSource dataSource() {
108         return dataSource;
109     }
110 
111     @Override
112     public String driverClassName() {
113         return driverClassName;
114     }
115 
116     @Override
117     public Optional<String> schema() {
118         return schema;
119     }
120 
121     /**
122      * Create a jdbc url for an H2 embedded server, using a free port, starting the server.
123      *
124      * @return valid url
125      * @throws SQLException on failure to start an H2 server
126      */
127     private String embeddedUrl() throws SQLException {
128 
129         // start an H2 server on an available port
130         final Server server = Server.createTcpServer();
131         System.setProperty("h2.bindAddress", LOCAL_HOST);
132         server.start();
133 
134         // construct a JDBC URL for the above server
135         return EMBEDDED_URL_PREFIX + server.getPort() + EMBEDDED_URL_SUFFIX;
136     }
137 
138     /**
139      * Return the value of the system property specified. Throws an exception if not specified.
140      *
141      * @param key the name of the system property
142      * @return the string value of the system property
143      * @throws RuntimeException when that value is not specified, with an appropriate message
144      */
145     private String mandatoryPropertyValue(final String key) {
146         final String value = System.getProperty(key, null);
147         if (value == null) {
148             throw new RuntimeException("Mandatory java system property '" + key + "' not specified. See com.atlassian.refapp.api.ConnectionProvider for more information.");
149         }
150         return value;
151     }
152 }