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}