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.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.activity.Initializable;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.quartz.JobKey;
028import org.quartz.SchedulerException;
029
030import org.ametys.core.observation.Event;
031import org.ametys.core.observation.ObservationManager;
032import org.ametys.core.schedule.Runnable;
033import org.ametys.core.ui.Callable;
034import org.ametys.core.ui.ClientSideElement;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.core.schedule.Scheduler;
037import org.ametys.runtime.model.type.ElementType;
038import org.ametys.runtime.model.type.ModelItemTypeConstants;
039import org.ametys.web.ObservationConstants;
040import org.ametys.web.data.type.ModelItemTypeExtensionPoint;
041import org.ametys.web.publication.PublishPageRunnable;
042import org.ametys.web.publication.UnpublishPageRunnable;
043import org.ametys.web.repository.page.ModifiablePage;
044import org.ametys.web.repository.page.Page;
045import org.ametys.web.repository.page.jcr.DefaultPage;
046import org.ametys.web.rights.PageRightAssignmentContext;
047
048/**
049 * This {@link ClientSideElement} creates a button representing the schedule publication status of a page.
050 *
051 */
052public class ScheduledPageClientSideElement extends AbstractPageClientSideElement implements Initializable
053{
054    private ObservationManager _observationManager;
055    private Scheduler _scheduler;
056    private ModelItemTypeExtensionPoint _pageDataTypeExtensionPoint;
057    private ElementType<ZonedDateTime> _dateElementType;
058
059    @Override
060    public void service(ServiceManager smanager) throws ServiceException
061    {
062        super.service(smanager);
063        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
064        _scheduler = (Scheduler) smanager.lookup(Scheduler.ROLE);
065        _pageDataTypeExtensionPoint = (ModelItemTypeExtensionPoint) smanager.lookup(ModelItemTypeExtensionPoint.ROLE_PAGE_DATA);
066    }
067
068    @SuppressWarnings("unchecked")
069    public void initialize() throws Exception
070    {
071        _dateElementType = (ElementType<ZonedDateTime>) _pageDataTypeExtensionPoint.getExtension(ModelItemTypeConstants.DATETIME_TYPE_ID);
072    }
073    
074    /**
075     * Get the publication dates of a page
076     * @param pageId the id of the page
077     * @return a map
078     */
079    @Callable (rights = Callable.READ_ACCESS, rightContext = PageRightAssignmentContext.ID, paramIndex = 0)
080    public Map<String, Object> getPublicationDates(String pageId)
081    {
082        Map<String, Object> dates = new HashMap<> ();
083        
084        Page page = _resolver.resolveById(pageId);
085        
086        ZonedDateTime startDate = page.getValue(DefaultPage.METADATA_PUBLICATION_START_DATE);
087        if (startDate != null)
088        {
089            dates.put("startDate", _dateElementType.toString(startDate));
090        }
091        
092        ZonedDateTime endDate = page.getValue(DefaultPage.METADATA_PUBLICATION_END_DATE);
093        if (endDate != null)
094        {
095            dates.put("endDate", _dateElementType.toString(endDate));
096        }
097        
098        return dates;
099    }
100    
101    /**
102     * Set date of publication of pages
103     * @param pageIds The ids of pages to update
104     * @param startDateAsStr The start date. Can be null.
105     * @param endDateAsStr The end date. Can be null.
106     * @return true if operation has succeeded.
107     */
108    @Callable (rights = Callable.SKIP_BUILTIN_CHECK)
109    public Map<String, Object> setPublicationDate (List<String> pageIds, String startDateAsStr, String endDateAsStr)
110    {
111        List<String> allRightIds = new ArrayList<>();
112        List<Map<String, Object>> noRightPages = new ArrayList<>();
113        List<Map<String, Object>> errorPages = new ArrayList<>();
114        
115        ZonedDateTime startDate = startDateAsStr != null ? _dateElementType.castValue(startDateAsStr) : null;
116        ZonedDateTime endDate = endDateAsStr != null ? _dateElementType.castValue(endDateAsStr) : null;
117        
118        for (String id : pageIds)
119        {
120            ModifiablePage page = _resolver.resolveById(id);
121            UserIdentity userIdentity = _currentUserProvider.getUser();
122            
123            try
124            {
125                if (!hasRight(page))
126                {
127                    noRightPages.add(Map.of("id", id, "title", page.getTitle()));
128                }
129                else
130                {
131                    _scheduleStartDate(page, userIdentity, startDate);
132                    _scheduleEndDate(page, userIdentity, endDate);
133                    
134                    page.saveChanges();
135                    allRightIds.add(id);
136                    
137                    // Notify listeners in case the page has to be publish or unpublish right now
138                    Map<String, Object> eventParams = new HashMap<>();
139                    eventParams.put(ObservationConstants.ARGS_PAGE, page);
140                    _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_CHANGED, _currentUserProvider.getUser(), eventParams));
141                    _observationManager.notify(new Event(ObservationConstants.EVENT_PAGE_UPDATED, _currentUserProvider.getUser(), eventParams));
142                }
143            }
144            catch (SchedulerException e)
145            {
146                errorPages.add(Map.of("id", id, "title", page.getTitle()));
147                getLogger().error("An error occured when trying to schedule the publishing of the page " + id, e);
148            }
149        }
150        
151        return Map.of("allright-pages", allRightIds, "noright-pages", noRightPages, "error-pages", errorPages);
152    }
153    
154    private void _scheduleStartDate (ModifiablePage page, UserIdentity userIdentity, ZonedDateTime startDate) throws SchedulerException
155    {
156        // Publish job
157        Runnable publishRunnable = new PublishPageRunnable(page.getId(), page.getTitle(), userIdentity, startDate);
158        JobKey jobKey = new JobKey(publishRunnable.getId(), Scheduler.JOB_GROUP);
159        
160        if (_scheduler.getScheduler().checkExists(jobKey))
161        {
162            // Remove any existing corresponding job
163            getLogger().debug("Removing the existing job for publishing {} ({})", page.getTitle(), page.getId());
164            _scheduler.getScheduler().deleteJob(jobKey);
165        }
166        
167        if (startDate != null)
168        {
169            page.setValue(DefaultPage.METADATA_PUBLICATION_START_DATE, startDate);
170            
171            if (startDate.isAfter(ZonedDateTime.now()))
172            {
173                // No necessary to run job if date is in the past
174                getLogger().debug("Creating a job for publishing {} ({}) on {}", page.getTitle(), page.getId(), startDate);
175                _scheduler.scheduleJob(publishRunnable);
176            }
177            else
178            {
179                getLogger().debug("No job needed for publishing {} ({}) on {}", page.getTitle(), page.getId(), startDate);
180            }
181        }
182        else
183        {
184            page.removeValue(DefaultPage.METADATA_PUBLICATION_START_DATE);
185            getLogger().debug("No schedule for publishing was required for {} ({})", page.getTitle(), page.getId());
186        }
187    }
188    
189    private void _scheduleEndDate (ModifiablePage page, UserIdentity userIdentity, ZonedDateTime endDate) throws SchedulerException
190    {
191        // Unpublish job
192        Runnable unpublishRunnable = new UnpublishPageRunnable(page.getId(), page.getTitle(), userIdentity, endDate);
193        JobKey jobKey = new JobKey(unpublishRunnable.getId(), Scheduler.JOB_GROUP);
194        
195        if (_scheduler.getScheduler().checkExists(jobKey))
196        {
197            // Remove any existing corresponding job
198            getLogger().debug("Removing the existing job for unpublishing {} ({})", page.getTitle(), page.getId());
199            _scheduler.getScheduler().deleteJob(jobKey);
200        }
201        
202        if (endDate != null)
203        {
204            page.setValue(DefaultPage.METADATA_PUBLICATION_END_DATE, endDate);
205            
206            if (endDate.isAfter(ZonedDateTime.now()))
207            {
208                // No necessary to run job if date is in the past
209                getLogger().debug("Creating a job for unpublishing {} ({}) on {}", page.getTitle(), page.getId(), endDate);
210                _scheduler.scheduleJob(unpublishRunnable);
211            }
212            else
213            {
214                getLogger().debug("No job needed for unpublishing {} ({}) on {}", page.getTitle(), page.getId(), endDate);
215            }
216        }
217        else
218        {
219            page.removeValue(DefaultPage.METADATA_PUBLICATION_END_DATE);
220            getLogger().debug("No schedule for unpublishing was required for {} ({})", page.getTitle(), page.getId());
221        }
222    }
223}