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        boolean enabled = Config.getInstance().getValue("remind.content.enabled");
095        if (!enabled)
096        {
097            return;
098        }
099         
100        if (_logger.isDebugEnabled())
101        {
102            _logger.debug("Initializing the alert scheduler...");
103        }
104        
105        // Schedule a timer to run each night.
106        String hourStr = Config.getInstance().getValue("alerts.scheduler.hour");
107        int hour = 0;
108        int minute = 0;
109        if (StringUtils.isNotEmpty(hourStr) && hourStr.indexOf(':') > 0)
110        {
111            String[] hourArray = StringUtils.split(hourStr, ':');
112            hour = Integer.parseInt(hourArray[0]);
113            minute = Integer.parseInt(hourArray[1]);
114        }
115        
116        GregorianCalendar calendar = new GregorianCalendar();
117        calendar.set(Calendar.AM_PM, hour < 12 ? Calendar.AM : Calendar.PM);
118        calendar.set(Calendar.HOUR, hour % 12);
119        calendar.set(Calendar.MINUTE, minute);
120        calendar.set(Calendar.SECOND, 0);
121        calendar.set(Calendar.MILLISECOND, 0);
122        
123        // Each day.
124        long period = 86400000;
125        Date firstTime = calendar.getTime();
126
127        Date now = new Date();
128
129        // If the given time today is past, schedule for tomorrow.
130        if (firstTime.compareTo(now) < 0)
131        {
132            calendar.add(Calendar.DAY_OF_MONTH, 1);
133            firstTime = calendar.getTime();
134        }
135        
136        if (_logger.isInfoEnabled())
137        {
138            _logger.info("Alerts scheduler: the alerts engine will run each day, starting " + firstTime.toString());
139        }
140        
141        _timer = new Timer("AlertsScheduler", true);
142        _timer.schedule(this, firstTime, period);
143    }
144    
145    @Override
146    public void run()
147    {
148        AlertEngine alertEngine = new AlertEngine();
149        
150        try
151        {
152            // Initialize and configure the engine.
153            alertEngine.initialize(_manager, _context);
154            alertEngine.configure(_configuration);
155        }
156        catch (Exception e)
157        {
158            throw new RuntimeException("Unable to initialize the alerts engine", e);
159        }
160        
161        // The thread will be started as daemon, as the scheduler is marked daemon itself.
162        new Thread(alertEngine, "AlertEngine").start();
163    }
164    
165    /**
166     * Run the scheduler to send instant alerts on contents
167     * @param contentIds The ids of concerned contents
168     * @param message the message to send to authorized users
169     */
170    public void sendInstantAlerts (List<String> contentIds, String message)
171    {
172        AlertEngine alertEngine = new AlertEngine(contentIds, message);
173        
174        try
175        {
176            // Initialize and configure the engine.
177            alertEngine.initialize(_manager, _context);
178            alertEngine.configure(_configuration);
179        }
180        catch (Exception e)
181        {
182            throw new RuntimeException("Unable to initialize the alerts engine", e);
183        }
184        
185        // The thread will be started as daemon, as the scheduler is marked daemon itself.
186        new Thread(alertEngine, "AlertEngine").start();
187    }
188    
189    /**
190     * Get the request URI.
191     * @param request the request object.
192     * @return the full request URI.
193     */
194    protected String _getRequestURI(Request request)
195    {
196        StringBuilder sb = new StringBuilder();
197        sb.append(request.getScheme());
198        sb.append("://");
199        sb.append(request.getServerName());
200        
201        // Construire une uri sans :80 en http et sans :443 en https
202        if (request.isSecure())
203        {
204            if (request.getServerPort() != 443)
205            {
206                sb.append(":");
207                sb.append(request.getServerPort());
208            }
209        }
210        else
211        {
212            if (request.getServerPort() != 80)
213            {
214                sb.append(":");
215                sb.append(request.getServerPort());
216            }
217        }
218        
219        sb.append(request.getContextPath());
220        
221        return sb.toString();
222    }
223        
224    @Override
225    public void dispose()
226    {
227        _context = null;
228        _logger = null;
229        _manager = null;
230        _configuration = null;
231        
232        cancel();
233        if (_timer != null)
234        {
235            _timer.cancel();
236            _timer = null;
237        }
238    }
239    
240}