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                        if (dataHolder.hasValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE))
119                        {
120                            dataHolder.removeValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
121
122                            if (getLogger().isInfoEnabled())
123                            {
124                                getLogger().info("Content with id : '" + uContent.getId() + "' does not have a scheduled archiving date anymore.");
125                            }
126                        }
127
128                        if (dataHolder.hasValue(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE))
129                        {
130                            dataHolder.removeValue(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE);
131                        }
132                    }
133                    else
134                    {
135                        dataHolder.setValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE, scheduledDate);
136                    }
137
138                    if (uContent.needsSave())
139                    {
140                        uContent.saveChanges();
141
142                        // report success
143                        successContents.add(uContent.getId());
144
145                        // Notify observer
146                        Map<String, Object> eventParams = new HashMap<>();
147                        eventParams.put(ObservationConstants.ARGS_CONTENT, content);
148                        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId);
149
150                        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams));
151                    }
152                }
153                else
154                {
155                    getLogger().error("Unable to set a scheduled archiving for the content : '" + content.getId() + "'. It is not a ModifiableMetadataAwareVersionableAmetysObject.");
156
157                    // report error
158                    List<String> i18nParams = new ArrayList<>();
159                    i18nParams.add(content.getTitle(null));
160                    failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_CONTENT_TYPE_ERROR, i18nParams));
161
162                }
163            }
164            catch (Exception e)
165            {
166                getLogger().error("Unexpected exception while trying to schedule an archiving for the content : '" + contentId + "'.");
167
168                // report error
169                List<String> i18nParams = new ArrayList<>();
170                i18nParams.add(contentId);
171                failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_UNEXPECTED_ERROR, i18nParams));
172            }
173        }
174
175        results.put("error", failedContents);
176        results.put("success", successContents);
177        
178        return results;
179    }
180
181    /**
182     * Gets the scheduled archiving dates of one or several contents
183     * @param contentIds the ids of the contents
184     * @return result the server's response in JSON
185     */
186    @Callable
187    public Map<String, Object> getScheduledArchivingDate(List<String> contentIds)
188    {
189        Map<String, Object> results = new HashMap<> ();
190        Set<ZonedDateTime> dates = new HashSet<>();
191    
192        // Retrieves the date for each content
193        for (String contentId : contentIds)
194        {
195            Content content = _resolver.resolveById(contentId);
196    
197            if (content instanceof ModifiableDataAwareVersionableAmetysObject)
198            {
199                ZonedDateTime date = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder().getValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
200                dates.add(date);
201            }
202        }
203    
204        // Retrieves the closest date from current time.
205        dates.remove(null);
206        ZonedDateTime scheduledDate = getDateNearestToCurrent(dates);
207    
208        // Generate the JSON
209        Map<String, Object> content = new HashMap<> ();
210        if (scheduledDate != null)
211        {
212            content.put("date", DateUtils.zonedDateTimeToString(scheduledDate));
213        }
214        results.put("content", content);
215        
216        return results;
217    }
218    
219    private ZonedDateTime getDateNearestToCurrent(Set<ZonedDateTime> dates)
220    {
221        ZonedDateTime returnDate = null;
222        ZonedDateTime currentDate = ZonedDateTime.now();
223
224        for (ZonedDateTime date : dates)
225        {
226            if (date.compareTo(currentDate) >= 0 && (returnDate == null || date.compareTo(returnDate) < 0))
227            {
228                returnDate = date;
229            }
230        }
231        return returnDate;
232    }
233    
234    @Override
235    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
236    {
237        boolean enabled = Config.getInstance().getValue("archive.scheduler.enabled");
238        if (enabled)
239        {
240            return super.getScripts(ignoreRights, contextParameters);
241        }
242        
243        return new ArrayList<>();
244    }
245}