Java tutorial
/* * Copyright 2015 Black Duck Software, Inc. * * 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 com.blackducksoftware.bdio.io; import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Strings.nullToEmpty; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.Nullable; import com.blackducksoftware.bdio.BlackDuckTerm; import com.blackducksoftware.bdio.BlackDuckType; import com.blackducksoftware.bdio.BlackDuckValue; import com.blackducksoftware.bdio.DoapTerm; import com.blackducksoftware.bdio.Node; import com.blackducksoftware.bdio.SimpleTerm; import com.blackducksoftware.bdio.SpdxTerm; import com.blackducksoftware.bdio.SpdxType; import com.blackducksoftware.bdio.SpdxValue; import com.blackducksoftware.bdio.Term; import com.blackducksoftware.bdio.Type; import com.blackducksoftware.bdio.XmlSchemaType; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.MapDifference; import com.google.common.collect.MapDifference.ValueDifference; import com.google.common.collect.Maps; class Specification { // NOTE: The specifications should be preserved in the state that they were released. If you change a constant that // the specification definition uses, you must change the definition back to the old value. /** * The initial version of the specification. Uses an empty string as the version number to indicate that there was * no specification version data stored in the initial version. */ private static final Specification v0 = new Specification("", new TermDefinitionMap() { { addPrefixMapping("spdx", "http://spdx.org/rdf/terms#"); addPrefixMapping("doap", "http://usefulinc.com/ns/doap#"); addPrefixMapping("rdfs", "http://www.w3.org/2000/01/rdf-schema#"); addPrefixMapping("xsd", "http://www.w3.org/2001/XMLSchema#"); // Because the vocabulary includes BlackDuckTerms, only type overrides need to be specified addTerm("size", BlackDuckTerm.SIZE, XmlSchemaType.LONG); addTerm("externalIdentifier", BlackDuckTerm.EXTERNAL_IDENTIFIER, BlackDuckType.EXTERNAL_IDENTIFIER); addTerm("externalSystemTypeId", BlackDuckTerm.EXTERNAL_SYSTEM_TYPE_ID, JsonLdType.ID); addTerm("matchDetail", BlackDuckTerm.MATCH_DETAIL, BlackDuckType.MATCH_DETAIL); addTerm("matchType", BlackDuckTerm.MATCH_TYPE, JsonLdType.ID); addTerm("name", DoapTerm.NAME); addTerm("homepage", DoapTerm.HOMEPAGE); addTerm("revision", DoapTerm.REVISION); addTerm("licence", DoapTerm.LICENSE, JsonLdType.ID); addTerm("fileName", SpdxTerm.FILE_NAME); addTerm("fileType", SpdxTerm.FILE_TYPE, JsonLdType.ID, Container.SET); addTerm("checksumValue", SpdxTerm.CHECKSUM_VALUE); addTerm("checksum", SpdxTerm.CHECKSUM, SpdxType.CHECKSUM); addTerm("algorithm", SpdxTerm.ALGORITHM, JsonLdType.ID); addTerm("artifactOf", SpdxTerm.ARTIFACT_OF, JsonLdType.ID); addTerm("licenseConcluded", SpdxTerm.LICENSE_CONCLUDED, JsonLdType.ID); addTerm("creationInfo", SpdxTerm.CREATION_INFO, SpdxType.CREATION_INFO); addValue("BD-Hub", "http://blackducksoftware.com/rdf/terms#externalIdentifier_BD-Hub"); addValue("BD-Suite", "http://blackducksoftware.com/rdf/terms#externalIdentifier_BD-Suite"); addValue("DEPENDENCY", BlackDuckValue.MATCH_TYPE_DEPENDENCY); addValue("PARTIAL", BlackDuckValue.MATCH_TYPE_PARTIAL); addValue("DIRECTORY", BlackDuckValue.FILE_TYPE_DIRECTORY); addValue("ARCHIVE", SpdxValue.FILE_TYPE_ARCHIVE); addValue("BINARY", SpdxValue.FILE_TYPE_BINARY); addValue("OTHER", SpdxValue.FILE_TYPE_OTHER); addValue("SOURCE", SpdxValue.FILE_TYPE_SOURCE); addValue("sha1", SpdxValue.CHECKSUM_ALGORITHM_SHA1); addValue("md5", SpdxValue.CHECKSUM_ALGORITHM_MD5); } }, new ImportResolver(), new ImportFrame() { { addReferenceTerm(DoapTerm.LICENSE); addReferenceTerm(SpdxTerm.ARTIFACT_OF); addReferenceTerm(SpdxTerm.LICENSE_CONCLUDED); } }); /** * Version 1.0.0 of the specification. * <p> * <b>Change log</b> * <ul> * <li>Added {@linkplain BlackDuckTerm#SPEC_VERSION specVersion}</li> * </ul> */ private static final Specification v1_0_0 = new Specification("1.0.0", new TermDefinitionMap(v0) { { // There were no changes to the context in this version of the specification, it basically is just a stop // gap to address the fact we released the initial version before formally defining a mechanism to record // changes in the specification. } }, new ImportResolver(v0), new ImportFrame(v0)); /** * Version 1.1.0 of the specification. * <p> * <b>Change log</b> * <ul> * <li>Added {@linkplain com.blackducksoftware.bdio.model.Relationship relationships}</li> * <li>Added external identifier system type aliases</li> * <li>Changed the external identifier system type mapping for Black Duck products</li> * </ul> */ private static final Specification v1_1_0 = new Specification("1.1.0", new TermDefinitionMap(v1_0_0) { { // Added relationships addTerm("relationshipType", SpdxTerm.RELATIONSHIP_TYPE, JsonLdType.ID); addTerm("relationship", SpdxTerm.RELATIONSHIP, SpdxType.RELATIONSHIP); addTerm("related", SpdxTerm.RELATED_SPDX_ELEMENT, JsonLdType.ID); addValue("DYNAMIC_LINK", SpdxValue.RELATIONSHIP_TYPE_DYNAMIC_LINK); // Added external identifiers addValue("anaconda", BlackDuckValue.EXTERNAL_IDENTIFIER_ANACONDA); addValue("bower", BlackDuckValue.EXTERNAL_IDENTIFIER_BOWER); addValue("cpan", BlackDuckValue.EXTERNAL_IDENTIFIER_CPAN); addValue("goget", BlackDuckValue.EXTERNAL_IDENTIFIER_GOGET); addValue("maven", BlackDuckValue.EXTERNAL_IDENTIFIER_MAVEN); addValue("npm", BlackDuckValue.EXTERNAL_IDENTIFIER_NPM); addValue("nuget", BlackDuckValue.EXTERNAL_IDENTIFIER_NUGET); addValue("rubygems", BlackDuckValue.EXTERNAL_IDENTIFIER_RUBYGEMS); // Changed the external identifiers for Black Duck products remove("BD-Hub"); addValue("bdhub", BlackDuckValue.EXTERNAL_IDENTIFIER_BDHUB); remove("BD-Suite"); addValue("bdsuite", BlackDuckValue.EXTERNAL_IDENTIFIER_BDSUITE); // Fixed typo remove("licence"); addTerm("license", DoapTerm.LICENSE, JsonLdType.ID); } }, new ImportResolver(v1_0_0) { @Override public TermDefinition removed(String alias, TermDefinition oldDefinition) { switch (alias) { case "BD-Hub": return TermDefinition.forValue(BlackDuckValue.EXTERNAL_IDENTIFIER_BDHUB); case "BD-Suite": return TermDefinition.forValue(BlackDuckValue.EXTERNAL_IDENTIFIER_BDSUITE); case "licence": return new TermDefinition(DoapTerm.LICENSE, ImmutableSet.<Type>of(JsonLdType.ID), null); default: return super.removed(alias, oldDefinition); } } }, new ImportFrame(v1_0_0) { { // Frame references addReferenceTerm(SpdxTerm.RELATED_SPDX_ELEMENT); } }); /** * Version 1.1.1 of the specification. * <p> * <b>Change log</b> * <ul> * <li>Added external identifier system type aliases</li> * </ul> */ private static final Specification v1_1_1 = new Specification("1.1.1", new TermDefinitionMap(v1_1_0) { { // Added external identifiers addValue("cocoapods", BlackDuckValue.EXTERNAL_IDENTIFIER_COCOAPODS); } }, new ImportResolver(v1_1_0), new ImportFrame(v1_1_0)); /** * Possible container types for term values. */ public enum Container { /** * Placeholder to use instead of {@code null}. */ UNKNOWN, // From the JSON-LD spec... LIST, SET, LANGUAGE, INDEX; /** * Create an immutable copy of the supplied input appropriate for this container type. */ public <T> Iterable<T> copyOf(Iterable<T> source) { Iterable<T> elements = source; if (this == SET) { elements = ImmutableSet.copyOf(elements); } // The result always needs to be a List or the JSON-LD library will choke return ImmutableList.copyOf(elements); } @Override public String toString() { if (this != Container.UNKNOWN) { return Enum.valueOf(JsonLdKeyword.class, name()).toString(); } else { throw new IllegalStateException("attempt to reference unknown container type"); } } } /** * A definition for a particular term. */ public static final class TermDefinition { /** * A special term definition used for the JSON-LD "@id" keyword. */ public static TermDefinition JSON_LD_ID = new TermDefinition(JsonLdKeyword.ID, ImmutableSet.<Type>of(JsonLdType.ID), null); /** * A special term definition used for the JSON-LD "@type" keyword. */ public static TermDefinition JSON_LD_TYPE = new TermDefinition(JsonLdKeyword.TYPE, ImmutableSet.<Type>of(JsonLdType.ID), Container.SET); /** * The fully qualified term identifier. */ private final Term term; /** * The types associated with this term. */ private final Set<Type> types; /** * The container type associated with this term. */ private final Container container; private TermDefinition(Term term, Set<Type> types, @Nullable Container container) { this.term = checkNotNull(term); this.types = ImmutableSet.copyOf(types); this.container = firstNonNull(container, Container.UNKNOWN); } /** * Returns a simple term definition with no types and no container type. */ public static TermDefinition defaultDefinition(Term term) { return new TermDefinition(term, ImmutableSet.<Type>of(), null); } /** * Returns a term definition for a value node. */ public static TermDefinition forValue(Node value) { return defaultDefinition(SimpleTerm.create(value.id())); } public Term getTerm() { return term; } public Set<Type> getTypes() { return types; } public Container getContainer() { return container; } } /** * A specialized map used to help building term definitions. */ private static class TermDefinitionMap extends LinkedHashMap<String, TermDefinition> { TermDefinitionMap() { } TermDefinitionMap(Specification specification) { putAll(specification.definitions); } protected void addPrefixMapping(String prefix, String identifier) { put(prefix, TermDefinition.defaultDefinition(SimpleTerm.create(identifier))); } protected void addValue(String alias, Node value) { put(alias, TermDefinition.forValue(value)); } protected void addValue(String alias, String value) { put(alias, TermDefinition.defaultDefinition(SimpleTerm.create(value))); } protected void addTerm(String alias, Term term, @Nullable Type type, @Nullable Container container) { put(alias, new TermDefinition(term, Optional.fromNullable(type).asSet(), container)); } protected void addTerm(String alias, Term term) { addTerm(alias, term, null); } protected void addTerm(String alias, Term term, @Nullable Type type) { addTerm(alias, term, type, null); } } /** * A definition that helps build a JSON-LD "frame" used for importing data. The framed data should look like it was * generated using the model classes. */ public static class ImportFrame { // This amounts to types represented by subclasses of AbstractTopLevelModel private static final List<Type> TOP_LEVEL_TYPES = ImmutableList.<Type>builder() .add(BlackDuckType.BILL_OF_MATERIALS).add(BlackDuckType.COMPONENT).add(BlackDuckType.FILE) .add(BlackDuckType.LICENSE).add(BlackDuckType.PROJECT).add(BlackDuckType.VULNERABILITY).build(); // There is really only one type which can be a reference type private static final Set<Type> REFERENCE_TYPES = ImmutableSet.<Type>of(JsonLdType.ID); /** * Filter used to match the reference terms. */ private final Predicate<TermDefinition> referenceTermsFilter; /** * Mutable state representing the terms that should be framed as references instead of embedded objects. */ private final Set<Term> referenceTerms = new HashSet<>(); ImportFrame() { // Default behavior is to not accept anything this(Predicates.alwaysFalse()); } ImportFrame(Specification specification) { // Combine the reference terms from the parent specification with this import frame this(specification.importFrame.referenceTerms()); } private ImportFrame(Predicate<? super TermDefinition> referenceTermsFilter) { // Combine the supplied reference terms with our mutable set of terms this.referenceTermsFilter = Predicates.and( // Makes sure we are only looking at term definitions with a type of "@id" // TODO This is getting evaluated at every depth Predicates.compose(Predicates.equalTo(REFERENCE_TYPES), new Function<TermDefinition, Set<Type>>() { @Override public Set<Type> apply(TermDefinition termDefinition) { return termDefinition.getTypes(); } }), // OR together any parent conditions plus our allowed reference terms Predicates.or(referenceTermsFilter, Predicates.compose(Predicates.in(referenceTerms), new Function<TermDefinition, Term>() { @Override public Term apply(TermDefinition termDefinition) { return termDefinition.getTerm(); } }))); } /** * Adds a term which should be framed as a reference (as opposed to an embedded object). */ protected void addReferenceTerm(Term term) { referenceTerms.add(term); } /** * Returns the top-level types which should appear in the graph. */ public List<Type> topLevelTypes() { return TOP_LEVEL_TYPES; } /** * Returns a predicate that matches references terms. */ public Predicate<TermDefinition> referenceTerms() { return referenceTermsFilter; } } /** * An import resolver is used to reconcile differences with older versions of the specification. When you are * importing older data (data generated to a previous version of the specification), the import resolver can be used * to produce an alternate set of term definitions to ensure the old data is properly migrated to the current * version of the specification. * <p> * Technically only the latest version of the specification needs an import resolver, though it may be useful to * build off of the resolvers from a previous version. */ public static class ImportResolver { @Nullable private final ImportResolver delegate; protected ImportResolver() { this.delegate = null; } protected ImportResolver(Specification specification) { this.delegate = specification.importResolver(); } /** * When the IRI mapped to an alias is changed, this method is used to determine which term definition should be * used during import. Typically you want the new definition to be used because the IRI from the latest * definition is used in model classes. For example, the alias "foo" might have been defined as * "http://example.com/foo" in a previous version of the specification; the current version defines "foo" as * "http://example.com/terms#foo" and that is the value used by the model classes when converting between nodes * and beans. The old data will contain values for "foo", and even though it was meant to be for * "http://example.com/foo", you want to select the new definition ("http://example.com/terms#foo") so the model * code will work as expected. * <p> * The default behavior is to always return the new definition. */ public TermDefinition changed(String alias, TermDefinition oldDefinition, TermDefinition newDefinition) { return delegate != null ? delegate.changed(alias, oldDefinition, newDefinition) : newDefinition; } /** * When an alias from a previous version no longer exists, either because it was removed or renamed, this method * is used to determine what the appropriate term definition should be during import. If the alias was dropped * completely this method should typically just return the old definition: you can avoid duck typing failures by * preserving the type information this way. If the alias was renamed, the resulting definition should point to * the new IRI (hopefully with the same type information). */ public TermDefinition removed(String alias, TermDefinition oldDefinition) { return delegate != null ? delegate.removed(alias, oldDefinition) : oldDefinition; } } /** * The map of available specification versions. */ private static Map<String, Specification> VERSIONS = Maps.uniqueIndex( // The ordering here is important for the latest() call to work ImmutableList.of(v0, v1_0_0, v1_1_0, v1_1_1), // Gee, it sure would be nice to have a lambda here new Function<Specification, String>() { @Override public String apply(Specification spec) { return spec.version(); } }); /** * Returns the specification for a version number. If the specification version number is not recognized or is * {@code null}, the initial specification is returned. */ public static Specification forVersion(@Nullable String specVersion) { return firstNonNull(VERSIONS.get(nullToEmpty(specVersion)), VERSIONS.get("")); } /** * Returns the latest version of the specification. */ public static Specification latest() { return Iterables.getLast(VERSIONS.values()); } /** * The version of this specification. */ private final String version; /** * The vocabulary for this specification. */ // Don't pretend like you think you can change this... private final String vocab = "http://blackducksoftware.com/rdf/terms#"; /** * The term definitions for this specification. */ private final Map<String, TermDefinition> definitions; /** * The import resolver used to adjust conflicts with older versions of the specification. */ private final ImportResolver importResolver; /** * The information about how to frame the data for import. */ private final ImportFrame importFrame; private Specification(String version, Map<String, TermDefinition> definitions, ImportResolver importResolver, ImportFrame importFrame) { this.version = checkNotNull(version); this.definitions = ImmutableMap.copyOf(definitions); this.importResolver = checkNotNull(importResolver); this.importFrame = checkNotNull(importFrame); } /** * Returns the version number of this specification. */ public final String version() { return version; } /** * Returns the vocabulary for this version of the specification. */ public final String vocab() { return vocab; } /** * Returns the import resolver for this version of the specification. */ private ImportResolver importResolver() { return importResolver; } /** * Returns a modified version of this specification whose term definitions can be used to map an older version of * the specification into a newer version. */ public Map<String, TermDefinition> importDefinitions() { MapDifference<String, TermDefinition> diff = Maps.difference(asTermDefinitions(), latest().asTermDefinitions()); if (diff.areEqual()) { return asTermDefinitions(); } else { // Use the resolver from the latest version of the specification to generate term definitions ImportResolver resolver = latest().importResolver(); Map<String, TermDefinition> termDefinitions = new LinkedHashMap<>(); // Entries on the right did not exist when the current version of the specification was // released, therefore we can ignore them in our reconstructed definitions // All common entries are unchanged between both versions of the specification termDefinitions.putAll(diff.entriesInCommon()); // Entries which have the same alias but different IRIs have been "re-mapped"; typically // (but not always) we want the alias to point to the latest IRI for (Entry<String, ValueDifference<TermDefinition>> e : diff.entriesDiffering().entrySet()) { String alias = e.getKey(); termDefinitions.put(alias, resolver.changed(alias, e.getValue().leftValue(), e.getValue().rightValue())); } // Entries on the left have been removed or renamed (their alias changed). We must delegate // to the import resolver to decide what do here. for (Entry<String, TermDefinition> e : diff.entriesOnlyOnLeft().entrySet()) { String alias = e.getKey(); termDefinitions.put(alias, resolver.removed(alias, e.getValue())); } return termDefinitions; } } /** * Returns all of the term definition aliases in this specification. */ public final Iterable<String> aliases() { return definitions.keySet(); } /** * Returns the term definition for an alias; */ public final Optional<TermDefinition> get(String alias) { return Optional.fromNullable(definitions.get(alias)); } /** * Returns the term definition for a term. */ public final Optional<TermDefinition> get(Term term) { for (TermDefinition definition : definitions.values()) { if (definition.getTerm().equals(term)) { return Optional.of(definition); } } return Optional.absent(); } /** * Returns all of the alias to term definition mappings. */ final Map<String, TermDefinition> asTermDefinitions() { // Definitions is immutable return definitions; } /** * Returns a JSON-LD frame for reconstructing the data as if it had been generated by serializing a list model * objects. */ public final Map<String, Object> importFrame() { final Map<String, Object> frame = new LinkedHashMap<>(); // Start by keeping only the top-level types frame.put("@type", Lists.transform(importFrame.topLevelTypes(), Functions.toStringFunction())); // Frame definition to turn of embedding final Map<String, Object> embedOff = ImmutableMap.of("@embed", (Object) Boolean.FALSE, "@omitDefault", (Object) Boolean.TRUE); // Add the term filters to disable embedding for (TermDefinition termDefinition : Iterables.filter(definitions.values(), importFrame.referenceTerms())) { frame.put(termDefinition.getTerm().toString(), embedOff); } return frame; } }