From 1de9317a404832e61d4281ce7360fbab4f53aeb1 Mon Sep 17 00:00:00 2001 From: Gabriel Harris-Rouquette Date: Sat, 9 Sep 2023 11:34:14 -0700 Subject: [PATCH] clean up micronaut framework integration Signed-off-by: Gabriel Harris-Rouquette --- .java-version | 2 +- .jvmopts | 1 + akka/build.gradle.kts | 32 +- akka/settings.gradle | 3 - akka/src/main/java/module-info.java | 5 - .../downloads/akka/AkkaExtension.java | 26 +- .../downloads/akka/AkkaSerializable.java | 7 + .../downloads/akka/ProductionAkkaSystem.java | 26 ++ akka/src/main/resources/refrerence.conf | 23 + akka/testkit/build.gradle.kts | 14 + .../test/akka/AkkaTestExtension.java | 44 ++ .../testkit/src/main/resources/reference.conf | 3 + .../downloads/artifact/api/Artifact.java | 46 -- .../artifact/api/ArtifactCollection.java | 42 -- .../artifact/api/ArtifactCoordinates.java | 66 --- .../artifact/api/ArtifactService.java | 83 ---- .../downloads/artifact/api/Group.java | 42 -- .../artifact/api/MavenCoordinates.java | 192 -------- .../downloads/artifact/api/VersionType.java | 115 ----- .../artifact/api/event/ArtifactUpdate.java | 110 ----- .../artifact/api/event/GroupUpdate.java | 74 --- .../artifact/api/query/ArtifactDetails.java | 129 ------ .../api/query/ArtifactRegistration.java | 82 ---- .../api/query/GetArtifactsResponse.java | 60 --- .../artifact/api/query/GroupRegistration.java | 56 --- .../artifact/api/query/GroupResponse.java | 61 --- .../artifact/api/query/GroupsResponse.java | 48 -- .../query/api/ArtifactQueryService.java | 45 -- .../query/api/GetArtifactDetailsResponse.java | 53 --- artifacts/README.md | 87 ---- artifacts/api/build.gradle.kts | 18 +- .../artifact/api/ArtifactCoordinates.java | 26 +- .../artifact/api/ArtifactService.java | 83 ---- .../artifact/api/query/ArtifactDetails.java | 22 +- .../api/query/ArtifactRegistration.java | 3 +- .../artifact/api/query/GroupRegistration.java | 17 +- .../artifact/api/query/GroupResponse.java | 10 +- artifacts/build.gradle.kts | 15 - artifacts/events/build.gradle.kts | 13 +- {akka => artifacts/events}/gradle.properties | 0 .../artifacts/events/ArtifactEvent.java | 5 +- .../artifacts/events}/DetailsEvent.java | 30 +- .../artifacts/events/GroupUpdate.java | 12 +- artifacts/gradle.properties | 1 - .../gradle/wrapper/gradle-wrapper.properties | 2 +- artifacts/micronaut-cli.yml | 6 - artifacts/server/build.gradle.kts | 108 ++--- .../artifacts/server/Application.java | 16 +- .../server/details/ArtifactDetailsEntity.java | 87 ++-- .../server/details/DetailsCommand.java | 3 +- .../server/details/DetailsManager.java | 136 ------ .../server/details/state/DetailsState.java | 6 +- .../server/details/state/PopulatedState.java | 5 +- .../server/global/GlobalCommand.java | 62 --- .../artifacts/server/global/GlobalEvent.java | 53 --- .../server/global/GlobalManager.java | 54 --- .../server/global/GlobalRegistration.java | 99 ---- .../artifacts/server/global/GlobalState.java | 38 -- .../artifacts/server/groups/GroupCommand.java | 63 --- .../artifacts/server/groups/GroupEntity.java | 206 --------- .../artifacts/server/groups/GroupEvent.java | 124 ----- .../artifacts/server/groups/GroupManager.java | 100 ---- .../server/groups/GroupsQueryController.java | 65 --- .../server/groups/state/EmptyState.java | 62 --- .../server/groups/state/GroupState.java | 47 -- .../server/groups/state/PopulatedState.java | 51 --- .../server/query/ArtifactsQuery.java | 12 - .../server/query/group/domain/GroupOrg.java | 27 ++ .../server/query/meta/ArtifactDto.java | 31 ++ .../query/meta/ArtifactQueryController.java | 52 +++ .../server/query/meta/ArtifactRepository.java | 22 + .../server/query/meta/domain/Group.java | 27 ++ .../query/meta/domain}/JpaArtifact.java | 108 ++--- .../meta/domain}/JpaArtifactTagValue.java | 51 +-- .../src/main/resources/application.conf | 32 ++ .../src/main/resources/application.toml | 10 - .../src/main/resources/application.yaml | 24 + .../changelog/01-create-artifacts-schema.xml | 125 +++++ .../main/resources/db/liquibase-changelog.xml | 10 + .../server/src/main/resources/logback.xml | 5 + .../server/ArtifactRepositoryTest.java | 118 +++++ .../src/test/resources/application-test.conf | 10 + .../src/test/resources/application-test.yaml | 22 + .../src/test}/resources/logback.xml | 7 +- .../artifacts/server/Application.java | 20 - .../server/query/ArtifactsQuery.java | 9 - .../server/query/GroupsQueryController.java | 28 -- artifacts/src/main/resources/application.toml | 10 - artifacts/src/main/resources/bootstrap.toml | 4 - .../downloads/artifacts/ArtifactsTest.java | 21 - artifacts/worker/build.gradle.kts | 79 ++-- .../worker/readside/ArtifactReadside.java | 102 +++++ .../worker/readside/JpaArtifact.java | 114 +++++ .../src/main/resources/application.conf | 32 ++ .../downloads/auth/api/AuthService.java | 54 --- .../auth/api/AuthenticationRequest.java | 36 -- .../downloads/auth/api/utils/AuthUtils.java | 144 ------ .../downloads/auth/AuthModule.java | 197 -------- .../downloads/auth/AuthServiceImpl.java | 81 ---- auth-impl/src/main/resources/application.conf | 20 - auth-impl/src/main/resources/logback.xml | 30 -- auth-impl/src/main/resources/reference.conf | 13 - build.gradle.kts | 132 ++---- build.sbt | 358 --------------- buildSrc/settings.gradle.kts | 9 + .../kotlin/soad.java-conventions.gradle.kts | 20 - dev/run_postgres.sh | 2 +- .../main/java/com/example/QuickstartApp.java | 62 --- .../main/java/com/example/UserRegistry.java | 101 ----- .../src/main/java/com/example/UserRoutes.java | 112 ----- downloads-api/src/main/java/module-info.java | 17 - .../spongepowered/downloads/api/Artifact.java | 46 -- .../downloads/api/ArtifactCollection.java | 42 -- .../downloads/api/ArtifactCoordinates.java | 66 --- .../spongepowered/downloads/api/Group.java | 42 -- .../downloads/api/MavenCoordinates.java | 192 -------- .../downloads/app/SystemOfADownloadsApp.java | 52 --- .../downloads/artifacts/ArtifactQueries.java | 59 --- .../downloads/artifacts/ArtifactRoutes.java | 92 ---- .../artifacts/transport/ArtifactDetails.java | 128 ------ .../transport/ArtifactRegistration.java | 82 ---- .../transport/GetArtifactDetailsResponse.java | 49 -- .../transport/GetArtifactsResponse.java | 40 -- .../transport/GroupRegistration.java | 56 --- .../artifacts/transport/GroupResponse.java | 36 -- .../artifacts/transport/GroupsResponse.java | 38 -- .../downloads/routes/VersionRoutes.java | 4 - .../downloads/versions/VersionQueries.java | 15 - .../downloads/versions/VersionRoutes.java | 72 --- .../versions/models/JpaTaggedVersion.java | 170 ------- .../models/JpaVersionedArtifactView.java | 231 ---------- .../versions/models/JpaVersionedAsset.java | 146 ------ .../models/JpaVersionedChangelog.java | 140 ------ .../versions/models/VersionedArtifactID.java | 59 --- .../versions/models/VersionedAssetID.java | 12 - .../versions/transport/QueryLatest.java | 46 -- .../versions/transport/QueryVersions.java | 57 --- .../versions/transport/TagCollection.java | 35 -- .../transport/VersionedChangelog.java | 65 --- .../versions/transport/VersionedCommit.java | 69 --- .../src/main/resources/application.conf | 6 - downloads-api/src/main/resources/logback.xml | 20 - .../test/java/com/example/UserRoutesTest.java | 77 ---- .../src/test/resources/application-test.conf | 3 - gradle.properties | 7 - gradle/libs.versions.toml | 46 ++ gradle/wrapper/gradle-wrapper.jar | Bin 60756 -> 62076 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 19 +- gradlew.bat | 1 + liquibase/changelog/akka/akka_001_init.sql | 62 +++ liquibase/changelog/akka/akka_2_8_2.xml | 14 + liquibase/changelog/changelog.xml | 2 +- liquibase/changelog/lagom/lagom_1_6.xml | 54 --- project/build.properties | 1 - project/plugins.sbt | 8 - .../auth/AuthenticatedInternalService.java | 63 --- .../auth/InternalApplicationProfile.java | 35 -- .../downloads/auth/SOADAuth.java | 32 -- server-auth/src/main/resources/reference.conf | 16 - settings.gradle.kts | 20 +- .../downloads/maven/MavenConstants.java | 29 -- .../maven/artifact/ArtifactMavenMetadata.java | 86 ---- .../downloads/maven/artifact/Versioning.java | 90 ---- .../downloads/maven/snapshot/Snapshot.java | 36 -- .../maven/snapshot/SnapshotAsset.java | 86 ---- .../maven/snapshot/SnapshotMetadata.java | 42 -- .../maven/snapshot/SnapshotVersioning.java | 47 -- .../sonatype/AssetSearchResponse.java | 43 -- .../downloads/sonatype/Component.java | 111 ----- .../sonatype/ComponentSearchResponse.java | 140 ------ .../downloads/sonatype/MavenPom.java | 82 ---- .../downloads/maven/MavenMetadataTest.java | 95 ---- .../test/resources/maven-metadata-example.xml | 49 -- .../java/systemofadownload/AkkaExtension.java | 56 --- .../java/systemofadownload/Application.java | 18 - .../SystemofadownloadController.java | 58 --- .../UnauthorizedHandler.java | 30 -- .../artifacts/ArtifactController.java | 33 -- .../artifacts/api/Artifact.java | 46 -- .../artifacts/api/ArtifactCollection.java | 43 -- .../artifacts/api/ArtifactCoordinates.java | 66 --- .../artifacts/api/ArtifactService.java | 69 --- .../artifacts/api/Group.java | 42 -- .../artifacts/api/MavenCoordinates.java | 192 -------- .../artifacts/api/VersionType.java | 115 ----- .../artifacts/api/event/ArtifactUpdate.java | 110 ----- .../artifacts/api/event/GroupUpdate.java | 74 --- .../artifacts/api/query/ArtifactDetails.java | 129 ------ .../api/query/ArtifactRegistration.java | 81 ---- .../api/query/GetArtifactsResponse.java | 60 --- .../api/query/GroupRegistration.java | 55 --- .../artifacts/api/query/GroupResponse.java | 61 --- .../artifacts/api/query/GroupsResponse.java | 48 -- .../query/ArtifactQueryController.java | 23 - .../groups/GroupController.java | 41 -- .../groups/query/GroupsQueryController.java | 32 -- src/main/resources/application.toml | 20 - src/main/resources/db/changelog/01-schema.xml | 10 - src/main/resources/db/liquibase-changelog.xml | 8 - src/main/resources/logback.xml | 15 - .../SystemofadownloadTest.java | 21 - .../synchronizer/SonatypeSynchronizer.java | 95 ---- .../SynchronizationExtension.java | 47 -- .../synchronizer/SynchronizerModule.java | 95 ---- .../synchronizer/SynchronizerSettings.java | 97 ---- .../actor/ArtifactSyncExtension.java | 84 ---- .../actor/ArtifactSyncWorker.java | 134 ------ .../actor/CommitDetailsRegistrar.java | 84 ---- .../synchronizer/actor/CommitRegistrar.java | 94 ---- .../synchronizer/akka/FlowUtil.java | 122 ----- .../assetsync/AssetSettingsExtension.java | 69 --- .../assetsync/VersionConsumer.java | 74 --- .../assetsync/VersionedComponentWorker.java | 427 ------------------ .../gitmanaged/ArtifactSubscriber.java | 134 ------ .../gitmanaged/CommitConsumer.java | 140 ------ .../gitmanaged/ScheduledCommitResolver.java | 302 ------------- .../gitmanaged/domain/GitCommand.java | 144 ------ .../gitmanaged/domain/GitEvent.java | 83 ---- .../gitmanaged/domain/GitManagedArtifact.java | 141 ------ .../gitmanaged/domain/GitState.java | 222 --------- .../util/jgit/ActorLoggerPrinterWriter.java | 121 ----- .../util/jgit/AssetCommitResolver.java | 178 -------- .../util/jgit/CommitResolutionManager.java | 410 ----------------- .../util/jgit/FileWalkerConsumer.java | 68 --- .../util/jgit/RepositoryCloner.java | 270 ----------- .../util/jgit/StubSystemReader.java | 124 ----- .../resync/RequestArtifactsToSync.java | 90 ---- .../synchronizer/resync/ResyncExtension.java | 44 -- .../synchronizer/resync/ResyncManager.java | 148 ------ .../synchronizer/resync/ResyncSettings.java | 45 -- .../domain/ArtifactSynchronizerAggregate.java | 204 --------- .../synchronizer/resync/domain/Command.java | 82 ---- .../synchronizer/resync/domain/SyncState.java | 60 --- .../resync/domain/SynchronizeEvent.java | 53 --- .../versionsync/ArtifactConsumer.java | 81 ---- .../ArtifactVersionSyncEntity.java | 307 ------------- .../ArtifactVersionSyncModule.java | 57 --- .../versionsync/BatchVersionSyncManager.java | 89 ---- .../versionsync/SyncRegistration.java | 93 ---- .../versionsync/VersionRegistrationState.java | 336 -------------- .../versionsync/VersionSyncEvent.java | 77 ---- .../main/resources/META-INF/persistence.xml | 12 - .../src/main/resources/application.conf | 84 ---- .../src/main/resources/logback.xml | 40 -- .../src/main/resources/reference.conf | 25 - .../src/main/resources/soad.gitconfig | 0 .../worker/CommitDetailsRegistrarTest.java | 6 - .../worker/CommitResolutionManagerTest.java | 120 ----- .../test/worker/TestGitSystemReader.java | 77 ---- .../src/test/resources/dummy.gitconfig | 0 .../versions/api/VersionsService.java | 74 --- .../versions/api/models/ArtifactUpdate.java | 59 --- .../api/models/CommitRegistration.java | 61 --- .../versions/api/models/TagRegistration.java | 54 --- .../versions/api/models/TagVersion.java | 76 ---- .../api/models/VersionRegistration.java | 109 ----- .../api/models/VersionedArtifactUpdates.java | 62 --- .../api/models/VersionedChangelog.java | 65 --- .../versions/api/models/VersionedCommit.java | 70 --- .../api/models/tags/ArtifactTagEntry.java | 58 --- .../api/models/tags/ArtifactTagValue.java | 43 -- .../api/models/tags/VersionTagValue.java | 35 -- .../versions/server/VersionsModule.java | 75 --- .../versions/server/VersionsServiceImpl.java | 370 --------------- .../versions/server/domain/ACCommand.java | 82 ---- .../versions/server/domain/ACEvent.java | 134 ------ .../server/domain/InvalidRequest.java | 40 -- .../versions/server/domain/State.java | 223 --------- .../domain/VersionedArtifactAggregate.java | 189 -------- .../server/domain/VersionedArtifactEvent.java | 87 ---- .../readside/AssetReadsidePersistence.java | 120 ----- .../versions/server/readside/JpaArtifact.java | 186 -------- .../JpaArtifactRegexRecommendation.java | 85 ---- .../server/readside/JpaArtifactTag.java | 98 ---- .../server/readside/JpaArtifactVersion.java | 149 ------ .../readside/JpaVersionedArtifactAsset.java | 144 ------ .../readside/VersionReadSidePersistence.java | 201 --------- .../server/readside/VersionedTagWorker.java | 146 ------ .../versions/worker/EntityStore.java | 37 -- .../versions/worker/VersionConfig.java | 52 --- .../versions/worker/VersionExtension.java | 46 -- .../worker/VersionsWorkerSupervisor.java | 69 --- .../versions/worker/WorkerModule.java | 82 ---- .../versions/worker/WorkerSpawner.java | 75 --- .../actor/artifacts/CommitExtractor.java | 238 ---------- .../artifacts/FileCollectionOperator.java | 83 ---- .../artifacts/PotentiallyUsableAsset.java | 36 -- .../actor/delegates/RawCommitReceiver.java | 70 --- .../versionedartifact/ArtifactEvent.java | 82 ---- .../versionedartifact/ArtifactState.java | 249 ---------- .../VersionedArtifactCommand.java | 112 ----- .../VersionedArtifactEntity.java | 306 ------------- .../downloads/versions/worker/intro.md | 40 -- .../worker/readside/CommitProcessor.java | 214 --------- .../readside/model/JpaVersionChangelog.java | 132 ------ .../readside/model/JpaVersionedArtifact.java | 121 ----- .../main/resources/META-INF/persistence.xml | 21 - .../src/main/resources/application.conf | 42 -- versions-impl/src/main/resources/logback.xml | 38 -- .../src/main/resources/reference.conf | 8 - .../VersionedArtifactAggregateTest.java | 74 --- .../versions/worker/CommitExtractorTest.java | 61 --- .../worker/FileCollectionOperatorTest.java | 75 --- .../worker/VersionedArtifactEntityTest.java | 131 ------ .../downloads/versions/RegexValidations.java | 64 --- .../src/test/resources/application-test.conf | 11 - .../test/resources/bad-commit-test-jar.jar | Bin 359 -> 0 bytes versions-impl/src/test/resources/manifest | 3 - .../src/test/resources/no-commit-test-jar.jar | Bin 339 -> 0 bytes versions-impl/src/test/resources/test-jar.jar | Bin 388 -> 0 bytes .../query/api/VersionsQueryService.java | 70 --- .../query/api/models/QueryLatest.java | 46 -- .../query/api/models/QueryVersions.java | 57 --- .../query/api/models/TagCollection.java | 35 -- .../query/api/models/VersionedChangelog.java | 65 --- .../query/api/models/VersionedCommit.java | 69 --- .../query/impl/VersionQueryModule.java | 37 -- .../query/impl/VersionQueryServiceImpl.java | 324 ------------- .../query/impl/models/JpaTaggedVersion.java | 170 ------- .../impl/models/JpaVersionedArtifactView.java | 231 ---------- .../query/impl/models/JpaVersionedAsset.java | 144 ------ .../impl/models/JpaVersionedChangelog.java | 140 ------ .../impl/models/VersionedArtifactID.java | 59 --- .../main/resources/META-INF/persistence.xml | 16 - .../src/main/resources/application.conf | 29 -- .../src/main/resources/logback.xml | 37 -- 327 files changed, 1355 insertions(+), 22577 deletions(-) delete mode 100644 akka/settings.gradle delete mode 100644 akka/src/main/java/module-info.java create mode 100644 akka/src/main/java/org/spongepowered/downloads/akka/AkkaSerializable.java create mode 100644 akka/src/main/java/org/spongepowered/downloads/akka/ProductionAkkaSystem.java create mode 100644 akka/src/main/resources/refrerence.conf create mode 100644 akka/testkit/build.gradle.kts create mode 100644 akka/testkit/src/main/java/org/spongepowered/downloads/test/akka/AkkaTestExtension.java create mode 100644 akka/testkit/src/main/resources/reference.conf delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Artifact.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCollection.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Group.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/MavenCoordinates.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/VersionType.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/ArtifactUpdate.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/GroupUpdate.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GetArtifactsResponse.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java delete mode 100644 artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupsResponse.java delete mode 100644 artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/ArtifactQueryService.java delete mode 100644 artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/GetArtifactDetailsResponse.java delete mode 100644 artifacts/README.md delete mode 100644 artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java rename {akka => artifacts/events}/gradle.properties (100%) rename artifacts/{server/src/main/java/org/spongepowered/downloads/artifacts/server/details => events/src/main/java/org/spongepowered/downloads/artifacts/events}/DetailsEvent.java (70%) delete mode 100644 artifacts/gradle.properties delete mode 100644 artifacts/micronaut-cli.yml delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsManager.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalCommand.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalEvent.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalManager.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalRegistration.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalState.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupCommand.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEntity.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEvent.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupManager.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupsQueryController.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/EmptyState.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/GroupState.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/PopulatedState.java delete mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java create mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/group/domain/GroupOrg.java create mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactDto.java create mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactQueryController.java create mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactRepository.java create mode 100644 artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/Group.java rename {downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models => artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain}/JpaArtifact.java (54%) rename {downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models => artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain}/JpaArtifactTagValue.java (73%) create mode 100644 artifacts/server/src/main/resources/application.conf delete mode 100644 artifacts/server/src/main/resources/application.toml create mode 100644 artifacts/server/src/main/resources/application.yaml create mode 100644 artifacts/server/src/main/resources/db/changelog/01-create-artifacts-schema.xml create mode 100644 artifacts/server/src/main/resources/db/liquibase-changelog.xml create mode 100644 artifacts/server/src/test/java/org/spongepowered/downloads/test/artifacts/server/ArtifactRepositoryTest.java create mode 100644 artifacts/server/src/test/resources/application-test.conf create mode 100644 artifacts/server/src/test/resources/application-test.yaml rename artifacts/{src/main => server/src/test}/resources/logback.xml (84%) delete mode 100644 artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java delete mode 100644 artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java delete mode 100644 artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/GroupsQueryController.java delete mode 100644 artifacts/src/main/resources/application.toml delete mode 100644 artifacts/src/main/resources/bootstrap.toml delete mode 100644 artifacts/src/test/java/org/spongepowered/downloads/artifacts/ArtifactsTest.java create mode 100644 artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/ArtifactReadside.java create mode 100644 artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/JpaArtifact.java create mode 100644 artifacts/worker/src/main/resources/application.conf delete mode 100644 auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthService.java delete mode 100644 auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthenticationRequest.java delete mode 100644 auth-api/src/main/java/org/spongepowered/downloads/auth/api/utils/AuthUtils.java delete mode 100644 auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthModule.java delete mode 100644 auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthServiceImpl.java delete mode 100644 auth-impl/src/main/resources/application.conf delete mode 100644 auth-impl/src/main/resources/logback.xml delete mode 100644 auth-impl/src/main/resources/reference.conf delete mode 100644 build.sbt create mode 100644 buildSrc/settings.gradle.kts delete mode 100644 buildSrc/src/main/kotlin/soad.java-conventions.gradle.kts delete mode 100644 downloads-api/src/main/java/com/example/QuickstartApp.java delete mode 100644 downloads-api/src/main/java/com/example/UserRegistry.java delete mode 100644 downloads-api/src/main/java/com/example/UserRoutes.java delete mode 100644 downloads-api/src/main/java/module-info.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/api/Artifact.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCollection.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCoordinates.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/api/Group.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/api/MavenCoordinates.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/app/SystemOfADownloadsApp.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactQueries.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactRoutes.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactDetails.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactRegistration.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactDetailsResponse.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactsResponse.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupRegistration.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupResponse.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupsResponse.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/routes/VersionRoutes.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionQueries.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionRoutes.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaTaggedVersion.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedArtifactView.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedAsset.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedChangelog.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedArtifactID.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedAssetID.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryLatest.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryVersions.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/TagCollection.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedChangelog.java delete mode 100644 downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedCommit.java delete mode 100644 downloads-api/src/main/resources/application.conf delete mode 100644 downloads-api/src/main/resources/logback.xml delete mode 100644 downloads-api/src/test/java/com/example/UserRoutesTest.java delete mode 100644 downloads-api/src/test/resources/application-test.conf delete mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 liquibase/changelog/akka/akka_001_init.sql create mode 100644 liquibase/changelog/akka/akka_2_8_2.xml delete mode 100644 liquibase/changelog/lagom/lagom_1_6.xml delete mode 100644 project/build.properties delete mode 100644 project/plugins.sbt delete mode 100644 server-auth/src/main/java/org/spongepowered/downloads/auth/AuthenticatedInternalService.java delete mode 100644 server-auth/src/main/java/org/spongepowered/downloads/auth/InternalApplicationProfile.java delete mode 100644 server-auth/src/main/java/org/spongepowered/downloads/auth/SOADAuth.java delete mode 100644 server-auth/src/main/resources/reference.conf delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/MavenConstants.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/ArtifactMavenMetadata.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/Versioning.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/Snapshot.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotAsset.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotMetadata.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotVersioning.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/sonatype/AssetSearchResponse.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/sonatype/Component.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/sonatype/ComponentSearchResponse.java delete mode 100644 sonatype/src/main/java/org/spongepowered/downloads/sonatype/MavenPom.java delete mode 100644 sonatype/src/test/java/org/spongepowered/downloads/maven/MavenMetadataTest.java delete mode 100644 sonatype/src/test/resources/maven-metadata-example.xml delete mode 100644 src/main/java/systemofadownload/AkkaExtension.java delete mode 100644 src/main/java/systemofadownload/Application.java delete mode 100644 src/main/java/systemofadownload/SystemofadownloadController.java delete mode 100644 src/main/java/systemofadownload/UnauthorizedHandler.java delete mode 100644 src/main/java/systemofadownload/artifacts/ArtifactController.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/Artifact.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/ArtifactCollection.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/ArtifactCoordinates.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/ArtifactService.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/Group.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/MavenCoordinates.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/VersionType.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/event/ArtifactUpdate.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/event/GroupUpdate.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/ArtifactDetails.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/ArtifactRegistration.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/GetArtifactsResponse.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/GroupRegistration.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/GroupResponse.java delete mode 100644 src/main/java/systemofadownload/artifacts/api/query/GroupsResponse.java delete mode 100644 src/main/java/systemofadownload/artifacts/query/ArtifactQueryController.java delete mode 100644 src/main/java/systemofadownload/groups/GroupController.java delete mode 100644 src/main/java/systemofadownload/groups/query/GroupsQueryController.java delete mode 100644 src/main/resources/application.toml delete mode 100644 src/main/resources/db/changelog/01-schema.xml delete mode 100644 src/main/resources/db/liquibase-changelog.xml delete mode 100644 src/main/resources/logback.xml delete mode 100644 src/test/java/systemofadownload/SystemofadownloadTest.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/SonatypeSynchronizer.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizationExtension.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerModule.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerSettings.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncExtension.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncWorker.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitDetailsRegistrar.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitRegistrar.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/akka/FlowUtil.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/AssetSettingsExtension.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionConsumer.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionedComponentWorker.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ArtifactSubscriber.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/CommitConsumer.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ScheduledCommitResolver.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitCommand.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitEvent.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitManagedArtifact.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitState.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/ActorLoggerPrinterWriter.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/AssetCommitResolver.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/CommitResolutionManager.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/FileWalkerConsumer.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/RepositoryCloner.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/StubSystemReader.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/RequestArtifactsToSync.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncExtension.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncManager.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncSettings.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/ArtifactSynchronizerAggregate.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/Command.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SyncState.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SynchronizeEvent.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactConsumer.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncEntity.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncModule.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/BatchVersionSyncManager.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/SyncRegistration.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionRegistrationState.java delete mode 100644 version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionSyncEvent.java delete mode 100644 version-synchronizer/src/main/resources/META-INF/persistence.xml delete mode 100644 version-synchronizer/src/main/resources/application.conf delete mode 100644 version-synchronizer/src/main/resources/logback.xml delete mode 100644 version-synchronizer/src/main/resources/reference.conf delete mode 100644 version-synchronizer/src/main/resources/soad.gitconfig delete mode 100644 version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitDetailsRegistrarTest.java delete mode 100644 version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitResolutionManagerTest.java delete mode 100644 version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/TestGitSystemReader.java delete mode 100644 version-synchronizer/src/test/resources/dummy.gitconfig delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/VersionsService.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/ArtifactUpdate.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/CommitRegistration.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagRegistration.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagVersion.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionRegistration.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedArtifactUpdates.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedChangelog.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedCommit.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagEntry.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagValue.java delete mode 100644 versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/VersionTagValue.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsModule.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsServiceImpl.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACCommand.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACEvent.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/InvalidRequest.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/State.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactAggregate.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactEvent.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/AssetReadsidePersistence.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifact.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactRegexRecommendation.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactTag.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactVersion.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaVersionedArtifactAsset.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionReadSidePersistence.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionedTagWorker.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/EntityStore.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionConfig.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionExtension.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionsWorkerSupervisor.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerModule.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerSpawner.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/CommitExtractor.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/FileCollectionOperator.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/PotentiallyUsableAsset.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/delegates/RawCommitReceiver.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactEvent.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactState.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactCommand.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactEntity.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/intro.md delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/CommitProcessor.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionChangelog.java delete mode 100644 versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionedArtifact.java delete mode 100644 versions-impl/src/main/resources/META-INF/persistence.xml delete mode 100644 versions-impl/src/main/resources/application.conf delete mode 100644 versions-impl/src/main/resources/logback.xml delete mode 100644 versions-impl/src/main/resources/reference.conf delete mode 100644 versions-impl/src/test/java/org/spongepowered/downloads/test/server/collection/VersionedArtifactAggregateTest.java delete mode 100644 versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/CommitExtractorTest.java delete mode 100644 versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/FileCollectionOperatorTest.java delete mode 100644 versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/VersionedArtifactEntityTest.java delete mode 100644 versions-impl/src/test/java/org/spongepowered/downloads/versions/RegexValidations.java delete mode 100644 versions-impl/src/test/resources/application-test.conf delete mode 100644 versions-impl/src/test/resources/bad-commit-test-jar.jar delete mode 100644 versions-impl/src/test/resources/manifest delete mode 100644 versions-impl/src/test/resources/no-commit-test-jar.jar delete mode 100644 versions-impl/src/test/resources/test-jar.jar delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/VersionsQueryService.java delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryLatest.java delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryVersions.java delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/TagCollection.java delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedChangelog.java delete mode 100644 versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedCommit.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryModule.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryServiceImpl.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaTaggedVersion.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedArtifactView.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedAsset.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedChangelog.java delete mode 100644 versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/VersionedArtifactID.java delete mode 100644 versions-query-impl/src/main/resources/META-INF/persistence.xml delete mode 100644 versions-query-impl/src/main/resources/application.conf delete mode 100644 versions-query-impl/src/main/resources/logback.xml diff --git a/.java-version b/.java-version index 46cbfbc7..fcc01369 100644 --- a/.java-version +++ b/.java-version @@ -1 +1 @@ -graalvm64-17.0.4 +20.0.1 diff --git a/.jvmopts b/.jvmopts index b7359b71..a2c5f585 100644 --- a/.jvmopts +++ b/.jvmopts @@ -3,4 +3,5 @@ -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8 +--enable-preview --add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/akka/build.gradle.kts b/akka/build.gradle.kts index 4ab025b9..99e22dcf 100644 --- a/akka/build.gradle.kts +++ b/akka/build.gradle.kts @@ -2,28 +2,16 @@ version = "0.1" group = "org.spongepowered.downloads" - -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project +plugins { + id("com.github.johnrengelman.shadow") + id("io.micronaut.library") +} dependencies { - implementation("com.ongres.scram:client:2.1") - implementation("jakarta.annotation:jakarta.annotation-api") - implementation(platform("com.typesafe.akka:akka-bom_${scalaVersion}:${akkaVersion}")) - implementation("com.typesafe.akka:akka-actor-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-sharding-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.lightbend.akka.management:akka-management_${scalaVersion}:${akkaManagementVersion}") - implementation("com.lightbend.akka.management:akka-management-cluster-bootstrap_${scalaVersion}:${akkaManagementVersion}") - - runtimeOnly("ch.qos.logback:logback-classic") - compileOnly("org.graalvm.nativeimage:svm") - - implementation("io.micronaut:micronaut-validation") + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") + implementation("io.micronaut.serde:micronaut-serde-jackson") + api("io.micronaut:micronaut-inject") + api(platform(libs.akkaBom)) + api(libs.bundles.actors) + implementation(libs.bundles.akkaManagement) } - - diff --git a/akka/settings.gradle b/akka/settings.gradle deleted file mode 100644 index 766979b3..00000000 --- a/akka/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ - -rootProject.name="akka" - diff --git a/akka/src/main/java/module-info.java b/akka/src/main/java/module-info.java deleted file mode 100644 index 4b98e64f..00000000 --- a/akka/src/main/java/module-info.java +++ /dev/null @@ -1,5 +0,0 @@ -module systemofadownload.akka { - requires akka.actor.typed; - requires akka.cluster.sharding; - exports org.spongepowered.downloads.akka; -} diff --git a/akka/src/main/java/org/spongepowered/downloads/akka/AkkaExtension.java b/akka/src/main/java/org/spongepowered/downloads/akka/AkkaExtension.java index 1a232f7d..5f24ff44 100644 --- a/akka/src/main/java/org/spongepowered/downloads/akka/AkkaExtension.java +++ b/akka/src/main/java/org/spongepowered/downloads/akka/AkkaExtension.java @@ -1,6 +1,7 @@ package org.spongepowered.downloads.akka; import akka.actor.typed.ActorSystem; +import akka.actor.typed.Behavior; import akka.actor.typed.Scheduler; import akka.actor.typed.SpawnProtocol; import akka.actor.typed.javadsl.Adapter; @@ -12,35 +13,32 @@ import com.typesafe.config.ConfigFactory; import io.micronaut.context.annotation.Bean; import io.micronaut.context.annotation.Factory; +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; @Factory public class AkkaExtension { @Bean - public Scheduler systemScheduler() { - return system().scheduler(); + public Scheduler systemScheduler(@NonNull ActorSystem system) { + return system.scheduler(); } @Bean public Config akkaConfig() { - return ConfigFactory.load(); + return ConfigFactory.defaultApplication(); } + @Singleton @Bean(preDestroy = "terminate") - public ActorSystem system() { - Config config = akkaConfig(); - return ActorSystem.create( - Behaviors.setup(ctx -> { - akka.actor.ActorSystem unTypedSystem = Adapter.toClassic(ctx.getSystem()); - AkkaManagement.get(unTypedSystem).start(); - ClusterBootstrap.get(unTypedSystem).start(); - return SpawnProtocol.create(); - }), config.getString("some.cluster.name")); + public ActorSystem system(@NonNull Behavior behavior, @NonNull Config config) { + return ActorSystem.create(behavior, "soad-master"); } @Bean - public ClusterSharding clusterSharding() { - return ClusterSharding.get(system()); + @Singleton + public ClusterSharding clusterSharding(@NonNull ActorSystem system) { + return ClusterSharding.get(system); } } diff --git a/akka/src/main/java/org/spongepowered/downloads/akka/AkkaSerializable.java b/akka/src/main/java/org/spongepowered/downloads/akka/AkkaSerializable.java new file mode 100644 index 00000000..793d772e --- /dev/null +++ b/akka/src/main/java/org/spongepowered/downloads/akka/AkkaSerializable.java @@ -0,0 +1,7 @@ +package org.spongepowered.downloads.akka; + +/** + * Marker interface for Akka serialization via Jackson + */ +public interface AkkaSerializable { +} diff --git a/akka/src/main/java/org/spongepowered/downloads/akka/ProductionAkkaSystem.java b/akka/src/main/java/org/spongepowered/downloads/akka/ProductionAkkaSystem.java new file mode 100644 index 00000000..62c6baa1 --- /dev/null +++ b/akka/src/main/java/org/spongepowered/downloads/akka/ProductionAkkaSystem.java @@ -0,0 +1,26 @@ +package org.spongepowered.downloads.akka; + +import akka.actor.typed.ActorSystem; +import akka.actor.typed.Behavior; +import akka.actor.typed.SpawnProtocol; +import akka.actor.typed.javadsl.Behaviors; +import akka.management.cluster.bootstrap.ClusterBootstrap; +import akka.management.javadsl.AkkaManagement; +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Requires; + +@Factory +public class ProductionAkkaSystem { + + @Bean + public Behavior productionGuardian() { + return Behaviors.setup(ctx -> { + final var system = ctx.getSystem(); + ClusterBootstrap.get(system).start(); + AkkaManagement.get(system).start(); + return SpawnProtocol.create(); + }); + } + +} diff --git a/akka/src/main/resources/refrerence.conf b/akka/src/main/resources/refrerence.conf new file mode 100644 index 00000000..7aa8e90a --- /dev/null +++ b/akka/src/main/resources/refrerence.conf @@ -0,0 +1,23 @@ + +akka { + actor { + provider = "cluster" + serialization-bindings { + "org.spongepowered.downloads.akka.AkkaSerializable" = jackson-json + } + } + remote.artery { + canonical { + hostname = "127.0.0.1" + port = 2551 + } + } + + cluster { + seed-nodes = [ + "akka://ClusterSystem@127.0.0.1:2551", + "akka://ClusterSystem@127.0.0.1:2552"] + + downing-provider-class = "akka.cluster.sbr.SplitBrainResolverProvider" + } +} diff --git a/akka/testkit/build.gradle.kts b/akka/testkit/build.gradle.kts new file mode 100644 index 00000000..ff4b171c --- /dev/null +++ b/akka/testkit/build.gradle.kts @@ -0,0 +1,14 @@ + + + +plugins { + id("com.github.johnrengelman.shadow") + id("io.micronaut.library") +} +dependencies { + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") + implementation("io.micronaut.serde:micronaut-serde-jackson") + api("io.micronaut:micronaut-inject") + api(project(":akka")) + api(libs.akka.testkit) +} diff --git a/akka/testkit/src/main/java/org/spongepowered/downloads/test/akka/AkkaTestExtension.java b/akka/testkit/src/main/java/org/spongepowered/downloads/test/akka/AkkaTestExtension.java new file mode 100644 index 00000000..69e37478 --- /dev/null +++ b/akka/testkit/src/main/java/org/spongepowered/downloads/test/akka/AkkaTestExtension.java @@ -0,0 +1,44 @@ +package org.spongepowered.downloads.test.akka; + +import akka.actor.testkit.typed.javadsl.ActorTestKit; +import akka.actor.testkit.typed.javadsl.BehaviorTestKit; +import akka.actor.typed.ActorSystem; +import akka.actor.typed.Behavior; +import akka.actor.typed.SpawnProtocol; +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import io.micronaut.context.annotation.Bean; +import io.micronaut.context.annotation.Factory; +import io.micronaut.context.annotation.Replaces; +import io.micronaut.core.annotation.NonNull; +import jakarta.inject.Singleton; + +@Factory +public class AkkaTestExtension { + + @Replaces + @Bean + public Behavior testBehavior() { + return SpawnProtocol.create(); + } + + @Replaces + @Bean + public Config testConfig() { + return ConfigFactory.defaultApplication() + .withFallback(BehaviorTestKit.applicationTestConfig()) + .resolve(); + } + + @Bean(preDestroy = "shutdownTestKit") + public ActorTestKit testKit() { + return ActorTestKit.create(); + } + + @Replaces(bean = ActorSystem.class) + @Singleton + public ActorSystem system(@NonNull ActorTestKit kit) { + return kit.system(); + } + +} diff --git a/akka/testkit/src/main/resources/reference.conf b/akka/testkit/src/main/resources/reference.conf new file mode 100644 index 00000000..02455b35 --- /dev/null +++ b/akka/testkit/src/main/resources/reference.conf @@ -0,0 +1,3 @@ +systemofadownload { + clustering = false +} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Artifact.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Artifact.java deleted file mode 100644 index 94c7ef77..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Artifact.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.net.URI; -import java.util.Optional; - -@JsonSerialize -public final record Artifact( - @JsonProperty Optional classifier, - @JsonProperty URI downloadUrl, - @JsonProperty String md5, - @JsonProperty String sha1, - @JsonProperty String extension -) { - @JsonCreator - public Artifact { - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCollection.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCollection.java deleted file mode 100644 index aab520db..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCollection.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -@JsonDeserialize -public final record ArtifactCollection( - @JsonProperty("assets") List components, - @JsonProperty("coordinates") MavenCoordinates coordinates -) { - - @JsonCreator - public ArtifactCollection { - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java deleted file mode 100644 index 99131c11..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.util.StringJoiner; - -@JsonDeserialize -public record ArtifactCoordinates( - @JsonProperty(required = true) String groupId, - @JsonProperty(required = true) String artifactId -) { - @JsonCreator - public ArtifactCoordinates { - } - - public MavenCoordinates version(String version) { - return MavenCoordinates.parse( - new StringJoiner(":").add(this.groupId()).add(this.artifactId()).add(version).toString()); - } - - public String asMavenString() { - return this.groupId() + ":" + this.artifactId(); - } - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String groupId() { - return groupId; - } - - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String artifactId() { - return artifactId; - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java deleted file mode 100644 index 00508fc5..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.broker.Topic; -import com.lightbend.lagom.javadsl.api.broker.kafka.KafkaProperties; -import com.lightbend.lagom.javadsl.api.transport.Method; -import org.spongepowered.downloads.artifact.api.event.ArtifactUpdate; -import org.spongepowered.downloads.artifact.api.event.GroupUpdate; -import org.spongepowered.downloads.artifact.api.query.ArtifactDetails; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; - -public interface ArtifactService extends Service { - - ServiceCall getArtifacts(String groupId); - - ServiceCall registerArtifacts( - String groupId - ); - - ServiceCall registerGroup(); - - ServiceCall, ArtifactDetails.Response> updateDetails(String groupId, String artifactId); - - ServiceCall getGroup(String groupId); - - ServiceCall getGroups(); - - Topic groupTopic(); - - Topic artifactUpdate(); - - @Override - default Descriptor descriptor() { - return Service.named("artifacts") - .withCalls( - Service.restCall(Method.GET, "/artifacts/groups/:groupId", this::getGroup), - Service.restCall(Method.GET, "/artifacts/groups", this::getGroups), - Service.restCall(Method.POST, "/artifacts/groups", this::registerGroup), - Service.restCall(Method.GET, "/artifacts/groups/:groupId/artifacts", this::getArtifacts), - Service.restCall(Method.POST, "/artifacts/groups/:groupId/artifacts", this::registerArtifacts), - Service.restCall(Method.PATCH, "/artifacts/groups/:groupId/artifacts/:artifactId/update", this::updateDetails) - ) - .withTopics( - Service.topic("group-activity", this::groupTopic) - .withProperty(KafkaProperties.partitionKeyStrategy(), GroupUpdate::groupId), - Service.topic("artifact-details-update", this::artifactUpdate) - .withProperty(KafkaProperties.partitionKeyStrategy(), ArtifactUpdate::partitionKey) - ) - .withAutoAcl(true); - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Group.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Group.java deleted file mode 100644 index 7e9145d1..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/Group.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -@JsonDeserialize -public record Group( - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String website -) { - - @JsonCreator - public Group { - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/MavenCoordinates.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/MavenCoordinates.java deleted file mode 100644 index c1acb521..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/MavenCoordinates.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.apache.maven.artifact.versioning.ComparableVersion; - -import java.util.Objects; -import java.util.StringJoiner; -import java.util.regex.Pattern; - -@JsonDeserialize -public final class MavenCoordinates implements Comparable { - - private static final Pattern MAVEN_REGEX = Pattern.compile("^[-\\w.]+$"); - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String groupId; - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String artifactId; - /** - * The version of an artifact, as defined by the Apache Maven documentation. This is - * traditionally specified as a Maven repository searchable version string, such as - * {@code 1.0.0-SNAPSHOT}. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String version; - - @JsonIgnore - public final VersionType versionType; - - @JsonIgnore - private final ComparableVersion mavenVersion; - - /** - * Parses a set of maven formatted coordinates as per - * Apache - * Maven's documentation. - * - * @param coordinates The coordinates delimited by `:` - * @return A parsed set of MavenCoordinates - */ - public static MavenCoordinates parse(final String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - return new MavenCoordinates(groupId, artifactId, version); - } - - public MavenCoordinates(String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonCreator - public MavenCoordinates( - @JsonProperty("groupId") final String groupId, - @JsonProperty("artifactId") final String artifactId, - @JsonProperty("version") final String version - ) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonIgnore - public String asStandardCoordinates() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.versionType.asStandardVersionString(this.version)) - .toString(); - } - - @JsonIgnore - public boolean isSnapshot() { - return this.versionType.isSnapshot(); - } - - @JsonIgnore - public ArtifactCoordinates asArtifactCoordinates() { - return new ArtifactCoordinates(this.groupId, this.artifactId); - } - - @Override - public String toString() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.version) - .toString(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || this.getClass() != o.getClass()) { - return false; - } - final MavenCoordinates that = (MavenCoordinates) o; - return Objects.equals(this.groupId, that.groupId) && Objects.equals( - this.artifactId, that.artifactId) && Objects.equals(this.version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.artifactId, this.version); - } - - @Override - public int compareTo(final MavenCoordinates o) { - final var group = this.groupId.compareTo(o.groupId); - if (group != 0) { - return group; - } - final var artifact = this.artifactId.compareTo(o.artifactId); - if (artifact != 0) { - return artifact; - } - return this.mavenVersion.compareTo(o.mavenVersion); - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/VersionType.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/VersionType.java deleted file mode 100644 index 471c4a5c..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/VersionType.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import java.util.StringJoiner; -import java.util.regex.Pattern; - -/** - * In conjunction with {@link MavenCoordinates}, can be used to determine the - * version type of the coordinates, and whether - */ -public enum VersionType { - /** - * A timestamp based file snapshot, such as {@code 1.0.0-20210118.163210-1} - * to where it can be interpreted that the {@link #SNAPSHOT snapshot} version - * would be {@code 1.0.0-SNAPSHOT} that happened to build at date time - * {@code January 18th, 2021 at 16h32m10s} and it's the first build. - */ - TIMESTAMP_SNAPSHOT { - @Override - public boolean isSnapshot() { - return true; - } - - @Override - public String asStandardVersionString(final String version) { - final var split = version.split("-"); - final var stringJoiner = new StringJoiner("-"); - for (int i = 0; i < split.length - 2; i++) { - stringJoiner.add(split[i]); - } - - return stringJoiner.add(SNAPSHOT_VERSION).toString(); - } - }, - - /** - * A standard generic snapshot relative version of a release, such as {@code 1.0.0-SNAPSHOT}. - */ - SNAPSHOT { - @Override - public boolean isSnapshot() { - return true; - } - }, - - /** - * A standard release version not abiding by any snapshot guidelines, considered - * final and singular, such as {@code 1.0.0} - */ - RELEASE; - - /* - Simple SNAPSHOT placeholder - */ - private static final String SNAPSHOT_VERSION = "SNAPSHOT"; - - /* - Verifies the pattern that the snapshot version is date.time-build formatted, - enables the pattern match for a timestamped snapshot - */ - private static final Pattern VERSION_FILE_PATTERN = Pattern.compile("^(.*)-(\\d{8}.\\d{6})-(\\d+)$"); - - private static final Pattern TIMESTAMP_TO_REPLACE = Pattern.compile("(\\d{8}.\\d{6})-(\\d+)$"); - - public static VersionType fromVersion(final String version) { - if (version == null || version.isEmpty()) { - throw new IllegalArgumentException("Version cannot be empty"); - } - // Simple check to find out if the version ends with SNAPSHOT. - if (version.regionMatches( - true, - version.length() - SNAPSHOT_VERSION.length(), - SNAPSHOT_VERSION, - 0, - SNAPSHOT_VERSION.length() - )) { - return SNAPSHOT; - } - if (VERSION_FILE_PATTERN.matcher(version).matches()) { - return TIMESTAMP_SNAPSHOT; - } - return RELEASE; - } - - public boolean isSnapshot() { - return false; - } - - public String asStandardVersionString(final String version) { - return version; - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/ArtifactUpdate.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/ArtifactUpdate.java deleted file mode 100644 index 828ea500..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/ArtifactUpdate.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.event; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(ArtifactUpdate.ArtifactRegistered.class), - @JsonSubTypes.Type(ArtifactUpdate.GitRepositoryAssociated.class), - @JsonSubTypes.Type(ArtifactUpdate.WebsiteUpdated.class), - @JsonSubTypes.Type(ArtifactUpdate.IssuesUpdated.class), - @JsonSubTypes.Type(ArtifactUpdate.DisplayNameUpdated.class), -}) -public interface ArtifactUpdate extends Jsonable { - - ArtifactCoordinates coordinates(); - - default String partitionKey() { - return this.coordinates().asMavenString(); - } - - @JsonTypeName("registered") - @JsonDeserialize - final record ArtifactRegistered( - ArtifactCoordinates coordinates - ) implements ArtifactUpdate { - - @JsonCreator - public ArtifactRegistered { - } - } - - @JsonTypeName("git-repository") - @JsonDeserialize - final record GitRepositoryAssociated( - ArtifactCoordinates coordinates, - String repository - ) implements ArtifactUpdate { - - @JsonCreator - public GitRepositoryAssociated { - } - } - - @JsonTypeName("website") - @JsonDeserialize - final record WebsiteUpdated( - ArtifactCoordinates coordinates, - String url - ) implements ArtifactUpdate { - - @JsonCreator - public WebsiteUpdated { - } - } - - @JsonTypeName("issues") - @JsonDeserialize - final record IssuesUpdated( - ArtifactCoordinates coordinates, - String url - ) implements ArtifactUpdate { - - @JsonCreator - public IssuesUpdated { - } - } - - @JsonTypeName("displayName") - @JsonDeserialize - final record DisplayNameUpdated( - ArtifactCoordinates coordinates, - String displayName - ) implements ArtifactUpdate { - - @JsonCreator - public DisplayNameUpdated { - } - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/GroupUpdate.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/GroupUpdate.java deleted file mode 100644 index 970a873e..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/event/GroupUpdate.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.event; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -import java.io.Serial; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(GroupUpdate.GroupRegistered.class), - @JsonSubTypes.Type(GroupUpdate.ArtifactRegistered.class), -}) -public interface GroupUpdate extends Jsonable { - - String groupId(); - - @JsonTypeName("group-registered") - @JsonDeserialize - record GroupRegistered(String groupId, String name, String website) - implements GroupUpdate { - - @JsonCreator - public GroupRegistered { - } - - } - - @JsonTypeName("artifact-registered") - @JsonDeserialize - final record ArtifactRegistered(ArtifactCoordinates coordinates) implements GroupUpdate { - - @Serial private static final long serialVersionUID = 6319289932327553919L; - - @JsonCreator - public ArtifactRegistered { - } - - - @Override - public String groupId() { - return this.coordinates.groupId(); - } - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java deleted file mode 100644 index 53c9567f..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.javadsl.api.transport.BadRequest; -import io.vavr.control.Either; -import io.vavr.control.Try; - -import java.net.URL; - -public final class ArtifactDetails { - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Update.Website.class, - name = "website"), - @JsonSubTypes.Type(value = Update.DisplayName.class, - name = "displayName"), - @JsonSubTypes.Type(value = Update.Issues.class, - name = "issues"), - @JsonSubTypes.Type(value = Update.GitRepository.class, - name = "gitRepository"), - }) - @JsonDeserialize - public sealed interface Update { - - Either validate(); - - record Website( - @JsonProperty(required = true) String website - ) implements Update { - - @JsonCreator - public Website { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.website())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.website()))); - } - } - - record DisplayName( - @JsonProperty(required = true) String display - ) implements Update { - - @JsonCreator - public DisplayName { - } - - @Override - public Either validate() { - return Either.right(this.display.trim()); - } - } - - record Issues( - @JsonProperty(required = true) String issues - ) implements Update { - @JsonCreator - public Issues { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.issues())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.issues()))); - } - } - - record GitRepository( - @JsonProperty(required = true) String gitRepo - ) implements Update { - - @JsonCreator - public GitRepository { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.gitRepo())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.gitRepo()))); - } - } - } - - @JsonSerialize - public record Response( - String name, - String displayName, - String website, - String issues, - String gitRepo - ) { - - } - - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java deleted file mode 100644 index 362cdd88..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -public final class ArtifactRegistration { - - @JsonSerialize - public record RegisterArtifact( - @JsonProperty(required = true) String artifactId, - @JsonProperty(required = true) String displayName - ) { - - @JsonCreator - public RegisterArtifact(final String artifactId, final String displayName) { - this.artifactId = artifactId; - this.displayName = displayName; - } - - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Response.GroupMissing.class, - name = "UnknownGroup"), - @JsonSubTypes.Type(value = Response.ArtifactRegistered.class, - name = "RegisteredArtifact"), - @JsonSubTypes.Type(value = Response.ArtifactAlreadyRegistered.class, - name = "AlreadyRegistered"), - }) - public sealed interface Response extends Jsonable { - - @JsonSerialize - record ArtifactRegistered(@JsonProperty ArtifactCoordinates coordinates) implements Response { - - } - - @JsonSerialize - record ArtifactAlreadyRegistered( - @JsonProperty String artifactName, - @JsonProperty String groupId - ) implements Response { - - } - - @JsonSerialize - record GroupMissing(@JsonProperty("groupId") String s) implements Response { - - } - - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GetArtifactsResponse.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GetArtifactsResponse.java deleted file mode 100644 index eec64a44..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GetArtifactsResponse.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GetArtifactsResponse.GroupMissing.class, name = "UnknownGroup"), - @JsonSubTypes.Type(value = GetArtifactsResponse.ArtifactsAvailable.class, name = "Artifacts"), -}) -public sealed interface GetArtifactsResponse extends Jsonable { - - @JsonSerialize - record GroupMissing(@JsonProperty String groupRequested) implements GetArtifactsResponse { - - @JsonCreator - public GroupMissing { - } - - } - - @JsonSerialize - record ArtifactsAvailable(@JsonProperty List artifactIds) - implements GetArtifactsResponse { - - @JsonCreator - public ArtifactsAvailable { - } - - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java deleted file mode 100644 index b9d21ab4..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.Group; - -public final class GroupRegistration { - - @JsonDeserialize - public record RegisterGroupRequest( - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String website - ) { - - @JsonCreator - public RegisterGroupRequest { } - - } - - public interface Response extends Jsonable { - - record GroupAlreadyRegistered(String groupNameRequested) implements Response { - } - - record GroupRegistered(Group group) implements Response { - - } - } -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java deleted file mode 100644 index a973e23f..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.Group; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GroupResponse.Missing.class, name = "MissingGroup"), - @JsonSubTypes.Type(value = GroupResponse.Available.class, name = "Group") -}) -public sealed interface GroupResponse extends Jsonable { - - @JsonSerialize - record Missing(@JsonProperty String groupId) implements GroupResponse { - @JsonCreator - public Missing(final String groupId) { - this.groupId = groupId; - } - - } - - @JsonSerialize - record Available(@JsonProperty Group group) implements GroupResponse { - - @JsonCreator - public Available(final Group group) { - this.group = group; - } - - } - -} diff --git a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupsResponse.java b/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupsResponse.java deleted file mode 100644 index 78416658..00000000 --- a/artifact-api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupsResponse.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.Group; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GroupsResponse.Available.class, name = "Groups") -}) -public interface GroupsResponse { - - @JsonSerialize - record Available(@JsonProperty List groups) - implements GroupsResponse { - @JsonCreator - public Available { - } - } -} diff --git a/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/ArtifactQueryService.java b/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/ArtifactQueryService.java deleted file mode 100644 index 7e002ddc..00000000 --- a/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/ArtifactQueryService.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.query.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.transport.Method; - -public interface ArtifactQueryService extends Service { - - ServiceCall getArtifactDetails(String groupId, String artifactId); - - @Override - default Descriptor descriptor() { - return Service.named("artifact-query") - .withCalls( - Service.restCall(Method.GET, "/artifacts-query/groups/:groupId/artifacts/:artifactId", this::getArtifactDetails) - ) - .withAutoAcl(true); - } -} diff --git a/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/GetArtifactDetailsResponse.java b/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/GetArtifactDetailsResponse.java deleted file mode 100644 index 5113f526..00000000 --- a/artifact-query-api/src/main/java/org/spongepowered/downloads/artifacts/query/api/GetArtifactDetailsResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.query.api; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.Map; -import io.vavr.collection.SortedSet; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GetArtifactDetailsResponse.RetrievedArtifact.class, - name = "latest") -}) -public sealed interface GetArtifactDetailsResponse { - - @JsonSerialize - record RetrievedArtifact( - ArtifactCoordinates coordinates, - String displayName, - String website, - String gitRepository, - String issues, - Map> tags - ) implements GetArtifactDetailsResponse { - - } -} diff --git a/artifacts/README.md b/artifacts/README.md deleted file mode 100644 index 47a547a0..00000000 --- a/artifacts/README.md +++ /dev/null @@ -1,87 +0,0 @@ -## Micronaut 3.8.2 Documentation - -- [User Guide](https://docs.micronaut.io/3.8.2/guide/index.html) -- [API Reference](https://docs.micronaut.io/3.8.2/api/index.html) -- [Configuration Reference](https://docs.micronaut.io/3.8.2/guide/configurationreference.html) -- [Micronaut Guides](https://guides.micronaut.io/index.html) ---- - -- [Shadow Gradle Plugin](https://plugins.gradle.org/plugin/com.github.johnrengelman.shadow) -## Feature github-workflow-ci documentation - -- [https://docs.github.com/en/actions](https://docs.github.com/en/actions) - - -## Feature test-resources documentation - -- [Micronaut Test Resources documentation](https://micronaut-projects.github.io/micronaut-test-resources/latest/guide/) - - -## Feature micronaut-aot documentation - -- [Micronaut AOT documentation](https://micronaut-projects.github.io/micronaut-aot/latest/guide/) - - -## Feature security-ldap documentation - -- [Micronaut Security LDAP documentation](https://micronaut-projects.github.io/micronaut-security/latest/guide/index.html#ldap) - - -## Feature security-jwt documentation - -- [Micronaut Security JWT documentation](https://micronaut-projects.github.io/micronaut-security/latest/guide/index.html) - - -## Feature data-r2dbc documentation - -- [Micronaut Data R2DBC documentation](https://micronaut-projects.github.io/micronaut-data/latest/guide/#dbc) - -- [https://r2dbc.io](https://r2dbc.io) - - -## Feature kafka documentation - -- [Micronaut Kafka Messaging documentation](https://micronaut-projects.github.io/micronaut-kafka/latest/guide/index.html) - - -## Feature openapi documentation - -- [Micronaut OpenAPI Support documentation](https://micronaut-projects.github.io/micronaut-openapi/latest/guide/index.html) - -- [https://www.openapis.org](https://www.openapis.org) - - -## Feature cache-caffeine documentation - -- [Micronaut Caffeine Cache documentation](https://micronaut-projects.github.io/micronaut-cache/latest/guide/index.html) - -- [https://github.com/ben-manes/caffeine](https://github.com/ben-manes/caffeine) - - -## Feature r2dbc documentation - -- [Micronaut R2DBC documentation](https://micronaut-projects.github.io/micronaut-r2dbc/latest/guide/) - -- [https://r2dbc.io](https://r2dbc.io) - - -## Feature junit-params documentation - -- [https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests](https://junit.org/junit5/docs/current/user-guide/#writing-tests-parameterized-tests) - - -## Feature discovery-kubernetes documentation - -- [Micronaut Kubernetes Service Discovery documentation](https://micronaut-projects.github.io/micronaut-kubernetes/latest/guide/#service-discovery) - - -## Feature http-client documentation - -- [Micronaut HTTP Client documentation](https://docs.micronaut.io/latest/guide/index.html#httpClient) - - -## Feature serialization-jackson documentation - -- [Micronaut Serialization Jackson Core documentation](https://micronaut-projects.github.io/micronaut-serialization/latest/guide/) - - diff --git a/artifacts/api/build.gradle.kts b/artifacts/api/build.gradle.kts index 399e5cbf..23911298 100644 --- a/artifacts/api/build.gradle.kts +++ b/artifacts/api/build.gradle.kts @@ -1,9 +1,15 @@ -val jacksonVersion:String by project -dependencies { - api(platform("com.fasterxml.jackson:jackson-bom:${jacksonVersion}")) - api("com.fasterxml.jackson:jackson-core") - api("com.fasterxml.jackson.core:jackson-databind") - api("com.fasterxml.jackson.core:jackson-annotations") +version = "0.1" +group = "org.spongepowered.downloads" + +plugins { + `java-library` +} + +dependencies { + api(platform(libs.jacksonBom)) + api(libs.bundles.serder) + api(libs.maven) + api(libs.vavr) } diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java index 99131c11..4bfc99db 100644 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java +++ b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactCoordinates.java @@ -30,6 +30,17 @@ import java.util.StringJoiner; +/** + * Representation of a simplified {@link MavenCoordinates} with representation + * of only the {@link #groupId()} and {@link #artifactId()}. In general, this is + * to represent an artifact as a whole, rather than any specific version or + * variant. + * + * @param groupId The group id of an artifact, as defined by the Apache Maven documentation. + * See Maven Coordinates. + * @param artifactId The artifact id of an artifact, as defined by the Apache Maven documentation. + * See Maven Coordinates. + */ @JsonDeserialize public record ArtifactCoordinates( @JsonProperty(required = true) String groupId, @@ -48,19 +59,4 @@ public String asMavenString() { return this.groupId() + ":" + this.artifactId(); } - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String groupId() { - return groupId; - } - - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String artifactId() { - return artifactId; - } } diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java deleted file mode 100644 index 00508fc5..00000000 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/ArtifactService.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifact.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.broker.Topic; -import com.lightbend.lagom.javadsl.api.broker.kafka.KafkaProperties; -import com.lightbend.lagom.javadsl.api.transport.Method; -import org.spongepowered.downloads.artifact.api.event.ArtifactUpdate; -import org.spongepowered.downloads.artifact.api.event.GroupUpdate; -import org.spongepowered.downloads.artifact.api.query.ArtifactDetails; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; - -public interface ArtifactService extends Service { - - ServiceCall getArtifacts(String groupId); - - ServiceCall registerArtifacts( - String groupId - ); - - ServiceCall registerGroup(); - - ServiceCall, ArtifactDetails.Response> updateDetails(String groupId, String artifactId); - - ServiceCall getGroup(String groupId); - - ServiceCall getGroups(); - - Topic groupTopic(); - - Topic artifactUpdate(); - - @Override - default Descriptor descriptor() { - return Service.named("artifacts") - .withCalls( - Service.restCall(Method.GET, "/artifacts/groups/:groupId", this::getGroup), - Service.restCall(Method.GET, "/artifacts/groups", this::getGroups), - Service.restCall(Method.POST, "/artifacts/groups", this::registerGroup), - Service.restCall(Method.GET, "/artifacts/groups/:groupId/artifacts", this::getArtifacts), - Service.restCall(Method.POST, "/artifacts/groups/:groupId/artifacts", this::registerArtifacts), - Service.restCall(Method.PATCH, "/artifacts/groups/:groupId/artifacts/:artifactId/update", this::updateDetails) - ) - .withTopics( - Service.topic("group-activity", this::groupTopic) - .withProperty(KafkaProperties.partitionKeyStrategy(), GroupUpdate::groupId), - Service.topic("artifact-details-update", this::artifactUpdate) - .withProperty(KafkaProperties.partitionKeyStrategy(), ArtifactUpdate::partitionKey) - ) - .withAutoAcl(true); - } - -} diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java index 27247571..56e6a717 100644 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java +++ b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactDetails.java @@ -26,7 +26,6 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; @@ -86,15 +85,22 @@ record GitRepository( } @JsonSerialize - public record Response( - String name, - String displayName, - String website, - String issues, - String gitRepo - ) { + @JsonTypeInfo(use = JsonTypeInfo.Id.NONE) + public sealed interface Response { + record Ok( + String name, + String displayName, + String website, + String issues, + String gitRepo + ) implements Response{ + + } + + record NotFound(String message) implements Response {} } + } diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java index 362cdd88..d7f0cc82 100644 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java +++ b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/ArtifactRegistration.java @@ -29,7 +29,6 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; public final class ArtifactRegistration { @@ -58,7 +57,7 @@ public RegisterArtifact(final String artifactId, final String displayName) { @JsonSubTypes.Type(value = Response.ArtifactAlreadyRegistered.class, name = "AlreadyRegistered"), }) - public sealed interface Response extends Jsonable { + public sealed interface Response { @JsonSerialize record ArtifactRegistered(@JsonProperty ArtifactCoordinates coordinates) implements Response { diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java index 040345fb..b127e321 100644 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java +++ b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupRegistration.java @@ -32,18 +32,19 @@ public final class GroupRegistration { @JsonDeserialize - public record RegisterGroupRequest( - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String website + public record RegisterGroupRequest( + @JsonProperty(required = true) String name, + @JsonProperty(required = true) String groupCoordinates, + @JsonProperty(required = true) String website ) { - @JsonCreator - public RegisterGroupRequest { } - + @JsonCreator + public RegisterGroupRequest { } - public interface Response { + } + + public sealed interface Response { record GroupAlreadyRegistered(String groupNameRequested) implements Response { } diff --git a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java index a973e23f..628fc841 100644 --- a/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java +++ b/artifacts/api/src/main/java/org/spongepowered/downloads/artifact/api/query/GroupResponse.java @@ -28,16 +28,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; import org.spongepowered.downloads.artifact.api.Group; -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GroupResponse.Missing.class, name = "MissingGroup"), - @JsonSubTypes.Type(value = GroupResponse.Available.class, name = "Group") -}) -public sealed interface GroupResponse extends Jsonable { +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) +public sealed interface GroupResponse { @JsonSerialize record Missing(@JsonProperty String groupId) implements GroupResponse { diff --git a/artifacts/build.gradle.kts b/artifacts/build.gradle.kts index 8c40ab6c..b28b04f6 100644 --- a/artifacts/build.gradle.kts +++ b/artifacts/build.gradle.kts @@ -1,18 +1,3 @@ -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project - -subprojects { - dependencies { - implementation(project(":akka")) - } -} -dependencies { - -} - - diff --git a/artifacts/events/build.gradle.kts b/artifacts/events/build.gradle.kts index 61ccd4eb..d2abb374 100644 --- a/artifacts/events/build.gradle.kts +++ b/artifacts/events/build.gradle.kts @@ -1,12 +1,17 @@ -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project +plugins { + `java-library` +} + +java { + sourceCompatibility = JavaVersion.toVersion("20") + targetCompatibility = JavaVersion.toVersion("20") +} dependencies { api(project(":artifacts:api")) + api(project(":akka")) } diff --git a/akka/gradle.properties b/artifacts/events/gradle.properties similarity index 100% rename from akka/gradle.properties rename to artifacts/events/gradle.properties diff --git a/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/ArtifactEvent.java b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/ArtifactEvent.java index f2fe681c..7afa5009 100644 --- a/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/ArtifactEvent.java +++ b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/ArtifactEvent.java @@ -1,11 +1,14 @@ package org.spongepowered.downloads.artifacts.events; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.spongepowered.downloads.akka.AkkaSerializable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -public sealed interface ArtifactEvent { +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") +public sealed interface ArtifactEvent extends AkkaSerializable { ArtifactCoordinates coordinates(); diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsEvent.java b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/DetailsEvent.java similarity index 70% rename from artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsEvent.java rename to artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/DetailsEvent.java index 80886e24..eadddd99 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsEvent.java +++ b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/DetailsEvent.java @@ -22,37 +22,21 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.downloads.artifacts.server.details; +package org.spongepowered.downloads.artifacts.events; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; +import org.spongepowered.downloads.akka.AkkaSerializable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = DetailsEvent.ArtifactRegistered.class, name = "registered"), - @JsonSubTypes.Type(value = DetailsEvent.ArtifactDetailsUpdated.class, name = "details"), - @JsonSubTypes.Type(value = DetailsEvent.ArtifactIssuesUpdated.class, name = "issues"), - @JsonSubTypes.Type(value = DetailsEvent.ArtifactGitRepositoryUpdated.class, name = "git-repo"), - @JsonSubTypes.Type(value = DetailsEvent.ArtifactWebsiteUpdated.class, name = "website"), -}) -public interface DetailsEvent extends AggregateEvent, Jsonable { - - AggregateEventShards TAG = AggregateEventTag.sharded(DetailsEvent.class, 3); - - @Override - default AggregateEventTagger aggregateTag() { - return TAG; - } +public interface DetailsEvent extends AkkaSerializable { @JsonDeserialize + @JsonTypeName("registered") record ArtifactRegistered( ArtifactCoordinates coordinates ) implements DetailsEvent { @@ -62,6 +46,7 @@ record ArtifactRegistered( } @JsonDeserialize + @JsonTypeName("details") record ArtifactDetailsUpdated( ArtifactCoordinates coordinates, String displayName @@ -73,6 +58,7 @@ record ArtifactDetailsUpdated( } @JsonDeserialize + @JsonTypeName("issues") record ArtifactIssuesUpdated( ArtifactCoordinates coordinates, String url @@ -84,6 +70,7 @@ record ArtifactIssuesUpdated( } @JsonDeserialize + @JsonTypeName("git-repo") record ArtifactGitRepositoryUpdated( ArtifactCoordinates coordinates, String gitRepo @@ -95,6 +82,7 @@ record ArtifactGitRepositoryUpdated( } @JsonDeserialize + @JsonTypeName("website") record ArtifactWebsiteUpdated( ArtifactCoordinates coordinates, String url diff --git a/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/GroupUpdate.java b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/GroupUpdate.java index fd838b95..9f4253bc 100644 --- a/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/GroupUpdate.java +++ b/artifacts/events/src/main/java/org/spongepowered/downloads/artifacts/events/GroupUpdate.java @@ -25,20 +25,14 @@ package org.spongepowered.downloads.artifacts.events; import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import org.spongepowered.downloads.akka.AkkaSerializable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import java.io.Serial; - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(GroupUpdate.GroupRegistered.class), - @JsonSubTypes.Type(GroupUpdate.ArtifactRegistered.class), -}) -public interface GroupUpdate { +public sealed interface GroupUpdate extends AkkaSerializable { String groupId(); @@ -57,8 +51,6 @@ record GroupRegistered(String groupId, String name, String website) @JsonDeserialize final record ArtifactRegistered(ArtifactCoordinates coordinates) implements GroupUpdate { - @Serial private static final long serialVersionUID = 6319289932327553919L; - @JsonCreator public ArtifactRegistered { } diff --git a/artifacts/gradle.properties b/artifacts/gradle.properties deleted file mode 100644 index a9d280a0..00000000 --- a/artifacts/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -micronautVersion=3.8.2 diff --git a/artifacts/gradle/wrapper/gradle-wrapper.properties b/artifacts/gradle/wrapper/gradle-wrapper.properties index ae04661e..fae08049 100644 --- a/artifacts/gradle/wrapper/gradle-wrapper.properties +++ b/artifacts/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/artifacts/micronaut-cli.yml b/artifacts/micronaut-cli.yml deleted file mode 100644 index dc960e1f..00000000 --- a/artifacts/micronaut-cli.yml +++ /dev/null @@ -1,6 +0,0 @@ -applicationType: default -defaultPackage: org.spongepowered.downloads.artifacts -testFramework: junit -sourceLanguage: java -buildTool: gradle -features: [annotation-api, app-name, cache-caffeine, data, data-r2dbc, discovery-kubernetes, github-workflow-ci, graalvm, gradle, h2, http-client, java, java-application, junit, junit-params, kafka, logback, micronaut-aot, micronaut-build, netty-server, openapi, r2dbc, readme, security-annotations, security-jwt, security-ldap, serialization-jackson, shade, test-resources, toml, toml-build] diff --git a/artifacts/server/build.gradle.kts b/artifacts/server/build.gradle.kts index 21f64ea2..48d5dc38 100644 --- a/artifacts/server/build.gradle.kts +++ b/artifacts/server/build.gradle.kts @@ -1,79 +1,81 @@ +import io.micronaut.gradle.testresources.StartTestResourcesService +import io.micronaut.testresources.buildtools.KnownModules - -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project -val vavr: String by project +plugins { + id("io.micronaut.application") + id("io.micronaut.test-resources") + id("com.github.johnrengelman.shadow") +} -tasks { - dockerBuild { - images.add("${project.name}:${project.version}") - } - dockerBuildNative { - images.add("${project.name}:${project.version}") - - } -} -graalvmNative.toolchainDetection.set(false) micronaut { + runtime("netty") testRuntime("junit5") processing { incremental(true) - annotations("systemofadownload.*") + annotations("org.spongepowered.downloads.artifacts.*") } testResources { - additionalModules.add("r2dbc-postgresql") + enabled.set(true) + sharedServer.set(true) + additionalModules.addAll(KnownModules.R2DBC_POSTGRESQL) } } -graalvmNative { - binaries { - named("main") { - imageName.set("mn-graalvm-application") - buildArgs("--verboase") - } + +tasks { + test { + useJUnitPlatform() } } +tasks.withType().configureEach { + useClassDataSharing.set(false) +} + +graalvmNative.toolchainDetection.set(false) dependencies { implementation(project(":artifacts:api")) - implementation("io.vavr:vavr:${vavr}") + implementation(project(":artifacts:events")) + implementation(project(":akka")) + implementation(libs.vavr) annotationProcessor("io.micronaut.data:micronaut-data-processor") - annotationProcessor("io.micronaut:micronaut-http-validation") - annotationProcessor("io.micronaut.openapi:micronaut-openapi") - annotationProcessor("io.micronaut.security:micronaut-security-annotations") + annotationProcessor("io.micronaut.validation:micronaut-validation-processor") annotationProcessor("io.micronaut.serde:micronaut-serde-processor") - implementation("com.ongres.scram:client:2.1") - implementation("io.micronaut:micronaut-http-client") implementation("io.micronaut:micronaut-jackson-databind") - implementation("io.micronaut.data:micronaut-data-r2dbc") - implementation("io.micronaut.liquibase:micronaut-liquibase") - implementation("io.micronaut.reactor:micronaut-reactor") - implementation("io.micronaut.reactor:micronaut-reactor-http-client") - implementation("io.micronaut.security:micronaut-security-ldap") implementation("io.micronaut.serde:micronaut-serde-jackson") - implementation("io.micronaut.toml:micronaut-toml") - implementation("io.micronaut.xml:micronaut-jackson-xml") - implementation("io.swagger.core.v3:swagger-annotations") + implementation("io.micronaut:micronaut-http-server-netty") + + runtimeOnly("org.yaml:snakeyaml") + + implementation(libs.bundles.appSerder) + implementation(libs.bundles.akkaManagement) + implementation(libs.bundles.actorsPersistence) + + // databases + implementation("io.micronaut.data:micronaut-data-r2dbc") +// implementation("io.micronaut.sql:micronaut-vertx-pg-client") +// implementation("io.micronaut.sql:micronaut-hibernate-reactive") implementation("io.vertx:vertx-pg-client") - implementation("jakarta.annotation:jakarta.annotation-api") - implementation(platform("com.typesafe.akka:akka-bom_${scalaVersion}:${akkaVersion}")) - implementation("com.typesafe.akka:akka-actor-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-persistence-typed_${scalaVersion}") - implementation("com.lightbend.akka:akka-projection-core_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-sharding-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.lightbend.akka.management:akka-management_${scalaVersion}:${akkaManagementVersion}") - implementation("com.lightbend.akka.management:akka-management-cluster-bootstrap_${scalaVersion}:${akkaManagementVersion}") - - runtimeOnly("ch.qos.logback:logback-classic") + runtimeOnly(libs.postgres.r2dbc) runtimeOnly("org.postgresql:postgresql") - runtimeOnly("org.postgresql:r2dbc-postgresql") + + + implementation("io.micronaut.liquibase:micronaut-liquibase") + implementation("io.micronaut:micronaut-http-client-jdk") + testImplementation("io.micronaut.testresources:micronaut-test-resources-extensions-junit-platform") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("io.micronaut.test:micronaut-test-junit5") + testImplementation(project(":akka:testkit")) + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testResourcesService("org.postgresql:postgresql") compileOnly("org.graalvm.nativeimage:svm") - implementation("io.micronaut:micronaut-validation") + + testImplementation("org.junit.jupiter:junit-jupiter-engine") + + +//// compileOnly("org.graalvm.nativeimage:svm") +// +// implementation("io.micronaut:micronaut-validation") } diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java index 96a9adb2..bd12b8ba 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java @@ -3,34 +3,23 @@ import akka.actor.typed.ActorSystem; import akka.actor.typed.SpawnProtocol; import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import io.micronaut.context.annotation.Bean; import io.micronaut.context.annotation.Factory; -import io.micronaut.context.event.ApplicationEventListener; import io.micronaut.runtime.Micronaut; import io.micronaut.runtime.event.annotation.EventListener; import io.micronaut.runtime.server.event.ServerStartupEvent; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.info.*; import jakarta.inject.Inject; import jakarta.inject.Singleton; -import org.spongepowered.downloads.artifacts.server.global.GlobalManager; -@OpenAPIDefinition( - info = @Info( - title = "artifacts", - version = "0.0" - ) -) @Singleton @Factory public class Application { - private final ActorSystem system; + private final ActorSystem system; private final ClusterSharding sharding; @Inject public Application( - final ActorSystem system, + final ActorSystem system, final ClusterSharding sharding ) { this.system = system; @@ -43,6 +32,5 @@ public static void main(String[] args) { @EventListener public void onApplicationEvent(final ServerStartupEvent event) { - } } diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/ArtifactDetailsEntity.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/ArtifactDetailsEntity.java index 9f014a26..f6462d9a 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/ArtifactDetailsEntity.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/ArtifactDetailsEntity.java @@ -34,27 +34,23 @@ import akka.persistence.typed.javadsl.CommandHandlerWithReply; import akka.persistence.typed.javadsl.EventHandler; import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import com.lightbend.lagom.javadsl.api.transport.NotFound; -import com.lightbend.lagom.javadsl.persistence.AkkaTaggerAdapter; -import io.vavr.control.Either; +import io.micronaut.http.HttpResponse; import org.spongepowered.downloads.artifact.api.query.ArtifactDetails; -import org.spongepowered.downloads.artifact.details.state.DetailsState; -import org.spongepowered.downloads.artifact.details.state.EmptyState; -import org.spongepowered.downloads.artifact.details.state.PopulatedState; +import org.spongepowered.downloads.artifacts.events.DetailsEvent; +import org.spongepowered.downloads.artifacts.server.details.state.DetailsState; +import org.spongepowered.downloads.artifacts.server.details.state.EmptyState; +import org.spongepowered.downloads.artifacts.server.details.state.PopulatedState; import java.util.List; -import java.util.Set; -import java.util.function.Function; public class ArtifactDetailsEntity extends EventSourcedBehaviorWithEnforcedReplies { - private static final Either NOT_FOUND = Either.left( - new NotFound("group or artifact not found")); + + private static final HttpResponse NOT_FOUND = HttpResponse.notFound(new ArtifactDetails.Response.NotFound("group or artifact not found")); public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create( DetailsCommand.class, "DetailsEntity"); private final String artifactId; private final ActorContext ctx; - private final Function> tagger; private ArtifactDetailsEntity( ActorContext ctx, @@ -64,7 +60,6 @@ private ArtifactDetailsEntity( super(persistenceId); this.artifactId = entityId; this.ctx = ctx; - this.tagger = AkkaTaggerAdapter.fromLagom(context, DetailsEvent.TAG); } public static Behavior create( @@ -140,15 +135,13 @@ public CommandHandlerWithReply comma .persist(new DetailsEvent.ArtifactIssuesUpdated(s.coordinates(), cmd.validUrl().toString())) .thenReply( cmd.replyTo(), - us -> Either.right( - new ArtifactDetails.Response( - us.coordinates().artifactId(), - us.displayName(), - us.website(), - us.issues(), - us.gitRepository() - ) - ) + us -> HttpResponse.ok(new ArtifactDetails.Response.Ok( + us.coordinates().artifactId(), + us.displayName(), + us.website(), + us.issues(), + us.gitRepository() + )) ) ) .onCommand( @@ -157,15 +150,13 @@ public CommandHandlerWithReply comma .persist(new DetailsEvent.ArtifactIssuesUpdated(s.coordinates(), cmd.website().toString())) .thenReply( cmd.replyTo(), - us -> Either.right( - new ArtifactDetails.Response( - us.coordinates().artifactId(), - us.displayName(), - us.website(), - us.issues(), - us.gitRepository() - ) - ) + us -> HttpResponse.ok(new ArtifactDetails.Response.Ok( + us.coordinates().artifactId(), + us.displayName(), + us.website(), + us.issues(), + us.gitRepository() + )) ) ) .onCommand( @@ -174,15 +165,13 @@ public CommandHandlerWithReply comma .persist(new DetailsEvent.ArtifactIssuesUpdated(s.coordinates(), cmd.displayName())) .thenReply( cmd.replyTo(), - us -> Either.right( - new ArtifactDetails.Response( - us.coordinates().artifactId(), - us.displayName(), - us.website(), - us.issues(), - us.gitRepository() - ) - ) + us -> HttpResponse.ok(new ArtifactDetails.Response.Ok( + us.coordinates().artifactId(), + us.displayName(), + us.website(), + us.issues(), + us.gitRepository() + )) ) ) .onCommand( @@ -191,23 +180,17 @@ public CommandHandlerWithReply comma .persist(new DetailsEvent.ArtifactGitRepositoryUpdated(s.coordinates(), cmd.gitRemote())) .thenReply( cmd.replyTo(), - us -> Either.right( - new ArtifactDetails.Response( - us.coordinates().artifactId(), - us.displayName(), - us.website(), - us.issues(), - us.gitRepository() - ) - ) + us -> HttpResponse.ok(new ArtifactDetails.Response.Ok( + us.coordinates().artifactId(), + us.displayName(), + us.website(), + us.issues(), + us.gitRepository() + )) ) ); return builder.build(); } - @Override - public Set tagsFor(final DetailsEvent detailsEvent) { - return this.tagger.apply(detailsEvent); - } } diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsCommand.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsCommand.java index bcb8d442..04027110 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsCommand.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsCommand.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import io.micronaut.http.HttpResponse; import io.vavr.control.Either; +import org.spongepowered.downloads.akka.AkkaSerializable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; import org.spongepowered.downloads.artifact.api.query.ArtifactDetails; @@ -52,7 +53,7 @@ @JsonSubTypes.Type(value = DetailsCommand.UpdateDisplayName.class, name = "display-name") }) -public interface DetailsCommand { +public interface DetailsCommand extends AkkaSerializable { @JsonDeserialize final record RegisterArtifact(ArtifactCoordinates coordinates, diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsManager.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsManager.java deleted file mode 100644 index e851dbca..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/DetailsManager.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.details; - -import akka.NotUsed; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.sharding.typed.javadsl.EntityRef; -import akka.persistence.typed.PersistenceId; -import io.micronaut.http.HttpResponse; -import io.vavr.control.Either; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.InvalidRemoteException; -import org.eclipse.jgit.lib.Ref; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.query.ArtifactDetails; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; - -import java.net.URL; -import java.time.Duration; -import java.util.Collection; -import java.util.concurrent.CompletionStage; - -@Singleton -public class DetailsManager { - private final ClusterSharding clusterSharding; - private final Duration askTimeout = Duration.ofHours(5); - - @Inject - public DetailsManager(final ClusterSharding clusterSharding) { - this.clusterSharding = clusterSharding; - this.clusterSharding.init( - Entity.of( - ArtifactDetailsEntity.ENTITY_TYPE_KEY, - context -> ArtifactDetailsEntity.create( - context, - context.getEntityId(), - PersistenceId.of(context.getEntityTypeKey().name(), context.getEntityId()) - ) - ) - ); - } - - public CompletionStage UpdateWebsite( - ArtifactCoordinates coords, ArtifactDetails.Update.Website w - ) { - - final Either validate = w.validate(); - if (validate.isLeft()) { - throw validate.getLeft(); - } - final var validUrl = validate.get(); - return this.getDetailsEntity(coords) - .>ask( - r -> new DetailsCommand.UpdateWebsite(coords, validUrl, r), this.askTimeout) - .thenApply(Either::get); - } - - - public CompletionStage UpdateDisplayName( - ArtifactCoordinates coords, ArtifactDetails.Update.DisplayName d - ) { - final Either validate = d.validate(); - if (validate.isLeft()) { - throw validate.getLeft(); - } - final var displayName = validate.get(); - return this.getDetailsEntity(coords) - .>ask( - r -> new DetailsCommand.UpdateDisplayName(coords, displayName, r), - this.askTimeout - ) - .thenApply(Either::get); - } - - public CompletionStage UpdateGitRepository( - final ArtifactCoordinates coords, ArtifactDetails.Update.GitRepository gr - ) { - final Either validate = gr.validate(); - if (validate.isLeft()) { - throw validate.getLeft(); - } - final Collection refs; - try { - refs = Git.lsRemoteRepository() - .setRemote(gr.gitRepo()) - .call(); - } catch (InvalidRemoteException e) { - throw new BadRequest(String.format("Invalid remote: %s", gr.gitRepo())); - } catch (GitAPIException e) { - throw new BadRequest(String.format("Error resolving repository '%s'", gr.gitRepo())); - } - if (refs.isEmpty()) { - throw new BadRequest(String.format("Remote repository '%s' has no refs", gr.gitRepo())); - } - - return this.getDetailsEntity(coords) - .>ask( - r -> new DetailsCommand.UpdateGitRepository(coords, gr.gitRepo(), r), this.askTimeout) - .thenApply(Either::get); - } - - public CompletionStage UpdateIssues( - ArtifactCoordinates coords, ArtifactDetails.Update.Issues i - ) { - final Either validate = i.validate(); - if (validate.isLeft()) { - throw validate.getLeft(); - } - final var validUrl = validate.get(); - return this.getDetailsEntity(coords) - .>ask( - r -> new DetailsCommand.UpdateIssues(coords, validUrl, r), this.askTimeout).thenApply( - Either::get); - } - - private EntityRef getDetailsEntity(final ArtifactCoordinates coordinates) { - return this.getDetailsEntity(coordinates.groupId(), coordinates.artifactId()); - } - - private EntityRef getDetailsEntity(final String groupId, final String artifactId) { - return this.clusterSharding.entityRefFor(ArtifactDetailsEntity.ENTITY_TYPE_KEY, groupId + ":" + artifactId); - } - - public CompletionStage registerArtifact( - ArtifactRegistration.Response.ArtifactRegistered registered, - String displayName - ) { - return this.getDetailsEntity(registered.coordinates()) - .ask( - replyTo -> new DetailsCommand.RegisterArtifact( - registered.coordinates(), displayName, replyTo), this.askTimeout) - .thenApply(notUsed -> registered); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/DetailsState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/DetailsState.java index 5695e90f..15d01fdd 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/DetailsState.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/DetailsState.java @@ -24,10 +24,12 @@ */ package org.spongepowered.downloads.artifacts.server.details.state; -import com.lightbend.lagom.serialization.Jsonable; +import org.spongepowered.downloads.akka.AkkaSerializable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -public interface DetailsState extends Jsonable { +public sealed interface DetailsState extends AkkaSerializable + permits EmptyState, PopulatedState { + ArtifactCoordinates coordinates(); String displayName(); diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/PopulatedState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/PopulatedState.java index e120427d..10cf2b76 100644 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/PopulatedState.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/details/state/PopulatedState.java @@ -26,14 +26,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.CompressedJsonable; import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.details.DetailsEvent; +import org.spongepowered.downloads.artifacts.events.DetailsEvent; @JsonDeserialize public record PopulatedState(ArtifactCoordinates coordinates, String displayName, String website, String gitRepository, - String issues) implements DetailsState, CompressedJsonable { + String issues) implements DetailsState { @JsonCreator public PopulatedState { diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalCommand.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalCommand.java deleted file mode 100644 index 1fcaf252..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalCommand.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.global; - -import akka.Done; -import akka.actor.typed.ActorRef; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.Group; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(name = "Get", - value = GlobalCommand.GetGroups.class) -}) -public interface GlobalCommand extends Jsonable { - - @JsonDeserialize - record GetGroups(ActorRef replyTo) - implements GlobalCommand { - - @JsonCreator - public GetGroups { - } - } - - @JsonDeserialize - record RegisterGroup( - ActorRef replyTo, Group group) implements GlobalCommand { - - @JsonCreator - public RegisterGroup { - } - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalEvent.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalEvent.java deleted file mode 100644 index d9162e8f..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.global; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.Group; - -public interface GlobalEvent extends AggregateEvent, Jsonable { - - AggregateEventTag TAG = AggregateEventTag.of(GlobalEvent.class); - - @Override - default AggregateEventTagger aggregateTag() { - return TAG; - } - - @JsonDeserialize - final class GroupRegistered implements GlobalEvent { - public final Group group; - - @JsonCreator - public GroupRegistered(Group group) { - this.group = group; - } - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalManager.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalManager.java deleted file mode 100644 index 907ad2b0..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.global; - -import akka.Done; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.sharding.typed.javadsl.EntityRef; -import akka.persistence.typed.PersistenceId; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import org.spongepowered.downloads.artifact.api.Group; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -@Singleton -public final class GlobalManager { - private final Duration askTimeout = Duration.ofHours(5); - - private final ClusterSharding clusterSharding; - - @Inject - public GlobalManager(final ClusterSharding clusterSharding) { - this.clusterSharding = clusterSharding; - this.clusterSharding.init( - Entity.of( - GlobalRegistration.ENTITY_TYPE_KEY, - ctx -> GlobalRegistration.create( - ctx.getEntityId(), - PersistenceId.of(ctx.getEntityTypeKey().name(), ctx.getEntityId()) - ) - ) - ); - } - - public CompletionStage registerGroup( - GroupRegistration.Response.GroupRegistered registered - ) { - final Group group = registered.group(); - return this.getGlobalEntity() - .ask(replyTo -> new GlobalCommand.RegisterGroup(replyTo, group), this.askTimeout) - .thenApply(notUsed -> registered); - } - - - private EntityRef getGlobalEntity() { - return this.clusterSharding.entityRefFor(GlobalRegistration.ENTITY_TYPE_KEY, "global"); - } - - public CompletionStage getGroups() { - return this.getGlobalEntity().ask(GlobalCommand.GetGroups::new, this.askTimeout); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalRegistration.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalRegistration.java deleted file mode 100644 index 52d20c76..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalRegistration.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.global; - -import akka.Done; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; - -public class GlobalRegistration - extends EventSourcedBehaviorWithEnforcedReplies { - - public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create( - GlobalCommand.class, "GlobalEntity"); - private final String groupId; - private final ActorContext ctx; - - private GlobalRegistration(ActorContext ctx, String entityId, PersistenceId persistenceId) { - super(persistenceId); - this.ctx = ctx; - this.groupId = entityId; - } - - public static Behavior create(String entityId, PersistenceId persistenceId) { - return Behaviors.setup(ctx -> new GlobalRegistration(ctx, entityId, persistenceId)); - } - - @Override - public GlobalState emptyState() { - return new GlobalState(List.empty()); - } - - @Override - public EventHandler eventHandler() { - final var builder = this.newEventHandlerBuilder(); - builder.forAnyState() - .onEvent( - GlobalEvent.GroupRegistered.class, - (state, event) -> new GlobalState(state.groups().append(event.group)) - ); - return builder.build(); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder(); - builder.forAnyState() - .onCommand( - GlobalCommand.GetGroups.class, - (state, cmd) -> this.Effect().reply(cmd.replyTo(), new GroupsResponse.Available(state.groups())) - ) - .onCommand( - GlobalCommand.RegisterGroup.class, - this::handleRegisterGroup - ); - return builder.build(); - } - - private ReplyEffect handleRegisterGroup( - GlobalState state, GlobalCommand.RegisterGroup cmd - ) { - if (!state.groups().contains(cmd.group())) { - return this.Effect().persist(new GlobalEvent.GroupRegistered(cmd.group())) - .thenReply(cmd.replyTo(), (s) -> Done.done()); - } - return this.Effect().reply(cmd.replyTo(), Done.done()); - } - -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalState.java deleted file mode 100644 index 9a26c915..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/global/GlobalState.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.global; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.Group; - -@JsonDeserialize -public record GlobalState(List groups) { - - @JsonCreator - public GlobalState { - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupCommand.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupCommand.java deleted file mode 100644 index 08c03a79..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupCommand.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups; - -import akka.actor.typed.ActorRef; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; - -public sealed interface GroupCommand { - record GetGroup( - String groupId, - ActorRef replyTo - ) implements GroupCommand { - } - - record GetArtifacts( - String groupId, - ActorRef replyTo - ) implements GroupCommand { - - } - - record RegisterArtifact( - String artifact, - ActorRef replyTo - ) implements GroupCommand { - - } - - record RegisterGroup( - String mavenCoordinates, - String name, - String website, - ActorRef replyTo - ) implements GroupCommand { - - } - -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEntity.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEntity.java deleted file mode 100644 index d2fa9f83..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEntity.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups; - -import akka.cluster.sharding.typed.javadsl.EntityContext; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.CommandHandlerWithReplyBuilder; -import akka.persistence.typed.javadsl.EffectFactories; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventHandlerBuilder; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import akka.persistence.typed.javadsl.RetentionCriteria; -import io.vavr.control.Try; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.Group; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; -import org.spongepowered.downloads.artifacts.server.groups.state.EmptyState; -import org.spongepowered.downloads.artifacts.server.groups.state.GroupState; -import org.spongepowered.downloads.artifacts.server.groups.state.PopulatedState; - -import java.net.URL; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class GroupEntity - extends EventSourcedBehaviorWithEnforcedReplies { - - public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create(GroupCommand.class, "GroupEntity"); - private final String groupId; - private final Function> tagger; - - private GroupEntity(EntityContext context) { - super( - // PersistenceId needs a typeHint (or namespace) and entityId, - // we take then from the EntityContext - PersistenceId.of( - context.getEntityTypeKey().name(), // <- type hint - context.getEntityId() // <- business id - )); - // we keep a copy of cartI - this.groupId = context.getEntityId(); - this.tagger = AkkaTaggerAdapter.fromLagom(context, GroupEvent.TAG); - - } - - public static GroupEntity create(EntityContext context) { - return new GroupEntity(context); - } - - @Override - public GroupState emptyState() { - return new EmptyState(); - } - - @Override - public EventHandler eventHandler() { - final EventHandlerBuilder builder = this.newEventHandlerBuilder(); - - builder.forState(GroupState::isEmpty) - .onEvent( - GroupEvent.GroupRegistered.class, - this::handleRegistration - ); - builder.forStateType(PopulatedState.class) - .onEvent(GroupEvent.ArtifactRegistered.class, this::handleArtifactRegistration); - - return builder.build(); - } - - private GroupState handleRegistration( - final GroupState state, final GroupEvent.GroupRegistered event - ) { - return new PopulatedState(event.groupId, event.name, event.website, Set.of()); - } - - private GroupState handleArtifactRegistration( - final PopulatedState state, final GroupEvent.ArtifactRegistered event - ) { - final var add = Stream.concat( - state.artifacts().stream(), - Stream.of(event.artifact()) - ) - .collect(Collectors.toUnmodifiableSet()); - return new PopulatedState(state.groupCoordinates(), state.name(), state.website(), add); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final CommandHandlerWithReplyBuilder builder = this.newCommandHandlerWithReplyBuilder(); - - builder.forState(GroupState::isEmpty) - .onCommand(GroupCommand.RegisterGroup.class, this::respondToRegisterGroup) - .onCommand(GroupCommand.RegisterArtifact.class, (state, cmd) -> - this.Effect().reply(cmd.replyTo(), new ArtifactRegistration.Response.GroupMissing(state.name())) - ) - .onCommand(GroupCommand.GetGroup.class, (cmd) -> - this.Effect().reply(cmd.replyTo(), new GroupResponse.Missing(cmd.groupId())) - ) - .onCommand(GroupCommand.GetArtifacts.class, (cmd) -> - this.Effect().reply(cmd.replyTo(), new GetArtifactsResponse.GroupMissing(cmd.groupId())) - ) - ; - builder.forStateType(PopulatedState.class) - .onCommand(GroupCommand.RegisterGroup.class, (cmd) -> this.Effect().reply( - cmd.replyTo(), new GroupRegistration.Response.GroupAlreadyRegistered(cmd.mavenCoordinates()))) - .onCommand(GroupCommand.RegisterArtifact.class, this::respondToRegisterArtifact) - .onCommand(GroupCommand.GetGroup.class, this::respondToGetGroup) - .onCommand(GroupCommand.GetArtifacts.class, this::respondToGetVersions); - return builder.build(); - } - - @Override - public RetentionCriteria retentionCriteria() { - return RetentionCriteria.snapshotEvery(5, 2); - } - - @Override - public Set tagsFor(final GroupEvent groupEvent) { - return this.tagger.apply(groupEvent); - } - - private ReplyEffect respondToRegisterGroup( - final GroupState state, - final GroupCommand.RegisterGroup cmd - ) { - return this.Effect() - .persist(new GroupEvent.GroupRegistered(cmd.mavenCoordinates(), cmd.name(), cmd.website())) - .thenReply( - cmd.replyTo(), - newState -> new GroupRegistration.Response.GroupRegistered( - new Group( - newState.groupCoordinates(), - newState.name(), - newState.website() - )) - ); - } - - private ReplyEffect respondToRegisterArtifact( - final PopulatedState state, - final GroupCommand.RegisterArtifact cmd - ) { - if (state.artifacts().contains(cmd.artifact())) { - this.Effect().reply(cmd.replyTo(), new ArtifactRegistration.Response.ArtifactAlreadyRegistered( - cmd.artifact(), - state.groupCoordinates() - )); - } - - final var group = state.asGroup(); - final var coordinates = new ArtifactCoordinates(group.groupCoordinates(), cmd.artifact()); - final EffectFactories effect = this.Effect(); - return effect.persist(new GroupEvent.ArtifactRegistered(state.groupCoordinates(), cmd.artifact())) - .thenReply(cmd.replyTo(), (s) -> new ArtifactRegistration.Response.ArtifactRegistered(coordinates)); - } - - private ReplyEffect respondToGetGroup( - final PopulatedState state, final GroupCommand.GetGroup cmd - ) { - final String website = state.website(); - return this.Effect().reply(cmd.replyTo(), Try.of(() -> new URL(website)) - .mapTry(url -> { - final Group group = new Group(state.groupCoordinates(), state.name(), website); - return new GroupResponse.Available(group); - }) - .getOrElseGet(throwable -> new GroupResponse.Missing(cmd.groupId()))); - } - - private ReplyEffect respondToGetVersions( - final PopulatedState state, - final GroupCommand.GetArtifacts cmd - ) { - return this.Effect().reply( - cmd.replyTo(), new GetArtifactsResponse.ArtifactsAvailable(state.artifacts().stream().toList())); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEvent.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEvent.java deleted file mode 100644 index 01fdb996..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupEvent.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -import java.io.Serial; -import java.util.Objects; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(GroupEvent.GroupRegistered.class), - @JsonSubTypes.Type(GroupEvent.ArtifactRegistered.class), -}) -public interface GroupEvent extends AggregateEvent, Jsonable { - - AggregateEventShards TAG = AggregateEventTag.sharded(GroupEvent.class, 10); - - @Override - default AggregateEventTagger aggregateTag() { - return TAG; - } - - String groupId(); - - @JsonTypeName("group-registered") - @JsonDeserialize - final class GroupRegistered implements GroupEvent { - @Serial private static final long serialVersionUID = 0L; - - public final String groupId; - public final String name; - public final String website; - - @JsonCreator - public GroupRegistered(final String groupId, final String name, final String website) { - this.groupId = groupId; - this.name = name; - this.website = website; - } - - @Override - public String groupId() { - return this.groupId; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - final var that = (GroupRegistered) obj; - return Objects.equals(this.groupId, that.groupId) && - Objects.equals(this.name, that.name) && - Objects.equals(this.website, that.website); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.name, this.website); - } - - @Override - public String toString() { - return "GroupRegistered[" + - "groupId=" + this.groupId + ", " + - "name=" + this.name + ", " + - "website=" + this.website + ']'; - } - - } - - @JsonTypeName("artifact-registered") - @JsonDeserialize - record ArtifactRegistered( - String groupId, - String artifact - ) implements GroupEvent { - - public ArtifactCoordinates coordinates() { - return new ArtifactCoordinates(this.groupId, this.artifact); - } - - @JsonCreator - public ArtifactRegistered { - } - } - -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupManager.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupManager.java deleted file mode 100644 index 7783d3cf..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupManager.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.groups; - -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.sharding.typed.javadsl.EntityRef; -import com.lightbend.lagom.javadsl.api.transport.NotFound; -import jakarta.inject.Inject; -import jakarta.inject.Singleton; -import org.spongepowered.downloads.artifact.api.query.ArtifactRegistration; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; -import org.spongepowered.downloads.artifacts.server.details.DetailsManager; -import org.spongepowered.downloads.artifacts.server.global.GlobalManager; - -import java.time.Duration; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -@Singleton -public final class GroupManager { - private final ClusterSharding clusterSharding; - private final GlobalManager global; - private final Duration askTimeout = Duration.ofHours(5); - private final DetailsManager details; - - @Inject - public GroupManager(ClusterSharding clusterSharding, final DetailsManager details, final GlobalManager global) { - this.clusterSharding = clusterSharding; - this.global = global; - this.details = details; - this.clusterSharding.init( - Entity.of( - GroupEntity.ENTITY_TYPE_KEY, - GroupEntity::create - ) - ); - } - - public CompletionStage registerGroup( - GroupRegistration.RegisterGroupRequest registration - ) { - final String mavenCoordinates = registration.groupCoordinates(); - final String name = registration.name(); - final String website = registration.website(); - return this.groupEntity(registration.groupCoordinates().toLowerCase(Locale.ROOT)) - .ask( - replyTo -> new GroupCommand.RegisterGroup(mavenCoordinates, name, website, replyTo), - this.askTimeout - ).thenCompose(response -> { - if (!(response instanceof GroupRegistration.Response.GroupRegistered registered)) { - return CompletableFuture.completedFuture(response); - } - return this.global.registerGroup(registered); - - }); - } - - - private EntityRef groupEntity(final String groupId) { - return this.clusterSharding.entityRefFor(GroupEntity.ENTITY_TYPE_KEY, groupId.toLowerCase(Locale.ROOT)); - } - - public CompletionStage registerArtifact( - ArtifactRegistration.RegisterArtifact reg, String groupId - ) { - final var sanitizedGroupId = groupId.toLowerCase(Locale.ROOT); - return this.groupEntity(sanitizedGroupId) - .ask( - replyTo -> new GroupCommand.RegisterArtifact(reg.artifactId(), replyTo), this.askTimeout) - .thenCompose(response -> switch (response) { - case ArtifactRegistration.Response.GroupMissing missing -> - throw new NotFound(String.format("group %s does not exist", missing.s())); - - case ArtifactRegistration.Response.ArtifactRegistered registered -> - this.details.registerArtifact(registered, reg.displayName()); - - default -> CompletableFuture.completedFuture(response); - }); - } - - public CompletionStage getArtifacts(String groupId) { - return this.groupEntity(groupId) - .ask(replyTo -> new GroupCommand.GetArtifacts(groupId, replyTo), this.askTimeout) - .thenApply(response -> switch (response) { - case GetArtifactsResponse.GroupMissing m -> throw new NotFound(String.format("group '%s' not found", m.groupRequested())); - case GetArtifactsResponse.ArtifactsAvailable a -> a; - }); - } - - public CompletionStage get(String groupId) { - return this.groupEntity(groupId.toLowerCase(Locale.ROOT)) - .ask(replyTo -> new GroupCommand.GetGroup(groupId, replyTo), this.askTimeout) - .thenApply(response -> switch (response) { - case GroupResponse.Missing m -> throw new NotFound(String.format("group '%s' not found", m.groupId())); - case GroupResponse.Available a -> a; - }); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupsQueryController.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupsQueryController.java deleted file mode 100644 index 98ebb288..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/GroupsQueryController.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.groups; - -import akka.actor.typed.ActorSystem; -import akka.actor.typed.SpawnProtocol; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.sharding.typed.javadsl.EntityRef; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Body; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Post; -import jakarta.inject.Inject; -import org.spongepowered.downloads.artifact.api.query.GroupRegistration; -import org.spongepowered.downloads.artifact.api.query.GroupResponse; - -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; - -@Controller("/groups") -public class GroupsQueryController { - - private final ActorSystem system; - private final ClusterSharding sharding; - - @Inject - public GroupsQueryController( - final ActorSystem system, - ClusterSharding sharding - ) { - this.system = system; - this.sharding = sharding; - this.sharding.init( - Entity.of( - GroupEntity.ENTITY_TYPE_KEY, - GroupEntity::create - ) - ); - - } - - @Post("/") - public CompletableFuture> registerGroup( - @Body GroupRegistration.RegisterGroupRequest req - ) { - final var ref = this.sharding.entityRefFor( - GroupEntity.ENTITY_TYPE_KEY, - req.groupCoordinates() - ); - final var resp = ref.ask - ( - replyTo -> new GroupCommand.RegisterGroup( - req.groupCoordinates(), - req.name(), - req.website(), - replyTo - ), - Duration.ofSeconds(10) - ); - return resp - .>thenApply(HttpResponse::created) - .toCompletableFuture(); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/EmptyState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/EmptyState.java deleted file mode 100644 index e6b3de5a..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/EmptyState.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups.state; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.spongepowered.downloads.artifact.api.Group; - -@JsonDeserialize -public record EmptyState() implements GroupState { - - @JsonCreator - public EmptyState { - } - - @Override - public boolean isEmpty() { - return true; - } - - @Override - public Group asGroup() { - return new Group("", "", ""); - } - - @Override - public String website() { - return "null"; - } - - @Override - public String name() { - return "null"; - } - - @Override - public String groupCoordinates() { - return "null"; - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/GroupState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/GroupState.java deleted file mode 100644 index 8b133996..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/GroupState.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups.state; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.spongepowered.downloads.artifact.api.Group; - -@JsonDeserialize -@JsonSubTypes({ - @JsonSubTypes.Type(value = PopulatedState.class, name = "populated"), - @JsonSubTypes.Type(value = EmptyState.class, name = "empty") -}) -public sealed interface GroupState permits EmptyState, PopulatedState { - - boolean isEmpty(); - - Group asGroup(); - - String website(); - - String name(); - - String groupCoordinates(); -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/PopulatedState.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/PopulatedState.java deleted file mode 100644 index 56264f25..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/groups/state/PopulatedState.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.server.groups.state; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.spongepowered.downloads.artifact.api.Group; - -import java.util.Set; - -@JsonDeserialize -public record PopulatedState( - String groupCoordinates, - String name, - String website, - Set artifacts -) implements GroupState { - @JsonCreator - public PopulatedState { - } - - public boolean isEmpty() { - return this.groupCoordinates().isEmpty() || this.name().isEmpty(); - } - - public Group asGroup() { - return new Group(this.groupCoordinates(), this.name(), this.website()); - } -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java deleted file mode 100644 index fb46c84f..00000000 --- a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.query; - -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; - -@Controller("/groups/{groupID}/artifacts") -public class ArtifactsQuery { - - @Get("") - - -} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/group/domain/GroupOrg.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/group/domain/GroupOrg.java new file mode 100644 index 00000000..ed8de22f --- /dev/null +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/group/domain/GroupOrg.java @@ -0,0 +1,27 @@ +package org.spongepowered.downloads.artifacts.server.query.group.domain; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; +import io.micronaut.serde.annotation.Serdeable; +import org.spongepowered.downloads.artifacts.server.query.meta.domain.JpaArtifact; + +import java.util.List; + +@MappedEntity(value = "groups") +@Serdeable +public class GroupOrg { + + @Id + @GeneratedValue + private int id; + + @MappedProperty(value = "groupId") + private String groupId; + + @Relation(value =Relation.Kind.ONE_TO_MANY, mappedBy = "groupId") + private List artifacts; + +} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactDto.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactDto.java new file mode 100644 index 00000000..4ebb194d --- /dev/null +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactDto.java @@ -0,0 +1,31 @@ +package org.spongepowered.downloads.artifacts.server.query.meta; + +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; + +import java.io.Serializable; +import java.util.Set; + +/** + * DTO for {@link org.spongepowered.downloads.artifacts.server.query.meta.domain.JpaArtifact} + */ +public record ArtifactDto( + @NotNull String groupId, + @NotEmpty String artifactId, + String displayName, + String website, + String gitRepo, + String issues, + @NotNull Set tagValues +) implements Serializable { + /** + * DTO for {@link org.spongepowered.downloads.artifacts.server.query.meta.domain.JpaArtifactTagValue} + */ + public record Tag( + String artifactId, + String groupId, + String tagName, + String tagValue + ) implements Serializable { + } +} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactQueryController.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactQueryController.java new file mode 100644 index 00000000..87c90b5b --- /dev/null +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactQueryController.java @@ -0,0 +1,52 @@ +package org.spongepowered.downloads.artifacts.server.query.meta; + +import akka.actor.typed.ActorSystem; +import akka.actor.typed.SpawnProtocol; +import akka.cluster.sharding.typed.javadsl.ClusterSharding; +import io.micronaut.context.annotation.Requires; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.MediaType; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.PathVariable; +import io.micronaut.http.annotation.Status; +import jakarta.inject.Inject; +import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; +import reactor.core.publisher.Mono; + +import java.util.List; + +@Controller("/groups/{groupID}/artifacts") +@Requires("query") +public class ArtifactQueryController { + + private final ClusterSharding sharding; + private final ActorSystem system; + private final ArtifactRepository artifactsRepo; + + + @Inject + public ArtifactQueryController( + final ClusterSharding sharding, + final ActorSystem system, + final ArtifactRepository artifactsRepo + ) { + + this.sharding = sharding; + this.system = system; + this.artifactsRepo = artifactsRepo; + } + + @Get(value = "/{artifactId}", + produces = MediaType.APPLICATION_JSON + ) + @Status(HttpStatus.OK) + public Mono getArtifacts( + final @PathVariable String groupID, + final @PathVariable String artifactId + ) { + return this.artifactsRepo.findByGroupIdAndArtifactId(groupID, artifactId) + .map(a -> new GetArtifactsResponse.ArtifactsAvailable(List.of(a.getArtifactId()))) + .onErrorReturn(new GetArtifactsResponse.GroupMissing(groupID)); + } +} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactRepository.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactRepository.java new file mode 100644 index 00000000..5032a032 --- /dev/null +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/ArtifactRepository.java @@ -0,0 +1,22 @@ +package org.spongepowered.downloads.artifacts.server.query.meta; + +import io.micronaut.core.annotation.NonNull; +import io.micronaut.data.annotation.Query; +import io.micronaut.data.model.query.builder.sql.Dialect; +import io.micronaut.data.r2dbc.annotation.R2dbcRepository; +import io.micronaut.data.repository.reactive.ReactiveStreamsCrudRepository; +import org.spongepowered.downloads.artifacts.server.query.meta.domain.JpaArtifact; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Optional; + +@R2dbcRepository(dialect = Dialect.POSTGRES) +public interface ArtifactRepository extends ReactiveStreamsCrudRepository { + + @NonNull + List findArtifactIdByGroupId(@NonNull String groupId); + + @NonNull + Mono findByGroupIdAndArtifactId(String groupId, String artifactId); +} diff --git a/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/Group.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/Group.java new file mode 100644 index 00000000..05741eba --- /dev/null +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/Group.java @@ -0,0 +1,27 @@ +package org.spongepowered.downloads.artifacts.server.query.meta.domain; + +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; +import io.micronaut.serde.annotation.Serdeable; + +import java.util.List; + +@Serdeable +@MappedEntity +public class Group { + + @GeneratedValue + @Id + private Long id; + + @MappedProperty(value = "groupId") + private String groupId; + + @Relation(value = Relation.Kind.ONE_TO_MANY, + mappedBy = "groupId") + private List artifacts; + +} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifact.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifact.java similarity index 54% rename from downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifact.java rename to artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifact.java index 0ee0309d..27c5456a 100644 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifact.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifact.java @@ -22,94 +22,59 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.downloads.artifacts.models; +package org.spongepowered.downloads.artifacts.server.query.meta.domain; +import io.micronaut.data.annotation.GeneratedValue; +import io.micronaut.data.annotation.Id; +import io.micronaut.data.annotation.Join; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; +import io.micronaut.data.annotation.Relation; import io.vavr.Tuple; import io.vavr.Tuple2; import io.vavr.collection.Map; import io.vavr.collection.SortedSet; import io.vavr.collection.TreeMap; import io.vavr.collection.TreeSet; +import jakarta.validation.constraints.NotEmpty; import org.apache.maven.artifact.versioning.ComparableVersion; -import org.hibernate.annotations.Immutable; -import org.spongepowered.downloads.api.ArtifactCoordinates; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import java.io.Serializable; +import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; + import java.util.Comparator; -import java.util.Objects; import java.util.Set; -import java.util.stream.Collectors; - -@Immutable -@Entity(name = "Artifact") -@Table(name = "artifacts", - schema = "version") -@NamedQueries({ - @NamedQuery( - name = "Artifact.findByCoordinates", - query = "select a from Artifact a where a.groupId = :groupId and a.artifactId = :artifactId" - ) -}) -public class JpaArtifact implements Serializable { + +@MappedEntity(value = "artifacts", schema = "artifacts", alias = "artifact") +public class JpaArtifact { + + @GeneratedValue @Id - @Column(name = "id", - nullable = false, - updatable = false, - insertable = false) private int id; - @Column(name = "group_id", - nullable = false, - updatable = false, - insertable = false) + public int getId() { + return id; + } + + @MappedProperty(value = "group_id") private String groupId; - @Column(name = "artifact_id", - nullable = false, - updatable = false, - insertable = false) + @NotEmpty + @MappedProperty(value = "artifact_id") private String artifactId; - @Column(name = "display_name", - updatable = false, - insertable = false) + @MappedProperty(value = "display_name") private String displayName; - @Column(name = "website", - updatable = false, - insertable = false) + @MappedProperty(value = "website") private String website; - @Column(name = "git_repository", - updatable = false, - insertable = false) + @MappedProperty(value = "git_repository") private String gitRepo; - @Column(name = "issues", - updatable = false, - insertable = false) + @MappedProperty(value = "issues") private String issues; - @OneToMany(fetch = FetchType.EAGER, - targetEntity = JpaArtifactTagValue.class) - @JoinColumns({ - @JoinColumn(name = "artifact_id", - referencedColumnName = "artifact_id"), - @JoinColumn(name = "group_id", - referencedColumnName = "group_id") - }) private Set tagValues; public String getGroupId() { @@ -140,26 +105,6 @@ public Set getTagValues() { return tagValues; } - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaArtifact that = (JpaArtifact) o; - return id == that.id && groupId.equals(that.groupId) && artifactId.equals( - that.artifactId) && Objects.equals(displayName, that.displayName) && Objects.equals( - website, that.website) && Objects.equals(gitRepo, that.gitRepo) && Objects.equals( - issues, that.issues); - } - - @Override - public int hashCode() { - return Objects.hash(id, groupId, artifactId, displayName, website, gitRepo, issues); - } - public ArtifactCoordinates getCoordinates() { return new ArtifactCoordinates(this.groupId, this.artifactId); } @@ -178,4 +123,5 @@ public Map> getTagValuesForReply() { } return versionedTags; } + } diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifactTagValue.java b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifactTagValue.java similarity index 73% rename from downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifactTagValue.java rename to artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifactTagValue.java index d6a92e3d..9411baf9 100644 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/models/JpaArtifactTagValue.java +++ b/artifacts/server/src/main/java/org/spongepowered/downloads/artifacts/server/query/meta/domain/JpaArtifactTagValue.java @@ -22,27 +22,15 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ -package org.spongepowered.downloads.artifacts.models; +package org.spongepowered.downloads.artifacts.server.query.meta.domain; -import org.hibernate.annotations.Immutable; +import io.micronaut.data.annotation.MappedEntity; +import io.micronaut.data.annotation.MappedProperty; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.ManyToOne; -import javax.persistence.Table; import java.io.Serializable; import java.util.Objects; -@Immutable -@Entity(name = "ArtifactTagValue") -@Table(name = "artifact_tag_values", - schema = "version") -@IdClass(JpaArtifactTagValue.Identifier.class) +@MappedEntity(value = "versioned_tags", schema = "version") public class JpaArtifactTagValue { /* @@ -78,38 +66,23 @@ public int hashCode() { } } - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "artifact_id", - referencedColumnName = "artifact_id"), - @JoinColumn(name = "group_id", - referencedColumnName = "group_id") - }) private JpaArtifact artifact; - @Id - @Column(name = "artifact_id", - insertable = false, - updatable = false) + @MappedProperty(value = "artifact_id") private String artifactId; - @Id - @Column(name = "group_id", - insertable = false, - updatable = false) + @MappedProperty(value = "group_id") private String groupId; - @Id - @Column(name = "tag_name", - insertable = false, - updatable = false) + @MappedProperty(value = "tag_name") private String tagName; - @Id - @Column(name = "tag_value", - insertable = false, - updatable = false) + @MappedProperty(value = "tag_value") private String tagValue; + public JpaArtifact getArtifact() { + return artifact; + } + public String getTagName() { return tagName; } diff --git a/artifacts/server/src/main/resources/application.conf b/artifacts/server/src/main/resources/application.conf new file mode 100644 index 00000000..997ab44f --- /dev/null +++ b/artifacts/server/src/main/resources/application.conf @@ -0,0 +1,32 @@ +akka.persistence.journal.plugin = "akka.persistence.r2dbc.journal" +akka.persistence.snapshot-store.plugin = "akka.persistence.r2dbc.snapshot" +akka.persistence.state.plugin = "akka.persistence.r2dbc.state" + +akka.persistence.r2dbc { + journal.payload-column-type = JSONB + snapshot.payload-column-type = JSONB + state.payload-column-type = JSONB +} +akka.serialization.jackson.jackson-json.compression.algorithm = off + + +akka.persistence.r2dbc { + dialect = "postgres" + connection-factory { + driver = "postgres" + host = "localhost" + host = ${?DB_HOST} + database = "default" + database = ${?DB_NAME} + user = "admin" + user = ${?DB_USER} + password = "password" + password = ${?DB_PASSWORD} + + # ssl { + # enabled = on + # mode = "VERIFY_CA" + # root-cert = "/path/db_root.crt" + # } + } +} diff --git a/artifacts/server/src/main/resources/application.toml b/artifacts/server/src/main/resources/application.toml deleted file mode 100644 index 250d1b57..00000000 --- a/artifacts/server/src/main/resources/application.toml +++ /dev/null @@ -1,10 +0,0 @@ -micronaut.application.name = 'artifacts' -netty.default.allocator.max-order = 3 - -[r2dbc.datasources.default] -schema-generate = 'CREATE_DROP' -dialect = 'H2' - -[micronaut.security] -authentication = 'bearer' -token.jwt.signatures.secret.generator.secret = '${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisSecretForANewOne}' diff --git a/artifacts/server/src/main/resources/application.yaml b/artifacts/server/src/main/resources/application.yaml new file mode 100644 index 00000000..754ed774 --- /dev/null +++ b/artifacts/server/src/main/resources/application.yaml @@ -0,0 +1,24 @@ +datasources: + default: + + db-type: postgresql + dialect: POSTGRES + driver: postgresql + options: + currentSchema: artifact + pool: + max-size: 10 + max-idle-time: 30m + driver-class-name: org.postgresql.Driver +r2dbc: + datasources: + default: + db-type: postgresql + dialect: POSTGRES + +liquibase: + enabled: true + datasources: + default: + enabled: true + change-log: 'classpath:db/liquibase-changelog.xml' # (4) diff --git a/artifacts/server/src/main/resources/db/changelog/01-create-artifacts-schema.xml b/artifacts/server/src/main/resources/db/changelog/01-create-artifacts-schema.xml new file mode 100644 index 00000000..b6adedcb --- /dev/null +++ b/artifacts/server/src/main/resources/db/changelog/01-create-artifacts-schema.xml @@ -0,0 +1,125 @@ + + + + + + + + CREATE SCHEMA IF NOT EXISTS artifacts; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + select distinct a.artifact_id, a.group_id, v.version, v.recommended, v.manual_recommendation + from artifact.artifacts a inner join artifact.artifact_versions v on a.id = v.artifact_id + + + select + a.group_id, + a.artifact_id, + av.version, + va.classifier, va.extension, va.download_url, va.md5, va.sha1 + from artifact.versioned_assets va + inner join artifact.artifact_versions av on av.id = va.version_id + inner join artifact.artifacts a on a.id = av.artifact_id + + + select distinct a.group_id, a.artifact_id, v.version, v.ordering, v.id as version_id, vc.commit_sha, vc.repo, vc.branch, vc.changelog + from artifact.version_changelogs vc + inner join artifact.artifact_versions v on v.id = vc.version_id + inner join artifact.artifacts a on a.id = v.artifact_id + order by v.ordering desc + + + select distinct a.artifact_id, a.group_id, v.version, v.ordering, v.recommended, v.manual_recommendation + from artifact.artifacts a inner join artifact.artifact_versions v on a.id = v.artifact_id + order by v.ordering desc + + + + diff --git a/artifacts/server/src/main/resources/db/liquibase-changelog.xml b/artifacts/server/src/main/resources/db/liquibase-changelog.xml new file mode 100644 index 00000000..92a9682b --- /dev/null +++ b/artifacts/server/src/main/resources/db/liquibase-changelog.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/artifacts/server/src/main/resources/logback.xml b/artifacts/server/src/main/resources/logback.xml index 6010eb52..8cb499db 100644 --- a/artifacts/server/src/main/resources/logback.xml +++ b/artifacts/server/src/main/resources/logback.xml @@ -12,4 +12,9 @@ + + + + + diff --git a/artifacts/server/src/test/java/org/spongepowered/downloads/test/artifacts/server/ArtifactRepositoryTest.java b/artifacts/server/src/test/java/org/spongepowered/downloads/test/artifacts/server/ArtifactRepositoryTest.java new file mode 100644 index 00000000..8a5c796d --- /dev/null +++ b/artifacts/server/src/test/java/org/spongepowered/downloads/test/artifacts/server/ArtifactRepositoryTest.java @@ -0,0 +1,118 @@ +package org.spongepowered.downloads.test.artifacts.server; + +import io.micronaut.context.BeanContext; +import io.micronaut.core.type.Argument; +import io.micronaut.data.annotation.Query; +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +import io.micronaut.http.client.BlockingHttpClient; +import io.micronaut.http.client.HttpClient; +import io.micronaut.http.client.annotation.Client; +import io.micronaut.inject.BeanDefinition; +import io.micronaut.inject.ExecutableMethod; +import io.micronaut.runtime.EmbeddedApplication; +import io.micronaut.serde.annotation.Serdeable; +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.micronaut.test.extensions.junit5.annotation.TestResourcesScope; +import jakarta.inject.Inject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.spongepowered.downloads.artifacts.server.query.meta.ArtifactRepository; +import org.spongepowered.downloads.artifacts.server.query.meta.domain.JpaArtifact; +import reactor.core.publisher.Mono; + +import java.util.List; +import java.util.Optional; + + +@MicronautTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +@TestResourcesScope("testcontainers") +public class ArtifactRepositoryTest { + @Inject + BeanContext context; + + @Inject + EmbeddedApplication application; + + @Inject + @Client("/") + HttpClient httpClient; + + @Test + public void testItWorks() { + Assertions.assertTrue(application.isRunning()); + } + + @Test + void migrationsAreExposedViaAndEndpoint() { + BlockingHttpClient client = httpClient.toBlocking(); + + HttpResponse> response = client.exchange( + HttpRequest.GET("/liquibase"), + Argument.listOf(LiquibaseReport.class) + ); + Assertions.assertEquals(HttpStatus.OK, response.status()); + + LiquibaseReport liquibaseReport = response.body().get(0); + Assertions.assertNotNull(liquibaseReport); + Assertions.assertNotNull(liquibaseReport.getChangeSets()); + Assertions.assertEquals(2, liquibaseReport.getChangeSets().size()); + } + @Serdeable + static class LiquibaseReport { + + private List changeSets; + + public void setChangeSets(List changeSets) { + this.changeSets = changeSets; + } + + public List getChangeSets() { + return changeSets; + } + } + + @Serdeable + static class ChangeSet { + + private String id; + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + } + + @Test + public void testAnnotation() { + final BeanDefinition beanDefinition = context.getBeanDefinition(ArtifactRepository.class); + final ExecutableMethod findByGroupIdAndArtifactId = beanDefinition // (1) + .getRequiredMethod("findByGroupIdAndArtifactId", String.class, String.class); + String query = findByGroupIdAndArtifactId // (2) + .getAnnotationMetadata().stringValue(Query.class) // (3) + .orElse(null); + + final String expected = "SELECT artifact.\"id\",artifact.\"group_id\",artifact.\"artifact_id\",artifact.\"display_name\",artifact.\"website\",artifact.\"git_repository\",artifact.\"issues\",artifact.\"tag_values\",artifact.\"coordinates\",artifact.\"tag_values_for_reply\" FROM \"artifacts\".\"artifacts\" artifact WHERE (artifact.\"group_id\" = $1 AND artifact.\"artifact_id\" = $2)"; + Assertions.assertEquals( // (4) + expected, query); + } + + @Test + public void testGetArtifact() { + final ArtifactRepository repo = context.createBean(ArtifactRepository.class); + + + final Mono spongevanilla = repo.findByGroupIdAndArtifactId("org.spongepowered", "spongevanilla"); + final JpaArtifact a = spongevanilla.block(); + + } + + + +} diff --git a/artifacts/server/src/test/resources/application-test.conf b/artifacts/server/src/test/resources/application-test.conf new file mode 100644 index 00000000..a4090ab5 --- /dev/null +++ b/artifacts/server/src/test/resources/application-test.conf @@ -0,0 +1,10 @@ + +akka { + actor { + provider = "cluster" + serialization-bindings { + "org.spongepowered.downloads.akka.AkkaSerializable" = jackson-json + } + } + +} diff --git a/artifacts/server/src/test/resources/application-test.yaml b/artifacts/server/src/test/resources/application-test.yaml new file mode 100644 index 00000000..7ab844b7 --- /dev/null +++ b/artifacts/server/src/test/resources/application-test.yaml @@ -0,0 +1,22 @@ +test-resources: + containers: + postgres: + image-name: postgres:14.6 + username: testuser + password: testpassword + db-name: testdb + +liquibase: + enabled: true + datasources: + default: + change-log: 'classpath:db/liquibase-changelog.xml' # (4) +endpoints: + liquibase: + enabled: true + sensitive: false +micronaut: + environment: test + http: + client: + read-timeout: 5m diff --git a/artifacts/src/main/resources/logback.xml b/artifacts/server/src/test/resources/logback.xml similarity index 84% rename from artifacts/src/main/resources/logback.xml rename to artifacts/server/src/test/resources/logback.xml index 6010eb52..d8294a83 100644 --- a/artifacts/src/main/resources/logback.xml +++ b/artifacts/server/src/test/resources/logback.xml @@ -9,7 +9,12 @@ - + + + + + + diff --git a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java b/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java deleted file mode 100644 index 4af14913..00000000 --- a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/Application.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.spongepowered.downloads.artifacts.server; - -import io.micronaut.context.event.ApplicationEventListener; -import io.micronaut.runtime.Micronaut; -import io.micronaut.runtime.server.event.ServerStartupEvent; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.info.*; - -@OpenAPIDefinition( - info = @Info( - title = "artifacts", - version = "0.0" - ) -) -public class Application implements ApplicationEventListener { - - public static void main(String[] args) { - Micronaut.run(Application.class, args); - } -} diff --git a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java b/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java deleted file mode 100644 index 960d4ab5..00000000 --- a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/ArtifactsQuery.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.query; - -import io.micronaut.http.annotation.Controller; - -@Controller("/groups/{groupID}/artifacts") -public class ArtifactsQuery { - - -} diff --git a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/GroupsQueryController.java b/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/GroupsQueryController.java deleted file mode 100644 index 6c5b218c..00000000 --- a/artifacts/src/main/java/org/spongepowered/downloads/artifacts/server/query/GroupsQueryController.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.spongepowered.downloads.artifacts.server.query; - -import akka.actor.typed.ActorSystem; -import akka.actor.typed.SpawnProtocol; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Body; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.Post; -import jakarta.inject.Inject; - -@Controller("/groups") -public class GroupsQueryController { - - - @Inject - private ActorSystem system; - @Inject - private ClusterSharding sharding; - - @Post("/") - public HttpResponse registerGroup( - @Body GroupRegistration.RegisterGroupRequest req - ) { - return null; - } -} diff --git a/artifacts/src/main/resources/application.toml b/artifacts/src/main/resources/application.toml deleted file mode 100644 index 250d1b57..00000000 --- a/artifacts/src/main/resources/application.toml +++ /dev/null @@ -1,10 +0,0 @@ -micronaut.application.name = 'artifacts' -netty.default.allocator.max-order = 3 - -[r2dbc.datasources.default] -schema-generate = 'CREATE_DROP' -dialect = 'H2' - -[micronaut.security] -authentication = 'bearer' -token.jwt.signatures.secret.generator.secret = '${JWT_GENERATOR_SIGNATURE_SECRET:pleaseChangeThisSecretForANewOne}' diff --git a/artifacts/src/main/resources/bootstrap.toml b/artifacts/src/main/resources/bootstrap.toml deleted file mode 100644 index 82bc8221..00000000 --- a/artifacts/src/main/resources/bootstrap.toml +++ /dev/null @@ -1,4 +0,0 @@ - -[kubernetes.client.discovery] -mode = 'endpoint' -mode-configuration.endpoint.watch.enabled = true diff --git a/artifacts/src/test/java/org/spongepowered/downloads/artifacts/ArtifactsTest.java b/artifacts/src/test/java/org/spongepowered/downloads/artifacts/ArtifactsTest.java deleted file mode 100644 index 13573ede..00000000 --- a/artifacts/src/test/java/org/spongepowered/downloads/artifacts/ArtifactsTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.spongepowered.downloads.artifacts; - -import io.micronaut.runtime.EmbeddedApplication; -import io.micronaut.test.extensions.junit5.annotation.MicronautTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; - -import jakarta.inject.Inject; - -@MicronautTest(transactional = false) -class ArtifactsTest { - - @Inject - EmbeddedApplication application; - - @Test - void testItWorks() { - Assertions.assertTrue(application.isRunning()); - } - -} diff --git a/artifacts/worker/build.gradle.kts b/artifacts/worker/build.gradle.kts index 313f4efe..62090d55 100644 --- a/artifacts/worker/build.gradle.kts +++ b/artifacts/worker/build.gradle.kts @@ -1,47 +1,64 @@ +import org.gradle.kotlin.dsl.version +plugins { + id("com.github.johnrengelman.shadow") + id("io.micronaut.minimal.application") + id("io.micronaut.docker") + id("io.micronaut.test-resources") +} -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project +micronaut { + runtime("netty") + testRuntime("junit5") + processing { + incremental(true) + annotations("org.spongepowered.downloads.worker.*") + } + testResources { +// additionalModules.add("hibernate-reactive-postgresql") +// sharedServer.set(true) + } +} dependencies { implementation(project(":artifacts:api")) - annotationProcessor("io.micronaut.data:micronaut-data-processor") - annotationProcessor("io.micronaut:micronaut-http-validation") + implementation(project(":artifacts:events")) + implementation(libs.vavr) + + // Jackson + annotationProcessor("io.micronaut.serde:micronaut-serde-processor") + annotationProcessor("io.micronaut.validation:micronaut-validation-processor") + implementation("io.micronaut:micronaut-jackson-databind") + implementation("io.micronaut.serde:micronaut-serde-jackson") + implementation("io.micronaut.validation:micronaut-validation") + + // validation + + implementation("jakarta.annotation:jakarta.annotation-api") + implementation(libs.jakarta.validation) + + annotationProcessor("io.micronaut.microstream:micronaut-microstream-annotations") annotationProcessor("io.micronaut.openapi:micronaut-openapi") annotationProcessor("io.micronaut.security:micronaut-security-annotations") - annotationProcessor("io.micronaut.serde:micronaut-serde-processor") - implementation("com.ongres.scram:client:2.1") - implementation("io.micronaut:micronaut-http-client") + // DB Access + annotationProcessor("io.micronaut.data:micronaut-data-processor") implementation("io.micronaut:micronaut-jackson-databind") implementation("io.micronaut.data:micronaut-data-r2dbc") + implementation("io.micronaut.liquibase:micronaut-liquibase") - implementation("io.micronaut.reactor:micronaut-reactor") - implementation("io.micronaut.reactor:micronaut-reactor-http-client") - implementation("io.micronaut.security:micronaut-security-ldap") - implementation("io.micronaut.serde:micronaut-serde-jackson") - implementation("io.micronaut.toml:micronaut-toml") - implementation("io.micronaut.xml:micronaut-jackson-xml") - implementation("io.swagger.core.v3:swagger-annotations") + implementation("io.micronaut.sql:micronaut-jdbc-hikari") + runtimeOnly("ch.qos.logback:logback-classic") + runtimeOnly("org.postgresql:postgresql") implementation("io.vertx:vertx-pg-client") - implementation("jakarta.annotation:jakarta.annotation-api") - implementation(platform("com.typesafe.akka:akka-bom_${scalaVersion}:${akkaVersion}")) - implementation("com.typesafe.akka:akka-actor-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-persistence-typed_${scalaVersion}") - implementation("com.lightbend.akka:akka-projection-core_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-sharding-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.lightbend.akka.management:akka-management_${scalaVersion}:${akkaManagementVersion}") - implementation("com.lightbend.akka.management:akka-management-cluster-bootstrap_${scalaVersion}:${akkaManagementVersion}") +// implementation("jakarta.annotation:jakarta.annotation-api") + implementation(platform(libs.akkaBom)) + implementation(libs.bundles.actors) + implementation(libs.bundles.akkaManagement) + implementation(libs.bundles.actorsPersistence) runtimeOnly("ch.qos.logback:logback-classic") - runtimeOnly("org.postgresql:postgresql") - runtimeOnly("org.postgresql:r2dbc-postgresql") - compileOnly("org.graalvm.nativeimage:svm") +// compileOnly("org.graalvm.nativeimage:svm") - implementation("io.micronaut:micronaut-validation") +// implementation("io.micronaut:micronaut-validation") } diff --git a/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/ArtifactReadside.java b/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/ArtifactReadside.java new file mode 100644 index 00000000..50d8178d --- /dev/null +++ b/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/ArtifactReadside.java @@ -0,0 +1,102 @@ +/* + * This file is part of SystemOfADownload, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.downloads.artifacts.worker.readside; + +import akka.persistence.query.typed.EventEnvelope; +import akka.projection.r2dbc.javadsl.R2dbcHandler; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; + + +@Singleton +public class ArtifactReadside extends R2dbcHandler> { + + @Inject + public ArtifactReadside(final ReadSide readSide) { + readSide.register(DetailsWriter.class); + } + + static final class DetailsWriter extends ReadSideProcessor { + + private final JpaReadSide readSide; + + @Inject + DetailsWriter(final JpaReadSide readSide) { + this.readSide = readSide; + } + + @Override + public ReadSideHandler buildHandler() { + return this.readSide.builder("artifact-details-builder") + .setEventHandler(DetailsEvent.ArtifactRegistered.class, (em, event) -> { + findOrRegisterArtifact(em, event.coordinates()); + }) + .setEventHandler(DetailsEvent.ArtifactDetailsUpdated.class, (em, event) -> { + final var artifact = findOrRegisterArtifact(em, event.coordinates()); + artifact.setDisplayName(event.displayName()); + }) + .setEventHandler(DetailsEvent.ArtifactWebsiteUpdated.class, (em, event) -> { + final var artifact = findOrRegisterArtifact(em, event.coordinates()); + artifact.setWebsite(event.url()); + }) + .setEventHandler(DetailsEvent.ArtifactIssuesUpdated.class, (em, event) -> { + final var artifact = findOrRegisterArtifact(em, event.coordinates()); + artifact.setIssues(event.url()); + }) + .setEventHandler(DetailsEvent.ArtifactGitRepositoryUpdated.class, (em, event) -> { + final var artifact = findOrRegisterArtifact(em, event.coordinates()); + artifact.setGitRepo(event.gitRepo()); + }) + .build(); + } + + private JpaArtifact findOrRegisterArtifact( + final EntityManager em, final ArtifactCoordinates coordinates + ) { + final var artifactQuery = em.createNamedQuery( + "Artifact.findById", + JpaArtifact.class + ); + return artifactQuery.setParameter("groupId", coordinates.groupId()) + .setParameter("artifactId", coordinates.artifactId()) + .setMaxResults(1) + .getResultStream() + .findFirst() + .orElseGet(() -> { + final var jpaArtifact = new JpaArtifact(); + jpaArtifact.setGroupId(coordinates.groupId()); + jpaArtifact.setArtifactId(coordinates.artifactId()); + em.persist(jpaArtifact); + return jpaArtifact; + }); + } + + @Override + public PSequence> aggregateTags() { + return DetailsEvent.TAG.allTags(); + } + } +} diff --git a/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/JpaArtifact.java b/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/JpaArtifact.java new file mode 100644 index 00000000..7d62dee2 --- /dev/null +++ b/artifacts/worker/src/main/java/org/spongepowered/downloads/artifacts/worker/readside/JpaArtifact.java @@ -0,0 +1,114 @@ +/* + * This file is part of SystemOfADownload, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.downloads.artifacts.worker.readside; + +import jakarta.persistence.Entity; +import java.util.Objects; +import io.micronaut.serde.annotation.Serdeable; + +@Entity(name = "Artifact") +@Table(name = "artifacts", + schema = "version") +@NamedQueries({ + @NamedQuery(name = "Artifact.findById", + query = "select a from Artifact a where a.groupId = :groupId and a.artifactId = :artifactId" + ) +}) +@MappedEntity +@Serderable +public class JpaArtifact { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", + updatable = false, + nullable = false) + private long id; + + @Column(name = "group_id", + nullable = false) + private String groupId; + + @Column(name = "artifact_id", + nullable = false) + private String artifactId; + + @Column(name = "display_name") + private String displayName; + + @Column(name = "website") + private String website; + + @Column(name = "git_repository") + private String gitRepo; + + @Column(name = "issues") + private String issues; + + public void setGroupId(final String groupId) { + this.groupId = groupId; + } + + public void setArtifactId(final String artifactId) { + this.artifactId = artifactId; + } + + public void setDisplayName(final String displayName) { + this.displayName = displayName; + } + + public void setWebsite(final String website) { + this.website = website; + } + + public void setGitRepo(final String gitRepo) { + this.gitRepo = gitRepo; + } + + public void setIssues(final String issues) { + this.issues = issues; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + JpaArtifact that = (JpaArtifact) o; + return id == that.id && Objects.equals(groupId, that.groupId) && Objects.equals( + artifactId, that.artifactId) && Objects.equals( + displayName, that.displayName) && Objects.equals( + website, that.website) && Objects.equals(gitRepo, that.gitRepo) && Objects.equals( + issues, that.issues); + } + + @Override + public int hashCode() { + return Objects.hash(id, groupId, artifactId, displayName, website, gitRepo, issues); + } +} diff --git a/artifacts/worker/src/main/resources/application.conf b/artifacts/worker/src/main/resources/application.conf new file mode 100644 index 00000000..997ab44f --- /dev/null +++ b/artifacts/worker/src/main/resources/application.conf @@ -0,0 +1,32 @@ +akka.persistence.journal.plugin = "akka.persistence.r2dbc.journal" +akka.persistence.snapshot-store.plugin = "akka.persistence.r2dbc.snapshot" +akka.persistence.state.plugin = "akka.persistence.r2dbc.state" + +akka.persistence.r2dbc { + journal.payload-column-type = JSONB + snapshot.payload-column-type = JSONB + state.payload-column-type = JSONB +} +akka.serialization.jackson.jackson-json.compression.algorithm = off + + +akka.persistence.r2dbc { + dialect = "postgres" + connection-factory { + driver = "postgres" + host = "localhost" + host = ${?DB_HOST} + database = "default" + database = ${?DB_NAME} + user = "admin" + user = ${?DB_USER} + password = "password" + password = ${?DB_PASSWORD} + + # ssl { + # enabled = on + # mode = "VERIFY_CA" + # root-cert = "/path/db_root.crt" + # } + } +} diff --git a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthService.java b/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthService.java deleted file mode 100644 index f7ae9117..00000000 --- a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthService.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.transport.Method; - -public interface AuthService extends Service { - - final class Providers { - - public static final String LDAP = "ldap"; - } - - // The response will contain a JWT if the authentication succeeded. - // Uses standard Basic auth over HTTPS to login. - ServiceCall login(); - - ServiceCall logout(); - - default Descriptor descriptor() { - return Service.named("auth") - .withCalls( - Service.restCall(Method.POST, "/auth/login", this::login), - Service.restCall(Method.POST, "/auth/logout", this::logout) - ) - .withAutoAcl(true); - } -} diff --git a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthenticationRequest.java b/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthenticationRequest.java deleted file mode 100644 index 14bdde59..00000000 --- a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/AuthenticationRequest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth.api; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -public final class AuthenticationRequest { - - @JsonSerialize - public final record Response(@JsonProperty("token") String jwtToken) { - } - -} diff --git a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/utils/AuthUtils.java b/auth-api/src/main/java/org/spongepowered/downloads/auth/api/utils/AuthUtils.java deleted file mode 100644 index 84f348f1..00000000 --- a/auth-api/src/main/java/org/spongepowered/downloads/auth/api/utils/AuthUtils.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth.api.utils; - -import com.lightbend.lagom.javadsl.api.ServiceCall; -import io.vavr.collection.List; -import org.pac4j.core.authorization.authorizer.Authorizer; -import org.pac4j.core.client.Client; -import org.pac4j.core.client.DirectClient; -import org.pac4j.core.config.Config; -import org.pac4j.core.context.HttpConstants; -import org.pac4j.core.credentials.TokenCredentials; -import org.pac4j.core.profile.CommonProfile; -import org.pac4j.http.client.direct.HeaderClient; -import org.pac4j.jwt.config.encryption.EncryptionConfiguration; -import org.pac4j.jwt.config.encryption.SecretEncryptionConfiguration; -import org.pac4j.jwt.config.signature.SecretSignatureConfiguration; -import org.pac4j.jwt.config.signature.SignatureConfiguration; -import org.pac4j.jwt.credentials.authenticator.JwtAuthenticator; -import org.pac4j.jwt.profile.JwtGenerator; - -import javax.inject.Inject; -import java.time.Instant; -import java.time.temporal.ChronoUnit; -import java.util.Date; - -public final record AuthUtils(String encryptionSecret, String signatureSecret, - String nexusWebhookSecret, String internalHeaderSecret, - String internalHeaderKey) { - - @SuppressWarnings("rawtypes") - public Config config(final Client... additionalClients) { - final var jwtClient = this.createJwtClient(); - final var config = new Config(List.of(jwtClient).appendAll(List.of(additionalClients)).asJava()); - config.getClients().setDefaultSecurityClients(jwtClient.getName()); - this.setAuthorizers(config); - return config; - } - - public ServiceCall internalAuth(ServiceCall call) { - return call.handleRequestHeader(header -> header.withHeader(this.internalHeaderKey, this.internalHeaderSecret)); - } - - - private DirectClient createJwtClient() { - final var headerClient = new HeaderClient(); - headerClient.setName(AuthUtils.Types.JWT); - headerClient.setHeaderName(HttpConstants.AUTHORIZATION_HEADER); - headerClient.setPrefixHeader(HttpConstants.BEARER_HEADER_PREFIX); - - final var jwtAuthenticator = new JwtAuthenticator(); - if (!this.signatureSecret.isBlank()) { - jwtAuthenticator.addSignatureConfiguration(this.getSignatureConfiguration()); - } - if (!this.encryptionSecret.isBlank()) { - jwtAuthenticator.addEncryptionConfiguration(this.getEncryptionConfiguration()); - } - headerClient.setAuthenticator(jwtAuthenticator); // this should provide the correct profile automagically. - headerClient.setName(AuthUtils.Types.JWT); - return headerClient; - } - - public JwtGenerator createJwtGenerator() { - final var generator = new JwtGenerator<>(); - if (!this.signatureSecret.isBlank()) { - generator.setSignatureConfiguration(this.getSignatureConfiguration()); - } - if (!this.encryptionSecret.isBlank()) { - generator.setEncryptionConfiguration(this.getEncryptionConfiguration()); - } - generator.setExpirationTime(Date.from(Instant.now().plus(10, ChronoUnit.MINUTES))); - return generator; - } - - private void setAuthorizers(final Config config) { - config.addAuthorizer(Roles.ADMIN, Authorizers.ADMIN); - config.addAuthorizer(Roles.WEBHOOK, Authorizers.WEBHOOK); - } - - private EncryptionConfiguration getEncryptionConfiguration() { - return new SecretEncryptionConfiguration(this.encryptionSecret); - } - - private SignatureConfiguration getSignatureConfiguration() { - return new SecretSignatureConfiguration(this.signatureSecret); - } - - private SignatureConfiguration getSonatypeSignatureConfiguration() { - return new SecretSignatureConfiguration(this.nexusWebhookSecret); - } - - @Inject - public static AuthUtils configure(com.typesafe.config.Config config) { - final var authConfig = config.getConfig("systemofadownload.auth.secrets"); - final var encryptionSecret = authConfig.getString("encryption"); - final var signatureSecret = authConfig.getString("signature"); - final var nexusWebhookSecret = authConfig.getString("nexus-webhook"); - final var internalHeaderKey = authConfig.getString("internal-header"); - final var internalHeaderSecret = authConfig.getString("internal-secret"); - return new AuthUtils( - encryptionSecret, signatureSecret, nexusWebhookSecret, internalHeaderSecret, internalHeaderKey); - } - - static final class Authorizers { - static final Authorizer ADMIN = - (webContext, list) -> list.stream().anyMatch( - x -> !x.isExpired() && x.getRoles().contains(AuthUtils.Roles.ADMIN)); - static final Authorizer WEBHOOK = - (webContext, list) -> list.stream().anyMatch( - x -> !x.isExpired() && x.getRoles().contains(AuthUtils.Roles.WEBHOOK)); - } - - public static final class Types { - public static final String JWT = "jwt"; - public static final String WEBHOOK = "internal"; - } - - public static final class Roles { - public static final String ADMIN = "soad_admin"; - public static final String WEBHOOK = "soad_webhook"; - } -} diff --git a/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthModule.java b/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthModule.java deleted file mode 100644 index 2904d6c6..00000000 --- a/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthModule.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth; - -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.google.inject.name.Named; -import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; -import org.ldaptive.ConnectionConfig; -import org.ldaptive.DefaultConnectionFactory; -import org.ldaptive.auth.FormatDnResolver; -import org.ldaptive.auth.PooledBindAuthenticationHandler; -import org.ldaptive.pool.BlockingConnectionPool; -import org.ldaptive.pool.IdlePruneStrategy; -import org.ldaptive.pool.PoolConfig; -import org.ldaptive.pool.PooledConnectionFactory; -import org.ldaptive.pool.SearchValidator; -import org.pac4j.core.client.DirectClient; -import org.pac4j.core.config.Config; -import org.pac4j.core.credentials.UsernamePasswordCredentials; -import org.pac4j.core.credentials.authenticator.Authenticator; -import org.pac4j.core.exception.CredentialsException; -import org.pac4j.core.profile.CommonProfile; -import org.pac4j.http.client.direct.DirectBasicAuthClient; -import org.pac4j.jwt.profile.JwtGenerator; -import org.pac4j.ldap.profile.service.LdapProfileService; -import org.spongepowered.downloads.auth.api.AuthService; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import play.Environment; - -import javax.inject.Inject; -import java.time.Duration; -import java.util.Collection; -import java.util.concurrent.TimeUnit; - -// See: https://github.com/pac4j/lagom-pac4j-java-demo -// -// We could alternatively go for Cookie auth that uses LDAP as an initial check, but -// JWT may suffice - we can then just use LDAP as a way enable JWT generation. We can also just -// require LDAP every time... but that's probably not what we want. -// -// JWT is great as we won't want to store sessions. However, it's not great in the sense that it'll -// be hard to expire them when done with - but we could just generate secrets on the fly when necessary -// which expire themselves after some time, and can be forcefully expired when needed. -// -// JWTs should be short lived anyway. -public final class AuthModule extends AbstractModule implements ServiceGuiceSupport { - - private boolean useDummyCredentials; - private String ldapUrl; - private String ldapBaseUserDn; - private String ldapSoadOu; - - private Duration connectionTimeout; - private Duration responseTimeout; - private Duration blockWaitTime; - - private final com.typesafe.config.Config config; - private AuthUtils auth; - - @Inject - public AuthModule(final Environment environment, final com.typesafe.config.Config config) { - this.config = config; - } - - @Override - protected void configure() { - final var authConfig = this.config.getConfig("systemofadownload.auth"); - final var ldap = authConfig.getConfig("ldap"); - this.useDummyCredentials = authConfig.getBoolean("use-dummy-ldap"); - this.ldapUrl = ldap.getString("url"); - this.ldapBaseUserDn = ldap.getString("base-user-on"); - this.ldapSoadOu = ldap.getString("soad-ou"); - this.connectionTimeout = Duration.ofMillis(ldap.getDuration("connection-timeout", TimeUnit.MILLISECONDS)); - this.responseTimeout = Duration.ofSeconds(ldap.getDuration("response-timeout", TimeUnit.SECONDS)); - this.blockWaitTime = Duration.ofSeconds(ldap.getDuration("wait-time", TimeUnit.SECONDS)); - this.auth = AuthUtils.configure(this.config); - this.bindService(AuthService.class, AuthServiceImpl.class); - } - - // TODO: If we'd rather use a GraphQL endpoint to log in, rather than - // a header, then we don't need this - we do our LDAP query manually and - // create a JWT in the body of the request/response method. - @Provides - @Named(AuthService.Providers.LDAP) - protected DirectClient providerHeaderLDAPClient() { - final var basicAuthClient = new DirectBasicAuthClient(); - basicAuthClient.setName(AuthService.Providers.LDAP); - - final Authenticator authenticator; - // TODO: Ditch this once we're running. - if (this.useDummyCredentials) { - authenticator = ((usernamePasswordCredentials, webContext) -> { - if (usernamePasswordCredentials.getUsername().equals("soad") && usernamePasswordCredentials.getPassword().equals("systemofadownload")) { - final var profile = new CommonProfile(); - profile.setId("soad"); - usernamePasswordCredentials.setUserProfile(profile); - profile.addRole(AuthUtils.Roles.ADMIN); - return; - } - throw new CredentialsException("Incorrect username and password."); - }); - } else { - // http://www.pac4j.org/3.4.x/docs/authenticators/ldap.html - // This could probably be improved... but the documentation leaves something to be desired - final var dnResolver = new FormatDnResolver(); - dnResolver.setFormat("cn=%s," + this.ldapBaseUserDn); - final var connectionConfig = new ConnectionConfig(); - connectionConfig.setConnectTimeout(this.connectionTimeout); - connectionConfig.setResponseTimeout(this.responseTimeout); - connectionConfig.setLdapUrl(this.ldapUrl); - final var connectionFactory = new DefaultConnectionFactory(); - connectionFactory.setConnectionConfig(connectionConfig); - final var poolConfig = new PoolConfig(); - poolConfig.setMinPoolSize(1); - poolConfig.setMaxPoolSize(2); - poolConfig.setValidateOnCheckOut(true); - poolConfig.setValidateOnCheckIn(true); - poolConfig.setValidatePeriodically(false); - final var searchValidator = new SearchValidator(); - final var pruneStrategy = new IdlePruneStrategy(); - final var connectionPool = new BlockingConnectionPool(); - connectionPool.setPoolConfig(poolConfig); - connectionPool.setBlockWaitTime(this.blockWaitTime); - connectionPool.setValidator(searchValidator); - connectionPool.setPruneStrategy(pruneStrategy); - connectionPool.setConnectionFactory(connectionFactory); - connectionPool.initialize(); - final var pooledConnectionFactory = new PooledConnectionFactory(); - pooledConnectionFactory.setConnectionPool(connectionPool); - // dnResolver.setConnectionFactory(pooledConnectionFactory); - final var handler = new PooledBindAuthenticationHandler(); - handler.setConnectionFactory(pooledConnectionFactory); - final var ldaptiveAuthenticator = new org.ldaptive.auth.Authenticator(); - ldaptiveAuthenticator.setDnResolver(dnResolver); - ldaptiveAuthenticator.setAuthenticationHandler(handler); - - authenticator = new LdapProfileService(pooledConnectionFactory, ldaptiveAuthenticator, "ou", this.ldapBaseUserDn); - } - - // If we're IP whitelisting it, we just need to check the webcontext. Otherwise we'll want to - // add tokens and such - basicAuthClient.setAuthenticator(authenticator); - basicAuthClient.setAuthorizationGenerator((webContext, profile) -> { - final var ouAttr = profile.getAttribute("ou"); - final boolean isAdmin; - if (ouAttr instanceof String) { - isAdmin = ouAttr.equals(this.ldapSoadOu); - } else if (ouAttr instanceof Collection) { - isAdmin = ((Collection) ouAttr).contains(this.ldapSoadOu); - } else { - isAdmin = false; - } - - if (isAdmin) { - profile.addRole(AuthUtils.Roles.ADMIN); - } - return profile; - }); - return basicAuthClient; - } - - @Provides - @SOADAuth - protected JwtGenerator provideJwtGenerator() { - return this.auth.createJwtGenerator(); - } - - @Provides - @SOADAuth - protected Config configProvider(@Named(AuthService.Providers.LDAP) final DirectClient ldapClient) { - return this.auth.config(ldapClient); - } - -} diff --git a/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthServiceImpl.java b/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthServiceImpl.java deleted file mode 100644 index 632a3242..00000000 --- a/auth-impl/src/main/java/org/spongepowered/downloads/auth/AuthServiceImpl.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth; - -import akka.NotUsed; -import com.google.inject.Inject; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import org.pac4j.core.config.Config; -import org.pac4j.core.profile.CommonProfile; -import org.pac4j.jwt.profile.JwtGenerator; -import org.pac4j.lagom.javadsl.SecuredService; -import org.spongepowered.downloads.auth.api.AuthService; -import org.spongepowered.downloads.auth.api.AuthenticationRequest; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; - -import java.sql.Date; -import java.time.Duration; -import java.time.Instant; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; - -public final class AuthServiceImpl implements AuthService, SecuredService { - - private final Config securityConfig; - private final JwtGenerator profileJwtGenerator; - private final Duration expirationTime; - - @Inject - public AuthServiceImpl( - @SOADAuth final Config config, @SOADAuth final JwtGenerator profileJwtGenerator, - com.typesafe.config.Config applicationConfig - ) { - this.securityConfig = config; - this.profileJwtGenerator = profileJwtGenerator; - this.expirationTime = Duration.ofSeconds( - applicationConfig.getDuration("systemofadownload.auth.expiration", TimeUnit.SECONDS)); - } - - @Override - public ServiceCall login() { - return this.authorize(Providers.LDAP, AuthUtils.Roles.ADMIN, profile -> { - this.profileJwtGenerator.setExpirationTime(Date.from(Instant.now().plus(this.expirationTime))); - return notUsed -> CompletableFuture.completedFuture(new AuthenticationRequest.Response(this.profileJwtGenerator.generate(profile))); - }); - } - - @Override - public ServiceCall logout() { - // TODO - if it's even possible - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> { - return notUsed -> CompletableFuture.completedFuture(NotUsed.getInstance()); - }); - } - - @Override - public Config getSecurityConfig() { - return this.securityConfig; - } -} diff --git a/auth-impl/src/main/resources/application.conf b/auth-impl/src/main/resources/application.conf deleted file mode 100644 index 31b86ef2..00000000 --- a/auth-impl/src/main/resources/application.conf +++ /dev/null @@ -1,20 +0,0 @@ - -play.http.secret.key = ${?APPLICATION_SECRET} - -play.modules { - enabled += "org.spongepowered.downloads.auth.AuthModule" -} - -systemofadownload.auth { - use-dummy-ldap = true - expiration = "1h" -} - -play.filters.enabled += "play.filters.cors.CORSFilter" -play.filters.csrf.header.protectHeaders = null -play.filters.disabled += "play.filters.csrf.CSRFFilter" -play.filters.cors { - pathPrefixes = ["/auth"] - allowedHttpMethods = ["GET", "POST"] - preflightMaxAge = 3 days -} diff --git a/auth-impl/src/main/resources/logback.xml b/auth-impl/src/main/resources/logback.xml deleted file mode 100644 index 9bd2dad8..00000000 --- a/auth-impl/src/main/resources/logback.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - System.out - - %date{hh:MM:ss.SSS} [%level] [%thread] [%logger{5}/%marker] - %coloredLevel %msg%n - - - - - 8192 - true - - - - - - - - - - - - - - - - diff --git a/auth-impl/src/main/resources/reference.conf b/auth-impl/src/main/resources/reference.conf deleted file mode 100644 index 9f66f9c6..00000000 --- a/auth-impl/src/main/resources/reference.conf +++ /dev/null @@ -1,13 +0,0 @@ -systemofadownload.auth { - use-dummy-ldap = false - expiration = "300s" - ldap { - url = "ldap://localhost:389" - base-user-on = "dc=spongepowered,dc=org" - soad-ou = "soad" - - connection-timeout = 500 - response-timeout = "1s" - wait-time = "1s" - } -} diff --git a/build.gradle.kts b/build.gradle.kts index 026df055..aaf4fe26 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,117 +1,65 @@ - +import org.gradle.plugins.ide.idea.model.IdeaLanguageLevel version = "0.1" group = "systemofadownload" +plugins { + `java-library` + id("com.github.johnrengelman.shadow") version "8.1.1" apply false + id("io.micronaut.library") version "4.0.2" apply false + id("io.micronaut.application") version "4.0.2" apply false + id("io.micronaut.docker") version "4.0.2" apply false + id("io.micronaut.test-resources") version "4.0.2" apply false +} + repositories { mavenCentral() } -plugins { - `java-library` - `application` - id("com.github.johnrengelman.shadow") - id("io.micronaut.application") - id("io.micronaut.test-resources") +tasks { + register("runLiquibase", Exec::class) { + executable("docker") + args( + "run", + "--rm", + "--mount", "type=bind,source=${project.projectDir.absolutePath}/liquibase/changelog,target=/liquibase/changelog,readonly", + "--network=host", + "liquibase/liquibase:4.23-alpine", + "--logLevel=info", + "--url=jdbc:postgresql://localhost:5432/default", + "--defaultsFile=/liquibase/changelog/liquibase.properties", + "--changeLogFile=changelog.xml", + "--classpath=/liquibase/changelog", + "--username=admin", + "--password=password", + "update") + } } -val akkaVersion: String by project -val scalaVersion: String by project -val akkaManagementVersion: String by project -val akkaProjection: String by project allprojects { - apply(plugin = "java-library") - apply(plugin = "io.micronaut.application") - apply(plugin = "io.micronaut.test-resources") - apply(plugin = "com.github.johnrengelman.shadow") - java { - sourceCompatibility = JavaVersion.VERSION_17 - targetCompatibility = JavaVersion.VERSION_17 - if (JavaVersion.current() < JavaVersion.VERSION_17) { + sourceCompatibility = JavaVersion.VERSION_20 + targetCompatibility = JavaVersion.VERSION_20 + if (JavaVersion.current() < JavaVersion.VERSION_20) { toolchain { - languageVersion.set(JavaLanguageVersion.of(17)) + languageVersion.set(JavaLanguageVersion.of(20)) } } } -} - -dependencies { - annotationProcessor("io.micronaut.data:micronaut-data-processor") - annotationProcessor("io.micronaut:micronaut-http-validation") - annotationProcessor("io.micronaut.openapi:micronaut-openapi") - annotationProcessor("io.micronaut.security:micronaut-security-annotations") - annotationProcessor("io.micronaut.serde:micronaut-serde-processor") - implementation("com.ongres.scram:client:2.1") - implementation("io.micronaut:micronaut-http-client") - implementation("io.micronaut:micronaut-jackson-databind") - implementation("io.micronaut.data:micronaut-data-r2dbc") - implementation("io.micronaut.liquibase:micronaut-liquibase") - implementation("io.micronaut.reactor:micronaut-reactor") - implementation("io.micronaut.reactor:micronaut-reactor-http-client") - implementation("io.micronaut.security:micronaut-security-ldap") - implementation("io.micronaut.serde:micronaut-serde-jackson") - implementation("io.micronaut.toml:micronaut-toml") - implementation("io.micronaut.xml:micronaut-jackson-xml") - implementation("io.swagger.core.v3:swagger-annotations") - implementation("io.vertx:vertx-pg-client") - implementation("jakarta.annotation:jakarta.annotation-api") - implementation(platform("com.typesafe.akka:akka-bom_${scalaVersion}:${akkaVersion}")) - implementation("com.typesafe.akka:akka-actor-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-persistence-typed_${scalaVersion}") - implementation("com.lightbend.akka:akka-projection-core_${scalaVersion}:${akkaProjection}") - implementation("com.typesafe.akka:akka-cluster-sharding-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-cluster-typed_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.typesafe.akka:akka-discovery_${scalaVersion}") - implementation("com.lightbend.akka.management:akka-management_${scalaVersion}:${akkaManagementVersion}") - implementation("com.lightbend.akka.management:akka-management-cluster-bootstrap_${scalaVersion}:${akkaManagementVersion}") - - runtimeOnly("ch.qos.logback:logback-classic") - runtimeOnly("org.postgresql:postgresql") - runtimeOnly("org.postgresql:r2dbc-postgresql") - compileOnly("org.graalvm.nativeimage:svm") - - implementation("io.micronaut:micronaut-validation") - -} - - -application { - mainClass.set("systemofadownload.Application") -} -tasks { - dockerBuild { - images.add("${project.name}:${project.version}") - } - dockerBuildNative { - images.add("${project.name}:${project.version}") - - } -} -graalvmNative.toolchainDetection.set(false) -micronaut { - runtime("netty") - testRuntime("junit5") - processing { - incremental(true) - annotations("systemofadownload.*") - } - testResources { - additionalModules.add("r2dbc-postgresql") - } -} -graalvmNative { - binaries { - named("main") { - imageName.set("mn-graalvm-application") - buildArgs("--verboase") + tasks { + withType { + options.compilerArgs.add("--enable-preview") + } + withType { + jvmArgs("--enable-preview") } } + } + diff --git a/build.sbt b/build.sbt deleted file mode 100644 index 1cd077bb..00000000 --- a/build.sbt +++ /dev/null @@ -1,358 +0,0 @@ -import com.lightbend.lagom.core.LagomVersion -import com.typesafe.sbt.packager.docker.DockerChmodType -import de.heikoseeberger.sbtheader.HeaderPlugin.autoImport.{HeaderLicenseStyle, headerLicenseStyle} - -import scala.sys.process.Process - -ThisBuild / organization := "org.spongepowered" -ThisBuild / version := "0.2-SNAPSHOT" -ThisBuild / scalaVersion := "2.13.8" - -// License setup -ThisBuild / organizationName := "SpongePowered" -ThisBuild / startYear := Some(2020) -ThisBuild / licenses += ("MIT", url("https://opensource.org/licenses/MIT")) -ThisBuild / scmInfo := Some(ScmInfo(url("https://github.com/SpongePowered/SystemOfADownload"), - "scm:git@github.com:spongepowered/systemofadownload.git")) -ThisBuild / developers := List( - Developer( - id = "gabizou", - name = "Gabriel Harris-Rouquette", - email = "gabizou@spongepowered.org", - url = url("https://github.com/gabizou") - ) -) -ThisBuild / description := "A Web Application for indexing and cataloging Artifacts in Maven Repositories" -ThisBuild / homepage := Some(url("https://github.com/SpongePowered/SystemOfADownload")) -ThisBuild / pomIncludeRepository := { _ => false } -ThisBuild / publishMavenStyle := true -ThisBuild / versionScheme := Some("early-semver") - -// Basically, because of sbt's limited publishing to only one repository, -// we can take advantage of environment variables to publish to multiple -// repositories within the same job. -ThisBuild / publishTo := { - (sys.env.get("REPO_NAME"), sys.env.get("REPO_CREDENTIAL_FILE"), sys.env.get("SONATYPE_SNAPSHOT_REPO"), sys.env.get("SONATYPE_RELEASE_REPO")) match { - case (Some(name), Some(repoTarget), Some(snapshotRepo), Some(releaseRepo)) => { - credentials += Credentials(Path.userHome / ".sbt" / repoTarget) - if (isSnapshot.value) - Some(name at snapshotRepo) - else - Some(name at releaseRepo) - } - case (_, _, _, _) => None - } -} - -// Deployed Repositories -// TODO - Figure out deploying to our sonatype and to maven central -// Then also figure out deploying docker images??? - -// Liquibase Docker Tasks -lazy val buildLiquibaseImage = taskKey[Unit]("Build the Liquibase docker image") -val rootFilter = ScopeFilter(inProjects(soadRoot), inConfigurations(Compile)) -buildLiquibaseImage := { - val versionTag = version.all(rootFilter).value.head - Process(Seq("docker", "buildx", "build", "-t", s"spongepowered/systemofadownload-liquibase:$versionTag", "./liquibase/", "-f", "./liquibase/Dockerfile")).! -} - -lazy val runLiquibase = taskKey[Unit]("Runs the liquibase migration against a local dev database") - -lazy val setupDevEnvironment = taskKey[Unit]("Runs the necessary commands to set up a local environment to run the application") - -lazy val setupPostgres = taskKey[Unit]("Runs a postgres instance for local development") -lazy val setupKafka = taskKey[Unit]("Runs Kafka and zookeeper instance for local development") -setupDevEnvironment := { - setupPostgres.value - runLiquibase.value - setupKafka.value -} -setupPostgres := { - Process(Seq("sh", - s"${soadRoot.base.absolutePath}/dev/run_postgres.sh", - )).! -} - -setupKafka := { - Process(Seq("sh", s"${soadRoot.base.absolutePath}/dev/run_kafka.sh")).! -} - -runLiquibase := { - Process(Seq("docker", - "run", - "--rm", - "--mount", s"type=bind,source=${soadRoot.base.absolutePath}/liquibase/changelog,target=/liquibase/changelog,readonly", - "--network=host", - "liquibase/liquibase", - "--logLevel=info", - s"--url=jdbc:postgresql://localhost:5432/default", - "--defaultsFile=/liquibase/changelog/liquibase.properties", - "--changeLogFile=changelog.xml", - "--classpath=/liquibase/changelog", - "--username=admin", - "--password=password", - "update")).! -} - -// region dependency versions - -//noinspection SbtDependencyVersionInspection -ThisBuild / libraryDependencySchemes += "org.scala-lang.modules" %% "scala-java8-compat" % "always" - -lazy val vavr = "io.vavr" % "vavr" % "0.10.4" -lazy val vavrJackson = "io.vavr" % "vavr-jackson" % "0.10.3" - -lazy val pac4jHttp = "org.pac4j" % "pac4j-http" % "3.7.0" -lazy val pac4jJwt = "org.pac4j" % "pac4j-jwt" % "3.7.0" - -lazy val lagomPac4j = "org.pac4j" %% "lagom-pac4j" % "2.2.1" -lazy val lagomPac4jLdap = "org.pac4j" % "pac4j-ldap" % "3.7.0" - -// Enable Junit5 -lazy val junit = "org.junit.jupiter" % "junit-jupiter-api" % "5.9.0" % Test -// sbt-jupiter-interface -lazy val jupiterInterface = "net.aichler" % "jupiter-interface" % "0.11.1" % Test - - -// Play jackson uses 2.11, but 2.12 is backwards compatible -lazy val jacksonDataBind = "com.fasterxml.jackson.core" % "jackson-databind" % "2.14.0" -lazy val jacksonDataTypeJsr310 = "com.fasterxml.jackson.datatype" % "jackson-datatype-jsr310" % "2.14.0" -lazy val jacksonDataformatXml = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-xml" % "2.14.0" -lazy val jacksonDataformatCbor = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-cbor" % "2.14.0" -lazy val jacksonDatatypeJdk8 = "com.fasterxml.jackson.datatype" % "jackson-datatype-jdk8" % "2.14.0" -lazy val jacksonParameterNames = "com.fasterxml.jackson.module" % "jackson-module-parameter-names" % "2.14.0" -lazy val jacksonParanamer = "com.fasterxml.jackson.module" % "jackson-module-paranamer" % "2.14.0" -lazy val jacksonScala = "com.fasterxml.jackson.module" %% "jackson-module-scala" % "2.14.0" -lazy val jacksonGuava = "com.fasterxml.jackson.datatype" % "jackson-datatype-guava" % "2.14.0" -lazy val jacksonPcollections = "com.fasterxml.jackson.datatype" % "jackson-datatype-pcollections" % "2.14.0" -// endregion - -lazy val akkaHttp = "com.typesafe.akka" %% "akka-http" % LagomVersion.akkaHttp -lazy val akkaJackson = "com.typesafe.akka" %% "akka-http-jackson" % LagomVersion.akkaHttp - -lazy val akkaStreamTyped = "com.typesafe.akka" %% "akka-stream-typed" % LagomVersion.akka -lazy val akkaPersistenceTestkit = "com.typesafe.akka" %% "akka-persistence-testkit" % LagomVersion.akka % Test -lazy val akkaKubernetesDiscovery = "com.lightbend.akka.discovery" %% "akka-discovery-kubernetes-api" % "1.1.3" - -lazy val playFilterHelpers = "com.typesafe.play" %% "filters-helpers" % LagomVersion.play - -lazy val hibernate = "org.hibernate" % "hibernate-core" % "5.5.6" -lazy val postgres = "org.postgresql" % "postgresql" % "42.5.0" -lazy val hibernateTypes = "com.vladmihalcea" % "hibernate-types-55" % "2.20.0" - -lazy val guice = "com.google.inject" % "guice" % "5.1.0" - -lazy val jgit = "org.eclipse.jgit" % "org.eclipse.jgit" % "6.1.0.202203080745-r" -lazy val jgit_jsch = "org.eclipse.jgit" % "org.eclipse.jgit.ssh.jsch" % "6.1.0.202203080745-r" - -lazy val mavenArtifact = "org.apache.maven" % "maven-artifact" % "3.8.5" - -lazy val testContainers = "org.testcontainers" % "testcontainers" % "1.17.3" % Test -lazy val testContainersJunit = "org.testcontainers" % "junit-jupiter" % "1.17.3" % Test -lazy val testContainersPostgres = "org.testcontainers" % "postgresql" % "1.17.3" % Test - -// endregion - -// region - project blueprints - -def soadProject(name: String) = - Project(name, file(name)).settings( - moduleName := s"systemofadownload-$name", - Compile / javacOptions := Seq("--release", "17", "--enable-preview", "-parameters", "-encoding", "UTF-8"), //Override the settings Lagom sets - artifactName := { (_: ScalaVersion, module: ModuleID, artifact: Artifact) => - s"${artifact.name}-${module.revision}.${artifact.extension}" - }, - Compile / doc / sources := Seq.empty, - Compile / packageDoc / publishArtifact := false, - compileOrder := CompileOrder.JavaThenScala, //Needed so scalac doesn't try to parse the files - headerLicense := Some(HeaderLicense.Custom( - """This file is part of SystemOfADownload, licensed under the MIT License (MIT). - | - |Copyright (c) SpongePowered - |Copyright (c) contributors - | - |Permission is hereby granted, free of charge, to any person obtaining a copy - |of this software and associated documentation files (the "Software"), to deal - |in the Software without restriction, including without limitation the rights - |to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - |copies of the Software, and to permit persons to whom the Software is - |furnished to do so, subject to the following conditions: - | - |The above copyright notice and this permission notice shall be included in - |all copies or substantial portions of the Software. - | - |THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - |IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - |FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - |AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - |LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - |OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - |THE SOFTWARE. - |""".stripMargin - )), - headerLicenseStyle := HeaderLicenseStyle.SpdxSyntax, - headerEmptyLine := false - - ) - -def apiSoadProject(name: String) = - soadProject(name).settings( - libraryDependencies ++= Seq( - // Lagom Java API - lagomJavadslApi, - // Bump Jackson over Lagom's jackson - jacksonDataBind, - jacksonDataTypeJsr310, - jacksonDataformatCbor, - jacksonDatatypeJdk8, - jacksonParameterNames, - jacksonParanamer, - jacksonScala, - jacksonGuava, // Eventually not needed when we migrate off lagom - jacksonPcollections, // Eventually not needed when we migrate off lagom - // Then add the Lagom Jackson modules - lagomJavadslJackson, - //Language Features - vavr, - // Override guice from Lagom to support Java 16 - guice - ) - ) - -def serverSoadProject(name: String) = - soadProject(name) - .enablePlugins(LagomJava, DockerPlugin) - .settings( - libraryDependencies ++= Seq( - // Bump Jackson over Lagom's jackson - jacksonDataBind, - jacksonDataTypeJsr310, - jacksonDataformatCbor, - jacksonDatatypeJdk8, - jacksonParameterNames, - jacksonParanamer, - jacksonScala, - jacksonGuava, // Eventually not needed when we migrate off lagom - jacksonPcollections, // Eventually not needed when we migrate off lagom - //Lagom Dependencies - // Specifically set up Akka-Clustering - lagomJavadslCluster, - // Set up Discovery between Services - lagomJavadslAkkaDiscovery, - akkaKubernetesDiscovery, - // I mean, we are a server, aren't we? - lagomJavadslServer, - // Set up logging - lagomLogback, - //Language Features for Serialization/Deserialization - vavrJackson, - // Override guice from Lagom to support Java 16 - guice, - // Ensure the play filter helpers are included - playFilterHelpers, - //Test Dependencies - lagomJavadslTestKit, - junit, // Always enable Junit 5 - jupiterInterface - ) - ).settings( - dockerUpdateLatest := true, - dockerBaseImage := "eclipse-temurin:17.0.3_7-jre", - dockerChmodType := DockerChmodType.UserGroupWriteExecute, - dockerExposedPorts := Seq(9000, 8558, 2552), - dockerLabels ++= Map( - "author" -> "spongepowered" - ), - Docker / maintainer := "spongepowered", - Docker / packageName := s"systemofadownload-$name", - dockerUsername := Some("spongepowered"), - Universal / javaOptions ++= Seq( - "-Dpidfile.path=/dev/null" - ) - ) - -def implSoadProject(name: String, implFor: Project) = - serverSoadProject(name).dependsOn( - //The service we're implementing - implFor - ).settings( - libraryDependencies ++= Seq( - // We use kafka for all inter-service message forwarding - lagomJavadslKafkaBroker - ) - ) - -def implSoadProjectWithPersistence(name: String, implFor: Project) = - implSoadProject(name, implFor).settings( - libraryDependencies ++= Seq( - //Lagom Database Dependencies - lagomJavadslPersistenceJpa, - //Database Dependencies - hibernate, - postgres, - akkaPersistenceTestkit - ) - ) - -// endregion - -// region Project Definitions - -lazy val `downloads-api` = soadProject("downloads-api").enablePlugins(DockerPlugin) - .settings( - libraryDependencies ++= Seq( - // App - mavenArtifact, - - // Akka - akkaHttp, - akkaStreamTyped, - akkaKubernetesDiscovery, - - // Jackson serialization - akkaJackson, - jacksonDataBind, - jacksonDataTypeJsr310, - jacksonDataformatCbor, - jacksonDatatypeJdk8, - jacksonParameterNames, - jacksonParanamer, - jacksonScala, - //Language Features - vavr, - // Persistence - hibernate, - postgres, - // Testing - akkaPersistenceTestkit, - junit, - jupiterInterface - ), - dockerUpdateLatest := true, - dockerBaseImage := "eclipse-temurin:17.0.3_7-jre", - dockerChmodType := DockerChmodType.UserGroupWriteExecute, - dockerExposedPorts := Seq(9000, 8558, 2552), - // dockerLabels ++= Map( - // "author" -> "spongepowered" - // ), - Docker / maintainer := "spongepowered", - Docker / packageName := s"systemofadownload-$name", - dockerUsername := Some("spongepowered"), - Universal / javaOptions ++= Seq( - "-Dpidfile.path=/dev/null" - ) - ) - - -// endregion - -lazy val soadRoot = project.in(file(".")).settings( - name := "SystemOfADownload" -).aggregate( -) - -ThisBuild / lagomCassandraEnabled := false -ThisBuild / lagomKafkaEnabled := false -ThisBuild / lagomKafkaPort := 9092 -ThisBuild / lagomServicesPortRange := PortRange(21000, 23000) - diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts new file mode 100644 index 00000000..dee3e800 --- /dev/null +++ b/buildSrc/settings.gradle.kts @@ -0,0 +1,9 @@ + +dependencyResolutionManagement { + + versionCatalogs { + create("akka") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/buildSrc/src/main/kotlin/soad.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/soad.java-conventions.gradle.kts deleted file mode 100644 index 58082c01..00000000 --- a/buildSrc/src/main/kotlin/soad.java-conventions.gradle.kts +++ /dev/null @@ -1,20 +0,0 @@ -plugins { - id("java") - id("application") - id("com.github.johnrengelman.shadow") version "7.1.2" - id("io.micronaut.application") version "3.7.0" - id("io.micronaut.test-resources") version "3.7.0" -} - -group = "org.spongepowered" -version = "1.0" -repositories { - mavenCentral() -} - -dependencies { - - id("com.github.johnrengelman.shadow") version "7.1.2" - id("io.micronaut.application") version "3.7.0" - id("io.micronaut.test-resources") version "3.7.0" -} diff --git a/dev/run_postgres.sh b/dev/run_postgres.sh index 3cdf3bf6..c4760d07 100755 --- a/dev/run_postgres.sh +++ b/dev/run_postgres.sh @@ -17,6 +17,6 @@ if [ ! "$(docker ps -q -f name="${postgres_name}")" ]; then -e POSTGRES_PASSWORD=password \ -e POSTGRES_DB=default \ -p 5432:5432 \ - postgres:13-alpine \ + postgres:15-alpine \ postgres -N 500 fi diff --git a/downloads-api/src/main/java/com/example/QuickstartApp.java b/downloads-api/src/main/java/com/example/QuickstartApp.java deleted file mode 100644 index ac1187ee..00000000 --- a/downloads-api/src/main/java/com/example/QuickstartApp.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.example; - -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.http.javadsl.ConnectHttp; -import akka.http.javadsl.Http; -import akka.http.javadsl.ServerBinding; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.HttpResponse; -import akka.http.javadsl.server.Route; -import akka.stream.Materializer; -import akka.stream.javadsl.Flow; -import akka.actor.typed.javadsl.Adapter; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.ActorSystem; - -import java.net.InetSocketAddress; -import java.util.concurrent.CompletionStage; - -//#main-class -public class QuickstartApp { - // #start-http-server - static void startHttpServer(Route route, ActorSystem system) { - CompletionStage futureBinding = - Http.get(system).newServerAt("localhost", 8080).bind(route); - - futureBinding.whenComplete((binding, exception) -> { - if (binding != null) { - InetSocketAddress address = binding.localAddress(); - system.log().info("Server online at http://{}:{}/", - address.getHostString(), - address.getPort()); - } else { - system.log().error("Failed to bind HTTP endpoint, terminating system", exception); - system.terminate(); - } - }); - } - // #start-http-server - - public static void main(String[] args) throws Exception { - //#server-bootstrapping - Behavior rootBehavior = Behaviors.setup(context -> { - ActorRef userRegistryActor = - context.spawn(UserRegistry.create(), "UserRegistry"); - - UserRoutes userRoutes = new UserRoutes(context.getSystem(), userRegistryActor); - startHttpServer(userRoutes.userRoutes(), context.getSystem()); - - return Behaviors.empty(); - }); - - // boot up server using the route as defined below - ActorSystem.create(rootBehavior, "HelloAkkaHttpServer"); - //#server-bootstrapping - } - -} -//#main-class - - diff --git a/downloads-api/src/main/java/com/example/UserRegistry.java b/downloads-api/src/main/java/com/example/UserRegistry.java deleted file mode 100644 index c8d914d2..00000000 --- a/downloads-api/src/main/java/com/example/UserRegistry.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.example; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.AbstractBehavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Receive; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.*; - -//#user-registry-actor -public class UserRegistry extends AbstractBehavior { - - // actor protocol - interface Command {} - - public record GetUsers(ActorRef replyTo) implements Command { - } - - public record CreateUser(User user, ActorRef replyTo) implements Command { - } - - public record GetUserResponse(Optional maybeUser) { - } - - public record GetUser(String name, ActorRef replyTo) implements Command { - } - - - public record DeleteUser(String name, ActorRef replyTo) implements Command { - } - - - public record ActionPerformed(String description) implements Command { - } - - //#user-case-classes - public record User( - @JsonProperty("name") String name, - @JsonProperty("age") int age, - @JsonProperty("countryOfResidence") String countryOfResidence - ) { - @JsonCreator - public User {} - } - - public record Users(List users) { } - //#user-case-classes - - private final List users = new ArrayList<>(); - - private UserRegistry(ActorContext context) { - super(context); - } - - public static Behavior create() { - return Behaviors.setup(UserRegistry::new); - } - - @Override - public Receive createReceive() { - return newReceiveBuilder() - .onMessage(GetUsers.class, this::onGetUsers) - .onMessage(CreateUser.class, this::onCreateUser) - .onMessage(GetUser.class, this::onGetUser) - .onMessage(DeleteUser.class, this::onDeleteUser) - .build(); - } - - private Behavior onGetUsers(GetUsers command) { - // We must be careful not to send out users since it is mutable - // so for this response we need to make a defensive copy - command.replyTo.tell(new Users(List.copyOf(users))); - return this; - } - - private Behavior onCreateUser(CreateUser command) { - users.add(command.user); - command.replyTo.tell(new ActionPerformed(String.format("User %s created.", command.user.name))); - return this; - } - - private Behavior onGetUser(GetUser command) { - Optional maybeUser = users.stream() - .filter(user -> user.name.equals(command.name)) - .findFirst(); - command.replyTo.tell(new GetUserResponse(maybeUser)); - return this; - } - - private Behavior onDeleteUser(DeleteUser command) { - users.removeIf(user -> user.name.equals(command.name)); - command.replyTo.tell(new ActionPerformed(String.format("User %s deleted.", command.name))); - return this; - } - -} -//#user-registry-actor diff --git a/downloads-api/src/main/java/com/example/UserRoutes.java b/downloads-api/src/main/java/com/example/UserRoutes.java deleted file mode 100644 index 6eea10f3..00000000 --- a/downloads-api/src/main/java/com/example/UserRoutes.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.example; - -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletionStage; - -import com.example.UserRegistry.User; -import akka.actor.typed.ActorRef; -import akka.actor.typed.ActorSystem; -import akka.actor.typed.Scheduler; -import akka.actor.typed.javadsl.AskPattern; -import akka.http.javadsl.marshallers.jackson.Jackson; - -import static akka.http.javadsl.server.Directives.*; - -import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.PathMatchers; -import akka.http.javadsl.server.Route; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Routes can be defined in separated classes like shown in here - */ -//#user-routes-class -public class UserRoutes { - //#user-routes-class - private final static Logger log = LoggerFactory.getLogger(UserRoutes.class); - private final ActorRef userRegistryActor; - private final Duration askTimeout; - private final Scheduler scheduler; - - public UserRoutes(ActorSystem system, ActorRef userRegistryActor) { - this.userRegistryActor = userRegistryActor; - scheduler = system.scheduler(); - askTimeout = system.settings().config().getDuration("my-app.routes.ask-timeout"); - } - - private CompletionStage getUser(String name) { - return AskPattern.ask(userRegistryActor, ref -> new UserRegistry.GetUser(name, ref), askTimeout, scheduler); - } - - private CompletionStage deleteUser(String name) { - return AskPattern.ask(userRegistryActor, ref -> new UserRegistry.DeleteUser(name, ref), askTimeout, scheduler); - } - - private CompletionStage getUsers() { - return AskPattern.ask(userRegistryActor, UserRegistry.GetUsers::new, askTimeout, scheduler); - } - - private CompletionStage createUser(User user) { - return AskPattern.ask(userRegistryActor, ref -> new UserRegistry.CreateUser(user, ref), askTimeout, scheduler); - } - - /** - * This method creates one route (of possibly many more that will be part of your Web App) - */ - //#all-routes - public Route userRoutes() { - return pathPrefix("users", () -> - concat( - //#users-get-delete - pathEnd(() -> - concat( - get(() -> - onSuccess(getUsers(), - users -> complete(StatusCodes.OK, users, Jackson.marshaller()) - ) - ), - post(() -> - entity( - Jackson.unmarshaller(User.class), - user -> - onSuccess(createUser(user), performed -> { - log.info("Create result: {}", performed.description()); - return complete(StatusCodes.CREATED, performed, Jackson.marshaller()); - }) - ) - ) - ) - ), - //#users-get-delete - //#users-get-post - path(PathMatchers.segment(), (String name) -> - concat( - get(() -> - //#retrieve-user-info - rejectEmptyResponse(() -> - onSuccess(getUser(name), performed -> - complete(StatusCodes.OK, performed.maybeUser(), Jackson.marshaller()) - ) - ) - //#retrieve-user-info - ), - delete(() -> - //#users-delete-logic - onSuccess(deleteUser(name), performed -> { - log.info("Delete result: {}", performed.description()); - return complete(StatusCodes.OK, performed, Jackson.marshaller()); - } - ) - //#users-delete-logic - ) - ) - ) - //#users-get-post - ) - ); - } - //#all-routes - -} diff --git a/downloads-api/src/main/java/module-info.java b/downloads-api/src/main/java/module-info.java deleted file mode 100644 index e4c4aee5..00000000 --- a/downloads-api/src/main/java/module-info.java +++ /dev/null @@ -1,17 +0,0 @@ -module org.spongepowered.downloads.app { - exports org.spongepowered.downloads.api; - - requires akka.actor.typed; - requires akka.http; - requires akka.http.core; - requires akka.stream; - requires akka.actor; - requires com.fasterxml.jackson.annotation; - requires akka.http.marshallers.jackson; - requires org.slf4j; - requires org.hibernate.orm.core; - requires java.persistence; - requires io.vavr; - requires com.fasterxml.jackson.databind; - requires maven.artifact; -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/api/Artifact.java b/downloads-api/src/main/java/org/spongepowered/downloads/api/Artifact.java deleted file mode 100644 index a1a4cb4c..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/api/Artifact.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.net.URI; -import java.util.Optional; - -@JsonSerialize -public final record Artifact( - @JsonProperty Optional classifier, - @JsonProperty URI downloadUrl, - @JsonProperty String md5, - @JsonProperty String sha1, - @JsonProperty String extension -) { - @JsonCreator - public Artifact { - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCollection.java b/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCollection.java deleted file mode 100644 index a3b29fe8..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCollection.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -@JsonDeserialize -public final record ArtifactCollection( - @JsonProperty("assets") List components, - @JsonProperty("coordinates") MavenCoordinates coordinates -) { - - @JsonCreator - public ArtifactCollection { - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCoordinates.java b/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCoordinates.java deleted file mode 100644 index b9f11573..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/api/ArtifactCoordinates.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.util.StringJoiner; - -@JsonDeserialize -public record ArtifactCoordinates( - @JsonProperty(required = true) String groupId, - @JsonProperty(required = true) String artifactId -) { - @JsonCreator - public ArtifactCoordinates { - } - - public MavenCoordinates version(String version) { - return MavenCoordinates.parse( - new StringJoiner(":").add(this.groupId()).add(this.artifactId()).add(version).toString()); - } - - public String asMavenString() { - return this.groupId() + ":" + this.artifactId(); - } - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String groupId() { - return groupId; - } - - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String artifactId() { - return artifactId; - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/api/Group.java b/downloads-api/src/main/java/org/spongepowered/downloads/api/Group.java deleted file mode 100644 index 4efe1286..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/api/Group.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -@JsonDeserialize -public record Group( - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String website -) { - - @JsonCreator - public Group { - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/api/MavenCoordinates.java b/downloads-api/src/main/java/org/spongepowered/downloads/api/MavenCoordinates.java deleted file mode 100644 index cab9ae7a..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/api/MavenCoordinates.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.apache.maven.artifact.versioning.ComparableVersion; - -import java.util.Objects; -import java.util.StringJoiner; -import java.util.regex.Pattern; - -@JsonDeserialize -public final class MavenCoordinates implements Comparable { - - private static final Pattern MAVEN_REGEX = Pattern.compile("^[-\\w.]+$"); - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String groupId; - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String artifactId; - /** - * The version of an artifact, as defined by the Apache Maven documentation. This is - * traditionally specified as a Maven repository searchable version string, such as - * {@code 1.0.0-SNAPSHOT}. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String version; - - @JsonIgnore - public final VersionType versionType; - - @JsonIgnore - private final ComparableVersion mavenVersion; - - /** - * Parses a set of maven formatted coordinates as per - * Apache - * Maven's documentation. - * - * @param coordinates The coordinates delimited by `:` - * @return A parsed set of MavenCoordinates - */ - public static MavenCoordinates parse(final String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - return new MavenCoordinates(groupId, artifactId, version); - } - - public MavenCoordinates(String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonCreator - public MavenCoordinates( - @JsonProperty("groupId") final String groupId, - @JsonProperty("artifactId") final String artifactId, - @JsonProperty("version") final String version - ) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonIgnore - public String asStandardCoordinates() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.versionType.asStandardVersionString(this.version)) - .toString(); - } - - @JsonIgnore - public boolean isSnapshot() { - return this.versionType.isSnapshot(); - } - - @JsonIgnore - public ArtifactCoordinates asArtifactCoordinates() { - return new ArtifactCoordinates(this.groupId, this.artifactId); - } - - @Override - public String toString() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.version) - .toString(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || this.getClass() != o.getClass()) { - return false; - } - final MavenCoordinates that = (MavenCoordinates) o; - return Objects.equals(this.groupId, that.groupId) && Objects.equals( - this.artifactId, that.artifactId) && Objects.equals(this.version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.artifactId, this.version); - } - - @Override - public int compareTo(final MavenCoordinates o) { - final var group = this.groupId.compareTo(o.groupId); - if (group != 0) { - return group; - } - final var artifact = this.artifactId.compareTo(o.artifactId); - if (artifact != 0) { - return artifact; - } - return this.mavenVersion.compareTo(o.mavenVersion); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/app/SystemOfADownloadsApp.java b/downloads-api/src/main/java/org/spongepowered/downloads/app/SystemOfADownloadsApp.java deleted file mode 100644 index f5e37946..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/app/SystemOfADownloadsApp.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.spongepowered.downloads.app; - -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.ActorSystem; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.http.javadsl.Http; -import akka.http.javadsl.ServerBinding; -import akka.http.javadsl.server.Route; -import org.spongepowered.downloads.artifacts.ArtifactQueries; -import org.spongepowered.downloads.artifacts.ArtifactRoutes; - -import java.net.InetSocketAddress; -import java.util.concurrent.CompletionStage; - -public final class SystemOfADownloadsApp { - - public static void main(final String[] args) { - //#server-bootstrapping - Behavior rootBehavior = Behaviors.setup(context -> { - ActorRef userRegistryActor = - context.spawn(ArtifactQueries.create(), "ArtifactQueries"); - - ArtifactRoutes userRoutes = new ArtifactRoutes(context.getSystem(), userRegistryActor); - startHttpServer(userRoutes.artifactRoutes(), context.getSystem()); - - return Behaviors.empty(); - }); - - // boot up server using the route as defined below - ActorSystem.create(rootBehavior, "HelloAkkaHttpServer"); - //#server-bootstrapping - } - - static void startHttpServer(Route route, ActorSystem system) { - CompletionStage futureBinding = - Http.get(system).newServerAt("localhost", 8080).bind(route); - - futureBinding.whenComplete((binding, exception) -> { - if (binding != null) { - InetSocketAddress address = binding.localAddress(); - system.log().info("Server online at http://{}:{}/", - address.getHostString(), - address.getPort()); - } else { - system.log().error("Failed to bind HTTP endpoint, terminating system", exception); - system.terminate(); - } - }); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactQueries.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactQueries.java deleted file mode 100644 index a066952c..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactQueries.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.spongepowered.downloads.artifacts; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.AbstractBehavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Receive; -import org.spongepowered.downloads.artifacts.transport.GetArtifactDetailsResponse; -import org.spongepowered.downloads.artifacts.transport.GetArtifactsResponse; -import org.spongepowered.downloads.artifacts.transport.GroupResponse; -import org.spongepowered.downloads.artifacts.transport.GroupsResponse; - -public class ArtifactQueries extends AbstractBehavior { - - public static Behavior create() { - return Behaviors.receive(Command.class) - .build(); - } - - public ArtifactQueries(final ActorContext context) { - super(context); - } - - public sealed interface Command { - - record GetGroup( - String groupId, - ActorRef replyTo - ) implements Command { - } - - record GetArtifacts( - String groupId, - ActorRef replyTo - ) implements Command { - } - - record GetGroups( - ActorRef replyTo - ) implements Command { - } - - record GetArtifactDetails( - String groupId, - String artifactId, - ActorRef replyTo - ) implements Command { - } - - } - - - @Override - public Receive createReceive() { - return this.newReceiveBuilder() - .build(); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactRoutes.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactRoutes.java deleted file mode 100644 index 1733a99c..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/ArtifactRoutes.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.spongepowered.downloads.artifacts; - -import static akka.http.javadsl.server.Directives.complete; -import static akka.http.javadsl.server.Directives.concat; -import static akka.http.javadsl.server.Directives.get; -import static akka.http.javadsl.server.Directives.onSuccess; -import static akka.http.javadsl.server.Directives.path; -import static akka.http.javadsl.server.Directives.pathPrefix; -import static akka.http.javadsl.server.Directives.rejectEmptyResponse; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.ActorSystem; -import akka.actor.typed.Scheduler; -import akka.actor.typed.javadsl.AskPattern; -import akka.http.javadsl.marshallers.jackson.Jackson; -import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.PathMatchers; -import akka.http.javadsl.server.Route; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongepowered.downloads.artifacts.transport.GetArtifactsResponse; -import org.spongepowered.downloads.artifacts.transport.GroupResponse; -import org.spongepowered.downloads.artifacts.transport.GroupsResponse; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -public final class ArtifactRoutes { - - public static final Logger logger = LoggerFactory.getLogger(ArtifactRoutes.class); - - private final ActorRef artifactQueries; - private final Duration askTimeout; - private final Scheduler scheduler; - - public ArtifactRoutes(ActorSystem system, ActorRef artifactQueries) { - this.artifactQueries = artifactQueries; - scheduler = system.scheduler(); - askTimeout = system.settings().config().getDuration("my-app.routes.ask-timeout"); - } - - private CompletionStage getGroups() { - return AskPattern.ask(artifactQueries, ArtifactQueries.Command.GetGroups::new, askTimeout, scheduler); - } - - private CompletionStage getGroup(String name) { - return AskPattern.ask( - artifactQueries, ref -> new ArtifactQueries.Command.GetGroup(name, ref), askTimeout, scheduler); - } - - private CompletionStage getArtifacts(String name) { - return AskPattern.ask( - artifactQueries, ref -> new ArtifactQueries.Command.GetArtifacts(name, ref), askTimeout, scheduler); - } - - private Route getGroup() { - return get(() -> onSuccess(getGroups(), groups -> - complete(StatusCodes.OK, groups, Jackson.marshaller()) - )); - } - - /** - * This method creates one route (of possibly many more that will be part of your Web App) - */ - public Route artifactRoutes() { - // v1/groups - return pathPrefix("groups", () -> - concat( - getGroup(), - // v1/groups/:groupId/ - path(PathMatchers.segment(), (String groupId) -> - concat( - get(() -> rejectEmptyResponse(() -> - onSuccess(getGroup(groupId), performed -> { - final var group = performed.group(); - logger.info("Groups get: {}", group); - if (group.isEmpty()) { - return complete(StatusCodes.BAD_REQUEST, "group not found"); - } - return complete(StatusCodes.OK, group, Jackson.marshaller()); - }))), - // v1/groups/:groupId/artifacts - pathPrefix("artifacts", () -> get(() -> rejectEmptyResponse(() -> - onSuccess(getArtifacts(groupId), performed -> { - logger.info("Get result: {}", performed.artifactIds()); - return complete(StatusCodes.OK, performed, Jackson.marshaller()); - }))) - )) - ) - )); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactDetails.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactDetails.java deleted file mode 100644 index 7fad1598..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactDetails.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.control.Either; -import io.vavr.control.Try; - -import java.net.URL; - -public final class ArtifactDetails { - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Update.Website.class, - name = "website"), - @JsonSubTypes.Type(value = Update.DisplayName.class, - name = "displayName"), - @JsonSubTypes.Type(value = Update.Issues.class, - name = "issues"), - @JsonSubTypes.Type(value = Update.GitRepository.class, - name = "gitRepository"), - }) - @JsonDeserialize - public sealed interface Update { - - Either validate(); - - record Website( - @JsonProperty(required = true) String website - ) implements Update { - - @JsonCreator - public Website { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.website())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.website()))); - } - } - - record DisplayName( - @JsonProperty(required = true) String display - ) implements Update { - - @JsonCreator - public DisplayName { - } - - @Override - public Either validate() { - return Either.right(this.display.trim()); - } - } - - record Issues( - @JsonProperty(required = true) String issues - ) implements Update { - @JsonCreator - public Issues { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.issues())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.issues()))); - } - } - - record GitRepository( - @JsonProperty(required = true) String gitRepo - ) implements Update { - - @JsonCreator - public GitRepository { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.gitRepo())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.gitRepo()))); - } - } - } - - @JsonSerialize - public record Response( - String name, - String displayName, - String website, - String issues, - String gitRepo - ) { - - } - - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactRegistration.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactRegistration.java deleted file mode 100644 index 4ab6dcc7..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/ArtifactRegistration.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -public final class ArtifactRegistration { - - @JsonSerialize - public record RegisterArtifact( - @JsonProperty(required = true) String artifactId, - @JsonProperty(required = true) String displayName - ) { - - @JsonCreator - public RegisterArtifact(final String artifactId, final String displayName) { - this.artifactId = artifactId; - this.displayName = displayName; - } - - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Response.GroupMissing.class, - name = "UnknownGroup"), - @JsonSubTypes.Type(value = Response.ArtifactRegistered.class, - name = "RegisteredArtifact"), - @JsonSubTypes.Type(value = Response.ArtifactAlreadyRegistered.class, - name = "AlreadyRegistered"), - }) - public sealed interface Response extends Jsonable { - - @JsonSerialize - record ArtifactRegistered(@JsonProperty ArtifactCoordinates coordinates) implements Response { - - } - - @JsonSerialize - record ArtifactAlreadyRegistered( - @JsonProperty String artifactName, - @JsonProperty String groupId - ) implements Response { - - } - - @JsonSerialize - record GroupMissing(@JsonProperty("groupId") String s) implements Response { - - } - - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactDetailsResponse.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactDetailsResponse.java deleted file mode 100644 index 065628bf..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactDetailsResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.Map; -import io.vavr.collection.SortedSet; -import org.spongepowered.downloads.api.ArtifactCoordinates; - -import java.io.Serializable; -import java.util.Optional; - -@JsonSerialize -public record GetArtifactDetailsResponse(Optional maybeArtifact) { - - @JsonSerialize - record RetrievedArtifact( - ArtifactCoordinates coordinates, - String displayName, - String website, - String gitRepository, - String issues, - Map> tags - ) implements Serializable { - - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactsResponse.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactsResponse.java deleted file mode 100644 index f22f221b..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GetArtifactsResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; - -import java.io.Serializable; - - -@JsonSerialize -public record GetArtifactsResponse(@JsonProperty List artifactIds) implements Serializable { - @JsonCreator - public GetArtifactsResponse {} -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupRegistration.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupRegistration.java deleted file mode 100644 index e7815bb9..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupRegistration.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.Group; - -public final class GroupRegistration { - - @JsonDeserialize - public record RegisterGroupRequest( - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String website - ) { - - @JsonCreator - public RegisterGroupRequest { } - - } - - public interface Response extends Jsonable { - - record GroupAlreadyRegistered(String groupNameRequested) implements Response { - } - - record GroupRegistered(Group group) implements Response { - - } - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupResponse.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupResponse.java deleted file mode 100644 index 2a3e2790..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupResponse.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import org.spongepowered.downloads.api.Group; - -import java.io.Serializable; -import java.util.Optional; - -@JsonSerialize -public record GroupResponse(Optional group) implements Serializable { - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupsResponse.java b/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupsResponse.java deleted file mode 100644 index c2947a8a..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/artifacts/transport/GroupsResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.artifacts.transport; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import org.spongepowered.downloads.api.Group; - -@JsonSerialize -public record GroupsResponse( - @JsonProperty("groups") - List groups -) { - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/routes/VersionRoutes.java b/downloads-api/src/main/java/org/spongepowered/downloads/routes/VersionRoutes.java deleted file mode 100644 index 7d35e43f..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/routes/VersionRoutes.java +++ /dev/null @@ -1,4 +0,0 @@ -package org.spongepowered.downloads.routes; - -public class VersionRoutes { -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionQueries.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionQueries.java deleted file mode 100644 index b4723af2..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionQueries.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.spongepowered.downloads.versions; - -import akka.actor.typed.ActorRef; -import org.spongepowered.downloads.api.ArtifactCoordinates; -import org.spongepowered.downloads.api.MavenCoordinates; -import org.spongepowered.downloads.versions.transport.QueryLatest; -import org.spongepowered.downloads.versions.transport.QueryVersions; - -class VersionQueries { - - sealed interface Command { - record GetVersion(MavenCoordinates coordinates, ActorRef replyTo) implements Command {} - record GetVersions(ArtifactCoordinates coordinates, ActorRef replyTo) implements Command {} - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionRoutes.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionRoutes.java deleted file mode 100644 index 0a44794b..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/VersionRoutes.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.spongepowered.downloads.versions; - - -import static akka.http.javadsl.server.Directives.complete; -import static akka.http.javadsl.server.Directives.concat; -import static akka.http.javadsl.server.Directives.get; -import static akka.http.javadsl.server.Directives.onSuccess; -import static akka.http.javadsl.server.Directives.pathPrefix; -import static akka.http.javadsl.server.PathMatchers.segment; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.ActorSystem; -import akka.actor.typed.Scheduler; -import akka.actor.typed.javadsl.AskPattern; -import akka.http.javadsl.marshallers.jackson.Jackson; -import akka.http.javadsl.model.StatusCodes; -import akka.http.javadsl.server.PathMatchers; -import akka.http.javadsl.server.Route; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongepowered.downloads.api.ArtifactCoordinates; -import org.spongepowered.downloads.versions.transport.QueryVersions; - -import java.time.Duration; -import java.util.concurrent.CompletionStage; - -public final class VersionRoutes { - - public static final Logger logger = LoggerFactory.getLogger( - org.spongepowered.downloads.artifacts.ArtifactRoutes.class); - - private final ActorRef artifactQueries; - private final Duration askTimeout; - private final Scheduler scheduler; - - public VersionRoutes(ActorSystem system, ActorRef artifactQueries) { - this.artifactQueries = artifactQueries; - scheduler = system.scheduler(); - askTimeout = system.settings().config().getDuration("my-app.routes.ask-timeout"); - } - - CompletionStage getVersions(ArtifactCoordinates coordinates) { - return AskPattern.ask(this.artifactQueries, ref -> new VersionQueries.Command.GetVersions( - coordinates, ref - ), this.askTimeout, this.scheduler); - } - - - /** - * This method creates one route (of possibly many more that will be part of your Web App) - */ - public Route artifactRoutes() { - // v1/groups - return pathPrefix( - segment("groups").slash(segment()).slash(segment("artifacts").slash(segment())), - (groupId, artifactId) -> - concat( - get(() -> onSuccess(this.getVersions(new ArtifactCoordinates(groupId, artifactId)), (resp) -> { - if (resp.size() <= 0) { - return complete(StatusCodes.NOT_FOUND); - } - return complete(StatusCodes.OK, resp, Jackson.marshaller()); - })), - path(PathMatchers.segment("versions").slash(), version -> get(() -> - onSuccess(this.getVersions(), resp -> { - return complete(StatusCodes.OK, resp, Jackson.marshaller()); - }))) - - ) - ); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaTaggedVersion.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaTaggedVersion.java deleted file mode 100644 index b8d61a97..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaTaggedVersion.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.models; - -import org.hibernate.annotations.Immutable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.ManyToOne; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Objects; - -@Entity(name = "TaggedVersion") -@Immutable -@Table(name = "versioned_tags", schema = "version") -@NamedQueries({ - @NamedQuery(name = "TaggedVersion.findAllForVersion", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId and view.mavenArtifactId = :artifactId and view.version = :version - """ - ), - @NamedQuery( - name = "TaggedVersion.findAllMatchingTagValues", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId - and view.mavenArtifactId = :artifactId - and view.tagName = :tagName - and view.tagValue like :tagValue - """ - ), - @NamedQuery( - name = "TaggedVersion.findMatchingTagValuesAndRecommendation", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId - and view.mavenArtifactId = :artifactId - and view.tagName = :tagName - and view.tagValue like :tagValue - and (view.versionView.recommended = :recommended or view.versionView.manuallyRecommended = :recommended) - """ - ) -}) -public class JpaTaggedVersion implements Serializable { - - @Id - @Column(name = "version_id", updatable = false) - private long versionId; - - @Id - @Column(updatable = false, name = "artifact_internal_id") - private long artifactId; - - @Id - @Column(name = "maven_group_id", updatable = false) - private String mavenGroupId; - - @Id - @Column(name = "maven_artifact_id", updatable = false) - private String mavenArtifactId; - - @Id - @Column(name = "maven_version", - updatable = false) - private String version; - - @Id - @Column(name = "tag_name", - updatable = false) - private String tagName; - - @Column(name = "tag_value", - updatable = false) - private String tagValue; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "maven_version", - referencedColumnName = "version", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "maven_group_id", - referencedColumnName = "group_id", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "maven_artifact_id", - referencedColumnName = "artifact_id", - nullable = false, - updatable = false, - insertable = false) - }) - private JpaVersionedArtifactView versionView; - - public String getTagName() { - return tagName; - } - - public String getTagValue() { - return tagValue; - } - - public MavenCoordinates asMavenCoordinates() { - return new MavenCoordinates(this.mavenGroupId, this.mavenArtifactId, this.version); - } - - public JpaVersionedArtifactView getVersion() { - return this.versionView; - } - - public String getMavenVersion() { - return this.version; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaTaggedVersion that = (JpaTaggedVersion) o; - return versionId == that.versionId && artifactId == that.artifactId && Objects.equals( - mavenGroupId, that.mavenGroupId) && Objects.equals( - mavenArtifactId, that.mavenArtifactId) && Objects.equals( - version, that.version) && Objects.equals(tagName, that.tagName) && Objects.equals( - tagValue, that.tagValue); - } - - @Override - public int hashCode() { - return Objects.hash(versionId, artifactId, mavenGroupId, mavenArtifactId, version, tagName, tagValue); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedArtifactView.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedArtifactView.java deleted file mode 100644 index 5b9a2f40..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedArtifactView.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.models; - -import io.vavr.Tuple; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.hibernate.annotations.Immutable; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.query.api.models.TagCollection; -import org.spongepowered.downloads.versions.query.api.models.VersionedChangelog; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.net.URI; -import java.util.Comparator; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@Immutable -@Entity(name = "VersionedArtifactView") -@Table(name = "versioned_artifacts", - schema = "version") -@NamedQueries({ - @NamedQuery( - name = "VersionedArtifactView.count", - query = """ - select count(v) from VersionedArtifactView v - where v.groupId = :groupId and v.artifactId = :artifactId - """ - ), - @NamedQuery( - name = "VersionedArtifactView.recommendedCount", - query = """ - select count(v) from VersionedArtifactView v - where v.groupId = :groupId and v.artifactId = :artifactId and v.recommended = :recommended - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findByArtifact", - query = """ - select v from VersionedArtifactView v where v.artifactId = :artifactId and v.groupId = :groupId - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findByArtifactAndRecommendation", - query = """ - select v from VersionedArtifactView v - where v.artifactId = :artifactId and v.groupId = :groupId and (v.recommended = :recommended or v.manuallyRecommended = :recommended) - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findExplicitly", - query = """ - select v from VersionedArtifactView v - left join fetch v.tags - where v.artifactId = :artifactId and v.groupId = :groupId and v.version = :version - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findFullVersionDetails", - query = """ - select v from VersionedArtifactView v - left join fetch v.tags - left join fetch v.assets - where v.artifactId = :artifactId and v.groupId = :groupId and v.version = :version - """ - ) -}) -public class JpaVersionedArtifactView implements Serializable { - - @Id - @Column(name = "artifact_id", - updatable = false) - private String artifactId; - - @Id - @Column(name = "group_id", - updatable = false) - private String groupId; - - @Id - @Column(name = "version", - updatable = false) - private String version; - - @Column(name = "recommended") - private boolean recommended; - - @Column(name = "manual_recommendation") - private boolean manuallyRecommended; - - @Column(name = "ordering") - private int ordering; - - @OneToMany( - targetEntity = JpaTaggedVersion.class, - fetch = FetchType.LAZY, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "versionView") - private Set tags; - - @OneToMany( - targetEntity = JpaVersionedAsset.class, - cascade = CascadeType.ALL, - fetch = FetchType.LAZY, - orphanRemoval = true, - mappedBy = "versionView" - ) - private Set assets; - - @OneToOne( - targetEntity = JpaVersionedChangelog.class, - mappedBy = "versionView", - fetch = FetchType.LAZY - ) - private JpaVersionedChangelog changelog; - - public Set getTags() { - return tags; - } - - public String version() { - return this.version; - } - - public Map getTagValues() { - final var results = this.getTags(); - final var tuple2Stream = results.stream().map( - taggedVersion -> Tuple.of(taggedVersion.getTagName(), taggedVersion.getTagValue())); - return tuple2Stream - .collect(HashMap.collector()); - } - - public TagCollection asTagCollection() { - final var results = this.getTags(); - final var tuple2Stream = results.stream().map( - taggedVersion -> Tuple.of(taggedVersion.getTagName(), taggedVersion.getTagValue())); - return new TagCollection(tuple2Stream - .collect(HashMap.collector()), this.recommended); - } - - public boolean isRecommended() { - return this.recommended || this.manuallyRecommended; - } - - public MavenCoordinates asMavenCoordinates() { - return new MavenCoordinates(this.groupId + ":" + this.artifactId + ":" + this.version); - } - - Set getAssets() { - return assets; - } - - public List asArtifactList() { - return this.getAssets() - .stream() - .map( - asset -> new Artifact( - Optional.ofNullable(asset.getClassifier()), - URI.create(asset.getDownloadUrl()), - new String(asset.getMd5()), - new String(asset.getSha1()), - asset.getExtension() - ) - ).collect(List.collector()) - .sorted(Comparator.comparing(artifact -> artifact.classifier().orElse(""))); - } - - public Optional asVersionedCommit() { - final var changelog = this.changelog; - if (changelog == null) { - return Optional.empty(); - } - return Optional.of(changelog.getChangelog()); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedArtifactView that = (JpaVersionedArtifactView) o; - return Objects.equals(artifactId, that.artifactId) && Objects.equals( - groupId, that.groupId) && Objects.equals(version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(artifactId, groupId, version); - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedAsset.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedAsset.java deleted file mode 100644 index 3f3cd1d4..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedAsset.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.models; - -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.Type; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.IdClass; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Objects; - -@Immutable -@Entity(name = "VersionedAsset") -@Table(name = "artifact_versioned_assets", - schema = "version") -@IdClass(VersionedAssetID.class) -public class JpaVersionedAsset implements Serializable { - - @Id - @Column(name = "group_id") - private String groupId; - @Id - @Column(name = "artifact_id") - private String artifactId; - - @Id - @Column(name = "version") - private String version; - - @Id - @Column(name = "classifier") - private String classifier; - - @Id - @Column(name = "extension") - private String extension; - - @Column(name = "download_url") - private String downloadUrl; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "md5") - private byte[] md5; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "sha1") - private byte[] sha1; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "version", - referencedColumnName = "version", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "group_id", - referencedColumnName = "group_id", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "artifact_id", - referencedColumnName = "artifact_id", - nullable = false, - updatable = false, - insertable = false) - }) - private JpaVersionedArtifactView versionView; - - public String getClassifier() { - return classifier; - } - - public String getExtension() { - return extension; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public byte[] getMd5() { - return md5; - } - - public byte[] getSha1() { - return sha1; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedAsset that = (JpaVersionedAsset) o; - return Objects.equals(groupId, that.groupId) && Objects.equals( - artifactId, that.artifactId) && Objects.equals(version, that.version) && Objects.equals( - classifier, that.classifier) && Objects.equals(extension, that.extension) && Objects.equals( - downloadUrl, that.downloadUrl) && Arrays.equals(md5, that.md5) && Arrays.equals( - sha1, that.sha1) && Objects.equals(versionView, that.versionView); - } - - @Override - public int hashCode() { - int result = Objects.hash(groupId, artifactId, version, classifier, extension, downloadUrl, versionView); - result = 31 * result + Arrays.hashCode(md5); - result = 31 * result + Arrays.hashCode(sha1); - return result; - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedChangelog.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedChangelog.java deleted file mode 100644 index 68396320..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/JpaVersionedChangelog.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.models; - -import com.vladmihalcea.hibernate.type.json.JsonBinaryType; -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; -import org.spongepowered.downloads.versions.query.api.models.VersionedChangelog; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.net.URL; -import java.util.Objects; - -@Immutable -@Entity(name = "VersionedChangelog") -@Table( - name = "versioned_changelogs", - schema = "version" -) -@TypeDefs({ - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -}) -public class JpaVersionedChangelog implements Serializable { - - @Id - @Column(name = "version_id", updatable = false) - private String versionId; - - @Id - @Column(name = "group_id", updatable = false) - private String groupId; - - @Id - @Column(name = "artifact_id", updatable = false) - private String artifactId; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "version", referencedColumnName = "version", insertable = false, updatable = false), - @JoinColumn(name = "group_id", referencedColumnName = "group_id", insertable = false, updatable = false), - @JoinColumn(name = "artifact_id", referencedColumnName = "artifact_id", insertable = false, updatable = false) - }) - private JpaVersionedArtifactView versionView; - - @Column(name = "commit_sha", nullable = false) - private String sha; - - @Type(type = "jsonb") - @Column(name = "changelog", columnDefinition = "jsonb") - private VersionedChangelog changelog; - - @Column(name = "repo") - private URL repo; - - @Column(name = "branch") - private String branch; - - public String getSha() { - return sha; - } - - public VersionedChangelog getChangelog() { - return changelog; - } - - public URL getRepo() { - return repo; - } - - public String getBranch() { - return branch; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedChangelog that = (JpaVersionedChangelog) o; - return Objects.equals(versionId, that.versionId) && Objects.equals( - groupId, that.groupId) && Objects.equals(artifactId, that.artifactId) && Objects.equals( - versionView, that.versionView) && Objects.equals(sha, that.sha) && Objects.equals( - changelog, that.changelog) && Objects.equals(repo, that.repo) && Objects.equals( - branch, that.branch); - } - - @Override - public int hashCode() { - return Objects.hash(versionId, groupId, artifactId, versionView, sha, changelog, repo, branch); - } - - @Override - public String toString() { - return "JpaVersionedChangelog{" + - "versionId='" + versionId + '\'' + - ", groupId='" + groupId + '\'' + - ", artifactId='" + artifactId + '\'' + - ", versionView=" + versionView + - ", sha='" + sha + '\'' + - ", changelog=" + changelog + - ", repo=" + repo + - ", branch='" + branch + '\'' + - '}'; - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedArtifactID.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedArtifactID.java deleted file mode 100644 index 3156c74d..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedArtifactID.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.models; - -import java.io.Serializable; -import java.util.Objects; - -public class VersionedArtifactID implements Serializable { - private String artifactId; - - private String groupId; - - private String version; - - public VersionedArtifactID(final String artifactId, final String groupId, final String version) { - this.artifactId = artifactId; - this.groupId = groupId; - this.version = version; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - VersionedArtifactID that = (VersionedArtifactID) o; - return artifactId.equals(that.artifactId) && groupId.equals(that.groupId) && version.equals(that.version); - } - - @Override - public int hashCode() { - return Objects.hash(artifactId, groupId, version); - } -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedAssetID.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedAssetID.java deleted file mode 100644 index 7bbba094..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/models/VersionedAssetID.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.spongepowered.downloads.versions.models; - -import java.io.Serializable; - -public record VersionedAssetID( - String groupId, - String artifactId, - String version, - String classifier, - String extension - ) implements Serializable { -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryLatest.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryLatest.java deleted file mode 100644 index 80d4a01d..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryLatest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.transport; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.util.Optional; - -public interface QueryLatest { - - @JsonSerialize - record VersionInfo(MavenCoordinates coordinates, - List assets, - Map tagValues, - Optional commit, - boolean recommended - ) { - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryVersions.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryVersions.java deleted file mode 100644 index 2ac34a11..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/QueryVersions.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.transport; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.api.Artifact; -import org.spongepowered.downloads.api.MavenCoordinates; - -import java.util.Optional; - -public interface QueryVersions { - - @JsonSerialize - record VersionInfo(@JsonProperty Map artifacts, int offset, int limit, int size) { - - @JsonCreator - public VersionInfo { - } - } - - @JsonSerialize - record VersionDetails( - @JsonProperty("coordinates") MavenCoordinates coordinates, - @JsonProperty("commit") Optional commit, - @JsonProperty("assets") List components, - @JsonProperty("tags") Map tagValues, - @JsonProperty("recommended") boolean recommended - ) { - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/TagCollection.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/TagCollection.java deleted file mode 100644 index f740b7a8..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/TagCollection.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.transport; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.Map; - -@JsonSerialize -public record TagCollection( - Map tagValues, - boolean recommended -) { -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedChangelog.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedChangelog.java deleted file mode 100644 index efb8d8f0..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedChangelog.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.transport; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -import java.net.URI; - -@JsonDeserialize -public final record VersionedChangelog( - List commits, - @JsonInclude(JsonInclude.Include.NON_DEFAULT) boolean processing -) { - - @JsonCreator - public VersionedChangelog { - } - - @JsonDeserialize - public final record IndexedCommit( - VersionedCommit commit, - List submoduleCommits - ) { - @JsonCreator - public IndexedCommit { - } - } - - @JsonDeserialize - public final record Submodule( - String name, - URI gitRepository, - List commits - ) { - @JsonCreator - public Submodule { - } - } - -} diff --git a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedCommit.java b/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedCommit.java deleted file mode 100644 index 869d6888..00000000 --- a/downloads-api/src/main/java/org/spongepowered/downloads/versions/transport/VersionedCommit.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.transport; - - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.net.URI; -import java.time.ZonedDateTime; - -@JsonDeserialize -public record VersionedCommit( - String message, - String body, - String sha, - Author author, - Commiter commiter, - URI link, - ZonedDateTime commitDate -) { - - @JsonCreator - public VersionedCommit { - } - - @JsonDeserialize - public record Author( - String name, - String email - ) { - @JsonCreator - public Author { - } - } - - @JsonDeserialize - public record Commiter( - String name, - String email - ) { - @JsonCreator - public Commiter { - } - } -} - diff --git a/downloads-api/src/main/resources/application.conf b/downloads-api/src/main/resources/application.conf deleted file mode 100644 index acd17dfa..00000000 --- a/downloads-api/src/main/resources/application.conf +++ /dev/null @@ -1,6 +0,0 @@ -my-app { - routes { - # If ask takes more time than this to complete the request is failed - ask-timeout = 5s - } -} diff --git a/downloads-api/src/main/resources/logback.xml b/downloads-api/src/main/resources/logback.xml deleted file mode 100644 index b1fe9ae9..00000000 --- a/downloads-api/src/main/resources/logback.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - [%date{ISO8601}] [%level] [%logger] [%thread] [%X{akkaSource}] - %msg%n - - - - - 1024 - true - - - - - - - - diff --git a/downloads-api/src/test/java/com/example/UserRoutesTest.java b/downloads-api/src/test/java/com/example/UserRoutesTest.java deleted file mode 100644 index 02896cae..00000000 --- a/downloads-api/src/test/java/com/example/UserRoutesTest.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.example; - - -//#test-top -import akka.actor.typed.ActorRef; -import akka.http.javadsl.model.*; -import akka.http.javadsl.testkit.JUnitRouteTest; -import akka.http.javadsl.testkit.TestRoute; -import org.junit.*; -import org.junit.runners.MethodSorters; -import akka.http.javadsl.model.HttpRequest; -import akka.http.javadsl.model.StatusCodes; -import akka.actor.testkit.typed.javadsl.TestKitJunitResource; - - -//#set-up -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class UserRoutesTest extends JUnitRouteTest { - - @ClassRule - public static TestKitJunitResource testkit = new TestKitJunitResource(); - - //#test-top - // shared registry for all tests - private static ActorRef userRegistry; - private TestRoute appRoute; - - @BeforeClass - public static void beforeClass() { - userRegistry = testkit.spawn(UserRegistry.create()); - } - - @Before - public void before() { - UserRoutes userRoutes = new UserRoutes(testkit.system(), userRegistry); - appRoute = testRoute(userRoutes.userRoutes()); - } - - @AfterClass - public static void afterClass() { - testkit.stop(userRegistry); - } - - //#set-up - //#actual-test - @Test - public void test1NoUsers() { - appRoute.run(HttpRequest.GET("/users")) - .assertStatusCode(StatusCodes.OK) - .assertMediaType("application/json") - .assertEntity("{\"users\":[]}"); - } - - //#actual-test - //#testing-post - @Test - public void test2HandlePOST() { - appRoute.run(HttpRequest.POST("/users") - .withEntity(MediaTypes.APPLICATION_JSON.toContentType(), - "{\"name\": \"Kapi\", \"age\": 42, \"countryOfResidence\": \"jp\"}")) - .assertStatusCode(StatusCodes.CREATED) - .assertMediaType("application/json") - .assertEntity("{\"description\":\"User Kapi created.\"}"); - } - //#testing-post - - @Test - public void test3Remove() { - appRoute.run(HttpRequest.DELETE("/users/Kapi")) - .assertStatusCode(StatusCodes.OK) - .assertMediaType("application/json") - .assertEntity("{\"description\":\"User Kapi deleted.\"}"); - - } - //#set-up -} -//#set-up diff --git a/downloads-api/src/test/resources/application-test.conf b/downloads-api/src/test/resources/application-test.conf deleted file mode 100644 index 7f763324..00000000 --- a/downloads-api/src/test/resources/application-test.conf +++ /dev/null @@ -1,3 +0,0 @@ -include "application" - -# default config for tests, we just import the regular conf \ No newline at end of file diff --git a/gradle.properties b/gradle.properties deleted file mode 100644 index 52b199d6..00000000 --- a/gradle.properties +++ /dev/null @@ -1,7 +0,0 @@ -micronautVersion=3.8.1 -akkaVersion =2.7.0 -scalaVersion=2.13 -akkaManagementVersion=1.2.0 -akkaProjection =1.3.1 -jacksonVersion = 2.14.2 -vavr = 0.10.4 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..839385ba --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,46 @@ +[versions] +micronaut = "4.0.5" +scala = "2.13" +akka = "2.8.3" +jackson = "2.15.1" +maven_artifact = "3.8.5" +akkaManagementVersion = "1.4.1" +akkaProjection = "1.4.2" +akkaR2DBC = "1.1.1" +vavr = "0.10.4" +jakartaValidation = "3.0.2" + +[libraries] +vavr = { module = "io.vavr:vavr", version.ref = "vavr"} +akkaBom = { module = "com.typesafe.akka:akka-bom_2.13", version.ref = "akka" } +akka-actor = { module = "com.typesafe.akka:akka-actor-typed_2.13" } +akka-cluster-sharding = { module = "com.typesafe.akka:akka-cluster-sharding-typed_2.13" } +akka-cluster-typed = { module = "com.typesafe.akka:akka-cluster-typed_2.13" } + +akka-testkit = { module = "com.typesafe.akka:akka-actor-testkit-typed_2.13"} + +akka-persistence = { module ="com.typesafe.akka:akka-persistence-typed_2.13"} +akka-projection = { module = "com.lightbend.akka:akka-projection-r2dbc_2.13", version.ref = "akkaProjection"} +akka-r2dbc = { module = "com.lightbend.akka:akka-persistence-r2dbc_2.13", version.ref = "akkaR2DBC"} +postgres-r2dbc = { module = "org.postgresql:r2dbc-postgresql"} + +akka-discovery = { module = "com.typesafe.akka:akka-discovery_2.13" } +lightbend_management = { module = "com.lightbend.akka.management:akka-management_2.13", version.ref = "akkaManagementVersion"} +lightbend_bootstrap = { module = "com.lightbend.akka.management:akka-management-cluster-bootstrap_2.13", version.ref = "akkaManagementVersion"} + +jacksonBom = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" } +jackson-core = { module = "com.fasterxml.jackson.core:jackson-core" } +jackson-annotations = { module = "com.fasterxml.jackson.core:jackson-annotations" } +jackson-databind = { module = "com.fasterxml.jackson.core:jackson-databind" } +akka-jackson = { module = "com.typesafe.akka:akka-serialization-jackson_2.13"} +maven = { module = "org.apache.maven:maven-artifact", version.ref = "maven_artifact" } + +jakarta-validation = { module = "jakarta.validation:jakarta.validation-api", version.ref = "jakartaValidation"} + + +[bundles] +serder = ["jackson-core", "jackson-annotations", "jackson-databind"] +appSerder = ["jackson-databind", "jackson-annotations", "jackson-core", "akka-jackson"] +actors = ["akka-actor", "akka-cluster-typed", "akka-cluster-sharding"] +actorsPersistence = ["akka-persistence", "akka-projection", "akka-r2dbc", "postgres-r2dbc"] +akkaManagement = ["akka-discovery", "lightbend_bootstrap", "lightbend_management"] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 249e5832f090a2944b7473328c07c9755baa3196..c1962a79e29d3e0ab67b14947c167a862655af9b 100644 GIT binary patch delta 39834 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 G$^QWPgI3l6 delta 38507 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 + + + + + + diff --git a/liquibase/changelog/changelog.xml b/liquibase/changelog/changelog.xml index 64c2215a..9d5a66c2 100644 --- a/liquibase/changelog/changelog.xml +++ b/liquibase/changelog/changelog.xml @@ -3,5 +3,5 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog https://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"> - + diff --git a/liquibase/changelog/lagom/lagom_1_6.xml b/liquibase/changelog/lagom/lagom_1_6.xml deleted file mode 100644 index 746eef08..00000000 --- a/liquibase/changelog/lagom/lagom_1_6.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index 3161d214..00000000 --- a/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.6.1 diff --git a/project/plugins.sbt b/project/plugins.sbt deleted file mode 100644 index a77de1ce..00000000 --- a/project/plugins.sbt +++ /dev/null @@ -1,8 +0,0 @@ -addSbtPlugin("com.lightbend.lagom" % "lagom-sbt-plugin" % "1.6.7") -addSbtPlugin("de.heikoseeberger" % "sbt-header" % "5.7.0") -addDependencyTreePlugin -addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.1.2") - -resolvers += Resolver.jcenterRepo - -addSbtPlugin("net.aichler" % "sbt-jupiter-interface" % "0.10.0") diff --git a/server-auth/src/main/java/org/spongepowered/downloads/auth/AuthenticatedInternalService.java b/server-auth/src/main/java/org/spongepowered/downloads/auth/AuthenticatedInternalService.java deleted file mode 100644 index c5726e99..00000000 --- a/server-auth/src/main/java/org/spongepowered/downloads/auth/AuthenticatedInternalService.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth; - -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.server.HeaderServiceCall; -import com.lightbend.lagom.javadsl.server.ServerServiceCall; -import org.pac4j.core.profile.CommonProfile; -import org.pac4j.lagom.javadsl.SecuredService; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; - -import java.util.function.Function; - -public interface AuthenticatedInternalService extends SecuredService { - - @Override - default ServerServiceCall authorize( - final String clientName, - final String authorizerName, - final Function> serviceCall - ) { - return HeaderServiceCall.compose(requestHeader -> - requestHeader.getHeader(auth().internalHeaderKey()) - .filter(header -> header.equals(auth().internalHeaderSecret())) - .map(verified -> new InternalApplicationProfile()) - .map(serviceCall) - .orElseGet(() -> SecuredService.super.authorize(clientName, authorizerName, serviceCall)) - ); - } - - default ServiceCall authorizeInvoke( - final ServiceCall call - ) { - return call.handleRequestHeader(requestHeader -> requestHeader.withHeader( - this.auth().internalHeaderKey(), - this.auth().internalHeaderSecret() - )); - } - - AuthUtils auth(); -} diff --git a/server-auth/src/main/java/org/spongepowered/downloads/auth/InternalApplicationProfile.java b/server-auth/src/main/java/org/spongepowered/downloads/auth/InternalApplicationProfile.java deleted file mode 100644 index a1691f33..00000000 --- a/server-auth/src/main/java/org/spongepowered/downloads/auth/InternalApplicationProfile.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth; - -import org.pac4j.core.profile.CommonProfile; - -public class InternalApplicationProfile extends CommonProfile { - - public InternalApplicationProfile() { - this.setId("InternalApplication"); - } - -} diff --git a/server-auth/src/main/java/org/spongepowered/downloads/auth/SOADAuth.java b/server-auth/src/main/java/org/spongepowered/downloads/auth/SOADAuth.java deleted file mode 100644 index d132b022..00000000 --- a/server-auth/src/main/java/org/spongepowered/downloads/auth/SOADAuth.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.auth; - -/** - * Marker annotation to indicate that the returnable object should be the - * SOAD one. - */ -public @interface SOADAuth { -} diff --git a/server-auth/src/main/resources/reference.conf b/server-auth/src/main/resources/reference.conf deleted file mode 100644 index 6d98104e..00000000 --- a/server-auth/src/main/resources/reference.conf +++ /dev/null @@ -1,16 +0,0 @@ - -systemofadownload.auth.secrets { - # The encryption key must be at least 256 bits, so just populate a really - # big number here. Note that if multiple services are being used, the key - # must be the same across all the services, otherwise the tokens generated - # by the auth service will not be valid for other services. - encryption = "12345678901234567890123456789012" - signature = "12345678901234567890123456789012" - # For the webhook module, the specific secret when available, may well be - # deprecated in the future for a more registration-based webhook solution. - nexus-webhook = "" - # The internal header/secret key combination for inter-service - # communication. These should NOT remain the same in production! - internal-header = "some-header" - internal-secret = "some-secret" -} diff --git a/settings.gradle.kts b/settings.gradle.kts index bb2f00f9..50233302 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,20 +1,8 @@ -pluginManagement { - repositories { - mavenCentral() - gradlePluginPortal() - } - plugins { - id("com.github.johnrengelman.shadow") version "7.1.2" - id("io.micronaut.application") version "3.7.0" - id("io.micronaut.test-resources") version "3.7.0" - } -} -rootProject.name="systemofadownload" +rootProject.name="SystemOfADownload" dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.PREFER_PROJECT) // needed for forge-loom, unfortunately repositories { mavenCentral() maven("https://repo.spongepowered.org/repository/maven-public/") { @@ -24,9 +12,11 @@ dependencyResolutionManagement { } include( + "akka", + "akka:testkit", "artifacts", "artifacts:api", "artifacts:worker", "artifacts:server", - "artifacts:events") -include("akka") + "artifacts:events", + ) diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/MavenConstants.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/MavenConstants.java deleted file mode 100644 index 4c8a2bcd..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/MavenConstants.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven; - -public final class MavenConstants { - public static final String MAVEN_METADATA_FILE = "maven-metadata.xml"; -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/ArtifactMavenMetadata.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/ArtifactMavenMetadata.java deleted file mode 100644 index dd18167c..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/ArtifactMavenMetadata.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.artifact; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.util.Objects; - -@JsonDeserialize -@JsonIgnoreProperties(value = "modelVersion", ignoreUnknown = true) -public final class ArtifactMavenMetadata { - private final String groupId; - private final String artifactId; - private final Versioning versioning; - - @JsonCreator - public ArtifactMavenMetadata( - @JsonProperty("groupId") final String groupId, - @JsonProperty("artifactId") final String artifactId, - @JsonProperty("versioning") final Versioning versioning - ) { - this.groupId = groupId; - this.artifactId = artifactId; - this.versioning = versioning; - } - - public String groupId() { - return this.groupId; - } - - public String artifactId() { - return artifactId; - } - - public Versioning versioning() { - return this.versioning; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - final var that = (ArtifactMavenMetadata) obj; - return Objects.equals(this.groupId, that.groupId) && - Objects.equals(this.versioning, that.versioning); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.versioning); - } - - @Override - public String toString() { - return "ArtifactMavenMetadata[" + - "groupId=" + this.groupId + ", " + - "versioning=" + this.versioning + ']'; - } - - -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/Versioning.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/Versioning.java deleted file mode 100644 index 3153c06f..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/artifact/Versioning.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.artifact; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -import java.util.Objects; -import java.util.Optional; - -@JsonDeserialize -public final class Versioning { - public final String latest; - public final String release; - public final String lastUpdated; - public final List versions; - - public Versioning() { - this.latest = ""; - this.release = ""; - this.lastUpdated = ""; - this.versions = List.empty(); - } - - @JsonCreator - public Versioning( - @JsonProperty("latest") final String latest, - @JsonProperty("release") final String release, - @JsonProperty("lastUpdated") String lastUpdated, - @JsonProperty("versions") List versions - ) { - this.latest = latest == null ? "" : latest; - this.release = release == null ? "" : release; - this.lastUpdated = lastUpdated; - this.versions = versions; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - final var that = (Versioning) obj; - return Objects.equals(this.latest, that.latest) && - Objects.equals(this.release, that.release) && - Objects.equals(this.lastUpdated, that.lastUpdated) && - Objects.equals(this.versions, that.versions); - } - - @Override - public int hashCode() { - return Objects.hash(this.latest, this.release, this.lastUpdated, this.versions); - } - - @Override - public String toString() { - return "Versioning[" + - "latest=" + this.latest + ", " + - "release=" + this.release + ", " + - "lastUpdated=" + this.lastUpdated + ", " + - "versions=" + this.versions+ ']'; - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/Snapshot.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/Snapshot.java deleted file mode 100644 index 5936ecc7..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/Snapshot.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.snapshot; - -public class Snapshot { - - public final String timestamp; - public final int buildNumber; - - public Snapshot(final String timestamp, final int buildNumber) { - this.timestamp = timestamp; - this.buildNumber = buildNumber; - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotAsset.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotAsset.java deleted file mode 100644 index 299e138c..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotAsset.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.snapshot; - -import java.util.Objects; - -public final class SnapshotAsset { - private final String classifier; - private final String extension; - private final String value; - private final String updated; - - SnapshotAsset(final String classifier, final String extension, final String value, final String updated) { - this.classifier = classifier; - this.extension = extension; - this.value = value; - this.updated = updated; - } - - public String classifier() { - return this.classifier; - } - - public String extension() { - return this.extension; - } - - public String value() { - return this.value; - } - - public String updated() { - return this.updated; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - final var that = (SnapshotAsset) obj; - return Objects.equals(this.classifier, that.classifier) && - Objects.equals(this.extension, that.extension) && - Objects.equals(this.value, that.value) && - Objects.equals(this.updated, that.updated); - } - - @Override - public int hashCode() { - return Objects.hash(this.classifier, this.extension, this.value, this.updated); - } - - @Override - public String toString() { - return "SnapshotAsset[" + - "classifier=" + this.classifier + ", " + - "extension=" + this.extension + ", " + - "value=" + this.value + ", " + - "updated=" + this.updated + ']'; - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotMetadata.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotMetadata.java deleted file mode 100644 index f78d43f6..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotMetadata.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.snapshot; - -public final class SnapshotMetadata { - public final String groupId; - public final String artifactId; - public final String version; - public final SnapshotVersioning versioning; - - public SnapshotMetadata( - final String groupId, final String artifactId, final String version, - final SnapshotVersioning versioning - ) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versioning = versioning; - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotVersioning.java b/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotVersioning.java deleted file mode 100644 index b5106301..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/maven/snapshot/SnapshotVersioning.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven.snapshot; - -import io.vavr.collection.List; - -/** - * Represents a snapshot versioned maven metadata xml that SOAD will use to represent - * possible artifacts of snapshots. Note that due to the implicit requirements of - */ -public class SnapshotVersioning { - - public final Snapshot snapshot; - public final String lastUpdated; - public final List snapshotVersions; - - public SnapshotVersioning( - final Snapshot snapshot, final String lastUpdated, - final List snapshotVersions - ) { - this.snapshot = snapshot; - this.lastUpdated = lastUpdated; - this.snapshotVersions = snapshotVersions; - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/AssetSearchResponse.java b/sonatype/src/main/java/org/spongepowered/downloads/sonatype/AssetSearchResponse.java deleted file mode 100644 index 5868b14c..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/AssetSearchResponse.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.sonatype; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -import java.util.Optional; - -@JsonDeserialize -public record AssetSearchResponse( - @JsonProperty Optional continuationToken, - @JsonProperty(required = true) List items -) { - - @JsonCreator - public AssetSearchResponse { - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/Component.java b/sonatype/src/main/java/org/spongepowered/downloads/sonatype/Component.java deleted file mode 100644 index bce10302..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/Component.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.sonatype; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; - -@JsonDeserialize -@JsonSerialize -public record Component(String id, String repository, String format, - String group, String name, String version, - List assets) { - @JsonCreator - public Component( - @JsonProperty(value = "id", - required = true) final String id, - @JsonProperty(value = "repository", - required = true) final String repository, - @JsonProperty(value = "format", - required = true) final String format, - @JsonProperty(value = "group", - required = true) final String group, - @JsonProperty(value = "name", - required = true) final String name, - @JsonProperty(value = "version", - required = true) final String version, - @JsonProperty(value = "assets", - required = true) final List assets - ) { - this.id = id; - this.repository = repository; - this.format = format; - this.group = group; - this.name = name; - this.version = version; - this.assets = assets; - } - - @JsonDeserialize - public static record Asset( - @JsonProperty(value = "downloadUrl", - required = true) String downloadUrl, - @JsonProperty(value = "path", - required = true) String path, - @JsonProperty(value = "id", - required = true) String id, - @JsonProperty(value = "repository", - required = true) String repository, - @JsonProperty(value = "format", - required = true) String format, - @JsonProperty(value = "checksum", - required = true) Checksum checksum, - @JsonProperty(value = "contentType", - required = true) String contentType, - @JsonProperty(value = "lastModified", - required = true) String lastModified, - @JsonProperty(value = "maven2") Maven2 mavenData - ) { - @JsonCreator - public Asset { - } - } - - public static record Maven2( - @JsonProperty(value = "extension") String extension, - @JsonProperty(value = "groupId") String groupId, - @JsonProperty(value = "classifier") String classifier, - @JsonProperty(value = "artifactId") String artifactId, - @JsonProperty(value = "version") String version - ) { - - } - - @JsonDeserialize - public static record Checksum( - @JsonProperty(value = "sha1") String sha1, - @JsonProperty(value = "sha256") String sha256, - @JsonProperty(value = "sha512") String sha512, - @JsonProperty(value = "md5") String md5 - ) { - @JsonCreator - public Checksum { - } - - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/ComponentSearchResponse.java b/sonatype/src/main/java/org/spongepowered/downloads/sonatype/ComponentSearchResponse.java deleted file mode 100644 index 877692f1..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/ComponentSearchResponse.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.sonatype; - -import io.vavr.collection.List; - -import java.util.Objects; -import java.util.Optional; - -public final class ComponentSearchResponse { - private final List items; - private final Optional continuationToken; - - public ComponentSearchResponse(final List items, final Optional continuationToken) { - this.items = items; - this.continuationToken = continuationToken; - } - - public List items() { - return this.items; - } - - public Optional continuationToken() { - return this.continuationToken; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - final var that = (ComponentSearchResponse) obj; - return Objects.equals(this.items, that.items) && - Objects.equals(this.continuationToken, that.continuationToken); - } - - @Override - public int hashCode() { - return Objects.hash(this.items, this.continuationToken); - } - - @Override - public String toString() { - return "ComponentSearchResponse[" + - "items=" + this.items + ", " + - "continuationToken=" + this.continuationToken + ']'; - } - - public final static class Item { - private final String id; - private final String repository; - private final String format; - private final String group; - private final String name; - private final String version; - - public Item(final String id, final String repository, final String format, final String group, final String name, final String version) { - this.id = id; - this.repository = repository; - this.format = format; - this.group = group; - this.name = name; - this.version = version; - } - - public String id() { - return this.id; - } - - public String repository() { - return this.repository; - } - - public String format() { - return this.format; - } - - public String group() { - return this.group; - } - - public String name() { - return this.name; - } - - public String version() { - return this.version; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - final var that = (Item) obj; - return Objects.equals(this.id, that.id) && - Objects.equals(this.repository, that.repository) && - Objects.equals(this.format, that.format) && - Objects.equals(this.group, that.group) && - Objects.equals(this.name, that.name) && - Objects.equals(this.version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(this.id, this.repository, this.format, this.group, this.name, this.version); - } - - @Override - public String toString() { - return "Item[" + - "id=" + this.id + ", " + - "repository=" + this.repository + ", " + - "format=" + this.format + ", " + - "group=" + this.group + ", " + - "name=" + this.name + ", " + - "version=" + this.version + ']'; - } - } -} diff --git a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/MavenPom.java b/sonatype/src/main/java/org/spongepowered/downloads/sonatype/MavenPom.java deleted file mode 100644 index 6c8a2a27..00000000 --- a/sonatype/src/main/java/org/spongepowered/downloads/sonatype/MavenPom.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.sonatype; - -import java.util.Objects; - -public final class MavenPom { - private final String groupId; - private final String artifactId; - private final String version; - private final String name; - - public MavenPom(final String groupId, final String artifactId, final String version, final String name) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.name = name; - } - - public String groupId() { - return this.groupId; - } - - public String artifactId() { - return this.artifactId; - } - - public String version() { - return this.version; - } - - public String name() { - return this.name; - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - final var that = (MavenPom) obj; - return Objects.equals(this.groupId, that.groupId) && - Objects.equals(this.artifactId, that.artifactId) && - Objects.equals(this.version, that.version) && - Objects.equals(this.name, that.name); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.artifactId, this.version, this.name); - } - - @Override - public String toString() { - return "MavenPom[" + - "groupId=" + this.groupId + ", " + - "artifactId=" + this.artifactId + ", " + - "version=" + this.version + ", " + - "name=" + this.name + ']'; - } -} diff --git a/sonatype/src/test/java/org/spongepowered/downloads/maven/MavenMetadataTest.java b/sonatype/src/test/java/org/spongepowered/downloads/maven/MavenMetadataTest.java deleted file mode 100644 index 46e9bba3..00000000 --- a/sonatype/src/test/java/org/spongepowered/downloads/maven/MavenMetadataTest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.maven; - - -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import io.vavr.collection.List; -import io.vavr.jackson.datatype.VavrModule; -import org.junit.jupiter.api.Test; -import org.spongepowered.downloads.maven.artifact.ArtifactMavenMetadata; -import org.spongepowered.downloads.maven.artifact.Versioning; - -import java.io.IOException; - -public final class MavenMetadataTest { - - @Test - public void TestMavenMetadataDeserialization() throws IOException { - final var mapper = new XmlMapper(); - mapper.registerModule(new VavrModule()); - final var mavenMetadataFile = getClass().getClassLoader().getResourceAsStream("maven-metadata-example.xml"); - final var artifactMavenMetadata = mapper.readValue(mavenMetadataFile, ArtifactMavenMetadata.class); - // assertions - assert "spongeapi".equals(artifactMavenMetadata.artifactId()); - assert "org.spongepowered".equals(artifactMavenMetadata.groupId()); - final var existingVersions = List.of( - "1.0.0-SNAPSHOT", - "1.0", - "1.1-SNAPSHOT", - "2.0", - "2.1-SNAPSHOT", - "3.0.0", - "3.0.1-SNAPSHOT", - "3.0.1-indev", - "3.1.0-SNAPSHOT", - "3.1.0", - "4.0.0-SNAPSHOT", - "4.0.0", - "4.0.1", - "4.0.2", - "4.0.3", - "4.1.0-SNAPSHOT", - "4.1.0", - "4.2.0-SNAPSHOT", - "5.0.0-SNAPSHOT", - "5.0.0", - "5.1.0-SNAPSHOT", - "5.1.0", - "5.2.0-SNAPSHOT", - "6.0.0-SNAPSHOT", - "6.0.0", - "6.1.0-SNAPSHOT", - "7.0.0-SNAPSHOT", - "7.0.0", - "7.1.0-SNAPSHOT", - "7.1.0", - "7.2.0-SNAPSHOT", - "7.2.0", - "7.3.0-SNAPSHOT", - "7.3.0", - "7.4.0-SNAPSHOT", - "8.0.0-SNAPSHOT", - "9.0.0-SNAPSHOT" - ); - final var expected = new Versioning( - "9.0.0-SNAPSHOT", - "7.3.0", - "20210616221657", - existingVersions - ); - assert expected.equals(artifactMavenMetadata.versioning()); - } -} diff --git a/sonatype/src/test/resources/maven-metadata-example.xml b/sonatype/src/test/resources/maven-metadata-example.xml deleted file mode 100644 index 9d47228b..00000000 --- a/sonatype/src/test/resources/maven-metadata-example.xml +++ /dev/null @@ -1,49 +0,0 @@ - - - org.spongepowered - spongeapi - - 9.0.0-SNAPSHOT - 7.3.0 - - 1.0.0-SNAPSHOT - 1.0 - 1.1-SNAPSHOT - 2.0 - 2.1-SNAPSHOT - 3.0.0 - 3.0.1-SNAPSHOT - 3.0.1-indev - 3.1.0-SNAPSHOT - 3.1.0 - 4.0.0-SNAPSHOT - 4.0.0 - 4.0.1 - 4.0.2 - 4.0.3 - 4.1.0-SNAPSHOT - 4.1.0 - 4.2.0-SNAPSHOT - 5.0.0-SNAPSHOT - 5.0.0 - 5.1.0-SNAPSHOT - 5.1.0 - 5.2.0-SNAPSHOT - 6.0.0-SNAPSHOT - 6.0.0 - 6.1.0-SNAPSHOT - 7.0.0-SNAPSHOT - 7.0.0 - 7.1.0-SNAPSHOT - 7.1.0 - 7.2.0-SNAPSHOT - 7.2.0 - 7.3.0-SNAPSHOT - 7.3.0 - 7.4.0-SNAPSHOT - 8.0.0-SNAPSHOT - 9.0.0-SNAPSHOT - - 20210616221657 - - diff --git a/src/main/java/systemofadownload/AkkaExtension.java b/src/main/java/systemofadownload/AkkaExtension.java deleted file mode 100644 index 25cd72da..00000000 --- a/src/main/java/systemofadownload/AkkaExtension.java +++ /dev/null @@ -1,56 +0,0 @@ -package systemofadownload; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.ActorSystem; -import akka.actor.typed.Scheduler; -import akka.actor.typed.SpawnProtocol; -import akka.actor.typed.javadsl.Adapter; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.ClusterShardingSettings; -import akka.cluster.sharding.typed.ShardingEnvelope; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.management.cluster.bootstrap.ClusterBootstrap; -import akka.management.javadsl.AkkaManagement; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import io.micronaut.context.annotation.Bean; -import io.micronaut.context.annotation.Factory; -import jakarta.inject.Singleton; - -@Factory -public class AkkaExtension { - - - @Bean - public Scheduler systemScheduler() { - return system().scheduler(); - } - - @Bean - public Config akkaConfig() { - return ConfigFactory.load(); - } - - @Bean(preDestroy = "terminate") - public ActorSystem system() { - Config config = akkaConfig(); - return ActorSystem.create( - Behaviors.setup(ctx -> { - akka.actor.ActorSystem unTypedSystem = Adapter.toClassic(ctx.getSystem()); - AkkaManagement.get(unTypedSystem).start(); - ClusterBootstrap.get(unTypedSystem).start(); - return SpawnProtocol.create(); - }), config.getString("some.cluster.name")); - } - - @Bean - public ClusterSharding clusterSharding() { - return ClusterSharding.get(system()); - } - - @Bean - public ActorRef> someShardRegion() { - return clusterSharding().init(Entity.of(null, null)); - } -} diff --git a/src/main/java/systemofadownload/Application.java b/src/main/java/systemofadownload/Application.java deleted file mode 100644 index 6b627497..00000000 --- a/src/main/java/systemofadownload/Application.java +++ /dev/null @@ -1,18 +0,0 @@ -package systemofadownload; - -import io.micronaut.runtime.Micronaut; -import io.swagger.v3.oas.annotations.*; -import io.swagger.v3.oas.annotations.info.*; - -@OpenAPIDefinition( - info = @Info( - title = "systemofadownload", - version = "0.0" - ) -) -public class Application { - - public static void main(String[] args) { - Micronaut.run(Application.class, args); - } -} \ No newline at end of file diff --git a/src/main/java/systemofadownload/SystemofadownloadController.java b/src/main/java/systemofadownload/SystemofadownloadController.java deleted file mode 100644 index a27608d5..00000000 --- a/src/main/java/systemofadownload/SystemofadownloadController.java +++ /dev/null @@ -1,58 +0,0 @@ -package systemofadownload; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Scheduler; -import akka.actor.typed.javadsl.AskPattern; -import akka.cluster.sharding.typed.ShardingEnvelope; -import io.micronaut.http.annotation.*; -import io.micronaut.security.annotation.Secured; -import io.micronaut.security.rules.SecurityRule; -import io.micronaut.serde.annotation.Serdeable; -import jakarta.inject.Inject; -import reactor.core.publisher.Mono; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.CompletionStage; - -@Controller("/systemofadownload") -public class SystemofadownloadController { - record Command() {} - - private final ActorRef> region; - private final Scheduler scheduler; - - @Inject - public SystemofadownloadController( - ActorRef> region, - Scheduler scheduler - ) { - this.region = region; - this.scheduler = scheduler; - - } - - @Get(uri="/", produces="text/plain") - @Secured(SecurityRule.IS_ANONYMOUS) - public String index() { - return "Hello world"; - } - - public static final Duration TIMEOUT = Duration.ofSeconds(10); - - public void someFireForgetMethod(){ - this.region.tell(new ShardingEnvelope<>("foo", new Command())); - } - - record Foo() {} - public Mono someNeedResponseMethod(){ - CompletionStage willBeResponse = AskPattern.ask( - this.region, - replyTo -> new ShardingEnvelope<>("entityId", new Command()), - TIMEOUT, - scheduler - ); - return Mono.fromCompletionStage(willBeResponse); - } -} diff --git a/src/main/java/systemofadownload/UnauthorizedHandler.java b/src/main/java/systemofadownload/UnauthorizedHandler.java deleted file mode 100644 index f7c73c20..00000000 --- a/src/main/java/systemofadownload/UnauthorizedHandler.java +++ /dev/null @@ -1,30 +0,0 @@ -package systemofadownload; - -import io.micronaut.context.annotation.Replaces; -import io.micronaut.http.HttpRequest; -import io.micronaut.http.MutableHttpResponse; -import io.micronaut.security.authentication.AuthorizationException; -import io.micronaut.security.authentication.DefaultAuthorizationExceptionHandler; -import io.micronaut.serde.annotation.Serdeable; -import jakarta.inject.Singleton; - -import java.util.List; - -@Singleton -@Replaces(DefaultAuthorizationExceptionHandler.class) -public class UnauthorizedHandler extends DefaultAuthorizationExceptionHandler { - - @Override - public MutableHttpResponse handle(final HttpRequest request, final AuthorizationException exception) { - return super.handle(request, exception) - .body(new UnauthorizedError(401, List.of("Unauthorized"))); - } - - - @Serdeable - record UnauthorizedError( - int code, List errors - ) { - - } -} diff --git a/src/main/java/systemofadownload/artifacts/ArtifactController.java b/src/main/java/systemofadownload/artifacts/ArtifactController.java deleted file mode 100644 index 001967ae..00000000 --- a/src/main/java/systemofadownload/artifacts/ArtifactController.java +++ /dev/null @@ -1,33 +0,0 @@ -package systemofadownload.artifacts; - -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Body; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.micronaut.http.annotation.Post; -import io.micronaut.http.annotation.QueryValue; -import systemofadownload.artifacts.api.query.ArtifactRegistration; -import systemofadownload.artifacts.api.query.GetArtifactsResponse; - -import java.util.concurrent.Flow; - -@Controller("/groups/{groupID}/artifacts") -public class ArtifactController { - - - @Post("/") - public HttpResponse createArtifact( - @PathVariable String groupID, - @Body ArtifactRegistration.RegisterArtifact registration - ) { - return null; - } - - @Get("/{artifactID}") - public Flow.Publisher> getArtifact( - @PathVariable String groupID, - @PathVariable String artifactID) { - return null; - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/Artifact.java b/src/main/java/systemofadownload/artifacts/api/Artifact.java deleted file mode 100644 index 58746d7d..00000000 --- a/src/main/java/systemofadownload/artifacts/api/Artifact.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.net.URI; -import java.util.Optional; - -@JsonSerialize -public final record Artifact( - @JsonProperty Optional classifier, - @JsonProperty URI downloadUrl, - @JsonProperty String md5, - @JsonProperty String sha1, - @JsonProperty String extension -) { - @JsonCreator - public Artifact { - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/ArtifactCollection.java b/src/main/java/systemofadownload/artifacts/api/ArtifactCollection.java deleted file mode 100644 index 718b7f57..00000000 --- a/src/main/java/systemofadownload/artifacts/api/ArtifactCollection.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.util.List; - -@JsonDeserialize -public final record ArtifactCollection( - @JsonProperty("assets") List components, - @JsonProperty("coordinates") MavenCoordinates coordinates -) { - - @JsonCreator - public ArtifactCollection { - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/ArtifactCoordinates.java b/src/main/java/systemofadownload/artifacts/api/ArtifactCoordinates.java deleted file mode 100644 index 1cd351fa..00000000 --- a/src/main/java/systemofadownload/artifacts/api/ArtifactCoordinates.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.util.StringJoiner; - -@JsonDeserialize -public record ArtifactCoordinates( - @JsonProperty(required = true) String groupId, - @JsonProperty(required = true) String artifactId -) { - @JsonCreator - public ArtifactCoordinates { - } - - public MavenCoordinates version(String version) { - return MavenCoordinates.parse( - new StringJoiner(":").add(this.groupId()).add(this.artifactId()).add(version).toString()); - } - - public String asMavenString() { - return this.groupId() + ":" + this.artifactId(); - } - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String groupId() { - return groupId; - } - - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - public String artifactId() { - return artifactId; - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/ArtifactService.java b/src/main/java/systemofadownload/artifacts/api/ArtifactService.java deleted file mode 100644 index 3f7773c4..00000000 --- a/src/main/java/systemofadownload/artifacts/api/ArtifactService.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import akka.NotUsed; -import systemofadownload.artifacts.api.event.ArtifactUpdate; -import systemofadownload.artifacts.api.event.GroupUpdate; -import systemofadownload.artifacts.api.query.ArtifactDetails; -import systemofadownload.artifacts.api.query.ArtifactRegistration; -import systemofadownload.artifacts.api.query.GetArtifactsResponse; -import systemofadownload.artifacts.api.query.GroupRegistration; -import systemofadownload.artifacts.api.query.GroupResponse; -import systemofadownload.artifacts.api.query.GroupsResponse; - -public interface ArtifactService { - - ServiceCall, ArtifactDetails.Response> updateDetails(String groupId, String artifactId); - - ServiceCall getGroup(String groupId); - - ServiceCall getGroups(); - - Topic groupTopic(); - - Topic artifactUpdate(); - - @Override - default Descriptor descriptor() { - return Service.named("artifacts") - .withCalls( - Service.restCall(Method.GET, "/artifacts/groups/:groupId", this::getGroup), - Service.restCall(Method.GET, "/artifacts/groups", this::getGroups), - Service.restCall(Method.POST, "/artifacts/groups", this::registerGroup), - Service.restCall(Method.GET, "/artifacts/groups/:groupId/artifacts", this::getArtifacts), - Service.restCall(Method.POST, "/artifacts/groups/:groupId/artifacts", this::registerArtifacts), - Service.restCall(Method.PATCH, "/artifacts/groups/:groupId/artifacts/:artifactId/update", this::updateDetails) - ) - .withTopics( - Service.topic("group-activity", this::groupTopic) - .withProperty(KafkaProperties.partitionKeyStrategy(), GroupUpdate::groupId), - Service.topic("artifact-details-update", this::artifactUpdate) - .withProperty(KafkaProperties.partitionKeyStrategy(), ArtifactUpdate::partitionKey) - ) - .withAutoAcl(true); - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/Group.java b/src/main/java/systemofadownload/artifacts/api/Group.java deleted file mode 100644 index d42e7ee5..00000000 --- a/src/main/java/systemofadownload/artifacts/api/Group.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -@JsonDeserialize -public record Group( - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String website -) { - - @JsonCreator - public Group { - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/MavenCoordinates.java b/src/main/java/systemofadownload/artifacts/api/MavenCoordinates.java deleted file mode 100644 index 18ee831b..00000000 --- a/src/main/java/systemofadownload/artifacts/api/MavenCoordinates.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.apache.maven.artifact.versioning.ComparableVersion; - -import java.util.Objects; -import java.util.StringJoiner; -import java.util.regex.Pattern; - -@JsonDeserialize -public final class MavenCoordinates implements Comparable { - - private static final Pattern MAVEN_REGEX = Pattern.compile("^[-\\w.]+$"); - - /** - * The group id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String groupId; - /** - * The artifact id of an artifact, as defined by the Apache Maven documentation. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String artifactId; - /** - * The version of an artifact, as defined by the Apache Maven documentation. This is - * traditionally specified as a Maven repository searchable version string, such as - * {@code 1.0.0-SNAPSHOT}. - * See Maven Coordinates. - */ - @JsonProperty(required = true) - public final String version; - - @JsonIgnore - public final VersionType versionType; - - @JsonIgnore - private final ComparableVersion mavenVersion; - - /** - * Parses a set of maven formatted coordinates as per - * Apache - * Maven's documentation. - * - * @param coordinates The coordinates delimited by `:` - * @return A parsed set of MavenCoordinates - */ - public static MavenCoordinates parse(final String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - return new MavenCoordinates(groupId, artifactId, version); - } - - public MavenCoordinates(String coordinates) { - final var splitCoordinates = coordinates.split(":"); - if (splitCoordinates.length < 3) { - throw new IllegalArgumentException( - "Coordinates are not formatted or delimited by the `:` character or contains fewer than the required size"); - } - final var groupId = splitCoordinates[0]; - if (!MAVEN_REGEX.asMatchPredicate().test(groupId)) { - throw new IllegalArgumentException("GroupId does not conform to regex rules for a maven group id"); - } - final var artifactId = splitCoordinates[1]; - if (!MAVEN_REGEX.asMatchPredicate().test(artifactId)) { - throw new IllegalArgumentException("ArtifactId does not conform to regex rules for a maven artifact id"); - } - final var version = splitCoordinates[2]; - - VersionType.fromVersion(version); // validates the version is going to be valid somewhat - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonCreator - public MavenCoordinates( - @JsonProperty("groupId") final String groupId, - @JsonProperty("artifactId") final String artifactId, - @JsonProperty("version") final String version - ) { - this.groupId = groupId; - this.artifactId = artifactId; - this.version = version; - this.versionType = VersionType.fromVersion(version); - this.mavenVersion = new ComparableVersion(version); - } - - @JsonIgnore - public String asStandardCoordinates() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.versionType.asStandardVersionString(this.version)) - .toString(); - } - - @JsonIgnore - public boolean isSnapshot() { - return this.versionType.isSnapshot(); - } - - @JsonIgnore - public ArtifactCoordinates asArtifactCoordinates() { - return new ArtifactCoordinates(this.groupId, this.artifactId); - } - - @Override - public String toString() { - return new StringJoiner(":") - .add(this.groupId) - .add(this.artifactId) - .add(this.version) - .toString(); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || this.getClass() != o.getClass()) { - return false; - } - final MavenCoordinates that = (MavenCoordinates) o; - return Objects.equals(this.groupId, that.groupId) && Objects.equals( - this.artifactId, that.artifactId) && Objects.equals(this.version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(this.groupId, this.artifactId, this.version); - } - - @Override - public int compareTo(final MavenCoordinates o) { - final var group = this.groupId.compareTo(o.groupId); - if (group != 0) { - return group; - } - final var artifact = this.artifactId.compareTo(o.artifactId); - if (artifact != 0) { - return artifact; - } - return this.mavenVersion.compareTo(o.mavenVersion); - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/VersionType.java b/src/main/java/systemofadownload/artifacts/api/VersionType.java deleted file mode 100644 index fc9c8a5b..00000000 --- a/src/main/java/systemofadownload/artifacts/api/VersionType.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api; - -import java.util.StringJoiner; -import java.util.regex.Pattern; - -/** - * In conjunction with {@link MavenCoordinates}, can be used to determine the - * version type of the coordinates, and whether - */ -public enum VersionType { - /** - * A timestamp based file snapshot, such as {@code 1.0.0-20210118.163210-1} - * to where it can be interpreted that the {@link #SNAPSHOT snapshot} version - * would be {@code 1.0.0-SNAPSHOT} that happened to build at date time - * {@code January 18th, 2021 at 16h32m10s} and it's the first build. - */ - TIMESTAMP_SNAPSHOT { - @Override - public boolean isSnapshot() { - return true; - } - - @Override - public String asStandardVersionString(final String version) { - final var split = version.split("-"); - final var stringJoiner = new StringJoiner("-"); - for (int i = 0; i < split.length - 2; i++) { - stringJoiner.add(split[i]); - } - - return stringJoiner.add(SNAPSHOT_VERSION).toString(); - } - }, - - /** - * A standard generic snapshot relative version of a release, such as {@code 1.0.0-SNAPSHOT}. - */ - SNAPSHOT { - @Override - public boolean isSnapshot() { - return true; - } - }, - - /** - * A standard release version not abiding by any snapshot guidelines, considered - * final and singular, such as {@code 1.0.0} - */ - RELEASE; - - /* - Simple SNAPSHOT placeholder - */ - private static final String SNAPSHOT_VERSION = "SNAPSHOT"; - - /* - Verifies the pattern that the snapshot version is date.time-build formatted, - enables the pattern match for a timestamped snapshot - */ - private static final Pattern VERSION_FILE_PATTERN = Pattern.compile("^(.*)-(\\d{8}.\\d{6})-(\\d+)$"); - - private static final Pattern TIMESTAMP_TO_REPLACE = Pattern.compile("(\\d{8}.\\d{6})-(\\d+)$"); - - public static VersionType fromVersion(final String version) { - if (version == null || version.isEmpty()) { - throw new IllegalArgumentException("Version cannot be empty"); - } - // Simple check to find out if the version ends with SNAPSHOT. - if (version.regionMatches( - true, - version.length() - SNAPSHOT_VERSION.length(), - SNAPSHOT_VERSION, - 0, - SNAPSHOT_VERSION.length() - )) { - return SNAPSHOT; - } - if (VERSION_FILE_PATTERN.matcher(version).matches()) { - return TIMESTAMP_SNAPSHOT; - } - return RELEASE; - } - - public boolean isSnapshot() { - return false; - } - - public String asStandardVersionString(final String version) { - return version; - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/event/ArtifactUpdate.java b/src/main/java/systemofadownload/artifacts/api/event/ArtifactUpdate.java deleted file mode 100644 index 7a88ccb6..00000000 --- a/src/main/java/systemofadownload/artifacts/api/event/ArtifactUpdate.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.event; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import systemofadownload.artifacts.api.ArtifactCoordinates; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(ArtifactUpdate.ArtifactRegistered.class), - @JsonSubTypes.Type(ArtifactUpdate.GitRepositoryAssociated.class), - @JsonSubTypes.Type(ArtifactUpdate.WebsiteUpdated.class), - @JsonSubTypes.Type(ArtifactUpdate.IssuesUpdated.class), - @JsonSubTypes.Type(ArtifactUpdate.DisplayNameUpdated.class), -}) -public interface ArtifactUpdate extends Jsonable { - - ArtifactCoordinates coordinates(); - - default String partitionKey() { - return this.coordinates().asMavenString(); - } - - @JsonTypeName("registered") - @JsonDeserialize - final record ArtifactRegistered( - ArtifactCoordinates coordinates - ) implements ArtifactUpdate { - - @JsonCreator - public ArtifactRegistered { - } - } - - @JsonTypeName("git-repository") - @JsonDeserialize - final record GitRepositoryAssociated( - ArtifactCoordinates coordinates, - String repository - ) implements ArtifactUpdate { - - @JsonCreator - public GitRepositoryAssociated { - } - } - - @JsonTypeName("website") - @JsonDeserialize - final record WebsiteUpdated( - ArtifactCoordinates coordinates, - String url - ) implements ArtifactUpdate { - - @JsonCreator - public WebsiteUpdated { - } - } - - @JsonTypeName("issues") - @JsonDeserialize - final record IssuesUpdated( - ArtifactCoordinates coordinates, - String url - ) implements ArtifactUpdate { - - @JsonCreator - public IssuesUpdated { - } - } - - @JsonTypeName("displayName") - @JsonDeserialize - final record DisplayNameUpdated( - ArtifactCoordinates coordinates, - String displayName - ) implements ArtifactUpdate { - - @JsonCreator - public DisplayNameUpdated { - } - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/event/GroupUpdate.java b/src/main/java/systemofadownload/artifacts/api/event/GroupUpdate.java deleted file mode 100644 index 06267ebd..00000000 --- a/src/main/java/systemofadownload/artifacts/api/event/GroupUpdate.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.event; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import systemofadownload.artifacts.api.ArtifactCoordinates; - -import java.io.Serial; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(GroupUpdate.GroupRegistered.class), - @JsonSubTypes.Type(GroupUpdate.ArtifactRegistered.class), -}) -public interface GroupUpdate extends Jsonable { - - String groupId(); - - @JsonTypeName("group-registered") - @JsonDeserialize - record GroupRegistered(String groupId, String name, String website) - implements GroupUpdate { - - @JsonCreator - public GroupRegistered { - } - - } - - @JsonTypeName("artifact-registered") - @JsonDeserialize - final record ArtifactRegistered(ArtifactCoordinates coordinates) implements GroupUpdate { - - @Serial private static final long serialVersionUID = 6319289932327553919L; - - @JsonCreator - public ArtifactRegistered { - } - - - @Override - public String groupId() { - return this.coordinates.groupId(); - } - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/ArtifactDetails.java b/src/main/java/systemofadownload/artifacts/api/query/ArtifactDetails.java deleted file mode 100644 index 92aa234f..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/ArtifactDetails.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.javadsl.api.transport.BadRequest; -import io.vavr.control.Either; -import io.vavr.control.Try; - -import java.net.URL; - -public final class ArtifactDetails { - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Update.Website.class, - name = "website"), - @JsonSubTypes.Type(value = Update.DisplayName.class, - name = "displayName"), - @JsonSubTypes.Type(value = Update.Issues.class, - name = "issues"), - @JsonSubTypes.Type(value = Update.GitRepository.class, - name = "gitRepository"), - }) - @JsonDeserialize - public sealed interface Update { - - Either validate(); - - record Website( - @JsonProperty(required = true) String website - ) implements Update { - - @JsonCreator - public Website { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.website())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.website()))); - } - } - - record DisplayName( - @JsonProperty(required = true) String display - ) implements Update { - - @JsonCreator - public DisplayName { - } - - @Override - public Either validate() { - return Either.right(this.display.trim()); - } - } - - record Issues( - @JsonProperty(required = true) String issues - ) implements Update { - @JsonCreator - public Issues { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.issues())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.issues()))); - } - } - - record GitRepository( - @JsonProperty(required = true) String gitRepo - ) implements Update { - - @JsonCreator - public GitRepository { - } - - @Override - public Either validate() { - return Try.of(() -> new URL(this.gitRepo())) - .toEither(() -> new BadRequest(String.format("Malformed url: %s", this.gitRepo()))); - } - } - } - - @JsonSerialize - public record Response( - String name, - String displayName, - String website, - String issues, - String gitRepo - ) { - - } - - -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/ArtifactRegistration.java b/src/main/java/systemofadownload/artifacts/api/query/ArtifactRegistration.java deleted file mode 100644 index b125349d..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/ArtifactRegistration.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import systemofadownload.artifacts.api.ArtifactCoordinates; - -public final class ArtifactRegistration { - - @JsonSerialize - public record RegisterArtifact( - @JsonProperty(required = true) String artifactId, - @JsonProperty(required = true) String displayName - ) { - - @JsonCreator - public RegisterArtifact(final String artifactId, final String displayName) { - this.artifactId = artifactId; - this.displayName = displayName; - } - - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Response.GroupMissing.class, - name = "UnknownGroup"), - @JsonSubTypes.Type(value = Response.ArtifactRegistered.class, - name = "RegisteredArtifact"), - @JsonSubTypes.Type(value = Response.ArtifactAlreadyRegistered.class, - name = "AlreadyRegistered"), - }) - public sealed interface Response { - - @JsonSerialize - record ArtifactRegistered(@JsonProperty ArtifactCoordinates coordinates) implements Response { - - } - - @JsonSerialize - record ArtifactAlreadyRegistered( - @JsonProperty String artifactName, - @JsonProperty String groupId - ) implements Response { - - } - - @JsonSerialize - record GroupMissing(@JsonProperty("groupId") String s) implements Response { - - } - - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/GetArtifactsResponse.java b/src/main/java/systemofadownload/artifacts/api/query/GetArtifactsResponse.java deleted file mode 100644 index 13c33681..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/GetArtifactsResponse.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import java.util.List; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GetArtifactsResponse.GroupMissing.class, name = "UnknownGroup"), - @JsonSubTypes.Type(value = GetArtifactsResponse.ArtifactsAvailable.class, name = "Artifacts"), -}) -public sealed interface GetArtifactsResponse { - - @JsonSerialize - record GroupMissing(@JsonProperty String groupRequested) implements GetArtifactsResponse { - - @JsonCreator - public GroupMissing { - } - - } - - @JsonSerialize - record ArtifactsAvailable(@JsonProperty List artifactIds) - implements GetArtifactsResponse { - - @JsonCreator - public ArtifactsAvailable { - } - - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/GroupRegistration.java b/src/main/java/systemofadownload/artifacts/api/query/GroupRegistration.java deleted file mode 100644 index 61e38c2d..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/GroupRegistration.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import systemofadownload.artifacts.api.Group; - -public final class GroupRegistration { - - @JsonDeserialize - public record RegisterGroupRequest( - @JsonProperty(required = true) String name, - @JsonProperty(required = true) String groupCoordinates, - @JsonProperty(required = true) String website - ) { - - @JsonCreator - public RegisterGroupRequest { } - - } - - public interface Response { - - record GroupAlreadyRegistered(String groupNameRequested) implements Response { - } - - record GroupRegistered(Group group) implements Response { - - } - } -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/GroupResponse.java b/src/main/java/systemofadownload/artifacts/api/query/GroupResponse.java deleted file mode 100644 index ca32acef..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/GroupResponse.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import systemofadownload.artifacts.api.Group; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GroupResponse.Missing.class, name = "MissingGroup"), - @JsonSubTypes.Type(value = GroupResponse.Available.class, name = "Group") -}) -public sealed interface GroupResponse extends Jsonable { - - @JsonSerialize - record Missing(@JsonProperty String groupId) implements GroupResponse { - @JsonCreator - public Missing(final String groupId) { - this.groupId = groupId; - } - - } - - @JsonSerialize - record Available(@JsonProperty Group group) implements GroupResponse { - - @JsonCreator - public Available(final Group group) { - this.group = group; - } - - } - -} diff --git a/src/main/java/systemofadownload/artifacts/api/query/GroupsResponse.java b/src/main/java/systemofadownload/artifacts/api/query/GroupsResponse.java deleted file mode 100644 index 5a0dd0ce..00000000 --- a/src/main/java/systemofadownload/artifacts/api/query/GroupsResponse.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package systemofadownload.artifacts.api.query; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import systemofadownload.artifacts.api.Group; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GroupsResponse.Available.class, name = "Groups") -}) -public interface GroupsResponse { - - @JsonSerialize - record Available(@JsonProperty List groups) - implements GroupsResponse { - @JsonCreator - public Available { - } - } -} diff --git a/src/main/java/systemofadownload/artifacts/query/ArtifactQueryController.java b/src/main/java/systemofadownload/artifacts/query/ArtifactQueryController.java deleted file mode 100644 index 0e4b9992..00000000 --- a/src/main/java/systemofadownload/artifacts/query/ArtifactQueryController.java +++ /dev/null @@ -1,23 +0,0 @@ -package systemofadownload.artifacts.query; - -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.micronaut.http.annotation.Post; -import systemofadownload.artifacts.api.query.ArtifactRegistration; -import systemofadownload.artifacts.api.query.GetArtifactsResponse; - -import java.util.concurrent.Flow; - -@Controller("/groups/{groupID}/artifacts") -public class ArtifactQueryController { - - - @Get("/") - public Flow.Publisher> getArtifacts( - @PathVariable String groupID - ) { - return null; - } -} diff --git a/src/main/java/systemofadownload/groups/GroupController.java b/src/main/java/systemofadownload/groups/GroupController.java deleted file mode 100644 index ddcbadd2..00000000 --- a/src/main/java/systemofadownload/groups/GroupController.java +++ /dev/null @@ -1,41 +0,0 @@ -package systemofadownload.groups; - -import akka.actor.typed.ActorSystem; -import akka.actor.typed.SpawnProtocol; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.stream.javadsl.AsPublisher; -import akka.stream.javadsl.JavaFlowSupport; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorFlow; -import akka.stream.typed.javadsl.ActorSink; -import io.micronaut.context.annotation.Bean; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Body; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import io.micronaut.http.annotation.Post; -import jakarta.inject.Inject; -import systemofadownload.artifacts.api.query.GetArtifactsResponse; -import systemofadownload.artifacts.api.query.GroupRegistration; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Flow; - -@Controller("/groups") -public class GroupController { - - @Inject - private ActorSystem system; - @Inject - private ClusterSharding sharding; - - @Post("/") - public HttpResponse registerGroup( - @Body GroupRegistration.RegisterGroupRequest req - ) { - return null; - } - -} diff --git a/src/main/java/systemofadownload/groups/query/GroupsQueryController.java b/src/main/java/systemofadownload/groups/query/GroupsQueryController.java deleted file mode 100644 index 3e40b5ab..00000000 --- a/src/main/java/systemofadownload/groups/query/GroupsQueryController.java +++ /dev/null @@ -1,32 +0,0 @@ -package systemofadownload.groups.query; - -import akka.actor.typed.ActorSystem; -import akka.stream.javadsl.AsPublisher; -import akka.stream.javadsl.JavaFlowSupport; -import akka.stream.javadsl.Source; -import io.micronaut.http.HttpResponse; -import io.micronaut.http.annotation.Controller; -import io.micronaut.http.annotation.Get; -import io.micronaut.http.annotation.PathVariable; -import jakarta.inject.Inject; -import systemofadownload.artifacts.api.query.GetArtifactsResponse; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.Flow; - -@Controller("/groups") -public class GroupsQueryController { - - @Inject - private ActorSystem system; - - @Get(uri = "/{groupID}") - public Flow.Publisher> g(@PathVariable String groupID) { - return Source.from(Arrays.asList("", "b")) - .via(akka.stream.javadsl.Flow.>fromFunction( - s -> HttpResponse.ok(new GetArtifactsResponse.ArtifactsAvailable(List.of(s))) - )) - .runWith(JavaFlowSupport.Sink.asPublisher(AsPublisher.WITH_FANOUT), this.system); - } -} diff --git a/src/main/resources/application.toml b/src/main/resources/application.toml deleted file mode 100644 index 4c3e2b17..00000000 --- a/src/main/resources/application.toml +++ /dev/null @@ -1,20 +0,0 @@ -micronaut.application.name = 'systemofadownload' -liquibase.datasources.default.change-log = 'classpath:db/liquibase-changelog.xml' -jpa.default.reactive = false -netty.default.allocator.max-order = 3 - -[r2dbc.datasources.default] -url = 'r2dbc:postgresql://localhost:5432/postgres' -username = 'postgres' -password = '' -dialect = 'POSTGRES' - -[micronaut.router.static-resources.swagger] -paths = 'classpath:META-INF/swagger' -mapping = '/swagger/**' - -[micronaut.router.static-resources.swagger-ui] -paths = 'classpath:META-INF/swagger/views/swagger-ui' -mapping = '/swagger-ui/**' - -micronaut.security.enabled=false diff --git a/src/main/resources/db/changelog/01-schema.xml b/src/main/resources/db/changelog/01-schema.xml deleted file mode 100644 index 7e9ab55b..00000000 --- a/src/main/resources/db/changelog/01-schema.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/main/resources/db/liquibase-changelog.xml b/src/main/resources/db/liquibase-changelog.xml deleted file mode 100644 index 468b33ab..00000000 --- a/src/main/resources/db/liquibase-changelog.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml deleted file mode 100644 index 6010eb52..00000000 --- a/src/main/resources/logback.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - true - - - %cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n - - - - - - - diff --git a/src/test/java/systemofadownload/SystemofadownloadTest.java b/src/test/java/systemofadownload/SystemofadownloadTest.java deleted file mode 100644 index 15fe3114..00000000 --- a/src/test/java/systemofadownload/SystemofadownloadTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package systemofadownload; - -import io.micronaut.runtime.EmbeddedApplication; -import io.micronaut.test.extensions.junit5.annotation.MicronautTest; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Assertions; - -import jakarta.inject.Inject; - -@MicronautTest(transactional = false) -class SystemofadownloadTest { - - @Inject - EmbeddedApplication application; - - @Test - void testItWorks() { - Assertions.assertTrue(application.isRunning()); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SonatypeSynchronizer.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SonatypeSynchronizer.java deleted file mode 100644 index 41bd9fec..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SonatypeSynchronizer.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer; - -import akka.actor.typed.Behavior; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.typed.ClusterSingleton; -import akka.cluster.typed.SingletonActor; -import akka.persistence.typed.PersistenceId; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.synchronizer.actor.CommitRegistrar; -import org.spongepowered.synchronizer.assetsync.VersionConsumer; -import org.spongepowered.synchronizer.gitmanaged.ArtifactSubscriber; -import org.spongepowered.synchronizer.gitmanaged.CommitConsumer; -import org.spongepowered.synchronizer.gitmanaged.ScheduledCommitResolver; -import org.spongepowered.synchronizer.gitmanaged.domain.GitManagedArtifact; -import org.spongepowered.synchronizer.resync.ResyncManager; -import org.spongepowered.synchronizer.resync.domain.ArtifactSynchronizerAggregate; -import org.spongepowered.synchronizer.versionsync.ArtifactConsumer; - -public final class SonatypeSynchronizer { - - public interface Command { - } - - public static Behavior create( - final ArtifactService artifactService, - final VersionsService versionsService, - final ClusterSharding clusterSharding, - final ObjectMapper mapper - ) { - return Behaviors.setup(context -> { - // don't do this, eventually we can swap this out from service layers - context.spawnAnonymous(Behaviors.supervise(CommitRegistrar.register(versionsService)) - .onFailure(SupervisorStrategy.restart())); - CommitConsumer.setupSubscribers(versionsService, context); - ArtifactSubscriber.setup(artifactService, context); - ScheduledCommitResolver.setup(artifactService, context); - - final var settings = SynchronizationExtension.SettingsProvider.get(context.getSystem()); - clusterSharding - .init( - Entity.of( - ArtifactSynchronizerAggregate.ENTITY_TYPE_KEY, - ArtifactSynchronizerAggregate::create - ) - ); - clusterSharding.init(Entity.of(GitManagedArtifact.ENTITY_TYPE_KEY, ctx -> GitManagedArtifact.create(PersistenceId.of(ctx.getEntityTypeKey().name(), ctx.getEntityId()), ctx.getEntityId()))); - - ArtifactConsumer.subscribeToArtifactUpdates( - context, artifactService, versionsService, clusterSharding, settings); - - VersionConsumer.subscribeToVersionedArtifactUpdates(versionsService, mapper, context, settings); - - final var resyncManager = ResyncManager.create(artifactService, settings.versionSync); - final var resyncBehavior = Behaviors.supervise(resyncManager) - .onFailure( - SupervisorStrategy.restart()); - final var actor = SingletonActor.of(resyncBehavior, "artifact-sync"); - ClusterSingleton.get(context.getSystem()).init(actor); - - // Scheduled full resynchronization with maven and therefor sonatype - return Behaviors.receive(SonatypeSynchronizer.Command.class) - .build(); - }); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizationExtension.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizationExtension.java deleted file mode 100644 index d22e5ee6..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizationExtension.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer; - -import akka.actor.AbstractExtensionId; -import akka.actor.ExtendedActorSystem; -import akka.actor.Extension; -import akka.actor.ExtensionId; -import akka.actor.ExtensionIdProvider; - -public class SynchronizationExtension extends AbstractExtensionId implements ExtensionIdProvider { - - public static final SynchronizationExtension SettingsProvider = new SynchronizationExtension(); - - @Override - public SynchronizerSettings createExtension(final ExtendedActorSystem system) { - return new SynchronizerSettings(system.settings().config().getConfig("systemofadownload.synchronizer")); - } - - - @Override - public ExtensionId lookup() { - return SettingsProvider; - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerModule.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerModule.java deleted file mode 100644 index f840b72e..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerModule.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer; - -import akka.actor.ActorSystem; -import akka.actor.typed.ActorRef; -import akka.actor.typed.javadsl.Adapter; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.inject.AbstractModule; -import com.google.inject.Inject; -import com.google.inject.Provider; -import com.google.inject.Provides; -import com.google.inject.TypeLiteral; -import com.lightbend.lagom.javadsl.api.ServiceInfo; -import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; -import org.pac4j.core.config.Config; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.auth.SOADAuth; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import play.Environment; -import play.api.libs.concurrent.AkkaGuiceSupport; - -public class SynchronizerModule extends AbstractModule implements ServiceGuiceSupport, AkkaGuiceSupport { - - private final AuthUtils auth; - - @Inject - public SynchronizerModule(final Environment environment, final com.typesafe.config.Config config) { - this.auth = AuthUtils.configure(config); - } - - @Override - protected void configure() { - this.bindClient(VersionsService.class); - this.bindClient(ArtifactService.class); - this.bindServiceInfo(ServiceInfo.of("Sonatype-Synchronizer")); - this.bind(new TypeLiteral>() { - }) - .toProvider(SynchronizerProvider.class) - .asEagerSingleton(); - } - - @Provides - @SOADAuth - protected Config configProvider() { - return this.auth.config(); - } - - public record SynchronizerProvider( - ArtifactService artifactService, - VersionsService versionsService, - ClusterSharding clusterSharding, - ObjectMapper mapper, - ActorSystem system - ) implements Provider> { - - @Inject - public SynchronizerProvider { - } - - @Override - public ActorRef get() { - return Adapter.spawn(this.system, SonatypeSynchronizer.create( - this.artifactService, - this.versionsService, - this.clusterSharding, - this.mapper - ), "Synchronizer"); - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerSettings.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerSettings.java deleted file mode 100644 index 660363f0..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/SynchronizerSettings.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer; - -import akka.actor.Extension; -import akka.actor.typed.BackoffSupervisorStrategy; -import akka.actor.typed.SupervisorStrategy; -import com.typesafe.config.Config; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -public class SynchronizerSettings implements Extension { - - public final ReactiveSync reactiveSync; - public final Asset asset; - public final VersionSync versionSync; - - public static final class Asset { - public final int poolSize; - public final int parallelism; - public final Duration initialBackoff; - public final Duration maximumBackoff; - public final double backoffFactor; - public final Duration timeout; - - public final BackoffSupervisorStrategy backoff; - - Asset(Config config) { - this.poolSize = config.getInt("pool-size"); - this.initialBackoff = Duration.ofSeconds(config.getDuration("initial-backoff", TimeUnit.SECONDS)); - this.maximumBackoff = Duration.ofSeconds(config.getDuration("maximum-backoff", TimeUnit.SECONDS)); - this.backoffFactor = config.getDouble("backoff-factor"); - this.backoff = SupervisorStrategy.restartWithBackoff( - this.initialBackoff, this.maximumBackoff, this.backoffFactor); - this.parallelism = config.getInt("parallelism"); - this.timeout = Duration.ofMinutes(config.getDuration("time-out", TimeUnit.MINUTES)); - } - } - - public static final class VersionSync { - public final int versionSyncPoolSize; - public final Duration interval; - public final Duration startupDelay; - - VersionSync(Config config) { - this.versionSyncPoolSize = config.getInt("pool-size"); - this.interval = Duration.ofSeconds(config.getDuration("interval", TimeUnit.SECONDS)); - this.startupDelay = Duration.ofSeconds(config.getDuration("delay", TimeUnit.SECONDS)); - } - } - - public static final class ReactiveSync { - public final int poolSize; - public final int parallelism; - public final Duration timeOut; - - ReactiveSync(Config config) { - this.poolSize = config.getInt("pool-size"); - this.parallelism = config.getInt("parallelism"); - this.timeOut = Duration.ofMinutes(config.getDuration("time-out", TimeUnit.MINUTES)); - } - } - - public SynchronizerSettings(Config config) { - final var assetConfig = config.getConfig("asset"); - this.asset = new Asset(assetConfig); - - final var versionSync = config.getConfig("version-sync"); - this.versionSync = new VersionSync(versionSync); - - final var reactiveSync = config.getConfig("reactive-sync"); - this.reactiveSync = new ReactiveSync(reactiveSync); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncExtension.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncExtension.java deleted file mode 100644 index 3e4f69c5..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncExtension.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.actor; - -import akka.actor.AbstractExtensionId; -import akka.actor.ExtendedActorSystem; -import akka.actor.Extension; -import akka.actor.ExtensionId; -import akka.actor.ExtensionIdProvider; -import com.typesafe.config.Config; - -import java.time.Duration; -import java.util.StringJoiner; -import java.util.concurrent.TimeUnit; - -public class ArtifactSyncExtension extends AbstractExtensionId - implements ExtensionIdProvider { - - public static final ArtifactSyncExtension SettingsProvider = new ArtifactSyncExtension(); - - @Override - public Settings createExtension(final ExtendedActorSystem system) { - return new Settings( - system.settings().config().getConfig("systemofadownload.synchronizer.worker.version-registration")); - } - - - @Override - public ExtensionId lookup() { - return SettingsProvider; - } - - public static final class Settings implements Extension { - - public final int poolSize; - public final int versionFanoutParallelism; - public final int parallelism; - public final Duration timeOut; - public final Duration individualTimeOut; - - public Settings(Config config) { - this.poolSize = config.getInt("pool-size"); - this.versionFanoutParallelism = config.getInt("fan-out-parallelism"); - this.parallelism = config.getInt("parallelism"); - this.timeOut = Duration.ofSeconds(config.getDuration("time-out", TimeUnit.SECONDS)); - this.individualTimeOut = Duration.ofSeconds(config.getDuration("registration-time-out", TimeUnit.SECONDS)); - - } - - @Override - public String toString() { - return new StringJoiner( - ", ", Settings.class.getSimpleName() + "[", "]") - .add("poolSize=" + poolSize) - .add("versionFanoutParallelism=" + versionFanoutParallelism) - .add("parallelism=" + parallelism) - .add("timeOut=" + timeOut) - .add("individualTimeOut=" + individualTimeOut) - .toString(); - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncWorker.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncWorker.java deleted file mode 100644 index adfc6f72..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/ArtifactSyncWorker.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.actor; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.synchronizer.resync.domain.ArtifactSynchronizerAggregate; -import org.spongepowered.synchronizer.versionsync.ArtifactVersionSyncEntity; -import org.spongepowered.synchronizer.versionsync.SyncRegistration; - -import java.time.Duration; - -public final class ArtifactSyncWorker { - - public interface Command { - } - - /** - * Starts the request that the specific artifact described by the - * {@link ArtifactCoordinates coordinates} are synchronized, versioned, - * and other side effects. A {@link Done} response is always given, - * regardless of outcome, since the sync performs multiple jobs in the - * background. - */ - public record PerformResync(ArtifactCoordinates coordinates, ActorRef replyTo) - implements Command { - } - - /** - * For use with just getting a {@link Done} reply back, some messages can be ignored. - */ - public record Ignored(ActorRef replyTo) implements Command { - } - - private record Failed(ArtifactCoordinates coordinates, ActorRef replyTo) implements Command { - } - - /** - * A post-reqeusted result signifying that the initial request for an - * artifact's versions to sync has been completed. - */ - private record WrappedResult(ActorRef replyTo) implements Command { - } - - public static Behavior create( - final ClusterSharding clusterSharding - ) { - return Behaviors.setup(ctx -> { - final ArtifactSyncExtension.Settings settings = ArtifactSyncExtension.SettingsProvider.get(ctx.getSystem()); - return awaiting(clusterSharding, settings); - }); - } - - private static Behavior awaiting( - final ClusterSharding clusterSharding, - final ArtifactSyncExtension.Settings settings - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage(PerformResync.class, msg -> { - ctx.getLog().debug("Running Sync"); - final var globalResyncRef = clusterSharding.entityRefFor( - ArtifactSynchronizerAggregate.ENTITY_TYPE_KEY, - msg.coordinates.asMavenString() - ); - final var versionSyncRef = clusterSharding.entityRefFor( - ArtifactVersionSyncEntity.ENTITY_TYPE_KEY, - msg.coordinates.asMavenString() - ); - ctx.pipeToSelf( - globalResyncRef - .>ask( - replyTo -> new org.spongepowered.synchronizer.resync.domain.Command.Resync(msg.coordinates, replyTo), settings.individualTimeOut) - .thenCompose(response -> - versionSyncRef - .ask( - replyTo -> new SyncRegistration.SyncBatch(msg.coordinates, response, replyTo), - Duration.ofMinutes(10) - ) - ), - (ok, exception) -> { - if (exception != null) { - ctx.getLog().error("Failed to resync by maven coordinates, may ask again", exception); - return new Failed(msg.coordinates, msg.replyTo); - } - - return new WrappedResult(msg.replyTo); - } - ); - return Behaviors.same(); - }) - .onMessage(WrappedResult.class, msg -> { - msg.replyTo.tell(Done.done()); - return Behaviors.same(); - }) - .onMessage(Ignored.class, msg -> { - msg.replyTo.tell(Done.done()); - return Behaviors.same(); - }) - .onMessage(Failed.class, msg -> { - msg.replyTo.tell(Done.done()); - return Behaviors.same(); - }) - .build()); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitDetailsRegistrar.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitDetailsRegistrar.java deleted file mode 100644 index 8523c78b..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitDetailsRegistrar.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.actor; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.receptionist.ServiceKey; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; - -public final class CommitDetailsRegistrar { - - public static final ServiceKey SERVICE_KEY = ServiceKey.create(Command.class, "commit-details-registrar"); - - @JsonDeserialize - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(HandleVersionedCommitReport.class), - @JsonSubTypes.Type(CommitNotFound.class), - @JsonSubTypes.Type(CompletedWork.class) - }) - public sealed interface Command extends Jsonable {} - - @JsonTypeName("handle-version-commit") - public record HandleVersionedCommitReport( - URI repo, - VersionedCommit versionedCommit, - MavenCoordinates coordinates, - ActorRef replyTo - ) implements Command { - @JsonCreator - public HandleVersionedCommitReport { - } - } - - @JsonTypeName("commit-not-found") - public record CommitNotFound( - URI repo, - String commitId, - MavenCoordinates coordinates, - ActorRef replyTo - ) implements Command { - @JsonCreator - public CommitNotFound { - } - } - - @JsonTypeName("completed-work") - record CompletedWork(ActorRef replyTo) implements Command { - @JsonCreator - public CompletedWork { - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitRegistrar.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitRegistrar.java deleted file mode 100644 index 9fb61f92..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/actor/CommitRegistrar.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.actor; - -import akka.Done; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.receptionist.Receptionist; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.CommitRegistration; -import org.spongepowered.synchronizer.gitmanaged.domain.GitCommand; -import org.spongepowered.synchronizer.gitmanaged.domain.GitManagedArtifact; - -import java.time.Duration; - -public class CommitRegistrar { - - public static Behavior register( - VersionsService versionsService - ) { - return Behaviors.setup(ctx -> { - final var registration = Receptionist.register(CommitDetailsRegistrar.SERVICE_KEY, ctx.getSelf()); - ctx.getSystem().receptionist().tell(registration); - final var sharding = ClusterSharding.get(ctx.getSystem()); - final var auth = AuthUtils.configure(ctx.getSystem().settings().config()); - return Behaviors.receive(CommitDetailsRegistrar.Command.class) - .onMessage(CommitDetailsRegistrar.HandleVersionedCommitReport.class, msg -> { - final var future = auth.internalAuth(versionsService.registerCommit( - msg.coordinates().groupId, msg.coordinates().artifactId, msg.coordinates().version - )).invoke(new CommitRegistration.ResolvedCommit( - msg.repo(), - msg.versionedCommit(), - msg.coordinates() - )).toCompletableFuture(); - ctx.pipeToSelf(future, (done, failure) -> { - if (failure != null) { - ctx.getLog().warn("Failed registering git details", failure); - } - return new CommitDetailsRegistrar.CompletedWork(msg.replyTo()); - }); - return Behaviors.same(); - }) - .onMessage(CommitDetailsRegistrar.CommitNotFound.class, msg -> { - final var future = auth.internalAuth(versionsService.registerCommit( - msg.coordinates().groupId, msg.coordinates().artifactId, msg.coordinates().version - )).invoke(new CommitRegistration.FailedCommit(msg.commitId(), msg.repo())) - .thenCompose(done -> sharding.entityRefFor(GitManagedArtifact.ENTITY_TYPE_KEY, - msg.coordinates().asArtifactCoordinates().asMavenString()) - .ask(replyTo -> new GitCommand.MarkVersionAsUnresolveable(replyTo, msg.coordinates(), msg.commitId()), - Duration.ofMinutes(10))) - .toCompletableFuture(); - - ctx.pipeToSelf(future, (done, failure) -> { - if (failure != null) { - ctx.getLog().warn("Failed registering git details", failure); - } - return new CommitDetailsRegistrar.CompletedWork(msg.replyTo()); - }); - return Behaviors.same(); - }) - .onMessage(CommitDetailsRegistrar.CompletedWork.class, msg -> { - msg.replyTo().tell(Done.getInstance()); - return Behaviors.same(); - }) - .build(); - }); - - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/akka/FlowUtil.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/akka/FlowUtil.java deleted file mode 100644 index 879b77d9..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/akka/FlowUtil.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.akka; - -import akka.Done; -import akka.NotUsed; -import akka.japi.Pair; -import akka.stream.FlowShape; -import akka.stream.UniformFanInShape; -import akka.stream.UniformFanOutShape; -import akka.stream.javadsl.Broadcast; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.GraphDSL; -import akka.stream.javadsl.Merge; -import akka.stream.javadsl.Partition; -import io.vavr.Tuple; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import io.vavr.control.Option; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -public final class FlowUtil { - - private static final Logger LOGGER = LoggerFactory.getLogger("FlowUtil"); - - @SuppressWarnings("unchecked") - @SafeVarargs - public static Flow broadcast(Flow... flows) { - final var gatheredFlows = Arrays.stream(flows).collect(List.collector()); - final var count = gatheredFlows.size(); - return Flow.fromGraph(GraphDSL.create(builder -> { - final var broadcast = builder.add(Broadcast.create(count)); - final var merge = builder.add(Merge.create(count)); - for (int i = 0; i < count; i++) { - builder.from(broadcast.out(i)) - .via(builder.add(gatheredFlows.get(i))) - .toInlet(merge.in(i)); - } - return FlowShape.apply(broadcast.in(), merge.out()); - })); - } - - @SuppressWarnings("unchecked") - @SafeVarargs - public static Flow splitClassFlows( - Pair, Flow>... pairs - ) { - final List, Flow>> flowPairs = Arrays.stream(pairs) - .collect(List.collector()); - final var count = flowPairs.size(); - - final Map, Integer> classToIndex = HashMap.ofEntries( - IntStream.range(0, count) - .mapToObj(i -> Tuple.of(pairs[i].first(), i)) - .collect(Collectors.toList()) - ); - - final Function decider = (message) -> flowPairs.map(Pair::first) - .filter(clazz -> clazz.isInstance(message)) - .map(classToIndex::get) - .filter(Option::isDefined) - .map(Option::get) - .getOrElse(count); - - final Flow ignored = Flow.fromFunction(message -> { - if (LOGGER.isDebugEnabled()) { - LOGGER.debug("ignoring message {}", message); - } - return Done.done(); - }); - return Flow.fromGraph(GraphDSL.create(builder -> { - final UniformFanInShape merge = builder.add(Merge.create(count + 1)); - final UniformFanOutShape fanout = builder - .add(Partition.create(count + 1, decider::apply)); - for (int i = 0; i < count; i++) { - builder.from(fanout.out(i)) - .via(builder.add((Flow) flowPairs.get(i).second().async())) - .toInlet(merge.in(i)); - } - builder.from(fanout.out(count)) - .via(builder.add(ignored)) - .toInlet(merge.in(count)); - return FlowShape.of(fanout.in(), merge.out()); - })); - } - - @SuppressWarnings("unchecked") - public static Flow subClassFlow(Flow subFlow) { - return Flow.create() - .map(t -> (S) t) - .via(subFlow); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/AssetSettingsExtension.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/AssetSettingsExtension.java deleted file mode 100644 index bcae2f5c..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/AssetSettingsExtension.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.assetsync; - -import akka.actor.AbstractExtensionId; -import akka.actor.ExtendedActorSystem; -import akka.actor.Extension; -import akka.actor.ExtensionIdProvider; -import com.typesafe.config.Config; -import io.vavr.collection.List; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -class AssetSettingsExtension extends AbstractExtensionId - implements ExtensionIdProvider { - public static final AssetSettingsExtension SettingsProvider = new AssetSettingsExtension(); - - @Override - public AssetRetrievalSettings createExtension(final ExtendedActorSystem system) { - return new AssetRetrievalSettings( - system.settings().config().getConfig("systemofadownload.synchronizer.worker.assets")); - } - - @Override - public AssetSettingsExtension lookup() { - return SettingsProvider; - } - - public static class AssetRetrievalSettings implements Extension { - public final String repository; - public final Duration timeout; - public final int retryCount; - public final List filesToIndex; - public final int poolSize; - - public AssetRetrievalSettings(Config config) { - this.repository = config.getString("repository"); - this.retryCount = config.getInt("retry"); - final var seconds = config.getDuration("timeout", TimeUnit.SECONDS); - this.timeout = Duration.ofSeconds(seconds); - final var stringList = config.getStringList("files-to-index"); - this.filesToIndex = List.ofAll(stringList); - this.poolSize = config.getInt("pool-size"); - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionConsumer.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionConsumer.java deleted file mode 100644 index b626b098..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionConsumer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.assetsync; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.stream.javadsl.Flow; -import akka.stream.typed.javadsl.ActorFlow; -import com.fasterxml.jackson.databind.ObjectMapper; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.ArtifactUpdate; -import org.spongepowered.synchronizer.SonatypeSynchronizer; -import org.spongepowered.synchronizer.SynchronizerSettings; - -public class VersionConsumer { - public static void subscribeToVersionedArtifactUpdates( - final VersionsService versionsService, final ObjectMapper mapper, - final ActorContext context, - final SynchronizerSettings settings - ) { - // region Synchronize Versioned Assets through Sonatype Search - final var componentPool = Routers.pool( - settings.asset.poolSize, - Behaviors.supervise(VersionedComponentWorker.gatherComponents(versionsService, mapper)) - .onFailure(settings.asset.backoff) - ); - final var componentRef = context.spawn( - componentPool, - "version-component-registration", - DispatcherSelector.defaultDispatcher() - ); - final Flow versionedFlow = ActorFlow.ask( - settings.asset.parallelism, - componentRef, - settings.asset.timeout, - (g, b) -> { - if (!(g instanceof ArtifactUpdate.ArtifactVersionRegistered a)) { - return new VersionedComponentWorker.Ignored(b); - } - return new VersionedComponentWorker.GatherComponentsForArtifact(a.coordinates(), b); - } - ); - versionsService.artifactUpdateTopic() - .subscribe() - .atLeastOnce(versionedFlow); - // endregion - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionedComponentWorker.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionedComponentWorker.java deleted file mode 100644 index fec41c61..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/assetsync/VersionedComponentWorker.java +++ /dev/null @@ -1,427 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.assetsync; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.pattern.CircuitBreakerOpenException; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.vavr.collection.List; -import io.vavr.control.Try; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.ArtifactCollection; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.sonatype.AssetSearchResponse; -import org.spongepowered.downloads.sonatype.Component; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; - -import java.net.URI; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeoutException; - -public final class VersionedComponentWorker { - public static final String ASSET_SEARCH_ENDPOINT = - """ - /service/rest/v1/search/assets?maven.groupId=%s&maven.artifactId=%s&maven.baseVersion=%s&maven.extension=%s - """.trim(); - public static final String ASSET_SEARCH_WITH_TOKEN = - """ - /service/rest/v1/search/assets?continuationToken=%s&maven.groupId=%s&maven.artifactId=%s&maven.baseVersion=%s&maven.extension=%s - """.trim(); - - public interface Command { - } - - public record GatherComponentsForArtifact( - MavenCoordinates coordinates, - int count, ActorRef replyTo) - implements Command { - - public GatherComponentsForArtifact(MavenCoordinates coordinates, ActorRef replyTo) { - this(coordinates, 0, replyTo); - } - } - - public record Ignored(ActorRef replyTo) implements Command { - - } - - private interface ChildResponse extends Command { - } - - private record ComponentsAvailable( - MavenCoordinates coordinates, - List assets, - ActorRef replyTo - ) implements ChildResponse { - } - - private record FailedAssetRetrieval( - MavenCoordinates coordinates, - int count, - ActorRef replyTo - ) implements ChildResponse { - } - - public static Behavior gatherComponents( - final VersionsService service, - final ObjectMapper mapper - ) { - return Behaviors.setup(ctx -> { - final AssetSettingsExtension.AssetRetrievalSettings config = AssetSettingsExtension.SettingsProvider.get( - ctx.getSystem()); - final var assetDispatcher = DispatcherSelector.fromConfig("asset-retrieval-dispatcher"); - final var gathererPool = Routers.pool(config.poolSize, idleFetcher(config, mapper)); - final var gathererRef = ctx.spawn( - Behaviors.supervise(gathererPool).onFailure(SupervisorStrategy.restart()), - "search-versioned-components", - assetDispatcher - ); - final var auth = AuthUtils.configure(ctx.getSystem().settings().config()); - final var assetRegisters = Routers.pool(config.poolSize, registerAssets(service, auth)); - final var assetRegistersRef = ctx.spawn( - Behaviors.supervise(assetRegisters).onFailure(SupervisorStrategy.restart()), - "versioned-asset-register", - assetDispatcher - ); - return idleGatherer(gathererRef, assetRegistersRef, config); - }); - } - - private static Behavior idleGatherer( - final ActorRef gathererRef, - final ActorRef assetRegistersRef, - final AssetSettingsExtension.AssetRetrievalSettings config - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage(GatherComponentsForArtifact.class, cmd -> { - config.filesToIndex.forEach(fileType -> ctx.ask( - ChildResponse.class, - gathererRef, - config.timeout, - ref -> new StartRequest(cmd.coordinates, fileType, ref, cmd.replyTo), - (response, throwable) -> { - if (throwable != null) { - return new FailedAssetRetrieval(cmd.coordinates, cmd.count + 1, cmd.replyTo); - } - // In case the child returned a failed asset retrieval - if (response instanceof FailedAssetRetrieval f) { - return new FailedAssetRetrieval(f.coordinates, f.count + 1, cmd.replyTo); - } - return response; - } - )); - return Behaviors.same(); - }) - .onMessage(ComponentsAvailable.class, available -> { - // now we've gotta register the components to the version service - available.replyTo.tell(Done.done()); - assetRegistersRef.tell(new AttemptRegistration(available.coordinates, available.assets)); - return Behaviors.same(); - }) - .onMessage(FailedAssetRetrieval.class, failed -> { - // Retry basically - ctx.getLog().error("Failed artifact attempt"); - if (failed.count >= config.retryCount) { - ctx.getLog().error("Aborting attempt to sync artifact assets"); - failed.replyTo.tell(Done.done()); - return Behaviors.same(); - } - ctx.getSelf().tell(new GatherComponentsForArtifact(failed.coordinates, failed.count, failed.replyTo)); - return Behaviors.same(); - }) - .onMessage(Ignored.class, ignored -> { - ignored.replyTo.tell(Done.done()); - return Behaviors.same(); - }) - .build()); - } - - private interface Gather { - } - - private record StartRequest( - MavenCoordinates coordinates, - String fileType, - ActorRef ref, - ActorRef completedReplyTo - ) implements Gather { - } - - private record ContinueRequest( - MavenCoordinates coordinates, - String fileType, - List existing, - String continuationToken, - ActorRef replyTo, - ActorRef completedReplyTo - ) implements Gather { - } - - private record Completed( - MavenCoordinates coordinates, - List existing, - ActorRef replyTo, - ActorRef completedReplyTo - ) implements Gather { - } - - private record Failed( - MavenCoordinates coordinates, - List recovered, - ActorRef replyTo, - ActorRef completedReplyTo - ) implements Gather { - } - - private static Behavior idleFetcher( - final AssetSettingsExtension.AssetRetrievalSettings config, - final ObjectMapper playMapper - ) { - return Behaviors.setup(ctx -> { - final var client = HttpClient.newBuilder().connectTimeout(config.timeout) - .executor(ctx.getExecutionContext()) - .build(); - return Behaviors.receive(Gather.class) - .onMessage(StartRequest.class, req -> { - runRequest(config, playMapper, ctx, client, req); - return working(config, playMapper, req, List.empty()); - }) - .onMessage(ContinueRequest.class, cont -> { - ctx.getLog().warn("Somehow got a continuation while idling"); - return Behaviors.same(); - }) - .onMessage(Completed.class, completed -> { - ctx.getLog().warn("Somehow got completed while idling"); - return Behaviors.same(); - }) - .onMessage(Failed.class, failed -> { - failed.replyTo.tell(new FailedAssetRetrieval(failed.coordinates, 0, failed.completedReplyTo)); - return Behaviors.same(); - }) - .build(); - }); - } - - private static void runRequest( - AssetSettingsExtension.AssetRetrievalSettings config, ObjectMapper playMapper, ActorContext ctx, - HttpClient client, StartRequest req - ) { - final var formatted = String.format( - config.repository + ASSET_SEARCH_ENDPOINT, - req.coordinates.groupId, - req.coordinates.artifactId, - req.coordinates.version, - req.fileType - ); - ctx.pipeToSelf(searchAssets(playMapper, client, formatted), (response, failure) -> { - if (failure != null) { - return new Failed(req.coordinates, List.empty(), req.ref, req.completedReplyTo); - } - return response.continuationToken() - .map( - token -> new ContinueRequest( - req.coordinates, req.fileType, response.items(), token, req.ref, - req.completedReplyTo - )) - .orElseGet( - () -> new Completed(req.coordinates, response.items(), req.ref, req.completedReplyTo)); - }); - } - - private static Behavior working(final AssetSettingsExtension.AssetRetrievalSettings config, - final ObjectMapper playMapper, - final StartRequest working, - final List queue - ) { - return Behaviors.setup(ctx -> { - final var client = HttpClient.newBuilder().connectTimeout(config.timeout) - .executor(ctx.getExecutionContext()) - .build(); - - return Behaviors.receive(Gather.class) - .onMessage(StartRequest.class, req -> working(config, playMapper, working, queue.append(req))) - .onMessage(ContinueRequest.class, cont -> { - final var formatted = String.format( - config.repository + ASSET_SEARCH_WITH_TOKEN, - cont.continuationToken, - cont.coordinates.groupId, - cont.coordinates.artifactId, - cont.coordinates.version, - cont.fileType - ); - ctx.pipeToSelf(searchAssets(playMapper, client, formatted), (response, throwable) -> { - if (throwable != null) { - return new Failed(cont.coordinates, cont.existing, cont.replyTo, cont.completedReplyTo); - } - final var completedAssets = cont.existing.appendAll(response.items()); - return response.continuationToken() - .map( - token -> new ContinueRequest( - cont.coordinates, cont.fileType, completedAssets, token, cont.replyTo, - cont.completedReplyTo - )) - .orElseGet(() -> new Completed(cont.coordinates, completedAssets, cont.replyTo, - cont.completedReplyTo - )); - }); - return Behaviors.same(); - }) - .onMessage(Completed.class, completed -> { - completed.replyTo.tell( - new ComponentsAvailable(completed.coordinates, completed.existing, completed.completedReplyTo)); - if (queue.isEmpty()) { - return idleFetcher(config, playMapper); - } - final var next = queue.head(); - runRequest(config, playMapper, ctx, client, next); - return working(config, playMapper, next, queue.tail()); - }) - .onMessage(Failed.class, failed -> { - runRequest(config, playMapper, ctx, client, working); - return Behaviors.same(); - }) - .build(); - }); - } - - private static CompletableFuture searchAssets( - final ObjectMapper playMapper, - final HttpClient client, - final String assetSearchUrl - ) { - return Try.of(() -> new URL(assetSearchUrl)) - .mapTry(URL::toURI) - .map(url -> HttpRequest.newBuilder(url).GET()) - .toCompletableFuture() - .thenCompose(req -> client.sendAsync(req.build(), HttpResponse.BodyHandlers.ofInputStream())) - .thenApply(HttpResponse::body) - .thenApply(stream -> Try.withResources(() -> stream) - .of(is -> playMapper.readValue(is, AssetSearchResponse.class)) - .get()); - } - - private interface Registrar { - } - - private record AttemptRegistration(MavenCoordinates coordinates, - List assets) implements Registrar { - } - - private interface RegistrationResult extends Registrar { - } - - private record AssetRegistrationCompleted(MavenCoordinates coordinates) implements RegistrationResult { - } - - private record AssetRegistrationFailed(MavenCoordinates coordinates) implements RegistrationResult { - } - - private record WrappedResult(RegistrationResult response) implements Registrar { - } - - private static Behavior registerAssets( - final VersionsService service, - final AuthUtils auth - ) { - return Behaviors.setup(ctx -> - Behaviors.withTimers(timers -> Behaviors.receive(Registrar.class) - .onMessage(AttemptRegistration.class, registration -> { - final var artifacts = registration.assets.map(asset -> - Try.of(() -> URI.create(asset.downloadUrl())) - .map(downloadUri -> Optional.ofNullable(asset.mavenData()) - .map(data -> new Artifact( - Optional.ofNullable(data.classifier()), - downloadUri, - asset.checksum().md5(), - asset.checksum().sha1(), - asset.mavenData().extension() - )) - ) - .filter(Optional::isPresent) - .map(Optional::get) - .toJavaOptional() - ) - .filter(Optional::isPresent) - .map(Optional::get); - final var collection = new ArtifactCollection(artifacts, registration.coordinates); - - ; - final var registrationFuture = auth.internalAuth(service.registerArtifactCollection( - registration.coordinates.groupId, - registration.coordinates.artifactId - )).invoke(new VersionRegistration.Register.Collection(collection)); - - ctx.pipeToSelf(registrationFuture, (response, throwable) -> { - if (throwable != null) { - if (throwable instanceof CompletionException ce) { - throwable = ce.getCause(); - } - if (throwable instanceof CircuitBreakerOpenException cboe) { - ctx.getLog().warn("Rescheduling asset registration for {}", registration.coordinates); - timers.startSingleTimer(registration.coordinates, registration, Duration.ofMillis(cboe.remainingDuration().toMillis())); - return new WrappedResult(new AssetRegistrationFailed(registration.coordinates)); - } else if (throwable instanceof TimeoutException) { - ctx.getLog().warn("Rescheduling asset registration for {}", registration.coordinates); - timers.startSingleTimer(registration.coordinates, registration, Duration.ofSeconds(2)); - return new WrappedResult(new AssetRegistrationFailed(registration.coordinates)); - } - ctx.getLog().error("Failed to register asset for " + registration.coordinates.asStandardCoordinates(), throwable); - return new WrappedResult(new AssetRegistrationFailed(registration.coordinates)); - - } - return new WrappedResult(new AssetRegistrationCompleted(registration.coordinates)); - }); - return Behaviors.same(); - }) - .onMessage(WrappedResult.class, result -> { - final var response = result.response; - if (response instanceof AssetRegistrationCompleted a) { - ctx.getLog().debug("Successful Asset registration of {}", a.coordinates); - } else if (response instanceof AssetRegistrationFailed f) { - ctx.getLog().warn("Failed registration of {}", f.coordinates); - } - return Behaviors.same(); - }) - .build()) - ); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ArtifactSubscriber.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ArtifactSubscriber.java deleted file mode 100644 index dcb00fe0..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ArtifactSubscriber.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Routers; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.japi.Pair; -import akka.stream.FlowShape; -import akka.stream.javadsl.Balance; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.GraphDSL; -import akka.stream.javadsl.Merge; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorFlow; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.artifact.api.event.ArtifactUpdate; -import org.spongepowered.synchronizer.akka.FlowUtil; -import org.spongepowered.synchronizer.gitmanaged.domain.GitCommand; -import org.spongepowered.synchronizer.gitmanaged.domain.GitManagedArtifact; -import org.spongepowered.synchronizer.gitmanaged.util.jgit.CommitResolutionManager; - -import java.net.URI; -import java.time.Duration; - -public final class ArtifactSubscriber { - - public static void setup(ArtifactService artifacts, ActorContext ctx) { - final Flow flow = generateFlows(ctx); - - artifacts.artifactUpdate() - .subscribe() - .atLeastOnce(flow); - } - - private static Flow generateFlows(ActorContext ctx) { - final var sharding = ClusterSharding.get(ctx.getSystem()); - final var repoAssociatedFlow = getRepoAssociatedFlow(sharding, ctx); - - return FlowUtil.splitClassFlows( - Pair.create(ArtifactUpdate.GitRepositoryAssociated.class, repoAssociatedFlow) - ); - } - - @SuppressWarnings("unchecked") - private static Flow getRepoAssociatedFlow( - ClusterSharding sharding, - ActorContext ctx - ) { - // Step 1 - Register the repository with GitManagedArtifact - final var registerRepo = Flow.fromFunction( - c -> sharding.entityRefFor(GitManagedArtifact.ENTITY_TYPE_KEY, c.coordinates().asMavenString()) - .ask( - replyTo -> new GitCommand.RegisterRepository(URI.create(c.repository()), replyTo), - Duration.ofSeconds(20) - ) - .thenApply(d -> c) - .toCompletableFuture() - .join() - ); - final var key = Routers.group(CommitResolutionManager.SERVICE_KEY); - final var workerRef = ctx.spawn(key, "repo-associated-commit-resolver"); - // Step 2 - Get unresolved commits from GitManagedArtifact to perform work - final Flow registerRepoFlow = Flow.create() - .mapAsync( - 1, c -> sharding.entityRefFor(GitManagedArtifact.ENTITY_TYPE_KEY, c.coordinates().asMavenString()) - .ask(GitCommand.GetUnresolvedVersions::new, Duration.ofSeconds(20)) - ) - .flatMapConcat(work -> { - final Map tuple2s = work.repositories().isEmpty() - ? HashMap.empty() - : work.unresolvedCommits(); - final List requests = tuple2s - .toList().map( - (t) -> new CommitResolutionManager.ResolveCommitDetails( - t._1(), t._2(), work.repositories(), null) - ); - return Source.from(requests); - }); - // Step 2a - Resolve the commits - final var flow = ActorFlow.ask( - 4, - workerRef, - Duration.ofMinutes(10), - (msg, replyTo) -> new CommitResolutionManager.ResolveCommitDetails( - msg.coordinates(), msg.commit(), msg.gitRepo(), replyTo) - ); - - return Flow.fromGraph(GraphDSL.create(b -> { - final var balance = b.add(Balance.create(4)); - final var zip = b.add(Merge.create(4)); - final var add = b.add(registerRepo.via(registerRepoFlow)); - b.from(add.out()) - .toFanOut(balance); - for (int i = 0; i < 4; i++) { - final var resolver = b.add(flow); - b.from(balance.out(i)) - .via(resolver) - .toInlet(zip.in(i)); - - } - return FlowShape.of(add.in(), zip.out()); - })); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/CommitConsumer.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/CommitConsumer.java deleted file mode 100644 index 3af605f2..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/CommitConsumer.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.actor.typed.receptionist.Receptionist; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.typed.Cluster; -import akka.japi.Pair; -import akka.stream.javadsl.Flow; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.VersionedArtifactUpdates; -import org.spongepowered.synchronizer.actor.CommitDetailsRegistrar; -import org.spongepowered.synchronizer.actor.CommitRegistrar; -import org.spongepowered.synchronizer.akka.FlowUtil; -import org.spongepowered.synchronizer.gitmanaged.domain.GitCommand; -import org.spongepowered.synchronizer.gitmanaged.domain.GitManagedArtifact; -import org.spongepowered.synchronizer.gitmanaged.util.jgit.CommitResolutionManager; - -import java.time.Duration; -import java.util.UUID; - -public class CommitConsumer { - - public static void setupSubscribers(VersionsService versionsService, ActorContext ctx) { - final var member = Cluster.get(ctx.getSystem()).selfMember(); - final var uid = UUID.randomUUID(); - final var workerName = "commit-resolver-" + uid; - if (member.hasRole("commit-resolver")) { - final var registrar = ctx.spawn(CommitRegistrar.register(versionsService), "commit-registrar-" + uid); - spawnCommitResolver(ctx, uid, workerName, registrar); - } - final var versionedAssetFlows = CommitConsumer.createVersionedAssetFlows(ctx); - versionsService.versionedArtifactUpdatesTopic() - .subscribe() - .atLeastOnce(versionedAssetFlows); - } - - public static Flow createVersionedAssetFlows( - ActorContext ctx - ) { - final var sharding = ClusterSharding.get(ctx.getSystem()); - final var associateCommitFlow = registerRawCommitFlow(sharding); - - final var registerResolvedCommits = registerResolvedCommitDetails(sharding); - - return FlowUtil.splitClassFlows( - Pair.create(VersionedArtifactUpdates.CommitExtracted.class, associateCommitFlow), - Pair.create(VersionedArtifactUpdates.GitCommitDetailsAssociated.class, registerResolvedCommits) - ); - } - - private static void spawnCommitResolver( - ActorContext ctx, UUID uid, - String workerName, - final ActorRef registrar - ) { - final ActorRef workerRef; - - final var resolver = CommitResolutionManager.resolveCommit(registrar); - final var supervisedResolver = Behaviors.supervise(resolver) - .onFailure(SupervisorStrategy.restartWithBackoff( - Duration.ofMillis(100), - Duration.ofSeconds(40), - 0.1 - )); - final var pool = Routers.pool(4, supervisedResolver); - - workerRef = ctx.spawn( - pool, - workerName + "-" + uid, - DispatcherSelector.defaultDispatcher() - ); - // Announce it to the cluster - ctx.getSystem().receptionist().tell(Receptionist.register(CommitResolutionManager.SERVICE_KEY, workerRef)); - - } - - private static Flow registerRawCommitFlow( - ClusterSharding sharding - ) { - return Flow.fromFunction(msg -> sharding.entityRefFor( - GitManagedArtifact.ENTITY_TYPE_KEY, - msg.coordinates().asArtifactCoordinates().asMavenString() - ) - .ask( - replyTo -> new GitCommand.RegisterRawCommit(msg.coordinates(), msg.commit(), replyTo), - Duration.ofSeconds(20) - ) - .toCompletableFuture() - .join() - ); - } - - private static Flow registerResolvedCommitDetails( - ClusterSharding sharding - ) { - return Flow.fromFunction(event -> sharding - .entityRefFor( - GitManagedArtifact.ENTITY_TYPE_KEY, - event.coordinates().asArtifactCoordinates().asMavenString() - ) - .ask( - replyTo -> new GitCommand.MarkVersionAsResolved(event.coordinates(), event.commit(), replyTo), - Duration.ofSeconds(20) - ) - .toCompletableFuture() - .join() - ); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ScheduledCommitResolver.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ScheduledCommitResolver.java deleted file mode 100644 index 404430c3..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/ScheduledCommitResolver.java +++ /dev/null @@ -1,302 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.actor.typed.receptionist.Receptionist; -import akka.actor.typed.receptionist.ServiceKey; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.typed.ClusterSingleton; -import akka.cluster.typed.SingletonActor; -import akka.japi.Pair; -import akka.stream.Graph; -import akka.stream.Materializer; -import akka.stream.SourceShape; -import akka.stream.javadsl.Balance; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.GraphDSL; -import akka.stream.javadsl.Merge; -import akka.stream.javadsl.Sink; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorFlow; -import akka.stream.typed.javadsl.ActorSink; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashSet; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.Group; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.artifact.api.event.ArtifactUpdate; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; -import org.spongepowered.synchronizer.SonatypeSynchronizer; -import org.spongepowered.synchronizer.akka.FlowUtil; -import org.spongepowered.synchronizer.gitmanaged.domain.GitCommand; -import org.spongepowered.synchronizer.gitmanaged.domain.GitManagedArtifact; -import org.spongepowered.synchronizer.gitmanaged.util.jgit.CommitResolutionManager; - -import java.time.Duration; - -public final class ScheduledCommitResolver { - - public static final ServiceKey SCHEDULED_REFRESH = ServiceKey.create( - ScheduledRefresh.class, "scheduled-refresh"); - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(Refresh.class), - @JsonSubTypes.Type(Register.class), - @JsonSubTypes.Type(CommitResolved.class), - @JsonSubTypes.Type(WorkCompleted.class), - @JsonSubTypes.Type(Resync.class) - }) - public sealed interface ScheduledRefresh extends Jsonable { - } - - @JsonTypeName("refresh") - public record Refresh() implements ScheduledRefresh { - } - - @JsonTypeName("resync") - record Resync() implements ScheduledRefresh { - } - - @JsonTypeName("register-coordinates") - record Register(ArtifactCoordinates coordinates, ActorRef replyTo) implements ScheduledRefresh { - } - - @JsonTypeName("resolved") - record CommitResolved() implements ScheduledRefresh { - } - - @JsonTypeName("completed") - record WorkCompleted() implements ScheduledRefresh { - } - - public static void setup( - ArtifactService artifactService, - ActorContext context - ) { - final var singleton = SingletonActor.of(setup(artifactService), "ScheduledCommitResolver"); - final var scheduler = ClusterSingleton.get(context.getSystem()).init(singleton); - final var sync = registerForSync(scheduler); - artifactService.artifactUpdate() - .subscribe() - .atLeastOnce(FlowUtil.splitClassFlows(Pair.create(ArtifactUpdate.ArtifactRegistered.class, sync))); - } - - private static Source parseResponseIntoArtifacts( - ArtifactService artifactService, - GroupsResponse r - ) { - final Source groups; - if (r instanceof GroupsResponse.Available a) { - groups = Source.from(a.groups().map(Group::groupCoordinates)); - } else { - groups = Source.empty(); - } - final var groupsToArtifacts = Flow.create() - .mapConcat(g -> { - final var join = artifactService.getArtifacts(g).invoke().toCompletableFuture().join(); - if (join instanceof GetArtifactsResponse.ArtifactsAvailable aa) { - return aa.artifactIds().map(id -> new ArtifactCoordinates(g, id)); - } else { - return List.empty(); - } - }); - return groups.via(groupsToArtifacts); - } - - private static Flow registerForSync( - final ActorRef scheduler - ) { - return ActorFlow.ask( - scheduler, Duration.ofMinutes(1), (reg, replyTo) -> new Register(reg.coordinates(), replyTo)); - } - - private static Behavior setup( - final ArtifactService artifactService - ) { - return Behaviors.setup(ctx -> Behaviors.withTimers(timers -> { - ctx.getSystem().receptionist().tell(Receptionist.register(SCHEDULED_REFRESH, ctx.getSelf())); - ctx.getLog().info("Starting ScheduledCommitResolver"); - timers.startSingleTimer("resync", new Resync(), Duration.ofSeconds(30)); - timers.startPeriodicTimer("refresh", new Refresh(), Duration.ofMinutes(1)); - ctx.getLog().info("Scheduled refresh every minute"); - - return Behaviors.receive(ScheduledRefresh.class) - .onMessage(Register.class, msg -> { - msg.replyTo.tell(Done.done()); - final var key = Routers.group(CommitResolutionManager.SERVICE_KEY); - final ActorRef workerRef = ctx.spawn( - key, "repo-associated-commit-resolver"); - return waiting(workerRef, HashSet.of(msg.coordinates()), artifactService); - }) - .onMessage(Refresh.class, msg -> Behaviors.same()) - .onMessage(Resync.class, msg -> resyncArtifactCoordinates(artifactService, ctx)) - .build(); - })); - } - - private static Behavior resyncArtifactCoordinates( - ArtifactService artifactService, ActorContext ctx - ) { - final Flow getGroups = Flow.fromFunction(s -> s.getGroups() - .invoke() - .toCompletableFuture() - .join()); - Source.single(artifactService) - .via(getGroups - .flatMapConcat((GroupsResponse r) -> parseResponseIntoArtifacts(artifactService, r))) - .via(ActorFlow.ask(ctx.getSelf(), Duration.ofSeconds(10), Register::new)) - .to(Sink.ignore()) - .run(ctx.getSystem()); - return Behaviors.same(); - } - - private static Behavior waiting( - final ActorRef workerRef, - final HashSet artifactsKeepingTracked, - final ArtifactService artifactService - ) { - return Behaviors.setup(ctx -> Behaviors.withTimers(timers -> { - final ClusterSharding sharding = ClusterSharding.get(ctx.getSystem()); - final Materializer mat = Materializer.createMaterializer(ctx.getSystem()); - return Behaviors.receive(ScheduledRefresh.class) - .onMessage(Resync.class, msg -> resyncArtifactCoordinates(artifactService, ctx)) - .onMessage(Refresh.class, msg -> { - performRefresh(workerRef, artifactsKeepingTracked, ctx, sharding, mat); - return working(workerRef, artifactsKeepingTracked, artifactService); - }) - .onMessage(Register.class, msg -> { - msg.replyTo.tell(Done.done()); - return waiting(workerRef, artifactsKeepingTracked.add(msg.coordinates()), artifactService); - }) - .build(); - })); - } - - @SuppressWarnings("unchecked") - private static void performRefresh( - final ActorRef workerRef, - final HashSet artifactsKeepingTracked, - final ActorContext ctx, - ClusterSharding sharding, - Materializer mat - ) { - final Source artifactsToWorkOn = Source.from(artifactsKeepingTracked); - final Flow workFetcherFlow = getWork(sharding).log( - "work-fetcher"); - final Flow parseWorkFlow = Flow.create() - .log("parse-work") - .flatMapConcat(work -> { - final Map tuple2s = work.unresolvedCommits(); - final List requests = tuple2s - .toList().map( - (t) -> new CommitResolutionManager.ResolveCommitDetails( - t._1(), t._2(), work.repositories(), null) - ); - return Source.from(requests); - }); - final Flow commitResolverAskFlow = ActorFlow.ask( - 4, - workerRef, - Duration.ofMinutes(10), - (m, replyTo) -> new CommitResolutionManager.ResolveCommitDetails( - m.coordinates(), m.commit(), m.gitRepo(), replyTo) - ); - final Graph, NotUsed> graph = GraphDSL.create(b -> { - final SourceShape artifacts = b.add(artifactsToWorkOn); - final var balance = b.add(Balance.create(4)); - final var zip = b.add(Merge.create(4)); - final var getWork = b.add(workFetcherFlow); - final var parseWork = b.add(parseWorkFlow.async()); - b.from(artifacts.out()) - .via(getWork) - .via(parseWork) - .toFanOut(balance); - for (int i = 0; i < 4; i++) { - final var resolver = b.add(commitResolverAskFlow.async()); - b.from(balance.out(i)) - .via(resolver) - .toInlet(zip.in(i)); - } - return SourceShape.of(zip.out()); - }); - final var log = ctx.getLog(); - final Sink sink = ActorSink.actorRef( - ctx.getSelf(), new WorkCompleted(), t -> { - log.error("Got an error while resolving commits", t); - return new WorkCompleted(); - }); - Source.fromGraph(graph) - .via(Flow.fromFunction(d -> new CommitResolved())) - .to(sink) - .run(mat); - } - - private static Behavior working( - ActorRef workerRef, - final HashSet artifactsKeepingTracked, - final ArtifactService artifactService - ) { - return Behaviors.setup(ctx -> Behaviors.withTimers(timers -> Behaviors.receive(ScheduledRefresh.class) - .onMessage(Resync.class, msg -> resyncArtifactCoordinates(artifactService, ctx)) - .onMessage(Refresh.class, msg -> Behaviors.same()) - .onMessage(Register.class, msg -> { - msg.replyTo.tell(Done.done()); - return working(workerRef, artifactsKeepingTracked.add(msg.coordinates()), artifactService - ); - }) - .onMessage(CommitResolved.class, msg -> Behaviors.same()) - .onMessage(WorkCompleted.class, msg -> { - timers.startSingleTimer(new Refresh(), Duration.ofMinutes(1)); - return waiting(workerRef, artifactsKeepingTracked, artifactService); - }) - .build())); - } - - private static Flow getWork(ClusterSharding sharding) { - return Flow.create() - .map(artifact -> sharding.entityRefFor(GitManagedArtifact.ENTITY_TYPE_KEY, artifact.asMavenString()) - .ask(GitCommand.GetUnresolvedVersions::new, Duration.ofSeconds(30)) - .toCompletableFuture() - .join()) - .filter(GitCommand.UnresolvedWork::hasWork); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitCommand.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitCommand.java deleted file mode 100644 index 7d0c2247..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitCommand.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.domain; - -import akka.Done; -import akka.actor.typed.ActorRef; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonDeserialize -@JsonSubTypes({ - @JsonSubTypes.Type(value = GitCommand.RegisterRepository.class, name = "register-repository"), - @JsonSubTypes.Type(value = GitCommand.RegisterRawCommit.class, name = "register-raw-commit"), - @JsonSubTypes.Type(value = GitCommand.GetRepositories.class, name = "get-repositories"), - @JsonSubTypes.Type(value = GitCommand.GetUnresolvedVersions.class, name = "get-unresolved-versions"), - @JsonSubTypes.Type(value = GitCommand.MarkVersionAsResolved.class, name = "mark-version-as-resolved"), - @JsonSubTypes.Type(value = GitCommand.MarkVersionAsUnresolveable.class, name = "mark-version-as-unresolveable"), -}) -public sealed interface GitCommand extends Jsonable { - - record RegisterRepository(URI repository, ActorRef replyTo) implements GitCommand { - @JsonCreator - public RegisterRepository { - } - } - - record RegisterRawCommit( - MavenCoordinates coordinates, - String commitSha, - ActorRef replyTo - ) implements GitCommand { - @JsonCreator - public RegisterRawCommit { - } - } - - @JsonDeserialize - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - sealed interface RepositoryResponse extends Jsonable { - List repositories(); - } - - @JsonDeserialize - record RepositoriesAvaiable(List repositories) implements RepositoryResponse { - @JsonCreator - public RepositoriesAvaiable { - } - } - - @JsonDeserialize - record NoRepositories() implements RepositoryResponse { - @JsonCreator - public NoRepositories { - } - - @Override - public List repositories() { - return List.empty(); - } - } - - record GetRepositories(ActorRef replyTo) implements GitCommand { - @JsonCreator - public GetRepositories { - } - } - - record GetUnresolvedVersions(ActorRef replyTo) implements GitCommand { - @JsonCreator - public GetUnresolvedVersions { - } - } - - record MarkVersionAsResolved( - MavenCoordinates coordinates, - VersionedCommit commit, - ActorRef replyTo - ) implements GitCommand { - @JsonCreator - public MarkVersionAsResolved { - } - } - - record MarkVersionAsUnresolveable( - ActorRef replyTo, - MavenCoordinates coordinates, - String commitSha - ) implements GitCommand {} - - - static final UnresolvedWork EMPTY = new UnresolvedWork(HashMap.empty(), List.empty()); - @JsonDeserialize - @JsonSerialize - record UnresolvedWork( - Map unresolvedCommits, - List repositories - ) implements Jsonable { - @JsonCreator - public UnresolvedWork { - } - - public boolean isEmpty() { - return unresolvedCommits.isEmpty(); - } - - public boolean hasWork() { - return !unresolvedCommits.isEmpty() && !this.repositories.isEmpty(); - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitEvent.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitEvent.java deleted file mode 100644 index a4c63b9d..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitEvent.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.domain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = GitEvent.RepositoryRegistered.class), - @JsonSubTypes.Type(value = GitEvent.CommitRegistered.class), - @JsonSubTypes.Type(value = GitEvent.CommitResolved.class), -}) -public sealed interface GitEvent extends AggregateEvent, Jsonable { - - AggregateEventShards INSTANCE = AggregateEventTag.sharded(GitEvent.class, 3); - - @Override - default AggregateEventTagger aggregateTag() { - return INSTANCE; - } - - @JsonTypeName("repository-registered") - record RepositoryRegistered(URI repository) implements GitEvent { - @JsonCreator - public RepositoryRegistered { - } - } - @JsonTypeName("commit-extracted") - record CommitRegistered(MavenCoordinates coordinates, String commit) implements GitEvent { - @JsonCreator - public CommitRegistered { - } - } - @JsonTypeName("commit-resolved") - record CommitResolved(MavenCoordinates coordinates, VersionedCommit resolvedCommit) implements GitEvent { - @JsonCreator - public CommitResolved { - } - } - - @JsonTypeName("commit-unresolved") - record CommitUnresolvable(MavenCoordinates coordinates, String commit) implements GitEvent { - @JsonCreator - public CommitUnresolvable { - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitManagedArtifact.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitManagedArtifact.java deleted file mode 100644 index 361f0e11..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitManagedArtifact.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.domain; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.RetentionCriteria; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.synchronizer.gitmanaged.ScheduledCommitResolver; - -public class GitManagedArtifact extends EventSourcedBehaviorWithEnforcedReplies { - - public static final EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create( - GitCommand.class, "git-managed-artifact"); - - private final ActorContext ctx; - private final ArtifactCoordinates coordinates; - private ActorRef scheduledRefresh; - - public static Behavior create(final PersistenceId persistenceId, final String entityId) { - return Behaviors.setup(ctx -> { - final var group = Routers.group(ScheduledCommitResolver.SCHEDULED_REFRESH); - final var scheduledRefresh = ctx.spawnAnonymous(group); - return new GitManagedArtifact(persistenceId, entityId, ctx, scheduledRefresh); - }); - } - - private GitManagedArtifact( - final PersistenceId persistenceId, - final String entityId, final ActorContext ctx, - final ActorRef scheduledRefresh - ) { - super(persistenceId); - this.scheduledRefresh = scheduledRefresh; - final var split = entityId.split(":"); - this.coordinates = new ArtifactCoordinates(split[0], split[1]); - this.ctx = ctx; - } - - @Override - public GitState emptyState() { - return new GitState.Empty(); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder(); - builder.forAnyState() - .onCommand(GitCommand.RegisterRepository.class, (state, cmd) -> this.Effect() - .persist(new GitEvent.RepositoryRegistered(cmd.repository())) - .thenRun(ns -> this.scheduledRefresh.tell(new ScheduledCommitResolver.Refresh())) - .thenReply(cmd.replyTo(), ns -> Done.done()) - ) - .onCommand(GitCommand.GetRepositories.class, (state, cmd) -> this.Effect() - .reply(cmd.replyTo(), state.repositories().isEmpty() ? - new GitCommand.NoRepositories() : new GitCommand.RepositoriesAvaiable(state.repositories())) - ) - .onCommand(GitCommand.GetUnresolvedVersions.class, (state, cmd) -> { - final var unresolvedWork = state.unresolvedVersions(); - this.ctx.getLog().debug("Unresolved versions: {}", unresolvedWork); - return this.Effect() - .reply(cmd.replyTo(), unresolvedWork); - } - ) - .onCommand(GitCommand.MarkVersionAsResolved.class, (state, cmd) -> this.Effect() - .persist(new GitEvent.CommitResolved(cmd.coordinates(), cmd.commit())) - .thenRun(() -> this.scheduledRefresh.tell(new ScheduledCommitResolver.Refresh())) - .thenReply(cmd.replyTo(), ns -> Done.done()) - ) - .onCommand(GitCommand.MarkVersionAsUnresolveable.class, (state, cmd) -> this.Effect() - .persist(new GitEvent.CommitUnresolvable(cmd.coordinates(), cmd.commitSha())) - .thenRun(() -> this.scheduledRefresh.tell(new ScheduledCommitResolver.Refresh())) - .thenReply(cmd.replyTo(), ns -> Done.done())) - .onCommand(GitCommand.RegisterRawCommit.class, (state, cmd) -> this.Effect() - .persist(new GitEvent.CommitRegistered(cmd.coordinates(), cmd.commitSha())) - .thenRun(() -> this.scheduledRefresh.tell(new ScheduledCommitResolver.Refresh())) - .thenReply(cmd.replyTo(), ns -> Done.done()) - ) - ; - return builder.build(); - } - - @Override - public EventHandler eventHandler() { - final var builder = this.newEventHandlerBuilder(); - builder.forAnyState() - .onEvent( - GitEvent.RepositoryRegistered.class, - (state, event) -> state.withRepository(event.repository()) - ) - .onEvent( - GitEvent.CommitResolved.class, - (state, event) -> state.withResolvedVersion(event.coordinates(), event.resolvedCommit()) - ) - .onEvent( - GitEvent.CommitRegistered.class, - (state, event) -> state.withRawCommit(event.coordinates(), event.commit()) - ).onEvent( - GitEvent.CommitUnresolvable.class, - (state, event) -> state.withUnresolvedVersion(event.coordinates(), event.commit()) - ) - ; - return builder.build(); - } - - @Override - public RetentionCriteria retentionCriteria() { - return RetentionCriteria.snapshotEvery(10, 2); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitState.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitState.java deleted file mode 100644 index 520ccc3a..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/domain/GitState.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.domain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import io.vavr.collection.TreeMap; -import org.apache.maven.artifact.versioning.ComparableVersion; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; -import java.util.Comparator; -import java.util.Optional; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(GitState.Empty.class), - @JsonSubTypes.Type(GitState.Registered.class) -}) -public sealed interface GitState extends Jsonable { - - Comparator LATEST_COMPARATOR = Comparator.comparing( - m -> new ComparableVersion(m.version)) - .reversed(); - - GitState withRepository(URI repository); - - List repositories(); - - GitCommand.UnresolvedWork unresolvedVersions(); - - GitState withResolvedVersion( - MavenCoordinates coordinates, final VersionedCommit commit - ); - - GitState withRawCommit(MavenCoordinates coordinates, String commitSha); - - GitState withUnresolvedVersion(MavenCoordinates coordinates, final String commit); - - @JsonTypeName("empty") - final record Empty() implements GitState { - @JsonCreator - public Empty { - } - - @Override - public GitState withRepository(final URI repository) { - return new Registered( - List.of(repository), TreeMap.empty(LATEST_COMPARATOR), - HashMap.empty() - ); - } - - @Override - public List repositories() { - return List.empty(); - } - - @Override - public GitCommand.UnresolvedWork unresolvedVersions() { - return GitCommand.EMPTY; - } - - @Override - public GitState withResolvedVersion( - final MavenCoordinates coordinates, - final VersionedCommit commit - ) { - return new Registered( - List.empty(), - TreeMap.empty(LATEST_COMPARATOR), - HashMap.of(coordinates, commit) - ); - } - - @Override - public GitState withRawCommit(final MavenCoordinates coordinates, final String commitSha) { - return new Registered( - List.empty(), - TreeMap.of(LATEST_COMPARATOR, coordinates, Optional.of(commitSha)), - HashMap.empty() - ); - } - - @Override - public GitState withUnresolvedVersion( - final MavenCoordinates coordinates, - final String commit - ) { - return new Registered( - List.empty(), - TreeMap.of(LATEST_COMPARATOR, coordinates, Optional.of(commit)), - HashMap.empty(), - HashMap.of(coordinates, commit) - ); - } - } - - @JsonTypeName("registered") - final record Registered( - List repository, - Map> commits, - Map resolved, - Map unresolvable - ) implements GitState { - @JsonCreator - public Registered { - } - - public Registered( - final List repository, - final Map> commits, - final Map resolved - ) { - this(repository, commits, resolved, HashMap.empty()); - } - - @Override - public GitState withRepository(final URI repository) { - final var repos = this.repository.toSet().add(repository).toList(); - // Reset unresolved commits by re-merging with the existing set of commits - final var newCommits = this.commits.merge( - this.unresolvable.mapValues(Optional::of), (a, b) -> a.isEmpty() ? b : a); - return new Registered(repos, newCommits, this.resolved, HashMap.empty()); - } - - @Override - public List repositories() { - return this.repository; - } - - @Override - public GitCommand.UnresolvedWork unresolvedVersions() { - if (this.repository.isEmpty()) { - return GitCommand.EMPTY; - } - final var unresolvedCommits = this.commits.filterValues(Optional::isPresent) - .mapValues(Optional::get); - // Try to get the latest to work with in the first place - final var versionsWithCommits = unresolvedCommits - .toSortedMap(LATEST_COMPARATOR.reversed(), t -> t._1, t -> t._2) - .take(16); - return new GitCommand.UnresolvedWork(versionsWithCommits, this.repository); - } - - @Override - public GitState withResolvedVersion( - final MavenCoordinates coordinates, - final VersionedCommit commit - ) { - if (this.resolved.containsKey(coordinates)) { - return this; - } - final var newCommits = this.commits.remove(coordinates); - final var newResolved = this.resolved.put(coordinates, commit); - final var newUnresolved = this.unresolvable.remove(coordinates); - return new Registered(this.repository, newCommits, newResolved, newUnresolved); - } - - @Override - public GitState withRawCommit(final MavenCoordinates coordinates, final String commitSha) { - if (this.resolved.containsKey(coordinates)) { - return this; - } - final var newCommits = this.commits.put(coordinates, Optional.of(commitSha)); - return new Registered(this.repository, newCommits, this.resolved); - } - - @Override - public GitState withUnresolvedVersion( - final MavenCoordinates coordinates, - final String commit - ) { - if (this.resolved.containsKey(coordinates)) { - return this; - } - if (this.unresolvable.containsKey(coordinates)) { - return this; - } - final var newUnresolvable = this.unresolvable.put(coordinates, commit); - final var newCommits = this.commits.remove(coordinates); - return new Registered( - this.repository, - newCommits, - this.resolved, - newUnresolvable - ); - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/ActorLoggerPrinterWriter.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/ActorLoggerPrinterWriter.java deleted file mode 100644 index 0123f08c..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/ActorLoggerPrinterWriter.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import org.eclipse.jgit.lib.BatchingProgressMonitor; -import org.eclipse.jgit.lib.ProgressMonitor; -import org.slf4j.Logger; - -import java.util.Objects; - -public final class ActorLoggerPrinterWriter extends BatchingProgressMonitor implements ProgressMonitor { - private final Logger logger; - - public ActorLoggerPrinterWriter(Logger logger) { - this.logger = logger; - } - - @Override - protected void onUpdate(final String taskName, final int workCurr) { - StringBuilder s = new StringBuilder(); - format(s, taskName, workCurr); - if (this.logger.isDebugEnabled()) { - this.logger.debug(s.toString()); - } - } - - private void format(StringBuilder s, String taskName, int workCurr) { - s.append("\r"); //$NON-NLS-1$ - s.append(taskName); - s.append(": "); //$NON-NLS-1$ - while (s.length() < 25) - s.append(' '); - s.append(workCurr); - } - @Override - protected void onEndTask(final String taskName, final int workCurr) { - StringBuilder s = new StringBuilder(); - format(s, taskName, workCurr); - if (this.logger.isDebugEnabled()) { - this.logger.debug(s.toString()); - } - } - - @Override - protected void onUpdate(final String taskName, final int workCurr, final int workTotal, final int percentDone) { - StringBuilder s = new StringBuilder(); - format(s, taskName, workCurr, workTotal, percentDone); - } - - @Override - protected void onEndTask(final String taskName, final int workCurr, final int workTotal, final int percentDone) { - StringBuilder s = new StringBuilder(); - format(s, taskName, workCurr, workTotal, percentDone); - } - - private void format(StringBuilder s, String taskName, int cmp, - int totalWork, int pcnt) { - s.append("\r"); //$NON-NLS-1$ - s.append(taskName); - s.append(": "); //$NON-NLS-1$ - while (s.length() < 25) - s.append(' '); - - String endStr = String.valueOf(totalWork); - StringBuilder curStr = new StringBuilder(String.valueOf(cmp)); - while (curStr.length() < endStr.length()) - curStr.insert(0, " "); //$NON-NLS-1$ - if (pcnt < 100) - s.append(' '); - if (pcnt < 10) - s.append(' '); - s.append(pcnt); - s.append("% ("); //$NON-NLS-1$ - s.append(curStr); - s.append("/"); //$NON-NLS-1$ - s.append(endStr); - s.append(")"); //$NON-NLS-1$ - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (ActorLoggerPrinterWriter) obj; - return Objects.equals(this.logger, that.logger); - } - - @Override - public int hashCode() { - return Objects.hash(logger); - } - - @Override - public String toString() { - return "ActorLoggerPrinterWriter[" + - "logger=" + logger + ']'; - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/AssetCommitResolver.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/AssetCommitResolver.java deleted file mode 100644 index b6c7d9d2..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/AssetCommitResolver.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import akka.NotUsed; -import akka.actor.typed.javadsl.ActorContext; -import akka.japi.Pair; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.Source; -import io.vavr.CheckedFunction1; -import io.vavr.Tuple3; -import io.vavr.collection.List; -import io.vavr.control.Try; -import org.eclipse.jgit.api.Git; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.SubmoduleConfig; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.TagOpt; -import org.slf4j.Logger; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; -import java.nio.file.Path; -import java.time.Instant; -import java.util.Collections; -import java.util.Optional; - -public final class AssetCommitResolver { - - - static Source startCommitResolution( - final ActorContext ctx, - final CommitResolutionManager.ArtifactRepoState newState, - final CommitResolutionManager.ResolveCommitDetails request - ) { - final var log = ctx.getLog(); - final var checkedOut = newState.checkedOut(); - final var commitSha = request.commit(); - return Source.from(Collections.singleton(ObjectId.fromString(commitSha))) - .mapConcat(objectId -> checkedOut.map(tuple -> tuple.append(objectId))) - .async() - .via(tryResolvingCommitFromGitDirectory(request.coordinates(), log)) - .async() - .via(parseResponse(request)); - } - - sealed interface CommitResolutionResponse { - } - - record CommitResolved( - URI repo, VersionedCommit commit - ) implements CommitResolutionResponse { - } - - public record CommitNotFound(MavenCoordinates coordinates, List uris, String commit) implements CommitResolutionResponse { - } - - - static Flow>, CommitResolutionResponse, NotUsed> parseResponse( - CommitResolutionManager.ResolveCommitDetails request - ) { - return Flow.>, CommitResolutionResponse>fromFunction( - opt -> - opt.map(pair -> new CommitResolved(pair.first(), pair.second())) - .orElseGet(() -> new CommitNotFound(request.coordinates(), request.gitRepo(), request.commit())) - ); - } - - static Flow, Optional>, NotUsed> tryResolvingCommitFromGitDirectory( - MavenCoordinates coordinates, Logger log - ) { - return Flow., Optional>>fromFunction(tuple3 -> { - if (log.isDebugEnabled()) { - log.debug("Resolving commit {} for {}", tuple3._3.getName(), coordinates); - log.debug("Opening Git directory {}", tuple3._2); - } - final var directory = tuple3._2().toFile(); - final var git = Git.open(directory); - final var fetchCmd = git.fetch() - .setRecurseSubmodules(SubmoduleConfig.FetchRecurseSubmodulesMode.YES) - .setTagOpt(TagOpt.FETCH_TAGS) - .setRemoveDeletedRefs(false); - fetchCmd.call(); - - return Try.withResources(() -> new RevWalk(git.getRepository())) - .of(tryFetchAndResolveCommit(coordinates, log, tuple3._3)) - .map(convertCommitToVersionedCommit(log, tuple3._1)) - .map(Optional::of) - .getOrElseGet(t -> { - log.error("Failed to resolve commit {} for {}", tuple3._3.getName(), coordinates); - return Optional.empty(); - }); - }); - } - - static CheckedFunction1 tryFetchAndResolveCommit( - MavenCoordinates coordinates, Logger log, ObjectId objectId - ) { - return revWalk -> { - final var commit = revWalk.lookupCommit(objectId); - revWalk.parseBody(commit); - if (log.isTraceEnabled()) { - log.trace("Commit Body Parsed {}", commit); - } - revWalk.parseHeaders(commit); - if (log.isTraceEnabled()) { - log.trace("Commit Headers Parsed {}", commit.getShortMessage()); - } - final var commitTime = commit.getCommitTime(); - if (log.isTraceEnabled()) { - log.trace("Commit Revision {}", commit.getCommitTime()); - } - if (log.isDebugEnabled()) { - log.debug( - "{} at {} has {}", coordinates, objectId.name(), commitTime); - } - return commit; - }; - } - - static java.util.function.Function> convertCommitToVersionedCommit( - Logger log, URI repo - ) { - return commit -> { - if (log.isTraceEnabled()) { - log.trace("Commit Resolved {}", commit); - } - final var commitMessage = commit.getShortMessage(); - final var commitBody = commit.getFullMessage(); - final var commitId = commit.getId().getName(); - final var commitDate = commit.getCommitTime(); - final var instant = Instant.ofEpochSecond(commitDate); - final var committerIdent = commit.getCommitterIdent(); - final var committer = new VersionedCommit.Commiter( - committerIdent.getName(), committerIdent.getEmailAddress()); - final var authorIdent = commit.getAuthorIdent(); - final var author = new VersionedCommit.Author( - authorIdent.getName(), authorIdent.getEmailAddress()); - final var timeZone = committerIdent - .getTimeZone(); - final var commitDateTime = instant.atZone(timeZone.toZoneId()); - final var commitUrl = URI.create(repo.toString() + "/commit/" + commitId); - return Pair.apply(repo, new VersionedCommit( - commitMessage, - commitBody, - commitId, - author, - committer, - Optional.of(commitUrl), - commitDateTime - )); - }; - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/CommitResolutionManager.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/CommitResolutionManager.java deleted file mode 100644 index 71e77786..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/CommitResolutionManager.java +++ /dev/null @@ -1,410 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.receptionist.ServiceKey; -import akka.japi.function.Function2; -import akka.stream.Materializer; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.Sink; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorSink; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashMap; -import io.vavr.collection.HashSet; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import io.vavr.collection.Set; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.synchronizer.actor.CommitDetailsRegistrar; - -import java.net.URI; -import java.nio.file.Path; -import java.time.Duration; -import java.util.UUID; -import java.util.function.Predicate; - -/** - * An {@link akka.actor.Actor} that accepts a Versioned Artifact with a commit - * sha to resolve the - * underlying Git Commit information (like the commit message, author, etc.) as - * the sha's are being resolved. - */ -public final class CommitResolutionManager { - - public static final ServiceKey SERVICE_KEY = ServiceKey.create(Command.class, "commit-resolver"); - - - @JsonDeserialize - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(ResolveCommitDetails.class), - }) - public sealed interface Command extends Jsonable { - } - - @JsonTypeName("resolve-commit-details") - public record ResolveCommitDetails( - MavenCoordinates coordinates, - String commit, - List gitRepo, - ActorRef replyTo - ) implements Command { - - @JsonCreator - public ResolveCommitDetails { - } - } - - public static Behavior resolveCommit(ActorRef registrar) { - return Behaviors.setup(ctx -> { - final var clonerBehavior = Behaviors.supervise(RepositoryCloner.cloner()) - .onFailure(SupervisorStrategy.restart()); - StubSystemReader.init(); - final var uuid = UUID.randomUUID(); - final var cloner = ctx.spawn(clonerBehavior, "repository-cloner-" + uuid); - final var materializer = Materializer.createMaterializer(ctx); - return awaiting(cloner, registrar, materializer, ClonedRepoState.EMPTY); - }); - } - - private static Behavior awaiting( - final ActorRef cloner, - final ActorRef registrar, - final Materializer materializer, - final ClonedRepoState state - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage(ResolveCommitDetails.class, msg -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Resolving commit details for {}", msg.coordinates); - } - final var repoState = state.repositoriesCloned.get(msg.coordinates.asArtifactCoordinates()); - if (repoState.isEmpty()) { - if (msg.gitRepo.isEmpty()) { - msg.replyTo.tell(Done.done()); - return Behaviors.same(); - } - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug("[{}] Cloning {}", msg.coordinates, msg.gitRepo); - } - ctx.ask( - RepositoryCloner.CloneResponse.class, - cloner, - Duration.ofMinutes(20), - replyTo -> new RepositoryCloner.CloneRepos( - msg.coordinates.asArtifactCoordinates(), msg.gitRepo, replyTo), - handleCloneResponse(ctx, msg, state) - ); - return waitingForClones(cloner, registrar, materializer, state, List.empty()); - } - final var artifactRepoState = repoState.get(); - final var unclonedRepos = artifactRepoState.repositories.filter(Predicate.not(artifactRepoState.checkedOut::containsKey)); - final var newUncloned = msg.gitRepo.filter(Predicate.not(artifactRepoState.checkedOut::containsKey)); - final var allUncloned = unclonedRepos.addAll(newUncloned); - if (!allUncloned.isEmpty()) { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug("[{}] Cloning {}", msg.coordinates.asStandardCoordinates(), allUncloned); - } - ctx.ask( - RepositoryCloner.CloneResponse.class, - cloner, - Duration.ofMinutes(20), - replyTo -> new RepositoryCloner.CloneRepos( - msg.coordinates.asArtifactCoordinates(), allUncloned.toList(), replyTo), - handleCloneResponse(ctx, msg, state) - ); - - return waitingForClones(cloner, registrar, materializer, state, List.empty()); - } - - startCommitResolution(materializer, ctx, msg, artifactRepoState); - - return waitingForResolution(cloner, registrar, materializer, state, List.empty()); - }) - .build() - ); - } - - private static Behavior waitingForClones( - final ActorRef cloner, - final ActorRef registrar, - final Materializer materializer, - final ClonedRepoState state, - final List queue - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage( - ResolveCommitDetails.class, - msg -> waitingForClones(cloner, registrar, materializer, state, queue.append(msg)) - ) - .onMessage(FailedCheckout.class, msg -> { - msg.msg.replyTo.tell(Done.done()); - if (queue.isEmpty()) { - return awaiting(cloner, registrar, materializer, state); - } - final var head = queue.head(); - ctx.ask( - RepositoryCloner.CloneResponse.class, - cloner, - Duration.ofMinutes(20), - replyTo -> new RepositoryCloner.CloneRepos( - head.coordinates.asArtifactCoordinates(), head.gitRepo, replyTo), - handleCloneResponse(ctx, head, state) - ); - queue.tail(); - return waitingForClones(cloner, registrar, materializer, state, queue); - }) - .onMessage(RepositoriesClonedReadyToResolve.class, msg -> { - if (msg.checkedOut.isEmpty()) { - ctx.getLog().warn( - "[{}] No repositories successfully checked out with {}", msg.msg.coordinates, msg.msg.gitRepo); - msg.msg.replyTo.tell(Done.done()); - return completeResolution(cloner, registrar, materializer, state, queue, ctx); - } - final ClonedRepoState newState = state.withClonedRepos(msg); - final var request = msg.msg; - final var repos = newState.repositoriesCloned.get( - request.coordinates.asArtifactCoordinates()).get(); - startCommitResolution(materializer, ctx, request, repos); - - return waitingForResolution(cloner, registrar, materializer, newState, queue); - }) - .build()); - } - - private static Behavior waitingForResolution( - final ActorRef cloner, - final ActorRef registrar, - final Materializer materializer, - final ClonedRepoState state, - final List queue - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage( - ResolveCommitDetails.class, - msg -> waitingForResolution(cloner, registrar, materializer, state, queue.append(msg)) - ) - .onMessage(RepositoriesClonedReadyToResolve.class, msg -> { - final ClonedRepoState newState = state.withClonedRepos(msg); - return waitingForResolution(cloner, registrar, materializer, newState, queue.append(msg.msg)); - }) - .onMessage(WrappedCommitResolutionResult.class, msg -> { - final var request = msg.msg; - final var result = msg.result; - if (result instanceof AssetCommitResolver.CommitNotFound notFound) { - registrar.tell( - new CommitDetailsRegistrar.CommitNotFound( - notFound.uris().head(), - notFound.commit(), - request.coordinates, - msg.msg.replyTo - ) - ); - } else if (result instanceof AssetCommitResolver.CommitResolved resolved) { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().info("[{}] Resolved commit {}", request.coordinates, resolved.commit().link()); - } - final var commit = resolved.commit(); - - registrar.tell( - new CommitDetailsRegistrar.HandleVersionedCommitReport(resolved.repo(), commit, - request.coordinates, - request.replyTo - )); - } - return Behaviors.same(); - }) - .onMessage(CompletedResolutionAttempts.class, msg -> { - ctx.getLog().info("[{}] Completed commit {} resolution", msg.msg.coordinates.asStandardCoordinates(), msg.msg.commit); - msg.msg.replyTo.tell(Done.done()); - return completeResolution(cloner, registrar, materializer, state, queue, ctx); - }) - .onMessage(ExceptionallyCompletedResultion.class, msg -> { - msg.msg.replyTo.tell(Done.done()); - return completeResolution(cloner, registrar, materializer, state, queue, ctx); - }) - .build() - ); - } - - private static Behavior completeResolution( - ActorRef cloner, ActorRef registrar, - Materializer materializer, ClonedRepoState state, List queue, ActorContext ctx - ) { - if (queue.isEmpty()) { - return awaiting(cloner, registrar, materializer, state); - } - final var head = queue.head(); - final var artifactCoordinates = head.coordinates.asArtifactCoordinates(); - if (state.repositoriesCloned.get(artifactCoordinates).isEmpty()) { - ctx.ask( - RepositoryCloner.CloneResponse.class, - cloner, - Duration.ofMinutes(20), - replyTo -> new RepositoryCloner.CloneRepos( - head.coordinates.asArtifactCoordinates(), head.gitRepo, replyTo), - handleCloneResponse(ctx, head, state) - ); - return waitingForClones(cloner, registrar, materializer, state, queue.tail()); - } - final var artifactRepoStates = state.repositoriesCloned.get(artifactCoordinates).get(); - - startCommitResolution(materializer, ctx, head, artifactRepoStates); - - return waitingForResolution(cloner, registrar, materializer, state, queue.tail()); - } - - - private static Function2 handleCloneResponse( - ActorContext ctx, ResolveCommitDetails msg, ClonedRepoState empty - ) { - return (reply, throwable) -> { - if (throwable != null) { - ctx.getLog().error("Failed to clone repo", throwable); - return new FailedCheckout(msg, empty); - } - if (reply instanceof RepositoryCloner.SuccessfullyCloned sc) { - return new RepositoriesClonedReadyToResolve( - msg, - new ClonedRepoState(HashMap.empty()), - sc.checkedOut() - ); - } - return new FailedCheckout(msg, empty); - }; - } - - private static void startCommitResolution( - Materializer materializer, ActorContext ctx, ResolveCommitDetails head, - ArtifactRepoState artifactRepoStates - ) { - final var flow = interpretResult(head); - final Source via = AssetCommitResolver.startCommitResolution(ctx, artifactRepoStates, head) - .async() - .via(flow); - final Sink sink = ActorSink.actorRef( - ctx.getSelf(), - new CompletedResolutionAttempts(head), - t -> new ExceptionallyCompletedResultion(head, t) - ); - via.to(sink).run(materializer); - } - - private static Flow interpretResult( - ResolveCommitDetails msg - ) { - return Flow.fromFunction( - response -> new WrappedCommitResolutionResult( - msg, response)); - } - - @JsonDeserialize - public record ArtifactRepoState( - Set repositories, - Map checkedOut, - Set inUse - ) implements Jsonable { - @JsonCreator - public ArtifactRepoState { - } - } - - @JsonDeserialize - public record ClonedRepoState( - Map repositoriesCloned - ) implements Jsonable { - static final ClonedRepoState EMPTY = new ClonedRepoState(HashMap.empty()); - - @JsonCreator - public ClonedRepoState { - } - - public ClonedRepoState withClonedRepos(RepositoriesClonedReadyToResolve msg) { - final var coordinates = msg.msg.coordinates.asArtifactCoordinates(); - - if (this.repositoriesCloned.containsKey(coordinates)) { - return this; - } - final var artifactState = new ArtifactRepoState( - msg.msg.gitRepo.toSet(), - msg.checkedOut, - msg.checkedOut.keySet() - ); - final var newRepositories = this.repositoriesCloned.put(coordinates, artifactState); - return new ClonedRepoState(newRepositories); - } - - public ClonedRepoState reset() { - return new ClonedRepoState(this.repositoriesCloned.mapValues(s -> new ArtifactRepoState( - s.repositories, - s.checkedOut, - HashSet.empty() - ))); - } - } - - private record RepositoriesClonedReadyToResolve( - ResolveCommitDetails msg, - ClonedRepoState state, - Map checkedOut - ) implements Command { - } - - private record FailedCheckout(ResolveCommitDetails msg, ClonedRepoState state) implements Command { - } - - private record WrappedCommitResolutionResult( - ResolveCommitDetails msg, - AssetCommitResolver.CommitResolutionResponse result - ) implements Command { - } - - private record CompletedResolutionAttempts( - ResolveCommitDetails msg - ) implements Command { - } - - private record ExceptionallyCompletedResultion( - ResolveCommitDetails msg, - Throwable error - ) implements Command { - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/FileWalkerConsumer.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/FileWalkerConsumer.java deleted file mode 100644 index 5b2a56be..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/FileWalkerConsumer.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import akka.actor.typed.ActorRef; -import io.vavr.CheckedConsumer; - -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.function.Function; - -public final class FileWalkerConsumer extends SimpleFileVisitor { - - private final CheckedConsumer acceptFile; - - public static FileWalkerConsumer tell(final ActorRef ref, Function messageCreator) { - return new FileWalkerConsumer(path -> ref.tell(messageCreator.apply(path))); - } - - public FileWalkerConsumer(final CheckedConsumer acceptFile) { - this.acceptFile = acceptFile; - } - - @Override - public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException { - try { - acceptFile.accept(file); - } catch (Throwable e) { - throw new IOException(e); - } - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException { - try { - acceptFile.accept(dir); - } catch (Throwable e) { - throw new IOException(e); - } - return FileVisitResult.CONTINUE; - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/RepositoryCloner.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/RepositoryCloner.java deleted file mode 100644 index 62af456e..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/RepositoryCloner.java +++ /dev/null @@ -1,270 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.japi.Pair; -import akka.stream.Materializer; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.Sink; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorSink; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashMap; -import io.vavr.collection.HashSet; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import io.vavr.collection.Set; -import io.vavr.control.Either; -import io.vavr.control.Try; -import org.eclipse.jgit.api.Git; -import org.slf4j.Logger; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.UUID; -import java.util.function.Function; - -public final class RepositoryCloner { - - sealed interface CloneCommand extends Jsonable { - } - - sealed interface CloneResponse extends Jsonable { - } - - record CloneRepos( - ArtifactCoordinates coordinates, - List urls, - ActorRef replyTo - ) implements CloneCommand { - } - - public static Behavior cloner() { - return Behaviors.setup(ctx -> { - final var materializer = Materializer.createMaterializer(ctx); - return waiting(materializer); - }); - } - - private static Behavior waiting(Materializer materializer) { - return Behaviors.setup(ctx -> Behaviors.receive(CloneCommand.class) - .onMessage(CloneRepos.class, msg -> { - callClone(ctx, materializer, msg); - return cloning(new CloneState(msg, msg.coordinates), materializer, List.empty()); - }) - .build()); - } - - record SuccessfullyCloned( - ArtifactCoordinates coordinates, - Map checkedOut - ) implements CloneResponse { - } - - record PartialClone( - ArtifactCoordinates coordinates, - Map checkedOut - ) implements CloneResponse { - } - - private record CloneState( - CloneRepos command, - ArtifactCoordinates coordinates, - Set completed, - Map checkedOut, - Set failed - ) { - - public CloneState(CloneRepos msg, ArtifactCoordinates coordinates) { - this(msg, coordinates, HashSet.empty(), HashMap.empty(), HashSet.empty()); - } - - CloneState withFailed(URI uri) { - if (this.completed.contains(uri)) { - return this; - } - return new CloneState( - this.command, - this.coordinates, - this.completed.add(uri), - this.checkedOut, - this.failed.add(uri) - ); - } - - CloneState withSucceeded(URI repo, Path path) { - if (this.completed.contains(repo)) { - return this; - } - return new CloneState( - this.command, - this.coordinates, - this.completed.add(repo), - this.checkedOut.put(repo, path), - this.failed - ); - } - } - - - private static Behavior cloning(CloneState state, Materializer materializer, List queue) { - return Behaviors.setup(ctx -> Behaviors.receive(CloneCommand.class) - .onMessage(CloneRepos.class, msg -> cloning(state, materializer, queue.append(msg))) - .onMessage(CloneFailed.class, msg -> { - ctx.getLog().warn(String.format("[%s] Clone failed for %s", msg.coordinates, msg.repo), msg.cause); - final var newState = state.withFailed(msg.repo); - return cloning(newState, materializer, queue); - }) - .onMessage(CloneSucceeded.class, msg -> { - final var newState = state.withSucceeded(msg.repo, msg.path); - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug("Clone Succeeded for {}", msg.repo); - } - return cloning(newState, materializer, queue); - }) - .onMessage(CloneFailedCompletion.class, msg -> { - ctx.getLog().warn("[{}] Clone failed for {}", msg.coordinates, msg.cause); - state.command.replyTo.tell(new PartialClone(state.coordinates, state.checkedOut)); - return cloneNextOrWait(materializer, queue, ctx); - }) - .onMessage(CloneCompleted.class, msg -> { - ctx.getLog().info("Clone completed for {}", state.coordinates); - state.command.replyTo.tell(new SuccessfullyCloned(state.coordinates, state.checkedOut)); - return cloneNextOrWait(materializer, queue, ctx); - }) - .build()); - } - - private static Behavior cloneNextOrWait( - final Materializer materializer, final List queue, - final ActorContext ctx - ) { - if (queue.isEmpty()) { - return waiting(materializer); - } - final var head = queue.head(); - final var nextState = new CloneState( - head, - head.coordinates - ); - callClone(ctx, materializer, head); - return cloning(nextState, materializer, queue.tail()); - } - - private static void callClone(ActorContext ctx, Materializer materializer, CloneRepos head) { - final Source urls = Source.from(head.urls); - final var log = ctx.getLog(); - final Flow flow = Flow.fromFunction(url -> cloneRepo(log, head.coordinates, url) - .map(path -> { - if (log.isDebugEnabled()) { - log.debug("[{}] Cloned {} to {}", head.coordinates.asMavenString(), url, path); - } - return new CloneSucceeded(head.coordinates, url, path); - }) - .mapLeft(uri -> { - if (log.isDebugEnabled()) { - log.debug("[{}] Clone failed for {}", head.coordinates.asMavenString(), uri); - } - return new CloneFailed(head.coordinates, uri.first(), uri.second()); - }) - .fold(Function.identity(), Function.identity()) - ); - final Sink sink = ActorSink.actorRef( - ctx.getSelf(), new CloneCompleted(head.coordinates), - t -> new CloneFailedCompletion(head.coordinates, t) - ); - urls - .via(flow) - .to(sink) - .run(materializer); - } - - - private record CloneFailed( - ArtifactCoordinates coordinates, - URI repo, - Throwable cause - ) implements CloneCommand { - } - - private record CloneFailedCompletion( - ArtifactCoordinates coordinates, - Throwable cause - ) implements CloneCommand { - - } - - private record CloneSucceeded( - ArtifactCoordinates coordinates, - URI repo, - Path path - ) implements CloneCommand { - } - - private record CloneCompleted(ArtifactCoordinates coordinates) implements CloneCommand { - } - - private static Either, Path> cloneRepo( - final Logger log, - final ArtifactCoordinates coordinates, - final URI remoteRepo - ) { - final var tempdirPrefix = String.format( - "soad-%s-%s", coordinates.artifactId(), - UUID.randomUUID() - ); - final var repoDirectory = Try.of(() -> Files.createTempDirectory( - tempdirPrefix - )); - - if (log.isTraceEnabled()) { - log.trace("Preparing directory for checkout {}", repoDirectory); - } - final var writer = new ActorLoggerPrinterWriter(log); - final var cloneCommand = repoDirectory.mapTry(Path::toFile) - .map(directory -> Git.cloneRepository() - .setDirectory(directory) - .setProgressMonitor(writer) - .setCloneAllBranches(false) - .setCloneSubmodules(true) - .setURI(remoteRepo.toString()) - ); - if (log.isTraceEnabled()) { - log.trace("Checking out {} to {}", remoteRepo, repoDirectory); - } - return cloneCommand.mapTry(org.eclipse.jgit.api.CloneCommand::call) - .flatMapTry(repo -> repoDirectory) - .toEither() - .mapLeft(t -> Pair.create(remoteRepo, t)); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/StubSystemReader.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/StubSystemReader.java deleted file mode 100644 index 01232b26..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/gitmanaged/util/jgit/StubSystemReader.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.gitmanaged.util.jgit; - -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.SystemReader; - -import java.io.File; -import java.net.URL; - -public class StubSystemReader extends SystemReader { - - private static final class Holder { - private static final URL dummyGitConfig = Holder.class.getClassLoader().getResource( - "soad.gitconfig"); - private static final File gitConfig = new File(dummyGitConfig.getFile()); - static final SystemReader INSTANCE = new StubSystemReader(gitConfig); - static { - SystemReader.setInstance(INSTANCE); - } - } - - public static SystemReader getInstance() { - return Holder.INSTANCE; - } - - public static void init() { - final var instance = getInstance(); - if (SystemReader.getInstance() != instance) { - SystemReader.setInstance(instance); - } - } - - private static final SystemReader proxy = SystemReader.getInstance(); - private final File userGitConfig; - - public StubSystemReader(File userGitConfig) { - super(); - this.userGitConfig = userGitConfig; - } - - @Override - public String getenv(String variable) { - return proxy.getenv(variable); - } - - @Override - public String getHostname() { - return proxy.getHostname(); - } - - @Override - public String getProperty(String key) { - return proxy.getProperty(key); - } - - @Override - public long getCurrentTime() { - return proxy.getCurrentTime(); - } - - @Override - public int getTimezone(long when) { - return proxy.getTimezone(when); - } - - @Override - public FileBasedConfig openUserConfig(Config parent, FS fs) { - return new FileBasedConfig(parent, userGitConfig, fs); - } - - // Return an empty system configuration, based on example in SystemReader.Default#openSystemConfig - @Override - public FileBasedConfig openSystemConfig(Config parent, FS fs) { - return new FileBasedConfig(parent, this.userGitConfig, fs) { - @Override - public void load() { - } - - @Override - public boolean isOutdated() { - return false; - } - }; - } - - @Override - public FileBasedConfig openJGitConfig(final Config parent, final FS fs) { - return new FileBasedConfig(parent, this.userGitConfig, fs) { - @Override - public void load() { - } - - @Override - public boolean isOutdated() { - return false; - } - }; - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/RequestArtifactsToSync.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/RequestArtifactsToSync.java deleted file mode 100644 index b787fbd3..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/RequestArtifactsToSync.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.query.GetArtifactsResponse; - -public class RequestArtifactsToSync { - - @JsonDeserialize - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - public sealed interface Command extends Jsonable { - } - - public record GatherGroupArtifacts( - String groupCoordinates, - ActorRef replyTo - ) implements Command { - } - - private record WrappedArtifactsToSync( - ArtifactsToSync result, - ActorRef replyTo - ) implements Command { - } - - public record ArtifactsToSync( - List artifactsNeeded - ) { - } - - public static Behavior create( - final ArtifactService artifactService - ) { - return Behaviors.setup(ctx -> Behaviors.receive(RequestArtifactsToSync.Command.class) - .onMessage(GatherGroupArtifacts.class, (g) -> { - final var listCompletableFuture = artifactService.getArtifacts(g.groupCoordinates).invoke() - .thenApply(artifactsResponse -> { - if (!(artifactsResponse instanceof GetArtifactsResponse.ArtifactsAvailable a)) { - return List.empty(); - } - return a.artifactIds() - .map(id -> new ArtifactCoordinates(g.groupCoordinates, id)); - }); - ctx.pipeToSelf(listCompletableFuture, (ok, exception) -> { - if (exception == null) { - return new WrappedArtifactsToSync(new ArtifactsToSync(ok), g.replyTo); - } - return new WrappedArtifactsToSync(new ArtifactsToSync(List.empty()), g.replyTo); - }); - return Behaviors.same(); - }) - .onMessage(WrappedArtifactsToSync.class, (w) -> { - w.replyTo.tell(w.result); - return Behaviors.same(); - }) - .build()); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncExtension.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncExtension.java deleted file mode 100644 index a058f185..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncExtension.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync; - -import akka.actor.AbstractExtensionId; -import akka.actor.ExtendedActorSystem; -import akka.actor.ExtensionIdProvider; - -public class ResyncExtension extends AbstractExtensionId - implements ExtensionIdProvider { - public static final ResyncExtension SettingsProvider = new ResyncExtension(); - - @Override - public ResyncSettings createExtension(final ExtendedActorSystem system) { - return new ResyncSettings(system.settings().config().getConfig("systemofadownload.synchronizer.worker.resync")); - } - - @Override - public ResyncExtension lookup() { - return SettingsProvider; - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncManager.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncManager.java deleted file mode 100644 index 5251200a..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncManager.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.Behavior; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.stream.javadsl.Flow; -import akka.stream.javadsl.Sink; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorFlow; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.Group; -import org.spongepowered.downloads.artifact.api.query.GroupsResponse; -import org.spongepowered.synchronizer.SynchronizerSettings; -import org.spongepowered.synchronizer.actor.ArtifactSyncWorker; - -import java.time.Duration; -import java.util.UUID; -import java.util.concurrent.CompletionStage; - -public final class ResyncManager { - - sealed interface Resync { - } - - record PerformResync() implements Resync { - } - - record ArtifactsToSync(List artifacts) implements Resync { - } - - public static Behavior create( - final ArtifactService artifactService, - final SynchronizerSettings.VersionSync versionSync - ) { - return Behaviors.withTimers(t -> { - t.startTimerWithFixedDelay("resync", new PerformResync(), versionSync.interval); - t.startSingleTimer("start", new PerformResync(), versionSync.startupDelay); - return setup(artifactService); - }); - } - - private static Behavior setup( - final ArtifactService artifactService - ) { - return Behaviors.setup(ctx -> { - final var sharding = ClusterSharding.get(ctx.getSystem()); - final var dispatch = DispatcherSelector.defaultDispatcher(); - final var requester = ctx.spawn( - Behaviors.supervise(RequestArtifactsToSync.create(artifactService)).onFailure( - SupervisorStrategy.restart()), - String.format("requester-%s-%d", UUID.randomUUID(), System.currentTimeMillis()), - dispatch - ); - final var artifactSyncPool = Routers.pool( - 4, - Behaviors.supervise(ArtifactSyncWorker.create(sharding)) - .onFailure( - SupervisorStrategy.restartWithBackoff(Duration.ofSeconds(1), Duration.ofMinutes(10), 0.2)) - ); - final var syncWorker = ctx.spawn( - artifactSyncPool, - String.format("pooled-artifact-synchronization-workers-%s-%d", UUID.randomUUID(), System.currentTimeMillis()), - dispatch - ); - final var requestFlow = ActorFlow.ask( - requester, - Duration.ofSeconds(30), - RequestArtifactsToSync.GatherGroupArtifacts::new - ); - - final var registrationFlow = ActorFlow.ask( - syncWorker, - Duration.ofMinutes(20), - ArtifactSyncWorker.PerformResync::new - ); - return awaiting(artifactService, requestFlow, registrationFlow); - }); - } - - private static Behavior awaiting( - final ArtifactService artifactService, - final Flow requestFlow, - final Flow registrationFlow - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Resync.class) - .onMessage(ArtifactsToSync.class, result -> { - Source.from(result.artifacts) - .async() - .via(registrationFlow.async()) - .runWith(Sink.ignore(), ctx.getSystem()); - return Behaviors.same(); - }) - .onMessage(PerformResync.class, g -> { - final var makeRequest = artifactService.getGroups() - .invoke() - .thenApply(groups -> ((GroupsResponse.Available) groups).groups()) - .thenCompose(groups -> { - final Sink, CompletionStage>> fold = Sink.fold( - List.empty(), List::appendAll); - return Source.from(groups.map(Group::groupCoordinates).asJava()) - .async() - .via(requestFlow) - .map(RequestArtifactsToSync.ArtifactsToSync::artifactsNeeded) - .runWith(fold, ctx.getSystem()); - }); - ctx.pipeToSelf(makeRequest, (ok, exception) -> { - if (exception != null) { - ctx.getLog().error("Failed to process sync", exception); - } - return new ArtifactsToSync(ok); - }); - return Behaviors.same(); - }) - .build()); - - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncSettings.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncSettings.java deleted file mode 100644 index 85533b3f..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/ResyncSettings.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync; - -import akka.actor.Extension; -import com.typesafe.config.Config; - -import java.time.Duration; -import java.util.concurrent.TimeUnit; - -public class ResyncSettings implements Extension { - public final String repository; - public final Duration timeout; - public final int retryCount; - public final String agentName; - - public ResyncSettings(Config config) { - this.repository = config.getString("repository"); - this.retryCount = config.getInt("retry"); - this.agentName = config.getString("agent-name"); - this.timeout = Duration.ofSeconds(config.getDuration("timeout", TimeUnit.SECONDS)); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/ArtifactSynchronizerAggregate.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/ArtifactSynchronizerAggregate.java deleted file mode 100644 index 7bb8d69d..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/ArtifactSynchronizerAggregate.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync.domain; - -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.EntityContext; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import com.fasterxml.jackson.dataformat.xml.XmlMapper; -import io.vavr.collection.List; -import io.vavr.control.Try; -import io.vavr.jackson.datatype.VavrModule; -import org.spongepowered.downloads.maven.artifact.ArtifactMavenMetadata; -import org.spongepowered.synchronizer.resync.ResyncExtension; -import org.spongepowered.synchronizer.resync.ResyncSettings; - -import javax.xml.stream.XMLInputFactory; -import java.io.Serial; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.util.StringJoiner; -import java.util.concurrent.CompletableFuture; - -public final class ArtifactSynchronizerAggregate - extends EventSourcedBehaviorWithEnforcedReplies { - public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create(Command.class, "ArtifactSynchronizer"); - private final ActorContext ctx; - private final ResyncSettings settings; - private final HttpClient httpClient; - private final XmlMapper mapper; - - public ArtifactSynchronizerAggregate( - final EntityContext context, final ActorContext ctx, - final ResyncSettings settings - ) { - super( - // PersistenceId needs a typeHint (or namespace) and entityId, - // we take then from the EntityContext - PersistenceId.of( - context.getEntityTypeKey().name(), // <- type hint - context.getEntityId() // <- business id - )); - this.ctx = ctx; - this.settings = settings; - final var mapper = new XmlMapper(); - mapper.registerModule(new VavrModule()); - this.httpClient = HttpClient.newBuilder() - .connectTimeout(this.settings.timeout) - .version(HttpClient.Version.HTTP_2) - .build(); - this.mapper = mapper; - } - - public static Behavior create(EntityContext context) { - return Behaviors.setup(ctx -> { - final ResyncSettings settings = ResyncExtension.SettingsProvider.get(ctx.getSystem()); - - return new ArtifactSynchronizerAggregate(context, ctx, settings); - }); - } - - @Override - public SyncState emptyState() { - return SyncState.EMPTY; - } - - @Override - public EventHandler eventHandler() { - final var builder = newEventHandlerBuilder() - .forAnyState() - .onEvent( - SynchronizeEvent.SynchronizedArtifacts.class, - (event) -> new SyncState(event.updatedTime(), event.metadata()) - ); - return builder.build(); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder() - .forAnyState() - .onCommand(Command.Resync.class, this::handleResync) - .onCommand(Command.WrappedResync.class, this::handleResponse); - return builder.build(); - } - - private ReplyEffect handleResponse(SyncState state, Command.WrappedResync cmd) { - if (cmd.response() instanceof Command.Failed) { - return this.Effect().reply(cmd.replyTo(), List.empty()); - } - if (cmd.response() instanceof Command.Completed c) { - final var metadata = c.metadata(); - if (metadata.versioning().lastUpdated.equals(state.lastUpdated)) { - final var versionsToSync = state.versions.versioning() - .versions.map(state.coordinates()::version); - return this.Effect() - .reply(cmd.replyTo(), versionsToSync); - } - return this.Effect() - .persist(new SynchronizeEvent.SynchronizedArtifacts(metadata, metadata.versioning().lastUpdated)) - .thenReply(cmd.replyTo(), s -> s.versions.versioning().versions.map(s.coordinates()::version)); - } - return this.Effect().reply(cmd.replyTo(), List.empty()); - } - - - private ReplyEffect handleResync(SyncState state, Command.Resync cmd) { - final var groupId = !state.groupId.equals(cmd.coordinates().groupId()) - ? cmd.coordinates().groupId() - : state.groupId; - final var artifactId = !state.artifactId.equals(cmd.coordinates().artifactId()) - ? cmd.coordinates().artifactId() - : state.artifactId; - ctx.pipeToSelf( - getArtifactMetadata(groupId, artifactId), - (response, throwable) -> { - if (throwable != null) { - if (throwable instanceof UnsupportedArtifactException ua) { - this.ctx.getLog().warn( - String.format("Unsupported artifact %s", state.coordinates()), - throwable - ); - } else { - this.ctx.getLog().error( - String.format("Unable to get maven-metadata.xml for artifact: %s", state.coordinates()), - throwable - ); - } - return new Command.WrappedResync(new Command.Failed(), cmd.replyTo()); - } - return new Command.WrappedResync(new Command.Completed(response), cmd.replyTo()); - } - ); - return this.Effect().noReply(); - } - - private static final class UnsupportedArtifactException extends Exception { - - - @Serial private static final long serialVersionUID = 4579607644429804821L; - - public UnsupportedArtifactException(final String artifact, final String url) { - super(String.format("Unsupported artifact by id %s using url: %s", artifact, url)); - } - } - - private CompletableFuture getArtifactMetadata(String groupId, String artifactId) { - final var url = new StringJoiner("/", this.settings.repository, "") - .add(groupId.replace(".", "/")) - .add(artifactId) - .add("maven-metadata.xml") - .toString(); - return Try.of(() -> URI.create(url)) - .map(uri -> HttpRequest.newBuilder() - .uri(uri) - .header("User-Agent", this.settings.agentName) - .build()) - .toCompletableFuture() - .thenCompose(request -> this.httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofInputStream()) - .thenCompose(response -> Try.of(() -> response) - .filterTry( - r -> r.statusCode() == 200, - () -> new UnsupportedArtifactException(groupId + ":" + artifactId, url) - ) - .toCompletableFuture() - ) - .thenApply(HttpResponse::body) - .thenApply(is -> Try.of(() -> XMLInputFactory.newFactory().createXMLStreamReader(is)).get()) - .thenCompose(reader -> Try.of( - () -> this.mapper.readValue(reader, ArtifactMavenMetadata.class)).toCompletableFuture()) - ); - } - -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/Command.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/Command.java deleted file mode 100644 index 8a36be72..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/Command.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync.domain; - -import akka.actor.typed.ActorRef; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.maven.artifact.ArtifactMavenMetadata; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = Command.Resync.class, name = "resync"), - @JsonSubTypes.Type(value = Command.Failed.class, name = "failed"), - @JsonSubTypes.Type(value = Command.WrappedResync.class, name = "wrapped-resync"), - @JsonSubTypes.Type(value = Command.Completed.class, name = "completed"), -}) -public interface Command extends Jsonable { - @JsonDeserialize - record Resync( - ArtifactCoordinates coordinates, - ActorRef> replyTo - ) implements Command { - @JsonCreator - public Resync {} - - } - - record Completed(ArtifactMavenMetadata metadata) implements Response { - @JsonCreator - public Completed { - } - } - - @JsonDeserialize - sealed interface Response extends Command { - } - - @JsonDeserialize - record Failed() implements Response { - @JsonCreator - public Failed { - } - } - - record WrappedResync( - Response response, - ActorRef> replyTo - ) implements Response { - @JsonCreator - public WrappedResync { - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SyncState.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SyncState.java deleted file mode 100644 index a8cdbaff..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SyncState.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync.domain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.maven.artifact.ArtifactMavenMetadata; -import org.spongepowered.downloads.maven.artifact.Versioning; - -@JsonDeserialize -final class SyncState implements Jsonable { - - public final String groupId; - public final String artifactId; - public final String lastUpdated; - public final ArtifactMavenMetadata versions; - - @JsonIgnore - static final SyncState EMPTY = new SyncState("", new ArtifactMavenMetadata("", "", new Versioning())); - - @JsonCreator - public SyncState( - final String lastUpdated, - final ArtifactMavenMetadata versions - ) { - this.lastUpdated = lastUpdated; - this.versions = versions; - this.groupId = versions.groupId(); - this.artifactId = versions.artifactId(); - } - - ArtifactCoordinates coordinates() { - return new ArtifactCoordinates(this.groupId, this.artifactId); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SynchronizeEvent.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SynchronizeEvent.java deleted file mode 100644 index ea505c28..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/resync/domain/SynchronizeEvent.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.resync.domain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.maven.artifact.ArtifactMavenMetadata; - -interface SynchronizeEvent extends Jsonable, AggregateEvent { - - AggregateEventShards TAG = AggregateEventTag.sharded(SynchronizeEvent.class, 3); - - @Override - default AggregateEventTagger aggregateTag() { - return TAG; - } - - @JsonDeserialize - record SynchronizedArtifacts( - ArtifactMavenMetadata metadata, - String updatedTime - ) implements SynchronizeEvent { - @JsonCreator - public SynchronizedArtifacts {} - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactConsumer.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactConsumer.java deleted file mode 100644 index 1fddd092..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactConsumer.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import akka.Done; -import akka.NotUsed; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Routers; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.stream.javadsl.Flow; -import akka.stream.typed.javadsl.ActorFlow; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.event.GroupUpdate; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.synchronizer.SonatypeSynchronizer; -import org.spongepowered.synchronizer.SynchronizerSettings; -import org.spongepowered.synchronizer.actor.ArtifactSyncWorker; - -public final class ArtifactConsumer { - public static void subscribeToArtifactUpdates( - final ActorContext context, - final ArtifactService artifactService, - final VersionsService versionsService, - final ClusterSharding clusterSharding, - final SynchronizerSettings settings - ) { - // region Synchronize Artifact Versions from Maven - final AuthUtils auth = AuthUtils.configure(context.getSystem().settings().config()); - ArtifactVersionSyncModule.setup(context, clusterSharding, auth, versionsService); - - final var syncWorkerBehavior = ArtifactSyncWorker.create(clusterSharding); - final var pool = Routers.pool( - settings.reactiveSync.poolSize, - syncWorkerBehavior - ); - - final var registrationRef = context.spawn( - pool, - "group-event-subscriber", - DispatcherSelector.defaultDispatcher() - ); - final Flow actorAsk = ActorFlow.ask( - settings.reactiveSync.parallelism, - registrationRef, settings.reactiveSync.timeOut, - (g, b) -> { - if (!(g instanceof GroupUpdate.ArtifactRegistered a)) { - return new ArtifactSyncWorker.Ignored(b); - } - return new ArtifactSyncWorker.PerformResync(a.coordinates(), b); - } - ); - artifactService.groupTopic() - .subscribe() - .atLeastOnce(actorAsk); - // endregion - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncEntity.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncEntity.java deleted file mode 100644 index e4675ae9..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncEntity.java +++ /dev/null @@ -1,307 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.actor.typed.javadsl.TimerScheduler; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.pattern.CircuitBreakerOpenException; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.RecoveryCompleted; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import akka.persistence.typed.javadsl.RetentionCriteria; -import akka.persistence.typed.javadsl.SignalHandler; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; - -import java.time.Duration; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeoutException; -import java.util.function.BiFunction; -import java.util.function.Supplier; - -public class ArtifactVersionSyncEntity - extends EventSourcedBehaviorWithEnforcedReplies { - - public static final EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create( - SyncRegistration.class, "artifact-version-sync"); - - private final ActorContext ctx; - private final TimerScheduler timers; - private final AuthUtils auth; - private final VersionsService service; - private final ActorRef batchSync; - - public static Behavior create( - final AuthUtils auth, final VersionsService service, final PersistenceId persistenceId - ) { - - return Behaviors.setup(ctx -> { - final var router = Routers.group(BatchVersionSyncManager.KEY); - final var ref = ctx.spawnAnonymous(router); - return Behaviors.withTimers( - timers -> new ArtifactVersionSyncEntity(ctx, timers, auth, service, persistenceId, ref)); - }); - } - - public ArtifactVersionSyncEntity( - final ActorContext ctx, - final TimerScheduler timers, - final AuthUtils auth, - final VersionsService service, - final PersistenceId persistenceId, - final ActorRef ref - ) { - super(persistenceId); - this.ctx = ctx; - this.timers = timers; - this.auth = auth; - this.service = service; - this.batchSync = ref; - } - - @Override - public VersionRegistrationState emptyState() { - return new VersionRegistrationState.Empty(); - } - - @Override - public EventHandler eventHandler() { - final var builder = this.newEventHandlerBuilder(); - builder.forAnyState() - .onEvent( - VersionSyncEvent.RegisteredBatch.class, - (state, evt) -> { - this.batchSync.tell(new BatchVersionSyncManager.ArtifactToSync(evt.artifact())); - return state.acceptBatch(evt.artifact(), evt.coordinates()); - } - ) - .onEvent(VersionSyncEvent.RegisteredVersion.class, (state, evt) -> { - this.batchSync.tell( - new BatchVersionSyncManager.ArtifactToSync(evt.coordinates().asArtifactCoordinates())); - return state.acceptVersion(evt.coordinates()); - }) - .onEvent(VersionSyncEvent.ResolvedVersion.class, (state, evt) -> { - this.batchSync.tell( - new BatchVersionSyncManager.ArtifactToSync(evt.coordinates().asArtifactCoordinates())); - return state.resolvedVersion(evt.coordinates()); - }) - .onEvent(VersionSyncEvent.StartedBatchRegistration.class, (state, evt) -> state.startBatch(evt.batched())) - .onEvent(VersionSyncEvent.FailedVersion.class, (state, evt) -> state.failedVersion(evt.coordinates())) - ; - return builder.build(); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder(); - builder.forAnyState() - .onCommand(SyncRegistration.Register.class, (state, cmd) -> { - if (state.hasVersion(cmd.coordinates())) { - return this.Effect() - .noReply(); - } - return this.Effect() - .persist(new VersionSyncEvent.RegisteredVersion(cmd.coordinates())) - .thenRun( - () -> this.timers.startSingleTimer( - cmd.coordinates(), SyncRegistration.Timeout.INSTANCE, Duration.ofSeconds(1))) - .thenNoReply(); - }) - .onCommand(SyncRegistration.MarkRegistered.class, (state, cmd) -> this.Effect() - .persist(new VersionSyncEvent.ResolvedVersion(cmd.coordinates())) - .thenRun(this::checkIfStillHasPending) - .thenNoReply() - ) - .onCommand(SyncRegistration.DelayRegistration.class, (state, cmd) -> { - this.timers.startSingleTimer( - cmd.coordinates(), new SyncRegistration.RetryFailed(cmd.coordinates()), cmd.duration()); - return this.Effect().noReply(); - }) - .onCommand(SyncRegistration.RetryFailed.class, (state, cmd) -> { - final var request = this.createRegistrationRequest(cmd.coordinates()); - this.ctx.pipeToSelf(request.serviceCall().get(), request.onComplete()::apply); - return this.Effect() - .noReply(); - }) - .onCommand(SyncRegistration.Refresh.class, (state, cmd) -> { - final List pending = state.getPending(); - if (state.isActive() && !pending.isEmpty()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug("Still awaiting for versions to complete registration: {}", pending); - } - pending.forEachWithIndex((coordinates, index) -> this.timers.startSingleTimer(coordinates, - new SyncRegistration.RetryFailed(coordinates), Duration.ofSeconds((int) (1 + (0.2 * index))) - )); - this.timers.startSingleTimer("refresh", SyncRegistration.Refresh.INSTANCE, Duration.ofSeconds(20 + pending.size())); - return this.Effect().noReply(); - } - return this.registerVersionsInBatches(state); - }) - .onCommand(SyncRegistration.AlreadyRegistered.class, (state, cmd) -> this.Effect() - .persist(new VersionSyncEvent.ResolvedVersion(cmd.coordinates())) - .thenRun(this::checkIfStillHasPending) - .thenNoReply() - ) - .onCommand(SyncRegistration.GroupUnregistered.class, (state, cmd) -> this.Effect() - .persist(new VersionSyncEvent.FailedVersion(cmd.coordinates())) - .thenRun(this::checkIfStillHasPending) - .thenNoReply() - ) - .onCommand(SyncRegistration.SyncBatch.class, (state, cmd) -> this.Effect() - .persist(new VersionSyncEvent.RegisteredBatch(cmd.artifact(), cmd.coordinates())) - .thenRun(() -> this.timers.startSingleTimer("timeout", SyncRegistration.Timeout.INSTANCE, - Duration.ofSeconds(1) - )) - .thenReply(cmd.replyTo(), ns -> Done.done())) - .onCommand(SyncRegistration.Timeout.class, (state, cmd) -> this.registerVersionsInBatches(state)) - ; - return builder.build(); - } - - private void checkIfStillHasPending(VersionRegistrationState state) { - // Go ahead and ask the state to update for the next batch - if (!state.isActive()) { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("No more pending versions, scheduling a refresh in 500 milliseconds"); - } - this.timers.startSingleTimer("refresh", SyncRegistration.Refresh.INSTANCE, Duration.ofMillis(500)); - } else { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("State is currently active, {}", state.getPending()); - } - } - } - - private ReplyEffect registerVersionsInBatches( - VersionRegistrationState state - ) { - if (state.isActive() && !state.getPending().isEmpty()) { - final var pending = state.getPending(); - this.ctx.getLog().warn("Resubmitting version registration due to pending versions: {}", pending); - pending.map(this::createRegistrationRequest).forEach( - p -> this.ctx.pipeToSelf(p.serviceCall().get(), p.onComplete::apply)); - return this.Effect().noReply(); - } - if (!state.getPending().isEmpty()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug("Rescheduling Refresh due to Pending registrations: {}", state.getPending()); - } - this.timers.startSingleTimer("refresh", SyncRegistration.Refresh.INSTANCE, Duration.ofSeconds(2)); - this.batchSync.tell(new BatchVersionSyncManager.ArtifactToSync(state.coordinates())); - return this.Effect().noReply(); - } - final List batched = state.getNextBatch(); - if (batched.isEmpty()) { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("No more pending batches"); - } - return this.Effect().noReply(); - } - final var map = batched.map(this::createRegistrationRequest); - return this.Effect() - .persist(new VersionSyncEvent.StartedBatchRegistration(batched)) - .thenRun(ns -> map.forEach(p -> this.ctx.pipeToSelf(p.serviceCall().get(), p.onComplete()::apply))) - .thenRun( - ns -> this.timers.startSingleTimer("refresh", SyncRegistration.Refresh.INSTANCE, Duration.ofSeconds(2))) - .thenRun(ns -> this.batchSync.tell(new BatchVersionSyncManager.ArtifactToSync(ns.coordinates()))) - .thenNoReply(); - } - - private VersionRegistrationParams createRegistrationRequest(MavenCoordinates c) { - - final var serviceCallSupplier = (Supplier>) () -> this.auth.internalAuth( - this.service.registerArtifactCollection(c.groupId, c.artifactId)) - .invoke(new VersionRegistration.Register.Version(c)) - .toCompletableFuture(); - final var onComplete = (BiFunction) (ok, failure) -> { - if (failure != null) { - if (failure instanceof CompletionException ce) { - failure = ce.getCause(); - } - if (failure instanceof CircuitBreakerOpenException cbe) { - this.ctx.getLog().warn("Circuit breaker is open, delaying registration of {}", c); - return new SyncRegistration.DelayRegistration( - c, Duration.ofMillis(cbe.remainingDuration().toMillis()).plus(Duration.ofSeconds(4))); - } else if (failure instanceof TimeoutException) { - ctx.getLog().warn("Rescheduling asset registration for {}", c); - return new SyncRegistration.DelayRegistration(c, Duration.ofSeconds(2)); - } - ctx.getLog().error( - String.format( - "Received error trying to synchronize %s", - c.asStandardCoordinates() - ), failure); - return new SyncRegistration.RetryFailed(c); - } - if (ok instanceof VersionRegistration.Response.ArtifactAlreadyRegistered a) { - ctx.getLog().trace("Redundant registration of {}", a.coordinates()); - return new SyncRegistration.AlreadyRegistered(c); - } else if (ok instanceof VersionRegistration.Response.GroupMissing gm) { - ctx.getLog().error("Group missing for {}", gm.groupId()); - return new SyncRegistration.GroupUnregistered(c); - } else if (ok instanceof VersionRegistration.Response.RegisteredArtifact r) { - ctx.getLog().trace("Successful registration of {}", r.mavenCoordinates()); - return new SyncRegistration.MarkRegistered(c); - } - ctx.getLog().warn("Failed registration synchronizing {} with response {}", c, ok); - return new SyncRegistration.RetryFailed(c); - }; - return new VersionRegistrationParams(serviceCallSupplier, onComplete); - } - - record VersionRegistrationParams( - Supplier> serviceCall, - BiFunction onComplete - ) { - } - - @Override - public SignalHandler signalHandler() { - final var builder = this.newSignalHandlerBuilder(); - // Enable restarting our timers - builder.onSignal(RecoveryCompleted.class, (state, signal) -> { - this.timers.startSingleTimer("refresh", SyncRegistration.Refresh.INSTANCE, Duration.ofMillis(500)); - }); - return super.signalHandler(); - } - - @Override - public RetentionCriteria retentionCriteria() { - return RetentionCriteria.snapshotEvery(100, 2); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncModule.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncModule.java deleted file mode 100644 index b8f1658a..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/ArtifactVersionSyncModule.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.persistence.typed.PersistenceId; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.synchronizer.SonatypeSynchronizer; - -public class ArtifactVersionSyncModule { - - public static void setup( - final ActorContext ctx, - final ClusterSharding sharding, - final AuthUtils authUtils, - final VersionsService service - ) { - ctx.spawnAnonymous(Behaviors.supervise(BatchVersionSyncManager.setup()) - .onFailure(SupervisorStrategy.resume())); - sharding.init(Entity.of( - ArtifactVersionSyncEntity.ENTITY_TYPE_KEY, - context -> ArtifactVersionSyncEntity.create(authUtils, service, - PersistenceId.of( - context.getEntityTypeKey().name(), - context.getEntityId() - ) - ) - )); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/BatchVersionSyncManager.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/BatchVersionSyncManager.java deleted file mode 100644 index e277da25..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/BatchVersionSyncManager.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.receptionist.Receptionist; -import akka.actor.typed.receptionist.ServiceKey; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashSet; -import io.vavr.collection.Set; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -import java.time.Duration; - -public final class BatchVersionSyncManager { - - static final ServiceKey KEY = ServiceKey.create(Command.class, "batch-version-sync"); - - interface Command extends Jsonable { - } - - public record ArtifactToSync(ArtifactCoordinates coordinates) implements Command { - } - - public enum Refresh implements Command { - INSTANCE - } - - public static Behavior setup() { - return Behaviors.setup(ctx -> { - ctx.getSystem().receptionist().tell(Receptionist.register(KEY, ctx.getSelf())); - return timedSync(HashSet.empty()); - }); - } - - private static Behavior timedSync(Set coordinates) { - return Behaviors.setup(ctx -> Behaviors.withTimers(timers -> { - timers.startSingleTimer("refresh", Refresh.INSTANCE, Duration.ofSeconds(20)); - return Behaviors.receive(Command.class) - .onMessage(ArtifactToSync.class, msg -> { - ctx.getLog().info("Received artifact to sync: {}", msg.coordinates); - if (coordinates.contains(msg.coordinates)) { - return Behaviors.same(); - } - final var sharding = ClusterSharding.get(ctx.getSystem()); - sharding.entityRefFor(ArtifactVersionSyncEntity.ENTITY_TYPE_KEY, msg.coordinates.asMavenString()) - .tell(SyncRegistration.Refresh.INSTANCE); - return timedSync(coordinates.add(msg.coordinates)); - }) - .onMessage(Refresh.class, msg -> { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug("Refreshing all artifacts: {}", coordinates); - } - final var sharding = ClusterSharding.get(ctx.getSystem()); - coordinates.forEach( - c -> sharding.entityRefFor(ArtifactVersionSyncEntity.ENTITY_TYPE_KEY, c.asMavenString()).tell( - SyncRegistration.Refresh.INSTANCE) - ); - timers.startSingleTimer("refresh", Refresh.INSTANCE, Duration.ofMinutes(1)); - return Behaviors.same(); - }) - .build(); - })); - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/SyncRegistration.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/SyncRegistration.java deleted file mode 100644 index ac626136..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/SyncRegistration.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.time.Duration; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(SyncRegistration.Register.class), - @JsonSubTypes.Type(SyncRegistration.SyncBatch.class), - @JsonSubTypes.Type(SyncRegistration.AlreadyRegistered.class), - @JsonSubTypes.Type(SyncRegistration.Timeout.class), - @JsonSubTypes.Type(SyncRegistration.GroupUnregistered.class), - @JsonSubTypes.Type(SyncRegistration.MarkRegistered.class), - @JsonSubTypes.Type(SyncRegistration.Refresh.class), - @JsonSubTypes.Type(SyncRegistration.RetryFailed.class) -}) -public sealed interface SyncRegistration extends Jsonable { - - @JsonTypeName("register") - record Register(MavenCoordinates coordinates) implements SyncRegistration { - public Register { - } - } - - @JsonTypeName("register-batch") - record SyncBatch(ArtifactCoordinates artifact, List coordinates, akka.actor.typed.ActorRef replyTo) implements SyncRegistration { - - } - - @JsonTypeName("stop-batch") - enum Timeout implements SyncRegistration { - INSTANCE; - } - - @JsonTypeName("update-batch") - enum Refresh implements SyncRegistration { - INSTANCE; - } - - @JsonTypeName("duplicate-registration") - record AlreadyRegistered(MavenCoordinates coordinates) implements SyncRegistration { - } - - @JsonTypeName("failed-registration") - record RetryFailed(MavenCoordinates coordinates) implements SyncRegistration { - } - - @JsonTypeName("delay-retry-registration") - record DelayRegistration(MavenCoordinates coordinates, Duration duration) implements SyncRegistration { - - } - - @JsonTypeName("group-missing") - record GroupUnregistered(MavenCoordinates coordinates) implements SyncRegistration { - } - - @JsonTypeName("successful-registration") - record MarkRegistered(MavenCoordinates coordinates) implements SyncRegistration { - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionRegistrationState.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionRegistrationState.java deleted file mode 100644 index dd3ec5b3..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionRegistrationState.java +++ /dev/null @@ -1,336 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = VersionRegistrationState.Empty.class, - name = "empty"), - @JsonSubTypes.Type(value = VersionRegistrationState.Registered.class, - name = "registered") -}) -public sealed interface VersionRegistrationState extends Jsonable { - - ArtifactCoordinates coordinates(); - - default boolean isActive() { - return false; - } - - VersionRegistrationState acceptBatch( - final ArtifactCoordinates artifact, final List coordinates - ); - - VersionRegistrationState acceptVersion(MavenCoordinates coordinates); - - default boolean hasVersion(MavenCoordinates coordinates) { - return false; - } - - default List getNextBatch() { - return List.empty(); - } - - VersionRegistrationState resolvedVersion(MavenCoordinates coordinates); - - default List getPending() { - return List.empty(); - } - - VersionRegistrationState startBatch(List batched); - - VersionRegistrationState failedVersion(MavenCoordinates coordinates); - - @JsonDeserialize - record Empty() implements VersionRegistrationState { - @JsonCreator - public Empty { - } - - @Override - public ArtifactCoordinates coordinates() { - return new ArtifactCoordinates("", ""); - } - - @Override - public VersionRegistrationState acceptBatch( - final ArtifactCoordinates artifact, - final List coordinates - ) { - return new Registered(artifact, coordinates.toMap(c -> c.version, (c) -> Registration.UNREGISTERED)); - } - - @Override - public VersionRegistrationState acceptVersion(final MavenCoordinates coordinates) { - return new Registered( - coordinates.asArtifactCoordinates(), - HashMap.of(coordinates.version, Registration.UNREGISTERED) - ); - } - - @Override - public VersionRegistrationState resolvedVersion(final MavenCoordinates coordinates) { - return new Registered( - coordinates.asArtifactCoordinates(), - HashMap.of(coordinates.version, Registration.REGISTERED) - ); - } - - @Override - public VersionRegistrationState startBatch( - final List batched - ) { - return new Active( - batched.head().asArtifactCoordinates(), - batched.toMap(c -> c.version, (c) -> Registration.UNREGISTERED), - batched - ); - } - - @Override - public VersionRegistrationState failedVersion(final MavenCoordinates coordinates) { - return new Registered(coordinates.asArtifactCoordinates(), HashMap.of(coordinates.version, Registration.UNREGISTERED)); - } - } - - @JsonDeserialize - enum Registration { - REGISTERED, - UNREGISTERED; - } - - @JsonDeserialize - record Registered( - ArtifactCoordinates coordinates, - Map versions - ) implements VersionRegistrationState { - @JsonCreator - public Registered { - } - - @Override - public boolean isActive() { - return false; - } - - @Override - public boolean hasVersion(final MavenCoordinates coordinates) { - return this.versions.containsKey(coordinates.version); - } - - @Override - public VersionRegistrationState acceptBatch( - final ArtifactCoordinates artifact, - final List coordinates - ) { - return new Registered( - this.coordinates, - this.versions.merge( - coordinates.toMap(c -> c.version, (c) -> Registration.UNREGISTERED), - (existing, registration) -> existing - ) - ); - } - - @Override - public VersionRegistrationState acceptVersion(final MavenCoordinates coordinates) { - if (this.versions.containsKey(coordinates.version)) { - return this; - } - return new Registered( - this.coordinates, - this.versions.put(coordinates.version, Registration.UNREGISTERED) - ); - } - - @Override - public List getNextBatch() { - return this.versions.filterValues(registration -> registration == Registration.UNREGISTERED) - .keySet() - .map(this.coordinates::version) - .toList() - .sorted() - .take(10); - } - - @Override - public VersionRegistrationState resolvedVersion(final MavenCoordinates coordinates) { - return new Registered( - this.coordinates, - this.versions.put(coordinates.version, Registration.REGISTERED) - ); - } - - @Override - public VersionRegistrationState startBatch( - final List batched - ) { - final var toBatch = batched.filter( - c -> this.versions.getOrElse(c.version, Registration.UNREGISTERED) == Registration.UNREGISTERED); - return new Active( - this.coordinates, - this.versions.merge( - toBatch.toMap(c -> c.version, (c) -> Registration.UNREGISTERED), - (existing, registration) -> existing - ), - toBatch - ); - } - - @Override - public VersionRegistrationState failedVersion(final MavenCoordinates coordinates) { - if (this.versions.containsKey(coordinates.version)) { - return this; - } - return new Registered( - this.coordinates, - this.versions.put(coordinates.version, Registration.UNREGISTERED) - ); - } - } - - record Active( - ArtifactCoordinates coordinates, - Map versions, - List queue - ) implements VersionRegistrationState { - @Override - public boolean isActive() { - return true; - } - - @Override - public boolean hasVersion(final MavenCoordinates coordinates) { - return this.versions.containsKey(coordinates.version); - } - - @Override - public VersionRegistrationState resolvedVersion(final MavenCoordinates coordinates) { - final var pending = this.queue.remove(coordinates); - final var updated = this.versions.put(coordinates.version, Registration.REGISTERED); - if (pending.isEmpty()) { - return new Registered( - this.coordinates, - updated - ); - } - return new Active( - this.coordinates, - updated, - pending - ); - } - - @Override - public VersionRegistrationState acceptBatch( - final ArtifactCoordinates artifact, - final List coordinates - ) { - return new Active( - this.coordinates, - this.versions.merge( - coordinates.toMap(c -> c.version, (c) -> Registration.UNREGISTERED), - (existing, registration) -> existing - ), - this.queue - ); - } - - @Override - public VersionRegistrationState acceptVersion(final MavenCoordinates coordinates) { - if (this.versions.containsKey(coordinates.version)) { - return this; - } - return new Active( - this.coordinates, - this.versions.put(coordinates.version, Registration.UNREGISTERED), - this.queue - ); - } - - @Override - public List getPending() { - return this.queue; - } - - @Override - public VersionRegistrationState startBatch( - final List batched - ) { - if (!this.queue.isEmpty()) { - return this; - } - final var toBatch = batched.filter( - c -> this.versions.getOrElse(c.version, Registration.UNREGISTERED) == Registration.UNREGISTERED - ).appendAll(this.queue); - return new Active( - this.coordinates, - this.versions.merge( - toBatch.toMap(c -> c.version, (c) -> Registration.UNREGISTERED), - (existing, registration) -> existing - ), - toBatch - ); - } - - @Override - public VersionRegistrationState failedVersion(final MavenCoordinates coordinates) { - if (this.versions.containsKey(coordinates.version)) { - return this; - } - return new Active( - this.coordinates, - this.versions.put(coordinates.version, Registration.UNREGISTERED), - this.queue - ); - } - - @Override - public List getNextBatch() { - if (this.queue.isEmpty()) { - return this.versions.filterValues(registration -> registration == Registration.UNREGISTERED) - .keySet() - .map(this.coordinates::version) - .toList() - .sorted() - .take(10); - } - return this.queue; - } - } -} diff --git a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionSyncEvent.java b/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionSyncEvent.java deleted file mode 100644 index 67a3a7d4..00000000 --- a/version-synchronizer/src/main/java/org/spongepowered/synchronizer/versionsync/VersionSyncEvent.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.synchronizer.versionsync; - -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(VersionSyncEvent.RegisteredVersion.class), - @JsonSubTypes.Type(VersionSyncEvent.RegisteredBatch.class), - @JsonSubTypes.Type(VersionSyncEvent.StartedBatchRegistration.class), - @JsonSubTypes.Type(VersionSyncEvent.ResolvedVersion.class), - @JsonSubTypes.Type(VersionSyncEvent.FailedVersion.class) -}) -public sealed interface VersionSyncEvent extends AggregateEvent, Jsonable { - - AggregateEventShards INSTANCE = AggregateEventTag.sharded(VersionSyncEvent.class, 3); - - @Override - default AggregateEventTagger aggregateTag() { - return INSTANCE; - } - - @JsonTypeName("registered-version") - record RegisteredVersion(MavenCoordinates coordinates) implements VersionSyncEvent {} - - @JsonTypeName("registered-batch") - record RegisteredBatch(ArtifactCoordinates artifact, List coordinates) implements VersionSyncEvent { - } - - @JsonTypeName("started-batch") - record StartedBatchRegistration(List batched) implements VersionSyncEvent { - } - - @JsonTypeName("resolved-version") - record ResolvedVersion(MavenCoordinates coordinates) implements VersionSyncEvent { - } - - @JsonTypeName("failed-version") - record FailedVersion(MavenCoordinates coordinates) implements VersionSyncEvent { - } -} diff --git a/version-synchronizer/src/main/resources/META-INF/persistence.xml b/version-synchronizer/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index df63fd1b..00000000 --- a/version-synchronizer/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.hibernate.jpa.HibernatePersistenceProvider - DefaultDS - true - - - - - - - diff --git a/version-synchronizer/src/main/resources/application.conf b/version-synchronizer/src/main/resources/application.conf deleted file mode 100644 index e0dd3d6d..00000000 --- a/version-synchronizer/src/main/resources/application.conf +++ /dev/null @@ -1,84 +0,0 @@ -play.modules.enabled += org.spongepowered.synchronizer.SynchronizerModule - -db.default { - driver = "org.postgresql.Driver" - url = "jdbc:postgresql://localhost:5432/default" - url = ${?POSTGRES_URL} - username = admin - username = ${?POSTGRES_USERNAME} - password = password - password = ${?POSTGRES_PASSWORD} -} -akka.cluster.roles += "commit-resolver" - -jdbc-defaults.slick.profile = "slick.jdbc.PostgresProfile$" - -akka.serialization.jackson { - jackson-modules += "io.vavr.jackson.datatype.VavrModule" -} -akka.persistence.snapshot-store { - snapshot-is-optional = true -} - -akka { - extensions = ${akka.extensions} [ - "org.spongepowered.synchronizer.assetsync.AssetSettingsExtension", - "org.spongepowered.synchronizer.resync.ResyncExtension", - "org.spongepowered.synchronizer.SynchronizationExtension", - "org.spongepowered.synchronizer.actor.ArtifactSyncExtension" - ] -} -asset-retrieval-dispatcher { - type = Dispatcher - executor = "thread-pool-executor" - thread-pool-executor { - fixed-pool-size = 16 - } - throughput = 1 -} -systemofadownload.synchronizer { - version-sync { - pool-size = 1 - interval = "300s" - delay = "60s" - } - reactive-sync { - pool-size = 1 - parallelism = 1 - time-out = "1h" - } - asset { - pool-size = 1 - parallelism = 1 - initial-backoff = "1m" - maximum-backoff = "600m" - backoff-factor = 10 - time-out = "1h" - } - timed-sync { - - } - worker { - assets { - repository = "https://repo.spongepowered.org" - timeout = "1h" - retry = 3 - files-to-index = ["jar", "pom"] - pool-size = 1 - } - resync { - repository = "https://repo.spongepowered.org/repository/maven-public/" - timeout = "1h" - retry = 1 - agent-name = "SystemOfADownload-Synchronizer" - } - version-registration { - parallelism = 1 - fan-out-parallelism = 1 - pool-size = 1 - time-out = "1h" - registration-time-out = "90000s" - } - } - -} diff --git a/version-synchronizer/src/main/resources/logback.xml b/version-synchronizer/src/main/resources/logback.xml deleted file mode 100644 index 8ca52f08..00000000 --- a/version-synchronizer/src/main/resources/logback.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - System.out - - %date{hh:MM:ss.SSS} [%level] [%thread] [%logger{5}/%marker] - %coloredLevel %msg%n - - - - - 8192 - true - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/version-synchronizer/src/main/resources/reference.conf b/version-synchronizer/src/main/resources/reference.conf deleted file mode 100644 index 15e6e946..00000000 --- a/version-synchronizer/src/main/resources/reference.conf +++ /dev/null @@ -1,25 +0,0 @@ -systemofadownload.commit-resolver { - cloner { - type = Dispatcher - executor = "thread-pool-executor" - thread-pool-executor { - fixed-pool-size = 2 - keep-alive-time = 60s - } - throughput = 1 - } - dispatcher { - type = Dispatcher - executor = "fork-join-executor" - fork-join-executor { - # Min number of threads to cap factor-based parallelism number to - parallelism-min = 2 - # Parallelism (threads) ... ceil(available processors * factor) - parallelism-factor = 2.0 - # Max number of threads to cap factor-based parallelism number to - parallelism-max = 10 - } - throughput = 2 - } - -} diff --git a/version-synchronizer/src/main/resources/soad.gitconfig b/version-synchronizer/src/main/resources/soad.gitconfig deleted file mode 100644 index e69de29b..00000000 diff --git a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitDetailsRegistrarTest.java b/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitDetailsRegistrarTest.java deleted file mode 100644 index 6f687f1e..00000000 --- a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitDetailsRegistrarTest.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.spongepowered.synchronizer.test.worker; - - -public class CommitDetailsRegistrarTest { - -} diff --git a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitResolutionManagerTest.java b/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitResolutionManagerTest.java deleted file mode 100644 index f431e2e8..00000000 --- a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/CommitResolutionManagerTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.spongepowered.synchronizer.test.worker; - -import akka.Done; -import akka.actor.testkit.typed.javadsl.FishingOutcomes; -import akka.actor.testkit.typed.javadsl.TestKitJunitResource; -import io.vavr.collection.List; -import org.eclipse.jgit.util.SystemReader; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.synchronizer.actor.CommitDetailsRegistrar; -import org.spongepowered.synchronizer.gitmanaged.util.jgit.CommitResolutionManager; - -import java.io.File; -import java.net.URI; -import java.time.Duration; - -public class CommitResolutionManagerTest { - - private TestKitJunitResource testKit; - - @BeforeEach - public void setup() { - this.testKit = new TestKitJunitResource(); - testKit.system().log().info("Starting test"); - // Because jgit has to be told to ignore a lot of things about the system - // we need to proxy - final var dummyGitConfig = this.getClass().getClassLoader().getResource("dummy.gitconfig"); - Assertions.assertNotNull(dummyGitConfig, "dummy git config cannot be null"); - final var gitConfig = new File(dummyGitConfig.getFile()); - SystemReader.setInstance(new TestGitSystemReader(gitConfig)); - } - - @AfterEach - public void teardown() { - testKit.system().log().info("Finishing test"); - testKit.system().terminate(); - } - - @Test - public void testCommitResolver() { - final var teller = testKit.createTestProbe(CommitDetailsRegistrar.Command.class); - final var actor = testKit.spawn(CommitResolutionManager.resolveCommit(teller.ref())); - final var coords = new ArtifactCoordinates("com.example", "test").version("1.0"); - final var commit = "d838fee5d8e834ba9fd4d1c4fe0f8214d6dc90fc"; - final var url = "https://github.com/spongepowered/configurate.git"; - final var uri = List.of(URI.create(url)); - final var probe = testKit - .createTestProbe(); - final var replyTo = probe - .ref(); - actor.tell(new CommitResolutionManager.ResolveCommitDetails( - coords, commit, uri, replyTo - )); - // This teller is the intermediary registrar that should receive HandleVersionedCommitReport - // that ultimately tells the probe a Done. - teller.fishForMessage(Duration.ofSeconds(60), m -> { - ((CommitDetailsRegistrar.HandleVersionedCommitReport) m).replyTo().tell(Done.getInstance()); - return FishingOutcomes.complete(); - }); - - probe.fishForMessage(Duration.ofSeconds(60), m -> FishingOutcomes.complete()); - } - - @Test - public void verifyNonExistentRepo() { - final var teller = testKit.createTestProbe(CommitDetailsRegistrar.Command.class); - final var actor = testKit.spawn(CommitResolutionManager.resolveCommit(teller.ref())); - final var coords = new ArtifactCoordinates("com.example", "test").version("1.0"); - final var commit = "d838fee5d8e834ba9fd4d1c4fe0f8214d6dc90fc"; - final var url = "https://example.com/git/doesnt-exist.git"; - final var uri = List.of(URI.create(url)); - final var probe = testKit - .createTestProbe(); - final var replyTo = probe - .ref(); - actor.tell(new CommitResolutionManager.ResolveCommitDetails( - coords, commit, uri, replyTo - )); - probe.fishForMessage(Duration.ofSeconds(10), m -> FishingOutcomes.complete()); - } - - - @Test - public void verifyNonExistentCommit() { - final var teller = testKit.createTestProbe(CommitDetailsRegistrar.Command.class); - final var actor = testKit.spawn(CommitResolutionManager.resolveCommit(teller.ref())); - final var coords = new ArtifactCoordinates("com.example", "test").version("1.0"); - final var commit = "a830fee5d8e894ba9aa4a1a4fe0f8214d6dc90fc"; - final var url = "https://github.com/spongepowered/configurate.git"; - final var uri = List.of(URI.create(url)); - final var probe = testKit - .createTestProbe(); - final var replyTo = probe - .ref(); - actor.tell(new CommitResolutionManager.ResolveCommitDetails( - coords, commit, uri, replyTo - )); - probe.fishForMessage(Duration.ofSeconds(60), m -> FishingOutcomes.complete()); - } - - @Test - public void verifyCommitFromTwoRepositories() { - final var teller = testKit.createTestProbe(CommitDetailsRegistrar.Command.class); - final var actor = testKit.spawn(CommitResolutionManager.resolveCommit(teller.ref())); - final var coords = new ArtifactCoordinates("org.spongepowered", "spongevanilla").version("1.16.5-8.1.0-RC1184"); - final var commit = "6e443ec04ded4385d12c2e609360e81a770fbfcb"; - final var url = "https://github.com/spongepowered/spongevanilla.git"; - final var newUrl = "https://github.com/spongepowered/sponge.git"; - final var repos = List.of(URI.create(newUrl), URI.create(url)); - final var probe = testKit.createTestProbe(); - final var replyTo = probe.ref(); - actor.tell(new CommitResolutionManager.ResolveCommitDetails( - coords, commit, repos, replyTo - )); - probe.fishForMessage(Duration.ofMinutes(10), m -> FishingOutcomes.complete()); - } -} diff --git a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/TestGitSystemReader.java b/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/TestGitSystemReader.java deleted file mode 100644 index ff574c14..00000000 --- a/version-synchronizer/src/test/java/org/spongepowered/synchronizer/test/worker/TestGitSystemReader.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.spongepowered.synchronizer.test.worker; - -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.storage.file.FileBasedConfig; -import org.eclipse.jgit.util.FS; -import org.eclipse.jgit.util.SystemReader; - -import java.io.File; - -public class TestGitSystemReader extends SystemReader { - private static final SystemReader proxy = SystemReader.getInstance(); - private final File userGitConfig; - - public TestGitSystemReader(File userGitConfig) { - super(); - this.userGitConfig = userGitConfig; - } - - @Override - public String getenv(String variable) { - return proxy.getenv(variable); - } - - @Override - public String getHostname() { - return proxy.getHostname(); - } - - @Override - public String getProperty(String key) { - return proxy.getProperty(key); - } - - @Override - public long getCurrentTime() { - return proxy.getCurrentTime(); - } - - @Override - public int getTimezone(long when) { - return proxy.getTimezone(when); - } - - @Override - public FileBasedConfig openUserConfig(Config parent, FS fs) { - return new FileBasedConfig(parent, userGitConfig, fs); - } - - // Return an empty system configuration, based on example in SystemReader.Default#openSystemConfig - @Override - public FileBasedConfig openSystemConfig(Config parent, FS fs) { - return new FileBasedConfig(parent, this.userGitConfig, fs) { - @Override - public void load() { - } - - @Override - public boolean isOutdated() { - return false; - } - }; - } - - @Override - public FileBasedConfig openJGitConfig(final Config parent, final FS fs) { - return new FileBasedConfig(parent, this.userGitConfig, fs) { - @Override - public void load() { - } - - @Override - public boolean isOutdated() { - return false; - } - }; - } -} diff --git a/version-synchronizer/src/test/resources/dummy.gitconfig b/version-synchronizer/src/test/resources/dummy.gitconfig deleted file mode 100644 index e69de29b..00000000 diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/VersionsService.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/VersionsService.java deleted file mode 100644 index d1cbfdcb..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/VersionsService.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.broker.Topic; -import com.lightbend.lagom.javadsl.api.transport.Method; -import org.spongepowered.downloads.versions.api.models.ArtifactUpdate; -import org.spongepowered.downloads.versions.api.models.CommitRegistration; -import org.spongepowered.downloads.versions.api.models.TagRegistration; -import org.spongepowered.downloads.versions.api.models.TagVersion; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; -import org.spongepowered.downloads.versions.api.models.VersionedArtifactUpdates; - -public interface VersionsService extends Service { - - ServiceCall registerArtifactCollection( - String groupId, String artifactId - ); - - ServiceCall registerArtifactTag(String groupId, String artifactId); - ServiceCall updateArtifactTag(String groupId, String artifactId); - - ServiceCall tagVersion(String groupId, String artifactId); - - ServiceCall registerCommit(String groupId, String artifactId, String version); - - Topic artifactUpdateTopic(); - - Topic versionedArtifactUpdatesTopic(); - - @Override - default Descriptor descriptor() { - return Service.named("versions") - .withCalls( - Service.restCall(Method.POST, "/versions/groups/:groupId/artifacts/:artifactId/versions", this::registerArtifactCollection), - Service.restCall(Method.POST, "/versions/groups/:groupId/artifacts/:artifactId/tags", this::registerArtifactTag), - Service.restCall(Method.PATCH, "/versions/groups/:groupId/artifacts/:artifactId/tags", this::updateArtifactTag), - Service.restCall(Method.POST, "/versions/groups/:groupId/artifacts/:artifactId/promotion", this::tagVersion), - Service.restCall(Method.PUT, "/versions/groups/:groupId/artifacts/:artifactId/versions/:version/commit", this::registerCommit) - ) - .withTopics( - Service.topic("artifact-update", this::artifactUpdateTopic), - Service.topic("versioned-artifact-updates", this::versionedArtifactUpdatesTopic) - ) - .withAutoAcl(true); - } - -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/ArtifactUpdate.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/ArtifactUpdate.java deleted file mode 100644 index f57902e0..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/ArtifactUpdate.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagEntry; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = ArtifactUpdate.ArtifactVersionRegistered.class, name = "new-version"), - @JsonSubTypes.Type(value = ArtifactUpdate.TagRegistered.class, name = "tag-update"), -}) -public interface ArtifactUpdate { - - @JsonDeserialize - final record ArtifactVersionRegistered(@JsonProperty("coordinates") MavenCoordinates coordinates) implements ArtifactUpdate { - - @JsonCreator - public ArtifactVersionRegistered { - } - } - - @JsonDeserialize - record TagRegistered(@JsonProperty("coordinates") ArtifactCoordinates coordinates, @JsonProperty("tag") ArtifactTagEntry entry) implements ArtifactUpdate { - - @JsonCreator - public TagRegistered { - } - } - -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/CommitRegistration.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/CommitRegistration.java deleted file mode 100644 index 51410e28..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/CommitRegistration.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.net.URI; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -@JsonSubTypes({ - @JsonSubTypes.Type(CommitRegistration.ResolvedCommit.class), - @JsonSubTypes.Type(CommitRegistration.FailedCommit.class) -}) -public sealed interface CommitRegistration extends Jsonable { - - record ResolvedCommit( - URI repo, - VersionedCommit versionedCommit, - MavenCoordinates coordinates - ) implements CommitRegistration { - @JsonCreator - public ResolvedCommit { - } - } - - record FailedCommit( - String commitSha, URI repo - ) implements CommitRegistration { - @JsonCreator - public FailedCommit { - } - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagRegistration.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagRegistration.java deleted file mode 100644 index fb6dfbc0..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagRegistration.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagEntry; - -public interface TagRegistration { - - @JsonDeserialize - final record Register(@JsonProperty("tag") ArtifactTagEntry entry) {} - - @JsonDeserialize - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Response.TagAlreadyRegistered.class, name = "AlreadyRegistered"), - @JsonSubTypes.Type(value = Response.TagSuccessfullyRegistered.class, name = "Success") - }) - interface Response extends Jsonable { - - final record TagAlreadyRegistered(@JsonProperty String name) implements TagRegistration.Response {} - - @JsonSerialize - final record TagSuccessfullyRegistered() implements TagRegistration.Response {} - - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagVersion.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagVersion.java deleted file mode 100644 index 544dbe72..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/TagVersion.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; - -public interface TagVersion { - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Request.SetRecommendationRegex.class, name = "recommendation") - }) - interface Request { - - @JsonDeserialize - final record SetRecommendationRegex( - String regex, - List valid, - List invalid, - boolean enableManualMarking - ) implements Request { - - @JsonCreator - public SetRecommendationRegex( - @JsonProperty(required = true) final String regex, - @JsonProperty(required = true) final List valid, - @JsonProperty(required = true) final List invalid, - @JsonProperty(required = true) final boolean enableManualMarking - ) { - this.regex = regex; - this.enableManualMarking = enableManualMarking; - this.valid = valid; - this.invalid = invalid; - } - } - } - - @JsonSubTypes({ - @JsonSubTypes.Type(value = TagVersion.Response.TagSuccessfullyRegistered.class, name = "Success") - }) - interface Response extends Jsonable { - - @JsonSerialize - final record TagSuccessfullyRegistered() implements TagVersion.Response {} - - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionRegistration.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionRegistration.java deleted file mode 100644 index 240fbf19..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionRegistration.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCollection; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -public final class VersionRegistration { - - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Register.Collection.class, - name = "Collection"), - @JsonSubTypes.Type(value = Register.Version.class, - name = "Version"), - }) - public interface Register { - - @JsonDeserialize - final record Collection( - @JsonProperty ArtifactCollection collection - ) implements Register { - - @JsonCreator - public Collection { - } - - } - - @JsonDeserialize - record Version(@JsonProperty MavenCoordinates coordinates) - implements Register { - @JsonCreator - public Version { - } - - } - - - } - - @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, - property = "type") - @JsonSubTypes({ - @JsonSubTypes.Type(value = Response.GroupMissing.class, - name = "GroupMissing"), - @JsonSubTypes.Type(value = Response.ArtifactAlreadyRegistered.class, - name = "AlreadyRegistered"), - @JsonSubTypes.Type(value = Response.RegisteredArtifact.class, - name = "Registered"), - }) - public interface Response extends Jsonable { - - @JsonDeserialize - final record ArtifactAlreadyRegistered( - @JsonProperty(required = true) MavenCoordinates coordinates - ) implements Response { - - @JsonCreator - public ArtifactAlreadyRegistered { - } - } - - @JsonDeserialize - final record RegisteredArtifact( - @JsonProperty(required = true) MavenCoordinates mavenCoordinates - ) implements Response { - - @JsonCreator - public RegisteredArtifact { - } - } - - @JsonDeserialize - final record GroupMissing(@JsonProperty(required = true) String groupId) implements Response { - - } - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedArtifactUpdates.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedArtifactUpdates.java deleted file mode 100644 index 4c8042c5..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedArtifactUpdates.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.annotation.JsonTypeName; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.net.URI; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = VersionedArtifactUpdates.GitCommitDetailsAssociated.class), - @JsonSubTypes.Type(value = VersionedArtifactUpdates.CommitExtracted.class), -}) -public interface VersionedArtifactUpdates extends Jsonable { - - @JsonTypeName("commit-extracted") - final record CommitExtracted( - MavenCoordinates coordinates, - List gitRepositories, - String commit - ) implements VersionedArtifactUpdates { - - @JsonCreator - public CommitExtracted { - } - } - @JsonTypeName("commit-associated") - final record GitCommitDetailsAssociated( - MavenCoordinates coordinates, - URI repo, VersionedCommit commit - ) implements VersionedArtifactUpdates { - - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedChangelog.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedChangelog.java deleted file mode 100644 index c7a8b2e4..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedChangelog.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -import java.net.URI; - -@JsonDeserialize -public final record VersionedChangelog( - List commits, - @JsonInclude(JsonInclude.Include.NON_DEFAULT) boolean processing -) { - - @JsonCreator - public VersionedChangelog { - } - - @JsonDeserialize - public final record IndexedCommit( - VersionedCommit commit, - List submoduleCommits - ) { - @JsonCreator - public IndexedCommit { - } - } - - @JsonDeserialize - public final record Submodule( - String name, - URI gitRepository, - List commits - ) { - @JsonCreator - public Submodule { - } - } - -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedCommit.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedCommit.java deleted file mode 100644 index e73d9bc0..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/VersionedCommit.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models; - - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.net.URI; -import java.time.ZonedDateTime; -import java.util.Optional; - -@JsonDeserialize -public record VersionedCommit( - String message, - String body, - String sha, - Author author, - Commiter commiter, - Optional link, - ZonedDateTime commitDate -) { - - @JsonCreator - public VersionedCommit { - } - - @JsonDeserialize - public record Author( - String name, - String email - ) { - @JsonCreator - public Author { - } - } - - @JsonDeserialize - public record Commiter( - String name, - String email - ) { - @JsonCreator - public Commiter { - } - } -} - diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagEntry.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagEntry.java deleted file mode 100644 index 266ccd63..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagEntry.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models.tags; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.control.Try; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.util.regex.Pattern; - -@JsonDeserialize -public record ArtifactTagEntry( - @JsonProperty(required = true) String name, - @JsonProperty(required = true) int matchingGroup, - @JsonProperty(required = true) String regex -) { - - @JsonCreator - public ArtifactTagEntry { - } - - public VersionTagValue generateValue(MavenCoordinates coordinates) { - final var expectedGroup = this.matchingGroup(); - final var matcher = Pattern.compile(this.regex()).matcher(coordinates.version); - final String value; - if (matcher.find()) { - value = Try.of(() -> matcher.group(expectedGroup)) - .getOrElse(""); - } else { - value = ""; - } - return new VersionTagValue(coordinates, this, value); - } -} diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagValue.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagValue.java deleted file mode 100644 index 01ec9590..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/ArtifactTagValue.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models.tags; - -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -public final record ArtifactTagValue( - MavenCoordinates coordinates, - Map tagValues, - boolean recommended -) { - public ArtifactTagValue promote(boolean promoted) { - return new ArtifactTagValue( - this.coordinates, - this.tagValues, - promoted - ); - } -} - diff --git a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/VersionTagValue.java b/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/VersionTagValue.java deleted file mode 100644 index a5d80b5e..00000000 --- a/versions-api/src/main/java/org/spongepowered/downloads/versions/api/models/tags/VersionTagValue.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.api.models.tags; - -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -public final record VersionTagValue( - MavenCoordinates coordinates, - ArtifactTagEntry tag, - String tagValue -) { - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsModule.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsModule.java deleted file mode 100644 index 773c2ed5..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsModule.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server; - -import com.google.inject.AbstractModule; -import com.google.inject.Provides; -import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; -import org.pac4j.core.config.Config; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.auth.SOADAuth; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.server.readside.AssetReadsidePersistence; -import org.spongepowered.downloads.versions.server.readside.VersionReadSidePersistence; -import org.spongepowered.downloads.versions.worker.readside.CommitProcessor; -import play.Environment; -import play.libs.akka.AkkaGuiceSupport; - -import javax.inject.Inject; - -public class VersionsModule extends AbstractModule implements ServiceGuiceSupport, AkkaGuiceSupport { - - private final AuthUtils auth; - - @SuppressWarnings("unused") // These parameters must match for Play's Guice handling to work. - @Inject - public VersionsModule(final Environment environment, final com.typesafe.config.Config config) { - this.auth = AuthUtils.configure(config); - } - - @Override - protected void configure() { - this.bindService(VersionsService.class, VersionsServiceImpl.class); - this.bindClient(ArtifactService.class); - - this.bind(VersionReadSidePersistence.class).asEagerSingleton(); - this.bind(AssetReadsidePersistence.class).asEagerSingleton(); - this.bind(CommitProcessor.class).asEagerSingleton(); - - } - - @Provides - @SOADAuth - protected Config configProvider() { - return this.auth.config(); - } - - @Provides - protected AuthUtils authProvider() { - return this.auth; - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsServiceImpl.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsServiceImpl.java deleted file mode 100644 index f0ffaddf..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/VersionsServiceImpl.java +++ /dev/null @@ -1,370 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server; - -import akka.Done; -import akka.NotUsed; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import akka.cluster.sharding.typed.javadsl.EntityRef; -import akka.japi.Pair; -import akka.stream.javadsl.Flow; -import com.google.inject.Inject; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.broker.Topic; -import com.lightbend.lagom.javadsl.api.transport.BadRequest; -import com.lightbend.lagom.javadsl.api.transport.NotFound; -import com.lightbend.lagom.javadsl.broker.TopicProducer; -import com.lightbend.lagom.javadsl.persistence.Offset; -import com.lightbend.lagom.javadsl.persistence.PersistentEntityRegistry; -import com.lightbend.lagom.javadsl.server.ServerServiceCall; -import io.vavr.control.Try; -import org.pac4j.core.config.Config; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.artifact.api.event.GroupUpdate; -import org.spongepowered.downloads.auth.AuthenticatedInternalService; -import org.spongepowered.downloads.auth.SOADAuth; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import org.spongepowered.downloads.versions.api.models.ArtifactUpdate; -import org.spongepowered.downloads.versions.api.models.CommitRegistration; -import org.spongepowered.downloads.versions.api.models.TagRegistration; -import org.spongepowered.downloads.versions.api.models.TagVersion; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; -import org.spongepowered.downloads.versions.api.models.VersionedArtifactUpdates; -import org.spongepowered.downloads.versions.server.domain.ACCommand; -import org.spongepowered.downloads.versions.server.domain.ACEvent; -import org.spongepowered.downloads.versions.server.domain.InvalidRequest; -import org.spongepowered.downloads.versions.server.domain.VersionedArtifactAggregate; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.ArtifactEvent; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactCommand; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactEntity; - -import java.net.URI; -import java.time.Duration; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.CompletableFuture; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class VersionsServiceImpl implements VersionsService, - AuthenticatedInternalService { - private final PersistentEntityRegistry persistentEntityRegistry; - private final Config securityConfig; - private final ClusterSharding clusterSharding; - private final Duration streamTimeout = Duration.ofSeconds(30); - private final AuthUtils auth; - - public static final Pattern VALID_COORDINATE_PORTION = Pattern.compile("^[\\w.-]+$"); - - @Inject - public VersionsServiceImpl( - final ClusterSharding clusterSharding, - final ArtifactService artifactService, - final PersistentEntityRegistry persistentEntityRegistry, - @SOADAuth final Config securityConfig, - final AuthUtils auth - ) { - this.clusterSharding = clusterSharding; - this.persistentEntityRegistry = persistentEntityRegistry; - this.securityConfig = securityConfig; - this.auth = auth; - - this.clusterSharding.init( - Entity.of( - VersionedArtifactAggregate.ENTITY_TYPE_KEY, - VersionedArtifactAggregate::create - ) - ); - - artifactService.groupTopic() - .subscribe() - .atLeastOnce(Flow.create().map(this::processGroupEvent)); - - } - - private Done processGroupEvent(GroupUpdate a) { - if (!(a instanceof GroupUpdate.ArtifactRegistered g)) { - return Done.done(); - } - final var coordinates = g.coordinates(); - return this.getCollection(coordinates) - .ask( - replyTo -> new ACCommand.RegisterArtifact(coordinates, replyTo), - this.streamTimeout - ) - .thenApply(notUsed -> Done.done()) - .toCompletableFuture() - .join(); - } - - @Override - public Config getSecurityConfig() { - return this.securityConfig; - } - - @Override - public ServerServiceCall registerArtifactCollection( - final String groupId, - final String artifactId - ) { - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> registration -> { - final var coordinates = parseCoordinates(groupId, artifactId); - if (registration instanceof VersionRegistration.Register.Version v) { - return this.getCollection(coordinates) - .ask( - replyTo -> new ACCommand.RegisterVersion(v.coordinates(), replyTo), - this.streamTimeout - ).thenApply(response -> { - if (response instanceof InvalidRequest) { - throw new NotFound("unknown artifact or group"); - } - return response; - }) - .thenCompose(r -> this.clusterSharding.entityRefFor( - VersionedArtifactEntity.ENTITY_TYPE_KEY, - v.coordinates().asStandardCoordinates() - ) - .ask( - replyTo -> new VersionedArtifactCommand.Register(v.coordinates(), replyTo), - this.streamTimeout - ) - .thenApply(notUsed -> r)); - } - if (registration instanceof VersionRegistration.Register.Collection c) { - return this.clusterSharding.entityRefFor( - VersionedArtifactEntity.ENTITY_TYPE_KEY, c.collection().coordinates().asStandardCoordinates()) - .ask( - replyTo -> new VersionedArtifactCommand.RegisterAssets( - c.collection().coordinates(), c.collection(), replyTo), this.streamTimeout) - .thenApply(response -> { - if (response instanceof InvalidRequest) { - throw new NotFound("unknown artifact or group"); - } - return response; - }); - } - throw new BadRequest("unknown registration request"); - }); - } - - @Override - public ServiceCall registerArtifactTag( - final String groupId, - final String artifactId - ) { - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> registration -> { - final var coordinates = parseCoordinates(groupId, artifactId); - return this.getCollection(coordinates) - .ask( - replyTo -> new ACCommand.RegisterArtifactTag(registration.entry(), replyTo), this.streamTimeout) - .thenApply(response -> { - if (response instanceof InvalidRequest) { - throw new NotFound("unknown artifact or group"); - } - return response; - }); - }); - } - - @Override - public ServiceCall updateArtifactTag( - final String groupId, - final String artifactId - ) { - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> registration -> { - final var coordinates = parseCoordinates(groupId, artifactId); - return this.getCollection(coordinates) - .ask( - replyTo -> new ACCommand.UpdateArtifactTag(registration.entry(), replyTo), this.streamTimeout) - .thenApply(response -> { - if (response instanceof InvalidRequest) { - throw new NotFound("unknown artifact or group"); - } - return response; - }); - }); - } - - @Override - public ServiceCall tagVersion( - final String groupId, - final String artifactId - ) { - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> request -> { - final var coordinates = parseCoordinates(groupId, artifactId); - if (!(request instanceof TagVersion.Request.SetRecommendationRegex s)) { - throw new BadRequest("unknown request"); - } - final var regex = Try.of(() -> Pattern.compile(s.regex())); - final var validFailures = s.valid() - .filter(valid -> regex.map(pattern -> pattern.matcher(valid)) - .mapTry(Matcher::find) - .map(b -> !b) - .getOrElse(true) // If exception, keep the version as failed - ); - final var invalidSuccesses = s.invalid() - .filter(invalid -> regex - .map(pattern -> pattern.matcher(invalid)) - .mapTry(Matcher::find) - .getOrElse(true) - ); - if (!validFailures.isEmpty()) { - throw new BadRequest("expected valid versions did not match regex: " + validFailures); - } - if (!invalidSuccesses.isEmpty()) { - throw new BadRequest("expected invalid versions matched regex successfully:" + invalidSuccesses); - } - return this.getCollection(coordinates) - .ask( - replyTo -> new ACCommand.RegisterPromotion(s.regex(), replyTo, s.enableManualMarking()), - this.streamTimeout - ) - .thenApply(response -> { - if (response instanceof InvalidRequest) { - throw new NotFound("unknown artifact or group"); - } - return response; - }); - }); - } - - @Override - public ServiceCall registerCommit( - final String groupId, final String artifactId, final String version - ) { - return this.authorize(AuthUtils.Types.JWT, AuthUtils.Roles.ADMIN, profile -> request -> { - final var coordinates = parseCoordinates(groupId, artifactId); - if (!VALID_COORDINATE_PORTION.matcher(version).matches()) { - throw new BadRequest("Invalid version: " + version); - } - final var mavenCoordinates = coordinates.version(version); - if (request instanceof CommitRegistration.ResolvedCommit rc) { - return this.clusterSharding.entityRefFor( - VersionedArtifactEntity.ENTITY_TYPE_KEY, - mavenCoordinates.asStandardCoordinates() - ) - .ask( - replyTo -> new VersionedArtifactCommand.RegisterResolvedCommit( - rc.versionedCommit(), - rc.repo(), - replyTo - ), - Duration.ofSeconds(30) - ) - .thenApply(done -> NotUsed.notUsed()); - } else if (request instanceof CommitRegistration.FailedCommit uc) { - return this.clusterSharding.entityRefFor( - VersionedArtifactEntity.ENTITY_TYPE_KEY, - mavenCoordinates.asStandardCoordinates() - ) - .ask( - replyTo -> new VersionedArtifactCommand.RegisterFailedCommit( - uc.commitSha(), - uc.repo(), - replyTo - ), - Duration.ofSeconds(30) - ) - .thenApply(done -> NotUsed.notUsed()); - } - - return CompletableFuture.completedStage(NotUsed.notUsed()); - }); - } - - private static List> convertEvent(Pair pair) { - final ACEvent event = pair.first(); - final ArtifactUpdate update; - if (event instanceof ACEvent.ArtifactVersionRegistered r) { - update = new ArtifactUpdate.ArtifactVersionRegistered(r.version()); - } else if (event instanceof ACEvent.ArtifactTagRegistered r) { - update = new ArtifactUpdate.TagRegistered(r.coordinates(), r.entry()); - } else { - return Collections.emptyList(); - } - return List.of(Pair.apply(update, pair.second())); - } - - @Override - public Topic artifactUpdateTopic() { - return TopicProducer.taggedStreamWithOffset( - ACEvent.INSTANCE.allTags(), - (aggregateTag, fromOffset) -> this.persistentEntityRegistry - .eventStream(aggregateTag, fromOffset) - .mapConcat(VersionsServiceImpl::convertEvent) - ); - } - - @Override - public Topic versionedArtifactUpdatesTopic() { - return TopicProducer.taggedStreamWithOffset( - ArtifactEvent.INSTANCE.allTags(), - (aggregateTag, fromOffset) -> this.persistentEntityRegistry - .eventStream(aggregateTag, fromOffset) - .mapConcat(VersionsServiceImpl::convertGitEvents) - ); - } - - private static List> convertGitEvents(Pair pair) { - final ArtifactEvent event = pair.first(); - final VersionedArtifactUpdates update; - if (event instanceof ArtifactEvent.CommitAssociated r) { - update = new VersionedArtifactUpdates.CommitExtracted( - r.coordinates(), r.repos().map(URI::create), r.commitSha()); - } else if (event instanceof ArtifactEvent.CommitResolved r) { - update = new VersionedArtifactUpdates.GitCommitDetailsAssociated( - r.coordinates(), r.repo(), r.versionedCommit()); - } else { - return Collections.emptyList(); - } - return List.of(Pair.apply(update, pair.second())); - } - - private EntityRef getCollection(final ArtifactCoordinates coordinates) { - return this.clusterSharding.entityRefFor( - VersionedArtifactAggregate.ENTITY_TYPE_KEY, coordinates.asMavenString()); - } - - private static ArtifactCoordinates parseCoordinates(final String groupID, final String artifactID) { - final String sanitizedGroupId = groupID.toLowerCase(Locale.ROOT); - if (!VALID_COORDINATE_PORTION.matcher(sanitizedGroupId).matches()) { - throw new BadRequest("Invalid groupId: " + groupID); - } - final String sanitizedArtifactId = artifactID.toLowerCase(Locale.ROOT); - if (!VALID_COORDINATE_PORTION.matcher(sanitizedArtifactId).matches()) { - throw new BadRequest("Invalid artifactId: " + artifactID); - } - return new ArtifactCoordinates(sanitizedGroupId, sanitizedArtifactId); - } - - @Override - public AuthUtils auth() { - return this.auth; - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACCommand.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACCommand.java deleted file mode 100644 index 0335c164..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACCommand.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import akka.NotUsed; -import akka.actor.typed.ActorRef; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.TagRegistration; -import org.spongepowered.downloads.versions.api.models.TagVersion; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagEntry; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = ACCommand.RegisterArtifact.class, name = "register-artifact"), - @JsonSubTypes.Type(value = ACCommand.RegisterVersion.class, name = "register-version"), - @JsonSubTypes.Type(value = ACCommand.RegisterArtifactTag.class, name = "register-tag"), - @JsonSubTypes.Type(value = ACCommand.UpdateArtifactTag.class, name = "update-tag"), - @JsonSubTypes.Type(value = ACCommand.RegisterPromotion.class, name = "register-promotion"), -}) -public sealed interface ACCommand extends Jsonable{ - - record RegisterArtifact( - ArtifactCoordinates coordinates, - ActorRef replyTo - ) implements ACCommand { - } - - record RegisterVersion( - MavenCoordinates coordinates, - ActorRef replyTo - ) implements ACCommand { - } - - record RegisterArtifactTag( - ArtifactTagEntry entry, - ActorRef replyTo - ) implements ACCommand { - } - - record UpdateArtifactTag( - ArtifactTagEntry entry, - ActorRef replyTo - ) implements ACCommand { - } - - record RegisterPromotion( - String regex, - ActorRef replyTo, - boolean enableManualMarking - ) implements ACCommand { - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACEvent.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACEvent.java deleted file mode 100644 index 8897e94f..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/ACEvent.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagEntry; - -import java.io.Serial; -import java.util.Objects; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME) -@JsonSubTypes({ - @JsonSubTypes.Type( - value = ACEvent.ArtifactTagRegistered.class, - name = "tag-registered" - ), - @JsonSubTypes.Type( - value = ACEvent.ArtifactCoordinatesUpdated.class, - name = "updated-coordinates" - ), - @JsonSubTypes.Type( - value = ACEvent.ArtifactVersionRegistered.class, - name = "version-registered" - ), - @JsonSubTypes.Type( - value = ACEvent.PromotionSettingModified.class, - name = "promotion-settings-modified" - ), - @JsonSubTypes.Type( - value = ACEvent.ArtifactVersionsResorted.class, - name = "versions-resorted" - ) -}) -public interface ACEvent extends AggregateEvent, Jsonable { - AggregateEventShards INSTANCE = AggregateEventTag.sharded(ACEvent.class, 10); - - @Override - default AggregateEventTagger aggregateTag() { - return INSTANCE; - } - - record ArtifactCoordinatesUpdated(ArtifactCoordinates coordinates) implements ACEvent { - - @JsonCreator - public ArtifactCoordinatesUpdated { - } - - } - - record ArtifactVersionRegistered( - MavenCoordinates version, - int sorting - ) implements ACEvent { - @Serial private static final long serialVersionUID = 0L; - - @JsonCreator - public ArtifactVersionRegistered { - } - - @Override - public boolean equals(final Object obj) { - if (obj == this) { - return true; - } - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - final var that = (ArtifactVersionRegistered) obj; - return Objects.equals(this.version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(this.version); - } - - @Override - public String toString() { - return "ArtifactVersionRegistered[" + - "version=" + this.version + ", "; - } - } - - @JsonDeserialize - record ArtifactTagRegistered(ArtifactCoordinates coordinates, @JsonProperty("entry") ArtifactTagEntry entry) - implements ACEvent { - - } - - @JsonDeserialize - record PromotionSettingModified(ArtifactCoordinates coordinates, String regex, boolean enableManualPromotion) - implements ACEvent { - } - - @JsonDeserialize - record ArtifactVersionsResorted( - ArtifactCoordinates coordinates, Map versionordering - ) implements ACEvent{ - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/InvalidRequest.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/InvalidRequest.java deleted file mode 100644 index fa444b46..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/InvalidRequest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import org.spongepowered.downloads.versions.api.models.TagRegistration; -import org.spongepowered.downloads.versions.api.models.TagVersion; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; - -/** - * An invalid request to return to the asker in the service implementation to - * signify the current state is literally invalid to perform the specified - * action. - */ -public record InvalidRequest() - implements TagRegistration.Response, - TagVersion.Response, - VersionRegistration.Response { -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/State.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/State.java deleted file mode 100644 index d69a07fd..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/State.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.CompressedJsonable; -import io.vavr.Tuple2; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import io.vavr.collection.SortedMap; -import io.vavr.collection.TreeMap; -import org.apache.maven.artifact.versioning.ComparableVersion; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagEntry; -import org.spongepowered.downloads.versions.api.models.tags.ArtifactTagValue; - -import java.util.Collections; -import java.util.Comparator; -import java.util.Locale; -import java.util.Objects; -import java.util.StringJoiner; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.regex.Pattern; - -public interface State { - boolean isRegistered(); - - static Empty empty() { - return new Empty(); - } - - final record Empty() implements State { - @Override - public boolean isRegistered() { - return false; - } - - public ACState register(ACEvent.ArtifactCoordinatesUpdated event) { - return new ACState(event.coordinates()); - } - } - - @JsonDeserialize - final record ACState( - ArtifactCoordinates coordinates, - SortedMap collection, - Map> versionedArtifacts, - boolean unregistered, - Map tags, - String promotionRegex, - boolean manualPromotionAllowed - ) implements CompressedJsonable, State { - - ACState(ArtifactCoordinates coordinates) { - this(coordinates, TreeMap.empty(), HashMap.empty(), false, HashMap.empty(), "", false); - } - - public boolean isRegistered() { - return !this.unregistered; - } - - public ACState withVersion(String version) { - final var versionMap = this.collection - .computeIfAbsent(version, convertArtifactVersionToTagValues(this, this.tags)) - ._2 - .toSortedMap(Comparator.comparing(ComparableVersion::new).reversed(), Tuple2::_1, Tuple2::_2); - return new ACState( - this.coordinates, - versionMap, - this.versionedArtifacts, - this.unregistered, - this.tags, - "", - false - ); - } - - public ACState withTag(ArtifactTagEntry entry) { - final var tagMap = this.tags().put(entry.name().toLowerCase(Locale.ROOT), entry); - final var versionedTags = this.collection - .replaceAll((version, values) -> convertArtifactVersionToTagValues(this, tagMap).apply(version)); - return new ACState( - this.coordinates, - versionedTags, - this.versionedArtifacts, - this.unregistered, - tagMap, - "", - false - ); - } - - private Function convertArtifactVersionToTagValues( - ACState state, Map tagMap - ) { - return version -> { - final var mavenCoordinates = state.coordinates.version(version); - final Map tagValues = tagMap.mapValues( - tag -> tag.generateValue(mavenCoordinates).tagValue()); - return new ArtifactTagValue(state.coordinates().version(version), tagValues, false); - }; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ACState acState = (ACState) o; - return Objects.equals(coordinates, acState.coordinates) && Objects.equals( - collection, acState.collection); - } - - @Override - public int hashCode() { - return Objects.hash(coordinates, collection); - } - - @Override - public String toString() { - return new StringJoiner( - ", ", ACState.class.getSimpleName() + "[", "]") - .add("coordinates=" + coordinates) - .add("collection=" + collection) - .toString(); - } - - public ACState withPromotionDetails(String regex, boolean enableManualPromotion) { - final var pattern = Pattern.compile(regex); - final var versionedTags = this.collection - .replaceAll((version, value) -> value.promote(pattern.matcher(version).find())) - .toSortedMap(Comparator.comparing(ComparableVersion::new).reversed(), Tuple2::_1, Tuple2::_2); - return new ACState( - this.coordinates, - versionedTags, - this.versionedArtifacts, - false, - this.tags, - regex, - enableManualPromotion - ); - } - - public ACState withAddedArtifacts(MavenCoordinates coordinates, List newArtifacts) { - final var existing = this.versionedArtifacts.get(coordinates.version) - .getOrElse(List::empty); - final var existingArtifactsByClassifier = existing.toMap(a -> a.classifier().orElse(""), Function.identity()); - final var newArtifactList = existing.appendAll( - newArtifacts.filter(Predicate.not(artifact -> existingArtifactsByClassifier.containsKey(artifact.classifier().orElse(""))))); - final var versionedArtifacts = this.versionedArtifacts.put(coordinates.version, newArtifactList); - return new ACState( - this.coordinates, - this.collection, - versionedArtifacts, - false, - this.tags, - this.promotionRegex, - this.manualPromotionAllowed - ); - } - - public java.util.List addVersion(MavenCoordinates coordinates) { - final var versions = this.collection - .keySet() - .toSortedSet(Comparator.comparing(ComparableVersion::new)); - final var newVersions = versions.add(coordinates.version); - final var newIndex = newVersions - .toList() - .indexOf(coordinates.version); - final var versionRegistered = new ACEvent.ArtifactVersionRegistered(coordinates, newIndex); - final var events = List.empty(); - if (newIndex >= versions.size()) { - return events.append(versionRegistered).toJavaList(); - } - if (versions.size() == newVersions.size()) { - return Collections.emptyList(); - } - // Figure out how many versions are being resorted - final var sortedVersions = newVersions.toSortedSet(Comparator.comparing(ComparableVersion::new)); - final java.util.Map versionsByIndex = new java.util.HashMap<>(); - versions.forEachWithIndex(versionsByIndex::put); - final java.util.Map updatedVersionsIndecies = new java.util.HashMap<>(); - sortedVersions.forEachWithIndex(updatedVersionsIndecies::put); - versionsByIndex.forEach((version, oldIndex) -> { - if (Objects.equals(updatedVersionsIndecies.get(version), oldIndex)) { - updatedVersionsIndecies.remove(version); - } - }); - final var trimmedIndecies = HashMap.ofAll(updatedVersionsIndecies); - final var versionMoved = new ACEvent.ArtifactVersionsResorted(this.coordinates, trimmedIndecies); - return events.append(versionRegistered).append(versionMoved).toJavaList(); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactAggregate.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactAggregate.java deleted file mode 100644 index e475d30e..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactAggregate.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import akka.NotUsed; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.EntityContext; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import akka.persistence.typed.javadsl.RetentionCriteria; -import com.lightbend.lagom.javadsl.persistence.AkkaTaggerAdapter; -import org.spongepowered.downloads.versions.api.models.TagRegistration; -import org.spongepowered.downloads.versions.api.models.TagVersion; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; - -import java.util.Locale; -import java.util.Set; -import java.util.function.Function; - -public final class VersionedArtifactAggregate - extends EventSourcedBehaviorWithEnforcedReplies { - - public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create(ACCommand.class, "VersionedArtifact"); - private final Function> tagger; - private final ActorContext ctx; - - public static Behavior create(final EntityContext context) { - return Behaviors.setup(ctx -> new VersionedArtifactAggregate(context, ctx)); - } - - private VersionedArtifactAggregate( - final EntityContext context, - final ActorContext ctx - ) { - super( - // PersistenceId needs a typeHint (or namespace) and entityId, - // we take then from the EntityContext - PersistenceId.of( - context.getEntityTypeKey().name(), // <- type hint - context.getEntityId() // <- business id - )); - this.tagger = AkkaTaggerAdapter.fromLagom(context, ACEvent.INSTANCE); - this.ctx = ctx; - } - - @Override - public State emptyState() { - return State.empty(); - } - - @Override - public EventHandler eventHandler() { - final var builder = this.newEventHandlerBuilder(); - builder.forStateType(State.Empty.class) - .onEvent(ACEvent.ArtifactCoordinatesUpdated.class, State.Empty::register); - builder.forStateType(State.ACState.class) - .onEvent(ACEvent.ArtifactTagRegistered.class, (state1, event1) -> state1.withTag(event1.entry())) - .onEvent( - ACEvent.ArtifactVersionRegistered.class, - (state2, event2) -> state2.withVersion(event2.version().version) - ) - .onEvent( - ACEvent.PromotionSettingModified.class, - (state, event) -> state.withPromotionDetails(event.regex(), event.enableManualPromotion()) - ) - .onEvent(ACEvent.ArtifactVersionsResorted.class, (state, event) -> state) - ; - return builder.build(); - } - - @Override - public Set tagsFor(final ACEvent acEvent) { - return this.tagger.apply(acEvent); - } - - @Override - public RetentionCriteria retentionCriteria() { - return RetentionCriteria.snapshotEvery(10, 2); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder(); - builder.forStateType(State.Empty.class) - .onCommand(ACCommand.RegisterArtifact.class, this::handleRegisterArtifact) - .onCommand( - ACCommand.RegisterArtifactTag.class, (cmd) -> this.Effect().reply(cmd.replyTo(), new InvalidRequest())) - .onCommand( - ACCommand.RegisterVersion.class, (cmd) -> this.Effect().reply(cmd.replyTo(), new InvalidRequest())) - .onCommand( - ACCommand.RegisterArtifactTag.class, (cmd) -> this.Effect().reply(cmd.replyTo(), new InvalidRequest())) - .onCommand( - ACCommand.UpdateArtifactTag.class, (cmd) -> this.Effect().reply(cmd.replyTo(), new InvalidRequest())) - .onCommand( - ACCommand.RegisterPromotion.class, (cmd) -> this.Effect().reply(cmd.replyTo(), new InvalidRequest())) - ; - builder.forStateType(State.ACState.class) - .onCommand(ACCommand.RegisterArtifact.class, (cmd) -> this.Effect().reply(cmd.replyTo(), NotUsed.notUsed())) - .onCommand(ACCommand.RegisterVersion.class, this::handleRegisterVersion) - .onCommand(ACCommand.RegisterArtifactTag.class, this::handlRegisterTag) - .onCommand(ACCommand.UpdateArtifactTag.class, this::handleUpdateTag) - .onCommand(ACCommand.RegisterPromotion.class, this::handlePromotionSetting) - ; - return builder.build(); - } - - private ReplyEffect handleRegisterVersion( - final State.ACState state, final ACCommand.RegisterVersion cmd - ) { - if (state.collection().containsKey(cmd.coordinates().version)) { - return this.Effect().reply( - cmd.replyTo(), - new VersionRegistration.Response.ArtifactAlreadyRegistered(cmd.coordinates()) - ); - } - return this.Effect() - .persist(state.addVersion(cmd.coordinates())) - .thenReply(cmd.replyTo(), (s) -> new VersionRegistration.Response.RegisteredArtifact(cmd.coordinates())); - } - - private ReplyEffect handleRegisterArtifact( - final State.Empty state, - final ACCommand.RegisterArtifact cmd - ) { - return this.Effect() - .persist(new ACEvent.ArtifactCoordinatesUpdated(cmd.coordinates())) - .thenReply(cmd.replyTo(), (s) -> NotUsed.notUsed()); - } - - private ReplyEffect handlRegisterTag( - final State.ACState state, - final ACCommand.RegisterArtifactTag cmd - ) { - if (state.tags().containsKey(cmd.entry().name().toLowerCase(Locale.ROOT))) { - return this.Effect().reply( - cmd.replyTo(), new TagRegistration.Response.TagAlreadyRegistered(cmd.entry().name())); - } - return this.Effect() - .persist(new ACEvent.ArtifactTagRegistered(state.coordinates(), cmd.entry())) - .thenReply(cmd.replyTo(), (s) -> new TagRegistration.Response.TagSuccessfullyRegistered()); - } - - private ReplyEffect handlePromotionSetting( - final State.ACState state, - final ACCommand.RegisterPromotion cmd - ) { - return this.Effect() - .persist(new ACEvent.PromotionSettingModified(state.coordinates(), cmd.regex(), cmd.enableManualMarking())) - .thenReply(cmd.replyTo(), (s) -> new TagVersion.Response.TagSuccessfullyRegistered()); - } - - private ReplyEffect handleUpdateTag( - final State.ACState state, - final ACCommand.UpdateArtifactTag cmd - ) { - return this.Effect() - .persist(new ACEvent.ArtifactTagRegistered(state.coordinates(), cmd.entry())) - .thenReply(cmd.replyTo(), (s) -> new TagRegistration.Response.TagSuccessfullyRegistered()); - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactEvent.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactEvent.java deleted file mode 100644 index 0dc24738..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/domain/VersionedArtifactEvent.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.domain; - -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.util.Objects; -import java.util.StringJoiner; - -public interface VersionedArtifactEvent extends AggregateEvent, Jsonable { - - AggregateEventShards TAG = AggregateEventTag.sharded(VersionedArtifactEvent.class, 10); - - @Override - default AggregateEventTagger aggregateTag() { - return TAG; - } - - String asMavenCoordinates(); - - class VersionRegistered implements VersionedArtifactEvent { - - public final MavenCoordinates coordinates; - - public VersionRegistered(final MavenCoordinates coordinates) { - this.coordinates = coordinates; - } - - - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - VersionRegistered that = (VersionRegistered) o; - return Objects.equals(coordinates, that.coordinates); - } - - @Override - public int hashCode() { - return Objects.hash(coordinates); - } - - @Override - public String toString() { - return new StringJoiner(", ", VersionRegistered.class.getSimpleName() + "[", "]") - .add("coordinates=" + coordinates) - .toString(); - } - - @Override - public String asMavenCoordinates() { - return this.coordinates.asStandardCoordinates(); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/AssetReadsidePersistence.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/AssetReadsidePersistence.java deleted file mode 100644 index 72936abd..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/AssetReadsidePersistence.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.ReadSide; -import com.lightbend.lagom.javadsl.persistence.ReadSideProcessor; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaReadSide; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaSession; -import org.pcollections.PSequence; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.ArtifactEvent; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.persistence.EntityManager; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.atomic.AtomicInteger; - -@Singleton -public class AssetReadsidePersistence { - - private final JpaSession session; - - @Inject - public AssetReadsidePersistence( - final ReadSide readSide, - final JpaSession session - ) { - this.session = session; - readSide.register(AssetReadsidePersistence.AssetWriter.class); - } - - static final class AssetWriter extends ReadSideProcessor { - - private final JpaReadSide readSide; - - private static final AtomicInteger counter = new AtomicInteger(); - - @Inject - AssetWriter(final JpaReadSide readSide) { - this.readSide = readSide; - } - - @Override - public ReadSideHandler buildHandler() { - return this.readSide.builder("asset_read_side_processor_" + counter.incrementAndGet()) - .setGlobalPrepare((em) -> {}) - .setEventHandler(ArtifactEvent.AssetsUpdated.class, (em, event) -> { - final var coordinates = event.coordinates(); - final var version = em.createNamedQuery( - "ArtifactVersion.findByCoordinates", - JpaArtifactVersion.class - ) - .setParameter("groupId", coordinates.groupId) - .setParameter("artifactId", coordinates.artifactId) - .setParameter("version", coordinates.version) - .setMaxResults(1) - .getSingleResult(); - event.artifacts() - .forEach(asset -> { - final var versionedAsset = findOrCreateVersionedAsset(em, version, asset); - versionedAsset.setDownloadUrl(asset.downloadUrl().toString()); - versionedAsset.setMd5(asset.md5().getBytes(StandardCharsets.UTF_8)); - versionedAsset.setSha1(asset.sha1().getBytes(StandardCharsets.UTF_8)); - versionedAsset.setExtension(asset.extension()); - }); - }) - .build(); - } - - private static JpaVersionedArtifactAsset findOrCreateVersionedAsset( - EntityManager em, JpaArtifactVersion version, Artifact asset - ) { - return em.createNamedQuery( - "VersionedAsset.findByVersion", - JpaVersionedArtifactAsset.class - ) - .setParameter("id", version.getId()) - .setParameter("classifier", asset.classifier().orElse("")) - .setParameter("extension", asset.extension()) - .setMaxResults(1) - .getResultStream() - .findFirst() - .orElseGet(() -> { - final var jpaAsset = new JpaVersionedArtifactAsset(); - jpaAsset.setClassifier(asset.classifier().orElse("")); - version.addAsset(jpaAsset); - return jpaAsset; - }); - } - - @Override - public PSequence> aggregateTags() { - return ArtifactEvent.INSTANCE.allTags(); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifact.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifact.java deleted file mode 100644 index 54d85836..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifact.java +++ /dev/null @@ -1,186 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.Index; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -@Entity(name = "Artifact") -@Table(name = "artifacts", - schema = "version", - indexes = { - @Index(name = "grouped_artifact", - columnList = "group_id, artifact_id", - unique = true) - }) -@NamedQueries({ - @NamedQuery( - name = "Artifact.selectByGroupAndArtifact", - query = """ - select a from Artifact a where a.groupId = :groupId and a.artifactId = :artifactId - """ - ), - @NamedQuery( - name = "Artifact.selectWithTags", - query = """ - select a from Artifact a where a.groupId = :groupId and a.artifactId = :artifactId - """ - ) -}) -public class JpaArtifact implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private int id; - - @Column(name = "group_id", - nullable = false) - private String groupId; - - @Column(name = "artifact_id", - nullable = false) - private String artifactId; - - @OneToMany( - targetEntity = JpaArtifactTag.class, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "artifact") - private Set tags = new HashSet<>(); - - @OneToMany( - targetEntity = JpaArtifactVersion.class, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "artifact") - private Set versions = new HashSet<>(); - - @OneToOne( - targetEntity = JpaArtifactRegexRecommendation.class, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "artifact" - ) - private JpaArtifactRegexRecommendation regexRecommendation; - - @Column(name = "git_repository") - private String repo; - - public int getId() { - return id; - } - - public void setId(final int id) { - this.id = id; - } - - public String getGroupId() { - return groupId; - } - - public void setGroupId(final String groupId) { - this.groupId = groupId; - } - - public String getArtifactId() { - return artifactId; - } - - public void setArtifactId(final String artifactId) { - this.artifactId = artifactId; - } - - public Set getTags() { - return tags; - } - - public void setTags(final Set tags) { - this.tags = tags; - } - - public void addVersion(JpaArtifactVersion version) { - this.versions.add(version); - version.setArtifact(this); - } - - public Set getVersions() { - return versions; - } - - public void setVersions(final Set versions) { - this.versions = versions; - } - - public void setRecommendation( - final JpaArtifactRegexRecommendation regexRecommendation - ) { - this.regexRecommendation = regexRecommendation; - regexRecommendation.setArtifact(this); - } - - public String getRepo() { - return repo; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaArtifact that = (JpaArtifact) o; - return id == that.id && Objects.equals(groupId, that.groupId) && Objects.equals( - artifactId, that.artifactId); - } - - @Override - public int hashCode() { - return Objects.hash(id, groupId, artifactId); - } - - public void addTag(JpaArtifactTag newTag) { - this.tags.add(newTag); - newTag.setArtifact(this); - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactRegexRecommendation.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactRegexRecommendation.java deleted file mode 100644 index 28794554..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactRegexRecommendation.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.NamedQuery; -import javax.persistence.OneToOne; -import javax.persistence.Table; - -@Entity(name = "RegexBasedRecommendation") -@Table(name = "artifact_recommendations", - schema = "version") -@NamedQuery(name = "RegexRecommendation.findByArtifact", - query = """ - select r from RegexBasedRecommendation r where r.artifact.id = :artifactId - """) -public class JpaArtifactRegexRecommendation { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private long id; - - @OneToOne - @JoinColumn(name = "artifact_id", - referencedColumnName = "id", - nullable = false) - private JpaArtifact artifact; - - @Column(name = "recommendation_regex", - nullable = false) - private String regex; - - @Column(name = "allow_manual_promotion") - private boolean manual; - - void setArtifact(JpaArtifact artifact) { - this.artifact = artifact; - } - - public String getRegex() { - return regex; - } - - public void setRegex(final String regex) { - this.regex = regex; - } - - public boolean isManual() { - return manual; - } - - public void setManual(final boolean manual) { - this.manual = manual; - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactTag.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactTag.java deleted file mode 100644 index 98fe79fb..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactTag.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.Table; - -@Entity(name = "ArtifactTag") -@Table(name = "artifact_tags", - schema = "version") -public class JpaArtifactTag { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private int id; - - @ManyToOne(optional = false, fetch = FetchType.LAZY) - @JoinColumn(name = "artifact_id", referencedColumnName = "id", nullable = false) - private JpaArtifact artifact; - - @Column(name = "tag_name", - nullable = false) - private String name; - - @Column(name = "tag_regex") - private String regex; - - @Column(name = "use_capture_group") - private int group; - - public JpaArtifactTag() { - } - - public JpaArtifact getArtifact() { - return artifact; - } - - void setArtifact(final JpaArtifact taggedVersion) { - this.artifact = taggedVersion; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public String getRegex() { - return regex; - } - - public void setRegex(final String regex) { - this.regex = regex; - } - - public int getGroup() { - return group; - } - - public void setGroup(final int group) { - this.group = group; - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactVersion.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactVersion.java deleted file mode 100644 index 809581cd..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaArtifactVersion.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; -import java.io.Serializable; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -@Entity(name = "ArtifactVersion") -@Table(name = "artifact_versions", - schema = "version", - uniqueConstraints = @UniqueConstraint( - columnNames = {"artifact_id", "version"}, - name = "artifact_version_unique_idx") -) -@NamedQueries({ - @NamedQuery( - name = "ArtifactVersion.findByVersion", - query = - """ - select distinct v from ArtifactVersion v where v.artifact.id = :artifactId and v.version = :version - """ - ), - - @NamedQuery( - name = "ArtifactVersion.findByCoordinates", - query = - """ - select distinct v from ArtifactVersion v - where v.artifact.groupId = :groupId and v.artifact.artifactId = :artifactId and v.version = :version - """ - ) -}) -class JpaArtifactVersion implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "artifact_id", - foreignKey = @ForeignKey(name = "artifact_versions_artifact_id_fkey"), - nullable = false) - private JpaArtifact artifact; - - @Column(name = "version", - nullable = false) - private String version; - - @Column(name = "ordering") - private int ordering; - - @OneToMany( - targetEntity = JpaVersionedArtifactAsset.class, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "versionedArtifact") - private Set assets = new HashSet<>(); - - void setArtifact(final JpaArtifact artifact) { - this.artifact = artifact; - } - - public JpaArtifact getArtifact() { - return artifact; - } - - public String getVersion() { - return version; - } - - public long getId() { - return id; - } - - public void setVersion(final String version) { - this.version = version; - } - - public void addAsset(final JpaVersionedArtifactAsset asset) { - this.assets.add(asset); - asset.setVersion(this); - } - - public void setOrdering(final int ordering) { - this.ordering = ordering; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaArtifactVersion that = (JpaArtifactVersion) o; - return id == that.id && Objects.equals(artifact, that.artifact) && Objects.equals( - version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(id, artifact, version); - } - -} - diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaVersionedArtifactAsset.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaVersionedArtifactAsset.java deleted file mode 100644 index a2b94925..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/JpaVersionedArtifactAsset.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import org.hibernate.annotations.Type; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.Table; -import java.util.Arrays; -import java.util.Objects; - -@Entity(name = "VersionedAsset") -@Table(name = "versioned_assets", - schema = "version") -@NamedQueries({ - @NamedQuery( - name = "VersionedAsset.findByVersion", - query = - """ - select a from VersionedAsset a - where a.versionedArtifact.id = :id and a.classifier = :classifier - and a.extension = :extension - """ - ) -}) -public class JpaVersionedArtifactAsset { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private long id; - - @ManyToOne(targetEntity = JpaArtifactVersion.class, - fetch = FetchType.LAZY) - @JoinColumn(name = "version_id", - referencedColumnName = "id", - nullable = false) - private JpaArtifactVersion versionedArtifact; - - @Column(name = "classifier", - nullable = false) - private String classifier; - - @Column(name = "download_url", - nullable = false) - private String downloadUrl; - - @Column(name = "extension", - nullable = false) - private String extension; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "md5") - private byte[] md5; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "sha1") - private byte[] sha1; - - public void setClassifier(final String classifier) { - this.classifier = classifier; - } - - public void setExtension(final String extension) { - this.extension = extension; - } - - public void setDownloadUrl(final String downloadUrl) { - this.downloadUrl = downloadUrl; - } - - public void setMd5(final byte[] md5) { - this.md5 = md5; - } - - public void setSha1(final byte[] sha1) { - this.sha1 = sha1; - } - - void setVersion(JpaArtifactVersion jpaArtifactVersion) { - this.versionedArtifact = jpaArtifactVersion; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedArtifactAsset that = (JpaVersionedArtifactAsset) o; - return id == that.id && Objects.equals( - versionedArtifact, that.versionedArtifact) && Objects.equals( - classifier, that.classifier) && Objects.equals( - downloadUrl, that.downloadUrl) && Objects.equals( - extension, that.extension) && Arrays.equals( - md5, that.md5) && Arrays.equals(sha1, that.sha1); - } - - @Override - public int hashCode() { - int result = Objects.hash(id, versionedArtifact, classifier, downloadUrl, extension); - result = 31 * result + Arrays.hashCode(md5); - result = 31 * result + Arrays.hashCode(sha1); - return result; - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionReadSidePersistence.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionReadSidePersistence.java deleted file mode 100644 index 7b796485..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionReadSidePersistence.java +++ /dev/null @@ -1,201 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import akka.actor.ActorSystem; -import akka.actor.typed.ActorRef; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.Adapter; -import akka.actor.typed.javadsl.Behaviors; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.ReadSide; -import com.lightbend.lagom.javadsl.persistence.ReadSideProcessor; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaReadSide; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaSession; -import org.pcollections.PSequence; -import org.spongepowered.downloads.versions.server.domain.ACEvent; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.persistence.EntityManager; -import java.util.concurrent.atomic.AtomicInteger; - -@Singleton -public class VersionReadSidePersistence { - - private final JpaSession session; - - @Inject - public VersionReadSidePersistence( - final ReadSide readSide, - final JpaSession session - ) { - this.session = session; - readSide.register(VersionWriter.class); - } - - static final class VersionWriter extends ReadSideProcessor { - - private final JpaReadSide readSide; - private final ActorRef refresher; - - private static final AtomicInteger counter = new AtomicInteger(); - - @Inject - VersionWriter(final JpaReadSide readSide, final JpaSession session, final ActorSystem system) { - this.readSide = readSide; - final var taggedWorker = VersionedTagWorker.create(session); - final var commandBehavior = Behaviors.supervise(taggedWorker).onFailure(SupervisorStrategy.restart()); - this.refresher = Adapter.spawn( - system.classicSystem(), commandBehavior, "version-tag-db-worker-" + counter.incrementAndGet()); - } - - @Override - public ReadSideHandler buildHandler() { - return this.readSide.builder("version-query-builder") - .setGlobalPrepare(this::createSchema) - .setEventHandler(ACEvent.ArtifactCoordinatesUpdated.class, (em, artifactCreated) -> { - System.out.printf("Herpaderp coordinates updated %s\n", artifactCreated.coordinates().toString()); - final var coordinates = artifactCreated.coordinates(); - final var artifactQuery = em.createNamedQuery( - "Artifact.selectByGroupAndArtifact", - JpaArtifact.class - ); - final var singleResult = artifactQuery.setParameter("groupId", coordinates.groupId()) - .setParameter("artifactId", coordinates.artifactId()) - .setMaxResults(1) - .getResultList(); - if (singleResult.isEmpty()) { - final var jpaArtifact = new JpaArtifact(); - jpaArtifact.setGroupId(coordinates.groupId()); - jpaArtifact.setArtifactId(coordinates.artifactId()); - em.persist(jpaArtifact); - } - }) - .setEventHandler(ACEvent.ArtifactVersionRegistered.class, (em, versionRegistered) -> { - final var coordinates = versionRegistered.version(); - final var query = em.createNamedQuery( - "Artifact.selectByGroupAndArtifact", - JpaArtifact.class - ); - query.setParameter("groupId", coordinates.groupId); - query.setParameter("artifactId", coordinates.artifactId); - final var artifact = query.getSingleResult(); - final var version = coordinates.version; - em.createNamedQuery( - "ArtifactVersion.findByVersion", - JpaArtifactVersion.class - ) - .setParameter("artifactId", artifact.getId()) - .setParameter("version", version) - .setMaxResults(1) - .getResultList() - .stream().findFirst() - .orElseGet(() -> { - final var jpaArtifactVersion = new JpaArtifactVersion(); - jpaArtifactVersion.setVersion(version); - jpaArtifactVersion.setOrdering(versionRegistered.sorting()); - artifact.addVersion(jpaArtifactVersion); - refresher.tell(new VersionedTagWorker.RefreshVersionTags()); - refresher.tell(new VersionedTagWorker.RefreshVersionRecommendation(coordinates.asArtifactCoordinates())); - return jpaArtifactVersion; - }); - }) - .setEventHandler(ACEvent.ArtifactTagRegistered.class, (em, tagRegistered) -> { - final var coordinates = tagRegistered.coordinates(); - final var artifactQuery = em.createNamedQuery( - "Artifact.selectWithTags", - JpaArtifact.class - ); - artifactQuery.setParameter("groupId", coordinates.groupId()); - artifactQuery.setParameter("artifactId", coordinates.artifactId()); - final var tag = tagRegistered.entry(); - final var artifact = artifactQuery.getSingleResult(); - final var jpaTag = artifact.getTags().stream() - .filter(jpatag -> jpatag.getName().equals(tag.name())) - .findFirst() - .orElseGet(() -> { - final var jpaArtifactTag = new JpaArtifactTag(); - artifact.addTag(jpaArtifactTag); - return jpaArtifactTag; - }); - jpaTag.setRegex(tag.regex()); - jpaTag.setName(tag.name()); - jpaTag.setGroup(tag.matchingGroup()); - refresher.tell(new VersionedTagWorker.RefreshVersionTags()); - }) - .setEventHandler(ACEvent.PromotionSettingModified.class, (em, promotion) -> { - final var coordinates = promotion.coordinates(); - final var artifactQuery = em.createNamedQuery( - "Artifact.selectWithTags", - JpaArtifact.class - ); - - artifactQuery.setParameter("groupId", coordinates.groupId()); - artifactQuery.setParameter("artifactId", coordinates.artifactId()); - final var artifact = artifactQuery.getSingleResult(); - final var recommendation = em.createNamedQuery( - "RegexRecommendation.findByArtifact", JpaArtifactRegexRecommendation.class) - .setParameter("artifactId", artifact.getId()) - .getResultList() - .stream() - .findFirst() - .orElseGet(() -> { - final var regexRecommendation = new JpaArtifactRegexRecommendation(); - artifact.setRecommendation(regexRecommendation); - return regexRecommendation; - }); - recommendation.setRegex(promotion.regex()); - recommendation.setManual(promotion.enableManualPromotion()); - - refresher.tell(new VersionedTagWorker.RefreshVersionRecommendation(promotion.coordinates())); - }) - .setEventHandler(ACEvent.ArtifactVersionsResorted.class, (em, event) -> { - event.versionordering().forEach((v, ordering) -> { - final var jpaVersion = em.createNamedQuery( - "ArtifactVersion.findByCoordinates", - JpaArtifactVersion.class - ) - .setParameter("groupId", event.coordinates().groupId()) - .setParameter("artifactId", event.coordinates().artifactId()) - .setParameter("version", v) - .setMaxResults(1) - .getSingleResult(); - jpaVersion.setOrdering(ordering); - }); - }) - .build(); - } - - private void createSchema(EntityManager em) { - } - - @Override - public PSequence> aggregateTags() { - return ACEvent.INSTANCE.allTags(); - } - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionedTagWorker.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionedTagWorker.java deleted file mode 100644 index 204e0cdf..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/server/readside/VersionedTagWorker.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.server.readside; - -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaSession; -import io.vavr.collection.HashSet; -import io.vavr.collection.Set; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; - -import java.time.Duration; -import java.time.Instant; -import java.util.Optional; - -public final class VersionedTagWorker { - - public interface Command { - } - - static final record RefreshVersionTags() implements Command { - } - - static final record RefreshVersionRecommendation(ArtifactCoordinates coordinates) implements Command { - } - - private static final record TimedOut() implements Command { - } - - private static final record Completed(Data data, boolean updatedVersionedTags, int rowsAffected) - implements Command { - } - - private static final record Failed(Data data) implements Command { - } - - private static final record Data(Optional refreshVersions, - Set refreshRecommendations) { - - Data requestedRefreshVersions() { - return new Data(Optional.of(Instant.now()), this.refreshRecommendations); - } - - Data updateArtifactRecommendation(ArtifactCoordinates coordinates) { - return new Data(this.refreshVersions, this.refreshRecommendations.add(coordinates)); - } - } - - public static Behavior create( - final JpaSession session - ) { - return idle(session); - } - - private static Behavior idle(final JpaSession session) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage( - RefreshVersionTags.class, - cmd -> timed(new Data(Optional.of(Instant.now()), HashSet.empty()), session) - ) - .onMessage( - RefreshVersionRecommendation.class, - cmd -> timed(new Data(Optional.empty(), HashSet.of(cmd.coordinates)), session) - ) - .onMessage(Completed.class, cmd -> { - ctx.getLog().info("Completed refresh of {}, affected {}", cmd.data, cmd.rowsAffected); - return Behaviors.same(); - }) - .onMessage(Failed.class, cmd -> { - ctx.getLog().warn("Recovering from failed update, will re-attempt"); - return timed(cmd.data, session); - }) - .build() - ); - } - - private static Behavior timed( - final Data data, final JpaSession session - ) { - return Behaviors.setup(ctx -> Behaviors.withTimers(timers -> { - timers.startSingleTimer(new TimedOut(), Duration.ofSeconds(10)); - return waiting(data, session); - })); - } - - private static Behavior waiting( - final Data data, final JpaSession session - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Command.class) - .onMessage( - RefreshVersionTags.class, - cmd -> waiting(data.requestedRefreshVersions(), session) - ) - .onMessage( - RefreshVersionRecommendation.class, - cmd -> waiting(data.updateArtifactRecommendation(cmd.coordinates), session) - ) - .onMessage(TimedOut.class, timeout -> { - ctx.pipeToSelf(session.withTransaction(em -> { - final var updatedVersionedTags = data.refreshVersions - .map(time -> em - .createNativeQuery("select version.refreshversionedtags()") - .getSingleResult()) - .isPresent(); - final int rowsAffected = data.refreshRecommendations - .map(coordinates -> em.createNativeQuery( - "select version.refreshVersionRecommendations(:artifactId, :groupId)") - .setParameter("artifactId", coordinates.artifactId()) - .setParameter("groupId", coordinates.groupId()) - .getSingleResult()) - .sum().intValue(); - return new Completed(data, updatedVersionedTags, rowsAffected); - }), (msg, throwable) -> { - if (throwable != null) { - ctx.getLog().error("Failed to handle updating artifacts, aborting", throwable); - return new Failed(data); - } - return msg; - }); - return idle(session); - }) - .build()); - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/EntityStore.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/EntityStore.java deleted file mode 100644 index 779384a6..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/EntityStore.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.Entity; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactEntity; - -public final class EntityStore { - - public static void setupPersistedEntities(ClusterSharding sharding) { - sharding.init(Entity.of(VersionedArtifactEntity.ENTITY_TYPE_KEY, VersionedArtifactEntity::create)); - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionConfig.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionConfig.java deleted file mode 100644 index ebb956b8..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionConfig.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.actor.Extension; -import com.typesafe.config.Config; - -import java.time.Duration; - -public class VersionConfig implements Extension { - - public final CommitFetch commitFetch; - - public VersionConfig(Config config) { - final var commitConfig = config.getConfig("commit-fetch"); - this.commitFetch = new CommitFetch(commitConfig); - } - - public static final class CommitFetch { - public final int poolSize; - public final int parallelism; - public final Duration timeout; - - CommitFetch(Config config) { - this.poolSize = config.getInt("pool-size"); - this.parallelism = config.getInt("parallelism"); - this.timeout = config.getDuration("timeout"); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionExtension.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionExtension.java deleted file mode 100644 index 20624e55..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionExtension.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.actor.AbstractExtensionId; -import akka.actor.ExtendedActorSystem; -import akka.actor.Extension; -import akka.actor.ExtensionId; -import akka.actor.ExtensionIdProvider; - -public class VersionExtension extends AbstractExtensionId implements ExtensionIdProvider { - - public static final VersionExtension Settings = new VersionExtension(); - - @Override - public VersionConfig createExtension(final ExtendedActorSystem system) { - return new VersionConfig(system.settings().config().getConfig("systemofadownload.versions")); - } - - @Override - public ExtensionId lookup() { - return Settings; - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionsWorkerSupervisor.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionsWorkerSupervisor.java deleted file mode 100644 index 7afae9be..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/VersionsWorkerSupervisor.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.Cluster; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.versions.api.VersionsService; - -/** - * The "reactive" side of Versions where it performs all the various tasks - * involved with managing Artifact Versions and their artifact assets with - * relation to those versions. Crucially, this performs the various sync jobs - * required to derive a Version from an artifact, as well as the various - * metadata with that version, such as assets and the commit information for - * that asset. This is mostly a guardian actor, one that wires up children - * actors to perform the actual work against topic subscribers, either from - * {@link ArtifactService#artifactUpdate()} or - * {@link VersionsService#artifactUpdateTopic()}. - *

The important reasoning why this is split out from the Version Service - * implementation is that this particular supervisor may well be able to handle - * updates while the VersionsService implementation is the "organizer" of - * root information. - */ -public final class VersionsWorkerSupervisor { - - public static Behavior bootstrapWorkers() { - return Behaviors.setup(ctx -> { - final ClusterSharding sharding = ClusterSharding.get(ctx.getSystem()); - // Persistent EventBased Actors - EntityStore.setupPersistedEntities(sharding); - - // Workers available to do most jobs - final var member = Cluster.get(ctx.getSystem()).selfMember(); - final var system = ctx.getSystem(); - WorkerSpawner.spawnWorkers(system, member, ctx); - - // Finally, self, the supervisor - return Behaviors.receive(Void.class) - .build(); - }); - - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerModule.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerModule.java deleted file mode 100644 index 68c25c64..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerModule.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.actor.ActorSystem; -import akka.actor.typed.ActorRef; -import akka.actor.typed.javadsl.Adapter; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import com.google.inject.AbstractModule; -import com.google.inject.Provider; -import com.google.inject.TypeLiteral; -import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; -import org.spongepowered.downloads.artifact.api.ArtifactService; -import org.spongepowered.downloads.auth.api.utils.AuthUtils; -import org.spongepowered.downloads.versions.api.VersionsService; -import play.Environment; -import play.libs.akka.AkkaGuiceSupport; - -import javax.inject.Inject; - -public class WorkerModule extends AbstractModule implements ServiceGuiceSupport, AkkaGuiceSupport { - - private final AuthUtils auth; - - @SuppressWarnings("unused") // These parameters must match for Play's Guice handling to work. - @Inject - public WorkerModule(final Environment environment, final com.typesafe.config.Config config) { - this.auth = AuthUtils.configure(config); - } - - @Override - protected void configure() { - this.bind(new TypeLiteral>() { - }) - .toProvider(WorkerProvider.class) - .asEagerSingleton(); - - } - - public static record WorkerProvider( - ArtifactService artifacts, - VersionsService versions, - ClusterSharding sharding, - ActorSystem system - ) implements Provider> { - - @Inject - public WorkerProvider { - } - - @Override - public ActorRef get() { - return Adapter.spawn( - this.system, - VersionsWorkerSupervisor.bootstrapWorkers(), - "VersionsWorkerSupervisor" - ); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerSpawner.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerSpawner.java deleted file mode 100644 index a5c4f73d..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/WorkerSpawner.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker; - -import akka.actor.typed.ActorSystem; -import akka.actor.typed.DispatcherSelector; -import akka.actor.typed.SupervisorStrategy; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.actor.typed.receptionist.Receptionist; -import akka.cluster.Member; -import org.spongepowered.downloads.versions.worker.actor.artifacts.CommitExtractor; -import org.spongepowered.downloads.versions.worker.actor.artifacts.FileCollectionOperator; -import org.spongepowered.downloads.versions.worker.actor.delegates.RawCommitReceiver; - -import java.util.UUID; - -public final class WorkerSpawner { - - public static void spawnWorkers(ActorSystem system, Member member, ActorContext ctx) { - // Set up the usual actors - final var versionConfig = VersionExtension.Settings.get(system); - final var poolSizePerInstance = versionConfig.commitFetch.poolSize; - - if (member.hasRole("file-extractor")) { - final var commitFetcherUID = UUID.randomUUID(); - final var behavior = CommitExtractor.extractCommitFromAssets(); - final var assetRefresher = Behaviors.supervise(behavior) - .onFailure(SupervisorStrategy.resume()); - final var pool = Routers.pool(poolSizePerInstance, assetRefresher); - - final var commitExtractorRef = ctx.spawn( - pool, - "file-commit-worker-" + commitFetcherUID, - DispatcherSelector.defaultDispatcher() - ); - // Announce it to the cluster - ctx.getSystem().receptionist().tell(Receptionist.register(CommitExtractor.SERVICE_KEY, commitExtractorRef)); - - final var receiver = Behaviors.supervise(RawCommitReceiver.receive()) - .onFailure(SupervisorStrategy.resume()); - final var receiverRef = ctx.spawn(receiver, "file-scan-result-receiver-" + commitFetcherUID); - final var jarScanner = FileCollectionOperator.scanJarFilesForCommit(commitExtractorRef, receiverRef); - final var supervisedScanner = Behaviors.supervise(jarScanner).onFailure(SupervisorStrategy.resume()); - final var scannerPool = Routers.pool(poolSizePerInstance, supervisedScanner); - final var scannerRef = ctx.spawn(scannerPool, "file-collection-worker-" + commitFetcherUID); - ctx.getSystem().receptionist().tell(Receptionist.register(FileCollectionOperator.KEY, scannerRef)); - } - } - - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/CommitExtractor.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/CommitExtractor.java deleted file mode 100644 index b0a87bee..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/CommitExtractor.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.actor.artifacts; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.receptionist.ServiceKey; -import akka.japi.function.Function2; -import io.vavr.Tuple; -import io.vavr.Tuple2; -import io.vavr.collection.List; -import io.vavr.control.Try; -import org.eclipse.jgit.lib.ObjectId; - -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.PosixFilePermission; -import java.nio.file.attribute.PosixFilePermissions; -import java.util.EnumSet; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.jar.JarInputStream; -import java.util.jar.Manifest; - -/** - * Actor that extracts the commit from a jar file. - */ -public final class CommitExtractor { - - public static final ServiceKey SERVICE_KEY = ServiceKey.create( - ChildCommand.class, "commit-extractor"); - private static final FileAttribute> OWNER_READ_WRITE_ATTRIBUTE = PosixFilePermissions.asFileAttribute( - EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE)); - - public sealed interface ChildCommand { - } - - public final record AttemptFileCommit( - PotentiallyUsableAsset asset, - ActorRef ref - ) implements ChildCommand { - } - - private final record NoCommitsFound(PotentiallyUsableAsset asset, ActorRef ref) implements ChildCommand { - } - - private final record FailedFile( - Throwable throwable, - PotentiallyUsableAsset asset, - ActorRef replyTo - ) implements ChildCommand { - } - - private final record CommitRetrievedFromFile( - String sha, - PotentiallyUsableAsset asset, - ActorRef replyTo - ) implements ChildCommand { - } - - public sealed interface AssetCommitResponse { - } - - public final record DiscoveredCommitFromFile(String sha, PotentiallyUsableAsset asset) - implements AssetCommitResponse { - } - - - public final record NoCommitsFoundForFile(PotentiallyUsableAsset asset) implements AssetCommitResponse { - } - - public final record FailedToRetrieveCommit(PotentiallyUsableAsset asset) - implements AssetCommitResponse { - } - - public static Behavior extractCommitFromAssets() { - return Behaviors.setup(ctx -> Behaviors.receive(ChildCommand.class) - .onMessage(AttemptFileCommit.class, cmd -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Attempting file commit extraction from {}", cmd.asset); - } - ctx.pipeToSelf(fetchCommitIdFromFile(cmd), handleFileExtractionResult(ctx, cmd)); - return working(cmd.asset, List.empty()); - }) - .build()); - } - - private static Function2, Throwable, ChildCommand> handleFileExtractionResult( - ActorContext ctx, AttemptFileCommit cmd - ) { - return (sha, throwable) -> { - if (throwable != null) { - ctx.getLog().info("Marking file {} as failed", cmd.asset); - return new FailedFile(throwable, cmd.asset, cmd.ref); - } - if (sha.isEmpty()) { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().info("File {} doesn't have a commit", cmd.asset); - } - return new NoCommitsFound(cmd.asset, cmd.ref); - } - final var commit = sha.get(); - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug( - "[{}] Commit extracted from jar {}", cmd.asset.mavenCoordinates().asStandardCoordinates(), - commit.name() - ); - } - return new CommitRetrievedFromFile(commit.name(), cmd.asset, cmd.ref); - }; - } - - - private static Behavior working( - final PotentiallyUsableAsset working, - final List queue - ) { - return Behaviors.setup(ctx -> Behaviors.receive(ChildCommand.class) - .onMessage(AttemptFileCommit.class, cmd -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Received additional work while processing {}", working.mavenCoordinates()); - } - return working(working, queue.append(cmd)); - }) - .onMessage(FailedFile.class, cmd -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Failed commit extraction for " + cmd.asset.mavenCoordinates(), cmd.throwable); - } - cmd.replyTo.tell(new FailedToRetrieveCommit(cmd.asset)); - return swapToNext(ctx, queue); - }) - .onMessage(NoCommitsFound.class, cmd -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("No commits found for " + working.mavenCoordinates()); - } - cmd.ref.tell(new NoCommitsFoundForFile(cmd.asset)); - return swapToNext(ctx, queue); - }) - .onMessage(CommitRetrievedFromFile.class, cmd -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace( - "Commit extracted from {} for {}", cmd.asset.coordinates(), working.mavenCoordinates()); - } - cmd.replyTo.tell(new DiscoveredCommitFromFile(cmd.sha, cmd.asset)); - return swapToNext(ctx, queue); - }) - .build()); - } - - private static Behavior swapToNext( - final ActorContext ctx, - final List queue - ) { - if (queue.isEmpty()) { - return extractCommitFromAssets(); - } - final var next = queue.head(); - ctx.pipeToSelf(fetchCommitIdFromFile(next), handleFileExtractionResult(ctx, next)); - return working(next.asset, queue.tail()); - } - - private static CompletableFuture> fetchCommitIdFromFile( - AttemptFileCommit cmd - ) { - return Try.of(cmd.asset.downloadURL()::toURL) - .mapTry(req -> { - final var tempFile = Files.createTempFile( - String.format( - "commit-check-%s", - cmd.asset.coordinates() - ), - ".jar", - OWNER_READ_WRITE_ATTRIBUTE - ); - return Tuple.of(req, tempFile); - }) - .flatMap(CommitExtractor::getCommitFromFile) - .mapTry(CommitExtractor::extractCommitFromJarManifest) - .toCompletableFuture(); - } - - private static Try getCommitFromFile(Tuple2 tuple) { - return Try.withResources( - () -> Channels.newChannel(tuple._1.openStream()), - () -> FileChannel.open(tuple._2, StandardOpenOption.WRITE) - ) - .of((remoteFile, transfer) -> { - transfer.transferFrom(remoteFile, 0, Long.MAX_VALUE); - return tuple._2; - }); - } - - private static Optional extractCommitFromJarManifest(Path path) { - return Try.withResources( - () -> new JarInputStream(Files.newInputStream(path, StandardOpenOption.DELETE_ON_CLOSE))) - .of(JarInputStream::getManifest) - .filter(Objects::nonNull) - .mapTry(Manifest::getMainAttributes) - .mapTry(attributes -> attributes.getValue("Git-Commit")) - .map(Optional::ofNullable) - .map(opt -> opt.flatMap(sha -> Try.of(() -> ObjectId.fromString(sha)) - .map(Optional::of) - .getOrElse(Optional::empty) - )) - .getOrElse(Optional::empty); - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/FileCollectionOperator.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/FileCollectionOperator.java deleted file mode 100644 index c4755bbc..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/FileCollectionOperator.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.actor.artifacts; - -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.receptionist.ServiceKey; -import akka.stream.javadsl.Sink; -import akka.stream.javadsl.Source; -import akka.stream.typed.javadsl.ActorFlow; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.time.Duration; - -public final class FileCollectionOperator { - - public static final ServiceKey KEY = ServiceKey.create(Request.class, "file-collection-operator"); - - @JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) - @JsonSubTypes({ - @JsonSubTypes.Type(TryFindingCommitForFiles.class) - }) - @JsonDeserialize - public sealed interface Request extends Jsonable {} - - @JsonDeserialize - public static final record TryFindingCommitForFiles( - List files, - MavenCoordinates coordinates - ) implements Request {} - - public static Behavior scanJarFilesForCommit( - final ActorRef commitExtractor, - final ActorRef receiverRef - ) { - return Behaviors.setup(ctx -> Behaviors.receive(Request.class) - .onMessage(TryFindingCommitForFiles.class, msg -> { - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Received request for {}", msg); - } - final List files = msg.files(); - final var from = Source.from(files); - final var extraction = ActorFlow.ask( - 4, - commitExtractor, - Duration.ofMinutes(20), - CommitExtractor.AttemptFileCommit::new - ); - final var receiverSink = Sink.foreach(receiverRef::tell); - from.via(extraction).to(receiverSink).run(ctx.getSystem()); - return Behaviors.same(); - }) - .build()); - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/PotentiallyUsableAsset.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/PotentiallyUsableAsset.java deleted file mode 100644 index 3f186135..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/artifacts/PotentiallyUsableAsset.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.actor.artifacts; - -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.net.URI; - -public record PotentiallyUsableAsset( - MavenCoordinates mavenCoordinates, - String coordinates, - URI downloadURL -) { -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/delegates/RawCommitReceiver.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/delegates/RawCommitReceiver.java deleted file mode 100644 index b1a29844..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/actor/delegates/RawCommitReceiver.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.actor.delegates; - -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.Behaviors; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import org.spongepowered.downloads.versions.worker.actor.artifacts.CommitExtractor; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactCommand; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactEntity; - -public final class RawCommitReceiver { - - public static Behavior receive() { - return Behaviors.setup(ctx -> { - final var sharding = ClusterSharding.get(ctx.getSystem()); - return Behaviors.receive(CommitExtractor.AssetCommitResponse.class) - .onMessage(CommitExtractor.FailedToRetrieveCommit.class, msg -> { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug( - "[{}] Failed to retrieve commit", msg.asset().mavenCoordinates().asStandardCoordinates()); - } - sharding.entityRefFor(VersionedArtifactEntity.ENTITY_TYPE_KEY, msg.asset().mavenCoordinates().asStandardCoordinates()) - .tell(new VersionedArtifactCommand.MarkFilesAsErrored()); - return Behaviors.same(); - }) - .onMessage(CommitExtractor.DiscoveredCommitFromFile.class, msg -> { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug( - "[{}] Retrieved commit", msg.asset().mavenCoordinates().asStandardCoordinates()); - } - sharding.entityRefFor(VersionedArtifactEntity.ENTITY_TYPE_KEY, msg.asset().mavenCoordinates().asStandardCoordinates()) - .tell(new VersionedArtifactCommand.RegisterRawCommit(msg.sha())); - return Behaviors.same(); - }) - .onMessage(CommitExtractor.NoCommitsFoundForFile.class, msg -> { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug( - "[{}] No commit found", msg.asset().mavenCoordinates().asStandardCoordinates()); - } - sharding.entityRefFor(VersionedArtifactEntity.ENTITY_TYPE_KEY, msg.asset().mavenCoordinates().asStandardCoordinates()) - .tell(new VersionedArtifactCommand.MarkFilesAsErrored()); - return Behaviors.same(); - }) - .build(); - }); - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactEvent.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactEvent.java deleted file mode 100644 index 191c6176..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactEvent.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.domain.versionedartifact; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.javadsl.persistence.AggregateEvent; -import com.lightbend.lagom.javadsl.persistence.AggregateEventShards; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTagger; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.DEDUCTION) -public sealed interface ArtifactEvent extends AggregateEvent, Jsonable { - - AggregateEventShards INSTANCE = AggregateEventTag.sharded(ArtifactEvent.class, 3); - - @Override - default AggregateEventTagger aggregateTag() { - return INSTANCE; - } - - final record Registered(MavenCoordinates coordinates) implements ArtifactEvent { - } - - final record AssetsUpdated(MavenCoordinates coordinates, List artifacts) implements ArtifactEvent { - } - - final record FilesErrored() implements ArtifactEvent { - } - - final record CommitAssociated(MavenCoordinates coordinates, List repos, String commitSha) - implements ArtifactEvent { - @JsonCreator - public CommitAssociated { - } - } - - final record CommitResolved( - MavenCoordinates coordinates, - URI repo, - VersionedCommit versionedCommit - ) implements ArtifactEvent { - } - - public record CommitUnresolved( - MavenCoordinates coordinates, - String commitId - ) - implements ArtifactEvent { - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactState.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactState.java deleted file mode 100644 index 8b76931c..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/ArtifactState.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.domain.versionedartifact; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.HashSet; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.function.Predicate; - -@JsonDeserialize -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = ArtifactState.Unregistered.class, name = "unregistered"), - @JsonSubTypes.Type(value = ArtifactState.Registered.class, name = "registered") -}) -public sealed interface ArtifactState extends Jsonable { - - io.vavr.collection.List artifacts(); - - default boolean needsArtifactScan() { - return false; - } - - default boolean needsCommitResolution() { - return false; - } - - default Optional commitSha() { - return Optional.empty(); - } - - default io.vavr.collection.List repositories() { - return io.vavr.collection.List.empty(); - } - - final record Unregistered() implements ArtifactState { - private static final Unregistered INSTANCE = new Unregistered(); - - public ArtifactEvent register(VersionedArtifactCommand.Register cmd) { - return new ArtifactEvent.Registered(cmd.coordinates()); - } - - @Override - public io.vavr.collection.List artifacts() { - return io.vavr.collection.List.empty(); - } - } - - @JsonDeserialize - final record FileStatus( - Optional commitSha, - Optional commit, - boolean scanned, - io.vavr.collection.List artifacts, - boolean resolutionError) { - @JsonCreator - public FileStatus { - } - - static final FileStatus EMPTY = new FileStatus( - Optional.empty(), - Optional.empty(), - true, - io.vavr.collection.List.empty(), - false - ); - - public FileStatus withResultionError(boolean b) { - return new FileStatus( - commitSha, - commit, - scanned, - artifacts, - b - ); - } - } - - static Registered register(final ArtifactEvent.Registered event) { - return new Registered(event.coordinates(), HashSet.empty(), FileStatus.EMPTY); - } - - final record Registered( - MavenCoordinates coordinates, - HashSet repo, - FileStatus fileStatus - ) implements ArtifactState { - - public Registered { - } - - @Override - public Optional commitSha() { - return this.fileStatus.commitSha; - } - - @Override - public boolean needsArtifactScan() { - return !this.fileStatus.scanned; - } - - @Override - public boolean needsCommitResolution() { - return this.fileStatus.commit.isEmpty() && this.fileStatus.commitSha.isPresent(); - } - - @Override - public io.vavr.collection.List repositories() { - return this.repo.toList().map(URI::create); - } - - public List addAssets(io.vavr.collection.List artifacts) { - - final var filtered = artifacts - .filter(Predicate.not(a -> this.fileStatus.artifacts.map(Artifact::downloadUrl).contains(a.downloadUrl()))); - if (filtered.isEmpty()) { - return List.of(); - } - - return List.of(new ArtifactEvent.AssetsUpdated(this.coordinates, filtered)); - } - - public ArtifactState withAssets(io.vavr.collection.List artifacts) { - return new Registered( - this.coordinates, - this.repo, - new FileStatus( - this.fileStatus.commitSha, - this.fileStatus.commit, - this.fileStatus.commit.isPresent() || this.fileStatus.commitSha.isPresent(), - artifacts, - false - ) - ); - } - - @Override - public io.vavr.collection.List artifacts() { - return this.fileStatus.artifacts(); - } - - public ArtifactState markFilesErrored() { - return new Registered( - this.coordinates, - this.repo, - new FileStatus( - this.fileStatus().commitSha, - this.fileStatus.commit, - true, - this.fileStatus.artifacts, - false - ) - ); - } - - public List associateCommit(String commitSha) { - if (this.fileStatus.commitSha.isPresent()) { - return List.of(); - } - return List.of(new ArtifactEvent.CommitAssociated(this.coordinates, this.repo.toList(), commitSha)); - } - - public ArtifactState withCommit(String commitSha) { - if (this.fileStatus.commitSha.isPresent()) { - return this; - } - return new Registered( - this.coordinates, - this.repo, - new FileStatus( - Optional.of(commitSha), - this.fileStatus.commit, - true, - this.fileStatus.artifacts, - false - ) - ); - } - - public ArtifactState resolveCommit(ArtifactEvent.CommitResolved event) { - return new Registered( - this.coordinates, - this.repo, - new FileStatus( - this.fileStatus.commitSha, - Optional.of(event.versionedCommit()), - this.fileStatus.scanned, - this.fileStatus.artifacts, - false - ) - ); - } - - public ArtifactState withRepository(String repository) { - return new Registered( - this.coordinates, - this.repo.add(repository), - this.fileStatus - ); - } - - public List failedCommit(String commitId) { - return this.fileStatus.commit.map(v -> Collections.emptyList()) - .orElseGet(() -> List.of(new ArtifactEvent.CommitUnresolved(this.coordinates, commitId))); - } - - public ArtifactState markCommitAsUnresolved(ArtifactEvent.CommitUnresolved a) { - final var status = this.fileStatus.withResultionError(true); - return new Registered( - this.coordinates, - this.repo, - status - ); - } - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactCommand.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactCommand.java deleted file mode 100644 index 25e7b6e9..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactCommand.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.domain.versionedartifact; - -import akka.Done; -import akka.actor.typed.ActorRef; -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonSubTypes; -import com.fasterxml.jackson.annotation.JsonTypeInfo; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.lightbend.lagom.serialization.Jsonable; -import io.vavr.collection.List; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.ArtifactCollection; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; - -import java.net.URI; - -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type") -@JsonSubTypes({ - @JsonSubTypes.Type(value = VersionedArtifactCommand.Register.class, name = "register"), - @JsonSubTypes.Type(value = VersionedArtifactCommand.AddAssets.class, name = "add-assets"), - @JsonSubTypes.Type(value = VersionedArtifactCommand.MarkFilesAsErrored.class, name = "errored-assets"), - @JsonSubTypes.Type(value = VersionedArtifactCommand.RegisterAssets.class, name = "register-assets"), - @JsonSubTypes.Type(value = VersionedArtifactCommand.RegisterRawCommit.class, name = "register-sha"), - @JsonSubTypes.Type(value = VersionedArtifactCommand.RegisterResolvedCommit.class, name = "register-commit"), -}) -@JsonDeserialize -public sealed interface VersionedArtifactCommand extends Jsonable { - - final record Register( - MavenCoordinates coordinates, - ActorRef replyTo - ) implements VersionedArtifactCommand { - @JsonCreator - public Register { - } - } - - record RegisterAssets( - MavenCoordinates coordinates, - ArtifactCollection collection, - ActorRef replyTo - ) implements VersionedArtifactCommand { - @JsonCreator - public RegisterAssets { - } - } - - final record AddAssets( - MavenCoordinates coordinates, - List artifacts, - ActorRef replyTo - ) implements VersionedArtifactCommand { - @JsonCreator - public AddAssets { - } - } - - final record MarkFilesAsErrored() implements VersionedArtifactCommand { - @JsonCreator - public MarkFilesAsErrored { - } - } - - final record RegisterRawCommit(String commitSha) implements VersionedArtifactCommand {} - - final record RegisterResolvedCommit( - VersionedCommit versionedCommit, - URI repo, - ActorRef replyTo - ) - implements VersionedArtifactCommand { - @JsonCreator - public RegisterResolvedCommit { - } - } - - record RegisterFailedCommit( - String commitId, - URI repo, - ActorRef replyTo - ) - implements VersionedArtifactCommand { - } - - public record RefreshCommitResolution() implements VersionedArtifactCommand { } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactEntity.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactEntity.java deleted file mode 100644 index 7ff0ebf0..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/domain/versionedartifact/VersionedArtifactEntity.java +++ /dev/null @@ -1,306 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.domain.versionedartifact; - -import akka.Done; -import akka.actor.typed.ActorRef; -import akka.actor.typed.Behavior; -import akka.actor.typed.javadsl.ActorContext; -import akka.actor.typed.javadsl.Behaviors; -import akka.actor.typed.javadsl.Routers; -import akka.cluster.sharding.typed.javadsl.EntityContext; -import akka.cluster.sharding.typed.javadsl.EntityTypeKey; -import akka.persistence.typed.PersistenceId; -import akka.persistence.typed.javadsl.CommandHandlerWithReply; -import akka.persistence.typed.javadsl.EventHandler; -import akka.persistence.typed.javadsl.EventSourcedBehaviorWithEnforcedReplies; -import akka.persistence.typed.javadsl.ReplyEffect; -import akka.persistence.typed.javadsl.RetentionCriteria; -import com.lightbend.lagom.javadsl.persistence.AkkaTaggerAdapter; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.versions.api.models.VersionRegistration; -import org.spongepowered.downloads.versions.worker.actor.artifacts.FileCollectionOperator; -import org.spongepowered.downloads.versions.worker.actor.artifacts.PotentiallyUsableAsset; - -import java.util.Arrays; -import java.util.Set; -import java.util.function.Function; - -public class VersionedArtifactEntity - extends EventSourcedBehaviorWithEnforcedReplies { - public static EntityTypeKey ENTITY_TYPE_KEY = EntityTypeKey.create( - VersionedArtifactCommand.class, "VersionedArtifactEntity"); - private final Function> tagger; - private final ActorRef scanFiles; - private final ActorContext ctx; - - public static Behavior create(final EntityContext context) { - return Behaviors.setup(ctx -> { - final var scanFiles = Routers.group(FileCollectionOperator.KEY); - final var scanRef = ctx.spawn(scanFiles, "scan-files-" + context.getEntityId()); - if (ctx.getLog().isTraceEnabled()) { - ctx.getLog().trace("Entity Context {}", context.getEntityId()); - } - return new VersionedArtifactEntity(context, ctx, scanRef); - }); - } - - private VersionedArtifactEntity( - final EntityContext entityContext, - final ActorContext ctx, - final ActorRef scanRef - ) { - super(PersistenceId.of(entityContext.getEntityTypeKey().name(), entityContext.getEntityId())); - this.ctx = ctx; - this.tagger = AkkaTaggerAdapter.fromLagom(entityContext, ArtifactEvent.INSTANCE); - this.scanFiles = scanRef; - } - - @Override - public ArtifactState emptyState() { - return new ArtifactState.Unregistered(); - } - - @Override - public EventHandler eventHandler() { - final var builder = this.newEventHandlerBuilder(); - builder.forStateType(ArtifactState.Unregistered.class) - .onEvent(ArtifactEvent.Registered.class, ArtifactState::register) - ; - builder.forStateType(ArtifactState.Registered.class) - .onEvent(ArtifactEvent.AssetsUpdated.class, (s, e) -> s.withAssets(e.artifacts())) - .onEvent(ArtifactEvent.FilesErrored.class, (s, e) -> s.markFilesErrored()) - .onEvent(ArtifactEvent.CommitAssociated.class, (s, e) -> s.withCommit(e.commitSha())) - .onEvent(ArtifactEvent.CommitResolved.class, ArtifactState.Registered::resolveCommit) - .onEvent(ArtifactEvent.CommitUnresolved.class, ArtifactState.Registered::markCommitAsUnresolved) - ; - return builder.build(); - } - - @Override - public CommandHandlerWithReply commandHandler() { - final var builder = this.newCommandHandlerWithReplyBuilder(); - builder.forStateType(ArtifactState.Unregistered.class) - .onCommand(VersionedArtifactCommand.Register.class, this::onRegister) - .onCommand(VersionedArtifactCommand.AddAssets.class, this::onEmptyAddAssets) - .onCommand(VersionedArtifactCommand.RegisterAssets.class, this::onEmptyRegisterAssets) - ; - builder.forStateType(ArtifactState.Registered.class) - .onCommand(VersionedArtifactCommand.Register.class, cmd -> this.Effect().reply(cmd.replyTo(), Done.done())) - .onCommand(VersionedArtifactCommand.RegisterAssets.class, this::onRegisterAssets) - .onCommand(VersionedArtifactCommand.AddAssets.class, this::onAddAssets) - .onCommand(VersionedArtifactCommand.MarkFilesAsErrored.class, this::onMarkFilesAsErrored) - .onCommand(VersionedArtifactCommand.RegisterRawCommit.class, this::onRegisterRawCommit) - .onCommand(VersionedArtifactCommand.RegisterResolvedCommit.class, this::handleCompletedCommit) - .onCommand(VersionedArtifactCommand.RegisterFailedCommit.class, this::handleFailedCommit) - .onCommand(VersionedArtifactCommand.RefreshCommitResolution.class, this::handleRefreshCommitStatus) - ; - return builder.build(); - } - - private ReplyEffect handleRefreshCommitStatus(final ArtifactState.Registered state, final VersionedArtifactCommand.RefreshCommitResolution cmd) { - if (state.fileStatus().commit().isPresent()) { - return this.Effect().noReply(); - } - if (state.commitSha().isEmpty()) { - return this.Effect().noReply(); - } - return this.Effect() - .persist(new ArtifactEvent.CommitAssociated(state.coordinates(), state.repo().toList(), state.commitSha().get())) - .thenNoReply(); - } - - private ReplyEffect handleFailedCommit( - final ArtifactState.Registered state, - final VersionedArtifactCommand.RegisterFailedCommit cmd - ) { - if (ctx.getLog().isDebugEnabled()) { - ctx.getLog().debug( - "[{}] Commit {} failed to resolve", state.coordinates().asStandardCoordinates(), cmd.commitId()); - } - return this.Effect() - .persist(state.failedCommit(cmd.commitId())) - .thenReply(cmd.replyTo(), ns -> Done.done()); - } - - private ReplyEffect onRegisterAssets( - final ArtifactState.Registered state, - final VersionedArtifactCommand.RegisterAssets cmd - ) { - final var artifacts = cmd.collection().components(); - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("[{}] Current assets: {}", state.coordinates(), state.artifacts()); - this.ctx.getLog().trace("[{}] Adding assets {}", state.coordinates(), artifacts); - } - return this.Effect() - .persist(state.addAssets(artifacts)) - .thenRun(ns -> { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("[{}] Updated assets", state.coordinates()); - } - if (ns.needsArtifactScan()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug( - "[{}] Telling FileCollectionOperator to fetch commit from files", - state.coordinates() - ); - } - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace( - "[{}] Assets available {}", state.coordinates(), ns.artifacts().map(Artifact::toString)); - } - final var usableAssets = ns.artifacts() - .filter(a -> "jar".equalsIgnoreCase(a.extension())) - .filter(a -> a.classifier().isEmpty() || a.classifier().filter(""::equals).isPresent()) - .map(a -> new PotentiallyUsableAsset(state.coordinates(), a.extension(), a.downloadUrl())); - if (!usableAssets.isEmpty()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug("[{}] Assets to scan {}", state.coordinates(), usableAssets); - } - this.scanFiles.tell( - new FileCollectionOperator.TryFindingCommitForFiles(usableAssets, state.coordinates())); - } - } - }) - .thenReply(cmd.replyTo(), ns -> new VersionRegistration.Response.RegisteredArtifact(cmd.coordinates())); - } - - private ReplyEffect onEmptyRegisterAssets( - final VersionedArtifactCommand.RegisterAssets cmd - ) { - this.ctx.getLog().warn("[{}] Registering collection on empty state", cmd.coordinates().asStandardCoordinates()); - return this.Effect() - .persist(Arrays.asList(new ArtifactEvent.Registered(cmd.coordinates()), new ArtifactEvent.AssetsUpdated(cmd.coordinates(), cmd.collection().components()))) - .thenReply(cmd.replyTo(), ns -> new VersionRegistration.Response.RegisteredArtifact(cmd.coordinates())); - } - - private ReplyEffect onEmptyAddAssets( - final VersionedArtifactCommand.AddAssets cmd - ) { - this.ctx.getLog().warn("[{}] Registering assets with empty state", cmd.coordinates().asStandardCoordinates()); - return this.Effect() - .persist(Arrays.asList(new ArtifactEvent.Registered(cmd.coordinates()), new ArtifactEvent.AssetsUpdated(cmd.coordinates(), cmd.artifacts()))) - .thenReply(cmd.replyTo(), ns -> Done.done()); - } - - private ReplyEffect handleCompletedCommit( - final ArtifactState.Registered state, final VersionedArtifactCommand.RegisterResolvedCommit cmd - ) { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("Received completed commit {}", cmd.versionedCommit()); - } - if (state.fileStatus().commit().isPresent()) { - this.ctx.getLog().warn("[{}] Ignoring {} with already registered commit {}", state.coordinates().asStandardCoordinates(), cmd.versionedCommit(), state.fileStatus().commit().get()); - return this.Effect().reply(cmd.replyTo(), Done.done()); - } - return this.Effect() - .persist(new ArtifactEvent.CommitResolved(state.coordinates(), cmd.repo(), cmd.versionedCommit())) - .thenReply(cmd.replyTo(), ns -> Done.done()); - } - - private ReplyEffect onRegisterRawCommit( - final ArtifactState.Registered state, - final VersionedArtifactCommand.RegisterRawCommit cmd - ) { - this.ctx.getLog().debug("Raw commit registered {}", cmd); - return this.Effect() - .persist(state.associateCommit(cmd.commitSha())) - .thenNoReply(); - } - - private ReplyEffect onMarkFilesAsErrored(ArtifactState state, VersionedArtifactCommand.MarkFilesAsErrored cmd) { - if (state.needsArtifactScan()) { - return this.Effect() - .persist(new ArtifactEvent.FilesErrored()) - .thenRun(ns -> this.ctx.getLog().debug("File as failed {}", ns)) - .thenNoReply(); - } - return this.Effect() - .persist(new ArtifactEvent.FilesErrored()) - .thenRun(ns -> this.ctx.getLog().debug("File as failed {}", ns)) - .thenNoReply(); - } - - private ReplyEffect onAddAssets( - final ArtifactState.Registered state, - final VersionedArtifactCommand.AddAssets cmd - ) { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("[{}] Current assets: {}", state.coordinates(), state.artifacts()); - this.ctx.getLog().trace("[{}] Adding assets {}", state.coordinates(), cmd.artifacts()); - } - return this.Effect() - .persist(state.addAssets(cmd.artifacts())) - .thenRun(ns -> { - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace("[{}] Updated assets", state.coordinates()); - } - if (ns.needsArtifactScan()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug( - "[{}] Telling FileCollectionOperator to fetch commit from files", - state.coordinates() - ); - } - if (this.ctx.getLog().isTraceEnabled()) { - this.ctx.getLog().trace( - "[{}] Assets available {}", state.coordinates(), ns.artifacts().map(Artifact::toString)); - } - final var usableAssets = ns.artifacts() - .filter(a -> "jar".equalsIgnoreCase(a.extension())) - .filter(a -> a.classifier().isEmpty() || a.classifier().filter(""::equals).isPresent()) - .map(a -> new PotentiallyUsableAsset(state.coordinates(), a.extension(), a.downloadUrl())); - if (!usableAssets.isEmpty()) { - if (this.ctx.getLog().isDebugEnabled()) { - this.ctx.getLog().debug("[{}] Assets to scan {}", state.coordinates(), usableAssets); - } - this.scanFiles.tell( - new FileCollectionOperator.TryFindingCommitForFiles(usableAssets, state.coordinates())); - } - } - }) - .thenReply(cmd.replyTo(), ns -> Done.done()); - } - - private ReplyEffect onRegister( - final ArtifactState.Unregistered state, - final VersionedArtifactCommand.Register cmd - ) { - return this.Effect() - .persist(state.register(cmd)) - .thenReply(cmd.replyTo(), ns -> Done.done()); - } - - - @Override - public Set tagsFor(final ArtifactEvent assetEvent) { - return this.tagger.apply(assetEvent); - } - - @Override - public RetentionCriteria retentionCriteria() { - return RetentionCriteria.snapshotEvery(5, 2); - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/intro.md b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/intro.md deleted file mode 100644 index 11aef813..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/intro.md +++ /dev/null @@ -1,40 +0,0 @@ -# Versions Worker - -Versions Worker is a collection of workers that perform versioned artifact introspection. -The primary use case is to organize all of the reactive pieces of work that come about from -"a new version is registered". The workers are designed to be pluggable, and the -[`VersionsWorkerSupervisor`](VersionsWorkerSupervisor.java) is the main entry point for this -side of the system. - -## Overview - -While a normal Lagom application is a single service with a single Guice module, the -Versions Worker has a separate paired module to initialize the guardian of the child worker -actors. There is an additional side effect that is gained out of this: the supervisor is able -to reference the VersionsService indirectly as a consumer, and therefore subscribe to the -[`VersionsService.artifactUpdateTopic()`](./../../../../../../../../../versions-api/src/main/java/org/spongepowered/downloads/versions/api/VersionsService.java) topic. -An advantage as well is that while each binary of the workers are capable of delegating their -work to avoid blocking eachother, the VersionsService remains active for the primitive information -as a query reference. - -## Interfaces between systems - -### Kafka - -Through heavy use of Akka Clustering and Akka remoting, we can spin off specific workloads to -specific instances, such as git commit resolution, or artifact binary downloading and processing, -or changelog parsing. To safely coordinate the flow of work and prevent soft failures, we rely heavily -on the TopicSubscriber pattern to have the assurance the work will be done, even if the actors/binaries -restart for any reason. Likewise, we can coordinate the flow of work to single instances for longer -running jobs, while allowing for rapid fire handling of "basic" data being transformed and -messages sent off. - -### ReadSideProcessors - -We take advantage of EventSourceBased actors to describe the state of the various entities (such as -a versioned artifact with assets not having a git commit, to becoming a git commit hash extracted -from the binary, to extracting the commit details from the git repository, to resolving the changes -between two ordinal versions) and take advantage that we can populate our database with the important -details from the various events, therefor enabling the Query service to simply query for the state of -whatever models it is interested in. - diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/CommitProcessor.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/CommitProcessor.java deleted file mode 100644 index db808b8d..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/CommitProcessor.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.readside; - -import akka.actor.ActorSystem; -import com.lightbend.lagom.javadsl.persistence.AggregateEventTag; -import com.lightbend.lagom.javadsl.persistence.ReadSide; -import com.lightbend.lagom.javadsl.persistence.ReadSideProcessor; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaReadSide; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaSession; -import io.vavr.collection.List; -import io.vavr.control.Try; -import org.pcollections.PSequence; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.api.models.VersionedChangelog; -import org.spongepowered.downloads.versions.api.models.VersionedCommit; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.ArtifactEvent; -import org.spongepowered.downloads.versions.worker.readside.model.JpaVersionChangelog; -import org.spongepowered.downloads.versions.worker.readside.model.JpaVersionedArtifact; - -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.persistence.EntityManager; -import java.net.URI; -import java.net.URL; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Optional; - -@Singleton -public final record CommitProcessor( - ReadSide readSide, - JpaSession session -) { - - @Inject - public CommitProcessor { - readSide.register(CommitWriter.class); - } - - static final class CommitWriter extends ReadSideProcessor { - - private static final Logger LOGGER = LoggerFactory.getLogger("CommitWriter"); - public static final ZonedDateTime EPOCH = ZonedDateTime.of(LocalDateTime.MIN, ZoneOffset.UTC); - - private final JpaReadSide readSide; - - @Inject - CommitWriter(final JpaReadSide readSide, final JpaSession session, final ActorSystem system) { - this.readSide = readSide; - } - - @Override - public ReadSideHandler buildHandler() { - return this.readSide.builder("version-commit-writer") - .setGlobalPrepare((em) -> { - }) - .setEventHandler(ArtifactEvent.FilesErrored.class, (em, e) -> { - }) - .setEventHandler(ArtifactEvent.Registered.class, (em, e) -> { - }) - .setEventHandler(ArtifactEvent.AssetsUpdated.class, (em, e) -> { - }) - .setEventHandler( - ArtifactEvent.CommitAssociated.class, - (em, e) -> { - final var coordinates = e.coordinates(); - final var results = getVersionedArtifacts(em, coordinates); - if (results.isEmpty()) { - return; - } - final JpaVersionedArtifact jpaVersionedArtifact = results.get(0); - if (jpaVersionedArtifact.getChangelog() == null) { - final var jpaVersionChangelog = new JpaVersionChangelog(); - jpaVersionedArtifact.setChangelog(jpaVersionChangelog); - } - final var jpaChangelog = jpaVersionedArtifact.getChangelog(); - final var author = new VersionedCommit.Author("", ""); - final var committer = new VersionedCommit.Commiter("", ""); - final ZonedDateTime epoch = ZonedDateTime.of( - LocalDate.EPOCH, LocalTime.MAX, ZoneId.systemDefault()); - final var artifactRepo = jpaVersionedArtifact.getArtifact().getRepo(); - final var commitLink = Optional.ofNullable(artifactRepo).map(URI::create); - final VersionedCommit rawCommit = new VersionedCommit( - "", "", e.commitSha(), author, committer, commitLink, epoch); - final var changelog = new VersionedChangelog( - List.of(new VersionedChangelog.IndexedCommit(rawCommit, List.empty())), true); - jpaChangelog.setSha(rawCommit.sha()); - jpaChangelog.setChangelog(changelog); - Try.ofSupplier(commitLink::get) - .mapTry(URI::toURL) - .toJavaOptional() - .ifPresent(jpaChangelog::setRepo); - em.persist(jpaChangelog); - } - ) - .setEventHandler( - ArtifactEvent.CommitResolved.class, - (em, e) -> { - final var coordinates = e.coordinates(); - final var results = getVersionedArtifacts(em, coordinates); - if (results.isEmpty()) { - return; - } - final JpaVersionedArtifact jpaVersionedArtifact = results.get(0); - if (jpaVersionedArtifact.getChangelog() == null) { - final var jpaVersionChangelog = new JpaVersionChangelog(); - jpaVersionedArtifact.setChangelog(jpaVersionChangelog); - jpaVersionChangelog.setSha(e.versionedCommit().sha()); - jpaVersionChangelog.setBranch("foo"); - final var commitUrl = e.repo().toString().replace(".git", "") + "/commit/" + e.versionedCommit().sha(); - Try.of(() -> new URL(commitUrl)) - .toJavaOptional() - .ifPresent(jpaVersionChangelog::setRepo); - } - final var jpaChangelog = jpaVersionedArtifact.getChangelog(); - final var commit = new VersionedChangelog.IndexedCommit(e.versionedCommit(), List.empty()); - final var changelog = new VersionedChangelog(List.of(commit), true); - jpaChangelog.setChangelog(changelog); - em.persist(jpaChangelog); - } - ) - .setEventHandler( - ArtifactEvent.CommitUnresolved.class, - (em, e) -> { - final var coordinates = e.coordinates(); - final var results = getVersionedArtifacts(em, coordinates); - if (results.isEmpty()) { - return; - } - final JpaVersionedArtifact jpaVersionedArtifact = results.get(0); - final var changelog = new VersionedChangelog(List.of( - new VersionedChangelog.IndexedCommit( - convertToInvalidCommit(e), List.empty() - )), false); - if (jpaVersionedArtifact.getChangelog() == null) { - final var jpaVersionChangelog = new JpaVersionChangelog(); - jpaVersionedArtifact.setChangelog(jpaVersionChangelog); - jpaVersionChangelog.setSha(e.commitId()); - jpaVersionChangelog.setBranch("foo"); - jpaVersionChangelog.setChangelog(changelog); - } - final var jpaChangelog = jpaVersionedArtifact.getChangelog(); - jpaChangelog.setChangelog(changelog); - em.persist(jpaChangelog); - } - ) - .build(); - } - - private static VersionedCommit convertToInvalidCommit(ArtifactEvent.CommitUnresolved e) { - return new VersionedCommit( - "Commit not available", - """ - This build has a commit that cannot be resolved, it may be - possible to resolve it in some backup, but generally this - build will not be supported by the community due to the - lack of a commit. - """, - e.commitId(), - new VersionedCommit.Author("", ""), - new VersionedCommit.Commiter("", ""), - Optional.empty(), - EPOCH - ); - } - - private java.util.List getVersionedArtifacts( - EntityManager em, MavenCoordinates coordinates - ) { - return em.createNamedQuery( - "GitVersionedArtifact.findByCoordinates", JpaVersionedArtifact.class) - .setParameter("groupId", coordinates.groupId) - .setParameter("artifactId", coordinates.artifactId) - .setParameter("version", coordinates.version) - .setMaxResults(1) - .getResultList(); - } - - @Override - public PSequence> aggregateTags() { - return ArtifactEvent.INSTANCE.allTags(); - } - } - -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionChangelog.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionChangelog.java deleted file mode 100644 index 9a43464d..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionChangelog.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.readside.model; - -import com.vladmihalcea.hibernate.type.json.JsonBinaryType; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; -import org.spongepowered.downloads.versions.api.models.VersionedChangelog; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.MapsId; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.net.URL; -import java.util.Objects; - -@Entity(name = "VersionedChangelog") -@Table(name = "version_changelogs", schema = "version") -@TypeDefs({ - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -}) -public class JpaVersionChangelog { - - @Id - @Column(name = "version_id") - private long id; - - @OneToOne(mappedBy = "changelog", optional = false) - @MapsId("id") - private JpaVersionedArtifact artifact; - - @Column(name = "commit_sha", nullable = false) - private String sha; - - @Type(type = "jsonb") - @Column(name = "changelog", columnDefinition = "jsonb") - private VersionedChangelog changelog; - - @Column(name = "repo") - private URL repo; - - @Column(name = "branch") - private String branch; - - public long getId() { - return id; - } - - void setId(long id) { - this.id = id; - } - - public void setSha(final String sha) { - this.sha = sha; - } - - public void setRepo(final URL repo) { - this.repo = repo; - } - - public void setBranch(final String branch) { - this.branch = branch; - } - - public JpaVersionedArtifact getArtifact() { - return artifact; - } - - void setArtifact(JpaVersionedArtifact artifact) { - this.artifact = artifact; - } - - public void setChangelog(VersionedChangelog changelog) { - this.changelog = changelog; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionChangelog that = (JpaVersionChangelog) o; - return id == that.id && Objects.equals(artifact, that.artifact) && Objects.equals( - sha, that.sha) && Objects.equals(changelog, that.changelog) && Objects.equals( - repo, that.repo) && Objects.equals(branch, that.branch); - } - - @Override - public int hashCode() { - return Objects.hash(id, artifact, sha, changelog, repo, branch); - } - - @Override - public String toString() { - return "JpaVersionChangelog{" + - "id=" + id + - ", artifact=" + artifact + - ", sha='" + sha + '\'' + - ", changelog=" + changelog + - ", repo=" + repo + - ", branch='" + branch + '\'' + - '}'; - } -} diff --git a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionedArtifact.java b/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionedArtifact.java deleted file mode 100644 index d2b702f8..00000000 --- a/versions-impl/src/main/java/org/spongepowered/downloads/versions/worker/readside/model/JpaVersionedArtifact.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.worker.readside.model; - -import org.spongepowered.downloads.versions.server.readside.JpaArtifact; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.ForeignKey; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.ManyToOne; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import javax.persistence.UniqueConstraint; -import java.io.Serializable; -import java.util.Objects; - -@Entity(name = "GitVersionedArtifact") -@Table(name = "artifact_versions", - schema = "version", - uniqueConstraints = @UniqueConstraint( - columnNames = {"artifact_id", "version"}, - name = "artifact_version_unique_idx") -) -@NamedQueries({ - @NamedQuery( - name = "GitVersionedArtifact.findByCoordinates", - query = - """ - select distinct v from GitVersionedArtifact v - where v.artifact.groupId = :groupId and v.artifact.artifactId = :artifactId and v.version = :version - """ - ) -}) -public class JpaVersionedArtifact implements Serializable { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "id", - updatable = false, - nullable = false) - private long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "artifact_id", - foreignKey = @ForeignKey(name = "artifact_versions_artifact_id_fkey"), - nullable = false) - private JpaArtifact artifact; - - @Column(name = "version", - nullable = false) - private String version; - - @OneToOne(targetEntity = JpaVersionChangelog.class, fetch = FetchType.LAZY) - @JoinColumn(name = "id", referencedColumnName = "version_id") - private JpaVersionChangelog changelog; - - public JpaArtifact getArtifact() { - return artifact; - } - - public String getVersion() { - return version; - } - - public JpaVersionChangelog getChangelog() { - return changelog; - } - - public void setChangelog(final JpaVersionChangelog changelog) { - this.changelog = changelog; - changelog.setId(this.id); - changelog.setArtifact(this); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedArtifact that = (JpaVersionedArtifact) o; - return id == that.id && Objects.equals(artifact, that.artifact) && Objects.equals( - version, that.version) && Objects.equals(changelog, that.changelog); - } - - @Override - public int hashCode() { - return Objects.hash(id, artifact, version, changelog); - } -} diff --git a/versions-impl/src/main/resources/META-INF/persistence.xml b/versions-impl/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index 344130a1..00000000 --- a/versions-impl/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - org.hibernate.jpa.HibernatePersistenceProvider - DefaultDS - org.spongepowered.downloads.versions.server.readside.JpaArtifactVersion - org.spongepowered.downloads.versions.server.readside.JpaArtifact - org.spongepowered.downloads.versions.server.readside.JpaArtifactTag - org.spongepowered.downloads.versions.server.readside.JpaArtifactRegexRecommendation - org.spongepowered.downloads.versions.server.readside.JpaVersionedArtifactAsset - org.spongepowered.downloads.versions.worker.readside.model.JpaVersionedArtifact - org.spongepowered.downloads.versions.worker.readside.model.JpaVersionChangelog - true - - - - - - - - - diff --git a/versions-impl/src/main/resources/application.conf b/versions-impl/src/main/resources/application.conf deleted file mode 100644 index 4e842b41..00000000 --- a/versions-impl/src/main/resources/application.conf +++ /dev/null @@ -1,42 +0,0 @@ -play.modules.enabled = ${play.modules.enabled} [ - org.spongepowered.downloads.versions.server.VersionsModule, - org.spongepowered.downloads.versions.worker.WorkerModule -] - -db.default { - driver = "org.postgresql.Driver" - url = "jdbc:postgresql://localhost:5432/default" - url = ${?POSTGRES_URL} - username = admin - username = ${?POSTGRES_USERNAME} - password = password - password = ${?POSTGRES_PASSWORD} -} - -jdbc-defaults.slick.profile = "slick.jdbc.PostgresProfile$" - -lagom.persistence.jpa { - # This must match the name in persistence.xml - persistence-unit = "default" -} -akka.serialization.jackson { - jackson-modules += "io.vavr.jackson.datatype.VavrModule" -} -akka.actor.allow-java-serialization = true - -play.http.parser.maxMemoryBuffer = 200k - -akka { - extensions = ${akka.extensions} [ - "org.spongepowered.downloads.versions.worker.VersionExtension" - ] -} - -play.filters.disabled += "play.filters.csrf.CSRFFilter" - -play.filters.enabled += "play.filters.cors.CORSFilter" -play.filters.cors { - pathPrefixes = ["/versions"] - allowedHttpMethods = ["GET", "POST", "PATCH"] - preflightMaxAge = 3 days -} diff --git a/versions-impl/src/main/resources/logback.xml b/versions-impl/src/main/resources/logback.xml deleted file mode 100644 index dbe42030..00000000 --- a/versions-impl/src/main/resources/logback.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - System.out - - %date{hh:MM:ss.SSS} [%level] [%thread] [%logger{5}/%marker] - %coloredLevel %msg%n - - - - - 8192 - true - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/versions-impl/src/main/resources/reference.conf b/versions-impl/src/main/resources/reference.conf deleted file mode 100644 index ec0e520b..00000000 --- a/versions-impl/src/main/resources/reference.conf +++ /dev/null @@ -1,8 +0,0 @@ -akka.cluster.roles = ${akka.cluster.roles} ["asset-fetcher", "file-extractor", "refresher", "commit-resolver"] -systemofadownload.versions { - commit-fetch { - pool-size = 8 - parallelism = 4 - timeout = "1h" - } -} diff --git a/versions-impl/src/test/java/org/spongepowered/downloads/test/server/collection/VersionedArtifactAggregateTest.java b/versions-impl/src/test/java/org/spongepowered/downloads/test/server/collection/VersionedArtifactAggregateTest.java deleted file mode 100644 index bdcbeb20..00000000 --- a/versions-impl/src/test/java/org/spongepowered/downloads/test/server/collection/VersionedArtifactAggregateTest.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.spongepowered.downloads.test.server.collection; - - -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.control.Option; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Test; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.versions.server.domain.ACEvent; -import org.spongepowered.downloads.versions.server.domain.State; - -import java.net.URISyntaxException; -import java.net.URL; -import java.util.Optional; - -public class VersionedArtifactAggregateTest { - - @Test - public void emptyStateTransition() { - final State empty = State.empty(); - Assertions.assertFalse(empty.isRegistered()); - } - - @Test - public void emptyStateRegistration() { - final State example = State.empty().register( - new ACEvent.ArtifactCoordinatesUpdated(new ArtifactCoordinates("com.example", "example"))); - - Assertions.assertTrue(example.isRegistered()); - } - - @Test - public void stateRegistrationWithVersion() throws URISyntaxException { - final ArtifactCoordinates exampleCoordinates = new ArtifactCoordinates("com.example", "example"); - State.ACState example = State.empty().register(new ACEvent.ArtifactCoordinatesUpdated(exampleCoordinates)); - final var exampleVersion = exampleCoordinates.version("0.0.1"); - final URL exampleJar = this.getClass().getClassLoader().getResource("test-jar.jar"); - Assumptions.assumeTrue(exampleJar != null); - Assertions.assertNotNull(exampleJar, "Example jar is missing, needed for various tests"); - final var exampleArtifact = new Artifact(Optional.of("universal"), exampleJar.toURI(), "foo", "bar", ".jar"); - example = example.withAddedArtifacts(exampleVersion, List.of(exampleArtifact)); - Assertions.assertFalse(example.versionedArtifacts().isEmpty(), "Should have a new versioned artifact"); - final Option> artifacts = example.versionedArtifacts().get(exampleVersion.version); - Assertions.assertFalse(artifacts.isEmpty(), "The artifact list for " + exampleVersion.version + " should be non-empty"); - Assertions.assertEquals(artifacts.get(), List.of(exampleArtifact), "List should be equal"); - - final var exampleNoClassifier = new Artifact(Optional.empty(), exampleJar.toURI(), "foo", "bar", ".jar"); - example = example.withAddedArtifacts(exampleVersion, List.of(exampleNoClassifier)); - Assertions.assertFalse(example.versionedArtifacts().isEmpty(), "Should have a new versioned artifact"); - final Option> newArtifacts = example.versionedArtifacts().get(exampleVersion.version); - Assertions.assertFalse(newArtifacts.isEmpty(), "The artifact list for " + exampleVersion.version + " should be non-empty"); - Assertions.assertEquals(newArtifacts.get(), List.of(exampleArtifact, exampleNoClassifier), "List should be equal"); - } - - @Test - public void stateReordering() { - final ArtifactCoordinates exampleCoordinates = new ArtifactCoordinates("com.example", "example"); - State.ACState example = State.empty().register(new ACEvent.ArtifactCoordinatesUpdated(exampleCoordinates)); - final var acEvents = example.addVersion(exampleCoordinates.version("0.0.1")); - Assertions.assertEquals(acEvents.size(), 1, "Should have one event"); - final var zero1 = example.withVersion("0.0.1"); - final var newEvents = zero1.addVersion(exampleCoordinates.version("0.0.2")); - Assertions.assertEquals(newEvents.size(), 1, "0.0.2 should be the only new event"); - final var zero3 = zero1.withVersion("0.0.3"); - final var addingZero2 = zero3.addVersion(exampleCoordinates.version("0.0.2")); - Assertions.assertEquals(2, addingZero2.size(), "Should have 1 event"); - Assertions.assertEquals(new ACEvent.ArtifactVersionRegistered(exampleCoordinates.version("0.0.2"), 1), addingZero2.get(0),"Should have the new event"); - Assertions.assertEquals(new ACEvent.ArtifactVersionsResorted(exampleCoordinates, HashMap.of("0.0.2", 1, "0.0.3", 2)), addingZero2.get(1),"Should have the new event"); - } - -} diff --git a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/CommitExtractorTest.java b/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/CommitExtractorTest.java deleted file mode 100644 index 52fc46ac..00000000 --- a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/CommitExtractorTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.spongepowered.downloads.test.versions.worker; - - -import akka.actor.testkit.typed.javadsl.TestKitJunitResource; -import akka.actor.testkit.typed.javadsl.TestProbe; -import akka.persistence.testkit.javadsl.EventSourcedBehaviorTestKit; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.worker.actor.artifacts.CommitExtractor; -import org.spongepowered.downloads.versions.worker.actor.artifacts.PotentiallyUsableAsset; - -import java.net.URISyntaxException; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class CommitExtractorTest { - - public static final TestKitJunitResource testkit = new TestKitJunitResource(EventSourcedBehaviorTestKit.config()); - - @Test - public void VerifyCommitExtraction() throws URISyntaxException { - final TestProbe replyTo = testkit.createTestProbe(); - final var extractor = testkit.spawn(CommitExtractor.extractCommitFromAssets()); - final var coordinates = MavenCoordinates.parse("org.spongepowered.downloads:test-bin:0.0.1"); - - final var testJarPath = this.getClass().getClassLoader().getResource("test-jar.jar"); - Assertions.assertNotNull(testJarPath, "test-jar.jar is null"); - final var asset = new PotentiallyUsableAsset(coordinates, ".jar", testJarPath.toURI()); - extractor.tell(new CommitExtractor.AttemptFileCommit(asset, replyTo.ref())); - - replyTo.expectMessage(new CommitExtractor.DiscoveredCommitFromFile("d838fee5d8e834ba9fd4d1c4fe0f8214d6dc90fc", asset)); - } - - @Test - public void InvalidCommitFailure() throws URISyntaxException { - final TestProbe replyTo = testkit.createTestProbe(); - final var extractor = testkit.spawn(CommitExtractor.extractCommitFromAssets()); - final var coordinates = MavenCoordinates.parse("org.spongepowered.downloads:test-bin:0.0.1"); - final var testJarPath = this.getClass().getClassLoader().getResource("bad-commit-test-jar.jar"); - Assertions.assertNotNull(testJarPath, "bad-commit-test-jar.jar is null"); - final var asset = new PotentiallyUsableAsset(coordinates, ".jar", testJarPath.toURI()); - extractor.tell(new CommitExtractor.AttemptFileCommit(asset, replyTo.ref())); - - replyTo.expectMessageClass(CommitExtractor.NoCommitsFoundForFile.class); - } - - @Test - public void NoCommitFailure() throws URISyntaxException { - final TestProbe replyTo = testkit.createTestProbe(); - final var extractor = testkit.spawn(CommitExtractor.extractCommitFromAssets()); - final var coordinates = MavenCoordinates.parse("org.spongepowered.downloads:test-bin:0.0.1"); - final var testJarPath = this.getClass().getClassLoader().getResource("no-commit-test-jar.jar"); - Assertions.assertNotNull(testJarPath, "no-commit-test-jar.jar is null"); - final var asset = new PotentiallyUsableAsset(coordinates, ".jar", testJarPath.toURI()); - extractor.tell(new CommitExtractor.AttemptFileCommit(asset, replyTo.ref())); - - replyTo.expectMessageClass(CommitExtractor.NoCommitsFoundForFile.class); - } - -} diff --git a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/FileCollectionOperatorTest.java b/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/FileCollectionOperatorTest.java deleted file mode 100644 index 2bb12db0..00000000 --- a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/FileCollectionOperatorTest.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.spongepowered.downloads.test.versions.worker; - -import akka.actor.testkit.typed.javadsl.FishingOutcomes; -import akka.actor.testkit.typed.javadsl.TestKitJunitResource; -import akka.persistence.testkit.javadsl.EventSourcedBehaviorTestKit; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import io.vavr.collection.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.worker.actor.artifacts.CommitExtractor; -import org.spongepowered.downloads.versions.worker.actor.artifacts.FileCollectionOperator; -import org.spongepowered.downloads.versions.worker.actor.artifacts.PotentiallyUsableAsset; - -import java.io.File; -import java.net.URISyntaxException; -import java.time.Duration; - -public class FileCollectionOperatorTest { - - private TestKitJunitResource testKit; - - private static Config config; - - @BeforeAll - public static void setupConfig() { - final var resource = VersionedArtifactEntityTest.class.getClassLoader().getResource("application-test.conf"); - Assertions.assertNotNull(resource); - final var file = new File(resource.getFile()); - config = ConfigFactory.parseFile(file); - } - - @BeforeEach - public void setup() { - this.testKit = new TestKitJunitResource(config.resolve() // Resolve the config first - .withFallback(EventSourcedBehaviorTestKit.config())); - } - - @AfterEach - public void teardown() { - testKit.system().terminate(); - } - - @Test - public void testKickoffFileCollection() throws URISyntaxException { - final var commandProbe = this.testKit.createTestProbe(); - final var responseProbe = this.testKit.createTestProbe(); - final var requestBehavior = FileCollectionOperator.scanJarFilesForCommit( - commandProbe.ref(), responseProbe.ref()); - final var testJarPath = this.getClass().getClassLoader().getResource("test-jar.jar"); - Assertions.assertNotNull(testJarPath, "test-jar.jar is missing"); - final var spawn = this.testKit.spawn(requestBehavior); - final var coordinates = MavenCoordinates.parse("com.example:example:1.0.0"); - final var asset = new PotentiallyUsableAsset( - coordinates, ".jar", testJarPath.toURI()); - spawn.tell(new FileCollectionOperator.TryFindingCommitForFiles(List.of(asset), coordinates)); - commandProbe.fishForMessage(Duration.ofSeconds(10), msg -> { - if (!(msg instanceof CommitExtractor.AttemptFileCommit afc)) { - return FishingOutcomes.fail("got a different command"); - } - afc.ref().tell(new CommitExtractor.NoCommitsFoundForFile(afc.asset())); - return FishingOutcomes.complete(); - }); - responseProbe.fishForMessage(Duration.ofSeconds(10), msg -> { - if (!(msg instanceof CommitExtractor.NoCommitsFoundForFile)) { - return FishingOutcomes.fail("got a different response than test expected"); - } - return FishingOutcomes.complete(); - }); - } -} diff --git a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/VersionedArtifactEntityTest.java b/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/VersionedArtifactEntityTest.java deleted file mode 100644 index 470577ce..00000000 --- a/versions-impl/src/test/java/org/spongepowered/downloads/test/versions/worker/VersionedArtifactEntityTest.java +++ /dev/null @@ -1,131 +0,0 @@ -package org.spongepowered.downloads.test.versions.worker; - -import akka.Done; -import akka.actor.testkit.typed.javadsl.FishingOutcomes; -import akka.actor.testkit.typed.javadsl.TestKitJunitResource; -import akka.actor.testkit.typed.javadsl.TestProbe; -import akka.actor.typed.receptionist.Receptionist; -import akka.cluster.sharding.typed.javadsl.ClusterSharding; -import akka.cluster.sharding.typed.javadsl.EntityContext; -import akka.persistence.testkit.javadsl.EventSourcedBehaviorTestKit; -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import io.vavr.collection.List; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.worker.actor.artifacts.FileCollectionOperator; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.ArtifactEvent; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.ArtifactState; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactCommand; -import org.spongepowered.downloads.versions.worker.domain.versionedartifact.VersionedArtifactEntity; - -import java.io.File; -import java.net.URISyntaxException; -import java.time.Duration; -import java.util.Optional; - -public class VersionedArtifactEntityTest { - - private TestKitJunitResource testKit; - private EventSourcedBehaviorTestKit eventSourcedTestKit; - - private static Config config; - private TestProbe fileOperatorProbe; - - @BeforeAll - public static void setupConfig() { - final var resource = VersionedArtifactEntityTest.class.getClassLoader().getResource("application-test.conf"); - Assertions.assertNotNull(resource); - final var file = new File(resource.getFile()); - config = ConfigFactory.parseFile(file); - } - - @BeforeEach - public void setup() { - this.testKit = new TestKitJunitResource(config.resolve() // Resolve the config first - .withFallback(EventSourcedBehaviorTestKit.config())); - testKit.system().log().info("Starting test"); - final var probe = this.testKit.createTestProbe(ClusterSharding.ShardCommand.class); - - final var ctx = new EntityContext<>( - VersionedArtifactEntity.ENTITY_TYPE_KEY, - "com.example:example:1.0.0", - probe.ref() - ); - this.fileOperatorProbe = this.testKit.createTestProbe(); - this.testKit.system().receptionist().tell(Receptionist.register(FileCollectionOperator.KEY, fileOperatorProbe.ref())); - - this.eventSourcedTestKit = - EventSourcedBehaviorTestKit.create(testKit.system(), VersionedArtifactEntity.create(ctx)); - } - - @AfterEach - public void teardown() { - testKit.system().log().info("Finishing test"); - testKit.system().terminate(); - } - - @Test - public void testEmptyState() { - final var state = this.eventSourcedTestKit.getState(); - Assertions.assertTrue(state.artifacts().isEmpty()); - Assertions.assertTrue(state.repositories().isEmpty()); - Assertions.assertFalse(state.needsArtifactScan()); - Assertions.assertFalse(state.needsCommitResolution()); - } - - @Test - public void testRegistration() { - final var state = this.eventSourcedTestKit - .runCommand(ref -> new VersionedArtifactCommand.Register( - MavenCoordinates.parse("com.example:example:1.0.0"), ref) - ) - .state(); - Assertions.assertTrue(state.artifacts().isEmpty()); - Assertions.assertTrue(state.repositories().isEmpty()); - Assertions.assertFalse(state.needsArtifactScan()); - Assertions.assertFalse(state.needsCommitResolution()); - } - - @Test - public void testAssetRegistration() throws URISyntaxException { - final var coordinates = MavenCoordinates.parse("com.example:example:1.0.0"); - this.eventSourcedTestKit - .runCommand(ref -> new VersionedArtifactCommand.Register( - coordinates, ref) - ) - .state(); - final var testJarPath = this.getClass().getClassLoader().getResource("test-jar.jar"); - Assertions.assertNotNull(testJarPath, "test-jar.jar isn't available, should be checked in"); - final var downloadUrl = testJarPath.toURI(); - final var artifact = new Artifact(Optional.empty(), downloadUrl, "test-jar.jar", "jar", "jar"); - final var newState = this.eventSourcedTestKit - .runCommand(ref -> new VersionedArtifactCommand.AddAssets(coordinates, List.of(artifact), ref)) - .state(); - this.fileOperatorProbe.fishForMessage(Duration.ofSeconds(10), r -> { - if (!(r instanceof FileCollectionOperator.TryFindingCommitForFiles tfcf)) { - return FishingOutcomes.fail("expected a try to commit"); - } - final var files = tfcf.files(); - if (files.size() != 1) { - return FishingOutcomes.fail("Expected 1 file only"); - } - final var file = files.get(0); - if (!file.downloadURL().equals(downloadUrl)) { - return FishingOutcomes.fail("Expected the same download url"); - } - return FishingOutcomes.complete(); - }); - Assertions.assertFalse(newState.artifacts().isEmpty()); - Assertions.assertTrue(newState.repositories().isEmpty()); - Assertions.assertTrue(newState.needsArtifactScan()); - Assertions.assertFalse(newState.needsCommitResolution()); - } - -} - diff --git a/versions-impl/src/test/java/org/spongepowered/downloads/versions/RegexValidations.java b/versions-impl/src/test/java/org/spongepowered/downloads/versions/RegexValidations.java deleted file mode 100644 index b2c16cb7..00000000 --- a/versions-impl/src/test/java/org/spongepowered/downloads/versions/RegexValidations.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import io.vavr.collection.List; -import io.vavr.control.Try; -import org.junit.jupiter.api.Test; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public final class RegexValidations { - - @Test - public void ValidateTagVersionRegistration() { - final var patternRegex = Pattern.compile( - "^\\d\\.\\d{1,2}(\\.\\d{1,2})?(-((rc)|(pre))\\d)?-(\\d{1,2}\\.\\d{1,2})\\.\\d$" - ); - final var valids = List.of("1.12.2-7.3.0", "1.16.5-8.0.0", "1.9-4.1.0"); - final var invalids = List.of("1.12.2-7.3.0-RC1723", "1.16.5-8.0.0-RC495", "1.12.2-2838-7.3.1-RC3482"); - final var regex = Try.of(() -> patternRegex); - final var validSuccess = valids - .map(valid -> regex.map(pattern -> pattern.matcher(valid)) - .mapTry(Matcher::find).getOrElse(false) - ) - .filter(b -> !b) - .isEmpty(); - final var invalidSuccess = invalids - .map(invalid -> regex.map(pattern -> pattern.matcher(invalid)) - .mapTry(Matcher::find) - .getOrElse(() -> true) - ) - .filter(b -> !b) - .isEmpty(); - - assertTrue(validSuccess); - assertFalse(invalidSuccess); - } -} diff --git a/versions-impl/src/test/resources/application-test.conf b/versions-impl/src/test/resources/application-test.conf deleted file mode 100644 index 5da7a5df..00000000 --- a/versions-impl/src/test/resources/application-test.conf +++ /dev/null @@ -1,11 +0,0 @@ -akka.serialization.jackson { - # The Jackson JSON serializer will register these modules. - jackson-modules += "akka.serialization.jackson.AkkaJacksonModule" - jackson-modules += "akka.serialization.jackson.AkkaTypedJacksonModule" - # AkkaStreamsModule optionally included if akka-streams is in classpath - jackson-modules += "akka.serialization.jackson.AkkaStreamJacksonModule" - jackson-modules += "com.fasterxml.jackson.module.paramnames.ParameterNamesModule" - jackson-modules += "com.fasterxml.jackson.datatype.jdk8.Jdk8Module" - jackson-modules += "com.fasterxml.jackson.module.scala.DefaultScalaModule" - jackson-modules += "io.vavr.jackson.datatype.VavrModule" -} diff --git a/versions-impl/src/test/resources/bad-commit-test-jar.jar b/versions-impl/src/test/resources/bad-commit-test-jar.jar deleted file mode 100644 index f6c8294b9e453e3c47d67330615d33e0ff676186..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 359 zcmWIWW@Zs#;Nak3u-RE2%zy+q8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gtA{z_x69U)*nQ*O0J`C_?Wdo^V N0>WA#eFel}008IxP)q;- diff --git a/versions-impl/src/test/resources/manifest b/versions-impl/src/test/resources/manifest deleted file mode 100644 index 919c25c6..00000000 --- a/versions-impl/src/test/resources/manifest +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Git-Commit: d838fee5d8e834ba9fd4d1c4fe0f8214d6dc90fc - diff --git a/versions-impl/src/test/resources/no-commit-test-jar.jar b/versions-impl/src/test/resources/no-commit-test-jar.jar deleted file mode 100644 index 0552202ff560a57415d1c517485db7b38a3945ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 339 zcmWIWW@Zs#;Nak32-#U4%zy+q8CV#6T|*poJ^kGD|D9rBU}gyLX6FE@V1gy$z%bti&NtyO6>r>IkB1W(i9B(kqa|Bx80K@^_j7%a7s6K!> s599+>0Cy6|Kj>PKjRm~0p06A4nT>ph6a*M9%x$SpxWdRJ_Vt#p^y4S5`tHOQbxv9K;KBXZ zzZ<@LbQ^5Vo4>_$<>{2B&rx@m-rhM)sn&>t+2EpJV?xJz6C*bsOQXs*=Z=a)HdW`M z&Pp%!%UFNEzPRij>*n20x3B3v!}|Kx)8KDWnO~VfZvGU!zNZN2qB0;3@MdHZVLyjhY`RQ$b@S}@_v9fD;r1^6A;z{>5m`|0|1|^WuyQA diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/VersionsQueryService.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/VersionsQueryService.java deleted file mode 100644 index 6de09b6e..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/VersionsQueryService.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.Descriptor; -import com.lightbend.lagom.javadsl.api.Service; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.transport.Method; -import org.spongepowered.downloads.versions.query.api.models.QueryVersions; - -import java.util.Optional; - -public interface VersionsQueryService extends Service { - - ServiceCall artifactVersions( - String groupId, String artifactId, Optional tags, Optional limit, - Optional offset, Optional recommended - ); - - ServiceCall latestArtifact( - String groupId, String artifactId, Optional tags, Optional recommended - ); - - ServiceCall versionDetails( - String groupId, String artifactId, String version - ); - - @Override - default Descriptor descriptor() { - return Service.named("version-query") - .withCalls( - Service.restCall( - Method.GET, "/versions-query/groups/:groupId/artifacts/:artifactId/versions?tags&limit&offset&recommended", - this::artifactVersions - ), - Service.restCall( - Method.GET, "/versions-query/groups/:groupId/artifacts/:artifactId/versions/:version", - this::versionDetails - ), - Service.restCall( - Method.GET, "/versions-query/groups/:groupId/artifacts/:artifactId/latest?tags&recommended", - this::latestArtifact - ) - ) - .withAutoAcl(true); - } -} diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryLatest.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryLatest.java deleted file mode 100644 index c0715262..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryLatest.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api.models; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.util.Optional; - -public interface QueryLatest { - - @JsonSerialize - record VersionInfo(MavenCoordinates coordinates, - List assets, - Map tagValues, - Optional commit, - boolean recommended - ) { - } - -} diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryVersions.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryVersions.java deleted file mode 100644 index ec03727a..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/QueryVersions.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import java.util.Optional; - -public interface QueryVersions { - - @JsonSerialize - record VersionInfo(@JsonProperty Map artifacts, int offset, int limit, int size) { - - @JsonCreator - public VersionInfo { - } - } - - @JsonSerialize - record VersionDetails( - @JsonProperty("coordinates") MavenCoordinates coordinates, - @JsonProperty("commit") Optional commit, - @JsonProperty("assets") List components, - @JsonProperty("tags") Map tagValues, - @JsonProperty("recommended") boolean recommended - ) { - } - -} diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/TagCollection.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/TagCollection.java deleted file mode 100644 index ee04816c..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/TagCollection.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api.models; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.vavr.collection.Map; - -@JsonSerialize -public record TagCollection( - Map tagValues, - boolean recommended -) { -} diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedChangelog.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedChangelog.java deleted file mode 100644 index 7a1b714b..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedChangelog.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api.models; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import io.vavr.collection.List; - -import java.net.URI; - -@JsonDeserialize -public final record VersionedChangelog( - List commits, - @JsonInclude(JsonInclude.Include.NON_DEFAULT) boolean processing -) { - - @JsonCreator - public VersionedChangelog { - } - - @JsonDeserialize - public final record IndexedCommit( - VersionedCommit commit, - List submoduleCommits - ) { - @JsonCreator - public IndexedCommit { - } - } - - @JsonDeserialize - public final record Submodule( - String name, - URI gitRepository, - List commits - ) { - @JsonCreator - public Submodule { - } - } - -} diff --git a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedCommit.java b/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedCommit.java deleted file mode 100644 index 6f11529b..00000000 --- a/versions-query-api/src/main/java/org/spongepowered/downloads/versions/query/api/models/VersionedCommit.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.api.models; - - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; - -import java.net.URI; -import java.time.ZonedDateTime; - -@JsonDeserialize -public record VersionedCommit( - String message, - String body, - String sha, - Author author, - Commiter commiter, - URI link, - ZonedDateTime commitDate -) { - - @JsonCreator - public VersionedCommit { - } - - @JsonDeserialize - public record Author( - String name, - String email - ) { - @JsonCreator - public Author { - } - } - - @JsonDeserialize - public record Commiter( - String name, - String email - ) { - @JsonCreator - public Commiter { - } - } -} - diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryModule.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryModule.java deleted file mode 100644 index 6b75f6be..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryModule.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl; - -import com.google.inject.AbstractModule; -import com.lightbend.lagom.javadsl.server.ServiceGuiceSupport; -import org.spongepowered.downloads.versions.query.api.VersionsQueryService; - -public class VersionQueryModule extends AbstractModule implements ServiceGuiceSupport { - - @Override - protected void configure() { - this.bindService(VersionsQueryService.class, VersionQueryServiceImpl.class); - } -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryServiceImpl.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryServiceImpl.java deleted file mode 100644 index 1a4580ec..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/VersionQueryServiceImpl.java +++ /dev/null @@ -1,324 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl; - -import akka.NotUsed; -import com.lightbend.lagom.javadsl.api.ServiceCall; -import com.lightbend.lagom.javadsl.api.deser.ExceptionMessage; -import com.lightbend.lagom.javadsl.api.transport.BadRequest; -import com.lightbend.lagom.javadsl.api.transport.NotFound; -import com.lightbend.lagom.javadsl.api.transport.TransportErrorCode; -import com.lightbend.lagom.javadsl.api.transport.TransportException; -import com.lightbend.lagom.javadsl.persistence.jpa.JpaSession; -import io.vavr.Tuple; -import io.vavr.Tuple2; -import io.vavr.Value; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.apache.maven.artifact.versioning.ComparableVersion; -import org.spongepowered.downloads.artifact.api.ArtifactCoordinates; -import org.spongepowered.downloads.versions.query.api.VersionsQueryService; -import org.spongepowered.downloads.versions.query.api.models.QueryVersions; -import org.spongepowered.downloads.versions.query.api.models.VersionedChangelog; -import org.spongepowered.downloads.versions.query.impl.models.JpaTaggedVersion; -import org.spongepowered.downloads.versions.query.impl.models.JpaVersionedArtifactView; - -import javax.inject.Inject; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceException; -import java.util.Comparator; -import java.util.Locale; -import java.util.Optional; -import java.util.regex.Pattern; - -public record VersionQueryServiceImpl(JpaSession session) - implements VersionsQueryService { - public static final Pattern VALID_COORDINATE_PORTION = Pattern.compile("^[\\w.-]+$"); - - @Inject - public VersionQueryServiceImpl { - } - - @Override - public ServiceCall artifactVersions( - final String groupId, - final String artifactId, - final Optional tags, - final Optional limit, - final Optional offset, - final Optional recommended - ) { - return request -> { - validateCoordinates(groupId, artifactId); - return this.session.withTransaction( - t -> { - if (groupId.isBlank() || artifactId.isBlank()) { - throw new NotFound("unknown artifact"); - } - try { - final var query = new VersionQuery(groupId, artifactId, tags, limit, offset, recommended); - - if (query.tags.isEmpty()) { - return getUntaggedVersions(t, query); - } - return getTaggedVersions(t, query); - } catch (PersistenceException e) { - throw new TransportException( - TransportErrorCode.InternalServerError, new ExceptionMessage("Internal Server Error", "")); - } - }); - }; - } - - @Override - public ServiceCall latestArtifact( - final String groupId, - final String artifactId, - final Optional tags, - final Optional recommended - ) { - return request -> { - validateCoordinates(groupId, artifactId); - - return this.session.withTransaction( - t -> { - if (groupId.isBlank() || artifactId.isBlank()) { - throw new NotFound("unknown artifact"); - } - try { - final var query = new VersionQuery(groupId, artifactId, tags, recommended.orElse(false)); - - final var info = query.tags.isEmpty() - ? getUntaggedVersions(t, query) - : getTaggedVersions(t, query); - final var version = info.artifacts().keySet().head(); - final var coordinates = query.coordinates.version(version); - final var artifacts = t.createNamedQuery( - "VersionedArtifactView.findExplicitly", JpaVersionedArtifactView.class) - .setParameter("groupId", coordinates.groupId) - .setParameter("artifactId", coordinates.artifactId) - .setParameter("version", coordinates.version) - .getResultList(); - if (artifacts.isEmpty()) { - throw new NotFound("versioned artifact not found"); - } - final var versionedArtifact = artifacts.get(0); - final Optional commit = versionedArtifact.asVersionedCommit(); - return new QueryVersions.VersionDetails( - coordinates, commit, versionedArtifact.asArtifactList(), - versionedArtifact.getTagValues(), versionedArtifact.isRecommended() - - ); - } catch (PersistenceException e) { - e.printStackTrace(); - throw new TransportException( - TransportErrorCode.InternalServerError, new ExceptionMessage("Internal Server Error", "")); - } - }); - }; - } - - @Override - public ServiceCall versionDetails( - final String groupId, final String artifactId, final String version - ) { - return notUsed -> { - validateCoordinates(groupId, artifactId); - return this.session.withTransaction(em -> { - final var sanitizedGroupId = groupId.toLowerCase(Locale.ROOT).trim(); - final var sanitizedArtifactId = artifactId.toLowerCase(Locale.ROOT).trim(); - final var sanitizedVersion = version.trim(); - return em.createNamedQuery( - "VersionedArtifactView.findFullVersionDetails", JpaVersionedArtifactView.class) - .setParameter("groupId", sanitizedGroupId) - .setParameter("artifactId", sanitizedArtifactId) - .setParameter("version", sanitizedVersion) - .getResultList() - .stream() - .findFirst() - .map(versionView -> { - final var coordinates = versionView.asMavenCoordinates(); - final var assets = versionView.asArtifactList(); - final var tags = versionView.getTagValues(); - final var commit = versionView.asVersionedCommit(); - return new QueryVersions.VersionDetails( - coordinates, commit, assets, tags, versionView.isRecommended()); - }) - .orElseThrow(() -> new NotFound("group or artifact or version not found")); - }); - }; - } - - private static record ParameterizedTag(String tagName, String tagValue) { - } - - private static record VersionQuery( - ArtifactCoordinates coordinates, - int limit, - int offset, - Optional recommended, - List tags) { - - VersionQuery( - String groupId, String artifactId, - Optional tags, - boolean recommended - ) { - this( - new ArtifactCoordinates(groupId.toLowerCase(Locale.ROOT), artifactId.toLowerCase(Locale.ROOT)), - 25, - 0, - Optional.of(recommended), - gatherTags(tags) - ); - } - - VersionQuery( - String groupId, String artifactId, - final Optional tags, - final Optional limitOpt, - final Optional offsetOpt, - final Optional recommended - ) { - this( - new ArtifactCoordinates(groupId.toLowerCase(Locale.ROOT), artifactId.toLowerCase(Locale.ROOT)), - limitOpt.map(l -> Math.min(Math.max(l, 1), 25)).orElse(25), - offsetOpt.map(o -> Math.max(o, 0)).orElse(0), - recommended, - gatherTags(tags) - ); - } - - private static List gatherTags(Optional tags) { - return tags.map(rw -> rw.split(",")) - .map(List::of).orElseGet(List::of) - .map(tag -> tag.split(":")) - .filter(array -> array.length == 2) - .map(array -> new ParameterizedTag(array[0].toLowerCase(Locale.ROOT), array[1].strip())); - } - } - - private static QueryVersions.VersionInfo getUntaggedVersions( - EntityManager em, VersionQuery query - ) { - final int totalCount = query.recommended - .map(isRecommended -> em.createNamedQuery("VersionedArtifactView.recommendedCount", Long.class) - .setParameter("recommended", isRecommended) - ) - .orElseGet(() -> em.createNamedQuery("VersionedArtifactView.count", Long.class)) - .setParameter("groupId", query.coordinates.groupId()) - .setParameter("artifactId", query.coordinates.artifactId()) - .getSingleResult().intValue(); - if (totalCount <= 0) { - throw new NotFound("group or artifact not found"); - } - final var untaggedVersions = query.recommended - .map(isRecommended -> em.createNamedQuery( - "VersionedArtifactView.findByArtifactAndRecommendation", - JpaVersionedArtifactView.class - ) - .setParameter("recommended", isRecommended) - ) - .orElseGet(() -> em.createNamedQuery( - "VersionedArtifactView.findByArtifact", JpaVersionedArtifactView.class - )) - .setParameter("groupId", query.coordinates.groupId()) - .setParameter("artifactId", query.coordinates.artifactId()) - .setMaxResults(query.offset + query.limit) - .getResultList(); - final var mappedByCoordinates = untaggedVersions.stream() - .collect(List.collector()) - .drop(query.offset) - .take(query.limit); - final var versionsWithTags = mappedByCoordinates - .toSortedMap( - Comparator.comparing(ComparableVersion::new).reversed(), - JpaVersionedArtifactView::version, - JpaVersionedArtifactView::asTagCollection - ); - return new QueryVersions.VersionInfo(versionsWithTags, query.offset, query.limit, totalCount); - } - - private static QueryVersions.VersionInfo getTaggedVersions( - EntityManager em, VersionQuery query - ) { - // Otherwise, get the tagged versions that match the given tags - // which is a little advanced, because we'll have to literally gather the versioned values - // that match the tags, then do a shake down - final var map = query.tags.map(tag -> query.recommended.map( - recommended -> em.createNamedQuery( - "TaggedVersion.findMatchingTagValuesAndRecommendation", - JpaTaggedVersion.class - ) - .setParameter("recommended", recommended)) - .orElseGet(() -> em.createNamedQuery( - "TaggedVersion.findAllMatchingTagValues", JpaTaggedVersion.class - )) - .setParameter("groupId", query.coordinates.groupId()) - .setParameter("artifactId", query.coordinates.artifactId()) - .setParameter("tagName", tag.tagName) - .setParameter("tagValue", tag.tagValue + "%") - .getResultStream() - .map(tv -> Tuple.of(tv, Tuple.of(tv.getTagName(), tv.getTagValue()))) - .collect(List.collector()) - ).flatMap(Value::toStream); - if (map.isEmpty()) { - throw new NotFound("group or artifact not found"); - } - var versionedTags = HashMap.>>empty(); - - for (final Tuple2> tagged : map) { - versionedTags = versionedTags.put( - tagged._1.getMavenVersion(), - tagged.map(JpaTaggedVersion::getVersion, HashMap::of), - (o, i) -> Tuple.of(o._1, o._2.merge(i._2)) - ); - } - final var wantedTagNames = query.tags.map(ParameterizedTag::tagName); - final var validatedVersion = versionedTags - .filter((coordinates, tagMap) -> tagMap._2.keySet().containsAll(wantedTagNames)); - final var versionsForQuery = validatedVersion - .toSortedMap(Comparator.comparing(ComparableVersion::new).reversed(), tuple -> tuple._1, tuple -> tuple._2) - .drop(query.offset) - .take(query.limit); - return new QueryVersions.VersionInfo( - versionsForQuery.mapValues(tuple -> tuple._1.asTagCollection()), query.offset, query.limit, - validatedVersion.size() - ); - } - - private static void validateCoordinates(final String groupID, final String artifactID) { - final String sanitizedGroupId = groupID.toLowerCase(Locale.ROOT); - if (!VALID_COORDINATE_PORTION.matcher(sanitizedGroupId).matches()) { - throw new BadRequest("Invalid groupId: " + groupID); - } - final String sanitizedArtifactId = artifactID.toLowerCase(Locale.ROOT); - if (!VALID_COORDINATE_PORTION.matcher(sanitizedArtifactId).matches()) { - throw new BadRequest("Invalid artifactId: " + artifactID); - } - } - -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaTaggedVersion.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaTaggedVersion.java deleted file mode 100644 index 11dddae7..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaTaggedVersion.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl.models; - -import org.hibernate.annotations.Immutable; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.ManyToOne; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Objects; - -@Entity(name = "TaggedVersion") -@Immutable -@Table(name = "versioned_tags", schema = "version") -@NamedQueries({ - @NamedQuery(name = "TaggedVersion.findAllForVersion", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId and view.mavenArtifactId = :artifactId and view.version = :version - """ - ), - @NamedQuery( - name = "TaggedVersion.findAllMatchingTagValues", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId - and view.mavenArtifactId = :artifactId - and view.tagName = :tagName - and view.tagValue like :tagValue - """ - ), - @NamedQuery( - name = "TaggedVersion.findMatchingTagValuesAndRecommendation", - query = - """ - select view from TaggedVersion view - where view.mavenGroupId = :groupId - and view.mavenArtifactId = :artifactId - and view.tagName = :tagName - and view.tagValue like :tagValue - and (view.versionView.recommended = :recommended or view.versionView.manuallyRecommended = :recommended) - """ - ) -}) -public class JpaTaggedVersion implements Serializable { - - @Id - @Column(name = "version_id", updatable = false) - private long versionId; - - @Id - @Column(updatable = false, name = "artifact_internal_id") - private long artifactId; - - @Id - @Column(name = "maven_group_id", updatable = false) - private String mavenGroupId; - - @Id - @Column(name = "maven_artifact_id", updatable = false) - private String mavenArtifactId; - - @Id - @Column(name = "maven_version", - updatable = false) - private String version; - - @Id - @Column(name = "tag_name", - updatable = false) - private String tagName; - - @Column(name = "tag_value", - updatable = false) - private String tagValue; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "maven_version", - referencedColumnName = "version", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "maven_group_id", - referencedColumnName = "group_id", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "maven_artifact_id", - referencedColumnName = "artifact_id", - nullable = false, - updatable = false, - insertable = false) - }) - private JpaVersionedArtifactView versionView; - - public String getTagName() { - return tagName; - } - - public String getTagValue() { - return tagValue; - } - - public MavenCoordinates asMavenCoordinates() { - return new MavenCoordinates(this.mavenGroupId, this.mavenArtifactId, this.version); - } - - public JpaVersionedArtifactView getVersion() { - return this.versionView; - } - - public String getMavenVersion() { - return this.version; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaTaggedVersion that = (JpaTaggedVersion) o; - return versionId == that.versionId && artifactId == that.artifactId && Objects.equals( - mavenGroupId, that.mavenGroupId) && Objects.equals( - mavenArtifactId, that.mavenArtifactId) && Objects.equals( - version, that.version) && Objects.equals(tagName, that.tagName) && Objects.equals( - tagValue, that.tagValue); - } - - @Override - public int hashCode() { - return Objects.hash(versionId, artifactId, mavenGroupId, mavenArtifactId, version, tagName, tagValue); - } -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedArtifactView.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedArtifactView.java deleted file mode 100644 index 3fe2881c..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedArtifactView.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl.models; - -import io.vavr.Tuple; -import io.vavr.collection.HashMap; -import io.vavr.collection.List; -import io.vavr.collection.Map; -import org.hibernate.annotations.Immutable; -import org.spongepowered.downloads.artifact.api.Artifact; -import org.spongepowered.downloads.artifact.api.MavenCoordinates; -import org.spongepowered.downloads.versions.query.api.models.TagCollection; -import org.spongepowered.downloads.versions.query.api.models.VersionedChangelog; - -import javax.persistence.CascadeType; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.NamedQueries; -import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.net.URI; -import java.util.Comparator; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; - -@Immutable -@Entity(name = "VersionedArtifactView") -@Table(name = "versioned_artifacts", - schema = "version") -@NamedQueries({ - @NamedQuery( - name = "VersionedArtifactView.count", - query = """ - select count(v) from VersionedArtifactView v - where v.groupId = :groupId and v.artifactId = :artifactId - """ - ), - @NamedQuery( - name = "VersionedArtifactView.recommendedCount", - query = """ - select count(v) from VersionedArtifactView v - where v.groupId = :groupId and v.artifactId = :artifactId and v.recommended = :recommended - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findByArtifact", - query = """ - select v from VersionedArtifactView v where v.artifactId = :artifactId and v.groupId = :groupId - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findByArtifactAndRecommendation", - query = """ - select v from VersionedArtifactView v - where v.artifactId = :artifactId and v.groupId = :groupId and (v.recommended = :recommended or v.manuallyRecommended = :recommended) - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findExplicitly", - query = """ - select v from VersionedArtifactView v - left join fetch v.tags - where v.artifactId = :artifactId and v.groupId = :groupId and v.version = :version - """ - ), - @NamedQuery( - name = "VersionedArtifactView.findFullVersionDetails", - query = """ - select v from VersionedArtifactView v - left join fetch v.tags - left join fetch v.assets - where v.artifactId = :artifactId and v.groupId = :groupId and v.version = :version - """ - ) -}) -public class JpaVersionedArtifactView implements Serializable { - - @Id - @Column(name = "artifact_id", - updatable = false) - private String artifactId; - - @Id - @Column(name = "group_id", - updatable = false) - private String groupId; - - @Id - @Column(name = "version", - updatable = false) - private String version; - - @Column(name = "recommended") - private boolean recommended; - - @Column(name = "manual_recommendation") - private boolean manuallyRecommended; - - @Column(name = "ordering") - private int ordering; - - @OneToMany( - targetEntity = JpaTaggedVersion.class, - fetch = FetchType.LAZY, - cascade = CascadeType.ALL, - orphanRemoval = true, - mappedBy = "versionView") - private Set tags; - - @OneToMany( - targetEntity = JpaVersionedAsset.class, - cascade = CascadeType.ALL, - fetch = FetchType.LAZY, - orphanRemoval = true, - mappedBy = "versionView" - ) - private Set assets; - - @OneToOne( - targetEntity = JpaVersionedChangelog.class, - mappedBy = "versionView", - fetch = FetchType.LAZY - ) - private JpaVersionedChangelog changelog; - - public Set getTags() { - return tags; - } - - public String version() { - return this.version; - } - - public Map getTagValues() { - final var results = this.getTags(); - final var tuple2Stream = results.stream().map( - taggedVersion -> Tuple.of(taggedVersion.getTagName(), taggedVersion.getTagValue())); - return tuple2Stream - .collect(HashMap.collector()); - } - - public TagCollection asTagCollection() { - final var results = this.getTags(); - final var tuple2Stream = results.stream().map( - taggedVersion -> Tuple.of(taggedVersion.getTagName(), taggedVersion.getTagValue())); - return new TagCollection(tuple2Stream - .collect(HashMap.collector()), this.recommended); - } - - public boolean isRecommended() { - return this.recommended || this.manuallyRecommended; - } - - public MavenCoordinates asMavenCoordinates() { - return new MavenCoordinates(this.groupId + ":" + this.artifactId + ":" + this.version); - } - - Set getAssets() { - return assets; - } - - public List asArtifactList() { - return this.getAssets() - .stream() - .map( - asset -> new Artifact( - Optional.ofNullable(asset.getClassifier()), - URI.create(asset.getDownloadUrl()), - new String(asset.getMd5()), - new String(asset.getSha1()), - asset.getExtension() - ) - ).collect(List.collector()) - .sorted(Comparator.comparing(artifact -> artifact.classifier().orElse(""))); - } - - public Optional asVersionedCommit() { - final var changelog = this.changelog; - if (changelog == null) { - return Optional.empty(); - } - return Optional.of(changelog.getChangelog()); - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedArtifactView that = (JpaVersionedArtifactView) o; - return Objects.equals(artifactId, that.artifactId) && Objects.equals( - groupId, that.groupId) && Objects.equals(version, that.version); - } - - @Override - public int hashCode() { - return Objects.hash(artifactId, groupId, version); - } - -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedAsset.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedAsset.java deleted file mode 100644 index 2e67c520..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedAsset.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl.models; - -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.Type; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.Lob; -import javax.persistence.ManyToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Objects; - -@Immutable -@Entity(name = "VersionedAsset") -@Table(name = "artifact_versioned_assets", - schema = "version") -public class JpaVersionedAsset implements Serializable { - - @Id - @Column(name = "group_id") - private String groupId; - @Id - @Column(name = "artifact_id") - private String artifactId; - - @Id - @Column(name = "version") - private String version; - - @Id - @Column(name = "classifier") - private String classifier; - - @Id - @Column(name = "extension") - private String extension; - - @Column(name = "download_url") - private String downloadUrl; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "md5") - private byte[] md5; - - @Lob - @Type(type = "org.hibernate.type.BinaryType") - @Column(name = "sha1") - private byte[] sha1; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "version", - referencedColumnName = "version", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "group_id", - referencedColumnName = "group_id", - nullable = false, - updatable = false, - insertable = false), - @JoinColumn(name = "artifact_id", - referencedColumnName = "artifact_id", - nullable = false, - updatable = false, - insertable = false) - }) - private JpaVersionedArtifactView versionView; - - public String getClassifier() { - return classifier; - } - - public String getExtension() { - return extension; - } - - public String getDownloadUrl() { - return downloadUrl; - } - - public byte[] getMd5() { - return md5; - } - - public byte[] getSha1() { - return sha1; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedAsset that = (JpaVersionedAsset) o; - return Objects.equals(groupId, that.groupId) && Objects.equals( - artifactId, that.artifactId) && Objects.equals(version, that.version) && Objects.equals( - classifier, that.classifier) && Objects.equals(extension, that.extension) && Objects.equals( - downloadUrl, that.downloadUrl) && Arrays.equals(md5, that.md5) && Arrays.equals( - sha1, that.sha1) && Objects.equals(versionView, that.versionView); - } - - @Override - public int hashCode() { - int result = Objects.hash(groupId, artifactId, version, classifier, extension, downloadUrl, versionView); - result = 31 * result + Arrays.hashCode(md5); - result = 31 * result + Arrays.hashCode(sha1); - return result; - } -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedChangelog.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedChangelog.java deleted file mode 100644 index 90f9acb5..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/JpaVersionedChangelog.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl.models; - -import com.vladmihalcea.hibernate.type.json.JsonBinaryType; -import org.hibernate.annotations.Immutable; -import org.hibernate.annotations.Type; -import org.hibernate.annotations.TypeDef; -import org.hibernate.annotations.TypeDefs; -import org.spongepowered.downloads.versions.query.api.models.VersionedChangelog; - -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.FetchType; -import javax.persistence.Id; -import javax.persistence.JoinColumn; -import javax.persistence.JoinColumns; -import javax.persistence.OneToOne; -import javax.persistence.Table; -import java.io.Serializable; -import java.net.URL; -import java.util.Objects; - -@Immutable -@Entity(name = "VersionedChangelog") -@Table( - name = "versioned_changelogs", - schema = "version" -) -@TypeDefs({ - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) -}) -public class JpaVersionedChangelog implements Serializable { - - @Id - @Column(name = "version_id", updatable = false) - private String versionId; - - @Id - @Column(name = "group_id", updatable = false) - private String groupId; - - @Id - @Column(name = "artifact_id", updatable = false) - private String artifactId; - - @OneToOne(fetch = FetchType.LAZY) - @JoinColumns({ - @JoinColumn(name = "version", referencedColumnName = "version", insertable = false, updatable = false), - @JoinColumn(name = "group_id", referencedColumnName = "group_id", insertable = false, updatable = false), - @JoinColumn(name = "artifact_id", referencedColumnName = "artifact_id", insertable = false, updatable = false) - }) - private JpaVersionedArtifactView versionView; - - @Column(name = "commit_sha", nullable = false) - private String sha; - - @Type(type = "jsonb") - @Column(name = "changelog", columnDefinition = "jsonb") - private VersionedChangelog changelog; - - @Column(name = "repo") - private URL repo; - - @Column(name = "branch") - private String branch; - - public String getSha() { - return sha; - } - - public VersionedChangelog getChangelog() { - return changelog; - } - - public URL getRepo() { - return repo; - } - - public String getBranch() { - return branch; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - JpaVersionedChangelog that = (JpaVersionedChangelog) o; - return Objects.equals(versionId, that.versionId) && Objects.equals( - groupId, that.groupId) && Objects.equals(artifactId, that.artifactId) && Objects.equals( - versionView, that.versionView) && Objects.equals(sha, that.sha) && Objects.equals( - changelog, that.changelog) && Objects.equals(repo, that.repo) && Objects.equals( - branch, that.branch); - } - - @Override - public int hashCode() { - return Objects.hash(versionId, groupId, artifactId, versionView, sha, changelog, repo, branch); - } - - @Override - public String toString() { - return "JpaVersionedChangelog{" + - "versionId='" + versionId + '\'' + - ", groupId='" + groupId + '\'' + - ", artifactId='" + artifactId + '\'' + - ", versionView=" + versionView + - ", sha='" + sha + '\'' + - ", changelog=" + changelog + - ", repo=" + repo + - ", branch='" + branch + '\'' + - '}'; - } -} diff --git a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/VersionedArtifactID.java b/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/VersionedArtifactID.java deleted file mode 100644 index 8450b0eb..00000000 --- a/versions-query-impl/src/main/java/org/spongepowered/downloads/versions/query/impl/models/VersionedArtifactID.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is part of SystemOfADownload, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.downloads.versions.query.impl.models; - -import java.io.Serializable; -import java.util.Objects; - -public class VersionedArtifactID implements Serializable { - private String artifactId; - - private String groupId; - - private String version; - - public VersionedArtifactID(final String artifactId, final String groupId, final String version) { - this.artifactId = artifactId; - this.groupId = groupId; - this.version = version; - } - - @Override - public boolean equals(final Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - VersionedArtifactID that = (VersionedArtifactID) o; - return artifactId.equals(that.artifactId) && groupId.equals(that.groupId) && version.equals(that.version); - } - - @Override - public int hashCode() { - return Objects.hash(artifactId, groupId, version); - } -} diff --git a/versions-query-impl/src/main/resources/META-INF/persistence.xml b/versions-query-impl/src/main/resources/META-INF/persistence.xml deleted file mode 100644 index ba026222..00000000 --- a/versions-query-impl/src/main/resources/META-INF/persistence.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - org.hibernate.jpa.HibernatePersistenceProvider - DefaultDS - org.spongepowered.downloads.versions.query.impl.models.JpaTaggedVersion - org.spongepowered.downloads.versions.query.impl.models.JpaVersionedArtifactView - org.spongepowered.downloads.versions.query.impl.models.JpaVersionedAsset - org.spongepowered.downloads.versions.query.impl.models.JpaVersionedChangelog - true - - - - - - - diff --git a/versions-query-impl/src/main/resources/application.conf b/versions-query-impl/src/main/resources/application.conf deleted file mode 100644 index 6b3bb4ed..00000000 --- a/versions-query-impl/src/main/resources/application.conf +++ /dev/null @@ -1,29 +0,0 @@ -play.modules.enabled += org.spongepowered.downloads.versions.query.impl.VersionQueryModule - -db.default { - driver = "org.postgresql.Driver" - url = "jdbc:postgresql://localhost:5432/default" - url = ${?POSTGRES_URL} - username = admin - username = ${?POSTGRES_USERNAME} - password = password - password = ${?POSTGRES_PASSWORD} -} - -jdbc-defaults.slick.profile = "slick.jdbc.PostgresProfile$" - -lagom.persistence.jpa { - # This must match the name in persistence.xml - persistence-unit = "default" -} - -akka.serialization.jackson { - jackson-modules += "io.vavr.jackson.datatype.VavrModule" -} - -play.filters.enabled += "play.filters.cors.CORSFilter" -play.filters.cors { - pathPrefixes = ["/versions-query"] - allowedHttpMethods = ["GET"] - preflightMaxAge = 3 days -} diff --git a/versions-query-impl/src/main/resources/logback.xml b/versions-query-impl/src/main/resources/logback.xml deleted file mode 100644 index 9ef110a0..00000000 --- a/versions-query-impl/src/main/resources/logback.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - System.out - - %date{hh:MM:ss.SSS} [%level] [%thread] [%logger{5}/%marker] - %coloredLevel %msg MDC: {%mdc}%n - - - - - 8192 - true - - - - - - - - - - - - - - - - - - - - - - -