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.service.impl;
016    
017    import com.liferay.portal.OldServiceComponentException;
018    import com.liferay.portal.kernel.cache.CacheRegistryUtil;
019    import com.liferay.portal.kernel.dao.db.DB;
020    import com.liferay.portal.kernel.dao.db.DBFactoryUtil;
021    import com.liferay.portal.kernel.dao.orm.EntityCacheUtil;
022    import com.liferay.portal.kernel.dao.orm.FinderCacheUtil;
023    import com.liferay.portal.kernel.exception.PortalException;
024    import com.liferay.portal.kernel.exception.SystemException;
025    import com.liferay.portal.kernel.log.Log;
026    import com.liferay.portal.kernel.log.LogFactoryUtil;
027    import com.liferay.portal.kernel.upgrade.util.UpgradeTable;
028    import com.liferay.portal.kernel.upgrade.util.UpgradeTableFactoryUtil;
029    import com.liferay.portal.kernel.upgrade.util.UpgradeTableListener;
030    import com.liferay.portal.kernel.util.InstanceFactory;
031    import com.liferay.portal.kernel.util.ListUtil;
032    import com.liferay.portal.kernel.util.StringPool;
033    import com.liferay.portal.kernel.util.StringUtil;
034    import com.liferay.portal.kernel.xml.Document;
035    import com.liferay.portal.kernel.xml.DocumentException;
036    import com.liferay.portal.kernel.xml.Element;
037    import com.liferay.portal.kernel.xml.SAXReaderUtil;
038    import com.liferay.portal.kernel.xml.UnsecureSAXReaderUtil;
039    import com.liferay.portal.model.ModelHintsUtil;
040    import com.liferay.portal.model.ServiceComponent;
041    import com.liferay.portal.service.base.ServiceComponentLocalServiceBaseImpl;
042    import com.liferay.portal.service.configuration.ServiceComponentConfiguration;
043    import com.liferay.portal.util.PropsValues;
044    
045    import java.io.IOException;
046    import java.io.InputStream;
047    
048    import java.lang.reflect.Field;
049    
050    import java.security.PrivilegedExceptionAction;
051    
052    import java.util.ArrayList;
053    import java.util.List;
054    
055    /**
056     * @author Brian Wing Shun Chan
057     */
058    public class ServiceComponentLocalServiceImpl
059            extends ServiceComponentLocalServiceBaseImpl {
060    
061            @Override
062            public void destroyServiceComponent(
063                    ServiceComponentConfiguration serviceComponentConfiguration,
064                    ClassLoader classLoader) {
065    
066                    try {
067                            clearCacheRegistry(serviceComponentConfiguration);
068                    }
069                    catch (Exception e) {
070                            throw new SystemException(e);
071                    }
072            }
073    
074            @Override
075            public List<ServiceComponent> getLatestServiceComponents() {
076                    return serviceComponentFinder.findByMaxBuildNumber();
077            }
078    
079            @Override
080            public ServiceComponent initServiceComponent(
081                            ServiceComponentConfiguration serviceComponentConfiguration,
082                            ClassLoader classLoader, String buildNamespace, long buildNumber,
083                            long buildDate, boolean buildAutoUpgrade)
084                    throws PortalException {
085    
086                    try {
087                            ModelHintsUtil.read(
088                                    classLoader,
089                                    serviceComponentConfiguration.getModelHintsInputStream());
090                    }
091                    catch (Exception e) {
092                            throw new SystemException(e);
093                    }
094    
095                    try {
096                            ModelHintsUtil.read(
097                                    classLoader,
098                                    serviceComponentConfiguration.getModelHintsExtInputStream());
099                    }
100                    catch (Exception e) {
101                            throw new SystemException(e);
102                    }
103    
104                    ServiceComponent serviceComponent = null;
105                    ServiceComponent previousServiceComponent = null;
106    
107                    List<ServiceComponent> serviceComponents =
108                            serviceComponentPersistence.findByBuildNamespace(
109                                    buildNamespace, 0, 1);
110    
111                    if (serviceComponents.isEmpty()) {
112                            long serviceComponentId = counterLocalService.increment();
113    
114                            serviceComponent = serviceComponentPersistence.create(
115                                    serviceComponentId);
116    
117                            serviceComponent.setBuildNamespace(buildNamespace);
118                            serviceComponent.setBuildNumber(buildNumber);
119                            serviceComponent.setBuildDate(buildDate);
120                    }
121                    else {
122                            serviceComponent = serviceComponents.get(0);
123    
124                            if (serviceComponent.getBuildNumber() < buildNumber) {
125                                    previousServiceComponent = serviceComponent;
126    
127                                    long serviceComponentId = counterLocalService.increment();
128    
129                                    serviceComponent = serviceComponentPersistence.create(
130                                            serviceComponentId);
131    
132                                    serviceComponent.setBuildNamespace(buildNamespace);
133                                    serviceComponent.setBuildNumber(buildNumber);
134                                    serviceComponent.setBuildDate(buildDate);
135                            }
136                            else if (serviceComponent.getBuildNumber() > buildNumber) {
137                                    throw new OldServiceComponentException(
138                                            "Build namespace " + buildNamespace + " has build number " +
139                                                    serviceComponent.getBuildNumber() +
140                                                            " which is newer than " + buildNumber);
141                            }
142                            else {
143                                    return serviceComponent;
144                            }
145                    }
146    
147                    try {
148                            Document document = SAXReaderUtil.createDocument(StringPool.UTF8);
149    
150                            Element dataElement = document.addElement("data");
151    
152                            Element tablesSQLElement = dataElement.addElement("tables-sql");
153    
154                            String tablesSQL = StringUtil.read(
155                                    serviceComponentConfiguration.getSQLTablesInputStream());
156    
157                            tablesSQLElement.addCDATA(tablesSQL);
158    
159                            Element sequencesSQLElement = dataElement.addElement(
160                                    "sequences-sql");
161    
162                            String sequencesSQL = StringUtil.read(
163                                    serviceComponentConfiguration.getSQLSequencesInputStream());
164    
165                            sequencesSQLElement.addCDATA(sequencesSQL);
166    
167                            Element indexesSQLElement = dataElement.addElement("indexes-sql");
168    
169                            String indexesSQL = StringUtil.read(
170                                    serviceComponentConfiguration.getSQLIndexesInputStream());
171    
172                            indexesSQLElement.addCDATA(indexesSQL);
173    
174                            String dataXML = document.formattedString();
175    
176                            serviceComponent.setData(dataXML);
177    
178                            serviceComponentPersistence.update(serviceComponent);
179    
180                            serviceComponentLocalService.upgradeDB(
181                                    classLoader, buildNamespace, buildNumber, buildAutoUpgrade,
182                                    previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL);
183    
184                            removeOldServiceComponents(buildNamespace);
185    
186                            return serviceComponent;
187                    }
188                    catch (Exception e) {
189                            throw new SystemException(e);
190                    }
191            }
192    
193            @Override
194            public void upgradeDB(
195                            final ClassLoader classLoader, final String buildNamespace,
196                            final long buildNumber, final boolean buildAutoUpgrade,
197                            final ServiceComponent previousServiceComponent,
198                            final String tablesSQL, final String sequencesSQL,
199                            final String indexesSQL)
200                    throws Exception {
201    
202                    _pacl.doUpgradeDB(
203                            new DoUpgradeDBPrivilegedExceptionAction(
204                                    classLoader, buildNamespace, buildNumber, buildAutoUpgrade,
205                                    previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL));
206            }
207    
208            @Override
209            public void verifyDB() {
210                    List<ServiceComponent> serviceComponents =
211                            serviceComponentPersistence.findAll();
212    
213                    for (ServiceComponent serviceComponent : serviceComponents) {
214                            String buildNamespace = serviceComponent.getBuildNamespace();
215                            String tablesSQL = serviceComponent.getTablesSQL();
216                            String sequencesSQL = serviceComponent.getSequencesSQL();
217                            String indexesSQL = serviceComponent.getIndexesSQL();
218    
219                            try {
220                                    serviceComponentLocalService.upgradeDB(
221                                            null, buildNamespace, 0, false, null, tablesSQL,
222                                            sequencesSQL, indexesSQL);
223                            }
224                            catch (Exception e) {
225                                    _log.error(e, e);
226                            }
227                    }
228            }
229    
230            public class DoUpgradeDBPrivilegedExceptionAction
231                    implements PrivilegedExceptionAction<Void> {
232    
233                    public DoUpgradeDBPrivilegedExceptionAction(
234                            ClassLoader classLoader, String buildNamespace, long buildNumber,
235                            boolean buildAutoUpgrade, ServiceComponent previousServiceComponent,
236                            String tablesSQL, String sequencesSQL, String indexesSQL) {
237    
238                            _classLoader = classLoader;
239                            _buildNamespace = buildNamespace;
240                            _buildNumber = buildNumber;
241                            _buildAutoUpgrade = buildAutoUpgrade;
242                            _previousServiceComponent = previousServiceComponent;
243                            _tablesSQL = tablesSQL;
244                            _sequencesSQL = sequencesSQL;
245                            _indexesSQL = indexesSQL;
246                    }
247    
248                    public ClassLoader getClassLoader() {
249                            return _classLoader;
250                    }
251    
252                    @Override
253                    public Void run() throws Exception {
254                            doUpgradeDB(
255                                    _classLoader, _buildNamespace, _buildNumber, _buildAutoUpgrade,
256                                    _previousServiceComponent, _tablesSQL, _sequencesSQL,
257                                    _indexesSQL);
258    
259                            return null;
260                    }
261    
262                    private final boolean _buildAutoUpgrade;
263                    private final String _buildNamespace;
264                    private final long _buildNumber;
265                    private final ClassLoader _classLoader;
266                    private final String _indexesSQL;
267                    private final ServiceComponent _previousServiceComponent;
268                    private final String _sequencesSQL;
269                    private final String _tablesSQL;
270    
271            }
272    
273            public interface PACL {
274    
275                    public void doUpgradeDB(
276                                    DoUpgradeDBPrivilegedExceptionAction
277                                            doUpgradeDBPrivilegedExceptionAction)
278                            throws Exception;
279    
280            }
281    
282            protected void clearCacheRegistry(
283                            ServiceComponentConfiguration serviceComponentConfiguration)
284                    throws DocumentException {
285    
286                    InputStream inputStream =
287                            serviceComponentConfiguration.getHibernateInputStream();
288    
289                    if (inputStream == null) {
290                            return;
291                    }
292    
293                    Document document = UnsecureSAXReaderUtil.read(inputStream);
294    
295                    Element rootElement = document.getRootElement();
296    
297                    List<Element> classElements = rootElement.elements("class");
298    
299                    for (Element classElement : classElements) {
300                            String name = classElement.attributeValue("name");
301    
302                            CacheRegistryUtil.unregister(name);
303                    }
304    
305                    CacheRegistryUtil.clear();
306    
307                    if (PropsValues.CACHE_CLEAR_ON_PLUGIN_UNDEPLOY) {
308                            EntityCacheUtil.clearCache();
309                            FinderCacheUtil.clearCache();
310                    }
311            }
312    
313            protected void doUpgradeDB(
314                            ClassLoader classLoader, String buildNamespace, long buildNumber,
315                            boolean buildAutoUpgrade, ServiceComponent previousServiceComponent,
316                            String tablesSQL, String sequencesSQL, String indexesSQL)
317                    throws Exception {
318    
319                    DB db = DBFactoryUtil.getDB();
320    
321                    if (previousServiceComponent == null) {
322                            if (_log.isInfoEnabled()) {
323                                    _log.info("Running " + buildNamespace + " SQL scripts");
324                            }
325    
326                            db.runSQLTemplateString(tablesSQL, true, false);
327                            db.runSQLTemplateString(sequencesSQL, true, false);
328                            db.runSQLTemplateString(indexesSQL, true, false);
329                    }
330                    else if (buildAutoUpgrade) {
331                            if (_log.isInfoEnabled()) {
332                                    _log.info(
333                                            "Upgrading " + buildNamespace +
334                                                    " database to build number " + buildNumber);
335                            }
336    
337                            if (!tablesSQL.equals(previousServiceComponent.getTablesSQL())) {
338                                    if (_log.isInfoEnabled()) {
339                                            _log.info("Upgrading database with tables.sql");
340                                    }
341    
342                                    db.runSQLTemplateString(tablesSQL, true, false);
343    
344                                    upgradeModels(classLoader, previousServiceComponent, tablesSQL);
345                            }
346    
347                            if (!sequencesSQL.equals(
348                                            previousServiceComponent.getSequencesSQL())) {
349    
350                                    if (_log.isInfoEnabled()) {
351                                            _log.info("Upgrading database with sequences.sql");
352                                    }
353    
354                                    db.runSQLTemplateString(sequencesSQL, true, false);
355                            }
356    
357                            if (!indexesSQL.equals(previousServiceComponent.getIndexesSQL()) ||
358                                    !tablesSQL.equals(previousServiceComponent.getTablesSQL())) {
359    
360                                    if (_log.isInfoEnabled()) {
361                                            _log.info("Upgrading database with indexes.sql");
362                                    }
363    
364                                    db.runSQLTemplateString(indexesSQL, true, false);
365                            }
366                    }
367            }
368    
369            protected List<String> getModelNames(ClassLoader classLoader)
370                    throws DocumentException, IOException {
371    
372                    List<String> modelNames = new ArrayList<>();
373    
374                    String xml = StringUtil.read(
375                            classLoader, "META-INF/portlet-model-hints.xml");
376    
377                    modelNames.addAll(getModelNames(xml));
378    
379                    try {
380                            xml = StringUtil.read(
381                                    classLoader, "META-INF/portlet-model-hints-ext.xml");
382    
383                            modelNames.addAll(getModelNames(xml));
384                    }
385                    catch (Exception e) {
386                            if (_log.isInfoEnabled()) {
387                                    _log.info(
388                                            "No optional file META-INF/portlet-model-hints-ext.xml " +
389                                                    "found");
390                            }
391                    }
392    
393                    return modelNames;
394            }
395    
396            protected List<String> getModelNames(String xml) throws DocumentException {
397                    List<String> modelNames = new ArrayList<>();
398    
399                    Document document = UnsecureSAXReaderUtil.read(xml);
400    
401                    Element rootElement = document.getRootElement();
402    
403                    List<Element> modelElements = rootElement.elements("model");
404    
405                    for (Element modelElement : modelElements) {
406                            String name = modelElement.attributeValue("name");
407    
408                            modelNames.add(name);
409                    }
410    
411                    return modelNames;
412            }
413    
414            protected List<String> getModifiedTableNames(
415                    String previousTablesSQL, String tablesSQL) {
416    
417                    List<String> modifiedTableNames = new ArrayList<>();
418    
419                    List<String> previousTablesSQLParts = ListUtil.toList(
420                            StringUtil.split(previousTablesSQL, StringPool.SEMICOLON));
421                    List<String> tablesSQLParts = ListUtil.toList(
422                            StringUtil.split(tablesSQL, StringPool.SEMICOLON));
423    
424                    tablesSQLParts.removeAll(previousTablesSQLParts);
425    
426                    for (String tablesSQLPart : tablesSQLParts) {
427                            int x = tablesSQLPart.indexOf("create table ");
428                            int y = tablesSQLPart.indexOf(" (");
429    
430                            modifiedTableNames.add(tablesSQLPart.substring(x + 13, y));
431                    }
432    
433                    return modifiedTableNames;
434            }
435    
436            protected UpgradeTableListener getUpgradeTableListener(
437                    ClassLoader classLoader, Class<?> modelClass) {
438    
439                    String modelClassName = modelClass.getName();
440    
441                    String upgradeTableListenerClassName = modelClassName;
442    
443                    upgradeTableListenerClassName = StringUtil.replaceLast(
444                            upgradeTableListenerClassName, ".model.impl.", ".model.upgrade.");
445                    upgradeTableListenerClassName = StringUtil.replaceLast(
446                            upgradeTableListenerClassName, "ModelImpl", "UpgradeTableListener");
447    
448                    try {
449                            UpgradeTableListener upgradeTableListener =
450                                    (UpgradeTableListener)InstanceFactory.newInstance(
451                                            classLoader, upgradeTableListenerClassName);
452    
453                            if (_log.isInfoEnabled()) {
454                                    _log.info("Instantiated " + upgradeTableListenerClassName);
455                            }
456    
457                            return upgradeTableListener;
458                    }
459                    catch (Exception e) {
460                            if (_log.isDebugEnabled()) {
461                                    _log.debug(
462                                            "Unable to instantiate " + upgradeTableListenerClassName);
463                            }
464    
465                            return null;
466                    }
467            }
468    
469            protected void removeOldServiceComponents(String buildNamespace) {
470                    int serviceComponentsCount =
471                            serviceComponentPersistence.countByBuildNamespace(buildNamespace);
472    
473                    if (serviceComponentsCount < _SERVICE_COMPONENTS_MAX) {
474                            return;
475                    }
476    
477                    List<ServiceComponent> serviceComponents =
478                            serviceComponentPersistence.findByBuildNamespace(
479                                    buildNamespace, _SERVICE_COMPONENTS_MAX,
480                                    serviceComponentsCount);
481    
482                    for (int i = 0; i < serviceComponents.size(); i++) {
483                            ServiceComponent serviceComponent = serviceComponents.get(i);
484    
485                            serviceComponentPersistence.remove(serviceComponent);
486                    }
487            }
488    
489            protected void upgradeModels(
490                            ClassLoader classLoader, ServiceComponent previousServiceComponent,
491                            String tablesSQL)
492                    throws Exception {
493    
494                    List<String> modifiedTableNames = getModifiedTableNames(
495                            previousServiceComponent.getTablesSQL(), tablesSQL);
496    
497                    List<String> modelNames = getModelNames(classLoader);
498    
499                    for (String modelName : modelNames) {
500                            int pos = modelName.lastIndexOf(".model.");
501    
502                            Class<?> modelClass = Class.forName(
503                                    modelName.substring(0, pos) + ".model.impl." +
504                                            modelName.substring(pos + 7) + "ModelImpl",
505                                    true, classLoader);
506    
507                            Field dataSourceField = modelClass.getField("DATA_SOURCE");
508    
509                            String dataSource = (String)dataSourceField.get(null);
510    
511                            if (!dataSource.equals(_DATA_SOURCE_DEFAULT)) {
512                                    continue;
513                            }
514    
515                            Field tableNameField = modelClass.getField("TABLE_NAME");
516    
517                            String tableName = (String)tableNameField.get(null);
518    
519                            if (!modifiedTableNames.contains(tableName)) {
520                                    continue;
521                            }
522    
523                            Field tableColumnsField = modelClass.getField("TABLE_COLUMNS");
524    
525                            Object[][] tableColumns = (Object[][])tableColumnsField.get(null);
526    
527                            UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable(
528                                    tableName, tableColumns);
529    
530                            UpgradeTableListener upgradeTableListener = getUpgradeTableListener(
531                                    classLoader, modelClass);
532    
533                            Field tableSQLCreateField = modelClass.getField("TABLE_SQL_CREATE");
534    
535                            String tableSQLCreate = (String)tableSQLCreateField.get(null);
536    
537                            upgradeTable.setCreateSQL(tableSQLCreate);
538    
539                            if (upgradeTableListener != null) {
540                                    upgradeTableListener.onBeforeUpdateTable(
541                                            previousServiceComponent, upgradeTable);
542                            }
543    
544                            upgradeTable.updateTable();
545    
546                            if (upgradeTableListener != null) {
547                                    upgradeTableListener.onAfterUpdateTable(
548                                            previousServiceComponent, upgradeTable);
549                            }
550                    }
551            }
552    
553            private static final String _DATA_SOURCE_DEFAULT = "liferayDataSource";
554    
555            private static final int _SERVICE_COMPONENTS_MAX = 10;
556    
557            private static final Log _log = LogFactoryUtil.getLog(
558                    ServiceComponentLocalServiceImpl.class);
559    
560            private static final PACL _pacl = new NoPACL();
561    
562            private static class NoPACL implements PACL {
563    
564                    @Override
565                    public void doUpgradeDB(
566                                    DoUpgradeDBPrivilegedExceptionAction
567                                            doUpgradeDBPrivilegedExceptionAction)
568                            throws Exception {
569    
570                            doUpgradeDBPrivilegedExceptionAction.run();
571                    }
572    
573            }
574    
575    }