/** * 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.kernel.upgrade; import com.liferay.portal.kernel.dao.db.BaseDBProcess; import com.liferay.portal.kernel.dao.db.DB; import com.liferay.portal.kernel.dao.db.DBInspector; import com.liferay.portal.kernel.dao.db.DBManagerUtil; import com.liferay.portal.kernel.dao.db.DBProcessContext; import com.liferay.portal.kernel.dao.db.IndexMetadata; import com.liferay.portal.kernel.dao.db.IndexMetadataFactoryUtil; import com.liferay.portal.kernel.dao.jdbc.DataAccess; import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.upgrade.util.UpgradeColumn; import com.liferay.portal.kernel.upgrade.util.UpgradeTable; import com.liferay.portal.kernel.upgrade.util.UpgradeTableFactoryUtil; import com.liferay.portal.kernel.util.ClassUtil; import com.liferay.portal.kernel.util.LoggingTimer; import com.liferay.portal.kernel.util.ObjectValuePair; import com.liferay.portal.kernel.util.PortalClassLoaderUtil; import com.liferay.portal.kernel.util.StringBundler; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.lang.reflect.Field; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * @author Brian Wing Shun Chan * @author Alexander Chow */ public abstract class UpgradeProcess extends BaseDBProcess implements UpgradeStep { public void clearIndexesCache() { _portalIndexesSQL.clear(); } public int getThreshold() { // This upgrade process will only run if the build number is larger than // the returned threshold value. Return 0 to always run this upgrade // process. return 0; } public void upgrade() throws UpgradeException { long start = System.currentTimeMillis(); if (_log.isInfoEnabled()) { _log.info("Upgrading " + ClassUtil.getClassName(this)); } try (Connection con = DataAccess.getUpgradeOptimizedConnection()) { connection = con; doUpgrade(); } catch (Exception e) { throw new UpgradeException(e); } finally { connection = null; if (_log.isInfoEnabled()) { _log.info( "Completed upgrade process " + ClassUtil.getClassName(this) + " in " + (System.currentTimeMillis() - start) + "ms"); } } } public void upgrade(Class<?> upgradeProcessClass) throws UpgradeException { UpgradeProcess upgradeProcess = null; try { upgradeProcess = (UpgradeProcess)upgradeProcessClass.newInstance(); } catch (Exception e) { throw new UpgradeException(e); } upgradeProcess.upgrade(); } @Override public void upgrade(DBProcessContext dbProcessContext) throws UpgradeException { upgrade(); } public void upgrade(UpgradeProcess upgradeProcess) throws UpgradeException { upgradeProcess.upgrade(); } public interface Alterable { public static boolean containsIgnoreCase( Collection<String> columnNames, String columnName) { for (String curColumnName : columnNames) { if (StringUtil.equalsIgnoreCase(curColumnName, columnName)) { return true; } } return false; } /** * @deprecated As of 7.0.0, with no direct replacement */ @Deprecated public String getIndexedColumnName(); public String getSQL(String tableName); public boolean shouldAddIndex(Collection<String> columnNames); public boolean shouldDropIndex(Collection<String> columnNames); } public class AlterColumnName implements Alterable { public AlterColumnName(String oldColumnName, String newColumn) { _oldColumnName = oldColumnName; _newColumn = newColumn; String newColumnName = StringUtil.extractFirst( newColumn, StringPool.SPACE); if (newColumnName != null) { _newColumnName = newColumnName; } else { _newColumnName = _newColumn; } } /** * @deprecated As of 7.0.0, with no direct replacement */ @Deprecated @Override public String getIndexedColumnName() { return null; } @Override public String getSQL(String tableName) { StringBundler sb = new StringBundler(6); sb.append("alter_column_name "); sb.append(tableName); sb.append(StringPool.SPACE); sb.append(_oldColumnName); sb.append(StringPool.SPACE); sb.append(_newColumn); return sb.toString(); } @Override public boolean shouldAddIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _newColumnName); } @Override public boolean shouldDropIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _oldColumnName); } private final String _newColumn; private final String _newColumnName; private final String _oldColumnName; } public class AlterColumnType implements Alterable { public AlterColumnType(String columnName, String newType) { _columnName = columnName; _newType = newType; } /** * @deprecated As of 7.0.0, with no direct replacement */ @Deprecated @Override public String getIndexedColumnName() { return null; } @Override public String getSQL(String tableName) { StringBundler sb = new StringBundler(6); sb.append("alter_column_type "); sb.append(tableName); sb.append(StringPool.SPACE); sb.append(_columnName); sb.append(StringPool.SPACE); sb.append(_newType); return sb.toString(); } @Override public boolean shouldAddIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _columnName); } @Override public boolean shouldDropIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _columnName); } private final String _columnName; private final String _newType; } public class AlterTableAddColumn implements Alterable { public AlterTableAddColumn(String columnName) { _columnName = columnName; } /** * @deprecated As of 7.0.0, with no direct replacement */ @Deprecated @Override public String getIndexedColumnName() { return null; } @Override public String getSQL(String tableName) { StringBundler sb = new StringBundler(4); sb.append("alter table "); sb.append(tableName); sb.append(" add "); sb.append(_columnName); return sb.toString(); } @Override public boolean shouldAddIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _columnName); } @Override public boolean shouldDropIndex(Collection<String> columnNames) { return false; } private final String _columnName; } public class AlterTableDropColumn implements Alterable { public AlterTableDropColumn(String columnName) { _columnName = columnName; } /** * @deprecated As of 7.0.0, with no direct replacement */ @Deprecated @Override public String getIndexedColumnName() { return null; } @Override public String getSQL(String tableName) { StringBundler sb = new StringBundler(4); sb.append("alter table "); sb.append(tableName); sb.append(" drop column "); sb.append(_columnName); return sb.toString(); } @Override public boolean shouldAddIndex(Collection<String> columnNames) { return false; } @Override public boolean shouldDropIndex(Collection<String> columnNames) { return Alterable.containsIgnoreCase(columnNames, _columnName); } private final String _columnName; } protected void alter(Class<?> tableClass, Alterable... alterables) throws Exception { try (LoggingTimer loggingTimer = new LoggingTimer()) { Field tableNameField = tableClass.getField("TABLE_NAME"); String tableName = (String)tableNameField.get(null); DatabaseMetaData databaseMetaData = connection.getMetaData(); DBInspector dbInspector = new DBInspector(connection); try (ResultSet rs1 = databaseMetaData.getPrimaryKeys( dbInspector.getCatalog(), dbInspector.getSchema(), tableName); ResultSet rs2 = databaseMetaData.getIndexInfo( dbInspector.getCatalog(), dbInspector.getSchema(), normalizeName(tableName, databaseMetaData), false, false)) { Set<String> primaryKeyNames = new HashSet<>(); while (rs1.next()) { String primaryKeyName = StringUtil.toUpperCase( rs1.getString("PK_NAME")); if (primaryKeyName != null) { primaryKeyNames.add(primaryKeyName); } } Map<String, Set<String>> columnNamesMap = new HashMap<>(); while (rs2.next()) { String indexName = StringUtil.toUpperCase( rs2.getString("INDEX_NAME")); if ((indexName == null) || primaryKeyNames.contains(indexName)) { continue; } Set<String> columnNames = columnNamesMap.get(indexName); if (columnNames == null) { columnNames = new HashSet<>(); columnNamesMap.put(indexName, columnNames); } columnNames.add( StringUtil.toUpperCase(rs2.getString("COLUMN_NAME"))); } for (Alterable alterable : alterables) { for (Map.Entry<String, Set<String>> entry : columnNamesMap.entrySet()) { if (alterable.shouldDropIndex(entry.getValue())) { runSQL( "drop index " + entry.getKey() + " on " + tableName); } } runSQL(alterable.getSQL(tableName)); List<ObjectValuePair<String, IndexMetadata>> objectValuePairs = getIndexesSQL( tableClass.getClassLoader(), tableName); if (objectValuePairs == null) { continue; } for (ObjectValuePair<String, IndexMetadata> objectValuePair : objectValuePairs) { IndexMetadata indexMetadata = objectValuePair.getValue(); if (alterable.shouldAddIndex( Arrays.asList( indexMetadata.getColumnNames()))) { runSQLTemplateString( objectValuePair.getKey(), false, true); } } } } catch (SQLException sqle) { if (_log.isWarnEnabled()) { _log.warn("Fallback to recreating the table", sqle); } Field tableColumnsField = tableClass.getField("TABLE_COLUMNS"); Field tableSQLCreateField = tableClass.getField( "TABLE_SQL_CREATE"); Field tableSQLAddIndexesField = tableClass.getField( "TABLE_SQL_ADD_INDEXES"); upgradeTable( tableName, (Object[][])tableColumnsField.get(null), (String)tableSQLCreateField.get(null), (String[])tableSQLAddIndexesField.get(null)); } } } protected abstract void doUpgrade() throws Exception; protected List<ObjectValuePair<String, IndexMetadata>> getIndexesSQL( ClassLoader classLoader, String tableName) throws IOException { if (!PortalClassLoaderUtil.isPortalClassLoader(classLoader)) { List<ObjectValuePair<String, IndexMetadata>> objectValuePairs = new ArrayList<>(); try (InputStream is = classLoader.getResourceAsStream( "META-INF/sql/indexes.sql"); Reader reader = new InputStreamReader(is); UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(reader)) { String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { line = line.trim(); if (line.isEmpty()) { continue; } IndexMetadata indexMetadata = IndexMetadataFactoryUtil.createIndexMetadata(line); if (tableName.equals(indexMetadata.getTableName())) { objectValuePairs.add( new ObjectValuePair<>(line, indexMetadata)); } } } return objectValuePairs; } if (!_portalIndexesSQL.isEmpty()) { return _portalIndexesSQL.get(tableName); } try (InputStream is = classLoader.getResourceAsStream( "com/liferay/portal/tools/sql/dependencies/indexes.sql"); Reader reader = new InputStreamReader(is); UnsyncBufferedReader unsyncBufferedReader = new UnsyncBufferedReader(reader)) { String line = null; while ((line = unsyncBufferedReader.readLine()) != null) { line = line.trim(); if (line.isEmpty()) { continue; } IndexMetadata indexMetadata = IndexMetadataFactoryUtil.createIndexMetadata(line); List<ObjectValuePair<String, IndexMetadata>> objectValuePairs = _portalIndexesSQL.get(indexMetadata.getTableName()); if (objectValuePairs == null) { objectValuePairs = new ArrayList<>(); _portalIndexesSQL.put( indexMetadata.getTableName(), objectValuePairs); } objectValuePairs.add( new ObjectValuePair<>(line, indexMetadata)); } } return _portalIndexesSQL.get(tableName); } protected long increment() { DB db = DBManagerUtil.getDB(); return db.increment(); } protected long increment(String name) { DB db = DBManagerUtil.getDB(); return db.increment(name); } protected long increment(String name, int size) { DB db = DBManagerUtil.getDB(); return db.increment(name, size); } protected boolean isSupportsAlterColumnName() { DB db = DBManagerUtil.getDB(); return db.isSupportsAlterColumnName(); } protected boolean isSupportsAlterColumnType() { DB db = DBManagerUtil.getDB(); return db.isSupportsAlterColumnType(); } protected boolean isSupportsStringCaseSensitiveQuery() { DB db = DBManagerUtil.getDB(); return db.isSupportsStringCaseSensitiveQuery(); } protected boolean isSupportsUpdateWithInnerJoin() { DB db = DBManagerUtil.getDB(); return db.isSupportsUpdateWithInnerJoin(); } protected String normalizeName( String name, DatabaseMetaData databaseMetaData) throws SQLException { if (databaseMetaData.storesLowerCaseIdentifiers()) { return StringUtil.toLowerCase(name); } if (databaseMetaData.storesUpperCaseIdentifiers()) { return StringUtil.toUpperCase(name); } return name; } protected void upgradeTable(String tableName, Object[][] tableColumns) throws Exception { UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable( tableName, tableColumns); upgradeTable.updateTable(); } protected void upgradeTable( String tableName, Object[][] tableColumns, String createSQL, String[] indexesSQL, UpgradeColumn... upgradeColumns) throws Exception { try (LoggingTimer loggingTimer = new LoggingTimer(tableName)) { UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable( tableName, tableColumns, upgradeColumns); upgradeTable.setCreateSQL(createSQL); upgradeTable.setIndexesSQL(indexesSQL); upgradeTable.updateTable(); } } private static final Log _log = LogFactoryUtil.getLog(UpgradeProcess.class); private static final Map <String, List<ObjectValuePair<String, IndexMetadata>>> _portalIndexesSQL = new HashMap<>(); }