configuration/environments_scripts/sailing_server/files/usr/local/bin/refreshInstance.sh
... ...
@@ -153,17 +153,11 @@ install_environment ()
153 153
load_from_release_file ()
154 154
{
155 155
if [[ ${INSTALL_FROM_RELEASE} == "" ]]; then
156
- INSTALL_FROM_RELEASE="$(wget -O - https://releases.sapsailing.com/ 2>/dev/null | grep main- | tail -1 | sed -e 's/^.*\(main-[0-9]*\).*$/\1/')"
157
- echo "You didn't provide a release. Defaulting to latest master build https://releases.sapsailing.com/$INSTALL_FROM_RELEASE"
158
- # Alternatively, to download a release from Github, here is how the latest Java8/main-release can be obtained:
159
- # curl -L -H 'Authorization: Bearer ***' https://api.github.com/repos/SAP/sailing-analytics/releases 2>/dev/null | jq -r 'sort_by(.created_at) | reverse | map(select(.name | startswith("main-")))[0].assets[] | select(.content_type=="application/x-tar").id'
160
- # will output something like:
161
- # 169233159
162
- # which can then be used in a request such as:
163
- # curl -L -o main-1234567890.tar.gz -H 'Accept: application/octet-stream' -H 'Authorization: Bearer ***' 'https://api.github.com/repos/SAP/sailing-analytics/releases/assets/169233159'
164
- # Or to obtain the latest docker-17 release, try this:
165
- # curl -L -H 'Authorization: Bearer ***' https://api.github.com/repos/SAP/sailing-analytics/releases 2>/dev/null | jq -r 'sort_by(.created_at) | reverse | map(select(.name | startswith("docker-17-")))[0].assets[] | select(.content_type=="application/x-tar").id'
166
- # and then on like above...
156
+ GITHUB_RELEASE=$( curl -L "https://api.github.com/repos/SAP/sailing-analytics/releases?per_page=100" 2>/dev/null | jq -r 'sort_by(.created_at) | reverse | map(select(.name | startswith("main-")))[0].assets[] | select(.content_type=="application/x-tar")' )
157
+ INSTALL_FROM_RELEASE=$( echo "${GITHUB_RELEASE}" | jq -r '.name' | sed -e 's/\.tar\.gz$//' )
158
+ echo "You didn't provide a release. Defaulting to latest main branch build ${INSTALL_FROM_RELEASE}"
159
+ else
160
+ GITHUB_RELEASE=$( curl -L "https://api.github.com/repos/SAP/sailing-analytics/releases?per_page=100" 2>/dev/null | jq -r 'sort_by(.created_at) | reverse | map(select(.name=="'${INSTALL_FROM_RELEASE}'"))[0].assets[] | select(.content_type=="application/x-tar")' )
167 161
fi
168 162
if which mail; then
169 163
if [ -n "${BUILD_COMPLETE_NOTIFY}" ]; then
... ...
@@ -182,8 +176,8 @@ load_from_release_file ()
182 176
SCP_HOST=$( echo ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} | sed -e 's/^\([^:]*\):\?\([0-9]*\)\?$/\1/' )
183 177
scp ${SCP_PORT_OPTION} ${SCP_HOST}:/home/trac/releases/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME} .
184 178
else
185
- echo "Loading from release file https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME}"
186
- wget https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME}
179
+ echo "Loading from release file $( echo "${GITHUB_RELEASE}" | jq -r '.browser_download_url' )"
180
+ wget $( echo "${GITHUB_RELEASE}" | jq -r '.browser_download_url' )
187 181
fi
188 182
load_from_local_release_file
189 183
}
java/com.sap.sailing.domain.common/src/com/sap/sailing/domain/common/subscription/PremiumRole.java
... ...
@@ -13,7 +13,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
13 13
*/
14 14
public class PremiumRole extends RolePrototype {
15 15
private static final UUID ROLE_ID = UUID.fromString("7021e7a2-569a-11ec-bf63-0242ac130002");
16
- private static final long serialVersionUID = 8032532973066767581L;
17 16
private static final PremiumRole INSTANCE = new PremiumRole();
18 17
19 18
PremiumRole() {
java/com.sap.sailing.expeditionconnector/src/com/sap/sailing/expeditionconnector/UDPMirror.java
... ...
@@ -30,7 +30,6 @@ public class UDPMirror {
30 30
}
31 31
int listeningOnPort = Integer.valueOf(args[c++]);
32 32
byte[] buf = new byte[65536];
33
- @SuppressWarnings("resource") // Can't close resource due to the infinite loop. Will be closed, when the thread is terminated.
34 33
DatagramSocket udpSocket = new DatagramSocket(listeningOnPort);
35 34
DatagramPacket received = new DatagramPacket(buf, buf.length);
36 35
DatagramSocket[] sendingSockets = new DatagramSocket[(args.length - 1) / 2];
java/com.sap.sailing.landscape.test/src/com/sap/sailing/landscape/test/TestReleaseRepository.java
... ...
@@ -16,6 +16,6 @@ public class TestReleaseRepository {
16 16
17 17
@Test
18 18
public void testForAtLeastOneMasterRelease() {
19
- assertNotNull(SailingReleaseRepository.INSTANCE.getLatestMasterRelease());
19
+ assertNotNull(SailingReleaseRepository.INSTANCE.getLatestDefaultRelease());
20 20
}
21 21
}
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/LandscapeManagementPanel.java
... ...
@@ -948,9 +948,9 @@ public class LandscapeManagementPanel extends SimplePanel {
948 948
applicationReplicaSetToDefineLandingPageFor.getMaster(),
949 949
applicationReplicaSetToDefineLandingPageFor.getReplicas(),
950 950
applicationReplicaSetToDefineLandingPageFor.getVersion(),
951
+ applicationReplicaSetToDefineLandingPageFor.getReleaseNotesLink(),
951 952
applicationReplicaSetToDefineLandingPageFor.getHostname(),
952
- newDefaultRedirect,
953
- applicationReplicaSetToDefineLandingPageFor.getAutoScalingGroupAmiId()));
953
+ newDefaultRedirect, applicationReplicaSetToDefineLandingPageFor.getAutoScalingGroupAmiId()));
954 954
Notification.notify(stringMessages.successfullyUpdatedLandingPage(), NotificationType.SUCCESS);
955 955
}
956 956
});
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/client/LinkBuilder.java
... ...
@@ -127,10 +127,6 @@ public class LinkBuilder implements Builder<LinkBuilder, SafeHtml> {
127 127
return (port == 443 ? "https" : "http") + "://" + host + ":" + port + "/gwt/status";
128 128
}
129 129
130
- private String getReleaseNotesLink(final String version) {
131
- return "https://releases.sapsailing.com/" + version + "/release-notes.txt";
132
- }
133
-
134 130
/**
135 131
* Checks if an attribute is null and throws an exception if so.
136 132
*
... ...
@@ -193,7 +189,7 @@ public class LinkBuilder implements Builder<LinkBuilder, SafeHtml> {
193 189
case Version:
194 190
checkAttribute(replicaSet, "Replicaset");
195 191
final String version = replicaSet.getVersion();
196
- final String releaseNotesLink = getReleaseNotesLink(version);
192
+ final String releaseNotesLink = replicaSet.getReleaseNotesLink();
197 193
appendEc2Link(builder, releaseNotesLink, version);
198 194
break;
199 195
case MasterHost:
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/server/LandscapeManagementWriteServiceImpl.java
... ...
@@ -411,6 +411,7 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
411 411
private SailingApplicationReplicaSetDTO<String> convertToSailingApplicationReplicaSetDTO(
412 412
AwsApplicationReplicaSet<String, SailingAnalyticsMetrics, SailingAnalyticsProcess<String>> applicationServerReplicaSet,
413 413
Optional<String> optionalKeyName, byte[] privateKeyEncryptionPassphrase) throws Exception {
414
+ final Release release = applicationServerReplicaSet.getVersion(Landscape.WAIT_FOR_PROCESS_TIMEOUT, optionalKeyName, privateKeyEncryptionPassphrase);
414 415
return new SailingApplicationReplicaSetDTO<>(applicationServerReplicaSet.getName(),
415 416
convertToSailingAnalyticsProcessDTO(applicationServerReplicaSet.getMaster(), optionalKeyName, privateKeyEncryptionPassphrase),
416 417
Util.map(applicationServerReplicaSet.getReplicas(), r->{
... ...
@@ -420,9 +421,8 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
420 421
throw new RuntimeException(e);
421 422
}
422 423
}),
423
- applicationServerReplicaSet.getVersion(Landscape.WAIT_FOR_PROCESS_TIMEOUT, optionalKeyName, privateKeyEncryptionPassphrase).getName(),
424
- applicationServerReplicaSet.getHostname(), getLandscapeService().getDefaultRedirectPath(applicationServerReplicaSet.getDefaultRedirectRule()),
425
- applicationServerReplicaSet.getAutoScalingGroup() == null ? null :
424
+ release.getName(), release.getReleaseNotesURL().toString(), applicationServerReplicaSet.getHostname(),
425
+ getLandscapeService().getDefaultRedirectPath(applicationServerReplicaSet.getDefaultRedirectRule()), applicationServerReplicaSet.getAutoScalingGroup() == null ? null :
426 426
applicationServerReplicaSet.getAutoScalingGroup().getLaunchTemplateDefaultVersion() == null ? null :
427 427
applicationServerReplicaSet.getAutoScalingGroup().getLaunchTemplateDefaultVersion().launchTemplateData().imageId());
428 428
}
... ...
@@ -655,9 +655,9 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
655 655
}
656 656
}),
657 657
release.getName(),
658
+ release.getReleaseNotesURL().toString(),
658 659
getLandscapeService().getFullyQualifiedHostname(name, Optional.ofNullable(optionalDomainName)),
659
- getLandscapeService().getDefaultRedirectPath(result.getDefaultRedirectRule()),
660
- result.getAutoScalingGroup()==null?null:result.getAutoScalingGroup().getLaunchTemplateDefaultVersion().launchTemplateData().imageId());
660
+ getLandscapeService().getDefaultRedirectPath(result.getDefaultRedirectRule()), result.getAutoScalingGroup()==null?null:result.getAutoScalingGroup().getLaunchTemplateDefaultVersion().launchTemplateData().imageId());
661 661
}
662 662
663 663
@Override
... ...
@@ -732,10 +732,9 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
732 732
} catch (Exception e) {
733 733
throw new RuntimeException(e);
734 734
}
735
- }), release.getName(),
735
+ }), release.getName(), release.getReleaseNotesURL().toString(),
736 736
getLandscapeService().getFullyQualifiedHostname(replicaSetName, Optional.ofNullable(optionalDomainName)),
737
- getLandscapeService().getDefaultRedirectPath(result.getDefaultRedirectRule()),
738
- result.getAutoScalingGroup()==null?null:result.getAutoScalingGroup().getLaunchTemplateDefaultVersion().launchTemplateData().imageId());
737
+ getLandscapeService().getDefaultRedirectPath(result.getDefaultRedirectRule()), result.getAutoScalingGroup()==null?null:result.getAutoScalingGroup().getLaunchTemplateDefaultVersion().launchTemplateData().imageId());
739 738
}
740 739
741 740
@Override
... ...
@@ -859,9 +858,9 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
859 858
applicationReplicaSetToCreateLoadBalancerMappingFor.getMaster(),
860 859
applicationReplicaSetToCreateLoadBalancerMappingFor.getReplicas(),
861 860
applicationReplicaSetToCreateLoadBalancerMappingFor.getVersion(),
861
+ applicationReplicaSetToCreateLoadBalancerMappingFor.getReleaseNotesLink(),
862 862
applicationReplicaSetToCreateLoadBalancerMappingFor.getHostname(),
863
- RedirectDTO.toString(defaultRedirect.getPath(), defaultRedirect.getQuery()),
864
- applicationReplicaSetToCreateLoadBalancerMappingFor.getAutoScalingGroupAmiId());
863
+ RedirectDTO.toString(defaultRedirect.getPath(), defaultRedirect.getQuery()), applicationReplicaSetToCreateLoadBalancerMappingFor.getAutoScalingGroupAmiId());
865 864
}
866 865
867 866
@Override
... ...
@@ -908,8 +907,8 @@ public class LandscapeManagementWriteServiceImpl extends ResultCachingProxiedRem
908 907
throw new RuntimeException(e);
909 908
}
910 909
}),
911
- release.getName(), applicationReplicaSetToUpgrade.getHostname(),
912
- applicationReplicaSetToUpgrade.getDefaultRedirectPath(), applicationReplicaSetToUpgrade.getAutoScalingGroupAmiId());
910
+ release.getName(), release.getReleaseNotesURL().toString(),
911
+ applicationReplicaSetToUpgrade.getHostname(), applicationReplicaSetToUpgrade.getDefaultRedirectPath(), applicationReplicaSetToUpgrade.getAutoScalingGroupAmiId());
913 912
}
914 913
915 914
@Override
java/com.sap.sailing.landscape.ui/src/com/sap/sailing/landscape/ui/shared/SailingApplicationReplicaSetDTO.java
... ...
@@ -16,6 +16,7 @@ public class SailingApplicationReplicaSetDTO<ShardingKey> implements Named, IsSe
16 16
private SailingAnalyticsProcessDTO master;
17 17
private ArrayList<SailingAnalyticsProcessDTO> replicas;
18 18
private String version;
19
+ private String releaseNotesLink;
19 20
private String hostname;
20 21
private String defaultRedirectPath;
21 22
private String autoScalingGroupAmiId;
... ...
@@ -24,7 +25,8 @@ public class SailingApplicationReplicaSetDTO<ShardingKey> implements Named, IsSe
24 25
SailingApplicationReplicaSetDTO() {} // for GWT RPC serialization only
25 26
26 27
public SailingApplicationReplicaSetDTO(String replicaSetName, SailingAnalyticsProcessDTO master,
27
- Iterable<SailingAnalyticsProcessDTO> replicas, String version, String hostname, String defaultRedirectPath, String autoScalingGroupAmiId) {
28
+ Iterable<SailingAnalyticsProcessDTO> replicas, String version, String releaseNotesLink, String hostname,
29
+ String defaultRedirectPath, String autoScalingGroupAmiId) {
28 30
super();
29 31
this.master = master;
30 32
this.replicaSetName = replicaSetName;
... ...
@@ -59,6 +61,10 @@ public class SailingApplicationReplicaSetDTO<ShardingKey> implements Named, IsSe
59 61
return version;
60 62
}
61 63
64
+ public String getReleaseNotesLink() {
65
+ return releaseNotesLink;
66
+ }
67
+
62 68
/**
63 69
* @return a fully-qualified hostname which can, e.g., be used to look up the load balancer taking the requests for
64 70
* this application replica set.
java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/SailingReleaseRepository.java
... ...
@@ -1,8 +1,11 @@
1 1
package com.sap.sailing.landscape;
2 2
3 3
import com.sap.sse.landscape.ReleaseRepository;
4
-import com.sap.sse.landscape.impl.ReleaseRepositoryImpl;
4
+import com.sap.sse.landscape.impl.GithubReleasesRepository;
5 5
6 6
public interface SailingReleaseRepository extends ReleaseRepository {
7
- ReleaseRepository INSTANCE = new ReleaseRepositoryImpl("https://releases.sapsailing.com", /* master release name prefix */ "main");
7
+ ReleaseRepository INSTANCE = new GithubReleasesRepository(
8
+ "SAP", // owner
9
+ "sailing-analytics", // repo name
10
+ "main"); // main release name prefix
8 11
}
java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/LandscapeServiceImpl.java
... ...
@@ -802,7 +802,7 @@ public class LandscapeServiceImpl implements LandscapeService {
802 802
@Override
803 803
public Release getRelease(String releaseNameOrNullForLatestMaster) {
804 804
return releaseNameOrNullForLatestMaster==null
805
- ? SailingReleaseRepository.INSTANCE.getLatestMasterRelease()
805
+ ? SailingReleaseRepository.INSTANCE.getLatestDefaultRelease()
806 806
: SailingReleaseRepository.INSTANCE.getRelease(releaseNameOrNullForLatestMaster);
807 807
}
808 808
java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/impl/SailingAnalyticsProcessImpl.java
... ...
@@ -38,7 +38,6 @@ import com.sap.sse.landscape.aws.ApplicationProcessHost;
38 38
import com.sap.sse.landscape.aws.AwsLandscape;
39 39
import com.sap.sse.landscape.aws.MongoUriParser;
40 40
import com.sap.sse.landscape.aws.impl.AwsApplicationProcessImpl;
41
-import com.sap.sse.landscape.impl.ReleaseImpl;
42 41
import com.sap.sse.landscape.mongodb.Database;
43 42
import com.sap.sse.shared.util.Wait;
44 43
import com.sap.sse.util.HttpUrlConnectionHelper;
... ...
@@ -102,7 +101,7 @@ implements SailingAnalyticsProcess<ShardingKey> {
102 101
private boolean updateReleaseFromStatus(JSONObject status) {
103 102
final boolean success;
104 103
if (status.containsKey(STATUS_RELEASE_PROPERTY_NAME)) {
105
- release = new ReleaseImpl((String) status.get(STATUS_RELEASE_PROPERTY_NAME), SailingReleaseRepository.INSTANCE);
104
+ release = SailingReleaseRepository.INSTANCE.getRelease((String) status.get(STATUS_RELEASE_PROPERTY_NAME));
106 105
success = true;
107 106
} else {
108 107
success = false;
java/com.sap.sailing.landscape/src/com/sap/sailing/landscape/procedures/SailingAnalyticsApplicationConfiguration.java
... ...
@@ -33,7 +33,7 @@ extends AwsApplicationConfiguration<ShardingKey, SailingAnalyticsMetrics, Sailin
33 33
* <li>If no {@link #setTelnetPort(int) telnet port} is provided, the {@link #DEFAULT_TELNET_PORT} is used (14888).</li>
34 34
* <li>If no {@link #setExpeditionPort(int) expedition UDP port} is provided, the {@link #DEFAULT_EXPEDITION_PORT} is used (2010).</li>
35 35
* <li>If no {@link #setServerDirectory(String) server directory} is specified, it defaults to {@link ApplicationProcessHost#DEFAULT_SERVER_PATH}.</li>
36
- * <li>If no {@link #setRelease(Release) release} is specified, it defaults to {@link SailingReleaseRepository#getLatestMasterRelease()}.</li>
36
+ * <li>If no {@link #setRelease(Release) release} is specified, it defaults to {@link SailingReleaseRepository#getLatestDefaultRelease()}.</li>
37 37
* <li>The {@link DefaultProcessConfigurationVariables#ADDITIONAL_JAVA_ARGS} variable is extended by system properties that configure
38 38
* security, landscape data, and basic sailing master data to be shared across the {@link SharedLandscapeConstants#DEFAULT_DOMAIN_NAME} domain.</li>
39 39
* </ul>
... ...
@@ -144,7 +144,7 @@ extends AwsApplicationConfiguration<ShardingKey, SailingAnalyticsMetrics, Sailin
144 144
145 145
@Override
146 146
protected Optional<Release> getRelease() {
147
- return Optional.of(super.getRelease().orElse(SailingReleaseRepository.INSTANCE.getLatestMasterRelease()));
147
+ return Optional.of(super.getRelease().orElse(SailingReleaseRepository.INSTANCE.getLatestDefaultRelease()));
148 148
}
149 149
150 150
/**
java/com.sap.sailing.server/src/com/sap/sailing/server/security/EventManagerRole.java
... ...
@@ -13,7 +13,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes.ServerActions;
13 13
* server infrastructure configuration.
14 14
*/
15 15
public class EventManagerRole extends RolePrototype {
16
- private static final long serialVersionUID = 6775068846340911064L;
17 16
private static final EventManagerRole INSTANCE = new EventManagerRole();
18 17
19 18
EventManagerRole() {
java/com.sap.sailing.server/src/com/sap/sailing/server/security/SailingViewerRole.java
... ...
@@ -11,8 +11,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
11 11
* page and basic analytical frontends.
12 12
*/
13 13
public class SailingViewerRole extends RolePrototype {
14
- private static final long serialVersionUID = 3291793984984443193L;
15
-
16 14
private static final SailingViewerRole INSTANCE = new SailingViewerRole();
17 15
18 16
SailingViewerRole() {
java/com.sap.sailing.www/release_notes_admin.html
... ...
@@ -23,6 +23,16 @@
23 23
<div class="mainContent">
24 24
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
25 25
<div class="innerContent">
26
+ <h2 class="articleSubheadline">November 2025</h2>
27
+ <ul class="bulletList">
28
+ <li>The Landscape Management panel as well as the <tt>refreshInstance.sh</tt> shell script
29
+ now use GitHub releases instead of <tt>releases.sapsailing.com</tt> to list and install
30
+ latest or specific releases to an environment. In particular, <tt>refreshInstance.sh</tt>
31
+ considers the newest 100 releases; the Landscape Management panel can list, select,
32
+ and install <em>all</em> releases available. For the time being, and for simplicity,
33
+ we decided to not require a GitHub access token, which however will cause a rate limit
34
+ of 60 release requests per hour per IP address.</li>
35
+ </ul>
26 36
<h2 class="articleSubheadline">October 2025</h2>
27 37
<ul class="bulletList">
28 38
<li>When WindBots sent buffered data, e.g., from previous days, there was a possibility that
java/com.sap.sse.datamining.ui/src/main/resources/com/sap/sse/datamining/SSEDataMining.gwt.xml
... ...
@@ -12,6 +12,7 @@
12 12
<inherits name="com.sap.sse.datamining.DataMiningAnnotations" />
13 13
14 14
<source path="ui/client"/>
15
- <source path="ui/images"/>
15
+ <source path="ui/images"/>
16
+ <source path="ui/svg"/>
16 17
17 18
</module>
java/com.sap.sse.landscape.aws.test/src/com/sap/sse/landscape/aws/ConnectivityTest.java
... ...
@@ -267,7 +267,7 @@ public class ConnectivityTest<ProcessT extends AwsApplicationProcess<String, Sai
267 267
}),
268 268
Optional.of(Tags.with("Name", "MyHost").and("Hello", "World")),
269 269
"MONGODB_URI=\""+mongoEndpoint.getURI(Optional.of(new DatabaseImpl(mongoEndpoint, "winddbTest")))+"\"",
270
- "INSTALL_FROM_RELEASE="+SailingReleaseRepository.INSTANCE.getLatestMasterRelease().getName(),
270
+ "INSTALL_FROM_RELEASE="+SailingReleaseRepository.INSTANCE.getLatestDefaultRelease().getName(),
271 271
"SERVER_NAME=\""+serverName+"\"");
272 272
try {
273 273
assertNotNull(host);
java/com.sap.sse.landscape.test/src/com/sap/sse/landscape/impl/TestFolderBasedReleaseRepository.java
... ...
@@ -0,0 +1,33 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertFalse;
5
+import static org.junit.jupiter.api.Assertions.assertNotNull;
6
+
7
+import org.junit.jupiter.api.BeforeAll;
8
+import org.junit.jupiter.api.Test;
9
+
10
+import com.sap.sse.common.Util;
11
+import com.sap.sse.landscape.Release;
12
+
13
+public class TestFolderBasedReleaseRepository {
14
+ private static final String MAIN = "main";
15
+ private static FolderBasedReleaseRepositoryImpl repository;
16
+
17
+ @BeforeAll
18
+ public static void setUp() {
19
+ repository = new FolderBasedReleaseRepositoryImpl("https://releases.sapsailing.com", MAIN);
20
+ }
21
+
22
+ @Test
23
+ public void testAtLeastOneRelease() {
24
+ assertFalse(Util.isEmpty(repository));
25
+ }
26
+
27
+ @Test
28
+ public void testAtLeastOneMainRelease() {
29
+ final Release latestMasterRelease = repository.getLatestDefaultRelease();
30
+ assertNotNull(latestMasterRelease);
31
+ assertEquals(MAIN, latestMasterRelease.getBaseName());
32
+ }
33
+}
java/com.sap.sse.landscape.test/src/com/sap/sse/landscape/impl/TestGithubReleaseRepository.java
... ...
@@ -0,0 +1,89 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import static org.junit.jupiter.api.Assertions.assertEquals;
4
+import static org.junit.jupiter.api.Assertions.assertFalse;
5
+import static org.junit.jupiter.api.Assertions.assertNotNull;
6
+import static org.junit.jupiter.api.Assertions.assertNull;
7
+
8
+import java.util.HashMap;
9
+import java.util.Map;
10
+import java.util.concurrent.ExecutionException;
11
+import java.util.concurrent.Future;
12
+import java.util.concurrent.ScheduledExecutorService;
13
+
14
+import org.junit.jupiter.api.BeforeAll;
15
+import org.junit.jupiter.api.Disabled;
16
+import org.junit.jupiter.api.Test;
17
+
18
+import com.sap.sse.common.Util;
19
+import com.sap.sse.landscape.Release;
20
+import com.sap.sse.util.ThreadPoolUtil;
21
+
22
+public class TestGithubReleaseRepository {
23
+ private static final String DOCKER_25 = "docker-25";
24
+ private static final String MAIN = "main";
25
+ private static GithubReleasesRepository repository;
26
+
27
+ @BeforeAll
28
+ public static void setUp() {
29
+ repository = new GithubReleasesRepository("SAP", "sailing-analytics", MAIN);
30
+ }
31
+
32
+ @Test
33
+ public void testAtLeastOneRelease() {
34
+ assertFalse(Util.isEmpty(repository));
35
+ }
36
+
37
+ @Test
38
+ public void testAtLeastOneMainRelease() {
39
+ final Release latestMasterRelease = repository.getLatestDefaultRelease();
40
+ assertNotNull(latestMasterRelease);
41
+ assertEquals(MAIN, latestMasterRelease.getBaseName());
42
+ }
43
+
44
+ @Test
45
+ public void testAtLeastOneDocker25Release() {
46
+ final Release latestDocker25Release = repository.getLatestRelease(DOCKER_25);
47
+ assertNotNull(latestDocker25Release);
48
+ assertEquals(DOCKER_25, latestDocker25Release.getBaseName());
49
+ }
50
+
51
+ @Test
52
+ public void testLinkToNextPageFromFirst() {
53
+ assertEquals("https://api.github.com/repositories/790295432/releases?page=2",
54
+ repository.getNextPageURL("<https://api.github.com/repositories/790295432/releases?page=2>; rel=\"next\", <https://api.github.com/repositories/790295432/releases?page=27>; rel=\"last\""));
55
+ }
56
+
57
+ @Test
58
+ public void testLinkToNextPageFromMiddleWith100PerPage() {
59
+ assertEquals("https://api.github.com/repositories/790295432/releases?per_page=100&page=5",
60
+ repository.getNextPageURL("<https://api.github.com/repositories/790295432/releases?per_page=100&page=3>; rel=\"prev\", <https://api.github.com/repositories/790295432/releases?per_page=100&page=5>; rel=\"next\", <https://api.github.com/repositories/790295432/releases?per_page=100&page=8>; rel=\"last\", <https://api.github.com/repositories/790295432/releases?per_page=100&page=1>; rel=\"first\""));
61
+ }
62
+
63
+ @Test
64
+ public void testLinkToNextPageFromLaste() {
65
+ assertNull(
66
+ repository.getNextPageURL("<https://api.github.com/repositories/790295432/releases?per_page=100&page=7>; rel=\"prev\", <https://api.github.com/repositories/790295432/releases?per_page=100&page=1>; rel=\"first\""));
67
+ }
68
+
69
+ @Disabled("Goes against a harsh GitHub rate limit of 60 requests per hour, so enable only for one-time manual tests")
70
+ @Test
71
+ public void testOldDocker17ReleaseExists() {
72
+ assertFalse(Util.isEmpty(Util.filter(repository, release->release.getName().equals("docker-17-202404262046"))));
73
+ }
74
+
75
+ @Test
76
+ public void testConcurrentAccess() throws InterruptedException, ExecutionException {
77
+ final ScheduledExecutorService threadPool = ThreadPoolUtil.INSTANCE.createForegroundTaskThreadPoolExecutor(10, getClass().getName()+":testConcurrentAccess()");
78
+ final Map<String, Future<Release>> futures = new HashMap<>();
79
+ final String[] prefixes = new String[] { "main", "docker-25", "docker-24", "docker-21", "docker-17" };
80
+ for (final String prefix : prefixes) {
81
+ futures.put(prefix, threadPool.submit(()->repository.getLatestRelease(prefix)));
82
+ }
83
+ for (final String prefix : prefixes) {
84
+ assertNotNull(futures.get(prefix).get());
85
+ assertEquals(prefix, futures.get(prefix).get().getBaseName());
86
+ }
87
+ threadPool.shutdown();
88
+ }
89
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/Release.java
... ...
@@ -1,9 +1,13 @@
1 1
package com.sap.sse.landscape;
2 2
3
-import java.net.MalformedURLException;
4 3
import java.net.URL;
4
+import java.text.ParseException;
5
+import java.text.SimpleDateFormat;
5 6
import java.util.HashMap;
6 7
import java.util.Map;
8
+import java.util.TimeZone;
9
+import java.util.logging.Level;
10
+import java.util.logging.Logger;
7 11
8 12
import com.sap.sse.common.Named;
9 13
import com.sap.sse.common.TimePoint;
... ...
@@ -15,26 +19,30 @@ import com.sap.sse.common.TimePoint;
15 19
*
16 20
*/
17 21
public interface Release extends UserDataProvider, Named {
22
+ Logger logger = Logger.getLogger(Release.class.getName());
23
+
18 24
String RELEASE_NOTES_FILE_NAME = "release-notes.txt";
19 25
String ARCHIVE_EXTENSION = ".tar.gz";
20 26
21
- ReleaseRepository getRepository();
22
-
23
- String getBaseName();
24
-
25
- TimePoint getCreationDate();
26
-
27
- default String getFolderURL() {
28
- return getRepository().getRepositoryBase()+"/"+getName()+"/";
27
+ default String getBaseName() {
28
+ return getName().substring(0, getName().lastIndexOf("-"));
29 29
}
30
-
31
- default URL getReleaseNotesURL() throws MalformedURLException {
32
- return new URL(getFolderURL()+RELEASE_NOTES_FILE_NAME);
30
+
31
+ default TimePoint getCreationDate() {
32
+ final String dateSubstring = getName().substring(getName().lastIndexOf("-")+1);
33
+ try {
34
+ final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmm");
35
+ simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
36
+ return TimePoint.of(simpleDateFormat.parse(dateSubstring));
37
+ } catch (ParseException e) {
38
+ logger.log(Level.WARNING, "Error parsing release date "+dateSubstring+". Returning null instead.", e);
39
+ return null;
40
+ }
33 41
}
42
+
43
+ URL getReleaseNotesURL();
34 44
35
- default URL getDeployableArchiveURL() throws MalformedURLException {
36
- return new URL(getFolderURL()+getName()+ARCHIVE_EXTENSION);
37
- }
45
+ URL getDeployableArchiveURL();
38 46
39 47
@Override
40 48
default Map<ProcessConfigurationVariable, String> getUserData() {
java/com.sap.sse.landscape/src/com/sap/sse/landscape/ReleaseRepository.java
... ...
@@ -12,8 +12,8 @@ public interface ReleaseRepository extends Iterable<Release> {
12 12
* @return the latest build with name prefix {@link #MASTER_RELEASE_NAME_PREFIX} if such a release exists in this
13 13
* repository, or {@code null} otherwise
14 14
*/
15
- default Release getLatestMasterRelease() {
16
- return getLatestRelease(getMasterReleaseNamePrefix());
15
+ default Release getLatestDefaultRelease() {
16
+ return getLatestRelease(getDefaultReleaseNamePrefix());
17 17
}
18 18
19 19
/**
... ...
@@ -26,7 +26,5 @@ public interface ReleaseRepository extends Iterable<Release> {
26 26
*/
27 27
Release getRelease(String releaseName);
28 28
29
- String getRepositoryBase();
30
-
31
- String getMasterReleaseNamePrefix();
29
+ String getDefaultReleaseNamePrefix();
32 30
}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/application/impl/ApplicationProcessImpl.java
... ...
@@ -36,7 +36,6 @@ import com.sap.sse.landscape.RotatingFileBasedLog;
36 36
import com.sap.sse.landscape.application.ApplicationProcess;
37 37
import com.sap.sse.landscape.application.ApplicationProcessMetrics;
38 38
import com.sap.sse.landscape.impl.ProcessImpl;
39
-import com.sap.sse.landscape.impl.ReleaseImpl;
40 39
import com.sap.sse.landscape.ssh.SshCommandChannel;
41 40
import com.sap.sse.shared.util.Wait;
42 41
import com.sap.sse.util.HttpUrlConnectionHelper;
... ...
@@ -106,7 +105,7 @@ implements ApplicationProcess<ShardingKey, MetricsT, ProcessT> {
106 105
} else {
107 106
final Matcher matcher = pattern.matcher(versionTxt);
108 107
if (matcher.find()) {
109
- result = new ReleaseImpl(matcher.group(1), releaseRepository);
108
+ result = releaseRepository.getRelease(matcher.group(1));
110 109
} else {
111 110
result = null;
112 111
}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/AbstractRelease.java
... ...
@@ -0,0 +1,12 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import com.sap.sse.common.impl.NamedImpl;
4
+import com.sap.sse.landscape.Release;
5
+
6
+public abstract class AbstractRelease extends NamedImpl implements Release {
7
+ private static final long serialVersionUID = 4872094283926485605L;
8
+
9
+ public AbstractRelease(String name) {
10
+ super(name);
11
+ }
12
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/AbstractReleaseRepository.java
... ...
@@ -0,0 +1,36 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import com.sap.sse.common.Util;
4
+import com.sap.sse.landscape.Release;
5
+import com.sap.sse.landscape.ReleaseRepository;
6
+
7
+public abstract class AbstractReleaseRepository implements ReleaseRepository {
8
+ private final String defaultReleaseNamePrefix;
9
+
10
+ public AbstractReleaseRepository(String defaultReleaseNamePrefix) {
11
+ super();
12
+ this.defaultReleaseNamePrefix = defaultReleaseNamePrefix;
13
+ }
14
+
15
+ @Override
16
+ public String getDefaultReleaseNamePrefix() {
17
+ return defaultReleaseNamePrefix;
18
+ }
19
+
20
+ @Override
21
+ public Release getLatestRelease(String releaseNamePrefix) {
22
+ Release result = null;
23
+ for (final Release release : this) { // invokes the iterator() method
24
+ if (release.getBaseName().equals(releaseNamePrefix) &&
25
+ (result == null || release.getCreationDate().after(result.getCreationDate()))) {
26
+ result = release;
27
+ }
28
+ }
29
+ return result;
30
+ }
31
+
32
+ @Override
33
+ public Release getRelease(String releaseName) {
34
+ return Util.first(Util.filter(this, r->r.getName().equals(releaseName)));
35
+ }
36
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/FolderBasedReleaseImpl.java
... ...
@@ -0,0 +1,44 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import java.net.MalformedURLException;
4
+import java.net.URL;
5
+
6
+import com.sap.sse.landscape.Release;
7
+
8
+/**
9
+ * Collaborates with {@link FolderBasedReleaseRepositoryImpl}.
10
+ *
11
+ * @author Axel Uhl (d043530)
12
+ *
13
+ */
14
+public class FolderBasedReleaseImpl extends AbstractRelease implements Release {
15
+ private static final long serialVersionUID = -225240683033821028L;
16
+ private final String repositoryBase;
17
+
18
+ public FolderBasedReleaseImpl(String name, String repositoryBase) {
19
+ super(name);
20
+ this.repositoryBase = repositoryBase;
21
+ }
22
+
23
+ @Override
24
+ public URL getReleaseNotesURL() {
25
+ try {
26
+ return new URL(getDownloadFolderURL()+RELEASE_NOTES_FILE_NAME);
27
+ } catch (MalformedURLException e) {
28
+ throw new RuntimeException(e);
29
+ }
30
+ }
31
+
32
+ @Override
33
+ public URL getDeployableArchiveURL() {
34
+ try {
35
+ return new URL(getDownloadFolderURL()+getName()+ARCHIVE_EXTENSION);
36
+ } catch (MalformedURLException e) {
37
+ throw new RuntimeException(e);
38
+ }
39
+ }
40
+
41
+ private String getDownloadFolderURL() {
42
+ return repositoryBase+"/"+getName()+"/";
43
+ }
44
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/FolderBasedReleaseRepositoryImpl.java
... ...
@@ -0,0 +1,73 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import java.io.ByteArrayOutputStream;
4
+import java.io.IOException;
5
+import java.io.InputStream;
6
+import java.net.URL;
7
+import java.net.URLConnection;
8
+import java.util.Collections;
9
+import java.util.Iterator;
10
+import java.util.LinkedList;
11
+import java.util.List;
12
+import java.util.logging.Level;
13
+import java.util.logging.Logger;
14
+import java.util.regex.Matcher;
15
+import java.util.regex.Pattern;
16
+
17
+import com.sap.sse.landscape.Release;
18
+import com.sap.sse.util.HttpUrlConnectionHelper;
19
+
20
+/**
21
+ * Assumes a simple folder exposed by a web server, such as Apache httpd, where the "repository base" references the
22
+ * folder which shows an "index" of the sub-folders in it, so that we can explore it. Each sub-folder is expected to be
23
+ * named after the corresponding release and must contain the release-notes.txt file (see
24
+ * {@link Release#RELEASE_NOTES_FILE_NAME}) as well as the .tar.gz file (see {@link Release#ARCHIVE_EXTENSION}) that
25
+ * represents the actual release file.
26
+ *
27
+ * @author Axel Uhl (d043530)
28
+ *
29
+ */
30
+public class FolderBasedReleaseRepositoryImpl extends AbstractReleaseRepository {
31
+ private static final Logger logger = Logger.getLogger(FolderBasedReleaseRepositoryImpl.class.getName());
32
+ private final String repositoryBase;
33
+
34
+ public FolderBasedReleaseRepositoryImpl(String repositoryBase, String defaultReleaseNamePrefix) {
35
+ super(defaultReleaseNamePrefix);
36
+ this.repositoryBase = repositoryBase;
37
+ }
38
+
39
+ private String getRepositoryBase() {
40
+ return repositoryBase;
41
+ }
42
+
43
+ @Override
44
+ public Iterator<Release> iterator() {
45
+ return getAvailableReleases().iterator();
46
+ }
47
+
48
+ private Iterable<Release> getAvailableReleases() {
49
+ final List<Release> result = new LinkedList<>();
50
+ try {
51
+ final URLConnection connection = HttpUrlConnectionHelper.redirectConnection(new URL(getRepositoryBase()));
52
+ final InputStream index = (InputStream) connection.getContent();
53
+ int read = 0;
54
+ final ByteArrayOutputStream bos = new ByteArrayOutputStream();
55
+ while ((read=index.read()) != -1) {
56
+ bos.write(read);
57
+ }
58
+ index.close();
59
+ final String contents = bos.toString();
60
+ final Pattern pattern = Pattern.compile("<a href=\"(([^/]*)-([0-9]*))/\">([^/]*)-([0-9]*)/</a>");
61
+ final Matcher m = pattern.matcher(contents);
62
+ int lastMatch = 0;
63
+ while (m.find(lastMatch)) {
64
+ result.add(new FolderBasedReleaseImpl(m.group(1), getRepositoryBase()));
65
+ lastMatch = m.end();
66
+ }
67
+ } catch (IOException e) {
68
+ logger.log(Level.SEVERE, "Error loading releases from repository at "+getRepositoryBase()+". Continuing empty.", e);
69
+ return Collections.emptyList();
70
+ }
71
+ return result;
72
+ }
73
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/GithubRelease.java
... ...
@@ -0,0 +1,37 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import java.net.MalformedURLException;
4
+import java.net.URL;
5
+
6
+import com.sap.sse.landscape.Release;
7
+
8
+public class GithubRelease extends AbstractRelease implements Release {
9
+ private static final long serialVersionUID = -8587383557709591724L;
10
+ private final String downloadURL;
11
+ private final String releaseNotesURL;
12
+
13
+ public GithubRelease(String name, String downloadURL, String releaseNotesURL) {
14
+ super(name);
15
+ this.downloadURL = downloadURL;
16
+ this.releaseNotesURL = releaseNotesURL;
17
+ }
18
+
19
+ @Override
20
+ public URL getReleaseNotesURL() {
21
+ try {
22
+ return new URL(releaseNotesURL);
23
+ } catch (MalformedURLException e) {
24
+ throw new RuntimeException(e);
25
+ }
26
+ }
27
+
28
+ @Override
29
+ public URL getDeployableArchiveURL() {
30
+ try {
31
+ return new URL(downloadURL);
32
+ } catch (MalformedURLException e) {
33
+ throw new RuntimeException(e);
34
+ }
35
+ }
36
+
37
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/GithubReleasesRepository.java
... ...
@@ -0,0 +1,377 @@
1
+package com.sap.sse.landscape.impl;
2
+
3
+import java.io.IOException;
4
+import java.io.InputStream;
5
+import java.io.InputStreamReader;
6
+import java.net.MalformedURLException;
7
+import java.net.URL;
8
+import java.net.URLConnection;
9
+import java.text.SimpleDateFormat;
10
+import java.util.Iterator;
11
+import java.util.LinkedList;
12
+import java.util.List;
13
+import java.util.NoSuchElementException;
14
+import java.util.concurrent.ConcurrentNavigableMap;
15
+import java.util.concurrent.ConcurrentSkipListMap;
16
+import java.util.logging.Logger;
17
+import java.util.regex.Matcher;
18
+import java.util.regex.Pattern;
19
+
20
+import org.json.simple.JSONArray;
21
+import org.json.simple.JSONObject;
22
+import org.json.simple.parser.JSONParser;
23
+import org.json.simple.parser.ParseException;
24
+
25
+import com.sap.sse.common.Duration;
26
+import com.sap.sse.common.TimePoint;
27
+import com.sap.sse.common.Util.Pair;
28
+import com.sap.sse.landscape.Release;
29
+import com.sap.sse.landscape.ReleaseRepository;
30
+import com.sap.sse.util.HttpUrlConnectionHelper;
31
+
32
+/**
33
+ * Can enumerate the {@link Release}s published by a GitHub repository and search releases whose name starts with a
34
+ * specific prefix. This assumes a public GitHub repository where releases can be freely downloaded from
35
+ * <code>https://github.com/{owner}/{repo}/releases/download/{release-name}</code>. The {@code api.github.com}'s
36
+ * {@code /releases} end point delivers the releases in descending chronological order, so newest releases first. With
37
+ * this, we can cache old results and try to get along with the harsh rate limit of only 60 requests per hour when used
38
+ * without authentication.
39
+ * <p>
40
+ *
41
+ * Due to the harsh rate limits we restrict loading even of the first page to once every two minutes; multiple requests
42
+ * within this duration will be answered from the cache. With this, a single instance of this class will typically
43
+ * request <em>all</em> releases only once, cache all these releases, and then look for newer releases at most every two
44
+ * minutes, thereby staying well within limits.
45
+ * <p>
46
+ *
47
+ * Enumerating the releases works through the inner class {@link ReleaseIterator}. If the last loading request for the
48
+ * first page happened more than those two minutes ago, another such request will be made and the new, yet uncached
49
+ * releases obtained from it will be added to the cache. Then, enumeration starts on the cache, delivering the newest
50
+ * release first. When the oldest cached element has been delivered through the iterator, the next action depends on
51
+ * whether or not the cache {@link #cacheContainsOldestRelease contains the oldest release} already. If so, no older
52
+ * release can exist, and iteration ends. Otherwise, more requests for further paginated release documents are sent
53
+ * until no more pages are found or releases older than the so far oldest release from the cache are found and added to
54
+ * the cache. Iteration then continues on the cache again.
55
+ * <p>
56
+ *
57
+ * The class is thread-safe in that it allows multiple threads to obtain iterators on a single instance of this class.
58
+ * The loading and caching of releases pages from GitHub, the invocation of the {@link #iterator()} method and the
59
+ * {@link ReleaseIterator#hasNext()} and {@link ReleaseIterator#next()} methods all obtain this object's monitor
60
+ * ({@code synchronized}). This may cause one iterator having to wait for another iterator's implicit loading actions.
61
+ * <p>
62
+ *
63
+ * @author Axel Uhl (d043530)
64
+ */
65
+public class GithubReleasesRepository extends AbstractReleaseRepository implements ReleaseRepository {
66
+ private final static Logger logger = Logger.getLogger(GithubReleasesRepository.class.getName());
67
+ private static final SimpleDateFormat isoDateTimeFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssX");
68
+ private final static String GITHUB_API_BASE_URL = "https://api.github.com";
69
+ private final static String GITHUB_BASE_URL = "https://github.com";
70
+ private final static int NUMBER_OF_RELEASES_PER_PAGE = 100; // default would be 30; maximum is 100
71
+ private final String owner;
72
+ private final String repositoryName;
73
+
74
+ /**
75
+ * The cache of releases as loaded from the GitHub web site. The cache is filled when iterating using a
76
+ * {@link ReleaseIterator}, by loading paginated release records, converting them to {@link GithubRelease} objects
77
+ * and storing them in this cache.
78
+ * <p>
79
+ *
80
+ * The cache does not guarantee to contain the newest releases, nor does it guarantee to go back all the way to the
81
+ * oldest release. Its contents are contiguous in the sense of how the releases are returned by the GitHub API in
82
+ * descending order of publication, from new to old. In other words, if there is a release cached that was published
83
+ * at time point {@code t1} and another at a later time point {@code t2}, then the cache is guaranteed to contain
84
+ * all releases published in the time range {@code [t1:t2]} (inclusive).
85
+ * <p>
86
+ *
87
+ * Should a {@link ReleaseIterator} have enumerated all releases back to the oldest one, the
88
+ * {@link #cacheContainsOldestRelease} flag will be set to {@code true} which means that when an iteration has
89
+ * reached the oldest release in the cache, iteration is complete, and no further page loading is necessary
90
+ * to complete the iteration.
91
+ */
92
+ private final ConcurrentNavigableMap<TimePoint, Release> releasesByPublishingTimePoint;
93
+
94
+ private boolean cacheContainsOldestRelease;
95
+
96
+ /**
97
+ * If {@link #cacheContainsOldestRelease} is {@code false}, meaning the cache hasn't seen all releases back to the oldest
98
+ * one, this field holds the URL of the next page to load that will obtain older releases. Note that if newer releases
99
+ * have been published since loading the so far oldest cached releases, one or more newer releases may have slipped
100
+ * into that next page. But page loading of older releases will add only releases effectively older than the oldest
101
+ * release in the cache so far.<p>
102
+ *
103
+ * It starts out with {@link #getReleasesURL()}.<p>
104
+ *
105
+ * Will be {@code null} if {@link #cacheContainsOldestRelease} is {@code true}.
106
+ */
107
+ private String nextPageURLForOlderReleases;
108
+
109
+ private TimePoint lastFetchOfNewestReleases;
110
+
111
+ private final static Duration RELOAD_NEWEST_RELEASES_AFTER_DURATION = Duration.ONE_MINUTE.times(2);
112
+
113
+ /**
114
+ * If {@link GithubReleasesRepository#lastFetchOfNewestReleases} is {@code null} or older than the
115
+ * {@link GithubReleasesRepository#RELOAD_NEWEST_RELEASES_AFTER_DURATION}, the page with newest releases is actually
116
+ * loaded. Otherwise, we assume that within the
117
+ * {@link GithubReleasesRepository#RELOAD_NEWEST_RELEASES_AFTER_DURATION} interval changes are sufficiently
118
+ * unlikely, so we will set the {@link #cachedReleasesIterator} to directly serve the current contents of the cache.
119
+ * <p>
120
+ *
121
+ * Always fetches the first page from the {@code /releases} end point and starts constructing and
122
+ * {@link GithubReleasesRepository#releasesByPublishingTimePoint caching} releases, until a publishing time point
123
+ * overlap with {@link GithubReleasesRepository#releasesByPublishingTimePoint} is found. Iteration then starts from
124
+ * that cache. If the iterator has returned all elements from the cache going backwards in publishing history, and
125
+ * {@link GithubReleasesRepository#cacheContainsOldestRelease} is {@code false}, indicating that the cache does not
126
+ * go back to the "beginning of time," and still more elements are requested from this iterator, paginated release
127
+ * documents need to get loaded again until we find even older releases than the oldest one from the cache. The
128
+ * loaded elements will be added to the cache, and a new internal iterator is launched on the cache starting from
129
+ * the then loaded element.
130
+ * <p>
131
+ *
132
+ * All releases found by loading a page are added to the
133
+ * {@link GithubReleasesRepository#releasesByPublishingTimePoint} cache. If the page with the oldest sequence of
134
+ * releases has been loaded (there is no next page then anymore), the
135
+ * {@link GithubReleasesRepository#cacheContainsOldestRelease} flag is set to {@code true}.
136
+ *
137
+ * @author Axel Uhl (d043530)
138
+ *
139
+ */
140
+ private class ReleaseIterator implements Iterator<Release> {
141
+ /**
142
+ * Used to enumerate the cached elements; will get assigned a new iterator after having reached the "old" end of
143
+ * the cache and {@link GithubReleasesRepository#loadPageWithNextOlderReleases() loading more older releases}.
144
+ */
145
+ private Iterator<Release> cachedReleasesIterator;
146
+
147
+ private ReleaseIterator() throws MalformedURLException, IOException, ParseException {
148
+ synchronized (GithubReleasesRepository.this) {
149
+ final TimePoint now = TimePoint.now();
150
+ if (lastFetchOfNewestReleases != null && lastFetchOfNewestReleases.until(now)
151
+ .compareTo(RELOAD_NEWEST_RELEASES_AFTER_DURATION) < 0) {
152
+ logger.fine(()->"No need to fetch page with newest releases; did that at "+lastFetchOfNewestReleases);
153
+ } else {
154
+ logger.fine(()->"Need to fetch page with newest releases because last request was at "+
155
+ (lastFetchOfNewestReleases==null?"<never>":lastFetchOfNewestReleases));
156
+ lastFetchOfNewestReleases = now;
157
+ fillCacheWithNewestReleases();
158
+ }
159
+ cachedReleasesIterator = releasesByPublishingTimePoint.descendingMap().values().iterator();
160
+ }
161
+ }
162
+
163
+ @Override
164
+ public boolean hasNext() {
165
+ synchronized (GithubReleasesRepository.this) {
166
+ // - we're delivering from the cache and the cache has more elements, or
167
+ // - we've reached the end of the cache but the cache doesn't contain the oldest release so we can load more pages
168
+ return cachedReleasesIterator.hasNext() || !cacheContainsOldestRelease;
169
+ }
170
+ }
171
+
172
+ @Override
173
+ public Release next() {
174
+ synchronized (GithubReleasesRepository.this) {
175
+ final Release result;
176
+ if (cachedReleasesIterator.hasNext()) {
177
+ result = cachedReleasesIterator.next();
178
+ } else {
179
+ if (cacheContainsOldestRelease) {
180
+ throw new NoSuchElementException();
181
+ } else {
182
+ final TimePoint oldestReleaseSoFar = releasesByPublishingTimePoint.firstKey();
183
+ try {
184
+ loadPageWithNextOlderReleases();
185
+ } catch (IOException | ParseException e) {
186
+ throw new RuntimeException(e);
187
+ }
188
+ cachedReleasesIterator = releasesByPublishingTimePoint.headMap(oldestReleaseSoFar).descendingMap().values().iterator();
189
+ if (!cachedReleasesIterator.hasNext()) {
190
+ throw new NoSuchElementException();
191
+ } else {
192
+ result = cachedReleasesIterator.next();
193
+ }
194
+ }
195
+ }
196
+ return result;
197
+ }
198
+ }
199
+ }
200
+
201
+ public GithubReleasesRepository(String owner, String repositoryName, String defaultReleaseNamePrefix) {
202
+ super(defaultReleaseNamePrefix);
203
+ this.owner = owner;
204
+ this.repositoryName = repositoryName;
205
+ this.releasesByPublishingTimePoint = new ConcurrentSkipListMap<>();
206
+ this.cacheContainsOldestRelease = false;
207
+ this.nextPageURLForOlderReleases = getReleasesURL();
208
+ this.lastFetchOfNewestReleases = null;
209
+ }
210
+
211
+ /**
212
+ * Loads a page of releases from {@code pageURL} and returns the link to the next page, or {@code null} if this was
213
+ * the last page available (therefore containing the oldest releases). The releases loaded are added to the cache if
214
+ * they are outside of the contiguous time range between oldest and newest publishing date as it was found in the
215
+ * cache when this method is invoked.
216
+ * <p>
217
+ *
218
+ * The method makes no changes to the cache or any other state of this instance.
219
+ *
220
+ * @return the link to the next page in the returned pair's {@link Pair#getA() A component}, and the sequence of
221
+ * releases loaded from that page with their publishing time points, ordered from newest to oldest.
222
+ */
223
+ private synchronized Pair<String, Iterable<Pair<TimePoint, GithubRelease>>> getReleasesFromPage(String pageURL) throws IOException, ParseException {
224
+ logger.info("Requesting releases page "+pageURL);
225
+ final URLConnection connection = HttpUrlConnectionHelper.redirectConnection(new URL(pageURL));
226
+ final InputStream index = (InputStream) connection.getContent();
227
+ final String xRatelimitRemaining = connection.getHeaderField("x-ratelimit-remaining");
228
+ logger.fine(()->""+xRatelimitRemaining+" requests left in this hour");
229
+ if (xRatelimitRemaining != null && Integer.valueOf(xRatelimitRemaining) <= 0) {
230
+ throw new RuntimeException("You hit the rate limit of "+connection.getHeaderField("x-ratelimit-limit"));
231
+ }
232
+ final String linkHeader = connection.getHeaderField("link");
233
+ final String nextPageURL = getNextPageURL(linkHeader);
234
+ logger.fine(()->nextPageURL==null?"This was the last page":("Next page will be "+nextPageURL));
235
+ final List<Pair<TimePoint, GithubRelease>> publishingTimePointsAndReleases = new LinkedList<>();
236
+ final JSONArray releasesJson = (JSONArray) new JSONParser().parse(new InputStreamReader(index));
237
+ for (final Object releaseObject : releasesJson) {
238
+ publishingTimePointsAndReleases.add(getPublishedAtAndReleaseFromJson((JSONObject) releaseObject));
239
+ }
240
+ return new Pair<>(nextPageURL, publishingTimePointsAndReleases);
241
+ }
242
+
243
+ /**
244
+ * Loads at least one page of releases, starting with {@link #getReleasesURL()}. The method ensures that a time-wise
245
+ * overlap with any pre-existing cache entries is established so that the cache entries are a contiguous prefix of
246
+ * the list of all releases that exist in the repository.
247
+ * <p>
248
+ *
249
+ * In particular, if the cache is empty when this method is called, only the first page needs loading.
250
+ * <p>
251
+ *
252
+ * If the cache already contained one or more releases when this method is called, page loading continues
253
+ * with the next page until a page contains a release published not after the newest release in the cache
254
+ * at the time when this method was called.
255
+ */
256
+ private synchronized void fillCacheWithNewestReleases() throws IOException, ParseException {
257
+ final boolean cacheWasEmpty = releasesByPublishingTimePoint.isEmpty();
258
+ final TimePoint publishingTimePointOfLatestReleaseSoFar = cacheWasEmpty ? null : releasesByPublishingTimePoint.lastKey();
259
+ String nextPageURL = getReleasesURL();
260
+ boolean overlap = false;
261
+ do {
262
+ final Pair<String, Iterable<Pair<TimePoint, GithubRelease>>> pageResults = getReleasesFromPage(nextPageURL);
263
+ for (final Pair<TimePoint, GithubRelease> publishingTimePointAndRelease : pageResults.getB()) {
264
+ overlap = !cacheWasEmpty && !publishingTimePointAndRelease.getA().after(publishingTimePointOfLatestReleaseSoFar);
265
+ if (cacheWasEmpty || publishingTimePointAndRelease.getA().after(publishingTimePointOfLatestReleaseSoFar)) {
266
+ releasesByPublishingTimePoint.put(publishingTimePointAndRelease.getA(), publishingTimePointAndRelease.getB());
267
+ }
268
+ }
269
+ nextPageURL = pageResults.getA();
270
+ if (cacheWasEmpty) {
271
+ rememberNextPageForOlderReleases(nextPageURL);
272
+ }
273
+ } while (!cacheWasEmpty && !overlap);
274
+ }
275
+
276
+ private void rememberNextPageForOlderReleases(String nextPageURL) {
277
+ nextPageURLForOlderReleases = nextPageURL;
278
+ if (nextPageURL == null) {
279
+ cacheContainsOldestRelease = true;
280
+ }
281
+ }
282
+
283
+ /**
284
+ * Loads the page referenced by {@link #nextPageURLForOlderReleases}, adjusting that very field to point to either
285
+ * the next older page or {@code null}, and adjusting {@link #cacheContainsOldestRelease} accordingly. Releases
286
+ * older than the so far oldest release are added to the cache. If {@link #cacheContainsOldestRelease} is
287
+ * {@code false}, the process continues until at least one older release has been added to the cache.<p>
288
+ *
289
+ * Precondition: {@code !}{@link #cacheContainsOldestRelease} {@code && }{@link #nextPageURLForOlderReleases}{@code != null}
290
+ */
291
+ private synchronized void loadPageWithNextOlderReleases() throws IOException, ParseException {
292
+ assert !cacheContainsOldestRelease;
293
+ final TimePoint publishingTimePointOfOldestReleaseInCacheSoFar = releasesByPublishingTimePoint.firstKey();
294
+ boolean addedAtLeastOneReleaseToCache = false;
295
+ do {
296
+ final Pair<String, Iterable<Pair<TimePoint, GithubRelease>>> pageResults = getReleasesFromPage(nextPageURLForOlderReleases);
297
+ for (final Pair<TimePoint, GithubRelease> publishedAtAndRelease : pageResults.getB()) {
298
+ if (publishedAtAndRelease.getA().before(publishingTimePointOfOldestReleaseInCacheSoFar)) {
299
+ addedAtLeastOneReleaseToCache = true;
300
+ releasesByPublishingTimePoint.put(publishedAtAndRelease.getA(), publishedAtAndRelease.getB());
301
+ }
302
+ }
303
+ rememberNextPageForOlderReleases(pageResults.getA());
304
+ } while (!addedAtLeastOneReleaseToCache && !cacheContainsOldestRelease);
305
+ }
306
+
307
+ @Override
308
+ public Release getLatestRelease(String releaseNamePrefix) {
309
+ Release result = null;
310
+ for (final Release release : this) { // invokes the iterator() method
311
+ if (release.getBaseName().equals(releaseNamePrefix)) {
312
+ result = release;
313
+ break; // here we assume that releases are enumerated from newest to oldest
314
+ }
315
+ }
316
+ return result;
317
+ }
318
+
319
+ private String getRepositoryPath() {
320
+ return owner+"/"+repositoryName;
321
+ }
322
+
323
+ private String getReleasesURL() {
324
+ return GITHUB_API_BASE_URL+"/repos/"+getRepositoryPath()+"/releases?per_page="+NUMBER_OF_RELEASES_PER_PAGE;
325
+ }
326
+
327
+ @Override
328
+ public Release getRelease(String releaseName) {
329
+ return new GithubRelease(releaseName, GITHUB_BASE_URL+"/"+getRepositoryPath()+"/releases/download/"+releaseName+"/"+releaseName+Release.ARCHIVE_EXTENSION,
330
+ GITHUB_BASE_URL+"/"+getRepositoryPath()+"/releases/download/"+releaseName+"/"+Release.RELEASE_NOTES_FILE_NAME);
331
+ }
332
+
333
+ @Override
334
+ public Iterator<Release> iterator() {
335
+ try {
336
+ return new ReleaseIterator();
337
+ } catch (Exception e) {
338
+ throw new RuntimeException(e);
339
+ }
340
+ }
341
+
342
+ private Pair<TimePoint, GithubRelease> getPublishedAtAndReleaseFromJson(JSONObject releaseJson) {
343
+ final String name = releaseJson.get("name").toString();
344
+ final String publishedAtISO = releaseJson.get("published_at").toString();
345
+ TimePoint publishedAt;
346
+ try {
347
+ publishedAt = TimePoint.of(isoDateTimeFormat.parse(publishedAtISO));
348
+ } catch (java.text.ParseException e) {
349
+ logger.warning("Couldn't read published_at time stamp for release "+name+": "+publishedAtISO);
350
+ throw new RuntimeException(e);
351
+ }
352
+ String archiveDownloadURL = null;
353
+ String releaseNotesURL = null;
354
+ for (final Object archiveAsset : (JSONArray) releaseJson.get("assets")) {
355
+ final JSONObject archiveAssetJson = (JSONObject) archiveAsset;
356
+ if (archiveAssetJson.get("content_type").equals("application/x-tar")) {
357
+ archiveDownloadURL = archiveAssetJson.get("browser_download_url").toString();
358
+ } else if (archiveAssetJson.get("name").equals(Release.RELEASE_NOTES_FILE_NAME)) {
359
+ releaseNotesURL = archiveAssetJson.get("browser_download_url").toString();
360
+ }
361
+ }
362
+ final GithubRelease release = new GithubRelease(name, archiveDownloadURL, releaseNotesURL);
363
+ return new Pair<>(publishedAt, release);
364
+ }
365
+
366
+ private static final Pattern nextPagePattern = Pattern.compile(".*<([^<]*)>; rel=\"next\".*");
367
+ String getNextPageURL(String linkHeader) {
368
+ final String result;
369
+ final Matcher m = nextPagePattern.matcher(linkHeader);
370
+ if (m.matches()) {
371
+ result = m.group(1);
372
+ } else {
373
+ result = null;
374
+ }
375
+ return result;
376
+ }
377
+}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/ReleaseImpl.java
... ...
@@ -1,47 +0,0 @@
1
-package com.sap.sse.landscape.impl;
2
-
3
-import java.text.ParseException;
4
-import java.text.SimpleDateFormat;
5
-import java.util.TimeZone;
6
-import java.util.logging.Level;
7
-import java.util.logging.Logger;
8
-
9
-import com.sap.sse.common.TimePoint;
10
-import com.sap.sse.common.impl.NamedImpl;
11
-import com.sap.sse.landscape.Release;
12
-import com.sap.sse.landscape.ReleaseRepository;
13
-
14
-public class ReleaseImpl extends NamedImpl implements Release {
15
- private static final Logger logger = Logger.getLogger(ReleaseImpl.class.getName());
16
- private static final long serialVersionUID = -225240683033821028L;
17
-
18
- private final ReleaseRepository repository;
19
-
20
- public ReleaseImpl(String name, ReleaseRepository repository) {
21
- super(name);
22
- this.repository = repository;
23
- }
24
-
25
- @Override
26
- public ReleaseRepository getRepository() {
27
- return repository;
28
- }
29
-
30
- @Override
31
- public String getBaseName() {
32
- return getName().substring(0, getName().lastIndexOf("-"));
33
- }
34
-
35
- @Override
36
- public TimePoint getCreationDate() {
37
- final String dateSubstring = getName().substring(getName().lastIndexOf("-")+1);
38
- try {
39
- final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmm");
40
- simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
41
- return TimePoint.of(simpleDateFormat.parse(dateSubstring));
42
- } catch (ParseException e) {
43
- logger.log(Level.WARNING, "Error parsing release date "+dateSubstring+". Returning null instead.", e);
44
- return null;
45
- }
46
- }
47
-}
java/com.sap.sse.landscape/src/com/sap/sse/landscape/impl/ReleaseRepositoryImpl.java
... ...
@@ -1,92 +0,0 @@
1
-package com.sap.sse.landscape.impl;
2
-
3
-import java.io.ByteArrayOutputStream;
4
-import java.io.IOException;
5
-import java.io.InputStream;
6
-import java.net.URL;
7
-import java.net.URLConnection;
8
-import java.util.Collections;
9
-import java.util.Iterator;
10
-import java.util.LinkedList;
11
-import java.util.List;
12
-import java.util.logging.Level;
13
-import java.util.logging.Logger;
14
-import java.util.regex.Matcher;
15
-import java.util.regex.Pattern;
16
-
17
-import com.sap.sse.common.Util;
18
-import com.sap.sse.landscape.Release;
19
-import com.sap.sse.landscape.ReleaseRepository;
20
-import com.sap.sse.util.HttpUrlConnectionHelper;
21
-
22
-public class ReleaseRepositoryImpl implements ReleaseRepository {
23
- private static final Logger logger = Logger.getLogger(ReleaseRepositoryImpl.class.getName());
24
- private final String repositoryBase;
25
-
26
- private final String masterReleaseNamePrefix;
27
-
28
- public ReleaseRepositoryImpl(String repositoryBase, String masterReleaseNamePrefix) {
29
- super();
30
- this.repositoryBase = repositoryBase;
31
- this.masterReleaseNamePrefix = masterReleaseNamePrefix;
32
- }
33
-
34
- @Override
35
- public String getRepositoryBase() {
36
- return repositoryBase;
37
- }
38
-
39
- @Override
40
- public String getMasterReleaseNamePrefix() {
41
- return masterReleaseNamePrefix;
42
- }
43
-
44
- private Iterable<Release> getAvailableReleases() {
45
- final List<Release> result = new LinkedList<>();
46
- try {
47
- final URLConnection connection = HttpUrlConnectionHelper.redirectConnection(new URL(getRepositoryBase()));
48
- final InputStream index = (InputStream) connection.getContent();
49
- int read = 0;
50
- final ByteArrayOutputStream bos = new ByteArrayOutputStream();
51
- while ((read=index.read()) != -1) {
52
- bos.write(read);
53
- }
54
- index.close();
55
- final String contents = bos.toString();
56
- final Pattern pattern = Pattern.compile("<a href=\"(([^/]*)-([0-9]*))/\">([^/]*)-([0-9]*)/</a>");
57
- final Matcher m = pattern.matcher(contents);
58
- int lastMatch = 0;
59
- while (m.find(lastMatch)) {
60
- result.add(new ReleaseImpl(m.group(1), this));
61
- lastMatch = m.end();
62
- }
63
- } catch (IOException e) {
64
- logger.log(Level.SEVERE, "Error loading releases from repository at "+getRepositoryBase()+". Continuing empty.", e);
65
- return Collections.emptyList();
66
- }
67
- return result;
68
- }
69
-
70
- @Override
71
- public Iterator<Release> iterator() {
72
- return getAvailableReleases().iterator();
73
- }
74
-
75
- @Override
76
- public Release getLatestRelease(String releaseNamePrefix) {
77
- Release result = null;
78
- for (final Release release : getAvailableReleases()) {
79
- if (release.getBaseName().equals(releaseNamePrefix) &&
80
- (result == null || release.getCreationDate().after(result.getCreationDate()))) {
81
- result = release;
82
- }
83
- }
84
- return result;
85
- }
86
-
87
- @Override
88
- public Release getRelease(String releaseName) {
89
- return Util.first(Util.filter(getAvailableReleases(), r->r.getName().equals(releaseName)));
90
- }
91
-
92
-}
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/AdminRole.java
... ...
@@ -1,7 +1,6 @@
1 1
package com.sap.sse.security.shared;
2 2
3 3
public class AdminRole extends RolePrototype {
4
- private static final long serialVersionUID = 3291793984984443193L;
5 4
6 5
private static final AdminRole INSTANCE = new AdminRole();
7 6
private static final String UUID_STRING = "dc77e3d1-d405-435e-8699-ce7245f6fd7a";
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/RolePrototype.java
... ...
@@ -4,13 +4,10 @@ import java.util.HashSet;
4 4
import java.util.Set;
5 5
import java.util.UUID;
6 6
7
-import com.sap.sse.common.NamedWithID;
8 7
import com.sap.sse.common.Util;
8
+import com.sap.sse.common.WithID;
9 9
10
-public abstract class RolePrototype implements NamedWithID {
11
-
12
- private static final long serialVersionUID = -3911998376131317304L;
13
-
10
+public abstract class RolePrototype implements WithID {
14 11
/*
15 12
* Might be used in Stringmessages to identify SubscriptionplanRoles. Do check and validate before changing.
16 13
*/
... ...
@@ -40,7 +37,7 @@ public abstract class RolePrototype implements NamedWithID {
40 37
}
41 38
}
42 39
43
- @Override
40
+// @Override
44 41
public String getName() {
45 42
return name;
46 43
}
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/ServerAdminRole.java
... ...
@@ -14,8 +14,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
14 14
*
15 15
*/
16 16
public class ServerAdminRole extends RolePrototype {
17
- private static final long serialVersionUID = -4196925850206436676L;
18
-
19 17
private static final ServerAdminRole INSTANCE = new ServerAdminRole();
20 18
21 19
ServerAdminRole() {
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/UserRole.java
... ...
@@ -4,8 +4,6 @@ import com.sap.sse.security.shared.HasPermissions.DefaultActions;
4 4
import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
5 5
6 6
public class UserRole extends RolePrototype {
7
- private static final long serialVersionUID = 3291793984984443193L;
8
-
9 7
private static final UserRole INSTANCE = new UserRole();
10 8
11 9
UserRole() {
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/subscription/AllDataMiningRole.java
... ...
@@ -10,7 +10,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
10 10
* Specifies a role that when associated to a user gives access to the date mining functionality on all servers.
11 11
*/
12 12
public class AllDataMiningRole extends RolePrototype {
13
- private static final long serialVersionUID = 6671501683896115261L;
14 13
private static final UUID ROLE_ID = UUID.fromString("de4205b5-ccf9-49b2-91e1-9a41b4db166b");
15 14
private static final AllDataMiningRole INSTANCE = new AllDataMiningRole();
16 15
java/com.sap.sse.security.common/src/com/sap/sse/security/shared/subscription/ArchiveDataMiningRole.java
... ...
@@ -10,7 +10,6 @@ import com.sap.sse.security.shared.impl.SecuredSecurityTypes;
10 10
* Specifies a role that when associated to a user gives access to the date mining functionality on archive server.
11 11
*/
12 12
public class ArchiveDataMiningRole extends RolePrototype {
13
- private static final long serialVersionUID = 6671501683896115261L;
14 13
private static final UUID ROLE_ID = UUID.fromString("f2993a7a-c08d-11ec-9d64-0242ac120002");
15 14
private static final ArchiveDataMiningRole INSTANCE = new ArchiveDataMiningRole();
16 15
java/target/configuration/logging_debug.properties
... ...
@@ -61,4 +61,7 @@ com.sap.sailing.domain.queclinkadapter.tracker.QueclinkUDPTracker.level = FINE
61 61
# Show locking progress in AIAgentImpl:
62 62
#com.sap.sailing.aiagent.impl.AIAgentImpl.level = FINE
63 63
# Show AI rules task enqueuing:
64
-com.sap.sailing.aiagent.impl.RaceListener.level = FINE
... ...
\ No newline at end of file
0
+com.sap.sailing.aiagent.impl.RaceListener.level = FINE
1
+
2
+# Show GithubReleasesRepository log output
3
+com.sap.sse.landscape.impl.GithubReleasesRepository.level = FINE
... ...
\ No newline at end of file
toolchains.xml
... ...
@@ -7,6 +7,17 @@
7 7
<toolchain>
8 8
<type>jdk</type>
9 9
<provides>
10
+ <version>8</version>
11
+ <id>JavaSE-1.8</id>
12
+ <vendor>sun</vendor>
13
+ </provides>
14
+ <configuration>
15
+ <jdkHome>/opt/sapjvm_8/jre</jdkHome>
16
+ </configuration>
17
+ </toolchain>
18
+ <toolchain>
19
+ <type>jdk</type>
20
+ <provides>
10 21
<version>1.8</version>
11 22
<id>JavaSE-1.8</id>
12 23
<vendor>sun</vendor>