XMLOutputHandler.java
/**
* Portions of this software was developed by employees of the National Institute
* of Standards and Technology (NIST), an agency of the Federal Government and is
* being made available as a public service. Pursuant to title 17 United States
* Code Section 105, works of NIST employees are not subject to copyright
* protection in the United States. This software may be subject to foreign
* copyright. Permission in the United States and in foreign countries, to the
* extent that NIST may hold copyright, to use, copy, modify, create derivative
* works, and distribute this software and its documentation without fee is hereby
* granted on a non-exclusive basis, provided that this notice and disclaimer
* of warranty appears in all copies.
*
* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
* EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
* THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
* INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
* SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT
* SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
* INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
* OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
* CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
* PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
* OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
*/
package gov.nist.secauto.swid.builder.output;
import gov.nist.secauto.swid.builder.AbstractLanguageSpecificBuilder;
import gov.nist.secauto.swid.builder.EntityBuilder;
import gov.nist.secauto.swid.builder.LinkBuilder;
import gov.nist.secauto.swid.builder.MetaBuilder;
import gov.nist.secauto.swid.builder.Role;
import gov.nist.secauto.swid.builder.SWIDBuilder;
import gov.nist.secauto.swid.builder.ValidationException;
import gov.nist.secauto.swid.builder.resource.AbstractResourceBuilder;
import gov.nist.secauto.swid.builder.resource.AbstractResourceCollectionBuilder;
import gov.nist.secauto.swid.builder.resource.EvidenceBuilder;
import gov.nist.secauto.swid.builder.resource.HashAlgorithm;
import gov.nist.secauto.swid.builder.resource.HashUtils;
import gov.nist.secauto.swid.builder.resource.PathRelativizer;
import gov.nist.secauto.swid.builder.resource.PayloadBuilder;
import gov.nist.secauto.swid.builder.resource.ResourceBuilder;
import gov.nist.secauto.swid.builder.resource.ResourceCollectionEntryGenerator;
import gov.nist.secauto.swid.builder.resource.file.AbstractFileSystemItemBuilder;
import gov.nist.secauto.swid.builder.resource.file.DirectoryBuilder;
import gov.nist.secauto.swid.builder.resource.file.FileBuilder;
import gov.nist.secauto.swid.builder.resource.firmware.FirmwareBuilder;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.Namespace;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import java.io.IOException;
import java.io.OutputStream;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
public class XMLOutputHandler implements OutputHandler {
public static final Namespace SWID_NAMESPACE
= Namespace.getNamespace("http://standards.iso.org/iso/19770/-2/2015/schema.xsd");
private final Format format;
public XMLOutputHandler() {
this(Format.getPrettyFormat());
}
public XMLOutputHandler(Format format) {
this.format = format;
}
@Override
public void write(SWIDBuilder builder, OutputStream os) throws IOException, ValidationException {
XMLOutputter out = new XMLOutputter(format);
out.output(generateXML(builder), os);
}
/**
* Creates a JDOM2 XML Document based on the content of the builder.
*
* @param builder
* the {@link SWIDBuilder} to use the information from to build the XML model
* @return a JDOM2 {@link Document} based on the SWID information
* @throws ValidationException
* if the SWID to be built is invalid
*/
public Document generateXML(SWIDBuilder builder) throws ValidationException {
builder.validate();
Document retval = buildDocument(builder);
return retval;
}
protected Document buildDocument(SWIDBuilder builder) {
return new Document(build(builder));
}
protected Element build(SWIDBuilder builder) {
Element element = new Element("SoftwareIdentity", SWID_NAMESPACE);
buildAbstractLanguageSpecificBuilder(builder, element);
// required attributes
element.setAttribute("name", builder.getName());
element.setAttribute("tagId", builder.getTagId());
// optional attributes
switch (builder.getTagType()) {
case PRIMARY:
break;
case CORPUS:
element.setAttribute("corpus", Boolean.TRUE.toString());
break;
case PATCH:
element.setAttribute("patch", Boolean.TRUE.toString());
break;
case SUPPLEMENTAL:
element.setAttribute("supplemental", Boolean.TRUE.toString());
break;
default:
throw new IllegalStateException("tagType: " + builder.getTagType().toString());
}
element.setAttribute("tagVersion", builder.getTagVersion().toString());
buildAttribute("version", builder.getVersion(), element);
buildAttribute("versionScheme", builder.getVersionScheme().getName(), element);
// child elements
// Required
for (EntityBuilder entity : builder.getEntities()) {
element.addContent(build(entity));
}
// optional
EvidenceBuilder evidence = builder.getEvidence();
if (evidence != null) {
element.addContent(build(evidence));
}
for (LinkBuilder link : builder.getLinks()) {
element.addContent(build(link));
}
for (MetaBuilder meta : builder.getMetas()) {
element.addContent(build(meta));
}
PayloadBuilder payload = builder.getPayload();
if (payload != null) {
element.addContent(build(payload));
}
return element;
}
protected Element build(EntityBuilder builder) {
Element element = new Element("Entity", SWID_NAMESPACE);
buildAbstractLanguageSpecificBuilder(builder, element);
// required attributes
element.setAttribute("name", builder.getName());
StringBuilder sb = null;
for (Role role : builder.getRoles()) {
if (sb == null) {
sb = new StringBuilder();
} else {
sb.append(' ');
}
sb.append(role.getName());
}
if (sb != null) {
element.setAttribute("role", sb.toString());
}
// optional attributes
buildAttribute("regid", builder.getRegid(), element);
buildAttribute("thumbprint", builder.getThumbprint(), element);
return element;
}
protected Element build(EvidenceBuilder builder) {
Element element = new Element("Evidence", SWID_NAMESPACE);
buildAbstractResourceCollectionBuilder(builder, element);
ZonedDateTime date = builder.getDate();
if (date != null) {
element.setAttribute("date", date.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
}
buildAttribute("deviceId", builder.getDeviceId(), element);
return element;
}
protected Element build(LinkBuilder builder) {
Element element = new Element("Link", SWID_NAMESPACE);
buildAbstractLanguageSpecificBuilder(builder, element);
// required attributes
element.setAttribute("href", builder.getHref().toString());
element.setAttribute("rel", builder.getRel());
// optional attributes
buildAttribute("artifact", builder.getArtifact(), element);
buildAttribute("media", builder.getMedia(), element);
buildAttribute("ownership", builder.getOwnership(), element);
buildAttribute("type", builder.getMediaType(), element);
buildAttribute("use", builder.getUse(), element);
return element;
}
protected Element build(MetaBuilder builder) {
Element element = new Element("Meta", SWID_NAMESPACE);
buildAttribute("activationStatus", builder.getActivationStatus(), element);
buildAttribute("channelType", builder.getChannelType(), element);
buildAttribute("colloquialVersion", builder.getColloquialVersion(), element);
buildAttribute("description", builder.getDescription(), element);
buildAttribute("edition", builder.getEdition(), element);
buildAttribute("entitlementDataRequired", builder.getEntitlementDataRequired(), element);
buildAttribute("entitlementKey", builder.getEntitlementKey(), element);
buildAttribute("generator", builder.getGenerator(), element);
buildAttribute("persistentId", builder.getPersistentId(), element);
buildAttribute("product", builder.getProductBaseName(), element);
buildAttribute("productFamily", builder.getProductFamily(), element);
buildAttribute("revision", builder.getRevision(), element);
buildAttribute("summary", builder.getSummary(), element);
buildAttribute("unspscCode", builder.getUnspscCode(), element);
buildAttribute("unspscVersion", builder.getUnspscVersion(), element);
return element;
}
protected Element build(PayloadBuilder builder) {
Element element = new Element("Payload", SWID_NAMESPACE);
buildAbstractResourceCollectionBuilder(builder, element);
return element;
}
private static <E extends AbstractResourceCollectionBuilder<E>> void
buildAbstractResourceCollectionBuilder(AbstractResourceCollectionBuilder<E> builder, Element element) {
buildAbstractLanguageSpecificBuilder(builder, element);
XMLResourceCollectionEntryGenerator creator = new XMLResourceCollectionEntryGenerator();
for (ResourceBuilder resourceBuilder : builder.getResources()) {
resourceBuilder.accept(element, creator);
}
}
private static <E extends AbstractLanguageSpecificBuilder<E>> void
buildAbstractLanguageSpecificBuilder(AbstractLanguageSpecificBuilder<E> builder, Element element) {
String language = builder.getLanguage();
if (language != null) {
element.setAttribute("lang", language, Namespace.XML_NAMESPACE);
}
}
private static void buildAttribute(String attributeName, String value, Element element) {
if (value != null) {
element.setAttribute(attributeName, value);
}
}
private static void buildAttribute(String attributeName, Object value, Element element) {
if (value != null) {
element.setAttribute(attributeName, value.toString());
}
}
private static class XMLResourceCollectionEntryGenerator implements ResourceCollectionEntryGenerator<Element> {
public XMLResourceCollectionEntryGenerator() {
}
@Override
public void generate(Element parent, DirectoryBuilder builder) {
Element element = new Element("Directory", XMLOutputHandler.SWID_NAMESPACE);
parent.addContent(element);
buildAbstractFileSystemItem(builder, element);
for (ResourceBuilder child : builder.getResources()) {
child.accept(element, this);
}
}
@Override
public void generate(Element parent, FileBuilder builder) {
Element element = new Element("File", XMLOutputHandler.SWID_NAMESPACE);
parent.addContent(element);
buildAbstractFileSystemItem(builder, element);
XMLOutputHandler.buildAttribute("size", builder.getSize(), element);
XMLOutputHandler.buildAttribute("version", builder.getVersion(), element);
Element rootElement = parent;
while (rootElement.getParentElement() != null) {
rootElement = rootElement.getParentElement();
}
for (Map.Entry<HashAlgorithm, byte[]> entry : builder.getHashAlgorithmToValueMap().entrySet()) {
HashAlgorithm algorithm = entry.getKey();
byte[] hashValue = entry.getValue();
Namespace ns = Namespace.getNamespace(algorithm.getName(), algorithm.getNamespace());
Namespace nsOld = rootElement.getNamespace(ns.getPrefix());
if (nsOld == null) {
rootElement.addNamespaceDeclaration(ns);
} else if (!nsOld.getURI().equals(ns.getURI())) {
element.addNamespaceDeclaration(ns);
}
element.setAttribute("hash", HashUtils.toHexString(hashValue), ns);
}
}
@Override
public void generate(Element parent, FirmwareBuilder firmwareBuilder) {
throw new UnsupportedOperationException("firmware is not supported by the XML SWID format");
}
private static <E extends AbstractFileSystemItemBuilder<E>> void
buildAbstractFileSystemItem(AbstractFileSystemItemBuilder<E> builder, Element element) {
buildAbstractResourceBuilder(builder, element);
XMLOutputHandler.buildAttribute("root", builder.getRoot(), element);
List<String> location = builder.getLocation();
if (location != null && !location.isEmpty()) {
element.setAttribute("location", PathRelativizer.toURI(location).toString());
}
XMLOutputHandler.buildAttribute("name", builder.getName(), element);
XMLOutputHandler.buildAttribute("key", builder.getKey(), element);
}
private static <E extends AbstractResourceBuilder<E>> void
buildAbstractResourceBuilder(AbstractResourceBuilder<E> builder, Element element) {
XMLOutputHandler.buildAbstractLanguageSpecificBuilder(builder, element);
}
}
}