91615a00b70fdbdf39a718e8a1a4889a04a2f6de
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> |