001/*
002 *  Copyright 2010 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 */
016package org.ametys.web.content.consistency;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.FileOutputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.util.ArrayList;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029
030import org.apache.avalon.framework.context.Context;
031import org.apache.avalon.framework.context.ContextException;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.cocoon.components.ContextHelper;
035import org.apache.cocoon.components.source.impl.SitemapSource;
036import org.apache.cocoon.environment.Request;
037import org.apache.commons.io.FileUtils;
038import org.apache.commons.lang.StringUtils;
039import org.apache.excalibur.source.SourceUtil;
040import org.xml.sax.InputSource;
041import org.xml.sax.SAXException;
042
043import org.ametys.core.user.UserIdentity;
044import org.ametys.core.user.population.PopulationContextHelper;
045import org.ametys.plugins.repository.AmetysObjectIterable;
046import org.ametys.plugins.repository.AmetysRepositoryException;
047import org.ametys.runtime.config.Config;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.web.repository.site.Site;
050import org.ametys.web.repository.site.SiteManager;
051
052/**
053 * Content consistency engine: generate consistency information for all contents.
054 * Sends a report e-mail if there are inconsistencies.
055 */
056public class ContentConsistencyEngine extends org.ametys.cms.content.consistency.ContentConsistencyEngine
057{
058    
059    private static Map<String, Boolean> _RUNNING_SITES = new HashMap<>();
060    
061    /** The site manager. */
062    protected SiteManager _siteManager;
063    
064    /** Name of the site to generate. */
065    protected String _siteName;
066    
067    /**
068     * Initialize the consistency engine.
069     * @param manager the avalon service manager.
070     * @param context the avalon context.
071     * @param siteName the name of the site to generate or null to generate all.
072     * @throws ContextException if an error occurs retrieving the environment context.
073     * @throws ServiceException if an error occurs retrieving a component.
074     */
075    public void initialize(ServiceManager manager, Context context, String siteName) throws ContextException, ServiceException
076    {
077        super.initialize(manager, context);
078        
079        _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE);
080        _siteName = siteName;
081    }
082    
083    /**
084     * Test if the engine is running at the time.
085     * @param siteName the site name.
086     * @return true if the engine is running, false otherwise.
087     */
088    public static boolean isRunning(String siteName)
089    {
090        return _RUNNING_SITES.containsKey(siteName);
091    }
092    
093    /**
094     * Set the running status of a site.
095     * @param siteName the site name.
096     * @param running true to set the site running, false otherwise.
097     */
098    private static void setRunning(String siteName, boolean running)
099    {
100        synchronized (siteName)
101        {
102            if (running)
103            {
104                _RUNNING_SITES.put(siteName, running);
105            }
106            else
107            {
108                _RUNNING_SITES.remove(siteName);
109            }
110        }
111    }
112    
113    @Override
114    protected void _dispose()
115    {
116        // Release the components.
117        _manager.release(_siteManager);
118        
119        super._dispose();
120    }
121    
122    @Override
123    protected void _generateReports() throws AmetysRepositoryException, IOException
124    {
125        // Generate the report.
126        AmetysObjectIterable<Site> sites = _siteManager.getSites();
127        for (Site site : sites)
128        {
129            String siteName = site.getName();
130            
131            if (_siteName == null || _siteName.equals(siteName))
132            {
133                // If the engine is already running, log an error and throw an exception.
134                if (isRunning(siteName))
135                {
136                    _LOGGER.error("Cannot start a global consistency check, as the engine is running at the time.");
137                }
138                else
139                {
140                    Request request = ContextHelper.getRequest(_context);
141                    request.setAttribute("siteName", site.getName());
142                    
143                    List<String> populationContexts = new ArrayList<>();
144                    
145                    // Set the population contexts to be able to get allowed users
146                    populationContexts.add("/sites/" + siteName);
147                    populationContexts.add("/sites-fo/" + siteName);
148                    
149                    request.setAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR, populationContexts);
150                    
151                    try
152                    {
153                        setRunning(siteName, true);
154                        _generateReport(site);
155                    }
156                    finally
157                    {
158                        setRunning(siteName, false);
159                    }
160                }
161            }
162        }
163    }
164    
165    /**
166     * Generate the full consistency report for a site.
167     * @param site the site.
168     * @throws IOException if an i/o error occurs.
169     */
170    protected void _generateReport(Site site) throws IOException
171    {
172        SitemapSource source = null;
173        File reportTmpFile = null;
174        
175        try
176        {
177            String siteName = site.getName();
178            
179            File siteDir = new File(_reportDirectory, siteName);
180            
181            // Create the directory if it does not exist.
182            FileUtils.forceMkdir(siteDir);
183            
184            // Resolve the report pipeline.
185            String url = "cocoon://_plugins/web/consistency/" + siteName + "/inconsistent-contents-report.xml";
186            source = (SitemapSource) _sourceResolver.resolveURI(url);
187            
188            // Save the report into a temporary file.
189            reportTmpFile = new File(siteDir, "report.tmp.xml");
190            OutputStream reportTmpOs = new FileOutputStream(reportTmpFile);
191            
192            SourceUtil.copy(source.getInputStream(), reportTmpOs);
193            
194            // If all went well until now, copy the temporary file to the real report file.
195            File reportFile = new File(siteDir, "report.xml");
196            FileUtils.copyFile(reportTmpFile, reportFile);
197            
198            try (InputStream reportIs = new FileInputStream(reportFile))
199            {
200                // Parse the report to know if there were contents with inconsistencies.
201                ContentExistsHandler handler = new ContentExistsHandler();
202                _saxParser.parse(new InputSource(reportIs), handler);
203                
204                // If inconsistent contents exist, send an e-mail.
205                if (handler.hasFailures())
206                {
207                    _sendErrorEmail();
208                }
209            }
210        }
211        catch (SAXException e)
212        {
213            _LOGGER.error("The consistency report could not be parsed.", e);
214        }
215        finally
216        {
217            // Delete the temporary file.
218            if (reportTmpFile != null)
219            {
220                reportTmpFile.delete();
221            }
222
223            if (source != null)
224            {
225                _sourceResolver.release(source);
226            }
227        }
228    }
229    
230    @Override
231    protected void _sendErrorEmail() throws IOException
232    {
233        Set<UserIdentity> users = _rightManager.getAllowedUsers(_MAIL_RIGHT, "/cms").resolveAllowedUsers(Config.getInstance().getValueAsBoolean("runtime.mail.massive.sending"));
234        
235        Map<String, String> params = _getEmailParams();
236        
237        List<String> subjectParams = new ArrayList<>();
238        subjectParams.add(params.get("siteTitle"));
239        
240        I18nizableText i18nSubject = new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_MAIL_SUBJECT", subjectParams);
241        
242        String subject = _i18nUtils.translate(i18nSubject);
243        String body = _getMailPart(params);
244        
245        if (StringUtils.isNotEmpty(body))
246        {
247            _sendMails(subject, body, users, _mailFrom);
248        }
249    }
250    
251    @Override
252    protected String _getMailUri (Map<String, String> parameters)
253    {
254        return "cocoon://_plugins/web/consistency/" + parameters.get("siteName") + "/inconsistent-contents-mail.html";
255    }
256    
257    @Override
258    protected Map<String, String> _getEmailParams()
259    {
260        Map<String, String> params = new HashMap<>();
261        
262        Request request = ContextHelper.getRequest(_context);
263        String siteName = (String) request.getAttribute("siteName");
264        
265        StringBuilder url = new StringBuilder(_baseUrl);
266        if (!_baseUrl.endsWith("/"))
267        {
268            url.append('/');
269        }
270        url.append(siteName).append("/index.html?uitool=uitool-global-consistency");
271        
272        params.put("url", url.toString());
273        
274        Site site = _siteManager.getSite(siteName);
275        String siteTitle = site.getTitle();
276        
277        params.put("siteTitle", siteTitle);
278        params.put("siteName", siteName);
279        
280        return params;
281    }
282    
283}