001/*
002 *  Copyright 2016 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.repository.events;
017
018import java.util.Calendar;
019import java.util.Date;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import javax.jcr.Node;
025import javax.jcr.RepositoryException;
026
027import org.apache.avalon.framework.configuration.Configurable;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033
034import org.ametys.core.user.CurrentUserProvider;
035import org.ametys.core.user.UserIdentity;
036import org.ametys.plugins.core.user.UserHelper;
037import org.ametys.plugins.repository.RepositoryConstants;
038import org.ametys.runtime.i18n.I18nizableText;
039import org.ametys.runtime.plugin.component.AbstractLogEnabled;
040import org.ametys.runtime.plugin.component.PluginAware;
041
042/**
043 * Default implementation for {@link EventType} storing event in JCR
044 */
045public class DefaultEventType extends AbstractLogEnabled implements Configurable, EventType, Serviceable, PluginAware
046{
047    /** The default time range to merge compatible events : 5 minutes */
048    private static final int __MAX_MERGE_EVENT_RANGE = 5 * 60 * 1000;
049    
050    /** The current user provider */
051    protected CurrentUserProvider _currentUserProvider;
052    /** Helper to get users */
053    protected UserHelper _userHelper;
054    
055    private Map<String, I18nizableText> _supportedEventTypes;
056
057    private String _pluginName;
058    
059    public void setPluginInfo(String pluginName, String featureName, String id)
060    {
061        _pluginName = pluginName;
062    }
063    
064    @Override
065    public void configure(Configuration configuration) throws ConfigurationException
066    {
067        _supportedEventTypes = new HashMap<>();
068        
069        Configuration[] supportedTypesConf = configuration.getChild("supported-types").getChildren("supported-type");
070        if (supportedTypesConf.length == 0)
071        {
072            throw new ConfigurationException("Missing 'supported-types' configuration", configuration);
073        }
074        
075        for (Configuration conf : supportedTypesConf)
076        {
077            String id = conf.getAttribute("id");
078            I18nizableText label = I18nizableText.parseI18nizableText(conf.getChild("label"), "plugin." + _pluginName, id);
079            _supportedEventTypes.put(id, label);
080        }
081    }
082    
083    @Override   
084    public void service(ServiceManager serviceManager) throws ServiceException
085    {
086        _currentUserProvider = (CurrentUserProvider) serviceManager.lookup(CurrentUserProvider.ROLE);
087        _userHelper = (UserHelper) serviceManager.lookup(UserHelper.ROLE);
088    }
089    
090    
091    @Override
092    public Map<String, I18nizableText> getSupportedTypes()
093    {
094        return _supportedEventTypes;
095    }
096    
097    @Override
098    public Node storeEvent(String eventId, Map<String, Object> parameters, EventHolder eventHolder) throws RepositoryException
099    {
100        Node eventsNode = eventHolder.getEventsRootNode();
101        Date now = new Date();
102        
103        Node eventNode = null;
104        try
105        {
106            eventNode = JCREventHelper.addEventNode(eventHolder, now, eventId, _currentUserProvider.getUser());
107            
108            storeAdditionalEventData(eventNode, parameters);
109            
110            eventNode.getSession().save();
111            
112            return eventNode;
113        }
114        catch (RepositoryException e)
115        {
116            if (eventNode != null)
117            {
118                eventNode.remove();
119                eventsNode.getSession().save();
120            }
121            throw new EventTypeProcessingException("Failed to create event of type " + eventId, e);
122        }
123    }
124    
125    /**
126     * Store additional data on event
127     * @param eventNode The event node
128     * @param parameters The event's parameters
129     * @throws RepositoryException if an error occurred
130     */
131    protected void storeAdditionalEventData (Node eventNode, Map<String, Object> parameters) throws RepositoryException
132    {
133        // Nothing
134    }
135    
136    @Override
137    public Map<String, Object> event2JSON(Node eventNode) throws RepositoryException
138    {
139        Map<String, Object> event = new HashMap<>();
140        
141        event.put("eventName", eventNode.getName());
142        
143        // Type
144        event.put("type", eventNode.getProperty(EVENT_TYPE).getString());
145        
146        // Date
147        Calendar date = eventNode.getProperty(EVENT_DATE).getDate();
148        event.put("date", date.getTime());
149
150        // Author
151        event.put("author", _userHelper.user2json(_getAuthor(eventNode)));
152
153        return event;
154    }
155    
156    /**
157     * Gets the identity of the author of the given event
158     * @param eventNode The node of the event
159     * @return the identity of the author of the given event
160     * @throws RepositoryException  if an error occurs while manipulating the repository
161     */
162    protected UserIdentity _getAuthor(Node eventNode) throws RepositoryException
163    {
164        Node authorNode = eventNode.getNode(EVENT_AUTHOR);
165        String populationId = authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":population").getString();
166        String login = authorNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ":login").getString();
167        
168        return new UserIdentity(login, populationId);
169    }
170    
171    @Override
172    @SuppressWarnings("unchecked")
173    public boolean isMergeable (Map<String, Object> event1, Map<String, Object> event2)
174    {
175        String type1 = (String) event1.get("type");
176        String type2 = (String) event2.get("type");
177        
178        if (!type1.equals(type2))
179        {
180            return false;
181        }
182        
183        Map<String, Object> author1 = (Map<String, Object>) event1.get("author");
184        Map<String, Object> author2 = (Map<String, Object>) event2.get("author");
185        
186        if (!author1.equals(author2))
187        {
188            return false;
189        }
190        
191        Date date1 = (Date) event1.get("date");
192        Date date2 = (Date) event2.get("date");
193        
194        return date1 != null && date2 != null && Math.abs(date1.getTime() - date2.getTime()) < __MAX_MERGE_EVENT_RANGE;
195    }
196    
197    @Override
198    public Map<String, Object> mergeEvents(List<Map<String, Object>> events)
199    {
200        Map<String, Object> mergedEvent = events.get(0);
201        
202        mergedEvent.put("amount", events.size());
203        
204        // Date range 
205        if (events.size() > 1)
206        {
207            Date startDate = null;
208            Date endDate = null;
209            
210            for (Map<String, Object> event : events)
211            {
212                Date eventDate = (Date) event.get("date");
213                if (startDate == null || eventDate.before(startDate))
214                {
215                    startDate = eventDate;
216                }
217                
218                if (endDate == null || eventDate.after(endDate))
219                {
220                    endDate = eventDate;
221                }
222            }
223            
224            mergedEvent.put("date", startDate);
225            
226            if (endDate != null && startDate != null && (endDate.getTime() - startDate.getTime() > 1000 * 60))
227            {
228                // Ignore end date if events are less than 1 minute apart 
229                mergedEvent.put("endDate", endDate);
230            }
231        }
232        
233        return mergedEvent;
234    }
235}