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