/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library 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 library 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. */ package com.liferay.portal.upgrade.util; import com.liferay.portal.dao.jdbc.postgresql.PostgreSQLJDBCUtil; import com.liferay.portal.kernel.dao.db.DB; import com.liferay.portal.kernel.dao.db.DBManagerUtil; import com.liferay.portal.kernel.dao.db.DBType; import com.liferay.portal.kernel.dao.jdbc.AutoBatchPreparedStatementUtil; import com.liferay.portal.kernel.dao.jdbc.DataAccess; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedWriter; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.upgrade.StagnantRowException; import com.liferay.portal.kernel.upgrade.UpgradeException; import com.liferay.portal.kernel.util.Base64; import com.liferay.portal.kernel.util.DateUtil; import com.liferay.portal.kernel.util.FileUtil; import com.liferay.portal.kernel.util.GetterUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.util.SystemProperties; import com.liferay.portal.kernel.util.Validator; import com.liferay.portal.kernel.uuid.PortalUUIDUtil; import java.io.FileReader; import java.io.FileWriter; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.sql.Clob; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.text.DateFormat; import java.util.Date; import org.apache.commons.lang.time.StopWatch; /** * @author Alexander Chow * @author Brian Wing Shun Chan * @author Raymond Augé */ public class Table { public Table(String tableName) { _tableName = tableName; } public Table(String tableName, Object[][] columns) { _tableName = tableName; setColumns(columns); } public void appendColumn(StringBuilder sb, Object value, boolean last) throws Exception { if (value == null) { throw new UpgradeException( "Nulls should never be inserted into the database. Attempted " + "to append column to " + sb.toString() + "."); } else if (value instanceof byte[]) { sb.append(Base64.encode((byte[])value)); } else if (value instanceof Clob || value instanceof String) { value = StringUtil.replace( (String)value, _SAFE_TABLE_CHARS[0], _SAFE_TABLE_CHARS[1]); sb.append(value); } else if (value instanceof Date) { DateFormat df = DateUtil.getISOFormat(); sb.append(df.format(value)); } else { sb.append(value); } sb.append(StringPool.COMMA); if (last) { sb.append(StringPool.NEW_LINE); } } public void appendColumn( StringBuilder sb, ResultSet rs, String name, Integer type, boolean last) throws Exception { Object value = null; try { value = getValue(rs, name, type); } catch (SQLException sqle) { if (name.equals("uuid_")) { sb.append(PortalUUIDUtil.generate()); } sb.append(StringPool.COMMA); if (last) { sb.append(StringPool.NEW_LINE); } return; } appendColumn(sb, value, last); } public void generateTempFile() throws Exception { Connection con = DataAccess.getUpgradeOptimizedConnection(); try { generateTempFile(con); } finally { DataAccess.cleanUp(con); } } public void generateTempFile(Connection con) throws Exception { PreparedStatement ps = null; ResultSet rs = null; boolean empty = true; Path tempFilePath = Files.createTempFile( Paths.get(SystemProperties.get(SystemProperties.TMP_DIR)), "temp-db-" + _tableName + "-", null); String tempFileName = tempFilePath.toString(); StopWatch stopWatch = new StopWatch(); stopWatch.start(); if (_log.isInfoEnabled()) { _log.info( "Starting backup of " + _tableName + " to " + tempFileName); } UnsyncBufferedWriter unsyncBufferedWriter = new UnsyncBufferedWriter( new FileWriter(tempFileName)); try { ps = getSelectPreparedStatement(con); rs = ps.executeQuery(); while (rs.next()) { String data = null; try { data = getExportedData(rs); unsyncBufferedWriter.write(data); _totalRows++; empty = false; } catch (StagnantRowException sre) { if (_log.isWarnEnabled()) { _log.warn( "Skipping stagnant data in " + _tableName + ": " + sre.getMessage()); } } } if (_log.isInfoEnabled()) { _log.info( "Finished backup of " + _tableName + " to " + tempFileName + " in " + stopWatch.getTime() + " ms"); } } catch (Exception e) { FileUtil.delete(tempFileName); throw e; } finally { DataAccess.cleanUp(ps, rs); unsyncBufferedWriter.close(); } if (!empty) { _tempFileName = tempFileName; return; } FileUtil.delete(tempFileName); } public Object[][] getColumns() { return _columns; } public String getCreateSQL() throws Exception { return _createSQL; } public String getDeleteSQL() throws Exception { return "DELETE FROM " + _tableName; } public String getExportedData(ResultSet rs) throws Exception { StringBuilder sb = new StringBuilder(); Object[][] columns = getColumns(); for (int i = 0; i < columns.length; i++) { boolean last = false; if ((i + 1) == columns.length) { last = true; } appendColumn( sb, rs, (String)columns[i][0], (Integer)columns[i][1], last); } return sb.toString(); } public String getInsertSQL() throws Exception { String sql = "INSERT INTO " + getInsertTableName() + " ("; for (int i = 0; i < _order.length; i++) { int pos = _order[i]; sql += _columns[pos][0]; if ((i + 1) < _columns.length) { sql += ", "; } else { sql += ") VALUES ("; } } for (int i = 0; i < _columns.length; i++) { sql += "?"; if ((i + 1) < _columns.length) { sql += ", "; } else { sql += ")"; } } return sql; } public String getInsertTableName() throws Exception { String createSQL = getCreateSQL(); if (Validator.isNotNull(createSQL)) { String createSQLLowerCase = StringUtil.toLowerCase(createSQL); int x = createSQLLowerCase.indexOf("create table "); if (x == -1) { return _tableName; } x += 13; int y = createSQL.indexOf(" ", x); return createSQL.substring(x, y).trim(); } else { return _tableName; } } public int[] getOrder() { return _order; } public PreparedStatement getSelectPreparedStatement(Connection con) throws Exception { return con.prepareStatement(getSelectSQL()); } public String getSelectSQL() throws Exception { if (_selectSQL == null) { /*String sql = "select "; for (int i = 0; i < _columns.length; i++) { sql += _columns[i][0]; if ((i + 1) < _columns.length) { sql += ", "; } else { sql += " from " + _tableName; } } return sql;*/ return "select * from " + _tableName; } else { return _selectSQL; } } public String getTableName() { return _tableName; } public String getTempFileName() { return _tempFileName; } public long getTotalRows() { return _totalRows; } public Object getValue(ResultSet rs, String name, Integer type) throws Exception { Object value = null; int t = type.intValue(); if (t == Types.BIGINT) { try { value = GetterUtil.getLong(rs.getLong(name)); } catch (SQLException sqle) { value = GetterUtil.getLong(rs.getString(name)); } } else if (t == Types.BIT) { value = GetterUtil.getBoolean(rs.getBoolean(name)); } else if ((t == Types.BLOB) || (t == Types.LONGVARBINARY)) { DB db = DBManagerUtil.getDB(); DBType dbType = db.getDBType(); if (dbType.equals(DBType.POSTGRESQL) && PostgreSQLJDBCUtil.isPGStatement(rs.getStatement())) { value = PostgreSQLJDBCUtil.getLargeObject(rs, name); } else { value = rs.getBytes(name); } if (value == null) { value = new byte[0]; } } else if (t == Types.BOOLEAN) { value = GetterUtil.getBoolean(rs.getBoolean(name)); } else if (t == Types.CLOB) { try { Clob clob = rs.getClob(name); if (clob == null) { value = StringPool.BLANK; } else { UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(clob.getCharacterStream()); StringBundler sb = new StringBundler(); String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { if (sb.length() != 0) { sb.append(_SAFE_TABLE_NEWLINE_CHARACTER); } sb.append(line); } value = sb.toString(); } } catch (Exception e) { // If the database doesn't allow CLOB types for the column // value, then try retrieving it as a String value = GetterUtil.getString(rs.getString(name)); } } else if (t == Types.DOUBLE) { value = GetterUtil.getDouble(rs.getDouble(name)); } else if (t == Types.FLOAT) { value = GetterUtil.getFloat(rs.getFloat(name)); } else if (t == Types.INTEGER) { value = GetterUtil.getInteger(rs.getInt(name)); } else if (t == Types.LONGVARCHAR) { value = GetterUtil.getString(rs.getString(name)); } else if (t == Types.NUMERIC) { value = GetterUtil.getLong(rs.getLong(name)); } else if (t == Types.SMALLINT) { value = GetterUtil.getShort(rs.getShort(name)); } else if (t == Types.TIMESTAMP) { try { value = rs.getTimestamp(name); } catch (Exception e) { } if (value == null) { value = StringPool.NULL; } } else if (t == Types.TINYINT) { value = GetterUtil.getShort(rs.getShort(name)); } else if (t == Types.VARCHAR) { value = GetterUtil.getString(rs.getString(name)); } else { throw new UpgradeException( "Upgrade code using unsupported class type " + type); } return value; } public void populateTable() throws Exception { Connection con = DataAccess.getUpgradeOptimizedConnection(); try { populateTable(con); } finally { DataAccess.cleanUp(con); } } public void populateTable(Connection con) throws Exception { if (_tempFileName == null) { return; } try (PreparedStatement ps = AutoBatchPreparedStatementUtil.autoBatch( con.prepareStatement(getInsertSQL())); UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(new FileReader(_tempFileName))) { String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { String[] values = StringUtil.split(line); Object[][] columns = getColumns(); if (values.length != columns.length) { throw new UpgradeException( "Column lengths differ between temp file and schema. " + "Attempted to insert row " + line + "."); } int[] order = getOrder(); for (int i = 0; i < order.length; i++) { int pos = order[i]; setColumn(ps, i, (Integer)columns[pos][1], values[pos]); } ps.addBatch(); } ps.executeBatch(); } if (_log.isDebugEnabled()) { _log.debug(getTableName() + " table populated with data"); } } public void populateTableRows(PreparedStatement ps, boolean batch) throws Exception { if (_log.isDebugEnabled()) { _log.debug("Updating rows for " + getTableName()); } if (batch) { ps.executeBatch(); } else { ps.executeUpdate(); } ps.close(); } public void setColumn( PreparedStatement ps, int index, Integer type, String value) throws Exception { int t = type.intValue(); int paramIndex = index + 1; if (t == Types.BIGINT) { ps.setLong(paramIndex, GetterUtil.getLong(value)); } else if ((t == Types.BLOB) || (t == Types.LONGVARBINARY)) { if (PostgreSQLJDBCUtil.isPGStatement(ps)) { PostgreSQLJDBCUtil.setLargeObject( ps, paramIndex, Base64.decode(value)); } else { ps.setBytes(paramIndex, Base64.decode(value)); } } else if (t == Types.BOOLEAN) { ps.setBoolean(paramIndex, GetterUtil.getBoolean(value)); } else if ((t == Types.CLOB) || (t == Types.LONGVARCHAR) || (t == Types.VARCHAR)) { value = StringUtil.replace( value, _SAFE_TABLE_CHARS[1], _SAFE_TABLE_CHARS[0]); ps.setString(paramIndex, value); } else if (t == Types.DOUBLE) { ps.setDouble(paramIndex, GetterUtil.getDouble(value)); } else if (t == Types.FLOAT) { ps.setFloat(paramIndex, GetterUtil.getFloat(value)); } else if (t == Types.INTEGER) { ps.setInt(paramIndex, GetterUtil.getInteger(value)); } else if (t == Types.SMALLINT) { ps.setShort(paramIndex, GetterUtil.getShort(value)); } else if (t == Types.TIMESTAMP) { if (StringPool.NULL.equals(value)) { ps.setTimestamp(paramIndex, null); } else { DateFormat df = DateUtil.getISOFormat(); ps.setTimestamp( paramIndex, new Timestamp(df.parse(value).getTime())); } } else if (t == Types.TINYINT) { ps.setShort(paramIndex, GetterUtil.getShort(value)); } else { throw new UpgradeException( "Upgrade code using unsupported class type " + type); } } public void setColumns(Object[][] columns) { _columns = columns; // LEP-7331 _order = new int[_columns.length]; int clobCount = 0; for (int i = 0; i < _columns.length; ++i) { Integer type = (Integer)columns[i][1]; if (type.intValue() == Types.CLOB) { clobCount++; int pos = _columns.length - clobCount; _order[pos] = i; } else { int pos = i - clobCount; _order[pos] = i; } } } public void setCreateSQL(String createSQL) throws Exception { _createSQL = createSQL; } public void setSelectSQL(String selectSQL) throws Exception { _selectSQL = selectSQL; } public void updateColumnValue( String columnName, String oldValue, String newValue) { Connection con = null; PreparedStatement ps = null; StringBundler sb = new StringBundler(7); sb.append("update "); sb.append(_tableName); sb.append(" set "); sb.append(columnName); sb.append(" = ? where "); sb.append(columnName); sb.append(" = ?"); String sql = sb.toString(); try { con = DataAccess.getUpgradeOptimizedConnection(); ps = con.prepareStatement(sql); ps.setString(1, newValue); ps.setString(2, oldValue); ps.executeUpdate(); } catch (SQLException sqle) { _log.error(sqle, sqle); throw new RuntimeException("Unable to execute " + sql, sqle); } finally { DataAccess.cleanUp(con, ps); } } private static final String[][] _SAFE_TABLE_CHARS = { {StringPool.COMMA, StringPool.NEW_LINE, StringPool.RETURN}, { Table._SAFE_TABLE_COMMA_CHARACTER, Table._SAFE_TABLE_NEWLINE_CHARACTER, Table._SAFE_TABLE_RETURN_CHARACTER } }; private static final String _SAFE_TABLE_COMMA_CHARACTER = "_SAFE_TABLE_COMMA_CHARACTER_"; private static final String _SAFE_TABLE_NEWLINE_CHARACTER = "_SAFE_TABLE_NEWLINE_CHARACTER_"; private static final String _SAFE_TABLE_RETURN_CHARACTER = "_SAFE_TABLE_RETURN_CHARACTER_"; private static final Log _log = LogFactoryUtil.getLog(Table.class); private Object[][] _columns; private String _createSQL; private int[] _order; private String _selectSQL; private final String _tableName; private String _tempFileName; private long _totalRows; }