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}