XmlDataWriter.java

  1. /*******************************************************************************
  2.  * Copyright 2013 André Rouél
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  ******************************************************************************/
  16. package net.sf.uadetector.writer;

  17. import java.io.OutputStream;
  18. import java.util.ArrayList;
  19. import java.util.Collections;
  20. import java.util.List;
  21. import java.util.Map.Entry;
  22. import java.util.SortedSet;

  23. import javax.annotation.Nonnull;
  24. import javax.annotation.concurrent.ThreadSafe;
  25. import javax.xml.parsers.DocumentBuilder;
  26. import javax.xml.parsers.DocumentBuilderFactory;
  27. import javax.xml.parsers.ParserConfigurationException;
  28. import javax.xml.transform.OutputKeys;
  29. import javax.xml.transform.Result;
  30. import javax.xml.transform.Source;
  31. import javax.xml.transform.Transformer;
  32. import javax.xml.transform.TransformerException;
  33. import javax.xml.transform.TransformerFactory;
  34. import javax.xml.transform.dom.DOMSource;
  35. import javax.xml.transform.stream.StreamResult;

  36. import net.sf.qualitycheck.Check;
  37. import net.sf.uadetector.internal.data.BrowserOperatingSystemMappingComparator;
  38. import net.sf.uadetector.internal.data.Data;
  39. import net.sf.uadetector.internal.data.IdentifiableComparator;
  40. import net.sf.uadetector.internal.data.OrderedPatternComparator;
  41. import net.sf.uadetector.internal.data.domain.Browser;
  42. import net.sf.uadetector.internal.data.domain.BrowserOperatingSystemMapping;
  43. import net.sf.uadetector.internal.data.domain.BrowserPattern;
  44. import net.sf.uadetector.internal.data.domain.BrowserType;
  45. import net.sf.uadetector.internal.data.domain.Device;
  46. import net.sf.uadetector.internal.data.domain.DevicePattern;
  47. import net.sf.uadetector.internal.data.domain.OperatingSystem;
  48. import net.sf.uadetector.internal.data.domain.OperatingSystemPattern;
  49. import net.sf.uadetector.internal.data.domain.Robot;
  50. import net.sf.uadetector.internal.util.RegularExpressionConverter;

  51. import org.w3c.dom.Document;
  52. import org.w3c.dom.Element;

  53. /**
  54.  * This utility is intended to transform an instance of {@code Data} into an <i>UAS data</i> conform XML document and
  55.  * allows us to recreate an <code>uas.xml</code>.
  56.  *
  57.  * @author André Rouél
  58.  */
  59. @ThreadSafe
  60. public final class XmlDataWriter {

  61.     interface Tag {
  62.         String BOT_INFO_URL = "bot_info_url";
  63.         String BROWSER = "browser";
  64.         String BROWSER_ID = "browser_id";
  65.         String BROWSER_INFO_URL = "browser_info_url";
  66.         String BROWSER_OS = "browser_os";
  67.         String BROWSER_REG = "browser_reg";
  68.         String BROWSER_TYPE = "browser_type";
  69.         String BROWSER_TYPES = "browser_types";
  70.         String BROWSERS = "browsers";
  71.         String BROWSERS_OS = "browsers_os";
  72.         String BROWSERS_REG = "browsers_reg";
  73.         String COMPANY = "company";
  74.         String DATA = "data";
  75.         String DESCRIPTION = "description";
  76.         String DEVICE = "device";
  77.         String DEVICE_ID = "device_id";
  78.         String DEVICE_INFO_URL = "device_info_url";
  79.         String DEVICE_REG = "device_reg";
  80.         String DEVICES = "devices";
  81.         String DEVICES_REG = "devices_reg";
  82.         String FAMILY = "family";
  83.         String ICON = "icon";
  84.         String ID = "id";
  85.         String LABEL = "label";
  86.         String NAME = "name";
  87.         String OPERATING_SYSTEM_REG = "operating_system_reg";
  88.         String OPERATING_SYSTEMS = "operating_systems";
  89.         String OPERATING_SYSTEMS_REG = "operating_systems_reg";
  90.         String ORDER = "order";
  91.         String OS = "os";
  92.         String OS_ID = "os_id";
  93.         String OS_INFO_URL = "os_info_url";
  94.         String REGSTRING = "regstring";
  95.         String ROBOT = "robot";
  96.         String ROBOTS = "robots";
  97.         String TYPE = "type";
  98.         String UASDATA = "uasdata";
  99.         String URL = "url";
  100.         String URL_COMPANY = "url_company";
  101.         String USERAGENT = "useragent";
  102.     }

  103.     private static final String INDENT_AMOUNT = "4";

  104.     private static final String INDENT_OPTION = "yes";

  105.     private static final String SCHEMA_URL = "http://user-agent-string.info/rpc/uasxmldata.dtd";

  106.     private static Element createBrowser(final Browser browser, final Document doc) {
  107.         final Element b = doc.createElement(Tag.BROWSER);
  108.         final Element id = doc.createElement(Tag.ID);
  109.         id.appendChild(doc.createTextNode(String.valueOf(browser.getId())));
  110.         b.appendChild(id);
  111.         final Element family = doc.createElement(Tag.TYPE);
  112.         family.appendChild(doc.createTextNode(String.valueOf(browser.getType().getId())));
  113.         b.appendChild(family);
  114.         final Element name = doc.createElement(Tag.NAME);
  115.         name.appendChild(doc.createTextNode(browser.getFamilyName()));
  116.         b.appendChild(name);
  117.         final Element url = doc.createElement(Tag.URL);
  118.         url.appendChild(doc.createCDATASection(browser.getUrl()));
  119.         b.appendChild(url);
  120.         final Element company = doc.createElement(Tag.COMPANY);
  121.         company.appendChild(doc.createCDATASection(browser.getProducer()));
  122.         b.appendChild(company);
  123.         final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
  124.         companyUrl.appendChild(doc.createCDATASection(browser.getProducerUrl()));
  125.         b.appendChild(companyUrl);
  126.         final Element icon = doc.createElement(Tag.ICON);
  127.         icon.appendChild(doc.createTextNode(browser.getIcon()));
  128.         b.appendChild(icon);
  129.         final Element botInfoUrl = doc.createElement(Tag.BROWSER_INFO_URL);
  130.         botInfoUrl.appendChild(doc.createTextNode(browser.getInfoUrl()));
  131.         b.appendChild(botInfoUrl);
  132.         return b;
  133.     }

  134.     private static Element createBrowserOperatingSystemMappings(final Data data, final Document doc) {
  135.         final List<BrowserOperatingSystemMapping> mappings = new ArrayList<BrowserOperatingSystemMapping>(
  136.                 data.getBrowserToOperatingSystemMappings());
  137.         Collections.sort(mappings, BrowserOperatingSystemMappingComparator.INSTANCE);

  138.         final Element browserTypesElement = doc.createElement(Tag.BROWSERS_OS);
  139.         for (final BrowserOperatingSystemMapping mapping : mappings) {
  140.             final Element t = doc.createElement(Tag.BROWSER_OS);
  141.             final Element browserId = doc.createElement(Tag.BROWSER_ID);
  142.             browserId.appendChild(doc.createTextNode(String.valueOf(mapping.getBrowserId())));
  143.             t.appendChild(browserId);
  144.             final Element osId = doc.createElement(Tag.OS_ID);
  145.             osId.appendChild(doc.createTextNode(String.valueOf(mapping.getOperatingSystemId())));
  146.             t.appendChild(osId);
  147.             browserTypesElement.appendChild(t);
  148.         }
  149.         return browserTypesElement;
  150.     }

  151.     private static Element createBrowserPatterns(final Data data, final Document doc) {
  152.         final List<BrowserPattern> patterns = new ArrayList<BrowserPattern>(data.getBrowserPatterns().size());
  153.         for (final Entry<Integer, SortedSet<BrowserPattern>> entry : data.getBrowserPatterns().entrySet()) {
  154.             patterns.addAll(entry.getValue());
  155.         }
  156.         Collections.sort(patterns, new OrderedPatternComparator<BrowserPattern>());

  157.         final Element browserTypesElement = doc.createElement(Tag.BROWSERS_REG);
  158.         for (final BrowserPattern pattern : patterns) {
  159.             final Element t = doc.createElement(Tag.BROWSER_REG);
  160.             final Element order = doc.createElement(Tag.ORDER);
  161.             order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
  162.             t.appendChild(order);
  163.             final Element id = doc.createElement(Tag.BROWSER_ID);
  164.             id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
  165.             t.appendChild(id);
  166.             final Element family = doc.createElement(Tag.REGSTRING);
  167.             family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
  168.             t.appendChild(family);
  169.             browserTypesElement.appendChild(t);
  170.         }
  171.         return browserTypesElement;
  172.     }

  173.     private static Element createBrowsers(final Data data, final Document doc) {
  174.         final Element browsersElement = doc.createElement(Tag.BROWSERS);
  175.         final List<Browser> browsers = new ArrayList<Browser>(data.getBrowsers());
  176.         Collections.sort(browsers, IdentifiableComparator.INSTANCE);
  177.         for (final Browser browser : browsers) {
  178.             browsersElement.appendChild(createBrowser(browser, doc));
  179.         }
  180.         return browsersElement;
  181.     }

  182.     private static Element createBrowserTypes(final Data data, final Document doc) {
  183.         final Element browserTypesElement = doc.createElement(Tag.BROWSER_TYPES);
  184.         final List<BrowserType> browserTypes = new ArrayList<BrowserType>(data.getBrowserTypes().values());
  185.         Collections.sort(browserTypes, IdentifiableComparator.INSTANCE);
  186.         for (final BrowserType browserType : browserTypes) {
  187.             final Element t = doc.createElement(Tag.BROWSER_TYPE);
  188.             final Element id = doc.createElement(Tag.ID);
  189.             id.appendChild(doc.createTextNode(String.valueOf(browserType.getId())));
  190.             t.appendChild(id);
  191.             final Element family = doc.createElement(Tag.TYPE);
  192.             family.appendChild(doc.createTextNode(String.valueOf(browserType.getName())));
  193.             t.appendChild(family);
  194.             browserTypesElement.appendChild(t);
  195.         }
  196.         return browserTypesElement;
  197.     }

  198.     private static Element createDescription(@Nonnull final Data data, @Nonnull final Document doc) {
  199.         final Element description = doc.createElement(Tag.DESCRIPTION);
  200.         final Element label = doc.createElement(Tag.LABEL);
  201.         description.appendChild(label).appendChild(
  202.                 doc.createTextNode("Data (format xml) for UASparser - http://user-agent-string.info/download/UASparser"));
  203.         final Element version = doc.createElement("version");
  204.         description.appendChild(version).appendChild(doc.createTextNode(data.getVersion()));
  205.         final Element md5Checksum = doc.createElement("checksum");
  206.         md5Checksum.setAttribute(Tag.TYPE, "MD5");
  207.         description.appendChild(md5Checksum).appendChild(
  208.                 doc.createTextNode("http://user-agent-string.info/rpc/get_data.php?format=xml&md5=y"));
  209.         final Element shaChecksum = doc.createElement("checksum");
  210.         shaChecksum.setAttribute(Tag.TYPE, "SHA1");
  211.         description.appendChild(shaChecksum).appendChild(
  212.                 doc.createTextNode("http://user-agent-string.info/rpc/get_data.php?format=xml&sha1=y"));
  213.         return description;
  214.     }

  215.     private static Element createDevice(final Device device, final Document doc) {
  216.         final Element b = doc.createElement(Tag.DEVICE);
  217.         final Element id = doc.createElement(Tag.ID);
  218.         id.appendChild(doc.createTextNode(String.valueOf(device.getId())));
  219.         b.appendChild(id);
  220.         final Element name = doc.createElement(Tag.NAME);
  221.         name.appendChild(doc.createTextNode(device.getName()));
  222.         b.appendChild(name);
  223.         final Element icon = doc.createElement(Tag.ICON);
  224.         icon.appendChild(doc.createTextNode(device.getIcon()));
  225.         b.appendChild(icon);
  226.         final Element botInfoUrl = doc.createElement(Tag.DEVICE_INFO_URL);
  227.         botInfoUrl.appendChild(doc.createTextNode(device.getInfoUrl()));
  228.         b.appendChild(botInfoUrl);
  229.         return b;
  230.     }

  231.     private static Element createDevicePatterns(final Data data, final Document doc) {
  232.         final List<DevicePattern> patterns = new ArrayList<DevicePattern>(data.getDevicePatterns().size());
  233.         for (final Entry<Integer, SortedSet<DevicePattern>> entry : data.getDevicePatterns().entrySet()) {
  234.             patterns.addAll(entry.getValue());
  235.         }
  236.         Collections.sort(patterns, new OrderedPatternComparator<DevicePattern>());

  237.         final Element deviceTypesElement = doc.createElement(Tag.DEVICES_REG);
  238.         for (final DevicePattern pattern : patterns) {
  239.             final Element t = doc.createElement(Tag.DEVICE_REG);
  240.             final Element order = doc.createElement(Tag.ORDER);
  241.             order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
  242.             t.appendChild(order);
  243.             final Element id = doc.createElement(Tag.DEVICE_ID);
  244.             id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
  245.             t.appendChild(id);
  246.             final Element family = doc.createElement(Tag.REGSTRING);
  247.             family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
  248.             t.appendChild(family);
  249.             deviceTypesElement.appendChild(t);
  250.         }
  251.         return deviceTypesElement;
  252.     }

  253.     private static Element createDevices(final Data data, final Document doc) {
  254.         final Element devicesElement = doc.createElement(Tag.DEVICES);
  255.         final List<Device> devices = new ArrayList<Device>(data.getDevices());
  256.         Collections.sort(devices, IdentifiableComparator.INSTANCE);
  257.         for (final Device device : devices) {
  258.             devicesElement.appendChild(createDevice(device, doc));
  259.         }
  260.         return devicesElement;
  261.     }

  262.     private static Element createOperatingSystem(final OperatingSystem operatingSystem, final Document doc) {
  263.         final Element os = doc.createElement(Tag.OS);
  264.         final Element id = doc.createElement("id");
  265.         id.appendChild(doc.createTextNode(String.valueOf(operatingSystem.getId())));
  266.         os.appendChild(id);
  267.         final Element family = doc.createElement(Tag.FAMILY);
  268.         family.appendChild(doc.createTextNode(operatingSystem.getFamily()));
  269.         os.appendChild(family);
  270.         final Element name = doc.createElement(Tag.NAME);
  271.         name.appendChild(doc.createTextNode(operatingSystem.getName()));
  272.         os.appendChild(name);
  273.         final Element url = doc.createElement(Tag.URL);
  274.         url.appendChild(doc.createCDATASection(operatingSystem.getUrl()));
  275.         os.appendChild(url);
  276.         final Element company = doc.createElement(Tag.COMPANY);
  277.         company.appendChild(doc.createCDATASection(operatingSystem.getProducer()));
  278.         os.appendChild(company);
  279.         final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
  280.         companyUrl.appendChild(doc.createCDATASection(operatingSystem.getProducerUrl()));
  281.         os.appendChild(companyUrl);
  282.         final Element icon = doc.createElement(Tag.ICON);
  283.         icon.appendChild(doc.createTextNode(operatingSystem.getIcon()));
  284.         os.appendChild(icon);
  285.         final Element botInfoUrl = doc.createElement(Tag.OS_INFO_URL);
  286.         botInfoUrl.appendChild(doc.createTextNode(operatingSystem.getInfoUrl()));
  287.         os.appendChild(botInfoUrl);
  288.         return os;
  289.     }

  290.     private static Element createOperatingSystemPatterns(final Data data, final Document doc) {
  291.         final List<OperatingSystemPattern> patterns = new ArrayList<OperatingSystemPattern>(data.getOperatingSystemPatterns().size());
  292.         for (final Entry<Integer, SortedSet<OperatingSystemPattern>> entry : data.getOperatingSystemPatterns().entrySet()) {
  293.             patterns.addAll(entry.getValue());
  294.         }
  295.         Collections.sort(patterns, new OrderedPatternComparator<OperatingSystemPattern>());

  296.         final Element browserTypesElement = doc.createElement(Tag.OPERATING_SYSTEMS_REG);
  297.         for (final OperatingSystemPattern pattern : patterns) {
  298.             final Element t = doc.createElement(Tag.OPERATING_SYSTEM_REG);
  299.             final Element order = doc.createElement(Tag.ORDER);
  300.             order.appendChild(doc.createTextNode(String.valueOf(pattern.getPosition())));
  301.             t.appendChild(order);
  302.             final Element id = doc.createElement(Tag.OS_ID);
  303.             id.appendChild(doc.createTextNode(String.valueOf(pattern.getId())));
  304.             t.appendChild(id);
  305.             final Element family = doc.createElement(Tag.REGSTRING);
  306.             family.appendChild(doc.createTextNode(RegularExpressionConverter.convertPatternToPerlRegex(pattern.getPattern())));
  307.             t.appendChild(family);
  308.             browserTypesElement.appendChild(t);
  309.         }
  310.         return browserTypesElement;
  311.     }

  312.     private static Element createOperatingSystems(final Data data, final Document doc) {
  313.         final Element operatingSystemsElement = doc.createElement(Tag.OPERATING_SYSTEMS);
  314.         final List<OperatingSystem> operatingSystems = new ArrayList<OperatingSystem>(data.getOperatingSystems());
  315.         Collections.sort(operatingSystems, IdentifiableComparator.INSTANCE);
  316.         for (final OperatingSystem operatingSystem : operatingSystems) {
  317.             operatingSystemsElement.appendChild(createOperatingSystem(operatingSystem, doc));
  318.         }
  319.         return operatingSystemsElement;
  320.     }

  321.     private static Element createRobots(final Data data, final Document doc) {
  322.         final Element robotsElement = doc.createElement(Tag.ROBOTS);
  323.         for (final Robot robot : data.getRobots()) {
  324.             robotsElement.appendChild(createRobots(robot, doc));
  325.         }
  326.         return robotsElement;
  327.     }

  328.     private static Element createRobots(final Robot robot, final Document doc) {
  329.         final Element r = doc.createElement(Tag.ROBOT);
  330.         final Element id = doc.createElement(Tag.ID);
  331.         id.appendChild(doc.createTextNode(String.valueOf(robot.getId())));
  332.         r.appendChild(id);
  333.         final Element useragent = doc.createElement(Tag.USERAGENT);
  334.         useragent.appendChild(doc.createCDATASection(robot.getUserAgentString()));
  335.         r.appendChild(useragent);
  336.         final Element family = doc.createElement(Tag.FAMILY);
  337.         family.appendChild(doc.createTextNode(robot.getFamilyName()));
  338.         r.appendChild(family);
  339.         final Element name = doc.createElement(Tag.NAME);
  340.         name.appendChild(doc.createTextNode(robot.getName()));
  341.         r.appendChild(name);
  342.         final Element company = doc.createElement(Tag.COMPANY);
  343.         company.appendChild(doc.createCDATASection(robot.getProducer()));
  344.         r.appendChild(company);
  345.         final Element companyUrl = doc.createElement(Tag.URL_COMPANY);
  346.         companyUrl.appendChild(doc.createCDATASection(robot.getProducerUrl()));
  347.         r.appendChild(companyUrl);
  348.         final Element icon = doc.createElement(Tag.ICON);
  349.         icon.appendChild(doc.createTextNode(robot.getIcon()));
  350.         r.appendChild(icon);
  351.         final Element botInfoUrl = doc.createElement(Tag.BOT_INFO_URL);
  352.         botInfoUrl.appendChild(doc.createTextNode(robot.getInfoUrl()));
  353.         r.appendChild(botInfoUrl);
  354.         return r;
  355.     }

  356.     @Nonnull
  357.     static DocumentBuilder newDocumentBuilder() throws ParserConfigurationException {
  358.         final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
  359.         return docFactory.newDocumentBuilder();
  360.     }

  361.     static void transform(@Nonnull final Source xmlInput, @Nonnull final Result xmlOutput) throws TransformerException {
  362.         Check.notNull(xmlInput, "xmlInput");
  363.         Check.notNull(xmlOutput, "xmlOutput");

  364.         final TransformerFactory transformerFactory = TransformerFactory.newInstance();
  365.         final Transformer transformer = transformerFactory.newTransformer();
  366.         transformer.setOutputProperty(OutputKeys.INDENT, INDENT_OPTION);
  367.         transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, SCHEMA_URL);
  368.         transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", INDENT_AMOUNT);
  369.         transformer.transform(xmlInput, xmlOutput);
  370.     }

  371.     /**
  372.      * Transforms a given {@code Data} instance into XML and writes it to the passed in {@code OutputStream}.
  373.      *
  374.      * @param data
  375.      *            {@code Data} to transform into XML
  376.      * @param outputStream
  377.      *            output stream to write
  378.      * @throws ParserConfigurationException
  379.      *             If a DocumentBuilder cannot be created which satisfies the configuration requested.
  380.      * @throws TransformerException
  381.      *             If an unrecoverable error occurs during the course of the transformation.
  382.      */
  383.     public static void write(@Nonnull final Data data, @Nonnull final OutputStream outputStream) throws ParserConfigurationException,
  384.             TransformerException {
  385.         Check.notNull(data, "data");
  386.         Check.notNull(outputStream, "outputStream");

  387.         final Document doc = newDocumentBuilder().newDocument();

  388.         // root element
  389.         final Element uasdataElement = doc.createElement(Tag.UASDATA);
  390.         doc.appendChild(uasdataElement);

  391.         // description element
  392.         uasdataElement.appendChild(createDescription(data, doc));

  393.         // data element
  394.         final Element dataElement = doc.createElement(Tag.DATA);
  395.         uasdataElement.appendChild(dataElement);

  396.         dataElement.appendChild(createRobots(data, doc));
  397.         dataElement.appendChild(createOperatingSystems(data, doc));
  398.         dataElement.appendChild(createBrowsers(data, doc));
  399.         dataElement.appendChild(createBrowserTypes(data, doc));
  400.         dataElement.appendChild(createBrowserPatterns(data, doc));
  401.         dataElement.appendChild(createBrowserOperatingSystemMappings(data, doc));
  402.         dataElement.appendChild(createOperatingSystemPatterns(data, doc));
  403.         dataElement.appendChild(createDevices(data, doc));
  404.         dataElement.appendChild(createDevicePatterns(data, doc));

  405.         // write the content to output stream
  406.         final DOMSource source = new DOMSource(doc);
  407.         final StreamResult result = new StreamResult(outputStream);
  408.         transform(source, result);
  409.     }

  410.     /**
  411.      * <strong>Attention:</strong> This class is not intended to create objects from it.
  412.      */
  413.     private XmlDataWriter() {
  414.         // This class is not intended to create objects from it.
  415.     }

  416. }