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}