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.util.HashSet; 019import java.util.List; 020import java.util.Map; 021import java.util.Set; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.cocoon.components.ContextHelper; 026import org.apache.cocoon.environment.Request; 027import org.apache.commons.collections4.ListUtils; 028import org.quartz.JobDataMap; 029import org.quartz.JobExecutionContext; 030 031import org.ametys.cms.content.consistency.ContentConsistencyManager.ConsistenciesReport; 032import org.ametys.core.cache.AbstractCacheManager; 033import org.ametys.core.schedule.progression.ContainerProgressionTracker; 034import org.ametys.core.schedule.progression.SimpleProgressionTracker; 035import org.ametys.core.user.UserIdentity; 036import org.ametys.core.util.JSONUtils; 037import org.ametys.plugins.core.schedule.Scheduler; 038import org.ametys.plugins.repository.AmetysObjectIterable; 039import org.ametys.runtime.config.Config; 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.runtime.i18n.I18nizableTextParameter; 042import org.ametys.web.WebHelper; 043import org.ametys.web.repository.site.Site; 044import org.ametys.web.repository.site.SiteManager; 045 046/** 047 * Content consistency engine: generate consistency information for all contents. 048 * Sends a report e-mail if there are inconsistencies. 049 */ 050public class CheckContentConsistencySchedulable extends org.ametys.cms.content.consistency.CheckContentConsistencySchedulable 051{ 052 /** The key for the name of the site containing the contents to check */ 053 public static final String SITE_NAME_KEY = "siteName"; 054 /** Right to receive the consistency report of content without site */ 055 public static final String RECEIVE_REPORT_NO_SITE_RIGHT = "Web_Rights_ReceiveConsistencyReport_No_Site"; 056 057 private static final String __WITH_SITE_REPORT = "with-site-report"; 058 private static final String __NO_SITE_REPORT = "no-site-report"; 059 private static final String __JOBDATAMAP_SITE_NAME_KEY = Scheduler.PARAM_VALUES_PREFIX + SITE_NAME_KEY; 060 061 /** The utils for JSON */ 062 protected JSONUtils _jsonUtils; 063 /** The site manager. */ 064 protected SiteManager _siteManager; 065 /** Web version of the content consistency manager */ 066 private ContentConsistencyManager _webContentConsistencyManager; 067 /** The cache manager */ 068 private AbstractCacheManager _cacheManager; 069 070 @Override 071 public void service(ServiceManager manager) throws ServiceException 072 { 073 super.service(manager); 074 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 075 _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE); 076 _webContentConsistencyManager = (ContentConsistencyManager) manager.lookup(org.ametys.cms.content.consistency.ContentConsistencyManager.ROLE); 077 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 078 } 079 080 @Override 081 public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 082 { 083 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 084 String siteAsMap = (String) jobDataMap.get(__JOBDATAMAP_SITE_NAME_KEY); 085 List<String> siteNames = _getSiteName(siteAsMap); 086 087 ContainerProgressionTracker noSiteReportStep = progressionTracker.addContainerStep(__NO_SITE_REPORT, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REPORT_STEP_NO_SITE_LABEL")); 088 noSiteReportStep.addSimpleStep(CHECK_CONTENTS, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_CHECK_CONTENTS_LABEL")); 089 noSiteReportStep.addSimpleStep(REMOVE_OUTDATED_RESULT, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REMOVE_OUTDATED_RESULT_LABEL")); 090 ContainerProgressionTracker withSitesReportStep = progressionTracker.addContainerStep(__WITH_SITE_REPORT, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REPORT_STEP_WITH_SITE_LABEL")); 091 092 // Start by checking if there is any content without site with error. 093 ConsistenciesReport noSiteReport = _webContentConsistencyManager.checkContentsWithoutSite(noSiteReportStep); 094 095 try (AmetysObjectIterable<Site> sites = _siteManager.getSites()) 096 { 097 for (Site currentSite : sites) 098 { 099 String currentStepId = __WITH_SITE_REPORT + "_" + currentSite.getId(); 100 Map<String, I18nizableTextParameter> param = Map.of("site", new I18nizableText(currentSite.getTitle())); 101 102 // Task weight was chosen arbitrarily because creating the report is way longer than the other tasks 103 ContainerProgressionTracker siteStep = withSitesReportStep.addContainerStep(currentStepId, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REPORT_STEP_WITH_SITE_CONTENTS_LABEL", param)); 104 ContainerProgressionTracker reportStep = siteStep.addContainerStep(__CHECK_CONTENT_CONSISTENCY_REPORT, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REPORT_STEP_LABEL"), 10); 105 reportStep.addSimpleStep(CHECK_CONTENTS, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_CHECK_CONTENTS_LABEL")); 106 reportStep.addSimpleStep(REMOVE_OUTDATED_RESULT, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_REMOVE_OUTDATED_RESULT_LABEL")); 107 siteStep.addSimpleStep(__CHECK_CONTENT_CONSISTENCY_NOTIFICATION, new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_NOTIFICATION_STEPLABEL")); 108 } 109 // Iterate over sites to checks requested sites, or all of them if no requested site 110 for (Site currentSite : sites) 111 { 112 String currentStepId = __WITH_SITE_REPORT + "_" + currentSite.getId(); 113 String currentSiteName = currentSite.getName(); 114 115 if (siteNames.isEmpty() || siteNames.contains(currentSiteName)) 116 { 117 // Make the site "current" 118 Request request = ContextHelper.getRequest(_context); 119 request.setAttribute("siteName", currentSiteName); 120 121 // Check content for current site 122 ContainerProgressionTracker currentSiteStep = (ContainerProgressionTracker) withSitesReportStep.getStep(currentStepId); 123 ConsistenciesReport report = _webContentConsistencyManager.checkContentsFromSite(currentSiteName, currentSiteStep.getStep(__CHECK_CONTENT_CONSISTENCY_REPORT)); 124 125 SimpleProgressionTracker notificationProgressionTracker = currentSiteStep.getStep(__CHECK_CONTENT_CONSISTENCY_NOTIFICATION); 126 // One of the report is not empty, we need to compute user to notify 127 if (!report.isEmpty() || !noSiteReport.isEmpty()) 128 { 129 Set<UserIdentity> siteUsers = getUsersToNotify(); 130 // Notification right for content without site are assigned by site 131 // (because there is no other way to assign a right) 132 // use the fake context to determine the user to notify 133 Set<UserIdentity> noSiteUsers = _rightManager.getAllowedUsers(RECEIVE_REPORT_NO_SITE_RIGHT, "/cms").resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending")); 134 135 Set<UserIdentity> intersection = new HashSet<>(siteUsers); 136 intersection.retainAll(noSiteUsers); 137 138 siteUsers.removeAll(intersection); 139 noSiteUsers.removeAll(intersection); 140 141 notificationProgressionTracker.setSize(siteUsers.size() + noSiteUsers.size() + intersection.size()); 142 143 // Include only the site contents 144 if (!siteUsers.isEmpty() && !report.isEmpty()) 145 { 146 _sendEmail(report, siteUsers, notificationProgressionTracker); 147 } 148 149 // Include only content without site 150 if (!noSiteUsers.isEmpty() && !noSiteReport.isEmpty()) 151 { 152 _sendEmail(noSiteReport, noSiteUsers, notificationProgressionTracker); 153 } 154 155 // Include content from site and outside 156 if (!intersection.isEmpty()) 157 { 158 ConsistenciesReport mergedReport = new ConsistenciesReport( 159 ListUtils.sum(report.results(), noSiteReport.results()), 160 ListUtils.sum(report.unchecked(), noSiteReport.unchecked())); 161 _sendEmail(mergedReport, intersection, notificationProgressionTracker); 162 } 163 } 164 else 165 { 166 notificationProgressionTracker.setSize(0); 167 } 168 } 169 170 // As looping on sites, we should simulate several request 171 // Otherwise the email builder may cache a skin resolution 172 _cacheManager.resetAllNonDispatchableRequestCaches(); 173 } 174 } 175 176 } 177 178 @Override 179 protected I18nizableText _getMailSubject(ConsistenciesReport report) 180 { 181 return new I18nizableText("plugin.web", "PLUGINS_WEB_GLOBAL_CONTENT_CONSISTENCY_MAIL_SUBJECT", List.of(_getSite().getTitle())); 182 } 183 184 private Site _getSite() 185 { 186 String siteName = _getCurrentSiteName(); 187 return _siteManager.getSite(siteName); 188 } 189 190 @Override 191 protected String _getReportUri() 192 { 193 String siteName = _getCurrentSiteName(); 194 195 StringBuilder url = new StringBuilder(_baseUrl); 196 if (!_baseUrl.endsWith("/")) 197 { 198 url.append('/'); 199 } 200 url.append(siteName).append("/index.html?uitool=uitool-global-consistency"); 201 202 return url.toString(); 203 } 204 205 /** 206 * Retrieves the names of the sites from given parameters 207 * @param siteAsMap JSON {@link Map} containing the sites names 208 * @return the names of the sites 209 */ 210 @SuppressWarnings("unchecked") 211 protected List<String> _getSiteName(String siteAsMap) 212 { 213 Map<String, Object> mapSite = _jsonUtils.convertJsonToMap(siteAsMap); 214 return mapSite.containsKey("sites") ? (List<String>) mapSite.get("sites") : List.of(); 215 } 216 217 /** 218 * Retrieves the current site name 219 * @return the current site name 220 */ 221 protected String _getCurrentSiteName() 222 { 223 Request request = ContextHelper.getRequest(_context); 224 return WebHelper.getSiteName(request); 225 } 226}