All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.streamsets.systemexplorer.api.ExplorerSchema Maven / Gradle / Ivy

There is a newer version: 6.2.1
Show newest version
/*
 * Copyright 2022 StreamSets 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.streamsets.systemexplorer.api;

import com.streamsets.pipeline.api.impl.Utils;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 

* Class that defines the metadata explorer schema for a stage. *

*

* A sub-class defining a metadata explorer schema must be set the stage {@code @StageDef} * for stages supporting schema definition. *

*
    *
  • Schema element names must be a single word.
  • *
  • Schema element names must be unique.
  • *
  • Schema elements may have multiple types (via the {@code also()} method).
  • *
*

* Explorer schemas are immutable. *

* Example of Explorer Schema for Snowflake: * {@code * public class SnowflakeExplorerSchema extends ExplorerSchema { * public SnowflakeExplorerSchema() { * super( * new Atom("warehouse", "warehouse.svg"), * new Atom("role", "role.svg"), * new Set( * "database", * "database,svg", * new Set( * "schema", * "schema.svg", * new Set("table", "table.svg", new Atom("column", "column.svg")).also("view", "view.svg"), * ) * ) * ); * } * } * } *

* For searching, when searching 'table' schema elements it may also return 'view' elements in the same * children set. *

*/ public class ExplorerSchema { /** *

* Empty schema. *

*/ public static final class None extends ExplorerSchema { public static None NONE = new None(); private None() {} } private static final Pattern WORD_PATTERN = Pattern.compile("\\w+"); /** *

* Base schema element class. *

*/ public static abstract class Element { public static final String NAME_ATTR = "name"; private final String name; private final String icon; private final boolean cacheable; private final Map additionalNamesIcons; private final List attributes; protected Element( boolean root, String name, String icon, boolean cacheable, Map additionalNamesIcons, List attributes ) { Utils.checkNotNull(attributes, "attributes"); Utils.checkArgument(!attributes.isEmpty(), "attributes"); Utils.checkArgument( attributes.stream().anyMatch(a -> a.name.equals(NAME_ATTR)), String.format("%s is required", NAME_ATTR) ); Utils.checkArgument( new HashSet<>(attributes).size() == attributes.size(), String.format( "Attribute names should be unique: %s", attributes.stream().map(a -> a.name).collect(Collectors.joining(",")) ) ); this.name = (root) ? name : checkName(name); this.icon = icon; this.cacheable = cacheable; this.additionalNamesIcons = Collections.unmodifiableMap(additionalNamesIcons.entrySet().stream().map( e -> new String[]{checkName(e.getKey()), e.getValue()} ).collect(Collectors.toMap(a -> a[0], a -> a[1]))); this.attributes = Collections.unmodifiableList(attributes); } String checkName(String name) { Utils.checkArgument(WORD_PATTERN.matcher(name).matches(), Utils.format("ExplorerSchema '{}' invalid schema element name '{}', must be a word", getClass().getSimpleName(), name )); return name; } /** * Returns names of the supported element attributes. * It's never null nor empty, and it's first element is always ExplorerSchema.Element.NAME_ATTR * * @return list of attribute names. */ public List getAttributes() { return attributes; } public boolean isCacheable() { return cacheable; } /** *

* Returns the element name. *

*/ public String getName() { return name; } /** *

* Returns the element icon. *

*/ public String getIcon() { return icon; } /** *

* Returns the additional (name, icon) pairs associated with this element. *

*/ public Map getAdditionalNamesIcons() { return additionalNamesIcons; } /** *

* If this element is an {@code Atom} it returns an {@code Optional} with it, * else it returns an empty {@code Optional}. *

*/ public Optional asAtom() { return Optional.ofNullable((this instanceof Atom) ? (Atom) this : null); } /** *

* If this element is an {@code Set} it returns an {@code Optional} with it, * else it returns an empty {@code Optional}. *

*/ public Optional asSet() { return Optional.ofNullable((this instanceof Set) ? (Set) this : null); } } public static class Attribute { private final String name; private final String label; public Attribute(final String name, final String label) { Utils.checkArgument(name != null, "name"); Utils.checkArgument(label != null, "label"); this.name = name; this.label = label; } public String getName() { return name; } public String getLabel() { return label; } } /** *

* A leaf schema element. *

*/ public static class Atom extends Element { /** *

* Creates an Atom schema element with no icon. *

*/ private Atom(String name, String icon, boolean cacheable, Map additionalNames, List attributeNames) { super(false, name, Utils.checkNotNull(icon, "icon"), cacheable, additionalNames, attributeNames); } @Override public String toString() { return "Atom{" + "name='" + getName() + '\'' + ", icon='" + getIcon() + '\'' + '}'; } } /** *

* A schema element that has one or more children schema element. *

*/ public static class Set extends Element { private final List elements; private final boolean recursive; private Set(boolean root, String name, String icon, boolean cacheable, boolean recursive, List elements, Map additionaNamesIcons, List attributes) { super(root, name, Utils.checkNotNull(icon, "icon"), cacheable, additionaNamesIcons, attributes); Utils.checkNotNull(elements, "elements"); List list = new ArrayList<>(); if (recursive) { Utils.checkArgument(elements.size() == 0, "elements"); } else { list.addAll(elements); } this.elements = Collections.unmodifiableList(list); this.recursive = recursive; } /** *

* Returns the children schema elements. *

*/ public List getElements() { return elements; } public boolean isRecursive() { return recursive; } @Override public String toString() { return "Set{" + "names='" + getName() + '\'' + ", icons='" + getIcon() + "', elements ='" + elements + '\'' +'}'; } } public static class ElementBuilder { public static LastAtomBuilder atom(final String name) { return new ExplorerSchema.ElementBuilder().createAtomBuilder(name); } public static LastSetBuilder set(final String name) { return new ExplorerSchema.ElementBuilder().createSetBuilder(name); } private ElementBuilder() { } private LastAtomBuilder createAtomBuilder(final String name) { return new LastAtomBuilder(name); } private LastSetBuilder createSetBuilder(final String name) { return new LastSetBuilder(name); } public static abstract class BaseElementBuilder { protected E element; private BaseElementBuilder(final E element) { this.element = element; } protected List addAttribute(final String attributeName, final String attributeLabel) { Utils.checkNotNull(attributeName, "attributeName"); Utils.checkArgument( element.getAttributes().stream().noneMatch(a -> a.name.equals(attributeName)), String.format("%s has already been added to the list of attribute names", attributeName) ); ArrayList attributes = new ArrayList<>(element.getAttributes()); attributes.add(new Attribute(attributeName, attributeLabel)); return attributes; } protected Map addAdditionalNameIcon(final String name, final String icon) { Utils.checkNotNull(name, "name"); Utils.checkNotNull(icon, "icon"); Map additionalNamesIcons = new HashMap<>(element.getAdditionalNamesIcons()); additionalNamesIcons.put(name, icon); return additionalNamesIcons; } } public static abstract class BaseAtomBuilder extends BaseElementBuilder { private BaseAtomBuilder(final String name) { super( new Atom(name, "", true, new HashMap<>(), Collections.singletonList(new Attribute(Element.NAME_ATTR, Element.NAME_ATTR))) ); } public C icon(final String icon) { element = new Atom(element.getName(), icon, element.isCacheable(), element.getAdditionalNamesIcons(), element.getAttributes()); return (C) this; } public C cacheable(final boolean cacheable) { element = new Atom(element.getName(), element.getIcon(), cacheable, element.getAdditionalNamesIcons(), element.getAttributes()); return (C) this; } public C also(final String name) { return also(name, ""); } public C also(final String name, final String icon) { element = new Atom(element.getName(), element.getIcon(), element.isCacheable(), addAdditionalNameIcon(name, icon), element.getAttributes()); return (C) this; } public C attribute(final String attributeName, final String attributeLabel) { element = new Atom(element.getName(), element.getIcon(), element.isCacheable(), element.getAdditionalNamesIcons(), addAttribute(attributeName, attributeLabel)); return (C) this; } public C attribute(final String attributeName) { return attribute(attributeName, attributeName); } } public static class AtomBuilder

extends BaseAtomBuilder> { private final P parent; private AtomBuilder(final P parent, final String name) { super(name); this.parent = parent; } public P endAtom() { return parent; } } public static class LastAtomBuilder extends BaseAtomBuilder { private LastAtomBuilder(final String name) { super(name); } public Atom endAtom() { return new Atom(element.getName(), element.getIcon(), element.isCacheable(), element.getAdditionalNamesIcons(), element.getAttributes()); } } public static class BaseSetBuilder extends BaseElementBuilder { protected final List builders = new ArrayList<>(); public BaseSetBuilder(final String name) { super(new Set(false, name, "", true, false, Collections.emptyList(), Collections.emptyMap(), Collections.singletonList(new Attribute(Element.NAME_ATTR, Element.NAME_ATTR)))); } public C icon(final String icon) { element = new Set(false, element.getName(), icon, element.isCacheable(), element.isRecursive(), Collections.emptyList(), element.getAdditionalNamesIcons(), element.getAttributes()); return (C) this; } public C cacheable(final boolean cacheable) { element = new Set(false, element.getName(), element.getIcon(), cacheable, element.isRecursive(), Collections.emptyList(), element.getAdditionalNamesIcons(), element.getAttributes()); return (C) this; } public C also(final String name) { return also(name, ""); } public C also(final String name, final String icon) { element = new Set(false, element.getName(), element.getIcon(), element.isCacheable(), element.isRecursive(), Collections.emptyList(), addAdditionalNameIcon(name, icon), element.getAttributes()); return (C) this; } public C attribute(final String attributeName, final String attributeLabel) { element = new Set(false, element.getName(), element.getIcon(), element.isCacheable(), element.isRecursive(), Collections.emptyList(), element.getAdditionalNamesIcons(), addAttribute(attributeName, attributeLabel)); return (C) this; } public C attribute(final String attributeName) { return attribute(attributeName, attributeName); } public AtomBuilder atom(final String name) { AtomBuilder atomBuilder = new AtomBuilder<>((C) this, name); builders.add(atomBuilder); return atomBuilder; } public SetBuilder set(final String name) { SetBuilder elementBuilder = new SetBuilder<>((C) this, name); builders.add(elementBuilder); return elementBuilder; } protected Set build(final List elements) { return new Set(false, element.getName(), element.getIcon(), element.isCacheable(), element.isRecursive(), elements, element.getAdditionalNamesIcons(), element.getAttributes()); } } public static class SetBuilder

extends BaseSetBuilder> { private final P parent; public SetBuilder(final P parent, final String name) { super(name); this.parent = parent; } public P endSet() { element = build(builders.stream().map(b -> b.element).collect(Collectors.toList())); return parent; } } public static class LastSetBuilder extends BaseSetBuilder { public LastSetBuilder(final String name) { super(name); } public Set endSet() { return build(builders.stream().map(b -> b.element).collect(Collectors.toList())); } } } private final Set root; private final java.util.Set elementNames; private final Map> parents; private ExplorerSchema() { root = null; elementNames = Collections.emptySet(); parents = Collections.emptyMap(); } /** *

* Constructor. *

*/ protected ExplorerSchema(Element element, Element... elements) { Utils.checkNotNull(element, "element"); List elementList = new ArrayList<>(); elementList.add(element); elementList.addAll(Arrays.asList(elements)); root = new Set( true, "", "", true, false, elementList, Collections.emptyMap(), Collections.singletonList(new Attribute(Element.NAME_ATTR, Element.NAME_ATTR)) ); List names = new ArrayList<>(); visit(root, e -> names.addAll(getSchemaElementNames(e))); java.util.Set dupNames = names .stream() .filter(name -> Collections.frequency(names, name) > 1) .collect(Collectors.toSet()); if (!dupNames.isEmpty()) { throw new IllegalArgumentException( String.format("ExplorerSchema '%s' has duplicate elements: '%s'", this.getClass().getSimpleName(), dupNames) ); } elementNames = Collections.unmodifiableSet(new HashSet<>(names)); parents = elementNames .stream() .collect(Collectors.toMap( name -> name, name -> { List list = new ArrayList<>(); findParents(getRoot().getElements(), name, list); return Collections.unmodifiableList(list); }) ); } /** *

* If the visitor functions returns false it means the visit should mend *

*/ private void visit(ExplorerSchema.Element element, Consumer visitor) { visitor.accept(element); element.asSet().ifPresent(set -> set.getElements().forEach(e -> visit(e, visitor))); } private java.util.List getSchemaElementNames(ExplorerSchema.Element element) { return (element.getName().isEmpty()) ? Collections.emptyList() : Stream.concat( Stream.of(element.getName()), element.getAdditionalNamesIcons().keySet().stream() ).collect(Collectors.toList()); } private static boolean findParents( List schemaElements, String name, List parents) { boolean found = false; for (ExplorerSchema.Element schemaElement : schemaElements) { found |= schemaElement.getName().equals(name); if (!found) { if (schemaElement instanceof ExplorerSchema.Set) { if (findParents(((ExplorerSchema.Set)schemaElement).getElements(), name, parents)) { parents.add(0, schemaElement.getName()); return true; } } } } return found; } /** *

* Returns the schema definition. *

*/ public Set getRoot() { return root; } /** *

* Returns a set with all the schema elements names, it includes the element names defined via {@code also()}. *

*/ public java.util.Set getElementNames() { return elementNames; } /** *

* Returns a list with the parents of a given element, in order from the top. *

*/ public List getParents(String elementName) { return parents.get(Utils.checkNotNull(elementName, "elementName")); } private Element findElement(Element element, String name) { if (element.getName().equals(name)) { return element; } if (element.asSet().isPresent()) { for (Element child : element.asSet().get().getElements()) { element = findElement(child, name); if (element != null) { return element; } } } return null; } private java.util.Set getChildrenName(Element element, int upToChildDepth) { if (upToChildDepth == 0 || element.asAtom().isPresent()) { return Collections.emptySet(); } java.util.Set names = new HashSet<>(); element.asSet().get().getElements().forEach(e -> { names.add(e.getName()); names.addAll(getChildrenName(e, upToChildDepth - 1)); }); return names; } /** *

* Returns the children names of an element up to the given child depth. *

*/ public java.util.Set getChildrenNames(String name, int upToChildDepth) { Utils.checkNotNull(name, "name"); Utils.checkArgument(upToChildDepth >=0, "upToChildDepth must be >= 0"); Element element = findElement(getRoot(), name); if (element == null) { throw new IllegalArgumentException(Utils.format( "ExplorerSchema '{}' element '{}' does not exist", getClass().getSimpleName(), name )); } return getChildrenName(element, upToChildDepth); } @Override public String toString() { return "ExplorerSchema{" + "root=" + root + '}'; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy