com.streamsets.systemexplorer.api.ExplorerSearcher Maven / Gradle / Ivy
/*
* Copyright 2017 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.ConfigIssue;
import com.streamsets.pipeline.api.impl.Utils;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
*
* Interface that performs an explorer search against the explorer data produced by the explorer loader.
*
*/
public interface ExplorerSearcher {
/**
*
* Initializes the search. Invoked before every search.
*
*/
default List init(final ExplorerRequestContext explorerRequestContext) {
return Collections.emptyList();
}
/**
* Does a search on the given {@code LoaderContext}.
*
* @param explorerLoaderContext - context that defines where to search for the data.
* @param searchRequest - search parameters that define what data to search for.
* Refer to the {@code SearchRequest} for details on how the search request parameters can be
* used to alter the structure of tree.
* @return {@code ExplorerSearcher.Tree} that always has root element with NULL as name and type, its children follow
* the structure of the explorer schema of the stage.
*/
Tree search(ExplorerLoaderContext explorerLoaderContext, SearchRequest searchRequest) throws InterruptedException, ExplorerSearcherException;
/**
* Destroys the search. Invoked after every search.
*/
default void destroy() {
}
/**
*
* Searches always return a tree. The tree conforms to the explorer schema defined in the stage.
*
*
* Tree instances always start with a ROOT node (name=null, type=null).
*
*
* The tree nodes have a type (schema element name), a value, and they may have children.
*
*
* For Atom elements children is always NULL.
*
*
* For Set elements children are:
* * NULL if the search didn't return any children for the current element
* * A list with a single NULL element to signal that the search didn't retrieve data at the children level so
* it does not know if there is data or not, a search at deeper level must be done to find out.
* * A non-empty list of trees with children.
*
*/
class Tree {
private final Map attributes;
private final String type;
private Map children;
/**
*
* Creates an empty tree, just the root node.
*
*/
public static Tree root() {
return new Tree();
}
private Tree() {
attributes = null;
type = null;
}
/**
*
* Create a tree node.
*
*/
public Tree(String name, String type) {
this(Collections.singletonMap(ExplorerSchema.Element.NAME_ATTR, name), type);
}
public Tree(Map attributes, String type) {
this.type = Utils.checkNotNull(type, "type");
Utils.checkNotNull(attributes, "attributes");
Utils.checkArgument(attributes.get(ExplorerSchema.Element.NAME_ATTR) != null, "At least the name attribute is required");
this.attributes = Collections.unmodifiableMap(new HashMap<>(attributes));
}
/**
* Returns the node attributes.
*
* @return unmodifiable map of node attributes.
*/
public Map getAttributes() {
return attributes;
}
/**
*
* The node value.
*
*/
public String getName() {
return Optional.ofNullable(attributes).map(a -> a.get(ExplorerSchema.Element.NAME_ATTR)).orElse(null);
}
/**
*
* The schema element name.
*
*/
public String getType() {
return type;
}
/**
*
* Adds a child tree.
*
*/
public Tree addChild(Tree tree) {
if (children == null) {
children = new LinkedHashMap<>();
}
children.put(tree.getName(), tree);
return this;
}
/**
*
* Returns a child tree, NULL if it does not exist.
*
*/
public Tree getChild(String value) {
return (children == null) ? null : children.get(value);
}
/**
*
* Return all the children of a node, NULL if none.
*
*/
public Collection getChildren() {
return (children == null) ? null : children.values();
}
@Override
public String toString() {
return "Tree{" + "name='" + getName() + '\'' + ", type='" + type + '\'' + ", children=" + children + '}';
}
}
/**
*
* Defines an explorer search request and how to produce the result tree.
*
*
* For the following explorer data:
*
* {@code
* database: HR
* schema: US
* table: EMPLOYEE
* column: ID, NAME, EMAIL, SSN
* table: OFFICE
* column: ID, ADDRESS, STATE
* schema: EU
* table: EMPLOYEE
* column: ID, NAME, EMAIL
* table: OFFICE
* column: ID, ADDRESS, COUNTRY
* database: PRODUCTS
* schema: PROD
* table: PRODUCT
* column: ID, NAME
* table: PRICE
* column: PRODUCT_ID, PRICE, CURRENCY
* table: STOCK
* column: PRODUCT_ID, QUANTITY
* database: MARKETING
* schema: GLOBAL
* table: CAMPAIGN
* column: ID, NAME
* }
*
*
* A search that returns the whole data would be:
*
*
* {@code
*
* { name : null
* value : null
* children: [
* { name: HR
* type: database
* children: [
* { name: US
* type: schema
* children: [
* { name: EMPLOYEE
* type: table
* children: [
* { name: ID, type: column }
* { name: NAME, type: column }
* { name: EMAIL, type: column }
* { name: SSN, type: column }
* ]
* }
* { name: OFFICE
* type: table
* children: [
* { name: ID, type: column }
* { name: ADDRESS, type: column }
* { name: STATE, type: column }
* ]
* }
* ]
* }
* { name : EU
* type : schema
* children: [
* { name: EMPLOYEE
* type: table
* children: [
* { name: ID, type: column }
* { name: NAME, type: column }
* { name: EMAIL, type: column }
* ]
* }
* { name: OFFICE
* type: table
* children: [
* { name: ID, type: column }
* { name: ADDRESS, type: column }
* { name: COUNTRY, type: column }
* ]
* }
* ]
* }
* ]
* }
* { name: PRODUCTS
* type: database
* children: [
* { name: PROD
* type: schema
* children: [
* { name: PRODUCT
* type: table
* children: [
* { name: ID, type: column }
* { name: NAME, type: column }
* ]
* }
* { name: PRICE
* type: table
* children: [
* { name: PRODUCT_ID, type: column }
* { name: PRICE, type: column }
* { name: CURRENCY, type: column }
* ]
* }
* { name: STOCK
* type: table
* children: [
* { name: PRODUCT_ID, type: column }
* { name: QUANTITY, type: column }
* ]
* }
* ]
* }
* ]
* }
* { name: MARKETING
* type: database
* children: [
* { name: GLOBAL
* type: schema
* children: [
* { name: CAMPAIGN
* type: table
* children: [
* { name: ID, type: column }
* { name: NAME, type: column }
* ]
* }
* ]
* }
* ]
* }
* [
* }
* }
*
*/
interface SearchRequest {
/**
*
* The schema element being search.
*
*/
String getSchemaElement();
/**
*
* Schema elements and values pairs that restrict the search domain to a subset of the explorer data.
*
*/
Map getFixedSchemaElements();
/**
*
* RSQL expression (using schema elements as RSQL selectors) to search within the search domain.
*
*/
String getSearch();
/**
*
* How many extra levels from the schema element being search are wanted in the result tree.
*
*/
int getExtraDepth();
/**
*
* It will truncate the result tree, from root down to the schema element being searched. The returned tree
* will have as root children the searched schema element nodes.
*
*/
boolean isUpTruncate();
/**
*
* The offset of the results.
*
*/
int getOffset();
/**
*
* The length of the results.
*
*/
int getLen();
}
final class NoOp implements ExplorerSearcher