Skip to content

Commit f5a2eaa

Browse files
committed
Merge branch 'release/0.3.0'
2 parents bedc65c + 315ffe7 commit f5a2eaa

42 files changed

Lines changed: 1400 additions & 308 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ subprojects {
1414
apply plugin: 'net.ltgt.errorprone'
1515

1616
group = 's10k.tool'
17-
version = '0.2.0'
17+
version = '0.3.0'
1818

1919
repositories {
2020
mavenCentral()
@@ -73,7 +73,7 @@ subprojects {
7373
asciiTableVersion = '1.8.0'
7474
commonsIoVersion = '2.20.0'
7575
picoCliVersion = '4.7.7'
76-
snCommonVersion = '4.3.0'
76+
snCommonVersion = '4.4.0'
7777
snCommonWebJakartaVersion = '2.1.1'
7878
superCsvVersion = '2.4.0'
7979
threetenExtraVersion = '1.8.0'

common/src/main/java/s10k/tool/common/cmd/ToolCmd.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,18 @@ public class ToolCmd implements ProfileProvider {
4949
*
5050
* <p>
5151
* This will populate the {@code profile}, either via the profile settings for
52-
* {@code profileName}, the {@code tokenId} or {@code tokenSecret} options, or
53-
* the {@link #SN_TOKEN_ID_ENV} and {@link #SN_TOKEN_SECRET_ENV} environment
52+
* {@code profileName}, the default profile if no {@code profileName} provided,
53+
* the {@code tokenId} or {@code tokenSecret} options, or the
54+
* {@link #SN_TOKEN_ID_ENV} and {@link #SN_TOKEN_SECRET_ENV} environment
5455
* variables.
5556
* </p>
5657
*
5758
* @param parseResult the parse result
5859
* @return the exit code result
5960
*/
6061
public int globalInit(ParseResult parseResult) {
61-
if (profileName != null && !profileName.isBlank()) {
62-
profile = ProfileUtils.profile(profileName);
63-
}
64-
if (profile == null) {
62+
profile = ProfileUtils.profile(profileName);
63+
if (profileName == null || profile == null) {
6564
// use command options if available
6665
if (tokenId != null && !tokenId.isEmpty() && tokenSecret != null && tokenSecret.length > 0) {
6766
profile = new ProfileInfo("", new SnTokenCredentials(tokenId, tokenSecret));
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package s10k.tool.common.domain;
2+
3+
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
4+
5+
import com.fasterxml.jackson.annotation.JsonIgnore;
6+
7+
import net.solarnetwork.domain.datum.ObjectDatumKind;
8+
import net.solarnetwork.util.StringUtils;
9+
10+
/**
11+
* An object and source, uniquely identifying a datum stream.
12+
*
13+
* @param kind the object kind
14+
* @param objectId the object ID
15+
* @param sourceId the source ID
16+
*/
17+
@RegisterReflectionForBinding
18+
public record ObjectAndSource(ObjectDatumKind kind, Long objectId, String sourceId)
19+
implements Comparable<ObjectAndSource> {
20+
21+
@Override
22+
public int compareTo(ObjectAndSource o) {
23+
int result = kind.compareTo(o.kind);
24+
if (result == 0) {
25+
result = objectId.compareTo(o.objectId);
26+
if (result == 0) {
27+
result = StringUtils.naturalSortCompare(sourceId, o.sourceId, true);
28+
}
29+
}
30+
return result;
31+
}
32+
33+
/**
34+
* Test if the node and source are both populated.
35+
*
36+
* @return {@code true} if both {@code nodeId} and {@code sourceId} are not
37+
* empty
38+
*/
39+
@JsonIgnore
40+
public boolean isValid() {
41+
return kind != null && objectId != null && objectId.longValue() != 0 && sourceId != null && !sourceId.isBlank();
42+
}
43+
44+
}

common/src/main/java/s10k/tool/common/domain/SnTokenCredentials.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding;
77

8+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
89
import com.fasterxml.jackson.annotation.JsonProperty;
910

1011
import net.solarnetwork.security.Snws2AuthorizationBuilder;
@@ -16,6 +17,7 @@
1617
*/
1718
@SuppressWarnings("ArrayRecordComponent")
1819
@RegisterReflectionForBinding
20+
@JsonIgnoreProperties(ignoreUnknown = true)
1921
public record SnTokenCredentials(
2022
// @formatter:off
2123
@JsonProperty("sn_token_id")
@@ -26,6 +28,15 @@ public record SnTokenCredentials(
2628
// @formatter:on
2729
) {
2830

31+
/**
32+
* Test if credentials are available.
33+
*
34+
* @return {@code true} if credentials are available
35+
*/
36+
public boolean hasCredentials() {
37+
return (tokenId != null && !tokenId.isBlank() && tokenSecret != null && tokenSecret.length > 0);
38+
}
39+
2940
@Override
3041
public String toString() {
3142
return "SnTokenCredentials[tokenId=" + tokenId + "]";
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package s10k.tool.common.domain;
2+
3+
import static java.time.ZoneOffset.UTC;
4+
import static net.solarnetwork.util.StringUtils.commaDelimitedStringFromCollection;
5+
import static s10k.tool.common.util.DateUtils.isMidnight;
6+
7+
import java.time.LocalDateTime;
8+
import java.time.ZonedDateTime;
9+
import java.util.Arrays;
10+
import java.util.Collection;
11+
12+
import org.springframework.util.LinkedMultiValueMap;
13+
import org.springframework.util.MultiValueMap;
14+
15+
import net.solarnetwork.domain.datum.ObjectDatumKind;
16+
17+
/**
18+
* Search filter for source information.
19+
*/
20+
public record SourceFilter(Collection<Long> objectIds, Collection<String> sourceIds, ZonedDateTime startDate,
21+
ZonedDateTime endDate, boolean useLocalDates, String metadataFilter, Collection<String> propertyNames,
22+
Collection<String> instantaneousPropertyNames, Collection<String> accumulatingPropertyNames,
23+
Collection<String> statusPropertyNames) {
24+
25+
/**
26+
* Create a filter from array parameters.
27+
*
28+
* @param objectIds the object (node/location) IDs, or
29+
* {@code null}
30+
* @param sourceIds the source IDs, or {@code null}
31+
* @param startDate the minimum date the node must have data
32+
* after
33+
* @param endDate the maximum date (exclusive) the node must
34+
* have data after
35+
* @param useLocalDates {@code true} to treat the min/max dates as
36+
* node local, otherwise as UTC
37+
* @param metadataFilter a metadata filter
38+
* @param propertyNames the property names, or {@code null}
39+
* @param instantaneousPropertyNames the instantaneous property names, or
40+
* {@code null}
41+
* @param accumulatingPropertyNames the accumulating property names, or
42+
* {@code null}
43+
* @param statusPropertyNames the status property names, or {@code null}
44+
* @return the new filter instance
45+
*/
46+
public static SourceFilter sourceFilter(Long[] objectIds, String[] sourceIds, ZonedDateTime startDate,
47+
ZonedDateTime endDate, boolean useLocalDates, String metadataFilter, String[] propertyNames,
48+
String[] instantaneousPropertyNames, String[] accumulatingPropertyNames, String[] statusPropertyNames) {
49+
// @formatter:off
50+
return new SourceFilter(
51+
objectIds != null && objectIds.length > 0 ? Arrays.asList(objectIds) : null,
52+
sourceIds != null && sourceIds.length > 0 ? Arrays.asList(sourceIds) : null,
53+
startDate,
54+
endDate,
55+
useLocalDates,
56+
metadataFilter,
57+
propertyNames != null && propertyNames.length > 0 ? Arrays.asList(propertyNames) : null,
58+
instantaneousPropertyNames != null && instantaneousPropertyNames.length > 0 ? Arrays.asList(instantaneousPropertyNames) : null,
59+
accumulatingPropertyNames != null && accumulatingPropertyNames.length > 0 ? Arrays.asList(accumulatingPropertyNames) : null,
60+
statusPropertyNames != null && statusPropertyNames.length > 0 ? Arrays.asList(statusPropertyNames) : null
61+
);
62+
// @formatter:on
63+
}
64+
65+
/**
66+
* Get a node multi-value map from this filter.
67+
*
68+
* @return the multi-value map, suitable for using as request parameters for
69+
* node datum stream metadata
70+
*/
71+
public MultiValueMap<String, Object> toNodeRequestMap() {
72+
return toRequestMap(ObjectDatumKind.Node);
73+
}
74+
75+
/**
76+
* Get a location multi-value map from this filter.
77+
*
78+
* @return the multi-value map, suitable for using as request parameters for
79+
* location datum stream metadata
80+
*/
81+
public MultiValueMap<String, Object> toLocationRequestMap() {
82+
return toRequestMap(ObjectDatumKind.Location);
83+
}
84+
85+
/**
86+
* Get a multi-value map from this filter.
87+
*
88+
* @return the multi-value map, suitable for using as request parameters
89+
*/
90+
public MultiValueMap<String, Object> toRequestMap(ObjectDatumKind kind) {
91+
var postBody = new LinkedMultiValueMap<String, Object>(4);
92+
if (objectIds != null && !objectIds.isEmpty()) {
93+
postBody.set((kind == ObjectDatumKind.Location ? "locationIds" : "nodeIds"),
94+
commaDelimitedStringFromCollection(objectIds));
95+
}
96+
if (sourceIds != null && !sourceIds.isEmpty()) {
97+
postBody.set("sourceIds", commaDelimitedStringFromCollection(sourceIds));
98+
}
99+
if (startDate != null) {
100+
LocalDateTime dt;
101+
if (useLocalDates) {
102+
dt = startDate.toLocalDateTime();
103+
} else {
104+
dt = startDate.withZoneSameInstant(UTC).toLocalDateTime();
105+
}
106+
postBody.set(useLocalDates ? "localStartDate" : "startDate", isMidnight(dt) ? dt.toLocalDate() : dt);
107+
}
108+
if (endDate != null) {
109+
LocalDateTime dt;
110+
if (useLocalDates) {
111+
dt = endDate.toLocalDateTime();
112+
} else {
113+
dt = endDate.withZoneSameInstant(UTC).toLocalDateTime();
114+
}
115+
postBody.set(useLocalDates ? "localEndDate" : "endDate", isMidnight(dt) ? dt.toLocalDate() : dt);
116+
}
117+
if (metadataFilter != null && !metadataFilter.isBlank()) {
118+
postBody.set("metadataFilter", metadataFilter);
119+
}
120+
if (propertyNames != null && !propertyNames.isEmpty()) {
121+
postBody.set("propertyNames", commaDelimitedStringFromCollection(propertyNames));
122+
}
123+
if (instantaneousPropertyNames != null && !instantaneousPropertyNames.isEmpty()) {
124+
postBody.set("instantaneousPropertyNames", commaDelimitedStringFromCollection(instantaneousPropertyNames));
125+
}
126+
if (accumulatingPropertyNames != null && !accumulatingPropertyNames.isEmpty()) {
127+
postBody.set("accumulatingPropertyNames", commaDelimitedStringFromCollection(accumulatingPropertyNames));
128+
}
129+
if (statusPropertyNames != null && !statusPropertyNames.isEmpty()) {
130+
postBody.set("statusPropertyNames", commaDelimitedStringFromCollection(statusPropertyNames));
131+
}
132+
return postBody;
133+
}
134+
135+
}

common/src/main/java/s10k/tool/common/domain/TableDisplayMode.java

Lines changed: 0 additions & 19 deletions
This file was deleted.

common/src/main/java/s10k/tool/common/util/ProfileUtils.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ public static Path userConfigurationDir() {
4343
/**
4444
* Load a profile.
4545
*
46-
* @param name the name of the profile to load
46+
* @param name the name of the profile to load, or {@code null} for the
47+
* "default" profile
4748
* @return the profile, or {@code null} if the profile does not exist
4849
*/
4950
public static ProfileInfo profile(String name) {
@@ -54,10 +55,17 @@ public static ProfileInfo profile(String name) {
5455
TomlMapper mapper = new TomlMapper();
5556
try (InputStream in = Files.newInputStream(credPath)) {
5657
JsonNode root = mapper.readTree(in);
57-
JsonNode credsNode = root.findValue(name);
58+
JsonNode credsNode = null;
59+
if (name == null || name.isBlank()) {
60+
credsNode = root;
61+
} else {
62+
credsNode = root.findValue(name);
63+
}
5864
if (credsNode != null) {
5965
SnTokenCredentials creds = mapper.treeToValue(credsNode, SnTokenCredentials.class);
60-
return new ProfileInfo(name, creds);
66+
if (creds.hasCredentials()) {
67+
return new ProfileInfo(name, creds);
68+
}
6169
}
6270
}
6371
} catch (Exception e) {

common/src/main/java/s10k/tool/common/util/RestUtils.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import java.io.InputStream;
55
import java.io.OutputStream;
66
import java.util.List;
7+
import java.util.function.Supplier;
78

89
import org.springframework.http.client.BufferingClientHttpRequestFactory;
910
import org.springframework.http.client.ClientHttpRequestFactory;
1011
import org.springframework.http.converter.HttpMessageConverter;
1112
import org.springframework.http.converter.cbor.MappingJackson2CborHttpMessageConverter;
1213
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
14+
import org.springframework.util.MultiValueMap;
1315
import org.springframework.web.client.RestClient;
1416
import org.springframework.web.client.RestTemplate;
17+
import org.springframework.web.util.UriBuilder;
1518

1619
import com.fasterxml.jackson.core.JsonGenerator;
1720
import com.fasterxml.jackson.core.JsonParser;
@@ -113,4 +116,17 @@ public static void cborToJson(ObjectMapper objectMapper, InputStream in, OutputS
113116
}
114117
}
115118

119+
/**
120+
* Helper to populate URI query parameters from a map provider.
121+
*
122+
* @param uriBuilder the builder
123+
* @param mapProvider the map provider
124+
*/
125+
public static void populateQueryParameters(UriBuilder uriBuilder, Supplier<MultiValueMap<String, ?>> mapProvider) {
126+
MultiValueMap<String, ?> params = mapProvider.get();
127+
for (var e : params.entrySet()) {
128+
uriBuilder.queryParam(e.getKey(), e.getValue());
129+
}
130+
}
131+
116132
}

0 commit comments

Comments
 (0)