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