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.workspaces.calendars;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.Objects;
024import java.util.UUID;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.commons.lang3.BooleanUtils;
029import org.apache.commons.lang3.StringUtils;
030import org.apache.commons.lang3.Strings;
031import org.apache.jackrabbit.util.Text;
032
033import org.ametys.cms.fo.ForceDefaultRepositoryWorkspaceCallableDecorator;
034import org.ametys.core.observation.Event;
035import org.ametys.core.right.RightManager.RightResult;
036import org.ametys.core.ui.Callable;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.plugins.explorer.ModifiableExplorerNode;
039import org.ametys.plugins.explorer.ObservationConstants;
040import org.ametys.plugins.explorer.resources.jcr.JCRResourcesCollection;
041import org.ametys.plugins.repository.AmetysObjectIterable;
042import org.ametys.plugins.repository.AmetysObjectIterator;
043import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
044import org.ametys.plugins.repository.jcr.DefaultTraversableAmetysObject;
045import org.ametys.plugins.repository.query.QueryHelper;
046import org.ametys.plugins.repository.query.expression.Expression;
047import org.ametys.plugins.repository.query.expression.Expression.Operator;
048import org.ametys.plugins.repository.query.expression.StringExpression;
049import org.ametys.plugins.workspaces.calendars.Calendar.CalendarVisibility;
050import org.ametys.plugins.workspaces.calendars.events.CalendarEvent;
051import org.ametys.plugins.workspaces.calendars.events.CalendarEventJSONHelper;
052import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendar;
053import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarFactory;
054import org.ametys.plugins.workspaces.calendars.task.TaskCalendar;
055import org.ametys.plugins.workspaces.project.objects.Project;
056import org.ametys.plugins.workspaces.tasks.TasksWorkspaceModule;
057import org.ametys.plugins.workspaces.tasks.WorkspaceTaskDAO;
058
059/**
060 * Calendar DAO
061 */
062public class CalendarDAO extends AbstractCalendarDAO
063{
064    /** Avalon Role */
065    public static final String ROLE = CalendarDAO.class.getName();
066
067    /** The tasks list JSON helper */
068    protected CalendarEventJSONHelper _calendarEventJSONHelper;
069
070    /** The task DAO */
071    protected WorkspaceTaskDAO _taskDAO;
072    
073    @Override
074    public void service(ServiceManager manager) throws ServiceException
075    {
076        super.service(manager);
077        _calendarEventJSONHelper = (CalendarEventJSONHelper) manager.lookup(CalendarEventJSONHelper.ROLE);
078        _taskDAO = (WorkspaceTaskDAO) manager.lookup(WorkspaceTaskDAO.ROLE);
079    }
080        
081    /**
082     * Get calendar info
083     * @param calendar The calendar
084     * @param recursive True to get data for sub calendars
085     * @param includeEvents True to also include child events
086     * @param useICSFormat true to use ICS Format for dates
087     * @return the calendar data in a map
088     */
089    public Map<String, Object> getCalendarData(Calendar calendar, boolean recursive, boolean includeEvents, boolean useICSFormat)
090    {
091        Map<String, Object> result = new HashMap<>();
092        
093        result.put("id", calendar.getId());
094        result.put("title", Text.unescapeIllegalJcrChars(calendar.getName()));
095        result.put("description", calendar.getDescription());
096        result.put("templateDesc", calendar.getTemplateDescription());
097        result.put("color", calendar.getColor());
098        result.put("visibility", calendar.getVisibility().name().toLowerCase());
099        result.put("public", calendar.getVisibility() == CalendarVisibility.PUBLIC);
100        
101        if (calendar instanceof WorkflowAwareCalendar calendarWA)
102        {
103            result.put("workflowName", calendarWA.getWorkflowName());
104        }
105        
106        result.put("isTaskCalendar", calendar instanceof TaskCalendar);
107        result.put("isTaskCalendarDisabled", calendar instanceof TaskCalendar cal && cal.isDisabled());
108        
109        if (recursive)
110        {
111            List<Map<String, Object>> calendarList = new LinkedList<>();
112            result.put("calendars", calendarList);
113            for (Calendar child : calendar.getChildCalendars())
114            {
115                calendarList.add(getCalendarData(child, recursive, includeEvents, useICSFormat));
116            }
117        }
118        
119        if (includeEvents)
120        {
121            List<Map<String, Object>> eventList = new LinkedList<>();
122            result.put("events", eventList);
123            
124            for (CalendarEvent event : calendar.getAllEvents())
125            {
126                eventList.add(_calendarEventJSONHelper.eventAsJson(event, useICSFormat));
127            }
128        }
129
130        result.put("rights", _extractCalendarRightData(calendar));
131        result.put("token", getCalendarIcsToken(calendar, true));
132        
133        
134        return result;
135    }
136    
137    /**
138     * Get calendar info
139     * @param calendar The calendar
140     * @return the calendar data in a map
141     */
142    public Map<String, Object> getCalendarProperties(Calendar calendar)
143    {
144        return getCalendarData(calendar, false, false, false);
145    }
146    /**
147     * Add a calendar
148     * @param inputName The desired name for the calendar
149     * @param color The calendar color
150     * @param isPublic true if the calendar is public
151     * @return The result map with id, parentId and name keys
152     */
153    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
154    public Map<String, Object> addCalendar(String inputName, String color, boolean isPublic)
155    {
156        String rootId = _getCalendarRoot(true).getId();
157        return addCalendar(rootId, inputName, StringUtils.EMPTY, StringUtils.EMPTY, color, isPublic ? CalendarVisibility.PUBLIC.name() : CalendarVisibility.PRIVATE.name(), "calendar-default", false);
158    }
159    
160    /**
161     * Add a calendar
162     * @param id The identifier of the parent in which the calendar will be added
163     * @param inputName The desired name for the calendar
164     * @param description The calendar description
165     * @param templateDesc The calendar template description
166     * @param color The calendar color
167     * @param visibility The calendar visibility
168     * @param workflowName The calendar workflow name
169     * @param renameIfExists True to rename if existing
170     * @return The result map with id, parentId and name keys
171     */
172    public Map<String, Object> addCalendar(String id, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists)
173    {
174        return addCalendar(_resolver.resolveById(id), inputName, description, templateDesc, color, visibility, workflowName, renameIfExists, true, true);
175    }
176        
177    /**
178     * Add a calendar
179     * @param parent The parent in which the calendar will be added
180     * @param inputName The desired name for the calendar
181     * @param description The calendar description
182     * @param templateDesc The calendar template description
183     * @param color The calendar color
184     * @param visibility The calendar visibility
185     * @param workflowName The calendar workflow name
186     * @param renameIfExists True to rename if existing
187     * @param checkRights true to check if the current user have enough rights to create the calendar
188     * @param notify True to notify the calendar creation
189     * @return The result map with id, parentId and name keys
190     */
191    public Map<String, Object> addCalendar(ModifiableTraversableAmetysObject parent, String inputName, String description, String templateDesc, String color, String visibility, String workflowName, Boolean renameIfExists, Boolean checkRights, boolean notify)
192    {
193        String originalName = Text.escapeIllegalJcrChars(inputName);
194        
195        // Check user right
196        if (checkRights)
197        {
198            _checkUserRights(parent, RIGHTS_CALENDAR_ADD);
199        }
200        
201        if (BooleanUtils.isNotTrue(renameIfExists) && parent.hasChild(originalName))
202        {
203            getLogger().warn("Cannot create the calendar with name '" + originalName + "', an object with same name already exists.");
204            return Map.of("message", "already-exist");
205        }
206        
207        if (!_explorerResourcesDAO.checkLock(parent))
208        {
209            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify the object '" + parent.getName() + "' but it is locked by another user");
210            return Map.of("message", "locked");
211        }
212        
213        int index = 2;
214        String name = originalName;
215        while (parent.hasChild(name))
216        {
217            name = originalName + " (" + index + ")";
218            index++;
219        }
220        
221        JCRCalendar calendar = parent.createChild(name, JCRCalendarFactory.CALENDAR_NODETYPE);
222        calendar.setWorkflowName(workflowName);
223        calendar.setDescription(description);
224        calendar.setTemplateDescription(templateDesc);
225        calendar.setColor(color);
226        calendar.setVisibility(StringUtils.isNotEmpty(visibility) ? CalendarVisibility.valueOf(visibility.toUpperCase()) : CalendarVisibility.PRIVATE);
227        parent.saveChanges();
228        
229        // Notify listeners
230        Map<String, Object> eventParams = new HashMap<>();
231        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
232        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId());
233        eventParams.put(ObservationConstants.ARGS_NAME, name);
234        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
235        
236        if (notify)
237        {
238            _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_CREATED, _currentUserProvider.getUser(), eventParams));
239        }
240        
241        return getCalendarProperties(calendar);
242    }
243
244    /**
245     * Edit a calendar
246     * @param id The identifier of the calendar
247     * @param inputName The new name
248     * @param templateDesc The new calendar template description
249     * @param color The calendar color
250     * @param isPublic true if the calendar is public
251     * @return The result map with id and name keys
252     */
253    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
254    public Map<String, Object> editCalendar(String id, String inputName, String templateDesc, String color, boolean isPublic)
255    {
256        CalendarVisibility visibility = isPublic ? CalendarVisibility.PUBLIC : CalendarVisibility.PRIVATE;
257        
258        assert id != null;
259        String rename = Text.escapeIllegalJcrChars(inputName);
260        
261        JCRCalendar calendar = _resolver.resolveById(id);
262
263        _checkUserRights(calendar, RIGHTS_CALENDAR_EDIT);
264        
265        String name = calendar.getName();
266        ModifiableTraversableAmetysObject parent = calendar.getParent();
267        
268        if (!Strings.CS.equals(rename, name) && parent.hasChild(rename))
269        {
270            getLogger().warn("Cannot edit the calendar with the new name '" + inputName + "', an object with same name already exists.");
271            return Map.of("message", "already-exist");
272        }
273        
274        if (!_explorerResourcesDAO.checkLock(calendar))
275        {
276            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to modify calendar '" + calendar.getName() + "' but it is locked by another user");
277            return Map.of("message", "locked");
278        }
279        
280        if (!Strings.CS.equals(name, rename))
281        {
282            int index = 2;
283            name = Text.escapeIllegalJcrChars(rename);
284            while (parent.hasChild(name))
285            {
286                name = rename + " (" + index + ")";
287                index++;
288            }
289            calendar.rename(name);
290        }
291        
292        calendar.setTemplateDescription(templateDesc);
293        calendar.setColor(color);
294        calendar.setVisibility(visibility);
295        
296        parent.saveChanges();
297        
298        // Notify listeners
299        Map<String, Object> eventParams = new HashMap<>();
300        eventParams.put(ObservationConstants.ARGS_ID, calendar.getId());
301        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parent.getId());
302        eventParams.put(ObservationConstants.ARGS_NAME, name);
303        eventParams.put(ObservationConstants.ARGS_PATH, calendar.getPath());
304        
305        _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_UPDATED, _currentUserProvider.getUser(), eventParams));
306
307        return getCalendarProperties(calendar);
308    }
309    
310    /**
311     * Edit the task calendar
312     * @param inputName the input name
313     * @param color the color
314     * @param isPublic <code>true</code> if the calendar is public
315     * @param disabled <code>true</code> if the calendar is disabled
316     * @return the calendar properties
317     * @throws IllegalAccessException if a right error occurred
318     */
319    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
320    public Map<String, Object> editTaskCalendar(String inputName, String color, boolean isPublic, boolean disabled) throws IllegalAccessException
321    {
322        Project project = _workspaceHelper.getProjectFromRequest();
323        TaskCalendar taskCalendar = getTaskCalendar(project, false);
324        
325        // Check user right
326        _checkUserRights(_getCalendarRoot(project, false), RIGHTS_CALENDAR_EDIT);
327        
328        if (taskCalendar != null)
329        {
330            taskCalendar.rename(inputName);
331            taskCalendar.setColor(color);
332            taskCalendar.setVisibility(isPublic ? CalendarVisibility.PUBLIC : CalendarVisibility.PRIVATE);
333            taskCalendar.disable(disabled);
334        }
335        return getCalendarProperties(taskCalendar);
336    }
337    
338    /**
339     * Delete a calendar
340     * @param id The id of the calendar
341     * @return The result map with id, parent id and message keys
342     */
343    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
344    public Map<String, Object> deleteCalendar(String id)
345    {
346        Map<String, Object> result = new HashMap<>();
347
348        assert id != null;
349        
350        JCRCalendar calendar = _resolver.resolveById(id);
351
352        _checkUserRights(calendar, RIGHTS_CALENDAR_DELETE);
353        
354        if (!_explorerResourcesDAO.checkLock(calendar))
355        {
356            getLogger().warn("User '" + _currentUserProvider.getUser() + "' try to delete calendar'" + calendar.getName() + "' but it is locked by another user");
357            result.put("message", "locked");
358            return result;
359        }
360        
361        ModifiableExplorerNode parent = calendar.getParent();
362        String parentId = parent.getId();
363        String name = calendar.getName();
364        String path = calendar.getPath();
365        
366        calendar.remove();
367        parent.saveChanges();
368     
369        // Notify listeners
370        Map<String, Object> eventParams = new HashMap<>();
371        eventParams.put(ObservationConstants.ARGS_ID, id);
372        eventParams.put(ObservationConstants.ARGS_PARENT_ID, parentId);
373        eventParams.put(ObservationConstants.ARGS_NAME, name);
374        eventParams.put(ObservationConstants.ARGS_PATH, path);
375        
376        _observationManager.notify(new Event(org.ametys.plugins.workspaces.calendars.ObservationConstants.EVENT_CALENDAR_DELETED, _currentUserProvider.getUser(), eventParams));
377        
378        result.put("id", id);
379        result.put("parentId", parentId);
380        
381        return result;
382    }
383        
384    /**
385     * Get or create the calendar ICS token
386     * @param calendar The calendar
387     * @param createIfNotExisting Create the token if none exists for the given calendar
388     * @return The token
389     */
390    public String getCalendarIcsToken(Calendar calendar, boolean createIfNotExisting)
391    {
392        String token = calendar.getIcsUrlToken();
393        
394        if (createIfNotExisting && token == null && calendar instanceof JCRCalendar)
395        {
396            token = UUID.randomUUID().toString();
397            ((JCRCalendar) calendar).setIcsUrlToken(token);
398            ((JCRCalendar) calendar).saveChanges();
399        }
400        
401        return token;
402    }
403    
404    /**
405     * Retrieve the calendar for the matching ICS token
406     * @param token The ICS token
407     * @return The calendar, or null if not found
408     */
409    public Calendar getCalendarFromIcsToken(String token)
410    {
411        if (StringUtils.isEmpty(token))
412        {
413            return null;
414        }
415        
416        Expression expr = new StringExpression(JCRCalendar.CALENDAR_ICS_TOKEN, Operator.EQ, token);
417        String calendarsQuery = QueryHelper.getXPathQuery(null, JCRCalendarFactory.CALENDAR_NODETYPE, expr);
418        AmetysObjectIterable<JCRCalendar> calendars = _resolver.query(calendarsQuery);
419        AmetysObjectIterator<JCRCalendar> calendarsIterator = calendars.iterator();
420        
421        if (calendarsIterator.getSize() > 0)
422        {
423            return calendarsIterator.next();
424        }
425        
426        // Don't find a token in default calendars, so check in the task calendars
427        return _projectManager.getProjects()
428            .stream()
429            .map(p -> this.getTaskCalendar(p, true))
430            .filter(Objects::nonNull)
431            .filter(c -> c.getIcsUrlToken().equals(token))
432            .findFirst()
433            .orElse(null);
434    }
435    
436    /**
437     * Internal method to extract the data concerning the right of the current user for a calendar
438     * @param calendar The calendar
439     * @return The map of right data. Keys are the rights id, and values indicates whether the current user has the right or not.
440     */
441    protected  Map<String, Object> _extractCalendarRightData(Calendar calendar)
442    {
443        Map<String, Object> rightsData = new HashMap<>();
444        
445        UserIdentity user = _currentUserProvider.getUser();
446        boolean isTaskCalendar = calendar instanceof TaskCalendar;
447        
448        // Add
449        rightsData.put("add-event", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendar) == RightResult.RIGHT_ALLOW);
450        
451        // edit - delete
452        rightsData.put("edit", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendar) == RightResult.RIGHT_ALLOW);
453        rightsData.put("delete", !isTaskCalendar && _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendar) == RightResult.RIGHT_ALLOW);
454        
455        return rightsData;
456    }
457    
458    /**
459     * Get the data of every available calendar for the current project
460     * @return the list of calendars
461     */
462    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
463    public List<Map<String, Object>> getCalendars()
464    {
465        Project project = _workspaceHelper.getProjectFromRequest();
466        
467        _checkReadAccess(project, CalendarWorkspaceModule.CALENDAR_MODULE_ID);
468        
469        List<Map<String, Object>> calendarsData = new ArrayList<>();
470        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
471        
472        for (Calendar calendar : calendarModule.getCalendars(project, true))
473        {
474            if (calendarModule.canView(calendar))
475            {
476                calendarsData.add(this.getCalendarProperties(calendar));
477            }
478        }
479        
480        return calendarsData;
481    }
482
483    /**
484     * Get the colors of calendars
485     * @return colors
486     */
487    @Callable (rights = Callable.NO_CHECK_REQUIRED, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
488    public Map<String, CalendarColorsComponent.CalendarColor> getColors()
489    {
490        return _calendarColors.getColors();
491    }
492    
493    /**
494     * Get user rights on root calendar of current project
495     * @return the user rights
496     */
497    @Callable (rights = Callable.NO_CHECK_REQUIRED, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
498    public Map<String, Object> getUserRights()
499    {
500        Map<String, Object> results = new HashMap<>();
501        ModifiableTraversableAmetysObject calendarRoot = _getCalendarRoot(false);
502        
503        UserIdentity user = _currentUserProvider.getUser();
504        results.put("canCreateCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_ADD, calendarRoot) == RightResult.RIGHT_ALLOW);
505        results.put("canEditCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
506        results.put("canRemoveCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_CALENDAR_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW);
507        results.put("canCreateEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_ADD, calendarRoot) == RightResult.RIGHT_ALLOW);
508        results.put("canEditEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
509        results.put("canRemoveAnyEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_DELETE, calendarRoot) == RightResult.RIGHT_ALLOW);
510        results.put("canRemoveSelfEvent", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_DELETE_OWN, calendarRoot) == RightResult.RIGHT_ALLOW);
511        results.put("canHandleResource", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_HANDLE_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW);
512        results.put("canBookResource", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_BOOK_RESOURCE, calendarRoot) == RightResult.RIGHT_ALLOW);
513        results.put("sharePrivateCalendar", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EVENT_EDIT, calendarRoot) == RightResult.RIGHT_ALLOW);
514        results.put("canExportICSLink", calendarRoot != null && _rightManager.hasRight(user, RIGHTS_EXPORT_ICS_LINK, calendarRoot) == RightResult.RIGHT_ALLOW);
515        
516        return results;
517    }
518
519    /**
520     * Get the calendar root
521     * @param createIfNotExist true to create root if not exist yet
522     * @return the calendar root
523     */
524    protected ModifiableTraversableAmetysObject _getCalendarRoot(boolean createIfNotExist)
525    {
526        return _getCalendarRoot(_workspaceHelper.getProjectFromRequest(), createIfNotExist);
527    }
528    
529    /**
530     * Get the calendar root form the project
531     * @param project the project
532     * @param createIfNotExist true to create root if not exist yet
533     * @return the calendar root
534     */
535    protected ModifiableTraversableAmetysObject _getCalendarRoot(Project project, boolean createIfNotExist)
536    {
537        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
538        return calendarModule.getCalendarsRoot(project, createIfNotExist);
539    }
540    
541    /**
542     * Get the data of calendar used to store resources
543     * @return the calendar used to store resources
544     */
545    @Callable (rights = Callable.CHECKED_BY_IMPLEMENTATION, decorators = ForceDefaultRepositoryWorkspaceCallableDecorator.DECORATOR_ID)
546    public Map<String, Object> getResourceCalendar()
547    {
548        Project project = _workspaceHelper.getProjectFromRequest();
549        _checkReadAccess(project, CalendarWorkspaceModule.CALENDAR_MODULE_ID);
550        
551        CalendarWorkspaceModule calendarModule = (CalendarWorkspaceModule) _workspaceModuleEP.getModule(CalendarWorkspaceModule.CALENDAR_MODULE_ID);
552        Calendar calendar = calendarModule.getResourceCalendar(project);
553        
554        return this.getCalendarProperties(calendar);
555    }
556    
557    /**
558     * Get the task calendar
559     * @param project the project
560     * @param onlyIfEnabled <code>true</code> to return the task calendar only if it is enabled
561     * @return the task calendar
562     */
563    public TaskCalendar getTaskCalendar(Project project, boolean onlyIfEnabled)
564    {
565        if (_projectManager.isModuleActivated(project, TasksWorkspaceModule.TASK_MODULE_ID) && _projectManager.isModuleActivated(project, CalendarWorkspaceModule.CALENDAR_MODULE_ID))
566        {
567            JCRResourcesCollection root = (JCRResourcesCollection) _getCalendarRoot(project, false);
568            TaskCalendar calendar = new TaskCalendar(project, root, _taskDAO);
569            return !onlyIfEnabled || !calendar.isDisabled() ? calendar : null;
570        }
571        
572        return null;
573    }
574    
575    /**
576     * <code>true</code> if the current user has read access on the task calendar
577     * @param project the project
578     * @return <code>true</code> if the current user has read access on the task calendar
579     */
580    public boolean hasTaskCalendarReadAccess(Project project)
581    {
582        TasksWorkspaceModule taskModule = (TasksWorkspaceModule) _workspaceModuleEP.getModule(TasksWorkspaceModule.TASK_MODULE_ID);
583        DefaultTraversableAmetysObject tasksRoot = taskModule.getTasksRoot(project, false);
584        if (tasksRoot == null)
585        {
586            return false;
587        }
588        return _rightManager.currentUserHasReadAccess(tasksRoot);
589    }
590}