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