001/* 002 * (C) Copyright ${year} Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 016 */ 017 018package org.nuxeo.segment.io; 019 020import java.io.Serializable; 021import java.lang.reflect.Field; 022import java.util.ArrayList; 023import java.util.HashMap; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.joda.time.DateTime; 032import org.nuxeo.ecm.core.api.NuxeoPrincipal; 033import org.nuxeo.runtime.api.Framework; 034import org.nuxeo.runtime.model.ComponentContext; 035import org.nuxeo.runtime.model.ComponentInstance; 036import org.nuxeo.runtime.model.DefaultComponent; 037import org.nuxeo.segment.io.extension.Group; 038import org.osgi.framework.Bundle; 039 040import com.github.segmentio.Analytics; 041import com.github.segmentio.AnalyticsClient; 042import com.github.segmentio.flush.Flusher; 043import com.github.segmentio.models.Context; 044import com.github.segmentio.models.EventProperties; 045import com.github.segmentio.models.Providers; 046import com.github.segmentio.models.Traits; 047 048/** 049 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 050 */ 051public class SegmentIOComponent extends DefaultComponent implements SegmentIO { 052 053 protected static Log log = LogFactory.getLog(SegmentIOComponent.class); 054 055 protected static final String DEFAULT_DEBUG_KEY = "FakeKey_ChangeMe"; 056 057 public final static String WRITE_KEY = "segment.io.write.key"; 058 059 public final static String CONFIG_EP = "config"; 060 061 public final static String MAPPER_EP = "mapper"; 062 063 public final static String PROVIDER_EP = "providers"; 064 065 public final static String FILTERS_EP = "filters"; 066 067 protected boolean debugMode = false; 068 069 protected Map<String, SegmentIOMapper> mappers; 070 071 protected Map<String, List<SegmentIOMapper>> event2Mappers = new HashMap<>(); 072 073 protected List<Map<String, Object>> testData = new LinkedList<>(); 074 075 protected SegmentIOConfig config; 076 077 protected SegmentIOProviders providersConfig; 078 079 protected SegmentIOUserFilter userFilters; 080 081 protected Providers providers; 082 083 protected Bundle bundle; 084 085 protected Flusher flusher; 086 087 public Bundle getBundle() { 088 return bundle; 089 } 090 091 @Override 092 public void activate(ComponentContext context) { 093 bundle = context.getRuntimeContext().getBundle(); 094 mappers = new HashMap<>(); 095 } 096 097 @Override 098 public void deactivate(ComponentContext context) { 099 flush(); 100 bundle = null; 101 } 102 103 @Override 104 public void registerContribution(Object contribution, 105 String extensionPoint, ComponentInstance contributor) { 106 if (CONFIG_EP.equalsIgnoreCase(extensionPoint)) { 107 config = (SegmentIOConfig) contribution; 108 } else if (MAPPER_EP.equalsIgnoreCase(extensionPoint)) { 109 SegmentIOMapper mapper = (SegmentIOMapper) contribution; 110 mappers.put(mapper.name, mapper); 111 } else if (PROVIDER_EP.equalsIgnoreCase(extensionPoint)) { 112 providersConfig = (SegmentIOProviders) contribution; 113 providers = null; 114 } else if (FILTERS_EP.equalsIgnoreCase(extensionPoint)) { 115 userFilters = (SegmentIOUserFilter) contribution; 116 } 117 } 118 119 @Override 120 public void applicationStarted(ComponentContext context) { 121 String key = getWriteKey(); 122 if (DEFAULT_DEBUG_KEY.equals(key)) { 123 log.info("Run Segment.io in debug mode : nothing will be sent to the server"); 124 debugMode = true; 125 } else { 126 Analytics.initialize(key); 127 } 128 computeEvent2Mappers(); 129 } 130 131 protected void computeEvent2Mappers() { 132 event2Mappers = new HashMap<String, List<SegmentIOMapper>>(); 133 for (SegmentIOMapper mapper : mappers.values()) { 134 for (String event : mapper.events) { 135 List<SegmentIOMapper> m4event = event2Mappers.get(event); 136 if (m4event == null) { 137 event2Mappers.put(event, new ArrayList<SegmentIOMapper>()); 138 m4event = event2Mappers.get(event); 139 } 140 if (!m4event.contains(mapper)) { 141 m4event.add(mapper); 142 } 143 } 144 } 145 } 146 147 @Override 148 public String getWriteKey() { 149 if (config != null) { 150 if (config.writeKey != null) { 151 return config.writeKey; 152 } 153 } 154 return Framework.getProperty(WRITE_KEY, DEFAULT_DEBUG_KEY); 155 } 156 157 @Override 158 public Map<String, String> getGlobalParameters() { 159 if (config != null) { 160 if (config.parameters != null) { 161 return config.parameters; 162 } 163 } 164 return new HashMap<>(); 165 } 166 167 protected Flusher getFlusher() { 168 if (flusher == null) { 169 try { 170 AnalyticsClient client = Analytics.getDefaultClient(); 171 Field field = client.getClass().getDeclaredField("flusher"); 172 field.setAccessible(true); 173 flusher = (Flusher) field.get(client); 174 } catch (ReflectiveOperationException e) { 175 log.error("Unable to access SegmentIO Flusher via reflection", 176 e); 177 } 178 } 179 return flusher; 180 } 181 182 @Override 183 public void identify(NuxeoPrincipal principal) { 184 identify(principal, null); 185 } 186 187 @Override 188 public Providers getProviders() { 189 if (providers == null) { 190 providers = new Providers(); 191 if (providersConfig != null) { 192 if (!providersConfig.enableDefaults) { 193 providers.setDefault(false); 194 } 195 for (String name : providersConfig.providers.keySet()) { 196 providers.setEnabled(name, 197 providersConfig.providers.get(name)); 198 } 199 } 200 } 201 return providers; 202 } 203 204 @Override 205 public void identify(NuxeoPrincipal principal, 206 Map<String, Serializable> metadata) { 207 208 SegmentIODataWrapper wrapper = new SegmentIODataWrapper(principal, 209 metadata); 210 211 if (!mustTrackprincipal(wrapper.getUserId())) { 212 log.debug("Skip user " + principal.getName()); 213 return; 214 } 215 216 if (Framework.isTestModeSet()) { 217 pushForTest("identify", wrapper.getUserId(), null, metadata); 218 Map<String, Serializable> groupMeta = wrapper.getGroupMetadata(); 219 if (groupMeta.size() > 0 && groupMeta.containsKey("id")) { 220 pushForTest("group", wrapper.getUserId(), "group", groupMeta); 221 } 222 } else { 223 if (debugMode) { 224 log.info("send identify for " + wrapper.getUserId() 225 + " with meta : " + metadata.toString()); 226 } else { 227 log.debug("send identify with " + metadata.toString()); 228 Traits traits = new Traits(); 229 traits.putAll(wrapper.getMetadata()); 230 Context ctx = new Context(); 231 ctx.setProviders(getProviders()); 232 Analytics.identify(wrapper.getUserId(), traits, ctx); 233 234 Map<String, Serializable> groupMeta = wrapper.getGroupMetadata(); 235 if (groupMeta.size() > 0 && groupMeta.containsKey("id")) { 236 Traits gtraits = new Traits(); 237 gtraits.putAll(groupMeta); 238 group((String) groupMeta.get("id"), wrapper.getUserId(), 239 gtraits, ctx); 240 } else { 241 // automatic grouping 242 if (principal.getCompany() != null) { 243 group(principal.getCompany(), wrapper.getUserId(), 244 null, ctx); 245 } else if (wrapper.getMetadata().get("company") != null) { 246 group((String) wrapper.getMetadata().get("company"), 247 wrapper.getUserId(), null, ctx); 248 } 249 } 250 } 251 } 252 } 253 254 protected void group(String groupId, String userId, Traits traits, 255 Context ctx) { 256 if (groupId == null || groupId.isEmpty()) { 257 return; 258 } 259 Flusher flusher = getFlusher(); 260 if (flusher != null) { 261 Group grp = new Group(userId, groupId, traits, new DateTime(), ctx); 262 flusher.enqueue(grp); 263 } else { 264 log.warn("Can not use Group API"); 265 } 266 } 267 268 protected void pushForTest(String action, String principalName, 269 String eventName, Map<String, Serializable> metadata) { 270 Map<String, Object> data = new HashMap<>(); 271 if (metadata != null) { 272 data.putAll(metadata); 273 } 274 data.put("action", action); 275 if (eventName != null) { 276 data.put("eventName", eventName); 277 } 278 data.put(SegmentIODataWrapper.PRINCIPAL_KEY, principalName); 279 testData.add(data); 280 } 281 282 public List<Map<String, Object>> getTestData() { 283 return testData; 284 } 285 286 protected boolean mustTrackprincipal(String principalName) { 287 SegmentIOUserFilter filter = getUserFilters(); 288 if (filter == null) { 289 return true; 290 } 291 return filter.canTrack(principalName); 292 } 293 294 @Override 295 public void track(NuxeoPrincipal principal, String eventName) { 296 track(principal, null); 297 } 298 299 @Override 300 public void track(NuxeoPrincipal principal, String eventName, 301 Map<String, Serializable> metadata) { 302 303 SegmentIODataWrapper wrapper = new SegmentIODataWrapper(principal, 304 metadata); 305 306 if (!mustTrackprincipal(wrapper.getUserId())) { 307 log.debug("Skip user " + principal.getName()); 308 return; 309 } 310 311 if (Framework.isTestModeSet()) { 312 pushForTest("track", wrapper.getUserId(), eventName, metadata); 313 } else { 314 if (debugMode) { 315 log.info("send track for " + eventName + " user : " 316 + wrapper.getUserId() + " with meta : " 317 + metadata.toString()); 318 } else { 319 log.debug("send track with " + metadata.toString()); 320 EventProperties eventProperties = new EventProperties(); 321 eventProperties.putAll(wrapper.getMetadata()); 322 Context ctx = new Context(); 323 ctx.setProviders(getProviders()); 324 Analytics.track(wrapper.getUserId(), eventName, 325 eventProperties, new DateTime(), ctx); 326 } 327 } 328 } 329 330 @Override 331 public void flush() { 332 if (!debugMode) { 333 // only flush if Analytics was actually initialized 334 Analytics.flush(); 335 } 336 } 337 338 public Map<String, List<SegmentIOMapper>> getMappers(List<String> events) { 339 Map<String, List<SegmentIOMapper>> targetMappers = new HashMap<String, List<SegmentIOMapper>>(); 340 for (String event : events) { 341 if (event2Mappers.containsKey(event)) { 342 targetMappers.put(event, event2Mappers.get(event)); 343 } 344 } 345 return targetMappers; 346 } 347 348 public Set<String> getMappedEvents() { 349 return event2Mappers.keySet(); 350 } 351 352 public Map<String, List<SegmentIOMapper>> getAllMappers() { 353 return event2Mappers; 354 } 355 356 @Override 357 public SegmentIOUserFilter getUserFilters() { 358 return userFilters; 359 } 360 361}