package org.rakam.module.website;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.cookie.Cookie;
import org.apache.avro.generic.GenericRecord;
import org.rakam.Mapper;
import org.rakam.collection.Event;
import org.rakam.collection.FieldDependencyBuilder;
import org.rakam.collection.FieldType;
import org.rakam.collection.SchemaField;
import org.rakam.plugin.EventMapper;
import org.rakam.plugin.SyncEventMapper;
import org.rakam.plugin.user.ISingleUserBatchOperation;
import org.rakam.plugin.user.UserPropertyMapper;
import org.rakam.server.http.HttpRequestException;
import org.rakam.util.MapProxyGenericRecord;
import ua_parser.CachingParser;
import ua_parser.Client;
import ua_parser.Parser;
import javax.inject.Inject;
import java.io.IOException;
import java.net.InetAddress;
import java.util.List;
@Mapper(name = "User Agent Event mapper",
description = "Parses user agent string and attaches new field related with the user agent of the user")
public class UserAgentEventMapper implements SyncEventMapper, UserPropertyMapper {
private final Parser uaParser;
private final boolean trackSpiders;
@Inject
public UserAgentEventMapper(WebsiteMapperConfig config) {
try {
uaParser = new CachingParser();
} catch (IOException e) {
throw Throwables.propagate(e);
}
this.trackSpiders = config.getTrackSpiders();
}
@Override
public List<Cookie> map(String project, List<? extends ISingleUserBatchOperation> user, RequestParams requestParams, InetAddress sourceAddress) {
for (ISingleUserBatchOperation data : user) {
if(data.getSetProperties() != null) {
mapInternal(requestParams, new MapProxyGenericRecord(data.getSetProperties()),
data.getSetProperties().get("_user_agent"));
}
if(data.getSetPropertiesOnce() != null) {
mapInternal(requestParams, new MapProxyGenericRecord(data.getSetPropertiesOnce()),
data.getSetPropertiesOnce().get("_user_agent"));
}
}
return null;
}
@Override
public List<Cookie> map(Event event, RequestParams extraProperties, InetAddress sourceAddress, HttpHeaders responseHeaders) {
GenericRecord properties = event.properties();
mapInternal(extraProperties, properties, properties.get("_user_agent"));
return null;
}
private void mapInternal(RequestParams extraProperties, GenericRecord properties, Object agent) {
String userAgent;
if (agent instanceof Boolean && ((Boolean) agent).booleanValue()) {
userAgent = extraProperties.headers().get("User-Agent");
} else if (agent instanceof String) {
userAgent = (String) agent;
} else {
userAgent = null;
}
if (userAgent != null) {
Client parsed;
try {
parsed = uaParser.parse(userAgent);
} catch (Exception e) {
return;
}
if (parsed.device != null && "Spider".equals(parsed.device.family)) {
// A bit SEO wouldn't hurt.
throw new HttpRequestException("Spiders are not allowed in Rakam Analytics.", HttpResponseStatus.FORBIDDEN);
}
if (properties.get("user_agent_family") == null) {
properties.put("_user_agent_family", parsed.userAgent.family);
}
if (trackSpiders && parsed.userAgent != null && properties.get("_user_agent_version") == null) {
try {
properties.put("_user_agent_version", Long.parseLong(parsed.userAgent.major));
} catch (NumberFormatException e) {
}
}
if (parsed.device != null && properties.get("_device_family") == null) {
properties.put("_device_family", parsed.device.family);
}
if (parsed.os != null) {
if (properties.get("_os") == null) {
properties.put("_os", parsed.os.family);
}
if (parsed.os.major != null && properties.get("_os_version") == null) {
try {
properties.put("_os_version", Long.parseLong(parsed.os.major));
} catch (Exception e) {
}
}
}
}
}
@Override
public void addFieldDependency(FieldDependencyBuilder builder) {
builder.addFields("_user_agent", ImmutableList.of(
new SchemaField("_user_agent_family", FieldType.STRING),
new SchemaField("_user_agent_version", FieldType.LONG),
new SchemaField("_os", FieldType.STRING),
new SchemaField("_os_version", FieldType.LONG),
new SchemaField("_device_family", FieldType.STRING)
));
}
}