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.ZoneId;
019import java.time.ZonedDateTime;
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;
028import org.apache.commons.lang.StringUtils;
029
030import org.ametys.core.observation.Event;
031import org.ametys.core.observation.ObservationManager;
032import org.ametys.core.user.CurrentUserProvider;
033import org.ametys.core.user.UserIdentity;
034import org.ametys.core.util.DateUtils;
035import org.ametys.plugins.explorer.ExplorerNode;
036import org.ametys.plugins.repository.AmetysObjectResolver;
037import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
038import org.ametys.plugins.workflow.store.AmetysObjectWorkflowStore;
039import org.ametys.plugins.workspaces.calendars.CalendarDAO;
040import org.ametys.plugins.workspaces.calendars.ModifiableCalendar;
041import org.ametys.plugins.workspaces.calendars.ObservationConstants;
042import org.ametys.plugins.workspaces.calendars.events.CalendarEvent;
043import org.ametys.plugins.workspaces.calendars.events.CalendarEventDAO;
044import org.ametys.plugins.workspaces.calendars.events.CalendarEventJSONHelper;
045import org.ametys.plugins.workspaces.calendars.events.ModifiableCalendarEvent;
046import org.ametys.plugins.workspaces.calendars.jcr.JCRCalendarEventFactory;
047import org.ametys.plugins.workspaces.tags.ProjectTagsDAO;
048import org.ametys.plugins.workspaces.workflow.AbstractNodeWorkflowComponent;
049
050import com.opensymphony.module.propertyset.PropertySet;
051import com.opensymphony.workflow.FunctionProvider;
052import com.opensymphony.workflow.StoreException;
053import com.opensymphony.workflow.WorkflowException;
054import com.opensymphony.workflow.spi.WorkflowEntry;
055import com.opensymphony.workflow.spi.WorkflowStore;
056
057/**
058 * Action for adding a calendar event
059 */
060public class AddEventFunction extends AbstractNodeWorkflowComponent implements FunctionProvider
061{
062    /** The Ametys object resolver */
063    protected AmetysObjectResolver _resolver;
064    
065    /** Observer manager. */
066    protected ObservationManager _observationManager;
067    
068    /** The current user provider. */
069    protected CurrentUserProvider _currentUserProvider;
070    
071    /** The project tags DAO */
072    protected ProjectTagsDAO _projectTagsDAO;
073
074    /** Calendar manager for workspaces */
075    protected CalendarDAO _calendarDAO;
076    
077    /** Calendar event manager for workspaces */
078    protected CalendarEventDAO _calendarEventDAO;
079
080    /** The tasks list JSON helper */
081    protected CalendarEventJSONHelper _calendarEventJSONHelper;
082    
083    @Override
084    public void service(ServiceManager smanager) throws ServiceException
085    {
086        super.service(smanager);
087        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
088        _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE);
089        _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
090        _projectTagsDAO = (ProjectTagsDAO) smanager.lookup(ProjectTagsDAO.ROLE);
091        _calendarDAO = (CalendarDAO) smanager.lookup(CalendarDAO.ROLE);
092        _calendarEventDAO = (CalendarEventDAO) smanager.lookup(CalendarEventDAO.ROLE);
093        _calendarEventJSONHelper = (CalendarEventJSONHelper) smanager.lookup(CalendarEventJSONHelper.ROLE);
094    }
095    
096    @Override
097    public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException
098    {
099        @SuppressWarnings("unchecked")
100        Map<String, Object> jsParameters = (Map<String, Object>) transientVars.get("parameters");
101        if (jsParameters == null)
102        {
103            throw new WorkflowException("Missing JS parameters");
104        }
105        
106        ExplorerNode object = getExplorerNode(transientVars);
107       
108        String parentId = (String) jsParameters.get("parentId");
109        
110        UserIdentity user = getUser(transientVars);
111        String selectedNode = (String) jsParameters.get("selectedNode");
112        
113        @SuppressWarnings("unchecked")
114        Map<String, Object> result = (Map<String, Object>) transientVars.get("result");
115        
116        // FIXME EXPLORER-441 Remove "renameIfExists" on calendar events workflow
117        // boolean renameIfExists = (Boolean) jsParameters.get("renameIfExists");
118        assert parentId != null;
119        
120        if (!(object instanceof ModifiableCalendar))
121        {
122            throw new IllegalClassException(ModifiableCalendar.class, object.getClass());
123        }
124        
125        ModifiableCalendar modifiableCalendar = (ModifiableCalendar) object;
126
127        // Create new event
128        ModifiableCalendarEvent event = ((ModifiableTraversableAmetysObject) modifiableCalendar).createChild("ametys:calendar-event", JCRCalendarEventFactory.CALENDAR_EVENT_NODETYPE);
129
130        ZonedDateTime now = ZonedDateTime.now();
131        event.setCreationDate(now);
132        event.setLastModified(now);
133        event.setCreator(user);
134
135        // Set event's properties
136        _setEventData(event, transientVars, jsParameters);
137        
138        // Bind workflow store
139        _initializeWorkflow(event, transientVars, jsParameters);
140        
141        modifiableCalendar.saveChanges();
142
143        transientVars.put("eventId", event.getId());
144        
145        result.put("id", event.getId());
146        result.put("parentId",  selectedNode == null ? parentId : selectedNode); 
147        result.put("event", _calendarEventJSONHelper.eventAsJsonWithOccurrences(event, false, event.getStartDate(), event.getEndDate()));
148        // Notify listeners
149        _notifyListeners(event);
150    }
151    
152    /**
153     * Initialize the workflow store
154     * @param event The event
155     * @param transientVars The transient variables
156     * @param jsParameters The JS parameters
157     * @throws WorkflowException if an error occurred
158     */
159    protected void _initializeWorkflow (ModifiableCalendarEvent event, Map transientVars, Map<String, Object> jsParameters) throws WorkflowException
160    {
161        long workflowId = ((WorkflowEntry) transientVars.get("entry")).getId();
162        
163        try
164        {
165            event.setWorkflowId(workflowId);
166            WorkflowStore workflowStore = (WorkflowStore) transientVars.get("store");
167            
168            if (workflowStore instanceof AmetysObjectWorkflowStore)
169            {
170                AmetysObjectWorkflowStore ametysObjectWorkflowStore = (AmetysObjectWorkflowStore) workflowStore;
171                ametysObjectWorkflowStore.bindAmetysObject(event);
172            }
173        }
174        catch (StoreException e)
175        {
176            throw new WorkflowException("Unable to link the workflow to the content", e);
177        }
178    }
179    
180    /**
181     * Set the event data
182     * @param event The event
183     * @param transientVars The transient variables
184     * @param jsParameters The JS parameters
185     * @throws WorkflowException if an error occurred
186     */
187    protected void _setEventData (ModifiableCalendarEvent event, Map transientVars, Map<String, Object> jsParameters) throws WorkflowException
188    {
189        @SuppressWarnings("unchecked")
190        Map<String, Object> result = (Map<String, Object>) transientVars.get("result");
191        String title = (String) jsParameters.get("title");
192        String desc = (String) jsParameters.get("description");
193        String startDateAsString = (String) jsParameters.get("startDate");
194
195        String zoneId = (String) jsParameters.get("zoneId");
196        ZoneId zone = ZoneId.of(zoneId);
197        event.setZone(zone);
198        
199        ZonedDateTime startDate = DateUtils.parseZonedDateTime(startDateAsString).withZoneSameInstant(zone);
200        String endDateAsString = (String) jsParameters.get("endDate");
201        ZonedDateTime endDate = DateUtils.parseZonedDateTime(endDateAsString).withZoneSameInstant(zone);
202        
203        Object rawFullDay = jsParameters.get("fullDay");
204        Boolean fullDay = rawFullDay instanceof Boolean ? (Boolean) rawFullDay : Boolean.parseBoolean((String) rawFullDay);
205        
206        String recurrenceType = (String) jsParameters.get("recurrenceType");
207        String untilDateAsString = (String) jsParameters.get("untilDate");
208        ZonedDateTime untilDate = DateUtils.parseZonedDateTime(untilDateAsString);
209        
210        UserIdentity user = getUser(transientVars);
211        
212        event.setTitle(title);
213        event.setDescription(desc);
214        event.setFullDay(fullDay);
215        
216        event.setStartDate(startDate);
217        event.setLastContributor(user);
218        event.setLastModified(ZonedDateTime.now());
219        
220        event.setRecurrenceType(recurrenceType);
221        if (untilDate !=  null)
222        {
223            event.setRepeatUntil(untilDate);
224        }
225              
226        event.setEndDate(endDate);
227        
228        String location = (String) jsParameters.get("location");
229        if (StringUtils.isNotEmpty(location))
230        {
231            event.setLocation(location);
232        }
233        
234        @SuppressWarnings("unchecked")
235        List<Object> tags = (List<Object>) jsParameters.getOrDefault("tags", new ArrayList<>());
236        List<String> createdTags = new ArrayList<>();
237        List<Map<String, Object>> createdTagsJson = new ArrayList<>();
238        // Tag new tags
239        for (Object tag : tags)
240        {
241            // Tag doesn't exist so create the tag
242            if (tag instanceof Map)
243            {
244                @SuppressWarnings("unchecked")
245                String tagText = (String) ((Map<String, Object>) tag).get("text");
246                List<Map<String, Object>> newTags = _projectTagsDAO.addTags(new String[] {tagText});
247                String newTag = (String) newTags.get(0).get("name");
248                event.tag(newTag);
249                createdTags.add(newTag);
250                createdTagsJson.addAll(newTags);
251            }
252            else
253            {
254                event.tag((String) tag);
255            }
256        }
257        // Untag unused tags
258        for (String tag : event.getTags())
259        {
260            if (!tags.contains(tag) && !createdTags.contains(tag))
261            {
262                event.untag(tag);
263            }
264        }
265
266        @SuppressWarnings("unchecked")
267        List<String> resources = (List<String>) jsParameters.getOrDefault("resourceIds", new ArrayList<>());
268        event.setResources(resources);
269        result.put("newTags", createdTagsJson);
270    }
271    
272    /**
273     * Notify listeners that the event has been created
274     * @param event The created event
275     */
276    protected void _notifyListeners (CalendarEvent event)
277    {
278        Map<String, Object> eventParams = new HashMap<>();
279        eventParams.put(ObservationConstants.ARGS_CALENDAR, event.getParent());
280        eventParams.put(ObservationConstants.ARGS_CALENDAR_EVENT, event);
281        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_ID, event.getId());
282        eventParams.put(org.ametys.plugins.explorer.ObservationConstants.ARGS_PARENT_ID, event.getParent().getId());
283        
284        _observationManager.notify(new Event(ObservationConstants.EVENT_CALENDAR_EVENT_CREATED, _currentUserProvider.getUser(), eventParams));
285    }
286}