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}