001    /**
002     * Copyright (c) 2000-2013 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.GetterUtil;
031    import com.liferay.portal.kernel.util.HttpUtil;
032    import com.liferay.portal.kernel.util.InstanceFactory;
033    import com.liferay.portal.kernel.util.ListUtil;
034    import com.liferay.portal.kernel.util.StringPool;
035    import com.liferay.portal.kernel.util.StringUtil;
036    import com.liferay.portal.kernel.xml.Document;
037    import com.liferay.portal.kernel.xml.DocumentException;
038    import com.liferay.portal.kernel.xml.Element;
039    import com.liferay.portal.kernel.xml.SAXReaderUtil;
040    import com.liferay.portal.kernel.xml.UnsecureSAXReaderUtil;
041    import com.liferay.portal.model.ModelHintsUtil;
042    import com.liferay.portal.model.ServiceComponent;
043    import com.liferay.portal.service.base.ServiceComponentLocalServiceBaseImpl;
044    import com.liferay.portal.tools.servicebuilder.Entity;
045    import com.liferay.portal.util.PropsUtil;
046    
047    import java.io.IOException;
048    import java.io.InputStream;
049    
050    import java.lang.reflect.Field;
051    
052    import java.security.PrivilegedExceptionAction;
053    
054    import java.util.ArrayList;
055    import java.util.List;
056    
057    import javax.servlet.ServletContext;
058    
059    /**
060     * @author Brian Wing Shun Chan
061     */
062    public class ServiceComponentLocalServiceImpl
063            extends ServiceComponentLocalServiceBaseImpl {
064    
065            public static final boolean CACHE_CLEAR_ON_PLUGIN_UNDEPLOY =
066                            GetterUtil.getBoolean(
067                                    PropsUtil.get("cache.clear.on.plugin.undeploy"));
068    
069            @Override
070            public void destroyServiceComponent(
071                            ServletContext servletContext, ClassLoader classLoader)
072                    throws SystemException {
073    
074                    try {
075                            clearCacheRegistry(servletContext);
076                    }
077                    catch (Exception e) {
078                            throw new SystemException(e);
079                    }
080            }
081    
082            @Override
083            public ServiceComponent initServiceComponent(
084                            ServletContext servletContext, ClassLoader classLoader,
085                            String buildNamespace, long buildNumber, long buildDate,
086                            boolean buildAutoUpgrade)
087                    throws PortalException, SystemException {
088    
089                    try {
090                            ModelHintsUtil.read(
091                                    classLoader, "META-INF/portlet-model-hints.xml");
092                    }
093                    catch (Exception e) {
094                            throw new SystemException(e);
095                    }
096    
097                    try {
098                            ModelHintsUtil.read(
099                                    classLoader, "META-INF/portlet-model-hints-ext.xml");
100                    }
101                    catch (Exception e) {
102                            throw new SystemException(e);
103                    }
104    
105                    ServiceComponent serviceComponent = null;
106                    ServiceComponent previousServiceComponent = null;
107    
108                    List<ServiceComponent> serviceComponents =
109                            serviceComponentPersistence.findByBuildNamespace(
110                                    buildNamespace, 0, 1);
111    
112                    if (serviceComponents.isEmpty()) {
113                            long serviceComponentId = counterLocalService.increment();
114    
115                            serviceComponent = serviceComponentPersistence.create(
116                                    serviceComponentId);
117    
118                            serviceComponent.setBuildNamespace(buildNamespace);
119                            serviceComponent.setBuildNumber(buildNumber);
120                            serviceComponent.setBuildDate(buildDate);
121                    }
122                    else {
123                            serviceComponent = serviceComponents.get(0);
124    
125                            if (serviceComponent.getBuildNumber() < buildNumber) {
126                                    previousServiceComponent = serviceComponent;
127    
128                                    long serviceComponentId = counterLocalService.increment();
129    
130                                    serviceComponent = serviceComponentPersistence.create(
131                                            serviceComponentId);
132    
133                                    serviceComponent.setBuildNamespace(buildNamespace);
134                                    serviceComponent.setBuildNumber(buildNumber);
135                                    serviceComponent.setBuildDate(buildDate);
136                            }
137                            else if (serviceComponent.getBuildNumber() > buildNumber) {
138                                    throw new OldServiceComponentException(
139                                            "Build namespace " + buildNamespace + " has build number " +
140                                                    serviceComponent.getBuildNumber() +
141                                                            " which is newer than " + buildNumber);
142                            }
143                            else {
144                                    return serviceComponent;
145                            }
146                    }
147    
148                    try {
149                            Document document = SAXReaderUtil.createDocument(StringPool.UTF8);
150    
151                            Element dataElement = document.addElement("data");
152    
153                            Element tablesSQLElement = dataElement.addElement("tables-sql");
154    
155                            String tablesSQL = HttpUtil.URLtoString(
156                                    servletContext.getResource("/WEB-INF/sql/tables.sql"));
157    
158                            tablesSQLElement.addCDATA(tablesSQL);
159    
160                            Element sequencesSQLElement = dataElement.addElement(
161                                    "sequences-sql");
162    
163                            String sequencesSQL = HttpUtil.URLtoString(
164                                    servletContext.getResource("/WEB-INF/sql/sequences.sql"));
165    
166                            sequencesSQLElement.addCDATA(sequencesSQL);
167    
168                            Element indexesSQLElement = dataElement.addElement("indexes-sql");
169    
170                            String indexesSQL = HttpUtil.URLtoString(
171                                    servletContext.getResource("/WEB-INF/sql/indexes.sql"));
172    
173                            indexesSQLElement.addCDATA(indexesSQL);
174    
175                            String dataXML = document.formattedString();
176    
177                            serviceComponent.setData(dataXML);
178    
179                            serviceComponentPersistence.update(serviceComponent);
180    
181                            serviceComponentLocalService.upgradeDB(
182                                    classLoader, buildNamespace, buildNumber, buildAutoUpgrade,
183                                    previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL);
184    
185                            removeOldServiceComponents(buildNamespace);
186    
187                            return serviceComponent;
188                    }
189                    catch (Exception e) {
190                            throw new SystemException(e);
191                    }
192            }
193    
194            @Override
195            public void upgradeDB(
196                            final ClassLoader classLoader, final String buildNamespace,
197                            final long buildNumber, final boolean buildAutoUpgrade,
198                            final ServiceComponent previousServiceComponent,
199                            final String tablesSQL, final String sequencesSQL,
200                            final String indexesSQL)
201                    throws Exception {
202    
203                    _pacl.doUpgradeDB(
204                            new DoUpgradeDBPrivilegedExceptionAction(
205                                    classLoader, buildNamespace, buildNumber, buildAutoUpgrade,
206                                    previousServiceComponent, tablesSQL, sequencesSQL, indexesSQL));
207            }
208    
209            @Override
210            public void verifyDB() throws SystemException {
211                    List<ServiceComponent> serviceComponents =
212                            serviceComponentPersistence.findAll();
213    
214                    for (ServiceComponent serviceComponent : serviceComponents) {
215                            String buildNamespace = serviceComponent.getBuildNamespace();
216                            String tablesSQL = serviceComponent.getTablesSQL();
217                            String sequencesSQL = serviceComponent.getSequencesSQL();
218                            String indexesSQL = serviceComponent.getIndexesSQL();
219    
220                            try {
221                                    serviceComponentLocalService.upgradeDB(
222                                            null, buildNamespace, 0, false, null, tablesSQL,
223                                            sequencesSQL, indexesSQL);
224                            }
225                            catch (Exception e) {
226                                    _log.error(e, e);
227                            }
228                    }
229            }
230    
231            public static interface PACL {
232    
233                    public void doUpgradeDB(
234                                    DoUpgradeDBPrivilegedExceptionAction
235                                            doUpgradeDBPrivilegedExceptionAction)
236                            throws Exception;
237    
238            }
239    
240            public class DoUpgradeDBPrivilegedExceptionAction
241                    implements PrivilegedExceptionAction<Void> {
242    
243                    public DoUpgradeDBPrivilegedExceptionAction(
244                            ClassLoader classLoader, String buildNamespace, long buildNumber,
245                            boolean buildAutoUpgrade, ServiceComponent previousServiceComponent,
246                            String tablesSQL, String sequencesSQL, String indexesSQL) {
247    
248                            _classLoader = classLoader;
249                            _buildNamespace = buildNamespace;
250                            _buildNumber = buildNumber;
251                            _buildAutoUpgrade = buildAutoUpgrade;
252                            _previousServiceComponent = previousServiceComponent;
253                            _tablesSQL = tablesSQL;
254                            _sequencesSQL = sequencesSQL;
255                            _indexesSQL = indexesSQL;
256                    }
257    
258                    public ClassLoader getClassLoader() {
259                            return _classLoader;
260                    }
261    
262                    @Override
263                    public Void run() throws Exception {
264                            doUpgradeDB(
265                                    _classLoader, _buildNamespace, _buildNumber, _buildAutoUpgrade,
266                                    _previousServiceComponent, _tablesSQL, _sequencesSQL,
267                                    _indexesSQL);
268    
269                            return null;
270                    }
271    
272                    private boolean _buildAutoUpgrade;
273                    private String _buildNamespace;
274                    private long _buildNumber;
275                    private ClassLoader _classLoader;
276                    private String _indexesSQL;
277                    private ServiceComponent _previousServiceComponent;
278                    private String _sequencesSQL;
279                    private String _tablesSQL;
280    
281            }
282    
283            protected void clearCacheRegistry(ServletContext servletContext)
284                    throws DocumentException {
285    
286                    InputStream inputStream = servletContext.getResourceAsStream(
287                            "/WEB-INF/classes/META-INF/portlet-hbm.xml");
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 (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<String>();
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<String>();
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<String>();
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                    throws SystemException {
471    
472                    int serviceComponentsCount =
473                            serviceComponentPersistence.countByBuildNamespace(buildNamespace);
474    
475                    if (serviceComponentsCount < _MAX_SERVICE_COMPONENTS) {
476                            return;
477                    }
478    
479                    List<ServiceComponent> serviceComponents =
480                            serviceComponentPersistence.findByBuildNamespace(
481                                    buildNamespace, _MAX_SERVICE_COMPONENTS,
482                                    serviceComponentsCount);
483    
484                    for (int i = 0; i < serviceComponents.size(); i++) {
485                            ServiceComponent serviceComponent = serviceComponents.get(i);
486    
487                            serviceComponentPersistence.remove(serviceComponent);
488                    }
489            }
490    
491            protected void upgradeModels(
492                            ClassLoader classLoader, ServiceComponent previousServiceComponent,
493                            String tablesSQL)
494                    throws Exception {
495    
496                    List<String> modifiedTableNames = getModifiedTableNames(
497                            previousServiceComponent.getTablesSQL(), tablesSQL);
498    
499                    List<String> modelNames = getModelNames(classLoader);
500    
501                    for (String modelName : modelNames) {
502                            int pos = modelName.lastIndexOf(".model.");
503    
504                            Class<?> modelClass = Class.forName(
505                                    modelName.substring(0, pos) + ".model.impl." +
506                                            modelName.substring(pos + 7) + "ModelImpl",
507                                    true, classLoader);
508    
509                            Field dataSourceField = modelClass.getField("DATA_SOURCE");
510    
511                            String dataSource = (String)dataSourceField.get(null);
512    
513                            if (!dataSource.equals(Entity.DEFAULT_DATA_SOURCE)) {
514                                    continue;
515                            }
516    
517                            Field tableNameField = modelClass.getField("TABLE_NAME");
518    
519                            String tableName = (String)tableNameField.get(null);
520    
521                            if (!modifiedTableNames.contains(tableName)) {
522                                    continue;
523                            }
524    
525                            Field tableColumnsField = modelClass.getField("TABLE_COLUMNS");
526    
527                            Object[][] tableColumns = (Object[][])tableColumnsField.get(null);
528    
529                            UpgradeTable upgradeTable = UpgradeTableFactoryUtil.getUpgradeTable(
530                                    tableName, tableColumns);
531    
532                            UpgradeTableListener upgradeTableListener = getUpgradeTableListener(
533                                    classLoader, modelClass);
534    
535                            Field tableSQLCreateField = modelClass.getField("TABLE_SQL_CREATE");
536    
537                            String tableSQLCreate = (String)tableSQLCreateField.get(null);
538    
539                            upgradeTable.setCreateSQL(tableSQLCreate);
540    
541                            if (upgradeTableListener != null) {
542                                    upgradeTableListener.onBeforeUpdateTable(
543                                            previousServiceComponent, upgradeTable);
544                            }
545    
546                            upgradeTable.updateTable();
547    
548                            if (upgradeTableListener != null) {
549                                    upgradeTableListener.onAfterUpdateTable(
550                                            previousServiceComponent, upgradeTable);
551                            }
552                    }
553            }
554    
555            private static final int _MAX_SERVICE_COMPONENTS = 10;
556    
557            private static Log _log = LogFactoryUtil.getLog(
558                    ServiceComponentLocalServiceImpl.class);
559    
560            private static 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    }