Skip to content

Commit

Permalink
Merge branch 'release/3.38.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
msqr committed Oct 30, 2024
2 parents 5204e62 + 75d3073 commit 6ce17c2
Show file tree
Hide file tree
Showing 24 changed files with 1,892 additions and 55 deletions.
2 changes: 1 addition & 1 deletion solarnet/cloud-integrations/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ dependencyManagement {
}

description = 'SolarNet: Cloud Integrations'
version = '1.7.0'
version = '1.8.0'

base {
archivesName = 'solarnet-cloud-integrations'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,63 @@
import net.solarnetwork.central.c2c.biz.CloudIntegrationService;
import net.solarnetwork.central.c2c.domain.CloudIntegrationConfiguration;
import net.solarnetwork.settings.SettingSpecifier;
import net.solarnetwork.settings.TextFieldSettingSpecifier;
import net.solarnetwork.settings.support.BasicTextFieldSettingSpecifier;

/**
* Abstract base implementation of {@link CloudIntegrationService}.
*
* @author matt
* @version 1.1
* @version 1.2
*/
public abstract class BaseCloudIntegrationService extends BaseCloudIntegrationsIdentifiableService
implements CloudIntegrationService {

/**
* A setting specifier for the
* {@link CloudIntegrationService#BASE_URL_SETTING}.
*
* @since 1.2
*/
public static final TextFieldSettingSpecifier BASE_URL_SETTING_SPECIFIER = new BasicTextFieldSettingSpecifier(
BASE_URL_SETTING, null);

/**
* A setting specifier for the
* {@link CloudIntegrationService#USERNAME_SETTING}.
*
* @since 1.2
*/
public static final TextFieldSettingSpecifier USERNAME_SETTING_SPECIFIER = new BasicTextFieldSettingSpecifier(
USERNAME_SETTING, null);

/**
* A setting specifier for the
* {@link CloudIntegrationService#PASSWORD_SETTING}.
*
* @since 1.2
*/
public static final TextFieldSettingSpecifier PASSWORD_SETTING_SPECIFIER = new BasicTextFieldSettingSpecifier(
PASSWORD_SETTING, null, true);

/**
* A setting specifier for the
* {@link CloudIntegrationService#OAUTH_CLIENT_ID_SETTING}.
*
* @since 1.2
*/
public static final TextFieldSettingSpecifier OAUTH_CLIENT_ID_SETTING_SPECIFIER = new BasicTextFieldSettingSpecifier(
OAUTH_CLIENT_ID_SETTING, null);

/**
* A setting specifier for the
* {@link CloudIntegrationService#OAUTH_CLIENT_SECRET_SETTING_SPECIFIER}.
*
* @since 1.2
*/
public static final TextFieldSettingSpecifier OAUTH_CLIENT_SECRET_SETTING_SPECIFIER = new BasicTextFieldSettingSpecifier(
OAUTH_CLIENT_SECRET_SETTING, null, true);

/** The supported datum stream services. */
protected final Collection<CloudDatumStreamService> datumStreamServices;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/* ==================================================================
* BaseSolcastCloudDatumStreamService.java - 30/10/2024 5:25:39 am
*
* Copyright 2024 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/

package net.solarnetwork.central.c2c.biz.impl;

import static net.solarnetwork.util.ObjectUtils.requireNonNullArgument;
import static net.solarnetwork.util.StringUtils.nonEmptyString;
import java.time.Clock;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import org.springframework.security.crypto.encrypt.TextEncryptor;
import org.springframework.web.client.RestOperations;
import net.solarnetwork.central.biz.UserEventAppenderBiz;
import net.solarnetwork.central.c2c.biz.CloudDatumStreamService;
import net.solarnetwork.central.c2c.biz.CloudIntegrationsExpressionService;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamMappingConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudDatumStreamPropertyConfigurationDao;
import net.solarnetwork.central.c2c.dao.CloudIntegrationConfigurationDao;
import net.solarnetwork.central.c2c.domain.CloudDatumStreamConfiguration;
import net.solarnetwork.settings.MultiValueSettingSpecifier;
import net.solarnetwork.settings.SettingSpecifier;
import net.solarnetwork.settings.support.BasicMultiValueSettingSpecifier;

/**
* Abstract base class for Solcast implementations of
* {@link CloudDatumStreamService}.
*
* @author matt
* @version 1.0
*/
public abstract class BaseSolcastCloudDatumStreamService
extends BaseRestOperationsCloudDatumStreamService {

/** The setting for latitude. */
public static final String LATITUDE_SETTING = "lat";

/** The setting for longitude. */
public static final String LONGITUDE_SETTING = "lon";

/** The setting for azimuth. */
public static final String AZIMUTH_SETTING = "azimuth";

/** The setting for tilt. */
public static final String TILT_SETTING = "tilt";

/** The setting for array type. */
public static final String ARRAY_TYPE_SETTING = "arrayType";

/** The setting for resolution. */
public static final String RESOLUTION_SETTING = "resolution";

/** The {@code parameters} default value. */
public static final String DEFAULT_PARAMETERS = "air_temp,dni,ghi";

/** The {@code resolution} default value. */
public static final Duration DEFAULT_RESOLUTION = Duration.ofMinutes(5);

/** The Solcast supported resolutions. */
public static final Set<Duration> SUPPORTED_RESOLUTIONS;
static {
SUPPORTED_RESOLUTIONS = Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
// @formatter:off
Duration.ofMinutes(5),
Duration.ofMinutes(10),
Duration.ofMinutes(15),
Duration.ofMinutes(20),
Duration.ofMinutes(30),
Duration.ofMinutes(60)
// @formatter:on
)));
}

/** Setting specifier for the {@link #ARRAY_TYPE_SETTING} setting. */
public static final MultiValueSettingSpecifier ARRAY_TYPE_SETTTING_SPECIFIER;
static {
BasicMultiValueSettingSpecifier arrayTypeSpec = new BasicMultiValueSettingSpecifier(
ARRAY_TYPE_SETTING, "");
Map<String, String> arrayTypeValues = new LinkedHashMap<>(3);
arrayTypeValues.put("", "");
arrayTypeValues.put("fixed", "Fixed");
arrayTypeValues.put("horizontal_single_axis", "Horizontal Single Axis");
arrayTypeSpec.setValueTitles(arrayTypeValues);
ARRAY_TYPE_SETTTING_SPECIFIER = arrayTypeSpec;
}

/** Setting specifier for the {@link #RESOLUTION_SETTING} setting. */
public static final MultiValueSettingSpecifier RESOLUTION_SETTING_SPECIFIER;
static {
BasicMultiValueSettingSpecifier resolutionSpec = new BasicMultiValueSettingSpecifier(
RESOLUTION_SETTING, DEFAULT_RESOLUTION.toString());
Map<String, String> resolutionMenuValues = new LinkedHashMap<>(SUPPORTED_RESOLUTIONS.size());
for ( Duration d : SUPPORTED_RESOLUTIONS ) {
String key = d.toString();
resolutionMenuValues.put(key, (d.getSeconds() / 60) + " minute");
}
resolutionSpec.setValueTitles(resolutionMenuValues);
RESOLUTION_SETTING_SPECIFIER = resolutionSpec;

}

/** The clock. */
protected final Clock clock;

/**
* Constructor.
*
* @param serviceIdentifier
* the service identifier
* @param userEventAppenderBiz
* the user event appender service
* @param encryptor
* the sensitive key encryptor
* @param expressionService
* the expression service
* @param integrationDao
* the integration DAO
* @param datumStreamDao
* the datum stream DAO
* @param datumStreamMappingDao
* the datum stream mapping DAO
* @param datumStreamPropertyDao
* the datum stream property DAO
* @param restOps
* the REST operations
* @param restOpsLogger
* the logger to use with the REST operations
* @param clock
* the clock to use
* @throws IllegalArgumentException
* if any argument is {@literal null}
*/
public BaseSolcastCloudDatumStreamService(String serviceIdentifier, String displayName,
UserEventAppenderBiz userEventAppenderBiz, TextEncryptor encryptor,
CloudIntegrationsExpressionService expressionService,
CloudIntegrationConfigurationDao integrationDao,
CloudDatumStreamConfigurationDao datumStreamDao,
CloudDatumStreamMappingConfigurationDao datumStreamMappingDao,
CloudDatumStreamPropertyConfigurationDao datumStreamPropertyDao,
List<SettingSpecifier> settings, RestOperations restOps, Logger restOpsLogger, Clock clock) {
super(serviceIdentifier, displayName, userEventAppenderBiz, encryptor, expressionService,
integrationDao, datumStreamDao, datumStreamMappingDao, datumStreamPropertyDao, settings,
new SolcastRestOperationsHelper(restOpsLogger, userEventAppenderBiz, restOps,
HTTP_ERROR_TAGS, encryptor,
integrationServiceIdentifier -> SolcastCloudIntegrationService.SECURE_SETTINGS));
this.clock = requireNonNullArgument(clock, "clock");
}

/**
* Resolve a resolution setting value.
*
* <p>
* The returned resolution will be limited to those in the
* {@link #SUPPORTED_RESOLUTIONS} set. If no {@link #RESOLUTION_SETTING} is
* available, or is not a supported resolution, then
* {@link #DEFAULT_RESOLUTION} will be returned.
* </p>
*
* @param datumStream
* the datum stream to resolve the resolution setting from
* @return the resolution, never {@literal null}
*/
public static Duration resolveResolution(CloudDatumStreamConfiguration datumStream) {
String resoValue = nonEmptyString(datumStream.serviceProperty(RESOLUTION_SETTING, String.class));
Duration result = DEFAULT_RESOLUTION;
if ( resoValue != null ) {
try {
result = Duration.ofSeconds(Long.parseLong(resoValue));
} catch ( NumberFormatException e ) {
try {
result = Duration.parse(resoValue);
} catch ( DateTimeParseException e2 ) {
// ignore and fall back to default
}
}
}
if ( !SUPPORTED_RESOLUTIONS.contains(result) ) {
result = DEFAULT_RESOLUTION;
}
return result;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,7 @@
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestClientResponseException;
import net.solarnetwork.central.biz.UserEventAppenderBiz;
import net.solarnetwork.central.c2c.biz.CloudDatumStreamPollService;
import net.solarnetwork.central.c2c.biz.CloudDatumStreamService;
Expand Down Expand Up @@ -226,15 +225,8 @@ public CloudDatumStreamPollTaskEntity call() throws Exception {
var oldState = taskInfo.getState();
taskInfo.setMessage(errMsg);
taskInfo.putServiceProps(errData);
if ( !((e instanceof RestClientException && !(e instanceof HttpClientErrorException))
|| t instanceof IOException) ) {
// stop processing job if not what appears to be a API IO exception
log.info(
"Stopping datum stream {} poll task by changing state from {} to {} after error: {}",
taskInfo.getId().ident(), oldState, Completed, e.toString());
taskInfo.setState(Completed);
} else {
// reset back to queued to try again
if ( t instanceof RestClientResponseException || t instanceof IOException ) {
// reset back to queued to try again if HTTP client or IO error
log.info(
"Resetting datum stream {} poll task by changing state from {} to {} after error: {}",
taskInfo.getId().ident(), oldState, Queued, e.toString());
Expand All @@ -244,6 +236,12 @@ public CloudDatumStreamPollTaskEntity call() throws Exception {
// bump date into future by 1 minute so we do not immediately try to process again
taskInfo.setExecuteAt(clock.instant().plusSeconds(60));
}
} else {
// stop processing job if not what appears to be a API IO exception
log.info(
"Stopping datum stream {} poll task by changing state from {} to {} after error: {}",
taskInfo.getId().ident(), oldState, Completed, e.toString());
taskInfo.setState(Completed);
}
userEventAppenderBiz.addEvent(taskInfo.getUserId(),
eventForConfiguration(taskInfo.getId(), POLL_ERROR_TAGS, errMsg, errData));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@

package net.solarnetwork.central.c2c.biz.impl;

import static net.solarnetwork.central.c2c.biz.CloudIntegrationService.PASSWORD_SETTING;
import static net.solarnetwork.central.c2c.biz.CloudIntegrationService.USERNAME_SETTING;
import static net.solarnetwork.central.c2c.biz.impl.CloudIntegrationsUtils.SECS_PER_HOUR;
import static net.solarnetwork.central.c2c.biz.impl.EgaugeCloudIntegrationService.BASE_URI_TEMPLATE;
import static net.solarnetwork.central.c2c.domain.CloudDataValue.dataValue;
Expand Down Expand Up @@ -115,7 +113,7 @@
* however.
*
* @author matt
* @version 1.0
* @version 1.1
*/
public class EgaugeCloudDatumStreamService extends BaseRestOperationsCloudDatumStreamService {

Expand All @@ -134,8 +132,8 @@ public class EgaugeCloudDatumStreamService extends BaseRestOperationsCloudDatumS
// @formatter:off
SETTINGS = List.of(
new BasicTextFieldSettingSpecifier(DEVICE_ID_FILTER, null),
new BasicTextFieldSettingSpecifier(USERNAME_SETTING, null),
new BasicTextFieldSettingSpecifier(PASSWORD_SETTING, null, true),
BaseCloudIntegrationService.USERNAME_SETTING_SPECIFIER,
BaseCloudIntegrationService.PASSWORD_SETTING_SPECIFIER,
new BasicTextFieldSettingSpecifier(GRANULARITY_SETTING, null)
);
// @formatter:on
Expand Down Expand Up @@ -358,17 +356,12 @@ public CloudDatumStreamQueryResult datum(CloudDatumStreamConfiguration datumStre
deviceId, valueProps);
final String queryRegisters = registerQueryParam(refsByRegisterName.values());

final List<GeneralDatum> resultDatum = new ArrayList<>(16);

List<GeneralDatum> datum = restOpsHelper.httpGet("List register data", datumStream,
final List<GeneralDatum> resultDatum = restOpsHelper.httpGet("List register data", datumStream,
JsonNode.class,
req -> fromUriString(BASE_URI_TEMPLATE).path(REGISTER_URL_PATH).queryParam("raw")
.queryParam("virtual", "value").queryParam("reg", queryRegisters)
.queryParam("time", queryTimeRange).buildAndExpand(deviceId).toUri(),
res -> parseDatum(res.getBody(), datumStream, deviceId, refsByRegisterName));
if ( datum != null ) {
resultDatum.addAll(datum);
}

// evaluate expressions on final datum
if ( !exprProps.isEmpty() ) {
Expand Down Expand Up @@ -706,6 +699,7 @@ private static List<GeneralDatum> parseDatum(JsonNode json,
datumVal = n.subtract(n1).multiply(type.getQuantum()).divide(deltaSecs,
RoundingMode.DOWN);
}
datumVal = (BigDecimal) property.applyValueTransforms(datumVal);
samples.putSampleValue(property.getPropertyType(), property.getPropertyName(),
datumVal);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,12 +103,12 @@ public class LocusEnergyCloudIntegrationService extends BaseOAuth2ClientCloudInt
public static final List<SettingSpecifier> SETTINGS;
static {
var settings = new ArrayList<SettingSpecifier>(1);
settings.add(new BasicTextFieldSettingSpecifier(OAUTH_CLIENT_ID_SETTING, null));
settings.add(new BasicTextFieldSettingSpecifier(OAUTH_CLIENT_SECRET_SETTING, null, true));
settings.add(new BasicTextFieldSettingSpecifier(USERNAME_SETTING, null));
settings.add(new BasicTextFieldSettingSpecifier(PASSWORD_SETTING, null, true));
settings.add(OAUTH_CLIENT_ID_SETTING_SPECIFIER);
settings.add(OAUTH_CLIENT_SECRET_SETTING_SPECIFIER);
settings.add(USERNAME_SETTING_SPECIFIER);
settings.add(PASSWORD_SETTING_SPECIFIER);
settings.add(new BasicTextFieldSettingSpecifier(PARTNER_ID_SETTING, null));
settings.add(new BasicTextFieldSettingSpecifier(BASE_URL_SETTING, null));
settings.add(BASE_URL_SETTING_SPECIFIER);
SETTINGS = Collections.unmodifiableList(settings);
}

Expand Down
Loading

0 comments on commit 6ce17c2

Please sign in to comment.