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