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