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