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