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