001/*
002 *  Copyright 2014 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.cms.alerts;
017
018import java.util.Calendar;
019import java.util.Date;
020import java.util.GregorianCalendar;
021import java.util.List;
022import java.util.Timer;
023import java.util.TimerTask;
024
025import org.apache.avalon.framework.activity.Disposable;
026import org.apache.avalon.framework.activity.Initializable;
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.configuration.Configurable;
029import org.apache.avalon.framework.configuration.Configuration;
030import org.apache.avalon.framework.configuration.ConfigurationException;
031import org.apache.avalon.framework.context.Context;
032import org.apache.avalon.framework.context.ContextException;
033import org.apache.avalon.framework.context.Contextualizable;
034import org.apache.avalon.framework.logger.LogEnabled;
035import org.apache.avalon.framework.logger.Logger;
036import org.apache.avalon.framework.service.ServiceException;
037import org.apache.avalon.framework.service.ServiceManager;
038import org.apache.avalon.framework.service.Serviceable;
039import org.apache.cocoon.environment.Request;
040import org.apache.commons.lang.StringUtils;
041
042import org.ametys.runtime.config.Config;
043
044/**
045 * Alerts scheduler: launches a cron which sends the alerts each night.
046 */
047public class AlertScheduler extends TimerTask implements Initializable, LogEnabled, Serviceable, Disposable, Contextualizable, Configurable, Component
048{
049    /** The Avalon role */
050    public static final String ROLE = AlertScheduler.class.getName();
051    
052    /** The service manager. */
053    protected ServiceManager _manager;
054    
055    /** The component configuration. */
056    protected Configuration _configuration;
057    
058    /** The avalon context. */
059    protected Context _context;
060    
061    /** The logger. */
062    protected Logger _logger;
063    
064    /** The timer. */
065    protected Timer _timer;
066    
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        _manager = manager;
071    }
072    
073    @Override
074    public void contextualize(Context context) throws ContextException
075    {
076        _context = context;
077    }
078    
079    @Override
080    public void configure(Configuration configuration) throws ConfigurationException
081    {
082        _configuration = configuration;
083    }
084    
085    @Override
086    public void enableLogging(Logger logger)
087    {
088        _logger = logger;
089    }
090    
091    @Override
092    public void initialize() throws Exception
093    {
094        if (!Config.getInstance().getValueAsBoolean("remind.content.enabled"))
095        {
096            return;
097        }
098         
099        if (_logger.isDebugEnabled())
100        {
101            _logger.debug("Initializing the alert scheduler...");
102        }
103        
104        // Schedule a timer to run each night.
105        String hourStr = Config.getInstance().getValueAsString("alerts.scheduler.hour");
106        int hour = 0;
107        int minute = 0;
108        if (StringUtils.isNotEmpty(hourStr) && hourStr.indexOf(':') > 0)
109        {
110            String[] hourArray = StringUtils.split(hourStr, ':');
111            hour = Integer.parseInt(hourArray[0]);
112            minute = Integer.parseInt(hourArray[1]);
113        }
114        
115        GregorianCalendar calendar = new GregorianCalendar();
116        calendar.set(Calendar.AM_PM, hour < 12 ? Calendar.AM : Calendar.PM);
117        calendar.set(Calendar.HOUR, hour % 12);
118        calendar.set(Calendar.MINUTE, minute);
119        calendar.set(Calendar.SECOND, 0);
120        calendar.set(Calendar.MILLISECOND, 0);
121        
122        // Each day.
123        long period = 86400000;
124        Date firstTime = calendar.getTime();
125
126        Date now = new Date();
127
128        // If the given time today is past, schedule for tomorrow.
129        if (firstTime.compareTo(now) < 0)
130        {
131            calendar.add(Calendar.DAY_OF_MONTH, 1);
132            firstTime = calendar.getTime();
133        }
134        
135        if (_logger.isInfoEnabled())
136        {
137            _logger.info("Alerts scheduler: the alerts engine will run each day, starting " + firstTime.toString());
138        }
139        
140        _timer = new Timer("AlertsScheduler", true);
141        _timer.schedule(this, firstTime, period);
142    }
143    
144    @Override
145    public void run()
146    {
147        AlertEngine alertEngine = new AlertEngine();
148        
149        try
150        {
151            // Initialize and configure the engine.
152            alertEngine.initialize(_manager, _context);
153            alertEngine.configure(_configuration);
154        }
155        catch (Exception e)
156        {
157            throw new RuntimeException("Unable to initialize the alerts engine", e);
158        }
159        
160        // The thread will be started as daemon, as the scheduler is marked daemon itself.
161        new Thread(alertEngine, "AlertEngine").start();
162    }
163    
164    /**
165     * Run the scheduler to send instant alerts on contents
166     * @param contentIds The ids of concerned contents
167     * @param message the message to send to authorized users
168     */
169    public void sendInstantAlerts (List<String> contentIds, String message)
170    {
171        AlertEngine alertEngine = new AlertEngine(contentIds, message);
172        
173        try
174        {
175            // Initialize and configure the engine.
176            alertEngine.initialize(_manager, _context);
177            alertEngine.configure(_configuration);
178        }
179        catch (Exception e)
180        {
181            throw new RuntimeException("Unable to initialize the alerts engine", e);
182        }
183        
184        // The thread will be started as daemon, as the scheduler is marked daemon itself.
185        new Thread(alertEngine, "AlertEngine").start();
186    }
187    
188    /**
189     * Get the request URI.
190     * @param request the request object.
191     * @return the full request URI.
192     */
193    protected String _getRequestURI(Request request)
194    {
195        StringBuilder sb = new StringBuilder();
196        sb.append(request.getScheme());
197        sb.append("://");
198        sb.append(request.getServerName());
199        
200        // Construire une uri sans :80 en http et sans :443 en https
201        if (request.isSecure())
202        {
203            if (request.getServerPort() != 443)
204            {
205                sb.append(":");
206                sb.append(request.getServerPort());
207            }
208        }
209        else
210        {
211            if (request.getServerPort() != 80)
212            {
213                sb.append(":");
214                sb.append(request.getServerPort());
215            }
216        }
217        
218        sb.append(request.getContextPath());
219        
220        return sb.toString();
221    }
222        
223    @Override
224    public void dispose()
225    {
226        _context = null;
227        _logger = null;
228        _manager = null;
229        _configuration = null;
230        
231        cancel();
232        if (_timer != null)
233        {
234            _timer.cancel();
235            _timer = null;
236        }
237    }
238    
239}