XmlDataWriter.java
/*******************************************************************************
* Copyright 2013 André Rouél
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package net.sf.uadetector.writer;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map.Entry;
import java.util.SortedSet;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import net.sf.qualitycheck.Check;
import net.sf.uadetector.internal.data.BrowserOperatingSystemMappingComparator;
import net.sf.uadetector.internal.data.Data;
import net.sf.uadetector.internal.data.IdentifiableComparator;
import net.sf.uadetector.internal.data.OrderedPatternComparator;
import net.sf.uadetector.internal.data.domain.Browser;
import net.sf.uadetector.internal.data.domain.BrowserOperatingSystemMapping;
import net.sf.uadetector.internal.data.domain.BrowserPattern;
import net.sf.uadetector.internal.data.domain.BrowserType;
import net.sf.uadetector.internal.data.domain.Device;
import net.sf.uadetector.internal.data.domain.DevicePattern;
import net.sf.uadetector.internal.data.domain.OperatingSystem;
import net.sf.uadetector.internal.data.domain.OperatingSystemPattern;
import net.sf.uadetector.internal.data.domain.Robot;
import net.sf.uadetector.internal.util.RegularExpressionConverter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* This utility is intended to transform an instance of {@code Data} into an <i>UAS data</i> conform XML document and
* allows us to recreate an <code>uas.xml</code>.
*
* @author André Rouél
*/
@ThreadSafe
public final class XmlDataWriter {
interface Tag {
String BOT_INFO_URL = "bot_info_url";
String BROWSER = "browser";
String BROWSER_ID = "browser_id";
String BROWSER_INFO_URL = "browser_info_url";
String BROWSER_OS = "browser_os";
String BROWSER_REG = "browser_reg";
String BROWSER_TYPE = "browser_type";
String BROWSER_TYPES = "browser_types";
String BROWSERS = "browsers";
String BROWSERS_OS = "browsers_os";
String BROWSERS_REG = "browsers_reg";
String COMPANY = "company";
String DATA = "data";
String DESCRIPTION = "description";
String DEVICE = "device";
String DEVICE_ID = "device_id";
String DEVICE_INFO_URL = "device_info_url";
String DEVICE_REG = "device_reg";
String DEVICES = "devices";
String DEVICES_REG = "devices_reg";
String FAMILY = "family";
String ICON = "icon";
String ID = "id";
String LABEL = "label";
String NAME = "name";
String OPERATING_SYSTEM_REG = "operating_system_reg";
String OPERATING_SYSTEMS = "operating_systems";
String OPERATING_SYSTEMS_REG = "operating_systems_reg";
String ORDER = "order";
String OS = "os";
String OS_ID = "os_id";
String OS_INFO_URL = "os_info_url";
String REGSTRING = "regstring";
String ROBOT = "robot";
String ROBOTS = "robots";
String TYPE = "type";
String UASDATA = "uasdata";
String URL = "url";
String URL_COMPANY = "url_company";
String USERAGENT = "useragent";
}
private static final String INDENT_AMOUNT = "4";
private static final String INDENT_OPTION = "yes";
private static final String SCHEMA_URL = "http://user-agent-string.info/rpc/uasxmldata.dtd";
private static Element createBrowser(final Browser browser, final Document doc) {
final Element b = doc.createElement(Tag.BROWSER);
final Element id = doc.createElement(Tag.ID);
id.appendChild(doc.createTextNode(String.valueOf(browser.getId())));
b.appendChild(id);
final Element family = doc.createElement(Tag.TYPE);
family.appendChild(doc.createTextNode(String.valueOf(browser.getType().getId())));
b.appendChild(family);
final Element name = doc.createElement(Tag.NAME);
name.appendChild(doc.createTextNode(browser.getFamilyName()));
b.appendChild(name);
final Element url = doc.createElement(Tag.URL);
url.appendChild(doc.createCDATASection(browser.getUrl()));
b.appendChild(url);
final Element company = doc.createElement(Tag.COMPANY);
company.appendChild(doc.createCDATASection(browser.getProducer()));
b.appendChild(company);
final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
companyUrl.appendChild(doc.createCDATASection(browser.getProducerUrl()));
b.appendChild(companyUrl);
final Element icon = doc.createElement(Tag.ICON);
icon.appendChild(doc.createTextNode(browser.getIcon()));
b.appendChild(icon);
final Element botInfoUrl = doc.createElement(Tag.BROWSER_INFO_URL);
botInfoUrl.appendChild(doc.createTextNode(browser.getInfoUrl()));
b.appendChild(botInfoUrl);
return b;
}
private static Element createBrowserOperatingSystemMappings(final Data data, final Document doc) {
final List<BrowserOperatingSystemMapping> mappings = new ArrayList<BrowserOperatingSystemMapping>(
data.getBrowserToOperatingSystemMappings());
Collections.sort(mappings, BrowserOperatingSystemMappingComparator.INSTANCE);
final Element browserTypesElement = doc.createElement(Tag.BROWSERS_OS);
for (final BrowserOperatingSystemMapping mapping : mappings) {
final Element t = doc.createElement(Tag.BROWSER_OS);
final Element browserId = doc.createElement(Tag.BROWSER_ID);
browserId.appendChild(doc.createTextNode(String.valueOf(mapping.getBrowserId())));
t.appendChild(browserId);
final Element osId = doc.createElement(Tag.OS_ID);
osId.appendChild(doc.createTextNode(String.valueOf(mapping.getOperatingSystemId())));
t.appendChild(osId);
browserTypesElement.appendChild(t);
}
return browserTypesElement;
}
private static Element createBrowserPatterns(final Data data, final Document doc) {
final List<BrowserPattern> patterns = new ArrayList<BrowserPattern>(data.getBrowserPatterns().size());
for (final Entry<Integer, SortedSet<BrowserPattern>> entry : data.getBrowserPatterns().entrySet()) {
patterns.addAll(entry.getValue());
}
Collections.sort(patterns, new OrderedPatternComparator<BrowserPattern>());
final Element browserTypesElement = doc.createElement(Tag.BROWSERS_REG);
for (final BrowserPattern pattern : patterns) {
final Element t = doc.createElement(Tag.BROWSER_REG);
final Element order = doc.createElement(Tag.ORDER);
order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
t.appendChild(order);
final Element id = doc.createElement(Tag.BROWSER_ID);
id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
t.appendChild(id);
final Element family = doc.createElement(Tag.REGSTRING);
family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
t.appendChild(family);
browserTypesElement.appendChild(t);
}
return browserTypesElement;
}
private static Element createBrowsers(final Data data, final Document doc) {
final Element browsersElement = doc.createElement(Tag.BROWSERS);
final List<Browser> browsers = new ArrayList<Browser>(data.getBrowsers());
Collections.sort(browsers, IdentifiableComparator.INSTANCE);
for (final Browser browser : browsers) {
browsersElement.appendChild(createBrowser(browser, doc));
}
return browsersElement;
}
private static Element createBrowserTypes(final Data data, final Document doc) {
final Element browserTypesElement = doc.createElement(Tag.BROWSER_TYPES);
final List<BrowserType> browserTypes = new ArrayList<BrowserType>(data.getBrowserTypes().values());
Collections.sort(browserTypes, IdentifiableComparator.INSTANCE);
for (final BrowserType browserType : browserTypes) {
final Element t = doc.createElement(Tag.BROWSER_TYPE);
final Element id = doc.createElement(Tag.ID);
id.appendChild(doc.createTextNode(String.valueOf(browserType.getId())));
t.appendChild(id);
final Element family = doc.createElement(Tag.TYPE);
family.appendChild(doc.createTextNode(String.valueOf(browserType.getName())));
t.appendChild(family);
browserTypesElement.appendChild(t);
}
return browserTypesElement;
}
private static Element createDescription(@Nonnull final Data data, @Nonnull final Document doc) {
final Element description = doc.createElement(Tag.DESCRIPTION);
final Element label = doc.createElement(Tag.LABEL);
description.appendChild(label).appendChild(
doc.createTextNode("Data (format xml) for UASparser - http://user-agent-string.info/download/UASparser"));
final Element version = doc.createElement("version");
description.appendChild(version).appendChild(doc.createTextNode(data.getVersion()));
final Element md5Checksum = doc.createElement("checksum");
md5Checksum.setAttribute(Tag.TYPE, "MD5");
description.appendChild(md5Checksum).appendChild(
doc.createTextNode("http://user-agent-string.info/rpc/get_data.php?format=xml&md5=y"));
final Element shaChecksum = doc.createElement("checksum");
shaChecksum.setAttribute(Tag.TYPE, "SHA1");
description.appendChild(shaChecksum).appendChild(
doc.createTextNode("http://user-agent-string.info/rpc/get_data.php?format=xml&sha1=y"));
return description;
}
private static Element createDevice(final Device device, final Document doc) {
final Element b = doc.createElement(Tag.DEVICE);
final Element id = doc.createElement(Tag.ID);
id.appendChild(doc.createTextNode(String.valueOf(device.getId())));
b.appendChild(id);
final Element name = doc.createElement(Tag.NAME);
name.appendChild(doc.createTextNode(device.getName()));
b.appendChild(name);
final Element icon = doc.createElement(Tag.ICON);
icon.appendChild(doc.createTextNode(device.getIcon()));
b.appendChild(icon);
final Element botInfoUrl = doc.createElement(Tag.DEVICE_INFO_URL);
botInfoUrl.appendChild(doc.createTextNode(device.getInfoUrl()));
b.appendChild(botInfoUrl);
return b;
}
private static Element createDevicePatterns(final Data data, final Document doc) {
final List<DevicePattern> patterns = new ArrayList<DevicePattern>(data.getDevicePatterns().size());
for (final Entry<Integer, SortedSet<DevicePattern>> entry : data.getDevicePatterns().entrySet()) {
patterns.addAll(entry.getValue());
}
Collections.sort(patterns, new OrderedPatternComparator<DevicePattern>());
final Element deviceTypesElement = doc.createElement(Tag.DEVICES_REG);
for (final DevicePattern pattern : patterns) {
final Element t = doc.createElement(Tag.DEVICE_REG);
final Element order = doc.createElement(Tag.ORDER);
order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
t.appendChild(order);
final Element id = doc.createElement(Tag.DEVICE_ID);
id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
t.appendChild(id);
final Element family = doc.createElement(Tag.REGSTRING);
family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
t.appendChild(family);
deviceTypesElement.appendChild(t);
}
return deviceTypesElement;
}
private static Element createDevices(final Data data, final Document doc) {
final Element devicesElement = doc.createElement(Tag.DEVICES);
final List<Device> devices = new ArrayList<Device>(data.getDevices());
Collections.sort(devices, IdentifiableComparator.INSTANCE);
for (final Device device : devices) {
devicesElement.appendChild(createDevice(device, doc));
}
return devicesElement;
}
private static Element createOperatingSystem(final OperatingSystem operatingSystem, final Document doc) {
final Element os = doc.createElement(Tag.OS);
final Element id = doc.createElement("id");
id.appendChild(doc.createTextNode(String.valueOf(operatingSystem.getId())));
os.appendChild(id);
final Element family = doc.createElement(Tag.FAMILY);
family.appendChild(doc.createTextNode(operatingSystem.getFamily()));
os.appendChild(family);
final Element name = doc.createElement(Tag.NAME);
name.appendChild(doc.createTextNode(operatingSystem.getName()));
os.appendChild(name);
final Element url = doc.createElement(Tag.URL);
url.appendChild(doc.createCDATASection(operatingSystem.getUrl()));
os.appendChild(url);
final Element company = doc.createElement(Tag.COMPANY);
company.appendChild(doc.createCDATASection(operatingSystem.getProducer()));
os.appendChild(company);
final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
companyUrl.appendChild(doc.createCDATASection(operatingSystem.getProducerUrl()));
os.appendChild(companyUrl);
final Element icon = doc.createElement(Tag.ICON);
icon.appendChild(doc.createTextNode(operatingSystem.getIcon()));
os.appendChild(icon);
final Element botInfoUrl = doc.createElement(Tag.OS_INFO_URL);
botInfoUrl.appendChild(doc.createTextNode(operatingSystem.getInfoUrl()));
os.appendChild(botInfoUrl);
return os;
}
private static Element createOperatingSystemPatterns(final Data data, final Document doc) {
final List<OperatingSystemPattern> patterns = new ArrayList<OperatingSystemPattern>(data.getOperatingSystemPatterns().size());
for (final Entry<Integer, SortedSet<OperatingSystemPattern>> entry : data.getOperatingSystemPatterns().entrySet()) {
patterns.addAll(entry.getValue());
}
Collections.sort(patterns, new OrderedPatternComparator<OperatingSystemPattern>());
final Element browserTypesElement = doc.createElement(Tag.OPERATING_SYSTEMS_REG);
for (final OperatingSystemPattern pattern : patterns) {
final Element t = doc.createElement(Tag.OPERATING_SYSTEM_REG);
final Element order = doc.createElement(Tag.ORDER);
order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
t.appendChild(order);
final Element id = doc.createElement(Tag.OS_ID);
id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
t.appendChild(id);
final Element family = doc.createElement(Tag.REGSTRING);
family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
t.appendChild(family);
browserTypesElement.appendChild(t);
}
return browserTypesElement;
}
private static Element createOperatingSystems(final Data data, final Document doc) {
final Element operatingSystemsElement = doc.createElement(Tag.OPERATING_SYSTEMS);
final List<OperatingSystem> operatingSystems = new ArrayList<OperatingSystem>(data.getOperatingSystems());
Collections.sort(operatingSystems, IdentifiableComparator.INSTANCE);
for (final OperatingSystem operatingSystem : operatingSystems) {
operatingSystemsElement.appendChild(createOperatingSystem(operatingSystem, doc));
}
return operatingSystemsElement;
}
private static Element createRobots(final Data data, final Document doc) {
final Element robotsElement = doc.createElement(Tag.ROBOTS);
for (final Robot robot : data.getRobots()) {
robotsElement.appendChild(createRobots(robot, doc));
}
return robotsElement;
}
private static Element createRobots(final Robot robot, final Document doc) {
final Element r = doc.createElement(Tag.ROBOT);
final Element id = doc.createElement(Tag.ID);
id.appendChild(doc.createTextNode(String.valueOf(robot.getId())));
r.appendChild(id);
final Element useragent = doc.createElement(Tag.USERAGENT);
useragent.appendChild(doc.createCDATASection(robot.getUserAgentString()));
r.appendChild(useragent);
final Element family = doc.createElement(Tag.FAMILY);
family.appendChild(doc.createTextNode(robot.getFamilyName()));
r.appendChild(family);
final Element name = doc.createElement(Tag.NAME);
name.appendChild(doc.createTextNode(robot.getName()));
r.appendChild(name);
final Element company = doc.createElement(Tag.COMPANY);
company.appendChild(doc.createCDATASection(robot.getProducer()));
r.appendChild(company);
final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
companyUrl.appendChild(doc.createCDATASection(robot.getProducerUrl()));
r.appendChild(companyUrl);
final Element icon = doc.createElement(Tag.ICON);
icon.appendChild(doc.createTextNode(robot.getIcon()));
r.appendChild(icon);
final Element botInfoUrl = doc.createElement(Tag.BOT_INFO_URL);
botInfoUrl.appendChild(doc.createTextNode(robot.getInfoUrl()));
r.appendChild(botInfoUrl);
return r;
}
@Nonnull
static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
return docFactory.newDocumentBuilder();
}
static void transform(@Nonnull final Source xmlInput, @Nonnull final Result xmlOutput) throws TransformerException {
Check.notNull(xmlInput, "xmlInput");
Check.notNull(xmlOutput, "xmlOutput");
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, INDENT_OPTION);
transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, SCHEMA_URL);
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", INDENT_AMOUNT);
transformer.transform(xmlInput, xmlOutput);
}
/**
* Transforms a given {@code Data} instance into XML and writes it to the passed in {@code OutputStream}.
*
* @param data
* {@code Data} to transform into XML
* @param outputStream
* output stream to write
* @throws ParserConfigurationException
* If a DocumentBuilder cannot be created which satisfies the configuration requested.
* @throws TransformerException
* If an unrecoverable error occurs during the course of the transformation.
*/
public static void write(@Nonnull final Data data, @Nonnull final OutputStream outputStream) throws ParserConfigurationException,
TransformerException {
Check.notNull(data, "data");
Check.notNull(outputStream, "outputStream");
final Document doc = newDocumentBuilder().newDocument();
// root element
final Element uasdataElement = doc.createElement(Tag.UASDATA);
doc.appendChild(uasdataElement);
// description element
uasdataElement.appendChild(createDescription(data, doc));
// data element
final Element dataElement = doc.createElement(Tag.DATA);
uasdataElement.appendChild(dataElement);
dataElement.appendChild(createRobots(data, doc));
dataElement.appendChild(createOperatingSystems(data, doc));
dataElement.appendChild(createBrowsers(data, doc));
dataElement.appendChild(createBrowserTypes(data, doc));
dataElement.appendChild(createBrowserPatterns(data, doc));
dataElement.appendChild(createBrowserOperatingSystemMappings(data, doc));
dataElement.appendChild(createOperatingSystemPatterns(data, doc));
dataElement.appendChild(createDevices(data, doc));
dataElement.appendChild(createDevicePatterns(data, doc));
// write the content to output stream
final DOMSource source = new DOMSource(doc);
final StreamResult result = new StreamResult(outputStream);
transform(source, result);
}
/**
* <strong>Attention:</strong> This class is not intended to create objects from it.
*/
private XmlDataWriter() {
// This class is not intended to create objects from it.
}
}