This commit is contained in:
coco
2026-07-03 15:56:07 +08:00
commit caef23209c
5767 changed files with 1004268 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
# binjr-adapter-jrds
[![Maven Central](https://img.shields.io/maven-central/v/eu.binjr/binjr-adapter-jrds.svg?label=Maven%20Central&style=flat-square)](https://search.maven.org/search?q=g:%22eu.binjr%22%20AND%20a:%22binjr-adapter-jrds%22)
This module implements a DataAdapter capable of consuming data from a [JRDS](https://github.com/fbacchella/jrds) instance.
JRDS is a monitoring and performance collection application. It already proposes a web based front-end that allow end-users
to visualize time-series as graphs which is based on [RRD4J](https://github.com/rrd4j/rrd4j), but does so by producing static images that cant be
zoomed in or out.
Using this DataAdapter, you can connect to a JRDS server via HTTP and use the flexibility offered by binjr to
navigate through the data.
This adapter supports authentication via Kerberos (see [here](https://github.com/binjr/binjr/wiki/Troubleshooting#kerberos-authentication-issues)
if you need help getting it to work).
+31
View File
@@ -0,0 +1,31 @@
/*
* Copyright 2018-2021 Frederic Thevenet
*
* 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.
*/
dependencies {
compileOnly project(':binjr-core')
}
jar {
manifest {
attributes(
'Specification-Title': project.name,
'Specification-Version': project.version,
'Implementation-Title': project.name,
'Implementation-Version': project.version,
'Build-Number': BINJR_BUILD_NUMBER
)
}
}
@@ -0,0 +1,141 @@
/*
* Copyright 2017-2020 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
import jakarta.xml.bind.annotation.*;
import java.util.List;
import java.util.stream.Collectors;
/**
* A annotated POJO class used to deserialize JRDS graph descriptor XML messages return by {@code /graphdesc?id=""} service.
*
* @author Frederic Thevenet
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "graphdec")
class Graphdesc {
@XmlElement(name = "name")
String name;
@XmlElement(name = "graphName")
String graphName;
@XmlElement(name = "graphClass")
String graphClass;
@XmlElement(name = "graphTitle")
String graphTitle;
@XmlElementWrapper(name = "unit")
@XmlElements({
@XmlElement(name = "SI", type = JrdsMetricUnitType.class),
@XmlElement(name = "binary", type = JrdsBinaryUnitType.class)
})
List<JrdsUnitType> unit;
@XmlElement(name = "verticalLabel")
String verticalLabel;
@XmlElement(name = "upperLimit")
String upperLimit;
@XmlElement(name = "lowerLimit")
String lowerLimit;
@XmlElement(name = "logarithmic")
String logarithmic;
@XmlElement(name = "add")
List<SeriesDesc> seriesDescList;
@XmlElement(name = "tree")
List<Tree> trees;
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Graphdesc{");
sb.append("name='").append(name).append('\'');
sb.append(", graphName='").append(graphName).append('\'');
sb.append(", graphClass='").append(graphClass).append('\'');
sb.append(", graphTitle='").append(graphTitle).append('\'');
sb.append(", unit='").append(unit).append('\'');
sb.append(", verticalLabel='").append(verticalLabel).append('\'');
sb.append(", upperLimit='").append(upperLimit).append('\'');
sb.append(", lowerLimit='").append(lowerLimit).append('\'');
sb.append(", logarithmic='").append(logarithmic).append('\'');
sb.append(", seriesDescList=").append(seriesDescList.stream().map(SeriesDesc::toString).collect(Collectors.joining(";")));
sb.append(", trees=").append(trees.stream().map(Tree::toString).collect(Collectors.joining(";")));
sb.append('}');
return sb.toString();
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "add")
static class SeriesDesc {
@XmlElement(name = "name")
String name = "";
@XmlElement(name = "dsName")
String dsName = "";
@XmlElement(name = "graphType")
String graphType = "";
@XmlElement(name = "color")
String color = "";
@XmlElement(name = "legend")
String legend = "";
@XmlElement(name = "rpn")
String rpn = "";
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("SeriesDesc{");
sb.append("name='").append(name).append('\'');
sb.append(", dsName='").append(dsName).append('\'');
sb.append(", graphType='").append(graphType).append('\'');
sb.append(", color='").append(color).append('\'');
sb.append(", legend='").append(legend).append('\'');
sb.append(", rpn='").append(rpn).append('\'');
sb.append('}');
return sb.toString();
}
}
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "tree")
static class Tree {
@XmlElement(name = "pathstring")
List<String> pathstring;
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("Tree{");
sb.append("pathstring=").append(pathstring);
sb.append('}');
return sb.toString();
}
}
@XmlSeeAlso({JrdsMetricUnitType.class, JrdsBinaryUnitType.class})
public static abstract class JrdsUnitType {
}
@XmlType(name = "SI")
public static class JrdsMetricUnitType extends JrdsUnitType {
@Override
public String toString() {
return "SI";
}
}
@XmlType(name = "binary")
public static class JrdsBinaryUnitType extends JrdsUnitType {
@Override
public String toString() {
return "binary";
}
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2017-2020 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
import eu.binjr.core.data.adapters.DataAdapter;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.dialogs.DataAdapterDialog;
import javafx.beans.binding.Bindings;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.geometry.VPos;
import javafx.scene.Node;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import java.net.URI;
import java.time.ZoneId;
import java.util.Collection;
import java.util.List;
/**
* An implementation of the {@link DataAdapterDialog} class that presents a dialog box to retrieve the parameters specific {@link JrdsDataAdapter}
*
* @author Frederic Thevenet
*/
public class JrdsAdapterDialog extends DataAdapterDialog<URI> {
private final ChoiceBox<JrdsTreeViewTab> tabsChoiceBox;
private final TextField extraArgumentTextField;
/**
* Initializes a new instance of the {@link JrdsAdapterDialog} class.
*
* @param owner the owner window for the dialog
*/
public JrdsAdapterDialog(Node owner) {
super(owner, Mode.URI, "mostRecentJrdsUrls", true);
this.setDialogHeaderText("Connect to a JRDS source");
this.tabsChoiceBox = new ChoiceBox<>();
tabsChoiceBox.getItems().addAll(JrdsTreeViewTab.values());
this.extraArgumentTextField = new TextField();
HBox.setHgrow(extraArgumentTextField, Priority.ALWAYS);
HBox hBox = new HBox(tabsChoiceBox, extraArgumentTextField);
hBox.setSpacing(10);
GridPane.setConstraints(hBox, 1, 2, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
tabsChoiceBox.getSelectionModel().select(JrdsTreeViewTab.HOSTS_TAB);
Label tabsLabel = new Label("Sorted By:");
GridPane.setConstraints(tabsLabel, 0, 2, 1, 1, HPos.LEFT, VPos.CENTER, Priority.ALWAYS, Priority.ALWAYS, new Insets(4, 0, 4, 0));
getParamsGridPane().getChildren().addAll(tabsLabel, hBox);
extraArgumentTextField.visibleProperty().bind(Bindings.createBooleanBinding(() -> this.tabsChoiceBox.valueProperty().get().getArgument() != null, this.tabsChoiceBox.valueProperty()));
}
@Override
protected Collection<DataAdapter> getDataAdapters() throws DataAdapterException {
return List.of(JrdsDataAdapter.fromUrl(
getSourceUri(),
ZoneId.of(getSourceTimezone()),
this.tabsChoiceBox.getValue(),
this.extraArgumentTextField.getText()));
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2020 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
import eu.binjr.common.logging.Logger;
import eu.binjr.core.data.adapters.TimeSeriesBinding;
import eu.binjr.core.data.workspace.ChartType;
import eu.binjr.core.data.workspace.UnitPrefixes;
import javafx.scene.paint.Color;
/**
* Factory used to build instances of {@link eu.binjr.core.data.adapters.SourceBinding} from values
* configured by the setters, for use with {@link JrdsDataAdapter}
*/
public class JrdsBindingBuilder extends TimeSeriesBinding.Builder {
private static final Logger logger = Logger.create(JrdsBindingBuilder.class);
@Override
protected JrdsBindingBuilder self() {
return this;
}
/**
* Sets the builder with all the parameters contained in a {@link Graphdesc} instance.
*
* @param graphdesc the graphdesc
* @return This builder.
*/
public JrdsBindingBuilder withGraphDesc(Graphdesc graphdesc) {
withLabel(isNullOrEmpty(graphdesc.name) ?
(isNullOrEmpty(graphdesc.graphName) ?
"???" : graphdesc.graphName) : graphdesc.name);
withGraphType(getChartType(graphdesc));
withPrefix(findPrefix(graphdesc));
withUnitName(graphdesc.verticalLabel);
return self();
}
/**
* Sets the builder with all the parameters contained in the {@link eu.binjr.sources.jrds.adapters.Graphdesc.SeriesDesc}
* at the provided index
*
* @param graphdesc the graphdesc
* @param idx the index
* @return This builder.
*/
public JrdsBindingBuilder withGraphDesc(Graphdesc graphdesc, int idx) {
Graphdesc.SeriesDesc desc = graphdesc.seriesDescList.get(idx);
withLabel(isNullOrEmpty(desc.name) ?
(isNullOrEmpty(desc.dsName) ?
(isNullOrEmpty(desc.legend) ?
"???" : desc.legend) : desc.dsName) : desc.name);
Color c = null;
try {
if (!isNullOrEmpty(desc.color)) {
c = Color.web(desc.color);
}
} catch (IllegalArgumentException e) {
logger.warn("Invalid color string for binding " + toString());
}
withColor(c);
withLegend(isNullOrEmpty(desc.legend) ?
(isNullOrEmpty(desc.name) ?
(isNullOrEmpty(desc.dsName) ?
"???" : desc.dsName) : desc.name) : desc.legend);
withGraphType(getChartType(desc));
withPrefix(findPrefix(graphdesc));
withUnitName(graphdesc.verticalLabel);
return self();
}
private UnitPrefixes findPrefix(Graphdesc graphdesc) {
if (graphdesc.unit != null && graphdesc.unit.size() > 0) {
if (graphdesc.unit.get(0) instanceof Graphdesc.JrdsMetricUnitType) {
return UnitPrefixes.METRIC;
}
if (graphdesc.unit.get(0) instanceof Graphdesc.JrdsBinaryUnitType) {
return UnitPrefixes.BINARY;
}
}
return UnitPrefixes.UNDEFINED;
}
private ChartType getChartType(Graphdesc graphdesc) {
return graphdesc.seriesDescList.stream()
.filter(desc -> !desc.graphType.equalsIgnoreCase("none") && !desc.graphType.equalsIgnoreCase("comment"))
.reduce((last, n) -> n)
.map(this::getChartType)
.orElse(ChartType.UNDEFINED);
}
private ChartType getChartType(Graphdesc.SeriesDesc desc) {
return switch (desc.graphType.toLowerCase()) {
case "area" -> ChartType.AREA;
case "line" -> ChartType.LINE;
case "stacked" -> ChartType.STACKED;
default -> ChartType.UNDEFINED;
};
}
private boolean isNullOrEmpty(String s) {
return s == null || s.trim().length() == 0;
}
}
@@ -0,0 +1,404 @@
/*
* Copyright 2016-2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import eu.binjr.common.logging.Logger;
import eu.binjr.common.xml.XmlUtils;
import eu.binjr.core.data.adapters.*;
import eu.binjr.core.data.codec.csv.CsvDecoder;
import eu.binjr.core.data.exceptions.DataAdapterException;
import eu.binjr.core.data.exceptions.FetchingDataFromAdapterException;
import eu.binjr.core.data.exceptions.InvalidAdapterParameterException;
import eu.binjr.core.data.exceptions.SourceCommunicationException;
import eu.binjr.core.data.timeseries.DoubleTimeSeriesProcessor;
import eu.binjr.core.dialogs.Dialogs;
import eu.binjr.sources.jrds.adapters.json.JsonJrdsItem;
import eu.binjr.sources.jrds.adapters.json.JsonJrdsTree;
import jakarta.xml.bind.JAXB;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import org.eclipse.fx.ui.controls.tree.FilterableTreeItem;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpResponse;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
/**
* This class provides an implementation of {@link SerializedDataAdapter} for JRDS.
*
* @author Frederic Thevenet
*/
@XmlAccessorType(XmlAccessType.FIELD)
public class JrdsDataAdapter extends HttpDataAdapter<Double> {
private static final String JRDS_FILTER = "filter";
private static final String JRDS_TREE = "tree";
private static final String ENCODING_PARAM_NAME = "encoding";
private static final String ZONE_ID_PARAM_NAME = "zoneId";
private static final String TREE_VIEW_TAB_PARAM_NAME = "treeViewTab";
private static final Logger logger = Logger.create(JrdsDataAdapter.class);
private static final char DELIMITER = ',';
private final Gson gson;
private CsvDecoder decoder;
private String filter;
private ZoneId zoneId;
private String encoding;
private JrdsTreeViewTab treeViewTab;
/**
* Initialises a new instance of the {@link JrdsDataAdapter} class.
*
* @throws DataAdapterException if an error occurs while initializing the adapter.
*/
public JrdsDataAdapter() throws DataAdapterException {
this(null, ZoneId.systemDefault(), "utf-8", JrdsTreeViewTab.HOSTS_TAB, "");
}
/**
* Initializes a new instance of the {@link JrdsDataAdapter} class.
*
* @param baseURL the URL to the JRDS webapp.
* @param zoneId the id of the time zone used to record dates.
* @param encoding the encoding used by the download servlet.
* @param treeViewTab the tab to apply.
* @param filter the filter to apply to the tree view.
* @throws DataAdapterException if an error occurs while initializing the adapter.
*/
public JrdsDataAdapter(URL baseURL, ZoneId zoneId, String encoding, JrdsTreeViewTab treeViewTab, String filter) throws DataAdapterException {
super(baseURL);
this.zoneId = zoneId;
this.encoding = encoding;
this.treeViewTab = treeViewTab;
this.filter = filter;
this.decoder = decoderFactory(zoneId);
gson = new Gson();
}
/**
* Builds a new instance of the {@link JrdsDataAdapter} class from the provided parameters.
*
* @param address the URL to the JRDS webapp.
* @param zoneId the id of the time zone used to record dates.
* @param treeViewTab the tab to apply.
* @param filter the filter to apply to the tree view.
* @return a new instance of the {@link JrdsDataAdapter} class.
* @throws DataAdapterException if an error occurs while initializing the adapter.
*/
public static JrdsDataAdapter fromUrl(String address, ZoneId zoneId, JrdsTreeViewTab treeViewTab, String filter) throws DataAdapterException {
return new JrdsDataAdapter(urlFromString(address), zoneId, "utf-8", treeViewTab, filter);
}
@Override
public FilterableTreeItem<SourceBinding> getBindingTree() throws DataAdapterException {
try {
JsonJrdsTree t = gson.fromJson(getJsonTree(treeViewTab.getCommand(), treeViewTab.getArgument(), filter), JsonJrdsTree.class);
Map<String, JsonJrdsItem> m = Arrays.stream(t.items).collect(Collectors.toMap(o -> o.id, (o -> o)));
FilterableTreeItem<SourceBinding> tree = new FilterableTreeItem<>(
new JrdsBindingBuilder()
.withLabel(getSourceName())
.withPath("/")
.withAdapter(this)
.build());
for (JsonJrdsItem branch : Arrays.stream(t.items)
.filter(jsonJrdsItem -> JRDS_TREE.equals(jsonJrdsItem.type) || JRDS_FILTER.equals(jsonJrdsItem.type))
.collect(Collectors.toList())) {
attachNode(tree, branch.id, m);
}
return tree;
} catch (JsonParseException e) {
throw new DataAdapterException("An error occurred while parsing the json response to getBindingTree request", e);
} catch (URISyntaxException e) {
throw new SourceCommunicationException("Error building URI for request", e);
}
}
@Override
protected URI craftFetchUri(String path, Instant begin, Instant end) throws DataAdapterException {
return craftRequestUri("download",
new UriParameter("id", path),
new UriParameter("begin", Long.toString(begin.toEpochMilli())),
new UriParameter("end", Long.toString(end.toEpochMilli()))
);
}
@Override
public String getSourceName() {
return new StringBuilder("[JRDS] ")
.append(getBaseAddress() != null ? getBaseAddress().getHost() : "???")
.append((getBaseAddress() != null && getBaseAddress().getPort() > 0) ? ":" + getBaseAddress().getPort() : "")
.append(" - ")
.append(treeViewTab != null ? treeViewTab : "???")
.append(filter != null ? filter : "")
.append(" (")
.append(zoneId != null ? zoneId : "???")
.append(")").toString();
}
@Override
public Map<String, String> getParams() {
Map<String, String> params = new HashMap<>(super.getParams());
params.put(ZONE_ID_PARAM_NAME, zoneId.toString());
params.put(ENCODING_PARAM_NAME, encoding);
params.put(TREE_VIEW_TAB_PARAM_NAME, treeViewTab.name());
params.put(JRDS_FILTER, this.filter);
return params;
}
@Override
public void loadParams(Map<String, String> params) throws DataAdapterException {
if (params == null) {
throw new InvalidAdapterParameterException("Could not find parameter list for adapter " + getSourceName());
}
super.loadParams(params);
encoding = validateParameterNullity(params, ENCODING_PARAM_NAME);
zoneId = validateParameter(params, ZONE_ID_PARAM_NAME,
s -> {
if (s == null) {
throw new InvalidAdapterParameterException("Parameter " + ZONE_ID_PARAM_NAME + " is missing in adapter " + getSourceName());
}
return ZoneId.of(s);
});
treeViewTab = validateParameter(params, TREE_VIEW_TAB_PARAM_NAME, s -> s == null ? JrdsTreeViewTab.valueOf(params.get(TREE_VIEW_TAB_PARAM_NAME)) : JrdsTreeViewTab.HOSTS_TAB);
this.filter = params.get(JRDS_FILTER);
this.decoder = decoderFactory(zoneId);
}
@Override
public String getEncoding() {
return encoding;
}
@Override
public ZoneId getTimeZoneId() {
return zoneId;
}
@Override
public CsvDecoder getDecoder() {
return decoder;
}
@Override
public void close() {
super.close();
}
/**
* Returns a collection of filters provided by the JRDS server.
*
* @return a collection of filters provided by the JRDS server.
* @throws DataAdapterException if an error occurs while parsing the server's response.
* @throws URISyntaxException if the generated url is not valid.
*/
public Collection<String> discoverFilters() throws DataAdapterException, URISyntaxException {
try {
JsonJrdsTree t = gson.fromJson(getJsonTree(treeViewTab.getCommand(), treeViewTab.getArgument()), JsonJrdsTree.class);
return Arrays.stream(t.items).filter(jsonJrdsItem -> JRDS_FILTER.equals(jsonJrdsItem.type)).map(i -> i.filter).collect(Collectors.toList());
} catch (JsonParseException e) {
throw new DataAdapterException("An error occurred while parsing the json response to getBindingTree request", e);
}
}
private void attachNode(FilterableTreeItem<SourceBinding> tree, String id, Map<String, JsonJrdsItem> nodes) throws DataAdapterException {
JsonJrdsItem n = nodes.get(id);
String currentPath = normalizeId(n.id);
FilterableTreeItem<SourceBinding> newBranch = new FilterableTreeItem<>(
new JrdsBindingBuilder()
.withParent(tree.getValue())
.withLabel(n.name)
.withPath(currentPath)
.withAdapter(this)
.build());
if (JRDS_FILTER.equals(n.type)) {
// add a dummy node so that the branch can be expanded
newBranch.getInternalChildren().add(new FilterableTreeItem<>(null));
// add a listener that will get the treeview filtered according to the selected filter/tag
newBranch.expandedProperty().addListener(new FilteredViewListener(n, newBranch));
} else {
if (n.children != null) {
for (JsonJrdsItem.JsonTreeRef ref : n.children) {
attachNode(newBranch, ref._reference, nodes);
}
} else {
// add a dummy node so that the branch can be expanded
newBranch.getInternalChildren().add(new FilterableTreeItem<>(null));
// add a listener so that bindings for individual datastore are added lazily to avoid
// dozens of individual call to "graphdesc" when the tree is built.
newBranch.expandedProperty().addListener(new GraphDescListener(currentPath, newBranch, tree));
}
}
tree.getInternalChildren().add(newBranch);
}
private String normalizeId(String id) {
if (id == null || id.trim().length() == 0) {
throw new IllegalArgumentException("Argument id cannot be null or blank");
}
String[] data = id.split("\\.");
return data[data.length - 1];
}
private String getJsonTree(String tabName, String argName) throws DataAdapterException, URISyntaxException {
return getJsonTree(tabName, argName, null);
}
private String getJsonTree(String tabName, String argName, String argValue) throws DataAdapterException, URISyntaxException {
List<NameValuePair> params = new ArrayList<>();
params.add(new UriParameter("tab", tabName));
if (argName != null && argValue != null && argValue.trim().length() > 0) {
params.add(new UriParameter(argName, argValue));
}
String entityString = doHttpGetJson(craftRequestUri("jsontree", params));
logger.trace(entityString);
return entityString;
}
private Graphdesc getGraphDescriptor(String id) throws DataAdapterException {
URI requestUri = craftRequestUri("graphdesc", new UriParameter("id", id));
return doHttpGet(requestUri, responseInfo -> HttpResponse.BodySubscribers.mapping(HttpResponse.BodySubscribers.ofInputStream(), body -> {
if (responseInfo.statusCode() == 404) {
// This is probably an older version of JRDS that doesn't provide the graphdesc service,
// so we're falling back to recovering the datastore name from the csv file provided by
// the download service.
logger.warn("Cannot found graphdesc service; falling back to legacy mode.");
try {
return getGraphDescriptorLegacy(id);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (body == null) {
return null;
}
try {
return JAXB.unmarshal(XmlUtils.toNonValidatingSAXSource(body), Graphdesc.class);
} catch (Exception e) {
throw new RuntimeException("Failed to unmarshall graphdesc response", e);
}
}));
}
private Graphdesc getGraphDescriptorLegacy(String id) throws DataAdapterException {
Instant now = ZonedDateTime.now().toInstant();
try (InputStream in = fetchRawData(id, now.minusSeconds(300), now, false)) {
List<String> headers = getDecoder().getDataColumnHeaders(in);
Graphdesc desc = new Graphdesc();
desc.seriesDescList = new ArrayList<>();
for (String header : headers) {
Graphdesc.SeriesDesc d = new Graphdesc.SeriesDesc();
d.name = header;
desc.seriesDescList.add(d);
}
return desc;
} catch (IOException e) {
throw new FetchingDataFromAdapterException(e);
}
}
private CsvDecoder decoderFactory(ZoneId zoneId) {
return new CsvDecoder(getEncoding(), DELIMITER,
DoubleTimeSeriesProcessor::new,
s -> ZonedDateTime.parse(s, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(zoneId)));
}
private class GraphDescListener implements ChangeListener<Boolean> {
private final String currentPath;
private final FilterableTreeItem<SourceBinding> newBranch;
private final FilterableTreeItem<SourceBinding> tree;
public GraphDescListener(String currentPath, FilterableTreeItem<SourceBinding> newBranch, FilterableTreeItem<SourceBinding> tree) {
this.currentPath = currentPath;
this.newBranch = newBranch;
this.tree = tree;
}
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
try {
Graphdesc graphdesc = getGraphDescriptor(currentPath);
newBranch.setValue(new JrdsBindingBuilder()
.withGraphDesc(graphdesc)
.withParent(tree.getValue())
.withLegend(newBranch.getValue().getLegend())
.withPath(currentPath)
.withAdapter(JrdsDataAdapter.this)
.build());
for (int i = 0; i < graphdesc.seriesDescList.size(); i++) {
String graphType = graphdesc.seriesDescList.get(i).graphType;
if (!"none".equalsIgnoreCase(graphType) && !"comment".equalsIgnoreCase(graphType)) {
newBranch.getInternalChildren().add(new FilterableTreeItem<>((new JrdsBindingBuilder()
.withGraphDesc(graphdesc, i)
.withParent(newBranch.getValue())
.withPath(currentPath)
.withAdapter(JrdsDataAdapter.this)
.build())));
}
}
//remove dummy node
newBranch.getInternalChildren().remove(0);
// remove the listener so it isn't executed next time node is expanded
newBranch.expandedProperty().removeListener(this);
} catch (Exception e) {
Dialogs.notifyException("Failed to retrieve graph description", e);
}
}
}
}
private class FilteredViewListener implements ChangeListener<Boolean> {
private final JsonJrdsItem n;
private final FilterableTreeItem<SourceBinding> newBranch;
public FilteredViewListener(JsonJrdsItem n, FilterableTreeItem<SourceBinding> newBranch) {
this.n = n;
this.newBranch = newBranch;
}
@Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
try {
JsonJrdsTree t = gson.fromJson(getJsonTree(treeViewTab.getCommand(), JRDS_FILTER, n.name), JsonJrdsTree.class);
Map<String, JsonJrdsItem> m = Arrays.stream(t.items).collect(Collectors.toMap(o -> o.id, (o -> o)));
for (JsonJrdsItem branch : Arrays.stream(t.items).filter(jsonJrdsItem -> JRDS_TREE.equals(jsonJrdsItem.type) || JRDS_FILTER.equals(jsonJrdsItem.type)).collect(Collectors.toList())) {
attachNode(newBranch, branch.id, m);
}
//remove dummy node
newBranch.getInternalChildren().remove(0);
// remove the listener so it isn't executed next time node is expanded
newBranch.expandedProperty().removeListener(this);
} catch (Exception e) {
Dialogs.notifyException("Failed to retrieve graph description", e);
}
}
}
}
}
@@ -0,0 +1,53 @@
/*
* Copyright 2017-2023 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
import eu.binjr.core.data.adapters.AdapterMetadata;
import eu.binjr.core.data.adapters.BaseDataAdapterInfo;
import eu.binjr.core.data.adapters.SourceLocality;
import eu.binjr.core.data.adapters.VisualizationType;
import eu.binjr.core.data.exceptions.CannotInitializeDataAdapterException;
import eu.binjr.core.preferences.AppEnvironment;
/**
* Defines the metadata associated with the JrdsDataAdapter.
*
* @author Frederic Thevenet
*/
@AdapterMetadata(
name = "JRDS",
description = "JRDS Data Adapter",
copyright = AppEnvironment.COPYRIGHT_NOTICE,
license = AppEnvironment.LICENSE,
siteUrl = AppEnvironment.HTTP_WWW_BINJR_EU,
adapterClass = JrdsDataAdapter.class,
dialogClass = JrdsAdapterDialog.class,
sourceLocality = SourceLocality.REMOTE,
apiLevel = AppEnvironment.PLUGIN_API_LEVEL,
visualizationType = VisualizationType.CHARTS
)
public class JrdsDataAdapterInfo extends BaseDataAdapterInfo {
/**
* Initialises a new instance of the {@link JrdsDataAdapterInfo} class.
*
* @throws CannotInitializeDataAdapterException if the adapter's initialization failed
*/
public JrdsDataAdapterInfo() throws CannotInitializeDataAdapterException {
super(JrdsDataAdapterInfo.class);
}
}
@@ -0,0 +1,90 @@
/*
* Copyright 2017-2020 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters;
/**
* An enumeration of JRDS tree tabs.
*
* @author Frederic Thevenet
*/
public enum JrdsTreeViewTab {
/**
* All Hosts
*/
HOSTS_TAB("All Hosts", "hoststab", null),
/**
* Single Host
*/
SINGLE_HOST("Host: ", "hoststab", "host"),
/**
* All Services
*/
SERVICE_TAB("All Services", "servicestab", null),
/**
* All Views
*/
VIEWS_TAB("All Views", "viewstab", null),
/**
* All Filters
*/
FILTER_TAB("All Filters", "filtertab", null),
/**
* Single Filter
*/
SINGLE_FILTER("Filter: ", "filtertab", "filter"),
/**
* All Tags
*/
TAGS_TAB("All Tags", "tagstab", null),
/**
* Single Tag
*/
SINGLE_TAG("Tag: ", "tagstab", "filter");
private final String argument;
private final String label;
private final String command;
JrdsTreeViewTab(String label, String command, String argument) {
this.label = label;
this.command = command;
this.argument = argument;
}
@Override
public String toString() {
return label;
}
/**
* Gets the command keyword associated with the enum entry
*
* @return the command keyword associated with the enum entry
*/
public String getCommand() {
return command;
}
/**
* Gets the argument associated with the enum entry
*
* @return the argument associated with the enum entry
*/
public String getArgument() {
return argument;
}
}
@@ -0,0 +1,66 @@
/*
* Copyright 2017-2018 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters.json;
import java.util.Arrays;
/**
* JRDS item data class
*/
public class JsonJrdsItem {
/**
* The name
*/
public String name;
/**
* The id
*/
public String id;
/**
* The type
*/
public String type;
/**
* The filter
*/
public String filter;
/**
* Children list
*/
public JsonTreeRef[] children;
/**
* TreeRef
*/
public static class JsonTreeRef {
/**
* reference
*/
public String _reference;
}
@Override
public String toString() {
return "JsonJrdsItem{" +
"name='" + name + '\'' +
", id='" + id + '\'' +
", type='" + type + '\'' +
", filter='" + filter + '\'' +
", children=" + Arrays.toString(children) +
'}';
}
}
@@ -0,0 +1,51 @@
/*
* Copyright 2017-2018 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters.json;
import java.util.Arrays;
/**
* Data class for JRDS status
*/
public class JsonJrdsStatus {
/**
* Hosts
*/
public int Hosts;
/**
* Probes
*/
public int Probes;
/**
* Timers
*/
public JsonJrdsTimer[] Timers;
/**
* Generation
*/
public int Generation;
@Override
public String toString() {
return "JsonJrdsStatus{" +
"Hosts=" + Hosts +
", Probes=" + Probes +
", Timers=" + Arrays.toString(Timers) +
", Generation=" + Generation +
'}';
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2017-2018 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters.json;
/**
* Data class for JRDS Timer
*/
public class JsonJrdsTimer {
/**
* Name
*/
public String Name;
/**
* Last collect
*/
public long LastCollect;
/**
* Last duration
*/
public long LastDuration;
@Override
public String toString() {
return "JsonJrdsTimer{" +
"Name='" + Name + '\'' +
", LastCollect=" + LastCollect +
", LastDuration=" + LastDuration +
'}';
}
}
@@ -0,0 +1,46 @@
/*
* Copyright 2017-2018 Frederic Thevenet
*
* 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 eu.binjr.sources.jrds.adapters.json;
import java.util.Arrays;
/**
* POJO definition used to decode JSON message.
*/
public class JsonJrdsTree {
/**
* indentifier
*/
public String identifier;
/**
* label
*/
public String label;
/**
* Items
*/
public JsonJrdsItem[] items;
@Override
public String toString() {
return "JsonJrdsTree{" +
"identifier='" + identifier + '\'' +
", label='" + label + '\'' +
", items=" + Arrays.toString(items) +
'}';
}
}
@@ -0,0 +1,18 @@
#
# Copyright 2017-2018 Frederic Thevenet
#
# 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.
#
# JRDS Data adapter service implementation
eu.binjr.sources.jrds.adapters.JrdsDataAdapterInfo