/*
* Copyright (C) 2009 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.exoplatform.services.jcr.impl.config;
import org.exoplatform.commons.utils.SecurityHelper;
import org.exoplatform.container.xml.PropertiesParam;
import org.exoplatform.services.database.utils.DialectDetecter;
import org.exoplatform.services.database.utils.JDBCUtils;
import org.exoplatform.services.jcr.config.ConfigurationPersister;
import org.exoplatform.services.jcr.config.RepositoryConfigurationException;
import org.exoplatform.services.jcr.impl.storage.jdbc.DBConstants;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
/**
* Repository service configuration persister.
*
* @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
* @version $Id: JDBCConfigurationPersister.java 11907 2008-03-13 15:36:21Z ksm $
*/
public class JDBCConfigurationPersister implements ConfigurationPersister
{
protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.core.JDBCConfigurationPersister");
public final static String PARAM_SOURCE_NAME = "source-name";
public final static String PARAM_DIALECT = "dialect";
protected static final String CONFIGNAME = "REPOSITORY-SERVICE-WORKING-CONFIG";
protected static final String C_DATA = "CONFIGDATA";
protected String configTableName = "JCR_CONFIG";
protected String sourceName;
protected String initSQL;
public class ConfigurationNotFoundException extends RepositoryConfigurationException
{
ConfigurationNotFoundException(String m)
{
super(m);
}
}
public class ConfigurationNotInitializedException extends RepositoryConfigurationException
{
ConfigurationNotInitializedException(String m)
{
super(m);
}
}
protected class ConfigDataHolder
{
private final byte[] config;
ConfigDataHolder(InputStream source) throws IOException
{
ByteArrayOutputStream configOut = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int read = 0;
while ((read = source.read(b)) > 0)
{
configOut.write(b, 0, read);
}
this.config = configOut.toByteArray();
}
InputStream getStream()
{
return new ByteArrayInputStream(config);
}
int getLength()
{
return config.length;
}
}
public void init(PropertiesParam params) throws RepositoryConfigurationException
{
String sourceNameParam = params.getProperty(PARAM_SOURCE_NAME);
if (sourceNameParam == null)
{
sourceNameParam = params.getProperty("sourceName"); // try old, pre 1.9 name
if (sourceNameParam == null)
{
throw new RepositoryConfigurationException("Repository service configuration. Source name ("
+ PARAM_SOURCE_NAME + ") is expected");
}
}
this.sourceName = sourceNameParam;
String dialectParam = params.getProperty(PARAM_DIALECT);
String dialect = dialectParam == null ? DBConstants.DB_DIALECT_AUTO : dialectParam.toUpperCase();
if (dialect.startsWith(DBConstants.DB_DIALECT_AUTO))
{
Connection conn = null;
try
{
conn = openConnection();
dialect = DialectDetecter.detect(conn.getMetaData());
}
catch (NamingException e)
{
throw new RepositoryConfigurationException(e);
}
catch (SQLException e)
{
throw new RepositoryConfigurationException(e);
}
finally
{
if (conn != null)
{
try
{
conn.close();
}
catch (SQLException e)
{
throw new RepositoryConfigurationException(e);
}
}
}
}
String binType = "BLOB";
if (dialect.startsWith(DBConstants.DB_DIALECT_GENERIC) || dialect.startsWith(DBConstants.DB_DIALECT_HSQLDB))
{
binType = "VARBINARY(1000000)"; // 1Mb
}
else if (dialect.startsWith(DBConstants.DB_DIALECT_PGSQL))
{
configTableName = configTableName.toUpperCase().toLowerCase(); // postgres needs it
binType = "BYTEA";
}
else if (dialect.startsWith(DBConstants.DB_DIALECT_MSSQL))
{
binType = "VARBINARY(max)";
}
else if (dialect.startsWith(DBConstants.DB_DIALECT_SYBASE))
{
binType = "IMAGE";
}
else if (dialect.startsWith(DBConstants.DB_DIALECT_INGRES))
{
configTableName = configTableName.toUpperCase().toLowerCase(); // ingres needs it
binType = "LONG BYTE";
}
else if (dialect.startsWith(DBConstants.DB_DIALECT_MYSQL))
{
binType = "LONGBLOB";
}
this.initSQL =
"CREATE TABLE " + configTableName + " (" + "NAME VARCHAR(64) NOT NULL, " + "CONFIG " + binType + " NOT NULL, "
+ "CONSTRAINT JCR_CONFIG_PK PRIMARY KEY(NAME))";
}
protected void checkInitialized() throws RepositoryConfigurationException
{
if (sourceName == null)
{
throw new RepositoryConfigurationException(
"Repository service configuration persister isn not initialized. Call init() before.");
}
}
protected Connection openConnection() throws NamingException, SQLException
{
final DataSource ds = (DataSource)new InitialContext().lookup(sourceName);
return SecurityHelper.doPrivilegedSQLExceptionAction(new PrivilegedExceptionAction<Connection>()
{
public Connection run() throws Exception
{
return ds.getConnection();
}
});
}
/**
* Check if config table already exists
*
* @param con
*/
protected boolean isDbInitialized(final Connection con)
{
return SecurityHelper.doPrivilegedAction(new PrivilegedAction<Boolean>()
{
public Boolean run()
{
return JDBCUtils.tableExists(configTableName, con);
}
});
}
public boolean hasConfig() throws RepositoryConfigurationException
{
checkInitialized();
try
{
Connection con = openConnection();
ResultSet res = null;
PreparedStatement ps = null;
try
{
if (isDbInitialized(con))
{
// check that data exists
ps = con.prepareStatement("SELECT COUNT(*) FROM " + configTableName + " WHERE NAME=?");
ps.setString(1, CONFIGNAME);
res = ps.executeQuery();
if (res.next())
{
return res.getInt(1) > 0;
}
}
return false;
}
finally
{
if (res != null)
{
try
{
res.close();
}
catch (SQLException e)
{
LOG.error("Can't close the ResultSet: " + e.getMessage());
}
}
if (ps != null)
{
try
{
ps.close();
}
catch (SQLException e)
{
LOG.error("Can't close the Statement: " + e.getMessage());
}
}
con.close();
}
}
catch (final SQLException e)
{
throw new RepositoryConfigurationException("Database exception. " + e, e);
}
catch (final NamingException e)
{
throw new RepositoryConfigurationException("JDNI exception. " + e, e);
}
}
public InputStream read() throws RepositoryConfigurationException
{
checkInitialized();
try
{
Connection con = openConnection();
ResultSet res = null;
PreparedStatement ps = null;
try
{
if (isDbInitialized(con))
{
ps = con.prepareStatement("SELECT * FROM " + configTableName + " WHERE NAME=?");
ps.setString(1, CONFIGNAME);
res = ps.executeQuery();
if (res.next())
{
ConfigDataHolder config = new ConfigDataHolder(res.getBinaryStream("config"));
return config.getStream();
}
else
{
throw new ConfigurationNotFoundException("No configuration data is found in database. Source name "
+ sourceName);
}
}
else
{
throw new ConfigurationNotInitializedException(
"Configuration table not is found in database. Source name " + sourceName);
}
}
finally
{
if (res != null)
{
try
{
res.close();
}
catch (SQLException e)
{
LOG.error("Can't close the ResultSet: " + e.getMessage());
}
}
if (ps != null)
{
try
{
ps.close();
}
catch (SQLException e)
{
LOG.error("Can't close the Statement: " + e.getMessage());
}
}
con.close();
}
}
catch (final IOException e)
{
throw new RepositoryConfigurationException("Configuration read exception. " + e, e);
}
catch (final SQLException e)
{
throw new RepositoryConfigurationException("Database exception. " + e, e);
}
catch (final NamingException e)
{
throw new RepositoryConfigurationException("JDNI exception. " + e, e);
}
}
public void write(InputStream confData) throws RepositoryConfigurationException
{
checkInitialized();
String sql = null;
try
{
Connection con = openConnection();
PreparedStatement ps = null;
try
{
if (!isDbInitialized(con))
{
// init db
con.setAutoCommit(true);
Statement st = con.createStatement();
st.executeUpdate(sql = initSQL);
st.close();
con.close();
// one new conn
con = openConnection();
}
con.setAutoCommit(false);
if (isDbInitialized(con))
{
ConfigDataHolder config = new ConfigDataHolder(confData);
if (hasConfig())
{
sql = "UPDATE " + configTableName + " SET CONFIG=? WHERE NAME=?";
ps = con.prepareStatement(sql);
ps.setBinaryStream(1, config.getStream(), config.getLength());
ps.setString(2, CONFIGNAME);
}
else
{
sql = "INSERT INTO " + configTableName + " (NAME, CONFIG) VALUES (?,?)";
ps = con.prepareStatement(sql);
ps.setString(1, CONFIGNAME);
ps.setBinaryStream(2, config.getStream(), config.getLength());
}
if (ps.executeUpdate() <= 0)
{
LOG.warn("Repository service configuration doesn't stored ok. "
+ "No rows was affected in JDBC operation. Datasource " + sourceName + ". SQL: " + sql);
}
}
else
{
throw new ConfigurationNotInitializedException(
"Configuration table can not be created in database. Source name " + sourceName + ". SQL: " + sql);
}
con.commit();
}
finally
{
if (ps != null)
{
try
{
ps.close();
}
catch (SQLException e)
{
LOG.error("Can't close the Statement: " + e.getMessage());
}
}
con.close();
}
}
catch (final IOException e)
{
throw new RepositoryConfigurationException("Configuration read exception. " + e, e);
}
catch (final SQLException e)
{
throw new RepositoryConfigurationException("Database exception. " + e + ". SQL: " + sql, e);
}
catch (final NamingException e)
{
throw new RepositoryConfigurationException("JDNI exception. " + e, e);
}
}
}