001/*
002 *  Copyright 2015 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.actions;
017
018import java.time.ZonedDateTime;
019import java.time.format.DateTimeFormatter;
020import java.util.ArrayList;
021import java.util.Date;
022import java.util.GregorianCalendar;
023import java.util.HashMap;
024import java.util.LinkedList;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import javax.jcr.RepositoryException;
030
031import org.apache.avalon.framework.component.Component;
032import org.apache.avalon.framework.logger.AbstractLogEnabled;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.commons.lang.BooleanUtils;
037import org.apache.commons.lang.IllegalClassException;
038import org.apache.commons.lang3.StringUtils;
039import org.apache.jackrabbit.util.Text;
040
041import org.ametys.core.observation.Event;
042import org.ametys.core.observation.ObservationManager;
043import org.ametys.core.right.RightManager;
044import org.ametys.core.right.RightManager.RightResult;
045import org.ametys.core.ui.Callable;
046import org.ametys.core.user.CurrentUserProvider;
047import org.ametys.core.user.User;
048import org.ametys.core.user.UserIdentity;
049import org.ametys.core.user.UserManager;
050import org.ametys.core.util.DateUtils;
051import org.ametys.plugins.explorer.ExplorerNode;
052import org.ametys.plugins.explorer.ModifiableExplorerNode;
053import org.ametys.plugins.explorer.ObservationConstants;
054import org.ametys.plugins.explorer.calendars.Calendar;
055import org.ametys.plugins.explorer.calendars.Calendar.CalendarVisibility;
056import org.ametys.plugins.explorer.calendars.CalendarEvent;
057import org.ametys.plugins.explorer.calendars.ModifiableCalendar;
058import org.ametys.plugins.explorer.calendars.ModifiableCalendarEvent;
059import org.ametys.plugins.explorer.calendars.jcr.JCRCalendar;
060import org.ametys.plugins.explorer.calendars.jcr.JCRCalendarEvent;
061import org.ametys.plugins.explorer.calendars.jcr.JCRCalendarFactory;
062import org.ametys.plugins.explorer.resources.ModifiableResourceCollection;
063import org.ametys.plugins.explorer.resources.actions.ExplorerResourcesDAO;
064import org.ametys.plugins.explorer.workflow.AbstractExplorerNodeWorkflowComponent;
065import org.ametys.plugins.repository.AmetysObject;
066import org.ametys.plugins.repository.AmetysObjectIterable;
067import org.ametys.plugins.repository.AmetysObjectResolver;
068import org.ametys.plugins.repository.AmetysRepositoryException;
069import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
070import org.ametys.plugins.workflow.support.WorkflowHelper;
071import org.ametys.plugins.workflow.support.WorkflowProvider;
072import org.ametys.runtime.i18n.I18nizableText;
073
074import com.opensymphony.workflow.Workflow;
075import com.opensymphony.workflow.WorkflowException;
076import com.opensymphony.workflow.spi.Step;
077
078/**
079 * Calendar DAO
080 */
081public class CalendarDAO extends AbstractLogEnabled implements Serviceable, Component
082{
083    /** Avalon Role */
084    public static final String ROLE = CalendarDAO.class.getName();
085    
086    /** Right to add a calendar */
087    public static final String RIGHTS_CALENDAR_ADD = "Plugin_Explorer_Calendar_Add";
088    /** Right to edit a calendar */
089    public static final String RIGHTS_CALENDAR_EDIT = "Plugin_Explorer_Calendar_Edit";
090    /** Right to delete a calendar */
091    public static final String RIGHTS_CALENDAR_DELETE = "Plugin_Explorer_Calendar_Delete";
092    /** Right to add a event */
093    public static final String RIGHTS_EVENT_ADD = "Plugin_Explorer_Event_Add";
094    /** Right to edit a event */
095    public static final String RIGHTS_EVENT_EDIT = "Plugin_Explorer_Event_Edit";
096    /** Right to propose a event */
097    public static final String RIGHTS_EVENT_PROPOSE = "Plugin_Explorer_Event_Propose";
098    /** Right to validate a event */
099    public static final String RIGHTS_EVENT_VALIDATE = "Plugin_Explorer_Event_Validate";
100    /** Right to refuse a event */
101    public static final String RIGHTS_EVENT_REFUSE = "Plugin_Explorer_Event_Refuse";
102    /** Right to delete a event */
103    public static final String RIGHTS_EVENT_DELETE = "Plugin_Explorer_Event_Delete";
104    /** Right to delete_own a event */
105    public static final String RIGHTS_EVENT_DELETE_OWN = "Plugin_Explorer_Owned_Event_Delete";
106    
107    /** Explorer resources DAO */
108    protected ExplorerResourcesDAO _explorerResourcesDAO;
109    
110    /** Ametys resolver */
111    protected AmetysObjectResolver _resolver;
112    
113    /** Observer manager. */
114    protected ObservationManager _observationManager;
115    
116    /** The current user provider. */
117    protected CurrentUserProvider _currentUserProvider;
118    
119    /** The rights manager */
120    protected RightManager _rightManager;
121    
122    /** User manager */
123    protected UserManager _userManager;
124    
125    /** The workflow provider */
126    protected WorkflowProvider _workflowProvider;
127    
128    /** The workflow helper */
129    protected WorkflowHelper _workflowHelper;
130
131    
132    public void service(ServiceManager manager) throws ServiceException
133    {
134        _explorerResourcesDAO = (ExplorerResourcesDAO) manager.lookup(ExplorerResourcesDAO.ROLE);
135        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
136        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
137        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
138        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
139        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
140        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
141        _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE);
142    }
143    
144    /**
145     * Get calendar info
146     * @param id The calendar id
147     * @param recursive True to get data for sub calendars
148     * @param includeEvents True to also include child events
149     * @return the calendar data in a map
150     */
151    @Callable
152    public Map<String, Object> getCalendarData(String id, boolean recursive, boolean includeEvents)
153    {
154        Calendar calendar = (Calendar) _resolver.resolveById(id);
155        return getCalendarData(calendar, recursive, includeEvents);
156    }
157    
158    /**
159     * Get calendar info
160     * @param calendar The calendar
161     * @param recursive True to get data for sub calendars
162     * @param includeEvents True to also include child events
163     * @return the calendar data in a map
164     */
165    public Map<String, Object> getCalendarData(Calendar calendar, boolean recursive, boolean includeEvents)
166    {
167        Map<String, Object> result = new HashMap<>();
168        
169        result.put("id", calendar.getId());
170        result.put("title", Text.unescapeIllegalJcrChars(calendar.getName()));
171        result.put("description", calendar.getDescription());
172        result.put("templateDesc", calendar.getTemplateDescription());
173        result.put("color", calendar.getColor());
174        result.put("visibility", calendar.getVisibility().name().toLowerCase());
175        result.put("workflowName", calendar.getWorkflowName());
176        
177        if (recursive)
178        {
179            List<Map<String, Object>> calendarList = new LinkedList<>();
180            result.put("calendars", calendarList);
181            
182            AmetysObjectIterable<AmetysObject> children = calendar.getChildren();
183            for (AmetysObject child : children)
184            {
185                if (child instanceof Calendar)
186                {
187                    calendarList.add(getCalendarData((Calendar) child, recursive, includeEvents));
188                }
189            }
190        }
191        
192        if (includeEvents)
193        {
194            List<Map<String, Object>> eventList = new LinkedList<>();
195            result.put("events", eventList);
196            
197            AmetysObjectIterable<AmetysObject> children = calendar.getChildren();
198            for (AmetysObject child : children)
199            {
200                if (child instanceof CalendarEvent)
201                {
202                    eventList.add(getEventData((CalendarEvent) child, false));
203                }
204            }
205        }
206        
207        return result;
208    }
209    
210    /**
211     * Get the template description of a calendar
212     * @param calendarId The identifier of the calendar
213     * @return The template description
214     */
215    @Callable
216    public String getTemplateDescription(String calendarId)
217    {
218        Calendar calendar = _resolver.resolveById(calendarId);
219        return StringUtils.defaultString(calendar.getTemplateDescription());
220    }
221    
222    /**
223     * Get event info
224     * @param ids The event ids
225     * @param fullInfo true to include full info (rights, parent id, etc...)
226     * @return the list of event data
227     */
228    @Callable
229    public List<Map<String, Object>> getEventsDataByIds(List<String> ids, boolean fullInfo)
230    {
231        List<CalendarEvent> events = new LinkedList<>();
232        for (String id : ids)
233        {
234            events.add((CalendarEvent) _resolver.resolveById(id));
235        }
236        
237        return getEventsData(events, fullInfo);
238    }
239    
240    /**
241     * Get event info
242     * @param events The events
243     * @param fullInfo true to include full info (rights, parent id, etc...)
244     * @return the list of event data
245     */
246    public List<Map<String, Object>> getEventsData(List<CalendarEvent> events, boolean fullInfo)
247    {
248        List<Map<String, Object>> result = new LinkedList<>();
249        
250        for (CalendarEvent event : events)
251        {
252            result.add(getEventData(event, fullInfo));
253        }
254        
255        return result;
256    }
257    
258    /**
259     * Get event info
260     * @param id The event id
261     * @param fullInfo true to include full info (rights, parent id, etc...)
262     * @return the event data in a map
263     */
264    @Callable
265    public Map<String, Object> getEventDataById(String id, boolean fullInfo)
266    {
267        CalendarEvent event = (CalendarEvent) _resolver.resolveById(id);
268        return getEventData(event, fullInfo);
269    }
270    
271    /**
272     * Get event info for a specific occurrence
273     * @param id The event id
274     * @param occurrence a string representing the occurrence date (ISO format).
275     * @param fullInfo true to include full info (rights, parent id, etc...)
276     * @return the event data in a map
277     */
278    @Callable
279    public Map<String, Object> getEventDataById(String id, String occurrence, boolean fullInfo)
280    {
281        CalendarEvent event = (CalendarEvent) _resolver.resolveById(id);
282        Date occurrenceDate = DateUtils.parse(occurrence);
283        
284        return getEventData(event, occurrenceDate, fullInfo);
285    }
286    
287    /**
288     * Get event info for a specific occurrence
289     * @param event The event
290     * @param occurrenceDate the occurrence
291     * @param fullInfo true to include full info (rights, parent id, etc...)
292     * @return the event data in a map
293     */
294    public Map<String, Object> getEventData(CalendarEvent event, Date occurrenceDate, boolean fullInfo)
295    {
296        Map<String, Object> eventData = getEventData(event, fullInfo);
297        
298        if (occurrenceDate != null)
299        {
300            // replace id, start and end date with specific occurrence data
301            eventData.putAll(getEventOccurrenceData(event, occurrenceDate));
302        }
303        
304        return eventData;
305    }
306    
307    /**
308     * Get event info
309     * @param event The event
310     * @param fullInfo true to include full info (rights, parent id, etc...)
311     * @return the event data in a map
312     */
313    public Map<String, Object> getEventData(CalendarEvent event, boolean fullInfo)
314    {
315        Calendar calendar = event.getParent();
316        Map<String, Object> result = new HashMap<>();
317        
318        result.put("id", event.getId());
319        result.put("calendarId", event.getParent().getId());
320        result.put("color", calendar.getColor());
321        
322        result.put("title", event.getTitle());
323        result.put("description", event.getDescription());
324        result.put("fullDay", event.getFullDay());
325        result.put("recurrenceType", event.getRecurrenceType().toString());
326        
327        result.put("location", event.getLocation());
328        result.put("keywords", event.getKeywords());
329        
330        Date untilDate = event.getRepeatUntil();
331        if (untilDate != null)
332        {
333            result.put("untilDate", DateUtils.dateToString(untilDate));
334        }
335        
336        Date startDateEvent = event.getStartDate();
337        Date endDateEvent = event.getEndDate();
338        
339        if (event.getFullDay())
340        {
341            GregorianCalendar gcStart = new GregorianCalendar();
342            gcStart.setTime(startDateEvent);
343            gcStart.set(java.util.Calendar.HOUR, 0);
344            gcStart.set(java.util.Calendar.MINUTE, 0);
345            gcStart.set(java.util.Calendar.SECOND, 0);
346            gcStart.set(java.util.Calendar.MILLISECOND, 0);
347            startDateEvent = gcStart.getTime();
348            
349            GregorianCalendar gcEnd = new GregorianCalendar();
350            gcEnd.setTime(endDateEvent);
351            gcEnd.set(java.util.Calendar.HOUR, 23);
352            gcEnd.set(java.util.Calendar.MINUTE, 59);
353            gcEnd.set(java.util.Calendar.SECOND, 59);
354            gcEnd.set(java.util.Calendar.MILLISECOND, 999);
355            endDateEvent = gcEnd.getTime();
356
357            GregorianCalendar gcEndPlus1 = new GregorianCalendar();
358            gcEndPlus1.setTime(endDateEvent);
359            gcEndPlus1.add(java.util.Calendar.DAY_OF_YEAR, 1);
360            gcEndPlus1.set(java.util.Calendar.HOUR, 0);
361            gcEndPlus1.set(java.util.Calendar.MINUTE, 0);
362            gcEndPlus1.set(java.util.Calendar.SECOND, 0);
363            gcEndPlus1.set(java.util.Calendar.MILLISECOND, 0);
364
365            long milis = gcEndPlus1.getTimeInMillis() - gcStart.getTimeInMillis();
366            Integer nbDays = Math.round(milis / (1000 * 60 * 60 * 24));
367
368            result.put("nbDays", nbDays.intValue());
369            result.put("endDateNextDay", DateUtils.dateToString(gcEndPlus1.getTime()));
370        }
371
372        result.put("startDate", DateUtils.dateToString(startDateEvent));
373        result.put("endDate", DateUtils.dateToString(endDateEvent));
374        
375        //excluded occurences
376        List<Date> excludedOccurences = event.getExcludedOccurences();
377        if (excludedOccurences != null && !excludedOccurences.isEmpty())
378        {
379            List<String> excludedOccurencesStrings = new ArrayList<>();
380            for (Date excludedOccurence : excludedOccurences)
381            {
382                excludedOccurencesStrings.add(DateUtils.dateToString(excludedOccurence));
383            }
384            result.put("excludedDates", excludedOccurencesStrings);
385        }
386
387        // creator
388        UserIdentity creatorIdentity = event.getCreator();
389        User creator = _userManager.getUser(creatorIdentity);
390        
391        result.put("creator", creatorIdentity);
392        result.put("creatorFullName", creator != null ? creator.getFullName() : creatorIdentity.getLogin());
393        result.put("creationDate", DateUtils.dateToString(event.getCreationDate()));
394        
395        // last modification
396        UserIdentity contributorIdentity = event.getLastContributor();
397        User contributor = _userManager.getUser(contributorIdentity);
398        
399        result.put("contributor", contributorIdentity);
400        result.put("contributorFullName", contributor != null ? contributor.getFullName() : contributorIdentity.getLogin());
401        result.put("lastModified", DateUtils.dateToString(event.getLastModified()));
402        
403        // last validation
404        UserIdentity validatorIdentity = event.getLastValidator();
405        
406        if (validatorIdentity != null)
407        {
408            result.put("validator", validatorIdentity);
409            User validator = _userManager.getUser(validatorIdentity);
410            result.put("validatorFullName", validator != null ? validator.getFullName() : validatorIdentity.getLogin());
411        }
412        
413        Date lastValidated = event.getLastValidated();
414        if (lastValidated != null)
415        {
416            result.put("lastValidated", DateUtils.dateToString(lastValidated));
417        }
418        
419        Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event);
420        long workflowId = event.getWorkflowId();
421        result.put("workflowId", workflowId);
422        result.put("workflowName", workflow.getWorkflowName(workflowId));
423        
424        List<Step> listSteps = workflow.getCurrentSteps(event.getWorkflowId());
425        if (!listSteps.isEmpty())
426        {
427            // We select the first current step
428            Step step = listSteps.get(0);
429            String stepName = _workflowHelper.getStepName(calendar.getWorkflowName(), step.getStepId());
430            String[] nameParts = stepName.split(":");
431            
432            result.put("stepId", step.getStepId());
433            result.put("stepName", nameParts[nameParts.length - 1]); // step name without the possible catalog name
434            
435            I18nizableText workflowStepName = new I18nizableText("application", stepName);
436            
437            if ("application".equals(workflowStepName.getCatalogue()))
438            {
439                result.put("icon-small-workflow", "/plugins/explorer/resources_workflow/" + workflowStepName.getKey() + "-small.png");
440            }
441            else
442            {
443                String pluginName = workflowStepName.getCatalogue().substring("plugin.".length());
444                result.put("icon-small-workflow", "/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepName.getKey() + "-small.png");
445            }
446        }
447        
448        if (fullInfo)
449        {
450            result.putAll(_getEventDataFullInfo(event));
451        }
452        
453        return result;
454    }
455    
456    /**
457     * Retrieves the event additional info (rights, parent id, etc...)
458     * @param event The event
459     * @return the event additional info (rights, parent id, etc...) in a map
460     */
461    protected Map<String, Object> _getEventDataFullInfo(CalendarEvent event)
462    {
463        Map<String, Object> result = new HashMap<>();
464        
465        ExplorerNode explorerNode = event.getParent();
466        ExplorerNode root = explorerNode;
467        while (true)
468        {
469            if (root.getParent() instanceof ExplorerNode)
470            {
471                root = root.getParent();
472            }
473            else
474            {
475                break;
476            }
477        }
478        result.put("rootId", root.getId());
479        result.put("parentId", explorerNode.getId());
480        result.put("name", event.getName());
481        result.put("path", explorerNode.getExplorerPath());
482        result.put("isModifiable", true);
483        
484        result.put("rights", _getUserRights(explorerNode));
485        
486        return result;
487    }
488    
489    /**
490     * Get the user rights on the resource collection
491     * @param node The explorer node
492     * @return The user's rights
493     */
494    protected Set<String> _getUserRights(ExplorerNode node)
495    {
496        return _rightManager.getUserRights(_currentUserProvider.getUser(), node);
497    }
498    
499    /**
500     * Get info about the occurrence of an event
501     * @param event The event
502     * @param date The start date of the occurrence of the event
503     * @return the event occurrence data in a map
504     */
505    public Map<String, Object> getEventOccurrenceData(CalendarEvent event, Date date)
506    {
507        Map<String, Object> result = new HashMap<>();
508        
509        String occurrenceDateIso = DateUtils.dateToString(date);
510        result.put("id", event.getId() + "$" + occurrenceDateIso);
511        result.put("occurrenceDate", occurrenceDateIso);
512        
513        Date firstStartDateEvent = event.getStartDate();
514        Date firstEndDateEvent = event.getEndDate();
515        
516        long diff = firstEndDateEvent.getTime() - firstStartDateEvent.getTime();
517        Date startDateEvent = date;
518        Date endDateEvent = new Date(date.getTime() + diff);
519        
520        if (event.getFullDay())
521        {
522            GregorianCalendar gcStart = new GregorianCalendar();
523            gcStart.setTime(startDateEvent);
524            gcStart.set(java.util.Calendar.HOUR_OF_DAY, 0);
525            gcStart.set(java.util.Calendar.MINUTE, 0);
526            gcStart.set(java.util.Calendar.SECOND, 0);
527            gcStart.set(java.util.Calendar.MILLISECOND, 0);
528            
529            startDateEvent = gcStart.getTime();
530            
531            GregorianCalendar gcEnd = new GregorianCalendar();
532            gcEnd.setTime(endDateEvent);
533            gcEnd.set(java.util.Calendar.HOUR_OF_DAY, 23);
534            gcEnd.set(java.util.Calendar.MINUTE, 59);
535            gcEnd.set(java.util.Calendar.SECOND, 59);
536            gcEnd.set(java.util.Calendar.MILLISECOND, 999);
537            
538            endDateEvent = gcEnd.getTime();
539        }
540        
541        result.put("startDate", DateUtils.dateToString(startDateEvent));
542        result.put("endDate", DateUtils.dateToString(endDateEvent));
543        
544        return result;
545    }
546    
547    /**
548     * Add a calendar
549     * @param id The identifier of the parent in which the calendar will be added
550     * @param inputName The desired name for the calendar
551     * @param description The calendar description
552     * @param templateDesc The calendar template description
553     * @param color The calendar color
554     * @param visibility The calendar visibility
555     * @param workflowName The calendar workflow name
556     * @param renameIfExists True to rename if existing
557     * @return The result map with id, parentId and name keys
558     * @throws IllegalAccessException If the user has no sufficient rights
559     */
560    @Callable
561    public Map<String, Object> addCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists) throws IllegalAccessException
562    {
563        Map<String, Object> result = new HashMap<>();
564        
565        String originalName = Text.escapeIllegalJcrChars(inputName);
566        assert id != null;
567        
568        AmetysObject object = _resolver.resolveById(id);
569        if (!(object instanceof ModifiableResourceCollection || object instanceof Calendar))
570        {
571            throw new IllegalClassException(ModifiableResourceCollection.class, object.getClass());
572        }
573        
574        ModifiableTraversableAmetysObject parent = (ModifiableTraversableAmetysObject) object;
575        
576        // Check user right
577        _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_ADD);
578        
579        if (BooleanUtils.isNotTrue(renameIfExists) && parent.hasChild(originalName))
580        {
581            getLogger().warn("Cannot create the calendar with name '" + originalName + "', an object with same name already exists.");
582            result.put("message", "already-exist");
583            return result;
584        }
585        
586        if (!_explorerResourcesDAO.checkLock(parent))
587        {
588            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify the object '" + object.getName() + "' but it is locked by another user");
589            result.put("message", "locked");
590            return result;
591        }
592        
593        int index = 2;
594        String name = originalName;
595        while (parent.hasChild(name))
596        {
597            name = originalName + " (" + index + ")";
598            index++;
599        }
600        
601        JCRCalendar calendar = parent.createChild(name, JCRCalendarFactory.CALENDAR_NODETYPE);
602        calendar.setWorkflowName(workflowName);
603        calendar.setDescription(description);
604        calendar.setTemplateDescription(templateDesc);
605        calendar.setColor(color);
606        calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE);
607        parent.saveChanges();
608        
609        // Notify listeners
610        Map<String, Object> eventParams = new HashMap<>();
611        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
612        eventParams.put(ObservationConstants.ARGS_PARENT_ID, id);
613        eventParams.put(ObservationConstants.ARGS_NAME, name);
614        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
615        
616        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_CREATED, _currentUserProvider.getUser(), eventParams));
617        
618        result.put("id", calendar.getId());
619        result.put("parentId", id);
620        result.put("name", Text.unescapeIllegalJcrChars(name));
621        
622        return result;
623    }
624    
625    /**
626     * Edit a calendar
627     * @param id The identifier of the calendar
628     * @param inputName The new name
629     * @param description The new description
630     * @param templateDesc The new calendar template description
631     * @param color The calendar color
632     * @param visibility The calendar visibility
633     * @param workflowName The calendar workflow name
634     * @param renameIfExists True to rename if existing
635     * @param fullEdit true to allow full edition, otherwise only the name will be changed
636     * @return The result map with id and name keys
637     * @throws IllegalAccessException If the user has no sufficient rights
638     */
639    @Callable
640    public Map<String, Object> editCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists, Boolean fullEdit) throws IllegalAccessException
641    {
642        // TODO handle template desc
643        Map<String, Object> result = new HashMap<>();
644        
645        assert id != null;
646        String rename = Text.escapeIllegalJcrChars(inputName);
647        
648        AmetysObject object = _resolver.resolveById(id);
649        if (!(object instanceof ModifiableCalendar))
650        {
651            throw new IllegalClassException(ModifiableCalendar.class, object.getClass());
652        }
653        
654        ModifiableCalendar calendar = (ModifiableCalendar) object;
655        
656        // Check user right
657        _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_EDIT);
658        
659        String name = calendar.getName();
660        ModifiableTraversableAmetysObject parent = calendar.getParent();
661        
662        if (BooleanUtils.isNotTrue(renameIfExists) && !StringUtils.equals(rename, name) && parent.hasChild(rename))
663        {
664            getLogger().warn("Cannot edit the calendar with the new name '" + inputName + "', an object with same name already exists.");
665            result.put("message", "already-exist");
666            return result;
667        }
668        
669        if (!_explorerResourcesDAO.checkLock(calendar))
670        {
671            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify calendar '" + object.getName() + "' but it is locked by another user");
672            result.put("message", "locked");
673            return result;
674        }
675        
676        if (!StringUtils.equals(name, rename))
677        {
678            int index = 2;
679            name = Text.escapeIllegalJcrChars(rename);
680            while (parent.hasChild(name))
681            {
682                name = rename + " (" + index + ")";
683                index++;
684            }
685            calendar.rename(name);
686        }
687        
688        if (BooleanUtils.isTrue(fullEdit))
689        {
690            calendar.setDescription(description);
691            calendar.setTemplateDescription(templateDesc);
692            calendar.setColor(color);
693            calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE);
694        }
695        
696        parent.saveChanges();
697        
698        // Notify listeners
699        Map<String, Object> eventParams = new HashMap<>();
700        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
701        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId());
702        eventParams.put(ObservationConstants.ARGS_NAME, name);
703        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
704        
705        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_UPDATED, _currentUserProvider.getUser(), eventParams));
706        
707        result.put("id", calendar.getId());
708        result.put("title", Text.unescapeIllegalJcrChars(name));
709
710        return result;
711    }
712    
713    /**
714     * Move a event to another calendar
715     * @param event The event to move
716     * @param parent The new parent calendar
717     * @throws AmetysRepositoryException if an error occurred while moving
718     */
719    public void move(JCRCalendarEvent event, JCRCalendar parent) throws AmetysRepositoryException
720    {
721        try
722        {
723            event.getNode().getSession().move(event.getNode().getPath(), parent.getNode().getPath() + "/ametys:calendar-event");
724            
725            Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event);
726
727            String previousWorkflowName = workflow.getWorkflowName(event.getWorkflowId());
728            String workflowName = parent.getWorkflowName();
729
730            if (!StringUtils.equals(previousWorkflowName, workflowName))
731            {
732                // If both calendar have a different workflow, initialize a new workflow instance for the event
733                HashMap<String, Object> inputs = new HashMap<>();
734                inputs.put(AbstractExplorerNodeWorkflowComponent.EXPLORERNODE_KEY, parent);
735                workflow = _workflowProvider.getAmetysObjectWorkflow(event);
736                
737                long workflowId = workflow.initialize(workflowName, 0, inputs);
738                event.setWorkflowId(workflowId);
739            }
740        }
741        catch (WorkflowException | RepositoryException e)
742        {
743            String errorMsg = String.format("Fail to move the event '%s' to the calendar '%s'.", event.getId(), parent.getId());
744            throw new AmetysRepositoryException(errorMsg, e);
745        }
746    }
747    
748    /**
749     * Delete a calendar
750     * @param id The id of the calendar
751     * @return The result map with id, parent id and message keys
752     * @throws IllegalAccessException If the user has no sufficient rights
753     */
754    @Callable
755    public Map<String, Object> deleteCalendar(String id) throws IllegalAccessException
756    {
757        Map<String, Object> result = new HashMap<>();
758
759        assert id != null;
760        
761        AmetysObject object = _resolver.resolveById(id);
762        if (!(object instanceof ModifiableCalendar))
763        {
764            throw new IllegalClassException(ModifiableCalendar.class, object.getClass());
765        }
766        
767        ModifiableCalendar calendar = (ModifiableCalendar) object;
768        
769        // Check user right
770        _explorerResourcesDAO.checkUserRight(object, RIGHTS_CALENDAR_DELETE);
771        
772        if (!_explorerResourcesDAO.checkLock(calendar))
773        {
774            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete calendar'" + object.getName() + "' but it is locked by another user");
775            result.put("message", "locked");
776            return result;
777        }
778        
779        ModifiableExplorerNode parent = calendar.getParent();
780        String parentId = parent.getId();
781        String name = calendar.getName();
782        String path = calendar.getPath();
783        
784        calendar.remove();
785        parent.saveChanges();
786     
787        // Notify listeners
788        Map<String, Object> eventParams = new HashMap<>();
789        eventParams.put(ObservationConstants.ARGS_ID, id);
790        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId);
791        eventParams.put(ObservationConstants.ARGS_NAME, name);
792        eventParams.put(ObservationConstants.ARGS_PATH, path);
793        
794        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_DELETED, _currentUserProvider.getUser(), eventParams));
795        
796        result.put("id", id);
797        result.put("parentId", parentId);
798        
799        return result;
800    }
801    
802    /**
803     * Do an event workflow action
804     * @param parameters The map of action parameters
805     * @return The map of results populated by the workflow action
806     * @throws WorkflowException if an error occurred
807     */
808    @Callable
809    public Map<String, Object> doWorkflowEventAction(Map<String, Object> parameters) throws WorkflowException
810    {
811        Map<String, Object> result = new HashMap<>();
812        HashMap<String, Object> inputs = new HashMap<>();
813
814        inputs.put("parameters", parameters);
815        inputs.put("result", result);
816        
817        String eventId = (String) parameters.get("id");
818        Long workflowInstanceId = null;
819        CalendarEvent event = null;
820        if (StringUtils.isNotEmpty(eventId))
821        {
822            event = _resolver.resolveById(eventId);
823            workflowInstanceId = event.getWorkflowId();
824        }
825        
826        inputs.put("eventId", eventId);
827        
828        Calendar calendar = null; 
829        String calendarId = (String) parameters.get("parentId");
830        
831        if (StringUtils.isNotEmpty(calendarId))
832        {
833            calendar = _resolver.resolveById(calendarId);
834        }
835        // parentId can be not provided for some basic actions where the event already exists
836        else if (event != null)
837        {
838            calendar = event.getParent();
839        }
840        else
841        {
842            throw new WorkflowException("Unable to retrieve the current calendar");
843        }
844        
845        inputs.put(AbstractExplorerNodeWorkflowComponent.EXPLORERNODE_KEY, calendar);
846        
847        String workflowName = calendar.getWorkflowName();
848        if (workflowName == null)
849        {
850            throw new IllegalArgumentException("The workflow name is not specified");
851        }
852        
853        int actionId =  (int) parameters.get("actionId");
854        
855        boolean sendMail = true;
856        String choice = (String) parameters.get("choice");
857        if (actionId == 2 && "unit".equals(choice))
858        {
859            sendMail = false;
860        }
861        inputs.put("sendMail", sendMail);
862        
863        Workflow workflow = _workflowProvider.getAmetysObjectWorkflow(event != null ? event : null);
864        
865        if (workflowInstanceId == null)
866        {
867            try
868            {
869                workflow.initialize(workflowName, actionId, inputs);
870            }   
871            catch (WorkflowException e)
872            {
873                getLogger().error("An error occured while creating workflow '" + workflowName + "' with action '" + actionId, e);
874                throw e;
875            }
876        }
877        else
878        {
879            try
880            {
881                workflow.doAction(workflowInstanceId, actionId, inputs);
882            }
883            catch (WorkflowException e)
884            {
885                getLogger().error("An error occured while doing action '" + actionId + "'with the workflow '" + workflowName, e);
886                throw e;
887            }
888        }
889        
890        return result;
891    }
892    
893    /**
894     * Delete an event
895     * @param id The id of the event
896     * @param occurrence a string representing the occurrence date (ISO format).
897     * @param choice The type of modification
898     * @return The result map with id, parent id and message keys
899     * @throws IllegalAccessException If the user has no sufficient rights
900     */
901    @Callable
902    public Map<String, Object> deleteEvent(String id, String occurrence, String choice) throws IllegalAccessException
903    {
904        Map<String, Object> result = new HashMap<>();
905
906        assert id != null;
907        
908        AmetysObject object = _resolver.resolveById(id);
909        if (!(object instanceof ModifiableCalendarEvent))
910        {
911            throw new IllegalClassException(ModifiableCalendarEvent.class, object.getClass());
912        }
913        
914        ModifiableCalendarEvent event = (ModifiableCalendarEvent) object;
915        ModifiableCalendar calendar = event.getParent();
916        
917        // Check user right
918        try
919        {
920            _explorerResourcesDAO.checkUserRight(calendar, RIGHTS_EVENT_DELETE);
921        }
922        catch (IllegalAccessException e)
923        {
924            UserIdentity user = _currentUserProvider.getUser();
925            UserIdentity creator = event.getCreator();
926            RightResult rightCreator = _rightManager.hasRight(user, RIGHTS_EVENT_DELETE_OWN, calendar);
927            boolean hasOwnDeleteRight = rightCreator == RightResult.RIGHT_ALLOW && creator.equals(user);
928            if (!hasOwnDeleteRight)
929            {
930                // rethrow exception
931                throw e;
932            }
933        }
934        
935        if (!_explorerResourcesDAO.checkLock(event))
936        {
937            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete event'" + object.getName() + "' but it is locked by another user");
938            result.put("message", "locked");
939            return result;
940        }
941        
942        String parentId = calendar.getId();
943        String name = event.getName();
944        String path = event.getPath();
945        
946        if (StringUtils.isNotBlank(choice) && choice.equals("unit"))
947        {
948            ArrayList<Date> excludedOccurrences = new ArrayList<>();
949            excludedOccurrences.addAll(event.getExcludedOccurences());
950            long date = ZonedDateTime.parse(occurrence, DateTimeFormatter.ISO_DATE_TIME).toInstant().toEpochMilli();
951            GregorianCalendar gCalendar = new GregorianCalendar();
952            gCalendar.setTimeInMillis(date);
953            gCalendar.set(java.util.Calendar.HOUR_OF_DAY, 0);
954            gCalendar.set(java.util.Calendar.MINUTE, 0);
955            gCalendar.set(java.util.Calendar.SECOND, 0);
956            gCalendar.set(java.util.Calendar.MILLISECOND, 0);
957            
958            Date dateExcluded = gCalendar.getTime();
959            excludedOccurrences.add(dateExcluded);
960            
961            event.setExcludedOccurrences(excludedOccurrences);
962        }
963        else
964        {
965            event.remove();
966        }
967        
968        calendar.saveChanges();
969        
970        // Notify listeners
971        Map<String, Object> eventParams = new HashMap<>();
972        eventParams.put(ObservationConstants.ARGS_CALENDAR, calendar);
973        eventParams.put(ObservationConstants.ARGS_ID, id);
974        eventParams.put(ObservationConstants.ARGS_NAME, name);
975        eventParams.put(ObservationConstants.ARGS_PATH, path);
976        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_DELETED, _currentUserProvider.getUser(), eventParams));
977        
978        result.put("id", id);
979        result.put("parentId", parentId);
980        
981        return result;
982    }
983}
984