Device detection services for 51Degrees Pipeline

console/OfflineProcessing.java

This example shows how to use 51Degrees device detection to process a file containing many User-Agents.

This example is available in full on GitHub.

This example requires a local data file. The free 'Lite' data file can be downloaded with the scripts in the data GitHub repository, which this repository includes at cxx/data. Fetch the submodules by running git submodule update --init --recursive, then run sh cxx/data/get-data-files.sh 51Degrees-LiteV4.1.hash from the repository root, which saves the file as cxx/data/device-detection/51Degrees-LiteV4.1.hash.

The Lite data file is only used for illustration, and has limited accuracy and capabilities. The Enterprise data file has every property. Contact us about a copy.

/* *********************************************************************
* This Original Work is copyright of 51 Degrees Mobile Experts Limited.
* Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House,
* Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU.
*
* This Original Work is licensed under the European Union Public Licence
* (EUPL) v.1.2 and is subject to its terms as set out below.
*
* If a copy of the EUPL was not distributed with this file, You can obtain
* one at https://opensource.org/licenses/EUPL-1.2.
*
* The 'Compatible Licences' set out in the Appendix to the EUPL (as may be
* amended by the European Commission) shall be deemed incompatible for
* the purposes of the Work and the provisions of the compatibility
* clause in Article 5 of the EUPL shall not apply.
*
* If using the Work as, or as part of, a network application, by
* including the attribution notice(s) required under Article 5 of the EUPL
* in the end user terms of the application under an appropriate heading,
* such notice(s) shall fulfill the requirements of that article.
* ********************************************************************* */
package fiftyone.devicedetection.examples.console;
import fiftyone.devicedetection.DeviceDetectionPipelineBuilder;
import fiftyone.devicedetection.examples.shared.DataFileHelper;
import fiftyone.devicedetection.hash.engine.onpremise.flowelements.DeviceDetectionHashEngine;
import fiftyone.devicedetection.shared.DeviceData;
import fiftyone.pipeline.core.data.FlowData;
import fiftyone.pipeline.core.flowelements.Pipeline;
import fiftyone.pipeline.engines.Constants;
import fiftyone.pipeline.engines.data.AspectPropertyValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import java.io.*;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static fiftyone.common.testhelpers.LogbackHelper.configureLogback;
import static fiftyone.devicedetection.examples.shared.PropertyHelper.asString;
import static fiftyone.pipeline.util.FileFinder.getFilePath;
public class OfflineProcessing {
static final Logger logger = LoggerFactory.getLogger(OfflineProcessing.class);
// This 51degrees "Lite" file is used from the data repository in
// cxx/data, and is downloaded if it is missing
//
// Note that the Lite data file is only used for illustration, and has
// a small number of Properties. Contact us for a copy of the
// Enterprise data file: /contact-us?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-devicedetection-examples-console-offlineprocessing.java&utm_term=lite_v_4_1_hash
public static final String LITE_V_4_1_HASH =
"device-detection/51Degrees-LiteV4.1.hash";
private static final String CONTACT_US = "/contact-us?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-devicedetection-examples-console-offlineprocessing.java&utm_term=enterprise-data-file";
// This 51degrees file of 20,000 examples is used from the same folder,
// and is downloaded if it is missing
public static final String HEADER_EVIDENCE_YML =
"device-detection/20000 Evidence Records.yml";
public static void main(String[] args) throws Exception {
configureLogback(getFilePath("logback.xml"));
File evidenceFile = DataFileHelper.findDataFile(HEADER_EVIDENCE_YML);
run(LITE_V_4_1_HASH, Files.newInputStream(evidenceFile.toPath()), System.out);
}
@SuppressWarnings("unchecked")
public static void run(String dataFile, InputStream is, OutputStream os) throws Exception {
String detectionFile;
try {
detectionFile =
DataFileHelper.findDataFile(dataFile).getAbsolutePath();
} catch (Exception e) {
DataFileHelper.cantFindDataFile(dataFile);
throw e;
}
/*
---- configure YAML for getting evidence and saving the results ----
*/
DumperOptions dumperOptions = new DumperOptions();
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
dumperOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.PLAIN);
dumperOptions.setSplitLines(false);
// get a YAML loader to iterate over the device evidence
Yaml yaml = new Yaml(dumperOptions);
Iterator<Object> evidenceIterator = yaml.loadAll(is).iterator();
/*
---- Build a pipeline ----
*/
logger.info("Constructing pipeline with on-premise hash " +
"engine from file " + dataFile);
// Build a new on-premise Hash engine in a try/resources so
// that the pipeline is disposed when done
try (Pipeline pipeline = new DeviceDetectionPipelineBuilder()
.useOnPremise(detectionFile, false)
// inhibit sharing usage for this test, usually
// this should be set "true"
.setShareUsage(false)
// inhibit auto-update of the data file for this test
.setAutoUpdate(false)
// -- Setting the Profile
// For information on profiles see
// /documentation/_device_detection__features__performance_options.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-devicedetection-examples-console-offlineprocessing.java&utm_term=run
//.setPerformanceProfile(Constants.PerformanceProfiles.MaxPerformance)
//.setPerformanceProfile(Constants.PerformanceProfiles.HighPerformance)
// Low memory profile has detection data streamed from disk on
// demand and is conservative in its use of memory, but
// slower because of disk access
.setPerformanceProfile(Constants.PerformanceProfiles.LowMemory)
//.setPerformanceProfile(Constants.PerformanceProfiles.Balanced)
// -- Setting the Graph
// see /documentation/_device_detection__hash.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-devicedetection-examples-console-offlineprocessing.java&utm_term=run#DeviceDetection_Hash_DataSetProduction
//.setUsePerformanceGraph(false)
//.setUsePredictiveGraph(true)
// -- Setting Predictive Power
// see /documentation/_device_detection__hash.html?utm_source=code&utm_medium=example&utm_campaign=51degrees-java&utm_content=console-src-main-java-fiftyone-devicedetection-examples-console-offlineprocessing.java&utm_term=run#DeviceDetection_Hash_PredictivePower
//.setDifference(0)
//.setDrift(0)
.build()) {
// get the details of the detection engine from the pipeline,
// to find out what data file we are using
DeviceDetectionHashEngine engine = pipeline.getElement(DeviceDetectionHashEngine.class);
logger.info("Device data file was created {}", engine.getDataFilePublishedDate());
/*
---- Iterate over the evidence ----
*/
// open a writer to collect the results
try (Writer writer = new OutputStreamWriter(os)) {
// read a batch of device data from the stream
int count = 0;
boolean needsEnterprise = false;
while (evidenceIterator.hasNext() && count < 20) {
// Flow data is the container for inputs and outputs that
// flow through the pipeline a flowdata instance is
// created by the pipeline factory method it's important
// to dispose flowdata - so wrap in a try/resources
try (FlowData flowData = pipeline.createFlowData()) {
// the evidence values in the test YAML data are read
// as a Map<String, String> - add the evidence to the
// flowData
flowData.addEvidence(
filterEvidence((Map<String, String>) evidenceIterator.next(),
"header."));
/*
---- Do the detection ----
*/
// carry out device-detection (and other
// pipeline actions) on the evidence
flowData.process();
// extract device data from the flowData
DeviceData device = flowData.get(DeviceData.class);
/*
---- use the device data - output to YAML in this case
*/
Map<String, ? super Object> resultMap = new HashMap<>();
resultMap.put("device.ismobile", asString(device.getIsMobile()));
resultMap.put("device.platformname", asString(device.getPlatformName()));
resultMap.put("device.platformversion", asString(device.getPlatformVersion()));
// Only the Enterprise data file has these properties.
// The free Lite data file has the ones above.
needsEnterprise |= putEnterpriseValue(resultMap,
"device.hardwarevendor", engine, "HardwareVendor",
device::getHardwareVendor);
needsEnterprise |= putEnterpriseValue(resultMap,
"device.hardwarename", engine, "HardwareName",
device::getHardwareName);
needsEnterprise |= putEnterpriseValue(resultMap,
"device.devicetype", engine, "DeviceType",
device::getDeviceType);
// to look at all device detection properties use the following:
// resultMap.putAll(getPopulatedProperties(device, "device."));
// write document to output stream as a YAML document
writer.write("---\n");
yaml.dump(flowData.getEvidence().asKeyMap(), writer);
yaml.dump(resultMap, writer);
writer.flush();
}
count++;
}
if (needsEnterprise) {
// A comment keeps the output a valid YAML stream.
writer.write("# Some of these properties need the " +
"Enterprise data file. Contact us for a copy at " +
CONTACT_US + "\n");
}
// finish the last YAML document
writer.write("...\n");
writer.flush();
logger.info("Finished processing {} records", count);
}
}
}
private static boolean putEnterpriseValue(Map<String, ? super Object> resultMap,
String key,
DeviceDetectionHashEngine engine,
String property,
Supplier<AspectPropertyValue<?>> value) {
if (DataFileHelper.hasProperty(engine, property)) {
resultMap.put(key, asString(value.get()));
return false;
}
resultMap.put(key, DataFileHelper.NEEDS_ENTERPRISE);
return true;
}
@SuppressWarnings("SameParameterValue")
private static Map<String, String> filterEvidence(Map<String, String> evidence, String prefix) {
return evidence.entrySet()
.stream()
.filter(e -> e.getKey().startsWith(prefix))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@SuppressWarnings({"SameParameterValue", "unused"})
private static Map<String, Object> getPopulatedProperties(DeviceData data, String prefix) {
return data.asKeyMap().entrySet()
.stream()
.filter(e -> ((AspectPropertyValue<?>) e.getValue()).hasValue())
.collect(Collectors.toMap(e -> prefix + e.getKey(),
e -> ((AspectPropertyValue<?>)e.getValue()).getValue()));
}
}
static void run(String dataFile, InputStream is, OutputStream os)
Process a YAML representation of evidence - and create a YAML output containing the processed evidenc...
Definition OfflineProcessing.java:106