Assume you have a PCollection with your records.
In most cases I see (clickstream data) these records (In this example this class is called “TestRecord”) contain the useragent string in a field and the parsed results must be added to these fields.
Now you must do two things:
Determine the names of the fields you need.
Add an instance of the (abstract) UserAgentAnalysisDoFn function and implement the functions as shown in the example below. Use the YauaaField annotation to get the setter for the requested fields.
Note that the name of the setters is not important, the system looks at the annotation.
Example: Only User-Agent
.apply("Extract Elements from Useragent and Client Hints",
ParDo.of(new UserAgentAnalysisDoFn<TestRecord>(15000) { // Setting the cacheSize@Overridepublic String getUserAgentString(TestRecord record) {
return record.useragent;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("DeviceClass")
publicvoidsetDC(TestRecord record, String value) {
record.deviceClass= value;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("AgentNameVersion")
publicvoidsetANV(TestRecord record, String value) {
record.agentNameVersion= value;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("OperatingSystemNameVersion")
publicvoidsetOSNV(TestRecord record, String value) {
record.operatingSystemNameVersion= value;
}
})
)
Example: User-Agent Client Hints
The only difference with the “Only User-Agent” implementation is that the getRequestHeaders is overridden.
The resulting map should have the original request header names as keys and the actual header value as the value.
Using the analyzer whould then be something like this:
.apply("Extract Elements from Useragent and Client Hints",
ParDo.of(new UserAgentAnalysisDoFn<TestRecord>(15000) { // Setting the cacheSize@Overridepublic Map<String, String>getRequestHeaders(TestRecord element) {
return element.getHeaders();
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("DeviceClass")
publicvoidsetDC(TestRecord record, String value) {
record.deviceClass= value;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("AgentNameVersion")
publicvoidsetANV(TestRecord record, String value) {
record.agentNameVersion= value;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("OperatingSystemNameVersion")
publicvoidsetOSNV(TestRecord record, String value) {
record.operatingSystemNameVersion= value;
}
})
)
Immutable instances in Apache Beam
Apache Beam requires a DoFn to never modify the provided instance and to always return a new instance that is then passed to the next processing step.
To handle this in a generic way UserAgentAnalysisDoFn has a “clone” method that does this by means of doing a round trip through serialization. If you can do a more efficient way for your specific class then please override the clone method.
If you define it as an anonymous inner class as shown above then the system will try to make this class to become public by means of the method .setAccessible(true).
There are situations in which this will fail (amongst others the SecurityManager can block this). If you run into such a scenario then simply ’not’ define it inline as an anonymous class and define it as a named (public) class instead.
So the earlier example will look something like this:
publicclassMyUserAgentAnalysisDoFnextends UserAgentAnalysisDoFn<TestRecord> {
@Overridepublic String getUserAgentString(TestRecord record) {
return record.useragent;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("DeviceClass")
publicvoidsetDC(TestRecord record, String value) {
record.deviceClass= value;
}
@SuppressWarnings("unused") // Called via the annotation@YauaaField("AgentNameVersion")
publicvoidsetANV(TestRecord record, String value) {
record.agentNameVersion= value;
}
}
and then in the topology simply do this
.apply("Extract Elements from Useragent",
ParDo.of(new MyUserAgentAnalysisDoFn()));