View Javadoc

1   /*
2    * Copyright 2012-2013 smartics, Kronseder & Reiner GmbH
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package de.smartics.properties.spi.config.ds;
17  
18  import java.io.PrintStream;
19  import java.sql.Connection;
20  import java.sql.PreparedStatement;
21  import java.sql.ResultSet;
22  import java.sql.SQLException;
23  import java.sql.Statement;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  
27  import org.apache.commons.lang.StringUtils;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  
31  import de.smartics.util.lang.Arguments;
32  
33  /**
34   * Helper to create database configurations with a data source.
35   */
36  public class DefaultDataSourceManager extends AbstractDataSourceDescriptor
37      implements PropertiesDataSourceManager
38  { // NOPMD
39    // ********************************* Fields *********************************
40  
41    // --- constants ------------------------------------------------------------
42  
43    /**
44     * The class version identifier.
45     */
46    private static final long serialVersionUID = 1L;
47  
48    /**
49     * Reference to the logger for this class.
50     */
51    private static final Logger LOG = LoggerFactory
52        .getLogger(DefaultDataSourceManager.class);
53  
54    // --- members --------------------------------------------------------------
55  
56    /**
57     * The proxy to provide access to a data source.
58     *
59     * @serial
60     */
61    private final DataSourceProxy dataSourceProxy;
62  
63    /**
64     * The properties to fill the database initially. May be <code>null</code>.
65     *
66     * @serial
67     */
68    private final Map<String, String> initialProperties;
69  
70    /**
71     * Signals to drop the properties table before creation.
72     *
73     * @serial
74     */
75    protected final boolean dropTable;
76  
77    /**
78     * The flag signals that any problems encountered on creating the table should
79     * be ignored. Useful if there are no means to check is a database table
80     * already exists.
81     *
82     * @serial
83     */
84    protected final boolean ignoreTableCreationProblems;
85  
86    /**
87     * The SQL statement to create the configuration table.
88     */
89    private final String createTableSqlStatement;
90  
91    // ****************************** Initializer *******************************
92  
93    // ****************************** Constructors ******************************
94  
95    /**
96     * Default constructor.
97     *
98     * @param builder the builder of manager instance.
99     */
100   protected DefaultDataSourceManager(final Builder builder)
101   {
102     super(builder);
103 
104     this.dataSourceProxy = builder.dataSourceProxy;
105     this.initialProperties = builder.initialProperties;
106     this.dropTable = builder.dropTable;
107     this.ignoreTableCreationProblems = builder.ignoreTableCreationProblems;
108     this.createTableSqlStatement =
109         createCreateTableSqlStatement(builder.createTableSqlStatementTemplate);
110   }
111 
112   // ****************************** Inner Classes *****************************
113 
114   /**
115    * The builder of manager instances.
116    */
117   public static class Builder extends AbstractDataSourceDescriptor.Builder
118   {
119     // ******************************** Fields ********************************
120 
121     // --- constants ----------------------------------------------------------
122 
123     /**
124      * The default table template.
125      * <p>
126      * The value of this constant is {@value}.
127      * </p>
128      */
129     public static final String DEFAULT_CREATE_TABLE_TEMPLATE =
130         "CREATE TABLE IF NOT EXISTS ${table}"
131             + " (${nameColumn}  VARCHAR(64) PRIMARY KEY,"
132             + " ${valueColumn} VARCHAR(255))";
133 
134     // --- members ------------------------------------------------------------
135 
136     /**
137      * The proxy to provide access to a data source.
138      */
139     protected DataSourceProxy dataSourceProxy;
140 
141     /**
142      * The properties to fill the database initially. May be <code>null</code>.
143      */
144     private Map<String, String> initialProperties;
145 
146     /**
147      * Signals to drop the properties table before creation.
148      */
149     private boolean dropTable = true;
150 
151     /**
152      * The flag signals that any problems encountered on creating the table
153      * should be ignored. Useful if there are no means to check is a database
154      * table already exists.
155      */
156     private boolean ignoreTableCreationProblems = true;
157 
158     /**
159      * The template to create an SQL statement to create the configuration
160      * table. Use <code>${table}</code>, <code>${nameColumn}</code> and
161      * <code>${valueColumn}</code> to reference the name of the configuration
162      * table, the column that stores the property names and the column storing
163      * the property values.
164      */
165     private String createTableSqlStatementTemplate =
166         DEFAULT_CREATE_TABLE_TEMPLATE;
167 
168     // ***************************** Initializer ******************************
169 
170     // ***************************** Constructors *****************************
171 
172     // ***************************** Inner Classes ****************************
173 
174     // ******************************** Methods *******************************
175 
176     // --- init ---------------------------------------------------------------
177 
178     // --- get&set ------------------------------------------------------------
179 
180     /**
181      * Sets the template to create an SQL statement to create the configuration
182      * table. Use <code>${table}</code>, <code>${nameColumn}</code> and
183      * <code>${valueColumn}</code> to reference the name of the configuration
184      * table, the column that stores the property names and the column storing
185      * the property values.
186      * <p>
187      * Per default the {@link #DEFAULT_CREATE_TABLE_TEMPLATE default table
188      * template} is used.
189      * </p>
190      *
191      * @param createTableSqlStatementTemplate the template to create an SQL
192      *          statement to create the configuration table.
193      * @see #DEFAULT_CREATE_TABLE_TEMPLATE
194      */
195     public final void setCreateTableSqlStatementTemplate(
196         final String createTableSqlStatementTemplate)
197     {
198       this.createTableSqlStatementTemplate = createTableSqlStatementTemplate;
199     }
200 
201     /**
202      * Sets the proxy to provide access to a data source.
203      *
204      * @param dataSourceProxy the proxy to provide access to a data source.
205      * @throws NullPointerException of {@code dataSourceProxy} is
206      *           <code>null</code>.
207      */
208     public final void setDataSourceProxy(final DataSourceProxy dataSourceProxy)
209       throws NullPointerException
210     {
211       Arguments.checkNotNull("dataSourceProxy", dataSourceProxy);
212       this.dataSourceProxy = dataSourceProxy;
213     }
214 
215     /**
216      * Sets the properties to fill the database initially. May be
217      * <code>null</code>.
218      *
219      * @param initialProperties the properties to fill the database initially.
220      */
221     public final void setInitialProperties(
222         final Map<String, String> initialProperties)
223     {
224       this.initialProperties = initialProperties;
225     }
226 
227     /**
228      * Sets the value for dropTable.
229      * <p>
230      * Signals to drop the properties table before creation.
231      *
232      * @param dropTable the value for dropTable.
233      */
234     public final void setDropTable(final boolean dropTable)
235     {
236       this.dropTable = dropTable;
237     }
238 
239     /**
240      * Sets the flag signals that any problems encountered on creating the table
241      * should be ignored. Useful if there are no means to check is a database
242      * table already exists.
243      *
244      * @param ignoreTableCreationProblems the flag signals that any problems
245      *          encountered on creating the table should be ignored.
246      */
247     public final void setIgnoreTableCreationProblems(
248         final boolean ignoreTableCreationProblems)
249     {
250       this.ignoreTableCreationProblems = ignoreTableCreationProblems;
251     }
252 
253     // --- business -----------------------------------------------------------
254 
255     /**
256      * Creates the instance.
257      *
258      * @return the created instance.
259      * @throws NullPointerException if no data source proxy has been provided.
260      */
261     public DefaultDataSourceManager build() throws NullPointerException
262     {
263       Arguments.checkNotNull("dataSourceProxy", dataSourceProxy);
264 
265       return new DefaultDataSourceManager(this);
266     }
267 
268     // --- object basics ------------------------------------------------------
269   }
270 
271   // ********************************* Methods ********************************
272 
273   // --- init -----------------------------------------------------------------
274 
275   private String createCreateTableSqlStatement(final String template)
276   {
277     if (template == null)
278     {
279       return null;
280     }
281 
282     final String sql =
283         StringUtils.replaceEach(template, new String[] { "${table}",
284                                                         "${nameColumn}",
285                                                         "${valueColumn}" },
286             new String[] { getTable(), getNameColumn(), getValueColumn() });
287     return sql;
288   }
289 
290   // --- get&set --------------------------------------------------------------
291 
292   @Override
293   public final String getDataSourceId()
294   {
295     return dataSourceProxy.getDataSourceId();
296   }
297 
298   @Override
299   public final DataSourceProxy getDataSourceProxy()
300   {
301     return dataSourceProxy;
302   }
303 
304   // --- business -------------------------------------------------------------
305 
306   @Override
307   public final void createConfigTable() throws DataSourceException
308   {
309     try
310     {
311       initDataSource();
312     }
313     catch (final SQLException e)
314     {
315       throw new DataSourceException("Cannot create configuration table.", e,
316           getDataSourceId());
317     }
318   }
319 
320   private void initDataSource() throws SQLException
321   {
322     if (createTableSqlStatement != null)
323     {
324       if (dropTable)
325       {
326         dropConfigTable();
327       }
328 
329       initTables();
330     }
331   }
332 
333   private void initTables() throws SQLException
334   {
335     final Connection connection =
336         dataSourceProxy.getDataSource().getConnection();
337 
338     try
339     {
340       if (!initializedTableExists(connection))
341       {
342         initTable(connection);
343 
344         initProperties(connection);
345       }
346     }
347     finally
348     {
349       connection.close();
350     }
351   }
352 
353   private boolean initializedTableExists(final Connection connection)
354     throws SQLException
355   {
356     final Statement statement = connection.createStatement();
357     try
358     {
359 
360       final String querySql = "SELECT COUNT(*) FROM " + getTable();
361       final ResultSet resultSet = statement.executeQuery(querySql);
362       if (resultSet.next())
363       {
364         final int count = resultSet.getInt(1);
365         return count > 0;
366       }
367 
368       return false;
369     }
370     catch (final SQLException e)
371     {
372       return false;
373     }
374     finally
375     {
376       statement.close();
377     }
378   }
379 
380   private void initTable(final Connection connection) throws SQLException
381   {
382     try
383     {
384       execute(connection, createTableSqlStatement);
385     }
386     catch (final SQLException e)
387     {
388       if (!ignoreTableCreationProblems)
389       {
390         throw e;
391       }
392     }
393   }
394 
395   private void initProperties(final Connection connection) throws SQLException
396   {
397     if (initialProperties != null && !initialProperties.isEmpty())
398     {
399       final PreparedStatement statement =
400           connection.prepareStatement("INSERT INTO " + getTable()
401                                       + " VALUES (?, ?)");
402       for (final Entry<String, String> property : initialProperties.entrySet())
403       {
404         statement.setString(1, property.getKey());
405         statement.setString(2, property.getValue());
406         statement.execute();
407       }
408 
409       statement.close();
410     }
411   }
412 
413   private void execute(final Connection connection, final String createTableSql)
414     throws SQLException
415   {
416     final Statement statement = connection.createStatement();
417     try
418     {
419       statement.executeUpdate(createTableSql);
420     }
421     finally
422     {
423       statement.close();
424     }
425   }
426 
427   @Override
428   public final void dropConfigTable() throws DataSourceException
429   {
430     try
431     {
432       final Connection connection =
433           dataSourceProxy.getDataSource().getConnection();
434 
435       try
436       {
437         final Statement statement = connection.createStatement();
438 
439         final String sql = "DROP TABLE " + getTable();
440         statement.executeUpdate(sql);
441       }
442       catch (final SQLException e)
443       {
444         LOG.warn("Dropping failed:  " + e.getMessage());
445       }
446       finally
447       {
448         connection.close();
449       }
450     }
451     catch (final SQLException e)
452     {
453       throw new DataSourceException("Cannot drop config table.", e,
454           getDataSourceId());
455     }
456   }
457 
458   @Override
459   public final void print(final PrintStream out) throws DataSourceException
460   {
461     try
462     {
463       final Connection connection =
464           dataSourceProxy.getDataSource().getConnection();
465 
466       try
467       {
468         final Statement statement = connection.createStatement();
469 
470         final String querySql =
471             "SELECT " + getNameColumn() + ", " + getValueColumn() + " FROM "
472                 + getTable();
473         final ResultSet resultSet = statement.executeQuery(querySql);
474         while (resultSet.next())
475         {
476           final String name = resultSet.getString(1);
477           final String value = resultSet.getString(2);
478 
479           out.println("  " + name + "=" + value);
480         }
481       }
482       finally
483       {
484         connection.close();
485       }
486     }
487     catch (final SQLException e)
488     {
489       throw new DataSourceException("Cannot print config table.", e,
490           getDataSourceId());
491     }
492   }
493 
494   // --- object basics --------------------------------------------------------
495 
496 }