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.clientsideelement;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang3.StringUtils;
029
030import org.ametys.cms.ObservationConstants;
031import org.ametys.cms.alerts.AlertsConstants;
032import org.ametys.cms.content.archive.ArchiveConstants;
033import org.ametys.cms.repository.Content;
034import org.ametys.core.observation.Event;
035import org.ametys.core.observation.ObservationManager;
036import org.ametys.core.ui.Callable;
037import org.ametys.core.ui.StaticClientSideElement;
038import org.ametys.core.util.DateUtils;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
041import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject;
042import org.ametys.runtime.config.Config;
043import org.ametys.runtime.i18n.I18nizableText;
044
045/**
046 * This element creates a ribbon button to schedule the archiving of a content
047 */
048public class ScheduleArchivingClientSideElement extends StaticClientSideElement
049{
050    /** I18n error key : content type error */
051    private static final String __I18N_KEY_CONTENT_TYPE_ERROR = "PLUGINS_CMS_ARCHIVE_SCHEDULE_CONTENT_TYPE_ERROR";
052
053    /** I18n error key : unexpected error */
054    private static final String __I18N_KEY_UNEXPECTED_ERROR = "PLUGINS_CMS_ARCHIVE_SCHEDULE_UNEXPECTED_ERROR";
055    
056    private ObservationManager _observationManager;
057    private AmetysObjectResolver _resolver;
058
059    @Override
060    public void service(ServiceManager manager) throws ServiceException
061    {
062        super.service(manager);
063        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
064        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
065    }
066    
067    /**
068     * Set or remove the <code>ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE</code> on a content.
069     * @param contentIds The ids of contents
070     * @param scheduledDateAsStr The schedule date as string
071     * @return the successful and failed contents
072     */
073    @Callable
074    public Map<String, Object> setScheduledArchivingDate (List<String> contentIds, String scheduledDateAsStr)
075    {
076        Map<String, Object> results = new HashMap<>();
077        
078        // Error list
079        List<I18nizableText> failedContents = new ArrayList<>();
080
081        // Success list, containing the contentId that have been successfully scheduled
082        List<String> successContents = new ArrayList<>();
083
084        ZonedDateTime scheduledDate = DateUtils.parseZonedDateTime(scheduledDateAsStr);
085        
086        if (StringUtils.isNotEmpty(scheduledDateAsStr))
087        {
088            if (scheduledDate == null)
089            {
090                getLogger().error("Cannot cast value '" + scheduledDateAsStr + "' into type 'date'");
091                throw new IllegalArgumentException("Invalid format for date value '" + scheduledDateAsStr + "'");
092            }
093
094            // Date must be set at least after tomorrow
095            /*if (scheduledDate.compareTo(new DateMidnight().plusDays(1).toDate()) < 0)
096            {
097                getLogger().error("The scheduled date '" + scheduledDate + "' cannot be set before the current date");
098                throw new IllegalArgumentException("The scheduled date '" + scheduledDate + "' cannot be set before the current date");
099            }*/
100        }
101        
102        // For each content
103        for (String contentId : contentIds)
104        {
105            try
106            {
107                Content content = _resolver.resolveById(contentId);
108
109                if (content instanceof ModifiableDataAwareVersionableAmetysObject)
110                {
111                    ModifiableDataAwareVersionableAmetysObject uContent = (ModifiableDataAwareVersionableAmetysObject) content;
112                    ModifiableModelLessDataHolder dataHolder = uContent.getUnversionedDataHolder();
113
114                    // Remove META_ARCHIVE_SCHEDULED_DATE and
115                    // SCHEDULED_ARCHIVING_REMINDER_LAST_DATE if date is empty.
116                    if (StringUtils.isEmpty(scheduledDateAsStr))
117                    {
118                        dataHolder.removeValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
119                        if (getLogger().isInfoEnabled())
120                        {
121                            getLogger().info("Content with id : '" + uContent.getId() + "' does not have a scheduled archiving date anymore.");
122                        }
123
124                        dataHolder.removeValue(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE);
125                    }
126                    else
127                    {
128                        dataHolder.setValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE, scheduledDate);
129                    }
130
131                    if (uContent.needsSave())
132                    {
133                        uContent.saveChanges();
134
135                        // report success
136                        successContents.add(uContent.getId());
137
138                        // Notify observer
139                        Map<String, Object> eventParams = new HashMap<>();
140                        eventParams.put(ObservationConstants.ARGS_CONTENT, content);
141                        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId);
142
143                        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams));
144                    }
145                }
146                else
147                {
148                    getLogger().error("Unable to set a scheduled archiving for the content : '" + content.getId() + "'. It is not a ModifiableMetadataAwareVersionableAmetysObject.");
149
150                    // report error
151                    List<String> i18nParams = new ArrayList<>();
152                    i18nParams.add(content.getTitle(null));
153                    failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_CONTENT_TYPE_ERROR, i18nParams));
154
155                }
156            }
157            catch (Exception e)
158            {
159                getLogger().error("Unexpected exception while trying to schedule an archiving for the content : '" + contentId + "'.");
160
161                // report error
162                List<String> i18nParams = new ArrayList<>();
163                i18nParams.add(contentId);
164                failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_UNEXPECTED_ERROR, i18nParams));
165            }
166        }
167
168        results.put("error", failedContents);
169        results.put("success", successContents);
170        
171        return results;
172    }
173
174    /**
175     * Gets the scheduled archiving dates of one or several contents
176     * @param contentIds the ids of the contents
177     * @return result the server's response in JSON
178     */
179    @Callable
180    public Map<String, Object> getScheduledArchivingDate(List<String> contentIds)
181    {
182        Map<String, Object> results = new HashMap<> ();
183        Set<ZonedDateTime> dates = new HashSet<>();
184    
185        // Retrieves the date for each content
186        for (String contentId : contentIds)
187        {
188            Content content = _resolver.resolveById(contentId);
189    
190            if (content instanceof ModifiableDataAwareVersionableAmetysObject)
191            {
192                ZonedDateTime date = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder().getValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
193                dates.add(date);
194            }
195        }
196    
197        // Retrieves the closest date from current time.
198        dates.remove(null);
199        ZonedDateTime scheduledDate = getDateNearestToCurrent(dates);
200    
201        // Generate the JSON
202        Map<String, Object> content = new HashMap<> ();
203        if (scheduledDate != null)
204        {
205            content.put("date", DateUtils.zonedDateTimeToString(scheduledDate));
206        }
207        results.put("content", content);
208        
209        return results;
210    }
211    
212    private ZonedDateTime getDateNearestToCurrent(Set<ZonedDateTime> dates)
213    {
214        ZonedDateTime returnDate = null;
215        ZonedDateTime currentDate = ZonedDateTime.now();
216
217        for (ZonedDateTime date : dates)
218        {
219            if (date.compareTo(currentDate) >= 0 && (returnDate == null || date.compareTo(returnDate) < 0))
220            {
221                returnDate = date;
222            }
223        }
224        return returnDate;
225    }
226    
227    @Override
228    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
229    {
230        boolean enabled = Config.getInstance().getValue("archive.scheduler.enabled");
231        if (enabled)
232        {
233            return super.getScripts(ignoreRights, contextParameters);
234        }
235        
236        return new ArrayList<>();
237    }
238}