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.plugins.workspaces.calendars.workflow;
017
018import java.time.ZonedDateTime;
019import java.time.temporal.ChronoUnit;
020import java.util.ArrayList;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.commons.lang.IllegalClassException;
028
029import org.ametys.core.observation.Event;
030import org.ametys.core.util.DateUtils;
031import org.ametys.plugins.messagingconnector.EventRecurrenceTypeEnum;
032import org.ametys.plugins.repository.AmetysObject;
033import org.ametys.plugins.repository.AmetysRepositoryException;
034import org.ametys.plugins.workflow.support.WorkflowProvider;
035import org.ametys.plugins.workspaces.calendars.Calendar;
036import org.ametys.plugins.workspaces.calendars.ModifiableCalendar;
037import org.ametys.plugins.workspaces.calendars.ObservationConstants;
038import org.ametys.plugins.workspaces.calendars.events.CalendarEvent;
039import org.ametys.plugins.workspaces.calendars.events.ModifiableCalendarEvent;
040import org.ametys.plugins.workspaces.workflow.AbstractNodeWorkflowComponent;
041
042import com.opensymphony.module.propertyset.PropertySet;
043import com.opensymphony.workflow.Workflow;
044import com.opensymphony.workflow.WorkflowException;
045import com.opensymphony.workflow.spi.WorkflowEntry;
046
047/**
048 * Action for editing a calendar event
049 */
050public class EditEventFunction extends AddEventFunction
051{
052    /** Workflow provider */
053    protected WorkflowProvider _workflowProvider;
054    
055    @Override
056    public void service(ServiceManager smanager) throws ServiceException
057    {
058        super.service(smanager);
059        _workflowProvider = (WorkflowProvider) smanager.lookup(WorkflowProvider.ROLE);
060    }
061    
062    @Override
063    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
064    {
065        @SuppressWarnings("unchecked")
066        Map<String, Object> jsParameters = (Map<String, Object>) transientVars.get("parameters");
067        if (jsParameters == null)
068        {
069            throw new WorkflowException("no parameters defined");
070        }
071       
072        String eventId = (String) jsParameters.get("id");
073        CalendarEvent event = _resolver.resolveById(eventId);
074        
075        String parentId = (String) jsParameters.get("parentId");
076        Calendar calendar = _resolver.resolveById(parentId);
077        
078        if (!(calendar instanceof ModifiableCalendar))
079        {
080            throw new IllegalClassException(ModifiableCalendar.class, calendar.getClass());
081        }
082        
083        if (!(event instanceof ModifiableCalendarEvent))
084        {
085            throw new IllegalClassException(ModifiableCalendarEvent.class, event.getClass());
086        }
087        
088        ModifiableCalendarEvent mEvent = (ModifiableCalendarEvent) event;
089        ModifiableCalendar mCalendar = (ModifiableCalendar) calendar;
090        
091        String choice = (String) jsParameters.get("choice");
092        
093        @SuppressWarnings("unchecked")
094        Map<String, Object> result = (Map<String, Object>) transientVars.get("result");
095        
096        if ("unit".equals(choice))
097        {
098            // Create a new event from the edited occurrence
099            _createEventFromOccurrence(mEvent, mCalendar, transientVars, jsParameters);
100            
101            // Exclude this occurrence from the initial event
102            String occurrenceDateAsString = (String) jsParameters.get("occurrenceDate");
103            ZonedDateTime occurrenceDate = DateUtils.parseZonedDateTime(occurrenceDateAsString);
104            _excludeOccurrence(mEvent, occurrenceDate);
105            
106            mEvent.saveChanges();
107            
108            _notifyListeners(mEvent);
109        }
110        else
111        {
112            // FIXME EXPLORER-441 Remove "renameIfExists" on calendar events workflow
113            // boolean renameIfExists = (Boolean) jsParameters.get("renameIfExists");
114            assert parentId != null;
115            
116            AmetysObject object = _resolver.resolveById(parentId);
117            if (!(object instanceof ModifiableCalendar))
118            {
119                throw new IllegalClassException(ModifiableCalendar.class, object.getClass());
120            }
121            
122            long workflowId = ((WorkflowEntry) transientVars.get("entry")).getId();
123            event.setWorkflowId(workflowId);
124            
125            // Compute the event's dates from the dates of the modified occurrence
126            ZonedDateTime[] dates = _computeEventDates(event, jsParameters);
127            
128            _setEventData(mEvent, transientVars, jsParameters);
129            
130            mEvent.setStartDate(dates[0]);
131            mEvent.setEndDate(dates[1]);
132            
133            mEvent.saveChanges();
134            
135            result.put("id", event.getId());
136            result.put("parentId", parentId);
137            result.put("event", _calendarEventJSONHelper.eventAsJsonWithOccurrences(event, false, event.getStartDate(), event.getEndDate()));
138            
139            // Notify listeners
140            _notifyListeners(mEvent);
141        }
142    }
143    
144    /**
145     * Create a new event from the occurrence of a event 
146     * @param initialEvent The initial event
147     * @param parentCalendar The parent calendar
148     * @param transientVars The transient variable
149     * @param jsParameters The JS parameters
150     */
151    protected void _createEventFromOccurrence (CalendarEvent initialEvent, Calendar parentCalendar, Map transientVars, Map<String, Object> jsParameters)
152    {
153        @SuppressWarnings("unchecked")
154        Map<String, Object> result = (Map<String, Object>) transientVars.get("result");
155        
156        // Computed workflow parameters to create a new event
157        if (!jsParameters.containsKey("title"))
158        {
159            jsParameters.put("title", initialEvent.getTitle());
160            jsParameters.put("desc", initialEvent.getDescription());
161            jsParameters.put("fullDay", initialEvent.getFullDay());
162        }
163        jsParameters.put("recurrenceType", EventRecurrenceTypeEnum.NEVER.toString());
164        jsParameters.put("untilDate", null);
165        
166        Map<String, Object> inputs = new HashMap<>();
167        inputs.put("parameters", jsParameters);
168        inputs.put(AbstractNodeWorkflowComponent.EXPLORERNODE_KEY, parentCalendar);
169        inputs.put("result", result);
170        
171        String workflowName = parentCalendar.getWorkflowName();
172        try
173        {
174            // Create a new event from the edited occurrence
175            Workflow workflow = _workflowProvider.getAmetysObjectWorkflow();
176            workflow.initialize(workflowName, 1, inputs);
177        }
178        catch (WorkflowException e)
179        {
180            throw new AmetysRepositoryException("Unable to create a new event with workflow '" + workflowName + "' and action 1'", e);
181        }
182    }
183    
184    /**
185     * Exclude a occurrence of a event
186     * @param event The event
187     * @param occurrenceDate The date to exclude
188     */
189    protected void _excludeOccurrence (ModifiableCalendarEvent event, ZonedDateTime occurrenceDate)
190    {
191        List<ZonedDateTime> excludedOccurrences = new ArrayList<>(event.getExcludedOccurences());
192        
193        excludedOccurrences.add(occurrenceDate.truncatedTo(ChronoUnit.DAYS));
194        
195        // Exclude the occurrence
196        event.setExcludedOccurrences(excludedOccurrences);
197    }
198    
199    /**
200     * Compute the start date and end date of the event from the edited occurrence
201     * @param event The event
202     * @param jsParameters The JS parameters
203     * @return an array with computed start and end dates
204     */
205    protected ZonedDateTime[] _computeEventDates (CalendarEvent event, Map<String, Object> jsParameters)
206    {
207        // Date of the edited occurrence before edition
208        String occurrenceDateAsString = (String) jsParameters.get("occurrenceDate");
209        ZonedDateTime occurrenceDate = DateUtils.parseZonedDateTime(occurrenceDateAsString);
210        
211        // Dates of the event before edition
212        long oldDiff = ChronoUnit.SECONDS.between(event.getStartDate(), event.getEndDate());
213        
214        ZonedDateTime occurrenceEndDate = occurrenceDate.plusSeconds(oldDiff);
215        
216        String startDateAsString = (String) jsParameters.get("startDate");
217        ZonedDateTime startDate = DateUtils.parseZonedDateTime(startDateAsString);
218        String endDateAsString = (String) jsParameters.get("endDate");
219        ZonedDateTime endDate = DateUtils.parseZonedDateTime(endDateAsString);
220        
221        long startDiff = ChronoUnit.SECONDS.between(occurrenceDate, startDate);
222        long endDiff = ChronoUnit.SECONDS.between(occurrenceEndDate, endDate);
223        
224        // Compute the new start and end date from the edited occurrence
225        return new ZonedDateTime[] {event.getStartDate().plusSeconds(startDiff), event.getEndDate().plusSeconds(endDiff)};
226    }
227    
228    
229    /**
230     * Notify listeners that the event has been updated
231     * @param event The updated event
232     */
233    @Override
234    protected void _notifyListeners (CalendarEvent event)
235    {
236        Map<String, Object> eventParams = new HashMap<>();
237        eventParams.put(ObservationConstants.ARGS_CALENDAR, event.getParent());
238        eventParams.put(ObservationConstants.ARGS_CALENDAR_EVENT, event);
239        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, event.getId());
240        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_PARENT_ID, event.getParent().getId());
241        
242        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_UPDATED, _currentUserProvider.getUser(), eventParams));
243    }
244}