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