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.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import javax.jcr.Node;
025import javax.jcr.NodeIterator;
026import javax.jcr.RepositoryException;
027
028import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
029
030/**
031 * This class is in charge of loading the various {@link EventType}.
032 * It also allows to communicate with them for the retrieval of events in JSON
033 */
034public class EventTypeExtensionPoint extends AbstractThreadSafeComponentExtensionPoint<EventType>
035{
036    /** The Avalon Role */
037    public static final String ROLE = EventTypeExtensionPoint.class.getName();
038    
039    /**
040     * Add an event in the wanted event holder
041     * @param eventId the id of the event
042     * @param parameters the parameters of the event
043     * @param eventHolder the event holder node
044     * @return the created node
045     * @throws RepositoryException if an exception occurs while manipulating the repository
046     */
047    public Node addEvent(String eventId, Map<String, Object> parameters, EventHolder eventHolder) throws RepositoryException
048    {
049        return getEventType(eventId).storeEvent(eventId, parameters, eventHolder);
050    }
051
052    /**
053     * Returns events of an event holder as JSON object 
054     * @param eventHolder the event holder
055     * @return the events
056     * @throws RepositoryException if an exception occurs while manipulating the repository
057     */
058    public List<Map<String, Object>> getEvents(EventHolder eventHolder) throws RepositoryException
059    {
060        return getEvents(eventHolder, null);
061    }
062    
063    /**
064     * Returns events of an event holder as JSON object 
065     * @param eventHolder the event holder
066     * @param eventIds the event's types to retrieve. Can be <code>null</code> to retrieve all events.
067     * @return the events
068     * @throws EventTypeProcessingException if failed to get events
069     */
070    public List<Map<String, Object>> getEvents(EventHolder eventHolder, Set<String> eventIds) throws EventTypeProcessingException
071    {
072        return getEvents(eventHolder, eventIds, Integer.MAX_VALUE);
073    }
074    
075    /**
076     * Returns events of an event holder as JSON object 
077     * @param eventHolder the event holder
078     * @param eventIds the event's types to retrieve. Can be <code>null</code> to retrieve all events.
079     * @param limit The max number of events
080     * @return the events
081     * @throws EventTypeProcessingException if failed to get events
082     */
083    public List<Map<String, Object>> getEvents(EventHolder eventHolder, Set<String> eventIds, int limit) throws EventTypeProcessingException
084    {
085        try
086        {
087            // TODO Do a Solr query and filter events on user rights
088            NodeIterator nodesIt = JCREventHelper.getEvents(eventHolder, eventIds != null ? eventIds.toArray(new String[eventIds.size()]) : null);
089            
090            List<Map<String, Object>> events = new ArrayList<>();
091            
092            int count = 0;
093            while (nodesIt.hasNext() && count < limit)
094            {
095                Node eventNode = nodesIt.nextNode();
096                String type = eventNode.getProperty(EventType.EVENT_TYPE).getString();
097                
098                Map<String, Object> event2json = getEventType(type).event2JSON(eventNode);
099                if (!event2json.isEmpty())
100                {
101                    events.add(event2json);
102                }
103                
104                count++;
105            }
106            
107            return events;
108        }
109        catch (RepositoryException e)
110        {
111            throw new EventTypeProcessingException("Failed to get events", e);
112        }
113    }
114    
115    /**
116     * Merge events 
117     * @param initialEvents The events to merge
118     * @return The merged events
119     */
120    public List<Map<String, Object>> mergeEvents(List<Map<String, Object>> initialEvents)
121    {
122        List<Map<String, Object>> mergedEvents = new ArrayList<>();
123        
124        List<Map<String, Object>> events = new ArrayList<>(initialEvents);
125        Map<String, List<Map<String, Object>>> mergeableEvents = new HashMap<>();
126        
127        for (Map<String, Object> event : events)
128        {
129            List<Map<String, Object>> clonedEvents = new ArrayList<>(initialEvents);
130            
131            for (Map<String, Object> clonedEvent : clonedEvents)
132            {
133                String eventName = (String) event.get("eventName");
134                String clonedEventName = (String) clonedEvent.get("eventName");
135                
136                if (!eventName.equals(clonedEventName))
137                {
138                    EventType eventType = getEventType((String) event.get("type"));
139                    EventType clonedEventType = getEventType((String) clonedEvent.get("type"));
140                    
141                    if (eventType == clonedEventType && eventType.isMergeable(event, clonedEvent))
142                    {
143                        // The two events can be merged
144                        if (!mergeableEvents.containsKey(eventName))
145                        {
146                            mergeableEvents.put(eventName, new ArrayList<>());
147                            mergeableEvents.get(eventName).add(event);
148                        }
149                        
150                        mergeableEvents.get(eventName).add(clonedEvent);
151                        
152                        // The events were merged : no need to re-process them
153                        initialEvents.remove(event);
154                        initialEvents.remove(clonedEvent);
155                    }
156                }
157                else
158                {
159                    mergeableEvents.put(eventName, new ArrayList<>());
160                    mergeableEvents.get(eventName).add(event);
161                }
162            }
163        }
164        
165        for (List<Map<String, Object>> evts : mergeableEvents.values())
166        {
167            EventType eventType = getEventType((String) evts.get(0).get("type"));
168            mergedEvents.add(eventType.mergeEvents(evts));
169        }
170        
171        return mergedEvents;
172    }
173    
174    /**
175     * Returns the first {@link EventType} matching the provided event id 
176     * @param eventId the id of the event
177     * @return The event type
178     */
179    public EventType getEventType(String eventId)
180    {
181        for (String extensionId : getExtensionsIds())
182        {
183            EventType eventType = getExtension(extensionId);
184            if (eventType.getSupportedTypes().keySet().contains(eventId))
185            {
186                return eventType;
187            }
188        }
189        
190        throw new EventTypeProcessingException("There is no EventType corresponding with the id '" + eventId + "'.");
191    }
192}