/* *Copyright (c) 2005-2010, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * *WSO2 Inc. licenses this file to you under the Apache License, *Version 2.0 (the "License"); you may not use this file except *in compliance with the License. *You may obtain a copy of the License at * *http://www.apache.org/licenses/LICENSE-2.0 * *Unless required by applicable law or agreed to in writing, *software distributed under the License is distributed on an *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *KIND, either express or implied. See the License for the *specific language governing permissions and limitations *under the License. */ package org.wso2.carbon.identity.core.persistence; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.identity.base.IdentityRuntimeException; import javax.sql.DataSource; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; import java.util.StringTokenizer; /** * This class handles identity database creation in the first start-up. It checks for the * SQL scripts for creating the tables inside $CARBON_HOME/dbscripts/identity directory. */ public class IdentityDBInitializer { private static final String DB_CHECK_SQL = "SELECT * FROM IDN_BASE_TABLE"; private static Log log = LogFactory.getLog(IdentityDBInitializer.class); Statement statement; private DataSource dataSource; private String delimiter = ";"; IdentityDBInitializer(DataSource dataSource) { this.dataSource = dataSource; } public static String getDatabaseType(Connection conn) throws IdentityRuntimeException{ String type = null; try { if (conn != null && (!conn.isClosed())) { DatabaseMetaData metaData = conn.getMetaData(); String databaseProductName = metaData.getDatabaseProductName(); if (databaseProductName.matches("(?i).*hsql.*")) { type = "hsql"; } else if (databaseProductName.matches("(?i).*derby.*")) { type = "derby"; } else if (databaseProductName.matches("(?i).*mysql.*")) { type = "mysql"; } else if (databaseProductName.matches("(?i).*oracle.*")) { type = "oracle"; } else if (databaseProductName.matches("(?i).*microsoft.*")) { type = "mssql"; } else if (databaseProductName.matches("(?i).*h2.*")) { type = "h2"; } else if (databaseProductName.matches("(?i).*db2.*")) { type = "db2"; } else if (databaseProductName.matches("(?i).*postgresql.*")) { type = "postgresql"; } else if (databaseProductName.matches("(?i).*openedge.*")) { type = "openedge"; } else if (databaseProductName.matches("(?i).*informix.*")) { type = "informix"; } else { String msg = "Unsupported database: " + databaseProductName + ". Database will not be created automatically by the WSO2 Identity Server. " + "Please create the database using appropriate database scripts for " + "the database."; throw IdentityRuntimeException.error(msg); } } } catch (SQLException e) { String msg = "Failed to create identity database." + e.getMessage(); throw IdentityRuntimeException.error(msg, e); } return type; } /** * Checks that a string buffer ends up with a given string. It may sound * trivial with the existing * JDK API but the various implementation among JDKs can make those * methods extremely resource intensive * and perform poorly due to massive memory allocation and copying. See * * @param buffer the buffer to perform the check on * @param suffix the suffix * @return <code>true</code> if the character sequence represented by the * argument is a suffix of the character sequence represented by * the StringBuffer object; <code>false</code> otherwise. Note that the * result will be <code>true</code> if the argument is the * empty string. */ public static boolean checkStringBufferEndsWith(StringBuffer buffer, String suffix) { if (suffix.length() > buffer.length()) { return false; } // this loop is done on purpose to avoid memory allocation performance // problems on various JDKs // StringBuffer.lastIndexOf() was introduced in jdk 1.4 and // implementation is ok though does allocation/copying // StringBuffer.toString().endsWith() does massive memory // allocation/copying on JDK 1.5 // See http://issues.apache.org/bugzilla/show_bug.cgi?id=37169 int endIndex = suffix.length() - 1; int bufferIndex = buffer.length() - 1; while (endIndex >= 0) { if (buffer.charAt(bufferIndex) != suffix.charAt(endIndex)) { return false; } bufferIndex--; endIndex--; } return true; } void createIdentityDatabase() { if (!isDatabaseStructureCreated()) { Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); statement = conn.createStatement(); executeSQLScript(); conn.commit(); log.debug("Identity tables are created successfully."); } catch (SQLException e) { String msg = "Failed to create database tables for Identity meta-data store. " + e.getMessage(); throw IdentityRuntimeException.error(msg, e); } finally { if (statement != null) { try { statement.close(); } catch (SQLException e) { log.error("Failed to close statement.", e); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { log.error("Failed to close database connection.", e); } } } } else { if (log.isDebugEnabled()) { log.debug("Identity Database already exists. Not creating a new database."); } } } /** * Checks whether database tables are created. * * @return <code>true</core> if checkSQL is success, else <code>false</code>. */ private boolean isDatabaseStructureCreated() { try { if (log.isDebugEnabled()) { log.debug("Running a query to test the database tables existence"); } // check whether the tables are already created with a query Connection conn = dataSource.getConnection(); Statement statement = null; try { statement = conn.createStatement(); ResultSet rs = statement.executeQuery(DB_CHECK_SQL); if (rs != null) { rs.close(); } } finally { try { if (statement != null) { statement.close(); } } finally { if (conn != null) { conn.close(); } } } } catch (SQLException e) { // Doesn't matter if DB scripts are executed again, because doesn't do any harm. return false; } return true; } private void executeSQLScript() { String databaseType = null; try { databaseType = IdentityDBInitializer.getDatabaseType(dataSource.getConnection()); } catch (Exception e) { throw IdentityRuntimeException.error("Error occurred while getting database type"); } boolean keepFormat = false; if ("oracle".equals(databaseType)) { delimiter = "/"; } else if ("db2".equals(databaseType)) { delimiter = "/"; } else if ("openedge".equals(databaseType)) { delimiter = "/"; keepFormat = true; } String dbScriptLocation = getDbScriptLocation(databaseType); StringBuffer sql = new StringBuffer(); BufferedReader reader = null; try { InputStream is = new FileInputStream(dbScriptLocation); reader = new BufferedReader(new InputStreamReader(is)); String line; while ((line = reader.readLine()) != null) { line = line.trim(); if (!keepFormat) { if (line.startsWith("//")) { continue; } if (line.startsWith("--")) { continue; } StringTokenizer st = new StringTokenizer(line); if (st.hasMoreTokens()) { String token = st.nextToken(); if ("REM".equalsIgnoreCase(token)) { continue; } } } sql.append(keepFormat ? "\n" : " ").append(line); // SQL defines "--" as a comment to EOL // and in Oracle it may contain a hint // so we cannot just remove it, instead we must end it if (!keepFormat && line.contains("--")) { sql.append("\n"); } if ((checkStringBufferEndsWith(sql, delimiter))) { executeSQL(sql.substring(0, sql.length() - delimiter.length())); sql.replace(0, sql.length(), ""); } } // Catch any statements not followed by ; if (sql.length() > 0) { executeSQL(sql.toString()); } } catch (IOException e) { throw IdentityRuntimeException.error("Error occurred while executing SQL script for creating identity " + "database", e); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { log.error("Error occurred while closing stream for Identity SQL script", e); } } } } protected String getDbScriptLocation(String databaseType) { String scriptName = databaseType + ".sql"; if (log.isDebugEnabled()) { log.debug("Loading database script from :" + scriptName); } String carbonHome = System.getProperty("carbon.home"); return carbonHome + "/dbscripts/identity/" + scriptName; } /** * executes given sql * * @param sql * @throws Exception */ private void executeSQL(String sql) { // Check and ignore empty statements if ("".equals(sql.trim())) { return; } ResultSet resultSet = null; Connection conn = null; try { if (log.isDebugEnabled()) { log.debug("SQL : " + sql); } boolean ret; int updateCount, updateCountTotal = 0; ret = statement.execute(sql); updateCount = statement.getUpdateCount(); resultSet = statement.getResultSet(); do { if (!ret) { if (updateCount != -1) { updateCountTotal += updateCount; } } ret = statement.getMoreResults(); if (ret) { updateCount = statement.getUpdateCount(); resultSet = statement.getResultSet(); } } while (ret); if (log.isDebugEnabled()) { log.debug(sql + " : " + updateCountTotal + " rows affected"); } conn = dataSource.getConnection(); SQLWarning warning = conn.getWarnings(); while (warning != null) { log.debug(warning + " sql warning"); warning = warning.getNextWarning(); } conn.clearWarnings(); } catch (SQLException e) { if (e.getSQLState().equals("X0Y32") || e.getSQLState().equals("42710")) { // eliminating the table already exception for the derby and DB2 database types if (log.isDebugEnabled()) { log.info("Table Already Exists. Hence, skipping table creation"); } } else { throw IdentityRuntimeException.error("Error occurred while executing : " + sql, e); } } finally { if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { log.error("Error occurred while closing result set.", e); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { log.error("Error occurred while closing sql connection.", e); } } } } }