001/*
002 *  Copyright 2013 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016
017package org.ametys.web.live;
018
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import javax.jcr.Repository;
025import javax.jcr.Session;
026import javax.mail.MessagingException;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.commons.lang.StringUtils;
033
034import org.ametys.cms.indexing.WorkspaceIndexer;
035import org.ametys.core.util.I18nUtils;
036import org.ametys.core.util.mail.SendMailHelper;
037import org.ametys.plugins.repository.AmetysObjectIterable;
038import org.ametys.runtime.config.Config;
039import org.ametys.runtime.i18n.I18nizableText;
040import org.ametys.runtime.plugin.component.AbstractLogEnabled;
041import org.ametys.web.WebConstants;
042import org.ametys.web.cache.CacheHelper;
043import org.ametys.web.cache.pageelement.PageElementCache;
044import org.ametys.web.indexing.SiteIndexer;
045import org.ametys.web.repository.site.Site;
046import org.ametys.web.repository.site.SiteManager;
047import org.ametys.web.skin.Skin;
048import org.ametys.web.skin.SkinsManager;
049
050/**
051 * Component for rebuild the live workspace, reindex all sitemaps and reset cache.
052 * Provide a way to rebuild the full live workspace, or only the live of a site.
053 */
054public class RebuildLiveComponent extends AbstractLogEnabled implements Component, Serviceable
055{
056    /** Avalon Role. */
057    public static final String ROLE = RebuildLiveComponent.class.getName();
058    
059    private Repository _repository;
060    private LivePopulatorExtensionPoint _livePopulatorExtensionPoint;
061    private SitePopulator _sitePopulator;
062    private SiteIndexer _siteIndexer;
063    private WorkspaceIndexer _workspaceIndexer;
064    private SiteManager _siteManager;
065    private SkinsManager _skinsManager;
066    private PageElementCache _inputDataCache;
067    private PageElementCache _zoneItemCache;
068    private I18nUtils _i18nUtils;
069    
070    @Override
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        _siteIndexer = (SiteIndexer) manager.lookup(SiteIndexer.ROLE);
074        _repository = (Repository) manager.lookup(Repository.class.getName());
075        _livePopulatorExtensionPoint = (LivePopulatorExtensionPoint)  manager.lookup(LivePopulatorExtensionPoint.ROLE);
076        _sitePopulator = (SitePopulator) manager.lookup(SitePopulator.ROLE);
077        _workspaceIndexer = (WorkspaceIndexer) manager.lookup(WorkspaceIndexer.ROLE);
078        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
079        _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE);
080        _inputDataCache = (PageElementCache) manager.lookup(PageElementCache.ROLE + "/inputData");
081        _zoneItemCache = (PageElementCache) manager.lookup(PageElementCache.ROLE + "/zoneItem");
082        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
083    }
084    
085    /**
086     * Rebuild live workspace, index all sitemaps and reset cache.
087     * @return true if the all rebuilt succeed
088     * @throws Exception if an error occurs.
089     */
090    public Map<String, String> rebuildLiveWorkspace() throws Exception
091    {
092        Map<String, String> errors = new HashMap<>();
093        
094        Session session = null;
095        Session liveSession = null;
096        
097        if (getLogger().isInfoEnabled())
098        {
099            getLogger().info("Rebuilding live workspace...");
100        }
101        
102        try
103        {
104            session = _repository.login();
105            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
106            
107            // Synchronize other data
108            for (String id : _livePopulatorExtensionPoint.getExtensionsIds())
109            {
110                if (getLogger().isInfoEnabled())
111                {
112                    getLogger().info("Populating live workspace using LivePopulator " + id);
113                }
114                
115                LivePopulator populator = _livePopulatorExtensionPoint.getExtension(id);
116                
117                populator.populate(session, liveSession);
118                
119                if (liveSession.hasPendingChanges())
120                {
121                    liveSession.save();
122                }
123                
124                if (getLogger().isInfoEnabled())
125                {
126                    getLogger().info("LivePopulator " + id + "ended.");
127                }
128            }
129            
130            if (getLogger().isInfoEnabled())
131            {
132                getLogger().info("Live workspace populated");
133            }
134        }
135        catch (Throwable t)
136        {
137            errors.put(null, t.getMessage());
138            getLogger().error("Failed to populate live workspace", t);
139            
140            I18nizableText i18nSubject = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDALL_ERROR_MAIL_SUBJECT");
141            
142            String cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValueAsString("cms.url"), "index.html"), "/");
143            List<String> i18nParams = new ArrayList<>();
144            i18nParams.add(t.getMessage());
145            i18nParams.add(cmsUrl + "/_admin/_plugins/core/administrator/logs/view.html");
146            
147            I18nizableText i18nBody = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDALL_ERROR_MAIL_BODY", i18nParams);
148            _sendErrorEmail(i18nSubject, i18nBody);
149        }
150        finally
151        {
152            if (session != null)
153            {
154                session.logout();
155            }
156            
157            if (liveSession != null)
158            {
159                liveSession.logout();
160            }
161        }
162        
163        if (getLogger().isInfoEnabled())
164        {
165            getLogger().info("Reindexing live workspace.");
166        }
167
168        // Index the whole live workspace and not just every site.
169        _workspaceIndexer.index(WebConstants.LIVE_WORKSPACE);
170        
171        if (getLogger().isInfoEnabled())
172        {
173            getLogger().info("The live workspace was successfully indexed.");
174        }
175        
176        // TODO Clear only the live workspace?
177        // Clear every site's cache.
178        AmetysObjectIterable<Site> siteIterable = _siteManager.getSites();
179        
180        for (Site site : siteIterable)
181        {
182            try
183            {
184                _clearCache(site);
185            }
186            catch (Exception e)
187            {
188                errors.put(site.getName(), e.getMessage());
189                getLogger().error("Failed to rebuild live workspace for site '" + site.getName() + "'", e);
190                
191                List<String> i18nSubjectParams = new ArrayList<>();
192                i18nSubjectParams.add(site.getTitle());
193                I18nizableText i18nSubject = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDPREVIEW_ERROR_MAIL_SUBJECT", i18nSubjectParams);
194                
195                String cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValueAsString("cms.url"), "index.html"), "/");
196                List<String> i18nBodyParams = new ArrayList<>();
197                i18nBodyParams.add(site.getTitle());
198                i18nBodyParams.add(site.getName());
199                i18nBodyParams.add(e.getMessage());
200                i18nBodyParams.add(cmsUrl + "/_admin/_plugins/core/administrator/logs/view.html");
201                
202                I18nizableText i18nBody = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDPREVIEW_ERROR_MAIL_BODY", i18nBodyParams);
203                _sendErrorEmail(i18nSubject, i18nBody);
204            }
205        }
206        
207        if (getLogger().isInfoEnabled())
208        {
209            getLogger().info("Live workspace rebuilt");
210        }
211        
212        return errors;
213    }
214    
215    /**
216     * Rebuild the live of a site, index all sitemaps and reset cache.
217     * @param site The site to be rebuilt
218     * @throws Exception if an error occurs.
219     */
220    public void rebuildLive(Site site) throws Exception
221    {
222        String siteName = site.getName();
223        
224        Skin skin = _skinsManager.getSkin(site.getSkinId());
225        assert skin != null;
226
227        try
228        {
229            if (getLogger().isInfoEnabled())
230            {
231                getLogger().info("Rebuilding site " + siteName + " started");
232            }
233
234            if (getLogger().isInfoEnabled())
235            {
236                getLogger().info("Populating site " + siteName + " started");
237            }
238            
239            long t0 = System.currentTimeMillis();
240            _sitePopulator.populate(site, skin);
241            
242            if (getLogger().isInfoEnabled())
243            {
244                getLogger().info("Populating site " + siteName + " ended in " + (System.currentTimeMillis() - t0) + " ms");
245            }
246            
247            _reindex(site);
248            _clearCache(site);
249            
250            if (getLogger().isInfoEnabled())
251            {
252                getLogger().info("Rebuilding site " + siteName + " ended in " + (System.currentTimeMillis() - t0) + " ms");
253            }
254        }
255        catch (Exception e)
256        {
257            List<String> i18nSubjectParams = new ArrayList<>();
258            i18nSubjectParams.add(site.getTitle());
259            I18nizableText i18nSubject = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDPREVIEW_ERROR_MAIL_SUBJECT", i18nSubjectParams);
260            
261            String cmsUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValueAsString("cms.url"), "index.html"), "/");
262            List<String> i18nBodyParams = new ArrayList<>();
263            i18nBodyParams.add(site.getTitle());
264            i18nBodyParams.add(site.getName());
265            i18nBodyParams.add(e.getMessage());
266            i18nBodyParams.add(cmsUrl + "/_admin/_plugins/core/administrator/logs/view.html");
267            
268            I18nizableText i18nBody = new I18nizableText("plugin.web", "PLUGINS_WEB_ADMINISTRATOR_SITES_HANDLE_BUILDPREVIEW_ERROR_MAIL_BODY", i18nBodyParams);
269            _sendErrorEmail(i18nSubject, i18nBody);
270            
271            throw new Exception("Failed to rebuild live workspace for site '" + site.getName() + "'", e);
272        }
273    }
274    
275    private void _reindex(Site site) throws Exception
276    {
277        if (getLogger().isInfoEnabled())
278        {
279            getLogger().info("Indexing site '" + site.getName() + "'.");
280        }
281        
282        // Reindex the site in the live workspace.
283        _siteIndexer.indexSite(site.getName(), WebConstants.LIVE_WORKSPACE);
284        
285        if (getLogger().isInfoEnabled())
286        {
287            getLogger().info("Indexing of site '" + site.getName() + "' ended.");
288        }
289    }
290    
291    private void _clearCache(Site site) throws Exception
292    {
293        if (getLogger().isInfoEnabled())
294        {
295            getLogger().info("Clearing cache for site " + site.getName());
296        }
297        
298        CacheHelper.invalidateCache(site, getLogger());
299        _inputDataCache.clear(null, site.getName());
300        _zoneItemCache.clear(null, site.getName());
301    }
302    
303    private void _sendErrorEmail(I18nizableText i18nSubject, I18nizableText i18nBody)
304    {
305        String from = Config.getInstance().getValueAsString("smtp.mail.from");
306        String sysadmin = Config.getInstance().getValueAsString("smtp.mail.sysadminto");
307
308        if (StringUtils.isNotBlank(sysadmin))
309        {
310            try
311            {
312                String subject = _i18nUtils.translate(i18nSubject);
313                String body = _i18nUtils.translate(i18nBody);
314                
315                SendMailHelper.sendMail(subject, null, body, sysadmin, from);
316            }
317            catch (MessagingException e)
318            {
319                if (getLogger().isWarnEnabled())
320                {
321                    getLogger().warn("Could not send e-mail to " + sysadmin + " after build live failure", e);
322                }
323            }
324        }
325    }
326}