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}