001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.upgrade;
016    
017    import com.liferay.portal.kernel.dao.db.BaseDBProcess;
018    import com.liferay.portal.kernel.dao.db.DB;
019    import com.liferay.portal.kernel.dao.db.DBManagerUtil;
020    import com.liferay.portal.kernel.dao.db.DBProcessContext;
021    import com.liferay.portal.kernel.dao.db.IndexMetadata;
022    import com.liferay.portal.kernel.dao.db.IndexMetadataFactoryUtil;
023    import com.liferay.portal.kernel.dao.jdbc.DataAccess;
024    import com.liferay.portal.kernel.io.unsync.UnsyncBufferedReader;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.upgrade.util.UpgradeColumn;
028    import com.liferay.portal.kernel.upgrade.util.UpgradeTable;
029    import com.liferay.portal.kernel.upgrade.util.UpgradeTableFactoryUtil;
030    import com.liferay.portal.kernel.util.ArrayUtil;
031    import com.liferay.portal.kernel.util.ClassUtil;
032    import com.liferay.portal.kernel.util.LoggingTimer;
033    import com.liferay.portal.kernel.util.ObjectValuePair;
034    import com.liferay.portal.kernel.util.PortalClassLoaderUtil;
035    import com.liferay.portal.kernel.util.StringBundler;
036    import com.liferay.portal.kernel.util.StringPool;
037    import com.liferay.portal.kernel.util.StringUtil;
038    
039    import java.io.IOException;
040    import java.io.InputStream;
041    import java.io.InputStreamReader;
042    import java.io.Reader;
043    
044    import java.lang.reflect.Field;
045    
046    import java.sql.Connection;
047    import java.sql.DatabaseMetaData;
048    import java.sql.ResultSet;
049    import java.sql.SQLException;
050    
051    import java.util.ArrayList;
052    import java.util.HashMap;
053    import java.util.HashSet;
054    import java.util.List;
055    import java.util.Map;
056    import java.util.Set;
057    
058    /**
059     * @author Brian Wing Shun Chan
060     * @author Alexander Chow
061     */
062    public abstract class UpgradeProcess
063            extends BaseDBProcess implements UpgradeStep {
064    
065            public void clearIndexesCache() {
066                    _portalIndexesSQL.clear();
067            }
068    
069            public int getThreshold() {
070    
071                    // This upgrade process will only run if the build number is larger than
072                    // the returned threshold value. Return 0 to always run this upgrade
073                    // process.
074    
075                    return 0;
076            }
077    
078            public void upgrade() throws UpgradeException {
079                    long start = System.currentTimeMillis();
080    
081                    if (_log.isInfoEnabled()) {
082                            _log.info("Upgrading " + ClassUtil.getClassName(this));
083                    }
084    
085                    try (Connection con = DataAccess.getUpgradeOptimizedConnection()) {
086                            connection = con;
087    
088                            doUpgrade();
089                    }
090                    catch (Exception e) {
091                            throw new UpgradeException(e);
092                    }
093                    finally {
094                            connection = null;
095    
096                            if (_log.isInfoEnabled()) {
097                                    _log.info(
098                                            "Completed upgrade process " +
099                                                    ClassUtil.getClassName(this) + " in " +
100                                                            (System.currentTimeMillis() - start) + "ms");
101                            }
102                    }
103            }
104    
105            public void upgrade(Class<?> upgradeProcessClass) throws UpgradeException {
106                    UpgradeProcess upgradeProcess = null;
107    
108                    try {
109                            upgradeProcess = (UpgradeProcess)upgradeProcessClass.newInstance();
110                    }
111                    catch (Exception e) {
112                            throw new UpgradeException(e);
113                    }
114    
115                    upgradeProcess.upgrade();
116            }
117    
118            @Override
119            public void upgrade(DBProcessContext dbProcessContext)
120                    throws UpgradeException {
121    
122                    upgrade();
123            }
124    
125            public void upgrade(UpgradeProcess upgradeProcess) throws UpgradeException {
126                    upgradeProcess.upgrade();
127            }
128    
129            public interface Alterable {
130    
131                    public String getIndexedColumnName();
132    
133                    public String getSQL(String tableName);
134    
135            }
136    
137            public class AlterColumnName implements Alterable {
138    
139                    public AlterColumnName(String oldColumnName, String newColumn) {
140                            _oldColumnName = oldColumnName;
141                            _newColumn = newColumn;
142                    }
143    
144                    @Override
145                    public String getIndexedColumnName() {
146                            return _oldColumnName;
147                    }
148    
149                    @Override
150                    public String getSQL(String tableName) {
151                            StringBundler sb = new StringBundler(6);
152    
153                            sb.append("alter_column_name ");
154                            sb.append(tableName);
155                            sb.append(StringPool.SPACE);
156                            sb.append(_oldColumnName);
157                            sb.append(StringPool.SPACE);
158                            sb.append(_newColumn);
159    
160                            return sb.toString();
161                    }
162    
163                    private final String _newColumn;
164                    private final String _oldColumnName;
165    
166            }
167    
168            public class AlterColumnType implements Alterable {
169    
170                    public AlterColumnType(String columnName, String newType) {
171                            _columnName = columnName;
172                            _newType = newType;
173                    }
174    
175                    @Override
176                    public String getIndexedColumnName() {
177                            return _columnName;
178                    }
179    
180                    @Override
181                    public String getSQL(String tableName) {
182                            StringBundler sb = new StringBundler(6);
183    
184                            sb.append("alter_column_type ");
185                            sb.append(tableName);
186                            sb.append(StringPool.SPACE);
187                            sb.append(_columnName);
188                            sb.append(StringPool.SPACE);
189                            sb.append(_newType);
190    
191                            return sb.toString();
192                    }
193    
194                    private final String _columnName;
195                    private final String _newType;
196    
197            }
198    
199            public class AlterTableAddColumn implements Alterable {
200    
201                    public AlterTableAddColumn(String columnName) {
202                            _columnName = columnName;
203                    }
204    
205                    @Override
206                    public String getIndexedColumnName() {
207                            return null;
208                    }
209    
210                    @Override
211                    public String getSQL(String tableName) {
212                            StringBundler sb = new StringBundler(4);
213    
214                            sb.append("alter table ");
215                            sb.append(tableName);
216                            sb.append(" add ");
217                            sb.append(_columnName);
218    
219                            return sb.toString();
220                    }
221    
222                    private final String _columnName;
223    
224            }
225    
226            public class AlterTableDropColumn implements Alterable {
227    
228                    public AlterTableDropColumn(String columnName) {
229                            _columnName = columnName;
230                    }
231    
232                    @Override
233                    public String getIndexedColumnName() {
234                            return _columnName;
235                    }
236    
237                    @Override
238                    public String getSQL(String tableName) {
239                            StringBundler sb = new StringBundler(4);
240    
241                            sb.append("alter table ");
242                            sb.append(tableName);
243                            sb.append(" drop column ");
244                            sb.append(_columnName);
245    
246                            return sb.toString();
247                    }
248    
249                    private final String _columnName;
250    
251            }
252    
253            protected void alter(Class<?> tableClass, Alterable... alterables)
254                    throws Exception {
255    
256                    try (LoggingTimer loggingTimer = new LoggingTimer()) {
257                            Field tableNameField = tableClass.getField("TABLE_NAME");
258    
259                            String tableName = (String)tableNameField.get(null);
260    
261                            DatabaseMetaData databaseMetaData = connection.getMetaData();
262    
263                            try (ResultSet rs1 = databaseMetaData.getPrimaryKeys(
264                                            null, null, tableName);
265                                    ResultSet rs2 = databaseMetaData.getIndexInfo(
266                                            null, null, normalizeName(tableName, databaseMetaData),
267                                            false, false)) {
268    
269                                    Set<String> primaryKeyNames = new HashSet<>();
270    
271                                    while (rs1.next()) {
272                                            String primaryKeyName = rs1.getString("PK_NAME");
273    
274                                            if (primaryKeyName != null) {
275                                                    primaryKeyNames.add(primaryKeyName);
276                                            }
277                                    }
278    
279                                    Map<String, Set<String>> columnNamesMap = new HashMap<>();
280    
281                                    while (rs2.next()) {
282                                            String indexName = rs2.getString("INDEX_NAME");
283    
284                                            if ((indexName == null) ||
285                                                    primaryKeyNames.contains(indexName)) {
286    
287                                                    continue;
288                                            }
289    
290                                            Set<String> columnNames = columnNamesMap.get(indexName);
291    
292                                            if (columnNames == null) {
293                                                    columnNames = new HashSet<>();
294    
295                                                    columnNamesMap.put(indexName, columnNames);
296                                            }
297    
298                                            columnNames.add(rs2.getString("COLUMN_NAME"));
299                                    }
300    
301                                    for (Alterable alterable : alterables) {
302                                            String columnName = alterable.getIndexedColumnName();
303    
304                                            for (Map.Entry<String, Set<String>> entry :
305                                                            columnNamesMap.entrySet()) {
306    
307                                                    Set<String> columnNames = entry.getValue();
308    
309                                                    if (columnNames.contains(columnName)) {
310                                                            runSQL(
311                                                                    "drop index " + entry.getKey() + " on " +
312                                                                            tableName);
313                                                    }
314                                            }
315    
316                                            runSQL(alterable.getSQL(tableName));
317    
318                                            List<ObjectValuePair<String, IndexMetadata>>
319                                                    objectValuePairs = getIndexesSQL(
320                                                            tableClass.getClassLoader(), tableName);
321    
322                                            if (objectValuePairs == null) {
323                                                    continue;
324                                            }
325    
326                                            for (ObjectValuePair<String, IndexMetadata>
327                                                            objectValuePair : objectValuePairs) {
328    
329                                                    IndexMetadata indexMetadata =
330                                                            objectValuePair.getValue();
331    
332                                                    if (!ArrayUtil.contains(
333                                                                    indexMetadata.getColumnNames(), columnName)) {
334    
335                                                            continue;
336                                                    }
337    
338                                                    runSQLTemplateString(
339                                                            objectValuePair.getKey(), false, true);
340                                            }
341                                    }
342                            }
343                            catch (SQLException sqle) {
344                                    if (_log.isWarnEnabled()) {
345                                            _log.warn("Fallback to recreating the table", sqle);
346                                    }
347    
348                                    Field tableColumnsField = tableClass.getField("TABLE_COLUMNS");
349                                    Field tableSQLCreateField = tableClass.getField(
350                                            "TABLE_SQL_CREATE");
351                                    Field tableSQLAddIndexesField = tableClass.getField(
352                                            "TABLE_SQL_ADD_INDEXES");
353    
354                                    upgradeTable(
355                                            tableName, (Object[][])tableColumnsField.get(null),
356                                            (String)tableSQLCreateField.get(null),
357                                            (String[])tableSQLAddIndexesField.get(null));
358                            }
359                    }
360            }
361    
362            protected abstract void doUpgrade() throws Exception;
363    
364            protected List<ObjectValuePair<String, IndexMetadata>> getIndexesSQL(
365                            ClassLoader classLoader, String tableName)
366                    throws IOException {
367    
368                    if (!PortalClassLoaderUtil.isPortalClassLoader(classLoader)) {
369                            List<ObjectValuePair<String, IndexMetadata>> objectValuePairs =
370                                    new ArrayList<>();
371    
372                            try (InputStream is = classLoader.getResourceAsStream(
373                                            "META-INF/sql/indexes.sql");
374                                    Reader reader = new InputStreamReader(is);
375                                    UnsyncBufferedReader unsyncBufferedReader =
376                                            new UnsyncBufferedReader(reader)) {
377    
378                                    String line = null;
379    
380                                    while ((line = unsyncBufferedReader.readLine()) != null) {
381                                            line = line.trim();
382    
383                                            if (line.isEmpty()) {
384                                                    continue;
385                                            }
386    
387                                            IndexMetadata indexMetadata =
388                                                    IndexMetadataFactoryUtil.createIndexMetadata(line);
389    
390                                            if (tableName.equals(indexMetadata.getTableName())) {
391                                                    objectValuePairs.add(
392                                                            new ObjectValuePair<>(line, indexMetadata));
393                                            }
394                                    }
395                            }
396    
397                            return objectValuePairs;
398                    }
399    
400                    if (!_portalIndexesSQL.isEmpty()) {
401                            return _portalIndexesSQL.get(tableName);
402                    }
403    
404                    try (InputStream is = classLoader.getResourceAsStream(
405                                    "com/liferay/portal/tools/sql/dependencies/indexes.sql");
406                            Reader reader = new InputStreamReader(is);
407                            UnsyncBufferedReader unsyncBufferedReader =
408                                    new UnsyncBufferedReader(reader)) {
409    
410                            String line = null;
411    
412                            while ((line = unsyncBufferedReader.readLine()) != null) {
413                                    line = line.trim();
414    
415                                    if (line.isEmpty()) {
416                                            continue;
417                                    }
418    
419                                    IndexMetadata indexMetadata =
420                                            IndexMetadataFactoryUtil.createIndexMetadata(line);
421    
422                                    List<ObjectValuePair<String, IndexMetadata>> objectValuePairs =
423                                            _portalIndexesSQL.get(indexMetadata.getTableName());
424    
425                                    if (objectValuePairs == null) {
426                                            objectValuePairs = new ArrayList<>();
427    
428                                            _portalIndexesSQL.put(
429                                                    indexMetadata.getTableName(), objectValuePairs);
430                                    }
431    
432                                    objectValuePairs.add(
433                                            new ObjectValuePair<>(line, indexMetadata));
434                            }
435                    }
436    
437                    return _portalIndexesSQL.get(tableName);
438            }
439    
440            protected long increment() {
441                    DB db = DBManagerUtil.getDB();
442    
443                    return db.increment();
444            }
445    
446            protected long increment(String name) {
447                    DB db = DBManagerUtil.getDB();
448    
449                    return db.increment(name);
450            }
451    
452            protected long increment(String name, int size) {
453                    DB db = DBManagerUtil.getDB();
454    
455                    return db.increment(name, size);
456            }
457    
458            protected boolean isSupportsAlterColumnName() {
459                    DB db = DBManagerUtil.getDB();
460    
461                    return db.isSupportsAlterColumnName();
462            }
463    
464            protected boolean isSupportsAlterColumnType() {
465                    DB db = DBManagerUtil.getDB();
466    
467                    return db.isSupportsAlterColumnType();
468            }
469    
470            protected boolean isSupportsStringCaseSensitiveQuery() {
471                    DB db = DBManagerUtil.getDB();
472    
473                    return db.isSupportsStringCaseSensitiveQuery();
474            }
475    
476            protected boolean isSupportsUpdateWithInnerJoin() {
477                    DB db = DBManagerUtil.getDB();
478    
479                    return db.isSupportsUpdateWithInnerJoin();
480            }
481    
482            protected String normalizeName(
483                            String name, DatabaseMetaData databaseMetaData)
484                    throws SQLException {
485    
486                    if (databaseMetaData.storesLowerCaseIdentifiers()) {
487                            return StringUtil.toLowerCase(name);
488                    }
489    
490                    if (databaseMetaData.storesUpperCaseIdentifiers()) {
491                            return StringUtil.toUpperCase(name);
492                    }
493    
494                    return name;
495            }
496    
497            protected void upgradeTable(String tableName, Object[][] tableColumns)
498                    throws Exception {
499    
500                    UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable(
501                            tableName, tableColumns);
502    
503                    upgradeTable.updateTable();
504            }
505    
506            protected void upgradeTable(
507                            String tableName, Object[][] tableColumns, String createSQL,
508                            String[] indexesSQL, UpgradeColumn... upgradeColumns)
509                    throws Exception {
510    
511                    try (LoggingTimer loggingTimer = new LoggingTimer(tableName)) {
512                            UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable(
513                                    tableName, tableColumns, upgradeColumns);
514    
515                            upgradeTable.setCreateSQL(createSQL);
516                            upgradeTable.setIndexesSQL(indexesSQL);
517    
518                            upgradeTable.updateTable();
519                    }
520            }
521    
522            private static final Log _log = LogFactoryUtil.getLog(UpgradeProcess.class);
523    
524            private static final Map
525                    <String, List<ObjectValuePair<String, IndexMetadata>>>
526                            _portalIndexesSQL = new HashMap<>();
527    
528    }