001/*
002 *  Copyright 2015 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.clientsideelement;
017
018import java.text.DateFormat;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Date;
022import java.util.GregorianCalendar;
023import java.util.HashMap;
024import java.util.List;
025import java.util.Locale;
026import java.util.Map;
027
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.i18n.I18nUtils;
035import org.quartz.JobKey;
036import org.quartz.SchedulerException;
037
038import org.ametys.core.observation.Event;
039import org.ametys.core.observation.ObservationManager;
040import org.ametys.core.schedule.Runnable;
041import org.ametys.core.ui.Callable;
042import org.ametys.core.ui.ClientSideElement;
043import org.ametys.plugins.core.schedule.Scheduler;
044import org.ametys.runtime.i18n.I18nizableText;
045import org.ametys.runtime.parameter.ParameterHelper;
046import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
047import org.ametys.web.ObservationConstants;
048import org.ametys.web.publication.PublishPageRunnable;
049import org.ametys.web.publication.UnpublishPageRunnable;
050import org.ametys.web.repository.page.ModifiablePage;
051import org.ametys.web.repository.page.Page;
052import org.ametys.web.repository.page.jcr.DefaultPage;
053
054/**
055 * This {@link ClientSideElement} creates a button representing the schedule publication status of a page.
056 *
057 */
058public class ScheduledPageClientSideElement extends AbstractPageClientSideElement implements Contextualizable
059{
060    private Context _context;
061    private ObservationManager _observationManager;
062    private Scheduler _scheduler;
063
064    @Override
065    public void contextualize(Context context) throws ContextException
066    {
067        _context = context;
068    }
069    
070    @Override
071    public void service(ServiceManager smanager) throws ServiceException
072    {
073        super.service(smanager);
074        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
075        _scheduler = (Scheduler) smanager.lookup(Scheduler.ROLE);
076    }
077    
078    /**
079     * Get the publication dates of a page
080     * @param pageId the id of the page
081     * @return a map
082     */
083    @Callable
084    public Map<String, Object> getPublicationDates(String pageId)
085    {
086        Map<String, Object> dates = new HashMap<> ();
087        
088        Page page = _resolver.resolveById(pageId);
089        
090        Date startDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_START_DATE, null);
091        if (startDate != null)
092        {
093            dates.put("startDate", ParameterHelper.valueToString(startDate));
094        }
095        
096        Date endDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_END_DATE, null);
097        if (endDate != null)
098        {
099            dates.put("endDate", ParameterHelper.valueToString(endDate));
100        }
101        
102        return dates;
103    }
104    
105    /**
106     * Set date of publication of pages
107     * @param pageIds The ids of pages to update
108     * @param startDateAsStr The start date. Can be null.
109     * @param endDateAsStr The end date. Can be null.
110     * @return true if operation has succeeded.
111     */
112    @Callable
113    public boolean setPublicationDate (List<String> pageIds, String startDateAsStr, String endDateAsStr)
114    {
115        Date startDate = startDateAsStr != null ? (Date) ParameterHelper.castValue(startDateAsStr, ParameterType.DATE) : null;
116        Date endDate = endDateAsStr != null ? (Date) ParameterHelper.castValue(endDateAsStr, ParameterType.DATE) : null;
117        
118        for (String id : pageIds)
119        {
120            ModifiablePage page = _resolver.resolveById(id);
121            
122            try
123            {
124                _scheduleStartDate(page, startDate);
125                _scheduleEndDate(page, endDate);
126                
127                page.saveChanges();
128                
129                // Notify listeners in case the page has to be publish or unpublish right now
130                Map<String, Object> eventParams = new HashMap<>();
131                eventParams.put(ObservationConstants.ARGS_PAGE, page);
132                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
133                
134                _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
135            }
136            catch (SchedulerException e)
137            {
138                if (getLogger().isErrorEnabled())
139                {
140                    getLogger().error("An error occured when trying to schedule the publishing of the page " + id, e);
141                }
142            }
143        }
144        
145        return true;
146    }
147    
148    private void _scheduleStartDate (ModifiablePage page, Date startDate) throws SchedulerException
149    {
150        // Publish job
151        Runnable publishRunnable = new PublishPageRunnable(page.getId(), page.getTitle(), startDate);
152        JobKey jobKey = new JobKey(publishRunnable.getId(), Scheduler.JOB_GROUP);
153        
154        if (_scheduler.getScheduler().checkExists(jobKey))
155        {
156            // If date is in past, unecessary to run the job
157            _scheduler.getScheduler().deleteJob(jobKey);
158        }
159        
160        if (startDate != null)
161        {
162            page.getMetadataHolder().setMetadata(DefaultPage.METADATA_PUBLICATION_START_DATE, startDate);
163            
164            if (startDate.after(new Date()))
165            {
166                // No necessary to run job if date is in the past
167                _scheduler.scheduleJob(publishRunnable);
168            }
169        }
170        else if (page.getMetadataHolder().hasMetadata(DefaultPage.METADATA_PUBLICATION_START_DATE))
171        {
172            page.getMetadataHolder().removeMetadata(DefaultPage.METADATA_PUBLICATION_START_DATE);
173        }
174    }
175    
176    private void _scheduleEndDate (ModifiablePage page, Date endDate) throws SchedulerException
177    {
178        // Unpublish job
179        Runnable unpublishRunnable = new UnpublishPageRunnable(page.getId(), page.getTitle(), endDate);
180        JobKey jobKey = new JobKey(unpublishRunnable.getId(), Scheduler.JOB_GROUP);
181        
182        if (_scheduler.getScheduler().checkExists(jobKey))
183        {
184            // A end publication was already scheduled, delete the job
185            _scheduler.getScheduler().deleteJob(jobKey);
186        }
187        
188        if (endDate != null)
189        {
190            page.getMetadataHolder().setMetadata(DefaultPage.METADATA_PUBLICATION_END_DATE, endDate);
191            
192            if (endDate.after(new Date()))
193            {
194                // No necessary to run job if date is in the past
195                _scheduler.scheduleJob(unpublishRunnable);
196            }
197        }
198        else if (page.getMetadataHolder().hasMetadata(DefaultPage.METADATA_PUBLICATION_END_DATE))
199        {
200            page.getMetadataHolder().removeMetadata(DefaultPage.METADATA_PUBLICATION_END_DATE);
201        }
202    }
203    
204    /**
205     * Get the page status
206     * @param pageIds The page ids
207     * @return the page status
208     */
209    @SuppressWarnings("unchecked")
210    @Callable
211    public Map<String, Object> getStatus (List<String> pageIds)
212    {
213        Map<String, Object> results = new HashMap<>();
214        
215        results.put("allright-pages", new ArrayList<Map<String, Object>>());
216        results.put("noright-pages", new ArrayList<Map<String, Object>>());
217        results.put("nomodifiable-pages", new ArrayList<Map<String, Object>>());
218        results.put("scheduled-pages", new ArrayList<Map<String, Object>>());
219        results.put("scheduled-valid-pages", new ArrayList<Map<String, Object>>());
220        results.put("scheduled-forthcoming-pages", new ArrayList<Map<String, Object>>());
221        results.put("scheduled-outofdate-pages", new ArrayList<Map<String, Object>>());
222        
223        Calendar now = new GregorianCalendar();
224        now.set(Calendar.HOUR_OF_DAY, 0);
225        now.set(Calendar.MINUTE, 0);
226        now.set(Calendar.SECOND, 0);
227        now.set(Calendar.MILLISECOND, 0);
228        
229        for (String pageId : pageIds)
230        {
231            Page page = _resolver.resolveById(pageId);
232            Map<String, Object> pageParams = getPageDefaultParameters(page);
233            
234            if (!(page instanceof ModifiablePage))
235            {
236                pageParams.put("description", getNoModifiablePageDescription(page));
237                List<Map<String, Object>> nomodifiablePages = (List<Map<String, Object>>) results.get("nomodifiable-pages");
238                nomodifiablePages.add(pageParams);
239            }
240            else if (!hasRight(page))
241            {
242                pageParams.put("description", getNoRightPageDescription(page));
243
244                List<Map<String, Object>> norightPages = (List<Map<String, Object>>) results.get("noright-pages");
245                norightPages.add(pageParams);
246            }
247            else
248            {
249                Date startDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_START_DATE, null);
250                Date endDate = page.getMetadataHolder().getDate(DefaultPage.METADATA_PUBLICATION_END_DATE, null);
251                
252                boolean isScheduled = startDate != null || endDate != null;
253                if (isScheduled)
254                {
255                    Map objectModel = ContextHelper.getObjectModel(_context);
256                    Locale locale = I18nUtils.findLocale(objectModel, "locale", null, Locale.getDefault(), true);
257                    
258                    if (_synchronizeComponent.isDateValid(page))
259                    {
260                        pageParams.put("description", _getDateValidDescription(page));
261                        
262                        List<Map<String, Object>> validPages = (List<Map<String, Object>>) results.get("scheduled-valid-pages");
263                        validPages.add(pageParams);
264                    }
265                    else if (endDate != null && endDate.before(now.getTime()))
266                    {
267                        pageParams.put("description", _getOutOfDateDescription(page, endDate, locale));
268                        
269                        List<Map<String, Object>> oodPages = (List<Map<String, Object>>) results.get("scheduled-outofdate-pages");
270                        oodPages.add(pageParams);
271                    }
272                    else if (startDate != null && startDate.after(now.getTime()))
273                    {
274                        pageParams.put("description", _getForthComingDescription(page, startDate, endDate, locale));
275                        
276                        List<Map<String, Object>> forthcomingPages = (List<Map<String, Object>>) results.get("scheduled-forthcoming-pages");
277                        forthcomingPages.add(pageParams);
278                    }
279                    
280                    List<Map<String, Object>> scheduledPages = (List<Map<String, Object>>) results.get("scheduled-pages");
281                    scheduledPages.add(pageParams);
282                }
283                
284                List<Map<String, Object>> allrightPages = (List<Map<String, Object>>) results.get("allright-pages");
285                allrightPages.add(pageParams);
286            }
287        }
288        
289        return results;
290    }
291    
292    private I18nizableText _getDateValidDescription (Page page)
293    {
294        List<String> i18nParameters = new ArrayList<>();
295        i18nParameters.add(page.getTitle());
296        
297        I18nizableText ed = (I18nizableText) this._script.getParameters().get("scheduled-page-valid-description");
298        return new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
299    }
300    
301    private I18nizableText _getOutOfDateDescription (Page page, Date endDate, Locale locale)
302    {
303        List<String> i18nParameters = new ArrayList<>();
304        i18nParameters.add(page.getTitle());
305        
306        DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, locale);
307        i18nParameters.add(endDate != null ? df.format(endDate) : "-");
308        
309        I18nizableText ed = (I18nizableText) this._script.getParameters().get("scheduled-page-outofdate-description");
310        return new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
311    }
312    
313    private I18nizableText _getForthComingDescription (Page page, Date startDate, Date endDate, Locale locale)
314    {
315        List<String> i18nParameters = new ArrayList<>();
316        i18nParameters.add(page.getTitle());
317        
318        DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, locale);
319        i18nParameters.add(startDate != null ? df.format(startDate) : "-");
320        i18nParameters.add(endDate != null ? df.format(endDate) : "-");
321        
322        I18nizableText ed = (I18nizableText) this._script.getParameters().get("scheduled-page-forthcoming-description");
323        return new I18nizableText(ed.getCatalogue(), ed.getKey(), i18nParameters);
324    }
325    
326}