/** * 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.service.impl; import com.liferay.portal.kernel.cache.CacheRegistryUtil; import com.liferay.portal.kernel.dao.db.DB; import com.liferay.portal.kernel.dao.db.DBContext; import com.liferay.portal.kernel.dao.db.DBManagerUtil; import com.liferay.portal.kernel.dao.db.DBProcessContext; import com.liferay.portal.kernel.exception.OldServiceComponentException; import com.liferay.portal.kernel.exception.PortalException; import com.liferay.portal.kernel.exception.SystemException; import com.liferay.portal.kernel.log.Log; import com.liferay.portal.kernel.log.LogFactoryUtil; import com.liferay.portal.kernel.model.ModelHintsUtil; import com.liferay.portal.kernel.model.Release; import com.liferay.portal.kernel.model.ServiceComponent; import com.liferay.portal.kernel.service.configuration.ServiceComponentConfiguration; import com.liferay.portal.kernel.upgrade.UpgradeStep; import com.liferay.portal.kernel.upgrade.util.UpgradeTable; import com.liferay.portal.kernel.upgrade.util.UpgradeTableFactoryUtil; import com.liferay.portal.kernel.upgrade.util.UpgradeTableListener; import com.liferay.portal.kernel.util.InstanceFactory; import com.liferay.portal.kernel.util.ListUtil; import com.liferay.portal.kernel.util.ObjectValuePair; import com.liferay.portal.kernel.util.StringPool; import com.liferay.portal.kernel.util.StringUtil; import com.liferay.portal.kernel.xml.Document; import com.liferay.portal.kernel.xml.DocumentException; import com.liferay.portal.kernel.xml.Element; import com.liferay.portal.kernel.xml.SAXReaderUtil; import com.liferay.portal.kernel.xml.UnsecureSAXReaderUtil; import com.liferay.portal.service.base.ServiceComponentLocalServiceBaseImpl; import com.liferay.portal.util.PropsValues; import com.liferay.registry.Filter; import com.liferay.registry.Registry; import com.liferay.registry.RegistryUtil; import com.liferay.registry.ServiceReference; import com.liferay.registry.ServiceTracker; import com.liferay.registry.ServiceTrackerCustomizer; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.Field; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * @author Brian Wing Shun Chan */ public class ServiceComponentLocalServiceImpl extends ServiceComponentLocalServiceBaseImpl { public ServiceComponentLocalServiceImpl() { Registry registry = RegistryUtil.getRegistry(); Filter filter = registry.getFilter( "(&(objectClass=" + UpgradeStep.class.getName() + ")(upgrade.from.schema.version=0.0.0)(upgrade.initial." + "database.creation=true))"); _upgradeStepServiceTracker = registry.trackServices( filter, new UpgradeStepServiceTrackerCustomizer()); _upgradeStepServiceTracker.open(); } @Override public void destroy() { super.destroy(); _upgradeStepServiceTracker.close(); } @Override public void destroyServiceComponent( ServiceComponentConfiguration serviceComponentConfiguration, ClassLoader classLoader) { if (PropsValues.CACHE_CLEAR_ON_PLUGIN_UNDEPLOY) { CacheRegistryUtil.clear(); } } @Override public List<ServiceComponent> getLatestServiceComponents() { return serviceComponentFinder.findByMaxBuildNumber(); } @Override public ServiceComponent initServiceComponent( ServiceComponentConfiguration serviceComponentConfiguration, ClassLoader classLoader, String buildNamespace, long buildNumber, long buildDate, boolean buildAutoUpgrade) throws PortalException { try { ModelHintsUtil.read( classLoader, serviceComponentConfiguration.getModelHintsInputStream()); } catch (Exception e) { throw new SystemException(e); } try { ModelHintsUtil.read( classLoader, serviceComponentConfiguration.getModelHintsExtInputStream()); } catch (Exception e) { throw new SystemException(e); } ServiceComponent serviceComponent = null; ServiceComponent previousServiceComponent = null; List<ServiceComponent> serviceComponents = serviceComponentPersistence.findByBuildNamespace( buildNamespace, 0, 1); if (serviceComponents.isEmpty()) { long serviceComponentId = counterLocalService.increment(); serviceComponent = serviceComponentPersistence.create( serviceComponentId); serviceComponent.setBuildNamespace(buildNamespace); serviceComponent.setBuildNumber(buildNumber); serviceComponent.setBuildDate(buildDate); } else { serviceComponent = serviceComponents.get(0); if (serviceComponent.getBuildNumber() < buildNumber) { previousServiceComponent = serviceComponent; long serviceComponentId = counterLocalService.increment(); serviceComponent = serviceComponentPersistence.create( serviceComponentId); serviceComponent.setBuildNamespace(buildNamespace); serviceComponent.setBuildNumber(buildNumber); serviceComponent.setBuildDate(buildDate); } else if (serviceComponent.getBuildNumber() > buildNumber) { throw new OldServiceComponentException( "Build namespace " + buildNamespace + " has build number " + serviceComponent.getBuildNumber() + " which is newer than " + buildNumber); } else { return serviceComponent; } } try { Document document = SAXReaderUtil.createDocument(StringPool.UTF8); Element dataElement = document.addElement("data"); Element tablesSQLElement = dataElement.addElement("tables-sql"); String tablesSQL = StringUtil.read( serviceComponentConfiguration.getSQLTablesInputStream()); tablesSQLElement.addCDATA(tablesSQL); Element sequencesSQLElement = dataElement.addElement( "sequences-sql"); String sequencesSQL = StringUtil.read( serviceComponentConfiguration.getSQLSequencesInputStream()); sequencesSQLElement.addCDATA(sequencesSQL); Element indexesSQLElement = dataElement.addElement("indexes-sql"); String indexesSQL = StringUtil.read( serviceComponentConfiguration.getSQLIndexesInputStream()); indexesSQLElement.addCDATA(indexesSQL); String dataXML = document.formattedString(); serviceComponent.setData(dataXML); serviceComponentPersistence.update(serviceComponent); serviceComponentLocalService.upgradeDB( classLoader, buildNamespace, buildNumber, buildAutoUpgrade, previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL); removeOldServiceComponents(buildNamespace); return serviceComponent; } catch (Exception e) { throw new SystemException(e); } } @Override public void upgradeDB( final ClassLoader classLoader, final String buildNamespace, final long buildNumber, final boolean buildAutoUpgrade, final ServiceComponent previousServiceComponent, final String tablesSQL, final String sequencesSQL, final String indexesSQL) throws Exception { _pacl.doUpgradeDB( new DoUpgradeDBPrivilegedExceptionAction( classLoader, buildNamespace, buildNumber, buildAutoUpgrade, previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL)); } @Override public void verifyDB() { for (Object service : _upgradeStepServiceTracker.getServices()) { ObjectValuePair<String, UpgradeStep> upgradeStepObjectValuePair = (ObjectValuePair<String, UpgradeStep>)service; String servletContextName = upgradeStepObjectValuePair.getKey(); UpgradeStep upgradeStep = upgradeStepObjectValuePair.getValue(); Release release = releaseLocalService.fetchRelease( servletContextName); if ((release != null) && !Objects.equals(release.getSchemaVersion(), "0.0.0")) { continue; } try { upgradeStep.upgrade( new DBProcessContext() { @Override public DBContext getDBContext() { return new DBContext(); } @Override public OutputStream getOutputStream() { return null; } }); releaseLocalService.updateRelease( servletContextName, "0.0.1", "0.0.0"); } catch (Exception e) { _log.error(e, e); } } } public class DoUpgradeDBPrivilegedExceptionAction implements PrivilegedExceptionAction<Void> { public DoUpgradeDBPrivilegedExceptionAction( ClassLoader classLoader, String buildNamespace, long buildNumber, boolean buildAutoUpgrade, ServiceComponent previousServiceComponent, String tablesSQL, String sequencesSQL, String indexesSQL) { _classLoader = classLoader; _buildNamespace = buildNamespace; _buildNumber = buildNumber; _buildAutoUpgrade = buildAutoUpgrade; _previousServiceComponent = previousServiceComponent; _tablesSQL = tablesSQL; _sequencesSQL = sequencesSQL; _indexesSQL = indexesSQL; } public ClassLoader getClassLoader() { return _classLoader; } @Override public Void run() throws Exception { doUpgradeDB( _classLoader, _buildNamespace, _buildNumber, _buildAutoUpgrade, _previousServiceComponent, _tablesSQL, _sequencesSQL, _indexesSQL); return null; } private final boolean _buildAutoUpgrade; private final String _buildNamespace; private final long _buildNumber; private final ClassLoader _classLoader; private final String _indexesSQL; private final ServiceComponent _previousServiceComponent; private final String _sequencesSQL; private final String _tablesSQL; } public interface PACL { public void doUpgradeDB( DoUpgradeDBPrivilegedExceptionAction doUpgradeDBPrivilegedExceptionAction) throws Exception; } protected void doUpgradeDB( ClassLoader classLoader, String buildNamespace, long buildNumber, boolean buildAutoUpgrade, ServiceComponent previousServiceComponent, String tablesSQL, String sequencesSQL, String indexesSQL) throws Exception { DB db = DBManagerUtil.getDB(); if (previousServiceComponent == null) { if (_log.isInfoEnabled()) { _log.info("Running " + buildNamespace + " SQL scripts"); } db.runSQLTemplateString(tablesSQL, true, false); db.runSQLTemplateString(sequencesSQL, true, false); db.runSQLTemplateString(indexesSQL, true, false); } else if (buildAutoUpgrade) { if (_log.isInfoEnabled()) { _log.info( "Upgrading " + buildNamespace + " database to build number " + buildNumber); } if (!tablesSQL.equals(previousServiceComponent.getTablesSQL())) { if (_log.isInfoEnabled()) { _log.info("Upgrading database with tables.sql"); } db.runSQLTemplateString(tablesSQL, true, false); upgradeModels(classLoader, previousServiceComponent, tablesSQL); } if (!sequencesSQL.equals( previousServiceComponent.getSequencesSQL())) { if (_log.isInfoEnabled()) { _log.info("Upgrading database with sequences.sql"); } db.runSQLTemplateString(sequencesSQL, true, false); } if (!indexesSQL.equals(previousServiceComponent.getIndexesSQL()) || !tablesSQL.equals(previousServiceComponent.getTablesSQL())) { if (_log.isInfoEnabled()) { _log.info("Upgrading database with indexes.sql"); } db.runSQLTemplateString(indexesSQL, true, false); } } } protected List<String> getModelNames(ClassLoader classLoader) throws DocumentException, IOException { List<String> modelNames = new ArrayList<>(); String xml = StringUtil.read( classLoader, "META-INF/portlet-model-hints.xml"); modelNames.addAll(getModelNames(xml)); try { xml = StringUtil.read( classLoader, "META-INF/portlet-model-hints-ext.xml"); modelNames.addAll(getModelNames(xml)); } catch (Exception e) { if (_log.isInfoEnabled()) { _log.info( "No optional file META-INF/portlet-model-hints-ext.xml " + "found"); } } return modelNames; } protected List<String> getModelNames(String xml) throws DocumentException { List<String> modelNames = new ArrayList<>(); Document document = UnsecureSAXReaderUtil.read(xml); Element rootElement = document.getRootElement(); List<Element> modelElements = rootElement.elements("model"); for (Element modelElement : modelElements) { String name = modelElement.attributeValue("name"); modelNames.add(name); } return modelNames; } protected List<String> getModifiedTableNames( String previousTablesSQL, String tablesSQL) { List<String> modifiedTableNames = new ArrayList<>(); List<String> previousTablesSQLParts = ListUtil.toList( StringUtil.split(previousTablesSQL, StringPool.SEMICOLON)); List<String> tablesSQLParts = ListUtil.toList( StringUtil.split(tablesSQL, StringPool.SEMICOLON)); tablesSQLParts.removeAll(previousTablesSQLParts); for (String tablesSQLPart : tablesSQLParts) { int x = tablesSQLPart.indexOf("create table "); int y = tablesSQLPart.indexOf(" ("); modifiedTableNames.add(tablesSQLPart.substring(x + 13, y)); } return modifiedTableNames; } protected UpgradeTableListener getUpgradeTableListener( ClassLoader classLoader, Class<?> modelClass) { String modelClassName = modelClass.getName(); String upgradeTableListenerClassName = modelClassName; upgradeTableListenerClassName = StringUtil.replaceLast( upgradeTableListenerClassName, ".model.impl.", ".model.upgrade."); upgradeTableListenerClassName = StringUtil.replaceLast( upgradeTableListenerClassName, "ModelImpl", "UpgradeTableListener"); try { UpgradeTableListener upgradeTableListener = (UpgradeTableListener)InstanceFactory.newInstance( classLoader, upgradeTableListenerClassName); if (_log.isInfoEnabled()) { _log.info("Instantiated " + upgradeTableListenerClassName); } return upgradeTableListener; } catch (Exception e) { if (_log.isDebugEnabled()) { _log.debug( "Unable to instantiate " + upgradeTableListenerClassName); } return null; } } protected void removeOldServiceComponents(String buildNamespace) { int serviceComponentsCount = serviceComponentPersistence.countByBuildNamespace(buildNamespace); if (serviceComponentsCount < _SERVICE_COMPONENTS_MAX) { return; } List<ServiceComponent> serviceComponents = serviceComponentPersistence.findByBuildNamespace( buildNamespace, _SERVICE_COMPONENTS_MAX, serviceComponentsCount); for (int i = 0; i < serviceComponents.size(); i++) { ServiceComponent serviceComponent = serviceComponents.get(i); serviceComponentPersistence.remove(serviceComponent); } } protected void upgradeModels( ClassLoader classLoader, ServiceComponent previousServiceComponent, String tablesSQL) throws Exception { List<String> modifiedTableNames = getModifiedTableNames( previousServiceComponent.getTablesSQL(), tablesSQL); List<String> modelNames = getModelNames(classLoader); for (String modelName : modelNames) { int pos = modelName.lastIndexOf(".model."); Class<?> modelClass = Class.forName( modelName.substring(0, pos) + ".model.impl." + modelName.substring(pos + 7) + "ModelImpl", true, classLoader); Field dataSourceField = modelClass.getField("DATA_SOURCE"); String dataSource = (String)dataSourceField.get(null); if (!dataSource.equals(_DATA_SOURCE_DEFAULT)) { continue; } Field tableNameField = modelClass.getField("TABLE_NAME"); String tableName = (String)tableNameField.get(null); if (!modifiedTableNames.contains(tableName)) { continue; } Field tableColumnsField = modelClass.getField("TABLE_COLUMNS"); Object[][] tableColumns = (Object[][])tableColumnsField.get(null); UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable( tableName, tableColumns); UpgradeTableListener upgradeTableListener = getUpgradeTableListener( classLoader, modelClass); Field tableSQLCreateField = modelClass.getField("TABLE_SQL_CREATE"); String tableSQLCreate = (String)tableSQLCreateField.get(null); upgradeTable.setCreateSQL(tableSQLCreate); if (upgradeTableListener != null) { upgradeTableListener.onBeforeUpdateTable( previousServiceComponent, upgradeTable); } upgradeTable.updateTable(); if (upgradeTableListener != null) { upgradeTableListener.onAfterUpdateTable( previousServiceComponent, upgradeTable); } } } private static final String _DATA_SOURCE_DEFAULT = "liferayDataSource"; private static final int _SERVICE_COMPONENTS_MAX = 10; private static final Log _log = LogFactoryUtil.getLog( ServiceComponentLocalServiceImpl.class); private static final PACL _pacl = new NoPACL(); private final ServiceTracker <UpgradeStep, ObjectValuePair<String, UpgradeStep>> _upgradeStepServiceTracker; private static class NoPACL implements PACL { @Override public void doUpgradeDB( DoUpgradeDBPrivilegedExceptionAction doUpgradeDBPrivilegedExceptionAction) throws Exception { doUpgradeDBPrivilegedExceptionAction.run(); } } private static class UpgradeStepServiceTrackerCustomizer implements ServiceTrackerCustomizer <UpgradeStep, ObjectValuePair<String, UpgradeStep>> { @Override public ObjectValuePair<String, UpgradeStep> addingService( ServiceReference<UpgradeStep> serviceReference) { Registry registry = RegistryUtil.getRegistry(); UpgradeStep upgradeStep = registry.getService(serviceReference); String servletContextName = (String)serviceReference.getProperty( "upgrade.bundle.symbolic.name"); return new ObjectValuePair<>(servletContextName, upgradeStep); } @Override public void modifiedService( ServiceReference<UpgradeStep> serviceReference, ObjectValuePair<String, UpgradeStep> service) { addingService(serviceReference); } @Override public void removedService( ServiceReference<UpgradeStep> serviceReference, ObjectValuePair<String, UpgradeStep> service) { } } }