001/*
002 *  Copyright 2019 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.odf.schedulable;
017
018import java.io.File;
019import java.io.FileInputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.avalon.framework.component.Component;
029import org.apache.avalon.framework.service.ServiceException;
030import org.apache.avalon.framework.service.ServiceManager;
031import org.apache.avalon.framework.service.Serviceable;
032import org.apache.excalibur.xml.dom.DOMParser;
033import org.apache.excalibur.xml.xpath.XPathProcessor;
034import org.quartz.JobKey;
035import org.quartz.SchedulerException;
036import org.w3c.dom.Document;
037import org.w3c.dom.NamedNodeMap;
038import org.w3c.dom.Node;
039import org.w3c.dom.NodeList;
040import org.xml.sax.InputSource;
041import org.xml.sax.SAXException;
042
043import org.ametys.core.schedule.Runnable;
044import org.ametys.core.schedule.Runnable.FireProcess;
045import org.ametys.core.schedule.Runnable.MisfirePolicy;
046import org.ametys.core.ui.Callable;
047import org.ametys.core.user.CurrentUserProvider;
048import org.ametys.plugins.core.impl.schedule.DefaultRunnable;
049import org.ametys.plugins.core.schedule.Scheduler;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.plugin.component.AbstractLogEnabled;
052import org.ametys.runtime.util.AmetysHomeHelper;
053
054/**
055 * The component for scheduling a global validation report on programs
056 */
057public class GlobalValidationReport extends AbstractLogEnabled implements Component, Serviceable
058{
059    /** The Avalon role */
060    public static final String ROLE = GlobalValidationReport.class.getName();
061    
062    private static final String __SCHEDULABLE_ID = "org.ametys.odf.schedulable.GlobalValidationSchedulable";
063    private static final String __JOB_KEY = __SCHEDULABLE_ID + "$task";
064    
065    private Scheduler _scheduler;
066    private CurrentUserProvider _userProvider;
067
068    private XPathProcessor _xPathProcessor;
069
070    private DOMParser _domParser;
071    
072    public void service(ServiceManager smanager) throws ServiceException
073    {
074        _scheduler = (Scheduler) smanager.lookup(Scheduler.ROLE);
075        _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
076        _domParser = (DOMParser) smanager.lookup(DOMParser.ROLE);
077        _xPathProcessor = (XPathProcessor) smanager.lookup(XPathProcessor.ROLE);
078    }
079    
080    /**
081     * Determines if the scheduler is currently running
082     * @return true if a check is already running
083     */
084    @Callable
085    public boolean isRunning()
086    {
087        return Boolean.TRUE.equals(_scheduler.isRunning(__JOB_KEY).get("running"));
088    }
089    
090    /**
091     * Start the global validation report on programs
092     * @return the result
093     */
094    @Callable
095    public Map<String, Object> startReport()
096    {
097        Map<String, Object> result = new HashMap<>();
098
099        try
100        {
101            Runnable runnable = new DefaultRunnable(__JOB_KEY, 
102                    new I18nizableText("plugin.odf", "PLUGINS_ODF_SCHEDULABLE_GLOBAL_VALIDATION_REPORT_LABEL"), 
103                    new I18nizableText("plugin.odf", "PLUGINS_ODF_SCHEDULABLE_GLOBAL_VALIDATION_REPORT_DESCRIPTION"), 
104                    FireProcess.NOW, 
105                    null /* cron*/, 
106                    __SCHEDULABLE_ID, 
107                    false /* removable */, 
108                    false /* modifiable */, 
109                    false /* deactivatable */, 
110                    MisfirePolicy.FIRE_ONCE, 
111                    false /* isVolatile */, 
112                    _userProvider.getUser(), 
113                    Collections.EMPTY_MAP);
114            
115            if (isRunning())
116            {
117                getLogger().warn("A global validation check is already running");
118                result.put("error", "already-running");
119                result.put("success", false);
120            }
121            else
122            {
123                JobKey jobKey = new JobKey(runnable.getId(), Scheduler.JOB_GROUP);
124                
125                if (_scheduler.getScheduler().checkExists(jobKey))
126                {
127                    _scheduler.getScheduler().deleteJob(jobKey);
128                }
129                _scheduler.scheduleJob(runnable);
130                result.put("success", true);
131            }
132            
133            return result;
134        }
135        catch (SchedulerException e)
136        {
137            getLogger().error("An error occured when trying to schedule the global validation status on programs", e);
138            result.put("success", false);
139            return result;
140        }
141    }
142    
143    /**
144     * Get the list of invalidated contents computed by the last report for a given content
145     * @param contentId the content id
146     * @return the list of invalidated contents
147     */
148    @Callable
149    public List<Map<String, Object>> getInvalidatedContents(String contentId)
150    {
151        List<Map<String, Object>> invalidatedContents = new ArrayList<>();
152        
153        File reportFile = new File(AmetysHomeHelper.getAmetysHomeData(), "odf/report/global-validation.xml");
154        
155        if (reportFile.exists())
156        {
157            try (InputStream is = new FileInputStream(reportFile))
158            {
159                Document doc = _domParser.parseDocument(new InputSource(is));
160                
161                NodeList nodeList = _xPathProcessor.selectNodeList(doc, "/programs/program[@id='" + contentId + "']/invalidatedContents/content");
162                
163                for (int i = 0; i < nodeList.getLength(); i++)
164                {
165                    Node node = nodeList.item(i);
166                    invalidatedContents.add(_getAttributes(node));
167                }
168            }
169            catch (IOException | SAXException e)
170            {
171                getLogger().error("Unable to get the list of invalidated contents computed by the last report for program '{}'", contentId, e);
172            }
173        }
174        
175        return invalidatedContents;
176    }
177    
178    private Map<String, Object> _getAttributes(Node node)
179    {
180        Map<String, Object> map = new HashMap<>();
181        
182        NamedNodeMap attributes = node.getAttributes();
183        for (int i = 0; i < attributes.getLength(); i++)
184        {
185            Node attribute = attributes.item(i);
186            
187            String name = attribute.getNodeName();
188            String value = attribute.getNodeValue();
189            
190            map.put(name, value);
191        }
192        
193        return map;
194    }
195}