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