From d4537ae4fd15127dad4dc890c7cf75e19d483f91 Mon Sep 17 00:00:00 2001 From: amvanbaren Date: Mon, 6 May 2024 18:02:17 +0300 Subject: [PATCH] Observe publish endpoint and download count processing Comment out download count observations --- .../eclipse/openvsx/ExtensionProcessor.java | 405 ++++++++++-------- .../org/eclipse/openvsx/ExtensionService.java | 75 ++-- .../eclipse/openvsx/ExtensionValidator.java | 116 ++--- .../eclipse/openvsx/IExtensionRegistry.java | 2 - .../eclipse/openvsx/LocalRegistryService.java | 179 ++++---- .../java/org/eclipse/openvsx/RegistryAPI.java | 61 +-- .../openvsx/UpstreamRegistryService.java | 2 - .../java/org/eclipse/openvsx/UserService.java | 90 ++-- .../eclipse/openvsx/cache/CacheService.java | 77 ++-- .../cache/ExtensionJsonCacheKeyGenerator.java | 2 +- ...testExtensionVersionCacheKeyGenerator.java | 2 +- .../openvsx/eclipse/EclipseService.java | 95 ++-- .../openvsx/metrics/MetricsConfiguration.java | 2 +- .../RegistryObservationConvention.java | 22 +- .../ExtractResourcesJobRequestHandler.java | 3 +- ...ExtractVsixManifestsJobRequestHandler.java | 3 +- .../FixTargetPlatformsJobRequestHandler.java | 3 +- ...nerateSha256ChecksumJobRequestHandler.java | 3 +- .../migration/SetPreReleaseJobService.java | 3 +- .../PublishExtensionVersionHandler.java | 206 +++++---- .../repositories/ExtensionRepository.java | 2 - .../ExtensionVersionJooqRepository.java | 2 - .../NamespaceMembershipRepository.java | 6 +- .../repositories/NamespaceRepository.java | 6 +- .../PersonalAccessTokenRepository.java | 4 +- .../repositories/RepositoryService.java | 47 +- .../openvsx/search/SearchUtilService.java | 14 +- .../storage/AzureBlobStorageService.java | 2 - .../storage/AzureDownloadCountProcessor.java | 91 ++-- .../storage/AzureDownloadCountService.java | 253 ++++++----- .../storage/GoogleCloudStorageService.java | 5 +- .../openvsx/storage/StorageUtilService.java | 33 +- .../org/eclipse/openvsx/util/ArchiveUtil.java | 37 +- .../eclipse/openvsx/util/VersionService.java | 8 +- .../openvsx/ExtensionProcessorTest.java | 9 +- .../openvsx/ExtensionValidatorTest.java | 36 +- .../org/eclipse/openvsx/RegistryAPITest.java | 34 +- .../java/org/eclipse/openvsx/UserAPITest.java | 6 + .../openvsx/adapter/VSCodeAPITest.java | 17 +- .../eclipse/openvsx/admin/AdminAPITest.java | 27 +- .../openvsx/cache/CacheServiceTest.java | 1 + .../openvsx/eclipse/EclipseServiceTest.java | 26 +- .../search/DatabaseSearchServiceTest.java | 6 + .../search/ElasticSearchServiceTest.java | 6 + .../eclipse/openvsx/util/ArchiveUtilTest.java | 5 +- 45 files changed, 1167 insertions(+), 867 deletions(-) diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java index e7679c636..514f1cfeb 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionProcessor.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.MissingNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.adapter.ExtensionQueryResult; @@ -48,12 +50,15 @@ public class ExtensionProcessor implements AutoCloseable { protected final Logger logger = LoggerFactory.getLogger(ExtensionProcessor.class); private final TempFile extensionFile; + private final ObservationRegistry observations; + private ZipFile zipFile; private JsonNode packageJson; private JsonNode vsixManifest; - public ExtensionProcessor(TempFile extensionFile) { + public ExtensionProcessor(TempFile extensionFile, ObservationRegistry observations) { this.extensionFile = extensionFile; + this.observations = observations; } @Override @@ -68,62 +73,68 @@ public void close() { } private void readInputStream() { - if (zipFile != null) { - return; - } - try { - zipFile = new ZipFile(extensionFile.getPath().toFile()); - } catch (ZipException exc) { - throw new ErrorResultException("Could not read zip file: " + exc.getMessage()); - } catch (EOFException exc) { - throw new ErrorResultException("Could not read from input stream: " + exc.getMessage()); - } catch (IOException exc) { - throw new RuntimeException(exc); - } + Observation.createNotStarted("ExtensionProcessor#readInputStream", observations).observe(() -> { + if (zipFile != null) { + return; + } + try { + zipFile = new ZipFile(extensionFile.getPath().toFile()); + } catch (ZipException exc) { + throw new ErrorResultException("Could not read zip file: " + exc.getMessage()); + } catch (EOFException exc) { + throw new ErrorResultException("Could not read from input stream: " + exc.getMessage()); + } catch (IOException exc) { + throw new RuntimeException(exc); + } + }); } private void loadPackageJson() { - if (packageJson != null) { - return; - } - readInputStream(); + Observation.createNotStarted("ExtensionProcessor#loadPackageJson", observations).observe(() -> { + if (packageJson != null) { + return; + } + readInputStream(); - // Read package.json - var bytes = ArchiveUtil.readEntry(zipFile, PACKAGE_JSON); - if (bytes == null) - throw new ErrorResultException("Entry not found: " + PACKAGE_JSON); - try { - var mapper = new ObjectMapper(); - packageJson = mapper.readTree(bytes); - } catch (JsonParseException exc) { - throw new ErrorResultException("Invalid JSON format in " + PACKAGE_JSON - + ": " + exc.getMessage()); - } catch (IOException exc) { - throw new RuntimeException(exc); - } + // Read package.json + var bytes = ArchiveUtil.readEntry(zipFile, PACKAGE_JSON, observations); + if (bytes == null) + throw new ErrorResultException("Entry not found: " + PACKAGE_JSON); + try { + var mapper = new ObjectMapper(); + packageJson = mapper.readTree(bytes); + } catch (JsonParseException exc) { + throw new ErrorResultException("Invalid JSON format in " + PACKAGE_JSON + + ": " + exc.getMessage()); + } catch (IOException exc) { + throw new RuntimeException(exc); + } + }); } private void loadVsixManifest() { - if (vsixManifest != null) { - return; - } + Observation.createNotStarted("ExtensionProcessor#loadVsixManifest", observations).observe(() -> { + if (vsixManifest != null) { + return; + } - readInputStream(); + readInputStream(); - // Read extension.vsixmanifest - var bytes = ArchiveUtil.readEntry(zipFile, VSIX_MANIFEST); - if (bytes == null) - throw new ErrorResultException("Entry not found: " + VSIX_MANIFEST); - - try { - var mapper = new XmlMapper(); - vsixManifest = mapper.readTree(bytes); - } catch (JsonParseException exc) { - throw new ErrorResultException("Invalid JSON format in " + VSIX_MANIFEST - + ": " + exc.getMessage()); - } catch (IOException exc) { - throw new RuntimeException(exc); - } + // Read extension.vsixmanifest + var bytes = ArchiveUtil.readEntry(zipFile, VSIX_MANIFEST, observations); + if (bytes == null) + throw new ErrorResultException("Entry not found: " + VSIX_MANIFEST); + + try { + var mapper = new XmlMapper(); + vsixManifest = mapper.readTree(bytes); + } catch (JsonParseException exc) { + throw new ErrorResultException("Invalid JSON format in " + VSIX_MANIFEST + + ": " + exc.getMessage()); + } catch (IOException exc) { + throw new RuntimeException(exc); + } + }); } private JsonNode findByIdInArray(Iterable iter, String id) { @@ -137,157 +148,197 @@ private JsonNode findByIdInArray(Iterable iter, String id) { } public String getExtensionName() { - loadVsixManifest(); - return vsixManifest.path("Metadata").path("Identity").path("Id").asText(); + return Observation.createNotStarted("ExtensionProcessor#getExtensionName", observations).observe(() -> { + loadVsixManifest(); + return vsixManifest.path("Metadata").path("Identity").path("Id").asText(); + }); } public String getNamespace() { - loadVsixManifest(); - return vsixManifest.path("Metadata").path("Identity").path("Publisher").asText(); + return Observation.createNotStarted("ExtensionProcessor#getNamespace", observations).observe(() -> { + loadVsixManifest(); + return vsixManifest.path("Metadata").path("Identity").path("Publisher").asText(); + }); } public List getExtensionDependencies() { - loadVsixManifest(); - var extDepenNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionDependencies"); - return asStringList(extDepenNode.path("Value").asText(), ","); + return Observation.createNotStarted("ExtensionProcessor#getExtensionDependencies", observations).observe(() -> { + loadVsixManifest(); + var extDepenNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionDependencies"); + return asStringList(extDepenNode.path("Value").asText(), ","); + }); } public List getBundledExtensions() { - loadVsixManifest(); - var extPackNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionPack"); - return asStringList(extPackNode.path("Value").asText(), ","); + return Observation.createNotStarted("ExtensionProcessor#getBundledExtensions", observations).observe(() -> { + loadVsixManifest(); + var extPackNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionPack"); + return asStringList(extPackNode.path("Value").asText(), ","); + }); } public List getExtensionKinds() { - loadVsixManifest(); - var extKindNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionKind"); - return asStringList(extKindNode.path("Value").asText(), ","); + return Observation.createNotStarted("ExtensionProcessor#getExtensionKinds", observations).observe(() -> { + loadVsixManifest(); + var extKindNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.ExtensionKind"); + return asStringList(extKindNode.path("Value").asText(), ","); + }); } public String getHomepage() { - loadVsixManifest(); - var extKindNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Learn"); - return extKindNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getHomepage", observations).observe(() -> { + loadVsixManifest(); + var extKindNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Learn"); + return extKindNode.path("Value").asText(); + }); } public String getRepository() { - loadVsixManifest(); - var sourceNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Source"); - return sourceNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getRepository", observations).observe(() -> { + loadVsixManifest(); + var sourceNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Source"); + return sourceNode.path("Value").asText(); + }); } public String getBugs() { - loadVsixManifest(); - var supportNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Support"); - return supportNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getBugs", observations).observe(() -> { + loadVsixManifest(); + var supportNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Links.Support"); + return supportNode.path("Value").asText(); + }); } public String getGalleryColor() { - loadVsixManifest(); - var colorNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Branding.Color"); - return colorNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getGalleryColor", observations).observe(() -> { + loadVsixManifest(); + var colorNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Branding.Color"); + return colorNode.path("Value").asText(); + }); } public String getGalleryTheme() { - loadVsixManifest(); - var themeNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Branding.Theme"); - return themeNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getGalleryTheme", observations).observe(() -> { + loadVsixManifest(); + var themeNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Services.Branding.Theme"); + return themeNode.path("Value").asText(); + }); } public List getLocalizedLanguages() { - loadVsixManifest(); - var languagesNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.LocalizedLanguages"); - return asStringList(languagesNode.path("Value").asText(), ","); + return Observation.createNotStarted("ExtensionProcessor#getLocalizedLanguages", observations).observe(() -> { + loadVsixManifest(); + var languagesNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.LocalizedLanguages"); + return asStringList(languagesNode.path("Value").asText(), ","); + }); } public boolean isPreview() { - loadVsixManifest(); - var galleryFlags = vsixManifest.path("Metadata").path("GalleryFlags"); - return asStringList(galleryFlags.asText(), " ").contains("Preview"); + return Observation.createNotStarted("ExtensionProcessor#isPreview", observations).observe(() -> { + loadVsixManifest(); + var galleryFlags = vsixManifest.path("Metadata").path("GalleryFlags"); + return asStringList(galleryFlags.asText(), " ").contains("Preview"); + }); } public boolean isPreRelease() { - loadVsixManifest(); - var preReleaseNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.PreRelease"); - return preReleaseNode.path("Value").asBoolean(false); + return Observation.createNotStarted("ExtensionProcessor#isPreRelease", observations).observe(() -> { + loadVsixManifest(); + var preReleaseNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.PreRelease"); + return preReleaseNode.path("Value").asBoolean(false); + }); } public String getSponsorLink() { - loadVsixManifest(); - var sponsorLinkNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.SponsorLink"); - return sponsorLinkNode.path("Value").asText(); + return Observation.createNotStarted("ExtensionProcessor#getSponsorLink", observations).observe(() -> { + loadVsixManifest(); + var sponsorLinkNode = findByIdInArray(vsixManifest.path("Metadata").path("Properties").path("Property"), "Microsoft.VisualStudio.Code.SponsorLink"); + return sponsorLinkNode.path("Value").asText(); + }); } public ExtensionVersion getMetadata() { - loadPackageJson(); - loadVsixManifest(); - var extVersion = new ExtensionVersion(); - extVersion.setVersion(getVersion()); - extVersion.setTargetPlatform(getTargetPlatform()); - extVersion.setPreview(isPreview()); - extVersion.setPreRelease(isPreRelease()); - extVersion.setDisplayName(vsixManifest.path("Metadata").path("DisplayName").asText()); - extVersion.setDescription(vsixManifest.path("Metadata").path("Description").path("").asText()); - extVersion.setEngines(getEngines(packageJson.path("engines"))); - extVersion.setCategories(asStringList(vsixManifest.path("Metadata").path("Categories").asText(), ",")); - extVersion.setExtensionKind(getExtensionKinds()); - extVersion.setTags(getTags()); - extVersion.setLicense(packageJson.path("license").textValue()); - extVersion.setHomepage(getHomepage()); - extVersion.setRepository(getRepository()); - extVersion.setSponsorLink(getSponsorLink()); - extVersion.setBugs(getBugs()); - extVersion.setMarkdown(packageJson.path("markdown").textValue()); - extVersion.setGalleryColor(getGalleryColor()); - extVersion.setGalleryTheme(getGalleryTheme()); - extVersion.setLocalizedLanguages(getLocalizedLanguages()); - extVersion.setQna(packageJson.path("qna").textValue()); - - return extVersion; + return Observation.createNotStarted("ExtensionProcessor#getMetadata", observations).observe(() -> { + loadPackageJson(); + loadVsixManifest(); + var extVersion = new ExtensionVersion(); + extVersion.setVersion(getVersion()); + extVersion.setTargetPlatform(getTargetPlatform()); + extVersion.setPreview(isPreview()); + extVersion.setPreRelease(isPreRelease()); + extVersion.setDisplayName(vsixManifest.path("Metadata").path("DisplayName").asText()); + extVersion.setDescription(vsixManifest.path("Metadata").path("Description").path("").asText()); + extVersion.setEngines(getEngines(packageJson.path("engines"))); + extVersion.setCategories(asStringList(vsixManifest.path("Metadata").path("Categories").asText(), ",")); + extVersion.setExtensionKind(getExtensionKinds()); + extVersion.setTags(getTags()); + extVersion.setLicense(packageJson.path("license").textValue()); + extVersion.setHomepage(getHomepage()); + extVersion.setRepository(getRepository()); + extVersion.setSponsorLink(getSponsorLink()); + extVersion.setBugs(getBugs()); + extVersion.setMarkdown(packageJson.path("markdown").textValue()); + extVersion.setGalleryColor(getGalleryColor()); + extVersion.setGalleryTheme(getGalleryTheme()); + extVersion.setLocalizedLanguages(getLocalizedLanguages()); + extVersion.setQna(packageJson.path("qna").textValue()); + + return extVersion; + }); } public String getVersion() { - return vsixManifest.path("Metadata").path("Identity").path("Version").asText(); + return Observation.createNotStarted("ExtensionProcessor#getVersion", observations).observe(() -> { + return vsixManifest.path("Metadata").path("Identity").path("Version").asText(); + }); } private String getTargetPlatform() { - var targetPlatform = vsixManifest.path("Metadata").path("Identity").path("TargetPlatform").asText(); - if(targetPlatform.isEmpty()) { - targetPlatform = TargetPlatform.NAME_UNIVERSAL; - } + return Observation.createNotStarted("ExtensionProcessor#getTargetPlatform", observations).observe(() -> { + var targetPlatform = vsixManifest.path("Metadata").path("Identity").path("TargetPlatform").asText(); + if (targetPlatform.isEmpty()) { + targetPlatform = TargetPlatform.NAME_UNIVERSAL; + } - return targetPlatform; + return targetPlatform; + }); } private List getTags() { - var tags = vsixManifest.path("Metadata").path("Tags").asText(); - return asStringList(tags, ",").stream() - .collect(Collectors.groupingBy(String::toLowerCase)) - .entrySet().stream() - .sorted(Map.Entry.comparingByKey()) - .map(e -> e.getValue().get(0)) - .collect(Collectors.toList()); + return Observation.createNotStarted("ExtensionProcessor#getTags", observations).observe(() -> { + var tags = vsixManifest.path("Metadata").path("Tags").asText(); + return asStringList(tags, ",").stream() + .collect(Collectors.groupingBy(String::toLowerCase)) + .entrySet().stream() + .sorted(Map.Entry.comparingByKey()) + .map(e -> e.getValue().get(0)) + .collect(Collectors.toList()); + }); } private List asStringList(String value, String sep){ - if (StringUtils.isEmpty(value)){ - return new ArrayList<>(); - } + return Observation.createNotStarted("ExtensionProcessor#asStringList", observations).observe(() -> { + if (StringUtils.isEmpty(value)) { + return new ArrayList<>(); + } - return Arrays.asList(value.split(sep)); + return Arrays.asList(value.split(sep)); + }); } private List getEngines(JsonNode node) { - if (node.isObject()) { - var result = new ArrayList(); - var fieldIter = node.fields(); - while (fieldIter.hasNext()) { - var entry = fieldIter.next(); - result.add(entry.getKey() + "@" + entry.getValue().textValue()); + return Observation.createNotStarted("ExtensionProcessor#getEngines", observations).observe(() -> { + if (node.isObject()) { + var result = new ArrayList(); + var fieldIter = node.fields(); + while (fieldIter.hasNext()) { + var entry = fieldIter.next(); + result.add(entry.getKey() + "@" + entry.getValue().textValue()); + } + return result; } - return result; - } - return null; + return null; + }); } public List getFileResources(ExtensionVersion extVersion) { @@ -307,7 +358,7 @@ public void processEachResource(ExtensionVersion extVersion, Consumer { byte[] bytes; try { - bytes = ArchiveUtil.readEntry(zipFile, zipEntry); + bytes = ArchiveUtil.readEntry(zipFile, zipEntry, ObservationRegistry.NOOP); } catch(ErrorResultException exc) { logger.warn(exc.getMessage()); bytes = null; @@ -327,16 +378,14 @@ public void processEachResource(ExtensionVersion extVersion, Consumer { + var binary = new FileResource(); + binary.setExtension(extVersion); + binary.setName(Optional.ofNullable(binaryName).orElse(NamingUtil.toFileFormat(extVersion, ".vsix"))); + binary.setType(FileResource.DOWNLOAD); + binary.setContent(null); + return binary; + }); } public FileResource generateSha256Checksum(ExtensionVersion extVersion) { @@ -361,7 +410,7 @@ public FileResource generateSha256Checksum(ExtensionVersion extVersion) { protected FileResource getManifest(ExtensionVersion extVersion) { readInputStream(); - var bytes = ArchiveUtil.readEntry(zipFile, PACKAGE_JSON); + var bytes = ArchiveUtil.readEntry(zipFile, PACKAGE_JSON, ObservationRegistry.NOOP); if (bytes == null) { return null; } @@ -402,29 +451,31 @@ public FileResource getChangelog(ExtensionVersion extVersion) { } public FileResource getLicense(ExtensionVersion extVersion) { - readInputStream(); - var license = new FileResource(); - license.setExtension(extVersion); - license.setType(FileResource.LICENSE); - - var assetPath = tryGetLicensePath(); - if(StringUtils.isNotEmpty(assetPath)) { - var bytes = ArchiveUtil.readEntry(zipFile, assetPath); - var lastSegmentIndex = assetPath.lastIndexOf('/'); - var lastSegment = assetPath.substring(lastSegmentIndex + 1); - - license.setName(lastSegment); - license.setContent(bytes); - return license; - } + return Observation.createNotStarted("ExtensionProcessor#getLicense", observations).observe(() -> { + readInputStream(); + var license = new FileResource(); + license.setExtension(extVersion); + license.setType(FileResource.LICENSE); + + var assetPath = tryGetLicensePath(); + if (StringUtils.isNotEmpty(assetPath)) { + var bytes = ArchiveUtil.readEntry(zipFile, assetPath, observations); + var lastSegmentIndex = assetPath.lastIndexOf('/'); + var lastSegment = assetPath.substring(lastSegmentIndex + 1); + + license.setName(lastSegment); + license.setContent(bytes); + return license; + } - return null; + return null; + }); } private Pair readFromVsixPackage(String assetType, String[] alternateNames) { var assetPath = tryGetAssetPath(assetType); if(StringUtils.isNotEmpty(assetPath)) { - var bytes = ArchiveUtil.readEntry(zipFile, assetPath); + var bytes = ArchiveUtil.readEntry(zipFile, assetPath, ObservationRegistry.NOOP); var lastSegmentIndex = assetPath.lastIndexOf('/'); var lastSegment = assetPath.substring(lastSegmentIndex + 1); return Pair.of(bytes, lastSegment); @@ -438,7 +489,7 @@ private Pair readFromAlternateNames(String[] names) { for (var name : names) { var entry = ArchiveUtil.getEntryIgnoreCase(zipFile, name); if (entry != null) { - var bytes = ArchiveUtil.readEntry(zipFile, entry); + var bytes = ArchiveUtil.readEntry(zipFile, entry, ObservationRegistry.NOOP); var lastSegmentIndex = entry.getName().lastIndexOf('/'); var lastSegment = entry.getName().substring(lastSegmentIndex + 1); return Pair.of(bytes, lastSegment); @@ -448,11 +499,13 @@ private Pair readFromAlternateNames(String[] names) { } private String tryGetLicensePath() { - loadVsixManifest(); - var licensePath = vsixManifest.path("Metadata").path("License").asText(); - return licensePath.isEmpty() - ? tryGetAssetPath(ExtensionQueryResult.ExtensionFile.FILE_LICENSE) - : licensePath; + return Observation.createNotStarted("ExtensionProcessor#tryGetLicensePath", observations).observe(() -> { + loadVsixManifest(); + var licensePath = vsixManifest.path("Metadata").path("License").asText(); + return licensePath.isEmpty() + ? tryGetAssetPath(ExtensionQueryResult.ExtensionFile.FILE_LICENSE) + : licensePath; + }); } private String tryGetAssetPath(String type) { @@ -482,7 +535,7 @@ protected FileResource getIcon(ExtensionVersion extVersion) { return null; } - var bytes = ArchiveUtil.readEntry(zipFile, iconPath); + var bytes = ArchiveUtil.readEntry(zipFile, iconPath, ObservationRegistry.NOOP); if (bytes == null) { return null; } @@ -506,7 +559,7 @@ public FileResource getVsixManifest(ExtensionVersion extVersion) { vsixManifest.setExtension(extVersion); vsixManifest.setName(VSIX_MANIFEST); vsixManifest.setType(FileResource.VSIXMANIFEST); - vsixManifest.setContent(ArchiveUtil.readEntry(zipFile, VSIX_MANIFEST)); + vsixManifest.setContent(ArchiveUtil.readEntry(zipFile, VSIX_MANIFEST, ObservationRegistry.NOOP)); return vsixManifest; } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java index 631ed1d78..8f0a5d544 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionService.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionService.java @@ -9,6 +9,8 @@ ********************************************************************************/ package org.eclipse.openvsx; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.transaction.Transactional; import jakarta.transaction.Transactional.TxType; import org.apache.commons.lang3.StringUtils; @@ -28,8 +30,10 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.time.LocalDateTime; import java.util.LinkedHashSet; +import java.util.concurrent.atomic.AtomicLong; @Component public class ExtensionService { @@ -40,6 +44,7 @@ public class ExtensionService { private final SearchUtilService search; private final CacheService cache; private final PublishExtensionVersionHandler publishHandler; + private final ObservationRegistry observations; @Value("${ovsx.publishing.require-license:false}") boolean requireLicense; @@ -48,12 +53,14 @@ public ExtensionService( RepositoryService repositories, SearchUtilService search, CacheService cache, - PublishExtensionVersionHandler publishHandler + PublishExtensionVersionHandler publishHandler, + ObservationRegistry observations ) { this.repositories = repositories; this.search = search; this.cache = cache; this.publishHandler = publishHandler; + this.observations = observations; } @Transactional @@ -64,44 +71,50 @@ public ExtensionVersion mirrorVersion(TempFile extensionFile, String signatureNa } public ExtensionVersion publishVersion(InputStream content, PersonalAccessToken token) { - var extensionFile = createExtensionFile(content); - var download = doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true); - publishHandler.publishAsync(download, extensionFile, this); - publishHandler.schedulePublicIdJob(download); - return download.getExtension(); + return Observation.createNotStarted("ExtensionService#publishVersion", observations).observe(() -> { + var extensionFile = createExtensionFile(content); + var download = doPublish(extensionFile, null, token, TimeUtil.getCurrentUTC(), true); + publishHandler.publishAsync(download, extensionFile, this); + publishHandler.schedulePublicIdJob(download); + return download.getExtension(); + }); } private FileResource doPublish(TempFile extensionFile, String binaryName, PersonalAccessToken token, LocalDateTime timestamp, boolean checkDependencies) { - try (var processor = new ExtensionProcessor(extensionFile)) { - var extVersion = publishHandler.createExtensionVersion(processor, token, timestamp, checkDependencies); - if (requireLicense) { - // Check the extension's license - var license = processor.getLicense(extVersion); - checkLicense(extVersion, license); + return Observation.createNotStarted("ExtensionService#doPublish", observations).observe(() -> { + try (var processor = new ExtensionProcessor(extensionFile, observations)) { + var extVersion = publishHandler.createExtensionVersion(processor, token, timestamp, checkDependencies); + if (requireLicense) { + // Check the extension's license + var license = processor.getLicense(extVersion); + Observation.createNotStarted("ExtensionService#checkLicense", observations).observe(() -> checkLicense(extVersion, license)); + } + + return processor.getBinary(extVersion, binaryName); } - - return processor.getBinary(extVersion, binaryName); - } + }); } private TempFile createExtensionFile(InputStream content) { - try (var input = new BufferedInputStream(content)) { - input.mark(0); - var skipped = input.skip(MAX_CONTENT_SIZE + 1); - if (skipped > MAX_CONTENT_SIZE) { - throw new ErrorResultException("The extension package exceeds the size limit of 512 MB.", HttpStatus.PAYLOAD_TOO_LARGE); + return Observation.createNotStarted("ExtensionService#createExtensionFile", observations).observe(() -> { + try (var input = new BufferedInputStream(content)) { + input.mark(0); + var skipped = input.skip(MAX_CONTENT_SIZE + 1); + if (skipped > MAX_CONTENT_SIZE) { + throw new ErrorResultException("The extension package exceeds the size limit of 512 MB.", HttpStatus.PAYLOAD_TOO_LARGE); + } + + var extensionFile = new TempFile("extension_", ".vsix"); + try(var out = Files.newOutputStream(extensionFile.getPath())) { + input.reset(); + input.transferTo(out); + } + + return extensionFile; + } catch (IOException e) { + throw new ErrorResultException("Failed to read extension file", e); } - - var extensionFile = new TempFile("extension_", ".vsix"); - try(var out = Files.newOutputStream(extensionFile.getPath())) { - input.reset(); - input.transferTo(out); - } - - return extensionFile; - } catch (IOException e) { - throw new ErrorResultException("Failed to read extension file", e); - } + }); } private void checkLicense(ExtensionVersion extVersion, FileResource license) { diff --git a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java index c542fc7c3..2c16e9b76 100644 --- a/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java +++ b/server/src/main/java/org/eclipse/openvsx/ExtensionValidator.java @@ -9,6 +9,8 @@ ********************************************************************************/ package org.eclipse.openvsx; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.apache.commons.lang3.StringUtils; import org.apache.tika.Tika; import org.apache.tika.mime.MediaType; @@ -46,6 +48,11 @@ public class ExtensionValidator { private final static int GALLERY_COLOR_SIZE = 16; private final Pattern namePattern = Pattern.compile("[\\w\\-\\+\\$~]+"); + private final ObservationRegistry observations; + + public ExtensionValidator(ObservationRegistry observations) { + this.observations = observations; + } public Optional validateNamespace(String namespace) { if (StringUtils.isEmpty(namespace) || namespace.equals("-")) { @@ -102,57 +109,63 @@ public List validateNamespaceDetails(NamespaceDetailsJson json) { } public Optional validateExtensionName(String name) { - if (StringUtils.isEmpty(name)) { - return Optional.of(new Issue("Name must not be empty.")); - } - if (!namePattern.matcher(name).matches()) { - return Optional.of(new Issue("Invalid extension name: " + name)); - } - if (name.length() > DEFAULT_STRING_SIZE) { - return Optional.of(new Issue("The extension name exceeds the current limit of " + DEFAULT_STRING_SIZE + " characters.")); - } - return Optional.empty(); + return Observation.createNotStarted("ExtensionValidator#validateExtensionName", observations).observe(() -> { + if (StringUtils.isEmpty(name)) { + return Optional.of(new Issue("Name must not be empty.")); + } + if (!namePattern.matcher(name).matches()) { + return Optional.of(new Issue("Invalid extension name: " + name)); + } + if (name.length() > DEFAULT_STRING_SIZE) { + return Optional.of(new Issue("The extension name exceeds the current limit of " + DEFAULT_STRING_SIZE + " characters.")); + } + return Optional.empty(); + }); } public Optional validateExtensionVersion(String version) { - var issues = new ArrayList(); - checkVersion(version, issues); - return issues.isEmpty() - ? Optional.empty() - : Optional.of(issues.get(0)); + return Observation.createNotStarted("ExtensionValidator#validateExtensionVersion", observations).observe(() -> { + var issues = new ArrayList(); + checkVersion(version, issues); + return issues.isEmpty() + ? Optional.empty() + : Optional.of(issues.get(0)); + }); } public List validateMetadata(ExtensionVersion extVersion) { - var issues = new ArrayList(); - checkVersion(extVersion.getVersion(), issues); - checkTargetPlatform(extVersion.getTargetPlatform(), issues); - checkCharacters(extVersion.getDisplayName(), "displayName", issues); - checkFieldSize(extVersion.getDisplayName(), DEFAULT_STRING_SIZE, "displayName", issues); - checkCharacters(extVersion.getDescription(), "description", issues); - checkFieldSize(extVersion.getDescription(), DESCRIPTION_SIZE, "description", issues); - checkCharacters(extVersion.getCategories(), "categories", issues); - checkFieldSize(extVersion.getCategories(), DEFAULT_STRING_SIZE, "categories", issues); - checkCharacters(extVersion.getTags(), "keywords", issues); - checkFieldSize(extVersion.getTags(), DEFAULT_STRING_SIZE, "keywords", issues); - checkCharacters(extVersion.getLicense(), "license", issues); - checkFieldSize(extVersion.getLicense(), DEFAULT_STRING_SIZE, "license", issues); - checkURL(extVersion.getHomepage(), "homepage", issues); - checkFieldSize(extVersion.getHomepage(), DEFAULT_STRING_SIZE, "homepage", issues); - checkURL(extVersion.getRepository(), "repository", issues); - checkFieldSize(extVersion.getRepository(), DEFAULT_STRING_SIZE, "repository", issues); - checkURL(extVersion.getBugs(), "bugs", issues); - checkFieldSize(extVersion.getBugs(), DEFAULT_STRING_SIZE, "bugs", issues); - checkInvalid(extVersion.getMarkdown(), s -> !MARKDOWN_VALUES.contains(s), "markdown", issues, - MARKDOWN_VALUES.toString()); - checkCharacters(extVersion.getGalleryColor(), "galleryBanner.color", issues); - checkFieldSize(extVersion.getGalleryColor(), GALLERY_COLOR_SIZE, "galleryBanner.color", issues); - checkInvalid(extVersion.getGalleryTheme(), s -> !GALLERY_THEME_VALUES.contains(s), "galleryBanner.theme", issues, - GALLERY_THEME_VALUES.toString()); - checkFieldSize(extVersion.getLocalizedLanguages(), DEFAULT_STRING_SIZE, "localizedLanguages", issues); - checkInvalid(extVersion.getQna(), s -> !QNA_VALUES.contains(s) && isInvalidURL(s), "qna", issues, - QNA_VALUES.toString() + " or a URL"); - checkFieldSize(extVersion.getQna(), DEFAULT_STRING_SIZE, "qna", issues); - return issues; + return Observation.createNotStarted("ExtensionValidator#validateMetadata", observations).observe(() -> { + var issues = new ArrayList(); + checkVersion(extVersion.getVersion(), issues); + checkTargetPlatform(extVersion.getTargetPlatform(), issues); + checkCharacters(extVersion.getDisplayName(), "displayName", issues); + checkFieldSize(extVersion.getDisplayName(), DEFAULT_STRING_SIZE, "displayName", issues); + checkCharacters(extVersion.getDescription(), "description", issues); + checkFieldSize(extVersion.getDescription(), DESCRIPTION_SIZE, "description", issues); + checkCharacters(extVersion.getCategories(), "categories", issues); + checkFieldSize(extVersion.getCategories(), DEFAULT_STRING_SIZE, "categories", issues); + checkCharacters(extVersion.getTags(), "keywords", issues); + checkFieldSize(extVersion.getTags(), DEFAULT_STRING_SIZE, "keywords", issues); + checkCharacters(extVersion.getLicense(), "license", issues); + checkFieldSize(extVersion.getLicense(), DEFAULT_STRING_SIZE, "license", issues); + checkURL(extVersion.getHomepage(), "homepage", issues); + checkFieldSize(extVersion.getHomepage(), DEFAULT_STRING_SIZE, "homepage", issues); + checkURL(extVersion.getRepository(), "repository", issues); + checkFieldSize(extVersion.getRepository(), DEFAULT_STRING_SIZE, "repository", issues); + checkURL(extVersion.getBugs(), "bugs", issues); + checkFieldSize(extVersion.getBugs(), DEFAULT_STRING_SIZE, "bugs", issues); + checkInvalid(extVersion.getMarkdown(), s -> !MARKDOWN_VALUES.contains(s), "markdown", issues, + MARKDOWN_VALUES.toString()); + checkCharacters(extVersion.getGalleryColor(), "galleryBanner.color", issues); + checkFieldSize(extVersion.getGalleryColor(), GALLERY_COLOR_SIZE, "galleryBanner.color", issues); + checkInvalid(extVersion.getGalleryTheme(), s -> !GALLERY_THEME_VALUES.contains(s), "galleryBanner.theme", issues, + GALLERY_THEME_VALUES.toString()); + checkFieldSize(extVersion.getLocalizedLanguages(), DEFAULT_STRING_SIZE, "localizedLanguages", issues); + checkInvalid(extVersion.getQna(), s -> !QNA_VALUES.contains(s) && isInvalidURL(s), "qna", issues, + QNA_VALUES.toString() + " or a URL"); + checkFieldSize(extVersion.getQna(), DEFAULT_STRING_SIZE, "qna", issues); + return issues; + }); } private void checkVersion(String version, List issues) { @@ -163,11 +176,14 @@ private void checkVersion(String version, List issues) { if (version.equals(VersionAlias.LATEST) || version.equals(VersionAlias.PRE_RELEASE) || version.equals("reviews")) { issues.add(new Issue("The version string '" + version + "' is reserved.")); } - try { - SemanticVersion.parse(version); - } catch (RuntimeException e) { - issues.add(new Issue(e.getMessage())); - } + + Observation.createNotStarted("SemanticVersion#parse", observations).observe(() -> { + try { + SemanticVersion.parse(version); + } catch (RuntimeException e) { + issues.add(new Issue(e.getMessage())); + } + }); } private void checkTargetPlatform(String targetPlatform, List issues) { diff --git a/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java b/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java index ab8bb57fa..47b111de2 100644 --- a/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java +++ b/server/src/main/java/org/eclipse/openvsx/IExtensionRegistry.java @@ -9,7 +9,6 @@ ********************************************************************************/ package org.eclipse.openvsx; -import io.micrometer.observation.annotation.Observed; import org.eclipse.openvsx.json.*; import org.eclipse.openvsx.search.ISearchService; import org.springframework.http.ResponseEntity; @@ -39,7 +38,6 @@ public interface IExtensionRegistry { QueryResultJson queryV2(QueryRequestV2 request); - @Observed NamespaceDetailsJson getNamespaceDetails(String namespace); ResponseEntity getNamespaceLogo(String namespaceName, String fileName); diff --git a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java index e646e407d..a08e8a0b3 100644 --- a/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/LocalRegistryService.java @@ -10,7 +10,8 @@ package org.eclipse.openvsx; import com.google.common.collect.Maps; -import io.micrometer.observation.annotation.Observed; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; @@ -62,6 +63,7 @@ public class LocalRegistryService implements IExtensionRegistry { private final EclipseService eclipse; private final CacheService cache; private final ExtensionVersionIntegrityService integrityService; + private final ObservationRegistry observations; public LocalRegistryService( EntityManager entityManager, @@ -74,7 +76,8 @@ public LocalRegistryService( StorageUtilService storageUtil, EclipseService eclipse, CacheService cache, - ExtensionVersionIntegrityService integrityService + ExtensionVersionIntegrityService integrityService, + ObservationRegistry observations ) { this.entityManager = entityManager; this.repositories = repositories; @@ -87,6 +90,7 @@ public LocalRegistryService( this.eclipse = eclipse; this.cache = cache; this.integrityService = integrityService; + this.observations = observations; } @Value("${ovsx.registry.version:}") @@ -388,7 +392,6 @@ public QueryResultJson queryV2(QueryRequestV2 request) { @Override @Transactional @Cacheable(CACHE_NAMESPACE_DETAILS_JSON) - @Observed public NamespaceDetailsJson getNamespaceDetails(String namespaceName) { var namespace = repositories.findNamespace(namespaceName); if (namespace == null) { @@ -593,38 +596,42 @@ public ResultJson verifyToken(String namespaceName, String tokenValue) { } public ExtensionJson publish(InputStream content, UserData user) throws ErrorResultException { - var token = users.createAccessToken(user, "One time use publish token"); - var json = publish(content, token.value); - users.deleteAccessToken(user, token.id); - return json; + return Observation.createNotStarted("LocalRegistryService#publish", observations).observe(() -> { + var token = users.createAccessToken(user, "One time use publish token"); + var json = publish(content, token.value); + users.deleteAccessToken(user, token.id); + return json; + }); } public ExtensionJson publish(InputStream content, String tokenValue) throws ErrorResultException { - var token = users.useAccessToken(tokenValue); - if (token == null || token.getUser() == null) { - throw new ErrorResultException("Invalid access token."); - } + return Observation.createNotStarted("LocalRegistryService#publish", observations).observe(() -> { + var token = users.useAccessToken(tokenValue); + if (token == null || token.getUser() == null) { + throw new ErrorResultException("Invalid access token."); + } - // Check whether the user has a valid publisher agreement - eclipse.checkPublisherAgreement(token.getUser()); + // Check whether the user has a valid publisher agreement + eclipse.checkPublisherAgreement(token.getUser()); - var extVersion = extensions.publishVersion(content, token); - var json = toExtensionVersionJson(extVersion, null, true); - json.success = "It can take a couple minutes before the extension version is available"; + var extVersion = extensions.publishVersion(content, token); + var json = toExtensionVersionJson(extVersion, null, true); + json.success = "It can take a couple minutes before the extension version is available"; - var sameVersions = repositories.findVersions(extVersion.getVersion(), extVersion.getExtension()); - if(sameVersions.stream().anyMatch(ev -> ev.isPreRelease() != extVersion.isPreRelease())) { - var existingRelease = extVersion.isPreRelease() ? "stable release" : "pre-release"; - var thisRelease = extVersion.isPreRelease() ? "pre-release" : "stable release"; - var extension = extVersion.getExtension(); - var semver = extVersion.getSemanticVersion(); - var newVersion = String.join(".", String.valueOf(semver.getMajor()), String.valueOf(semver.getMinor() + 1), "0"); + var sameVersions = repositories.findVersions(extVersion.getVersion(), extVersion.getExtension()); + if (sameVersions.stream().anyMatch(ev -> ev.isPreRelease() != extVersion.isPreRelease())) { + var existingRelease = extVersion.isPreRelease() ? "stable release" : "pre-release"; + var thisRelease = extVersion.isPreRelease() ? "pre-release" : "stable release"; + var extension = extVersion.getExtension(); + var semver = extVersion.getSemanticVersion(); + var newVersion = String.join(".", String.valueOf(semver.getMajor()), String.valueOf(semver.getMinor() + 1), "0"); - json.warning = "A " + existingRelease + " already exists for " + NamingUtil.toLogFormat(extension.getNamespace().getName(), extension.getName(), extVersion.getVersion()) + ".\n" + - "To prevent update conflicts, we recommend that this " + thisRelease + " uses " + newVersion + " as its version instead."; - } + json.warning = "A " + existingRelease + " already exists for " + NamingUtil.toLogFormat(extension.getNamespace().getName(), extension.getName(), extVersion.getVersion()) + ".\n" + + "To prevent update conflicts, we recommend that this " + thisRelease + " uses " + newVersion + " as its version instead."; + } - return json; + return json; + }); } @Transactional(rollbackOn = ResponseStatusException.class) @@ -796,55 +803,57 @@ private List getAllVersionReferences( } public ExtensionJson toExtensionVersionJson(ExtensionVersion extVersion, String targetPlatform, boolean onlyActive) { - var extension = extVersion.getExtension(); - var latest = repositories.findLatestVersionForAllUrls(extension, targetPlatform, false, onlyActive); - var latestPreRelease = repositories.findLatestVersionForAllUrls(extension, targetPlatform, true, onlyActive); - - var json = extVersion.toExtensionJson(); - json.preview = latest != null && latest.isPreview(); - json.versionAlias = new ArrayList<>(2); - if (latest != null && extVersion.getVersion().equals(latest.getVersion())) - json.versionAlias.add(VersionAlias.LATEST); - if (latestPreRelease != null && extVersion.getVersion().equals(latestPreRelease.getVersion())) - json.versionAlias.add(VersionAlias.PRE_RELEASE); - json.verified = isVerified(extVersion); - json.namespaceAccess = "restricted"; - json.unrelatedPublisher = !json.verified; - json.reviewCount = Optional.ofNullable(extension.getReviewCount()).orElse(0L); - var serverUrl = UrlUtil.getBaseUrl(); - json.namespaceUrl = createApiUrl(serverUrl, "api", json.namespace); - json.reviewsUrl = createApiUrl(serverUrl, "api", json.namespace, json.name, "reviews"); - - var allVersions = new ArrayList(); - if (latest != null) - allVersions.add(VersionAlias.LATEST); - if (latestPreRelease != null) - allVersions.add(VersionAlias.PRE_RELEASE); - - var versionBaseUrl = UrlUtil.createApiVersionBaseUrl(serverUrl, json.namespace, json.name, targetPlatform); - allVersions.addAll(repositories.findVersionStringsSorted(extension, targetPlatform, onlyActive)); - json.allVersionsUrl = UrlUtil.createAllVersionsUrl(json.namespace, json.name, targetPlatform, "versions"); - json.allVersions = Maps.newLinkedHashMapWithExpectedSize(allVersions.size()); - for(var version : allVersions) { - json.allVersions.put(version, createApiUrl(versionBaseUrl, version)); - } + return Observation.createNotStarted("LocalRegistryService#toExtensionVersionJson", observations).observe(() -> { + var extension = extVersion.getExtension(); + var latest = repositories.findLatestVersionForAllUrls(extension, targetPlatform, false, onlyActive); + var latestPreRelease = repositories.findLatestVersionForAllUrls(extension, targetPlatform, true, onlyActive); + + var json = extVersion.toExtensionJson(); + json.preview = latest != null && latest.isPreview(); + json.versionAlias = new ArrayList<>(2); + if (latest != null && extVersion.getVersion().equals(latest.getVersion())) + json.versionAlias.add(VersionAlias.LATEST); + if (latestPreRelease != null && extVersion.getVersion().equals(latestPreRelease.getVersion())) + json.versionAlias.add(VersionAlias.PRE_RELEASE); + json.verified = isVerified(extVersion); + json.namespaceAccess = "restricted"; + json.unrelatedPublisher = !json.verified; + json.reviewCount = Optional.ofNullable(extension.getReviewCount()).orElse(0L); + var serverUrl = UrlUtil.getBaseUrl(); + json.namespaceUrl = createApiUrl(serverUrl, "api", json.namespace); + json.reviewsUrl = createApiUrl(serverUrl, "api", json.namespace, json.name, "reviews"); + + var allVersions = new ArrayList(); + if (latest != null) + allVersions.add(VersionAlias.LATEST); + if (latestPreRelease != null) + allVersions.add(VersionAlias.PRE_RELEASE); + + var versionBaseUrl = UrlUtil.createApiVersionBaseUrl(serverUrl, json.namespace, json.name, targetPlatform); + allVersions.addAll(repositories.findVersionStringsSorted(extension, targetPlatform, onlyActive)); + json.allVersionsUrl = UrlUtil.createAllVersionsUrl(json.namespace, json.name, targetPlatform, "versions"); + json.allVersions = Maps.newLinkedHashMapWithExpectedSize(allVersions.size()); + for (var version : allVersions) { + json.allVersions.put(version, createApiUrl(versionBaseUrl, version)); + } - var fileUrls = storageUtil.getFileUrls(List.of(extVersion), serverUrl, withFileTypes(DOWNLOAD, MANIFEST, ICON, README, LICENSE, CHANGELOG, VSIXMANIFEST)); - json.files = fileUrls.get(extVersion.getId()); - if(json.files.containsKey(DOWNLOAD_SIG)) { - json.files.put(PUBLIC_KEY, UrlUtil.getPublicKeyUrl(extVersion)); - } - if (json.dependencies != null) { - json.dependencies.forEach(ref -> { - ref.url = createApiUrl(serverUrl, "api", ref.namespace, ref.extension); - }); - } - if (json.bundledExtensions != null) { - json.bundledExtensions.forEach(ref -> { - ref.url = createApiUrl(serverUrl, "api", ref.namespace, ref.extension); - }); - } - return json; + var fileUrls = storageUtil.getFileUrls(List.of(extVersion), serverUrl, withFileTypes(DOWNLOAD, MANIFEST, ICON, README, LICENSE, CHANGELOG, VSIXMANIFEST)); + json.files = fileUrls.get(extVersion.getId()); + if (json.files.containsKey(DOWNLOAD_SIG)) { + json.files.put(PUBLIC_KEY, UrlUtil.getPublicKeyUrl(extVersion)); + } + if (json.dependencies != null) { + json.dependencies.forEach(ref -> { + ref.url = createApiUrl(serverUrl, "api", ref.namespace, ref.extension); + }); + } + if (json.bundledExtensions != null) { + json.bundledExtensions.forEach(ref -> { + ref.url = createApiUrl(serverUrl, "api", ref.namespace, ref.extension); + }); + } + return json; + }); } public ExtensionJson toExtensionVersionJson( @@ -993,17 +1002,19 @@ public ExtensionJson toExtensionVersionJsonV2( } private boolean isVerified(ExtensionVersion extVersion) { - if (extVersion.getPublishedWith() == null) { - return false; - } + return Observation.createNotStarted("LocalRegistryService#isVerified", observations).observe(() -> { + if (extVersion.getPublishedWith() == null) { + return false; + } - var user = extVersion.getPublishedWith().getUser(); - if(UserData.ROLE_PRIVILEGED.equals(user.getRole())) { - return true; - } + var user = extVersion.getPublishedWith().getUser(); + if (UserData.ROLE_PRIVILEGED.equals(user.getRole())) { + return true; + } - var namespace = extVersion.getExtension().getNamespace(); - return repositories.isVerified(namespace, user); + var namespace = extVersion.getExtension().getNamespace(); + return repositories.isVerified(namespace, user); + }); } private boolean isVerified(ExtensionVersion extVersion, Map> membershipsByNamespaceId) { diff --git a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java index 73b5d928c..1192e8b2b 100644 --- a/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java +++ b/server/src/main/java/org/eclipse/openvsx/RegistryAPI.java @@ -10,7 +10,8 @@ package org.eclipse.openvsx; import com.google.common.collect.Iterables; -import io.micrometer.observation.annotation.Observed; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.headers.Header; @@ -49,15 +50,18 @@ public class RegistryAPI { private final LocalRegistryService local; private final UpstreamRegistryService upstream; private final UserService users; + private final ObservationRegistry observations; public RegistryAPI( LocalRegistryService local, UpstreamRegistryService upstream, - UserService users + UserService users, + ObservationRegistry observations ) { this.local = local; this.upstream = upstream; this.users = users; + this.observations = observations; } protected Iterable getRegistries() { @@ -180,7 +184,6 @@ public ResponseEntity verifyToken( @CrossOrigin @Operation() @ApiResponses({}) - @Observed public ResponseEntity getNamespaceDetails( @PathVariable @Parameter(description = "Namespace name", example = "redhat") String namespace @@ -1536,16 +1539,18 @@ public ResponseEntity publish( InputStream content, @RequestParam @Parameter(description = "A personal access token") String token ) { - try { - var json = local.publish(content, token); - var serverUrl = UrlUtil.getBaseUrl(); - var url = UrlUtil.createApiVersionUrl(serverUrl, json); - return ResponseEntity.status(HttpStatus.CREATED) - .location(URI.create(url)) - .body(json); - } catch (ErrorResultException exc) { - return exc.toResponseEntity(ExtensionJson.class); - } + return Observation.createNotStarted("RegistryAPI#publish", observations).observe(() -> { + try { + var json = local.publish(content, token); + var serverUrl = UrlUtil.getBaseUrl(); + var url = UrlUtil.createApiVersionUrl(serverUrl, json); + return ResponseEntity.status(HttpStatus.CREATED) + .location(URI.create(url)) + .body(json); + } catch (ErrorResultException exc) { + return exc.toResponseEntity(ExtensionJson.class); + } + }); } @PostMapping( @@ -1602,21 +1607,23 @@ public ResponseEntity publish( ) }) public ResponseEntity publish(InputStream content) { - try { - var user = users.findLoggedInUser(); - if (user == null) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN); - } + return Observation.createNotStarted("RegistryAPI#publish", observations).observe(() -> { + try { + var user = users.findLoggedInUser(); + if (user == null) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN); + } - var json = local.publish(content, user); - var serverUrl = UrlUtil.getBaseUrl(); - var url = UrlUtil.createApiUrl(serverUrl, "api", json.namespace, json.name, json.version); - return ResponseEntity.status(HttpStatus.CREATED) - .location(URI.create(url)) - .body(json); - } catch (ErrorResultException exc) { - return exc.toResponseEntity(ExtensionJson.class); - } + var json = local.publish(content, user); + var serverUrl = UrlUtil.getBaseUrl(); + var url = UrlUtil.createApiUrl(serverUrl, "api", json.namespace, json.name, json.version); + return ResponseEntity.status(HttpStatus.CREATED) + .location(URI.create(url)) + .body(json); + } catch (ErrorResultException exc) { + return exc.toResponseEntity(ExtensionJson.class); + } + }); } @PostMapping( diff --git a/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java b/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java index 9cbbc4370..be086dedf 100644 --- a/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java +++ b/server/src/main/java/org/eclipse/openvsx/UpstreamRegistryService.java @@ -9,7 +9,6 @@ ********************************************************************************/ package org.eclipse.openvsx; -import io.micrometer.observation.annotation.Observed; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.json.*; import org.eclipse.openvsx.search.ISearchService; @@ -72,7 +71,6 @@ public NamespaceJson getNamespace(String namespace) { } @Override - @Observed public NamespaceDetailsJson getNamespaceDetails(String namespace) { var urlTemplate = urlConfigService.getUpstreamUrl() + "/api/{namespace}/details"; var uriVariables = Map.of("namespace", namespace); diff --git a/server/src/main/java/org/eclipse/openvsx/UserService.java b/server/src/main/java/org/eclipse/openvsx/UserService.java index d356ba020..d809441f6 100644 --- a/server/src/main/java/org/eclipse/openvsx/UserService.java +++ b/server/src/main/java/org/eclipse/openvsx/UserService.java @@ -10,6 +10,8 @@ package org.eclipse.openvsx; import com.google.common.base.Joiner; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.eclipse.openvsx.cache.CacheService; @@ -43,30 +45,35 @@ public class UserService { private final StorageUtilService storageUtil; private final CacheService cache; private final ExtensionValidator validator; + private final ObservationRegistry observations; public UserService( EntityManager entityManager, RepositoryService repositories, StorageUtilService storageUtil, CacheService cache, - ExtensionValidator validator + ExtensionValidator validator, + ObservationRegistry observations ) { this.entityManager = entityManager; this.repositories = repositories; this.storageUtil = storageUtil; this.cache = cache; this.validator = validator; + this.observations = observations; } public UserData findLoggedInUser() { - var authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication != null) { - if (authentication.getPrincipal() instanceof IdPrincipal) { - var principal = (IdPrincipal) authentication.getPrincipal(); - return entityManager.find(UserData.class, principal.getId()); + return Observation.createNotStarted("UserService#findLoggedInUser", observations).observe(() -> { + var authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication != null) { + if (authentication.getPrincipal() instanceof IdPrincipal) { + var principal = (IdPrincipal) authentication.getPrincipal(); + return entityManager.find(UserData.class, principal.getId()); + } } - } - return null; + return null; + }); } @Transactional @@ -121,12 +128,14 @@ public UserData updateExistingUser(UserData user, OAuth2User oauth2User) { @Transactional public PersonalAccessToken useAccessToken(String tokenValue) { - var token = repositories.findAccessToken(tokenValue); - if (token == null || !token.isActive()) { - return null; - } - token.setAccessedTimestamp(TimeUtil.getCurrentUTC()); - return token; + return Observation.createNotStarted("UserService#useAccessToken", observations).observe(() -> { + var token = repositories.findAccessToken(tokenValue); + if (token == null || !token.isActive()) { + return null; + } + token.setAccessedTimestamp(TimeUtil.getCurrentUTC()); + return token; + }); } public String generateTokenValue() { @@ -138,19 +147,22 @@ public String generateTokenValue() { } public boolean hasPublishPermission(UserData user, Namespace namespace) { - if (UserData.ROLE_PRIVILEGED.equals(user.getRole())) { - // Privileged users can publish to every namespace. - return true; - } + return Observation.createNotStarted("UserService#hasPublishPermission", observations).observe(() -> { - var membership = repositories.findMembership(user, namespace); - if (membership == null) { - // The requesting user is not a member of the namespace. - return false; - } - var role = membership.getRole(); - return NamespaceMembership.ROLE_CONTRIBUTOR.equalsIgnoreCase(role) - || NamespaceMembership.ROLE_OWNER.equalsIgnoreCase(role); + if (UserData.ROLE_PRIVILEGED.equals(user.getRole())) { + // Privileged users can publish to every namespace. + return true; + } + + var membership = repositories.findMembership(user, namespace); + if (membership == null) { + // The requesting user is not a member of the namespace. + return false; + } + var role = membership.getRole(); + return NamespaceMembership.ROLE_CONTRIBUTOR.equalsIgnoreCase(role) + || NamespaceMembership.ROLE_OWNER.equalsIgnoreCase(role); + }); } @Transactional(rollbackOn = ErrorResultException.class) @@ -272,19 +284,21 @@ private void storeNamespaceLogo(Namespace namespace) { } @Transactional public AccessTokenJson createAccessToken(UserData user, String description) { - var token = new PersonalAccessToken(); - token.setUser(user); - token.setValue(generateTokenValue()); - token.setActive(true); - token.setCreatedTimestamp(TimeUtil.getCurrentUTC()); - token.setDescription(description); - entityManager.persist(token); - var json = token.toAccessTokenJson(); - // Include the token value after creation so the user can copy it - json.value = token.getValue(); - json.deleteTokenUrl = createApiUrl(UrlUtil.getBaseUrl(), "user", "token", "delete", Long.toString(token.getId())); + return Observation.createNotStarted("UserService#createAccessToken", observations).observe(() -> { + var token = new PersonalAccessToken(); + token.setUser(user); + token.setValue(generateTokenValue()); + token.setActive(true); + token.setCreatedTimestamp(TimeUtil.getCurrentUTC()); + token.setDescription(description); + entityManager.persist(token); + var json = token.toAccessTokenJson(); + // Include the token value after creation so the user can copy it + json.value = token.getValue(); + json.deleteTokenUrl = createApiUrl(UrlUtil.getBaseUrl(), "user", "token", "delete", Long.toString(token.getId())); - return json; + return json; + }); } @Transactional diff --git a/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java b/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java index 44621b855..e102904f3 100644 --- a/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java +++ b/server/src/main/java/org/eclipse/openvsx/cache/CacheService.java @@ -9,6 +9,8 @@ * ****************************************************************************** */ package org.eclipse.openvsx.cache; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.entities.UserData; @@ -38,17 +40,20 @@ public class CacheService { private final RepositoryService repositoryService; private final ExtensionJsonCacheKeyGenerator extensionJsonCacheKey; private final LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKey; + private final ObservationRegistry observations; public CacheService( CacheManager cacheManager, RepositoryService repositoryService, ExtensionJsonCacheKeyGenerator extensionJsonCacheKey, - LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKey + LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKey, + ObservationRegistry observations ) { this.cacheManager = cacheManager; this.repositoryService = repositoryService; this.extensionJsonCacheKey = extensionJsonCacheKey; this.latestExtensionVersionCacheKey = latestExtensionVersionCacheKey; + this.observations = observations; } public void evictNamespaceDetails() { @@ -81,28 +86,30 @@ public void evictExtensionJsons(UserData user) { } public void evictExtensionJsons(Extension extension) { - var cache = cacheManager.getCache(CACHE_EXTENSION_JSON); - if(cache == null) { - return; // cache is not created - } - if(extension.getVersions() == null) { - return; - } - - var versions = new ArrayList<>(VersionAlias.ALIAS_NAMES); - extension.getVersions().stream() - .map(ExtensionVersion::getVersion) - .forEach(versions::add); +// Observation.createNotStarted("CacheService#evictExtensionJsons", observations).observe(() -> { + var cache = cacheManager.getCache(CACHE_EXTENSION_JSON); + if (cache == null) { + return; // cache is not created + } + if (extension.getVersions() == null) { + return; + } - var namespaceName = extension.getNamespace().getName(); - var extensionName = extension.getName(); - var targetPlatforms = new ArrayList<>(TargetPlatform.TARGET_PLATFORM_NAMES); - targetPlatforms.add("null"); - for(var version : versions) { - for(var targetPlatform : targetPlatforms) { - cache.evictIfPresent(extensionJsonCacheKey.generate(namespaceName, extensionName, targetPlatform, version)); + var versions = new ArrayList<>(VersionAlias.ALIAS_NAMES); + extension.getVersions().stream() + .map(ExtensionVersion::getVersion) + .forEach(versions::add); + + var namespaceName = extension.getNamespace().getName(); + var extensionName = extension.getName(); + var targetPlatforms = new ArrayList<>(TargetPlatform.TARGET_PLATFORM_NAMES); + targetPlatforms.add("null"); + for (var version : versions) { + for (var targetPlatform : targetPlatforms) { + cache.evictIfPresent(extensionJsonCacheKey.generate(namespaceName, extensionName, targetPlatform, version)); + } } - } +// }); } public void evictExtensionJsons(ExtensionVersion extVersion) { @@ -130,23 +137,25 @@ public void evictLatestExtensionVersions() { } public void evictLatestExtensionVersion(Extension extension) { - var cache = cacheManager.getCache(CACHE_LATEST_EXTENSION_VERSION); - if(cache == null) { - return; - } +// Observation.createNotStarted("CacheService#evictLatestExtensionVersion", observations).observe(() -> { + var cache = cacheManager.getCache(CACHE_LATEST_EXTENSION_VERSION); + if(cache == null) { + return; + } - var targetPlatforms = new ArrayList<>(TargetPlatform.TARGET_PLATFORM_NAMES); - targetPlatforms.add(null); - for (var targetPlatform : targetPlatforms) { - for (var preRelease : List.of(true, false)) { - for (var onlyActive : List.of(true, false)) { - for(var type : ExtensionVersion.Type.values()) { - var key = latestExtensionVersionCacheKey.generate(extension, targetPlatform, preRelease, onlyActive, type); - cache.evictIfPresent(key); + var targetPlatforms = new ArrayList<>(TargetPlatform.TARGET_PLATFORM_NAMES); + targetPlatforms.add(null); + for (var targetPlatform : targetPlatforms) { + for (var preRelease : List.of(true, false)) { + for (var onlyActive : List.of(true, false)) { + for(var type : ExtensionVersion.Type.values()) { + var key = latestExtensionVersionCacheKey.generate(extension, targetPlatform, preRelease, onlyActive, type); + cache.evictIfPresent(key); + } } } } - } +// }); } private void invalidateCache(String cacheName) { diff --git a/server/src/main/java/org/eclipse/openvsx/cache/ExtensionJsonCacheKeyGenerator.java b/server/src/main/java/org/eclipse/openvsx/cache/ExtensionJsonCacheKeyGenerator.java index abeb45c11..628fe7160 100644 --- a/server/src/main/java/org/eclipse/openvsx/cache/ExtensionJsonCacheKeyGenerator.java +++ b/server/src/main/java/org/eclipse/openvsx/cache/ExtensionJsonCacheKeyGenerator.java @@ -9,7 +9,6 @@ * ****************************************************************************** */ package org.eclipse.openvsx.cache; -import io.micrometer.observation.annotation.Observed; import org.eclipse.openvsx.util.NamingUtil; import org.eclipse.openvsx.util.VersionAlias; import org.springframework.cache.interceptor.KeyGenerator; @@ -19,6 +18,7 @@ @Component public class ExtensionJsonCacheKeyGenerator implements KeyGenerator { + @Override public Object generate(Object target, Method method, Object... params) { var version = params.length == 4 ? (String) params[3] : VersionAlias.LATEST; diff --git a/server/src/main/java/org/eclipse/openvsx/cache/LatestExtensionVersionCacheKeyGenerator.java b/server/src/main/java/org/eclipse/openvsx/cache/LatestExtensionVersionCacheKeyGenerator.java index b32fad852..311a04bdb 100644 --- a/server/src/main/java/org/eclipse/openvsx/cache/LatestExtensionVersionCacheKeyGenerator.java +++ b/server/src/main/java/org/eclipse/openvsx/cache/LatestExtensionVersionCacheKeyGenerator.java @@ -9,7 +9,6 @@ * ****************************************************************************** */ package org.eclipse.openvsx.cache; -import io.micrometer.observation.annotation.Observed; import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.util.NamingUtil; @@ -22,6 +21,7 @@ @Component public class LatestExtensionVersionCacheKeyGenerator implements KeyGenerator { + @Override public Object generate(Object target, Method method, Object... params) { Extension extension; diff --git a/server/src/main/java/org/eclipse/openvsx/eclipse/EclipseService.java b/server/src/main/java/org/eclipse/openvsx/eclipse/EclipseService.java index 7d67aaf98..3a55a5da1 100644 --- a/server/src/main/java/org/eclipse/openvsx/eclipse/EclipseService.java +++ b/server/src/main/java/org/eclipse/openvsx/eclipse/EclipseService.java @@ -14,6 +14,8 @@ import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.Lists; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; @@ -72,6 +74,7 @@ public class EclipseService { private final EntityManager entityManager; private final RestTemplate restTemplate; private final ObjectMapper objectMapper; + private final ObservationRegistry observations; @Value("${ovsx.eclipse.base-url:}") String eclipseApiUrl; @@ -87,7 +90,8 @@ public EclipseService( TransactionTemplate transactions, ExtensionService extensions, EntityManager entityManager, - RestTemplate restTemplate + RestTemplate restTemplate, + ObservationRegistry observations ) { this.tokens = tokens; this.transactions = transactions; @@ -96,6 +100,7 @@ public EclipseService( this.restTemplate = restTemplate; this.objectMapper = new ObjectMapper(); this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + this.observations = observations; } private final Function parseDate = dateString -> { @@ -120,28 +125,30 @@ public boolean isActive() { * @throws ErrorResultException if the user has no active agreement */ public void checkPublisherAgreement(UserData user) { - if (!isActive()) { - return; - } - // Users without authentication provider have been created directly in the DB, - // so we skip the agreement check in this case. - if (user.getProvider() == null) { - return; - } - var eclipseData = user.getEclipseData(); - if (eclipseData == null || eclipseData.personId == null) { - throw new ErrorResultException("You must log in with an Eclipse Foundation account and sign a Publisher Agreement before publishing any extension."); - } - var profile = getPublicProfile(eclipseData.personId); - if (profile.publisherAgreements == null || profile.publisherAgreements.openVsx == null - || profile.publisherAgreements.openVsx.version == null) { - throw new ErrorResultException("You must sign a Publisher Agreement with the Eclipse Foundation before publishing any extension."); - } - if (!publisherAgreementVersion.equals(profile.publisherAgreements.openVsx.version)) { - throw new ErrorResultException("Your Publisher Agreement with the Eclipse Foundation is outdated (version " - + profile.publisherAgreements.openVsx.version + "). The current version is " - + publisherAgreementVersion + "."); - } + Observation.createNotStarted("EclipseService#checkPublisherAgreement", observations).observe(() -> { + if (!isActive()) { + return; + } + // Users without authentication provider have been created directly in the DB, + // so we skip the agreement check in this case. + if (user.getProvider() == null) { + return; + } + var eclipseData = user.getEclipseData(); + if (eclipseData == null || eclipseData.personId == null) { + throw new ErrorResultException("You must log in with an Eclipse Foundation account and sign a Publisher Agreement before publishing any extension."); + } + var profile = getPublicProfile(eclipseData.personId); + if (profile.publisherAgreements == null || profile.publisherAgreements.openVsx == null + || profile.publisherAgreements.openVsx.version == null) { + throw new ErrorResultException("You must sign a Publisher Agreement with the Eclipse Foundation before publishing any extension."); + } + if (!publisherAgreementVersion.equals(profile.publisherAgreements.openVsx.version)) { + throw new ErrorResultException("Your Publisher Agreement with the Eclipse Foundation is outdated (version " + + profile.publisherAgreements.openVsx.version + "). The current version is " + + publisherAgreementVersion + "."); + } + }); } /** @@ -232,28 +239,30 @@ else if (publisherAgreementVersion.equals(agreement.version)) * Get the publicly available user profile. */ public EclipseProfile getPublicProfile(String personId) { - checkApiUrl(); - var urlTemplate = eclipseApiUrl + "account/profile/{personId}"; - var uriVariables = Map.of("personId", personId); - var headers = new HttpHeaders(); - headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); - var request = new HttpEntity(headers); + return Observation.createNotStarted("EclipseService#getPublicProfile", observations).observe(() -> { + checkApiUrl(); + var urlTemplate = eclipseApiUrl + "account/profile/{personId}"; + var uriVariables = Map.of("personId", personId); + var headers = new HttpHeaders(); + headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON)); + var request = new HttpEntity(headers); - try { - var response = restTemplate.exchange(urlTemplate, HttpMethod.GET, request, String.class, uriVariables); - return parseEclipseProfile(response); - } catch (RestClientException exc) { - if (exc instanceof HttpStatusCodeException) { - var status = ((HttpStatusCodeException) exc).getStatusCode(); - if (status == HttpStatus.NOT_FOUND) - throw new ErrorResultException("No Eclipse profile data available for user: " + personId); - } + try { + var response = restTemplate.exchange(urlTemplate, HttpMethod.GET, request, String.class, uriVariables); + return parseEclipseProfile(response); + } catch (RestClientException exc) { + if (exc instanceof HttpStatusCodeException) { + var status = ((HttpStatusCodeException) exc).getStatusCode(); + if (status == HttpStatus.NOT_FOUND) + throw new ErrorResultException("No Eclipse profile data available for user: " + personId); + } - var url = UriComponentsBuilder.fromUriString(urlTemplate).build(uriVariables); - logger.error("Get request failed with URL: " + url, exc); - throw new ErrorResultException("Request for retrieving user profile failed: " + exc.getMessage(), - HttpStatus.INTERNAL_SERVER_ERROR); - } + var url = UriComponentsBuilder.fromUriString(urlTemplate).build(uriVariables); + logger.error("Get request failed with URL: " + url, exc); + throw new ErrorResultException("Request for retrieving user profile failed: " + exc.getMessage(), + HttpStatus.INTERNAL_SERVER_ERROR); + } + }); } /** diff --git a/server/src/main/java/org/eclipse/openvsx/metrics/MetricsConfiguration.java b/server/src/main/java/org/eclipse/openvsx/metrics/MetricsConfiguration.java index 979d82460..99526f5d1 100644 --- a/server/src/main/java/org/eclipse/openvsx/metrics/MetricsConfiguration.java +++ b/server/src/main/java/org/eclipse/openvsx/metrics/MetricsConfiguration.java @@ -21,7 +21,7 @@ @Configuration @Profile("!test") public class MetricsConfiguration { - @Bean + //@Bean public ObservedAspect observedAspect(ObservationRegistry observationRegistry) { return new ObservedAspect(observationRegistry, new RegistryObservationConvention()); } diff --git a/server/src/main/java/org/eclipse/openvsx/metrics/RegistryObservationConvention.java b/server/src/main/java/org/eclipse/openvsx/metrics/RegistryObservationConvention.java index aa8c71991..1f9cb6c67 100644 --- a/server/src/main/java/org/eclipse/openvsx/metrics/RegistryObservationConvention.java +++ b/server/src/main/java/org/eclipse/openvsx/metrics/RegistryObservationConvention.java @@ -28,18 +28,18 @@ public RegistryObservationConvention() { @Override public KeyValues getHighCardinalityKeyValues(ObservedAspect.ObservedAspectContext context) { - var joinPoint = context.getProceedingJoinPoint(); - var args = joinPoint.getArgs(); - var methodSignature = (MethodSignature) joinPoint.getSignature(); - var parameterNames = methodSignature.getParameterNames(); - var argKeyValues = new KeyValue[args.length]; - for(var i = 0; i < args.length; i++) { - var key = "args." + parameterNames[i]; - var value = convertObjectToString(args[i]); - argKeyValues[i] = KeyValue.of(key, value); - } +// var joinPoint = context.getProceedingJoinPoint(); +// var args = joinPoint.getArgs(); +// var methodSignature = (MethodSignature) joinPoint.getSignature(); +// var parameterNames = methodSignature.getParameterNames(); +// var argKeyValues = new KeyValue[args.length]; +// for(var i = 0; i < args.length; i++) { +// var key = "args." + parameterNames[i]; +// var value = convertObjectToString(args[i]); +// argKeyValues[i] = KeyValue.of(key, value); +// } - return ObservationConvention.super.getHighCardinalityKeyValues(context).and(argKeyValues); + return ObservationConvention.super.getHighCardinalityKeyValues(context);//.and(argKeyValues); } private String convertObjectToString(Object arg) { diff --git a/server/src/main/java/org/eclipse/openvsx/migration/ExtractResourcesJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/migration/ExtractResourcesJobRequestHandler.java index 58cab8120..443b06e2a 100644 --- a/server/src/main/java/org/eclipse/openvsx/migration/ExtractResourcesJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/migration/ExtractResourcesJobRequestHandler.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.migration; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.ExtensionProcessor; import org.eclipse.openvsx.util.NamingUtil; import org.jobrunr.jobs.annotations.Job; @@ -52,7 +53,7 @@ public void run(MigrationJobRequest jobRequest) throws Exception { if(Files.size(extensionFile.getPath()) == 0) { return; } - try (var extProcessor = new ExtensionProcessor(extensionFile)) { + try (var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { extProcessor.processEachResource(download.getExtension(), (resource) -> { resource.setStorageType(download.getStorageType()); migrations.uploadFileResource(resource); diff --git a/server/src/main/java/org/eclipse/openvsx/migration/ExtractVsixManifestsJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/migration/ExtractVsixManifestsJobRequestHandler.java index aba57f31d..adbf3f56f 100644 --- a/server/src/main/java/org/eclipse/openvsx/migration/ExtractVsixManifestsJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/migration/ExtractVsixManifestsJobRequestHandler.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.migration; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.ExtensionProcessor; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.NamingUtil; @@ -54,7 +55,7 @@ public void run(MigrationJobRequest jobRequest) throws Exception { if(Files.size(extensionFile.getPath()) == 0) { return; } - try (var extProcessor = new ExtensionProcessor(extensionFile)) { + try (var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { var vsixManifest = extProcessor.getVsixManifest(extVersion); vsixManifest.setStorageType(download.getStorageType()); migrations.uploadFileResource(vsixManifest); diff --git a/server/src/main/java/org/eclipse/openvsx/migration/FixTargetPlatformsJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/migration/FixTargetPlatformsJobRequestHandler.java index f4fb58ad6..79913648f 100644 --- a/server/src/main/java/org/eclipse/openvsx/migration/FixTargetPlatformsJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/migration/FixTargetPlatformsJobRequestHandler.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.migration; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.ExtensionProcessor; import org.eclipse.openvsx.ExtensionService; import org.eclipse.openvsx.admin.AdminService; @@ -60,7 +61,7 @@ public void run(MigrationJobRequest jobRequest) throws Exception { } boolean fixTargetPlatform; - try (var extProcessor = new ExtensionProcessor(extensionFile)) { + try (var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { fixTargetPlatform = !extProcessor.getMetadata().getTargetPlatform().equals(extVersion.getTargetPlatform()); } diff --git a/server/src/main/java/org/eclipse/openvsx/migration/GenerateSha256ChecksumJobRequestHandler.java b/server/src/main/java/org/eclipse/openvsx/migration/GenerateSha256ChecksumJobRequestHandler.java index 52f1e4048..47bb814bc 100644 --- a/server/src/main/java/org/eclipse/openvsx/migration/GenerateSha256ChecksumJobRequestHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/migration/GenerateSha256ChecksumJobRequestHandler.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.migration; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.ExtensionProcessor; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.NamingUtil; @@ -52,7 +53,7 @@ public void run(MigrationJobRequest jobRequest) throws Exception { if(Files.size(extensionFile.getPath()) == 0) { return; } - try (var extProcessor = new ExtensionProcessor(extensionFile)) { + try (var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { var checksum = extProcessor.generateSha256Checksum(extVersion); checksum.setStorageType(download.getStorageType()); migrations.uploadFileResource(checksum); diff --git a/server/src/main/java/org/eclipse/openvsx/migration/SetPreReleaseJobService.java b/server/src/main/java/org/eclipse/openvsx/migration/SetPreReleaseJobService.java index 3e87dc0bf..bda24f5d8 100644 --- a/server/src/main/java/org/eclipse/openvsx/migration/SetPreReleaseJobService.java +++ b/server/src/main/java/org/eclipse/openvsx/migration/SetPreReleaseJobService.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.migration; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.eclipse.openvsx.ExtensionProcessor; @@ -39,7 +40,7 @@ public List getExtensionVersions(MigrationJobRequest jobReques @Transactional public void updatePreviewAndPreRelease(ExtensionVersion extVersion, TempFile extensionFile) { - try(var extProcessor = new ExtensionProcessor(extensionFile)) { + try(var extProcessor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { extVersion.setPreRelease(extProcessor.isPreRelease()); extVersion.setPreview(extProcessor.isPreview()); } diff --git a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java index e81c0009f..4987421f1 100644 --- a/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java +++ b/server/src/main/java/org/eclipse/openvsx/publish/PublishExtensionVersionHandler.java @@ -10,6 +10,8 @@ package org.eclipse.openvsx.publish; import com.google.common.base.Joiner; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; @@ -47,6 +49,7 @@ public class PublishExtensionVersionHandler { private final JobRequestScheduler scheduler; private final UserService users; private final ExtensionValidator validator; + private final ObservationRegistry observations; public PublishExtensionVersionHandler( PublishExtensionVersionService service, @@ -55,7 +58,8 @@ public PublishExtensionVersionHandler( RepositoryService repositories, JobRequestScheduler scheduler, UserService users, - ExtensionValidator validator + ExtensionValidator validator, + ObservationRegistry observations ) { this.service = service; this.integrityService = integrityService; @@ -64,118 +68,128 @@ public PublishExtensionVersionHandler( this.scheduler = scheduler; this.users = users; this.validator = validator; + this.observations = observations; } @Transactional(rollbackOn = ErrorResultException.class) public ExtensionVersion createExtensionVersion(ExtensionProcessor processor, PersonalAccessToken token, LocalDateTime timestamp, boolean checkDependencies) { - // Extract extension metadata from its manifest - var extVersion = createExtensionVersion(processor, token.getUser(), token, timestamp); - var dependencies = processor.getExtensionDependencies(); - var bundledExtensions = processor.getBundledExtensions(); - if (checkDependencies) { - dependencies = dependencies.stream() - .map(this::checkDependency) - .collect(Collectors.toList()); - bundledExtensions = bundledExtensions.stream() - .map(this::checkBundledExtension) - .collect(Collectors.toList()); - } + return Observation.createNotStarted("PublishExtensionVersionHandler#createExtensionVersion", observations).observe(() -> { + // Extract extension metadata from its manifest + var extVersion = createExtensionVersion(processor, token.getUser(), token, timestamp); + var dependencies = processor.getExtensionDependencies(); + var bundledExtensions = processor.getBundledExtensions(); + if (checkDependencies) { + dependencies = dependencies.stream() + .map(this::checkDependency) + .collect(Collectors.toList()); + bundledExtensions = bundledExtensions.stream() + .map(this::checkBundledExtension) + .collect(Collectors.toList()); + } - extVersion.setDependencies(dependencies); - extVersion.setBundledExtensions(bundledExtensions); - if(integrityService.isEnabled()) { - extVersion.setSignatureKeyPair(repositories.findActiveKeyPair()); - } + extVersion.setDependencies(dependencies); + extVersion.setBundledExtensions(bundledExtensions); + if(integrityService.isEnabled()) { + extVersion.setSignatureKeyPair(repositories.findActiveKeyPair()); + } - return extVersion; + return extVersion; + }); } private ExtensionVersion createExtensionVersion(ExtensionProcessor processor, UserData user, PersonalAccessToken token, LocalDateTime timestamp) { - var namespaceName = processor.getNamespace(); - var namespace = repositories.findNamespace(namespaceName); - if (namespace == null) { - throw new ErrorResultException("Unknown publisher: " + namespaceName - + "\nUse the 'create-namespace' command to create a namespace corresponding to your publisher name."); - } - if (!users.hasPublishPermission(user, namespace)) { - throw new ErrorResultException("Insufficient access rights for publisher: " + namespace.getName()); - } + return Observation.createNotStarted("PublishExtensionVersionHandler#createExtensionVersion", observations).observe(() -> { + var namespaceName = processor.getNamespace(); + var namespace = repositories.findNamespace(namespaceName); + if (namespace == null) { + throw new ErrorResultException("Unknown publisher: " + namespaceName + + "\nUse the 'create-namespace' command to create a namespace corresponding to your publisher name."); + } + if (!users.hasPublishPermission(user, namespace)) { + throw new ErrorResultException("Insufficient access rights for publisher: " + namespace.getName()); + } - var extensionName = processor.getExtensionName(); - var nameIssue = validator.validateExtensionName(extensionName); - if (nameIssue.isPresent()) { - throw new ErrorResultException(nameIssue.get().toString()); - } + var extensionName = processor.getExtensionName(); + var nameIssue = validator.validateExtensionName(extensionName); + if (nameIssue.isPresent()) { + throw new ErrorResultException(nameIssue.get().toString()); + } - var versionIssue = validator.validateExtensionVersion(processor.getVersion()); - if (versionIssue.isPresent()) { - throw new ErrorResultException(versionIssue.get().toString()); - } + var version = processor.getVersion(); + var versionIssue = validator.validateExtensionVersion(version); + if (versionIssue.isPresent()) { + throw new ErrorResultException(versionIssue.get().toString()); + } - var extVersion = processor.getMetadata(); - if (extVersion.getDisplayName() != null && extVersion.getDisplayName().trim().isEmpty()) { - extVersion.setDisplayName(null); - } - extVersion.setTimestamp(timestamp); - extVersion.setPublishedWith(token); - extVersion.setActive(false); - - var extension = repositories.findExtension(extensionName, namespace); - if (extension == null) { - extension = new Extension(); - extension.setActive(false); - extension.setName(extensionName); - extension.setNamespace(namespace); - extension.setPublishedDate(extVersion.getTimestamp()); - - entityManager.persist(extension); - } else { - var existingVersion = repositories.findVersion(extVersion.getVersion(), extVersion.getTargetPlatform(), extension); - if (existingVersion != null) { - var extVersionId = NamingUtil.toLogFormat(namespaceName, extensionName, extVersion.getTargetPlatform(), extVersion.getVersion()); - var message = "Extension " + extVersionId + " is already published"; - message += existingVersion.isActive() ? "." : ", but currently isn't active and therefore not visible."; - throw new ErrorResultException(message); + var extVersion = processor.getMetadata(); + if (extVersion.getDisplayName() != null && extVersion.getDisplayName().trim().isEmpty()) { + extVersion.setDisplayName(null); } - } + extVersion.setTimestamp(timestamp); + extVersion.setPublishedWith(token); + extVersion.setActive(false); - extension.setLastUpdatedDate(extVersion.getTimestamp()); - extension.getVersions().add(extVersion); - extVersion.setExtension(extension); + var extension = repositories.findExtension(extensionName, namespace); + if (extension == null) { + extension = new Extension(); + extension.setActive(false); + extension.setName(extensionName); + extension.setNamespace(namespace); + extension.setPublishedDate(extVersion.getTimestamp()); - var metadataIssues = validator.validateMetadata(extVersion); - if (!metadataIssues.isEmpty()) { - if (metadataIssues.size() == 1) { - throw new ErrorResultException(metadataIssues.get(0).toString()); + entityManager.persist(extension); + } else { + var existingVersion = repositories.findVersion(extVersion.getVersion(), extVersion.getTargetPlatform(), extension); + if (existingVersion != null) { + var extVersionId = NamingUtil.toLogFormat(namespaceName, extensionName, extVersion.getTargetPlatform(), extVersion.getVersion()); + var message = "Extension " + extVersionId + " is already published"; + message += existingVersion.isActive() ? "." : ", but currently isn't active and therefore not visible."; + throw new ErrorResultException(message); + } } - throw new ErrorResultException("Multiple issues were found in the extension metadata:\n" - + Joiner.on("\n").join(metadataIssues)); - } - entityManager.persist(extVersion); - return extVersion; + extension.setLastUpdatedDate(extVersion.getTimestamp()); + extension.getVersions().add(extVersion); + extVersion.setExtension(extension); + + var metadataIssues = validator.validateMetadata(extVersion); + if (!metadataIssues.isEmpty()) { + if (metadataIssues.size() == 1) { + throw new ErrorResultException(metadataIssues.get(0).toString()); + } + throw new ErrorResultException("Multiple issues were found in the extension metadata:\n" + + Joiner.on("\n").join(metadataIssues)); + } + + entityManager.persist(extVersion); + return extVersion; + }); } private String checkDependency(String dependency) { - var split = dependency.split("\\."); - if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { - throw new ErrorResultException("Invalid 'extensionDependencies' format. Expected: '${namespace}.${name}'"); - } - var extensionCount = repositories.countExtensions(split[1], split[0]); - if (extensionCount == 0) { - throw new ErrorResultException("Cannot resolve dependency: " + dependency); - } + return Observation.createNotStarted("PublishExtensionVersionHandler#checkDependency", observations).observe(() -> { + var split = dependency.split("\\."); + if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { + throw new ErrorResultException("Invalid 'extensionDependencies' format. Expected: '${namespace}.${name}'"); + } + var extensionCount = repositories.countExtensions(split[1], split[0]); + if (extensionCount == 0) { + throw new ErrorResultException("Cannot resolve dependency: " + dependency); + } - return dependency; + return dependency; + }); } private String checkBundledExtension(String bundledExtension) { - var split = bundledExtension.split("\\."); - if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { - throw new ErrorResultException("Invalid 'extensionPack' format. Expected: '${namespace}.${name}'"); - } + return Observation.createNotStarted("PublishExtensionVersionHandler#checkBundledExtension", observations).observe(() -> { + var split = bundledExtension.split("\\."); + if (split.length != 2 || split[0].isEmpty() || split[1].isEmpty()) { + throw new ErrorResultException("Invalid 'extensionPack' format. Expected: '${namespace}.${name}'"); + } - return bundledExtension; + return bundledExtension; + }); } @Async @@ -189,7 +203,7 @@ public void publishAsync(FileResource download, TempFile extensionFile, Extensio service.storeDownload(download, extensionFile); service.persistResource(download); - try(var processor = new ExtensionProcessor(extensionFile)) { + try(var processor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { Consumer consumer = resource -> { service.storeResource(resource); service.persistResource(resource); @@ -228,7 +242,7 @@ public void mirror(FileResource download, TempFile extensionFile, String signatu if(signatureName != null) { service.mirrorResource(getSignatureResource(signatureName, extVersion)); } - try(var processor = new ExtensionProcessor(extensionFile)) { + try(var processor = new ExtensionProcessor(extensionFile, ObservationRegistry.NOOP)) { processor.getFileResources(extVersion).forEach(service::mirrorResource); service.mirrorResource(processor.generateSha256Checksum(extVersion)); // don't store file resources, they can be generated on the fly to avoid traversing entire zip file @@ -244,10 +258,12 @@ private FileResource getSignatureResource(String signatureName, ExtensionVersion } public void schedulePublicIdJob(FileResource download) { - var extension = download.getExtension().getExtension(); - if(StringUtils.isEmpty(extension.getPublicId())) { - var namespace = extension.getNamespace(); - scheduler.enqueue(new VSCodeIdNewExtensionJobRequest(namespace.getName(), extension.getName())); - } + Observation.createNotStarted("PublishExtensionVersionHandler#schedulePublicIdJob", observations).observe(() -> { + var extension = download.getExtension().getExtension(); + if (StringUtils.isEmpty(extension.getPublicId())) { + var namespace = extension.getNamespace(); + scheduler.enqueue(new VSCodeIdNewExtensionJobRequest(namespace.getName(), extension.getName())); + } + }); } } diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionRepository.java index 1240434ed..e2d73510a 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionRepository.java @@ -9,7 +9,6 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import io.micrometer.observation.annotation.Observed; import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.Namespace; import org.eclipse.openvsx.entities.UserData; @@ -26,7 +25,6 @@ public interface ExtensionRepository extends Repository { Streamable findByNamespace(Namespace namespace); - @Observed Streamable findByNamespaceAndActiveTrueOrderByNameAsc(Namespace namespace); Extension findByNameIgnoreCaseAndNamespace(String name, Namespace namespace); diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java index 3e52c2938..4b48631ef 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/ExtensionVersionJooqRepository.java @@ -9,7 +9,6 @@ * ****************************************************************************** */ package org.eclipse.openvsx.repositories; -import io.micrometer.observation.annotation.Observed; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.QueryRequest; @@ -621,7 +620,6 @@ public List findVersionsForUrls(Extension extension, String ta }); } - @Observed public ExtensionVersion findLatest( Extension extension, String targetPlatform, diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java index 2d5025cb8..00fe3c5f2 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceMembershipRepository.java @@ -9,12 +9,11 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import io.micrometer.observation.annotation.Observed; -import org.springframework.data.repository.Repository; -import org.springframework.data.util.Streamable; import org.eclipse.openvsx.entities.Namespace; import org.eclipse.openvsx.entities.NamespaceMembership; import org.eclipse.openvsx.entities.UserData; +import org.springframework.data.repository.Repository; +import org.springframework.data.util.Streamable; public interface NamespaceMembershipRepository extends Repository { @@ -24,7 +23,6 @@ public interface NamespaceMembershipRepository extends Repository findByNamespaceAndRoleIgnoreCase(Namespace namespace, String role); - @Observed long countByNamespaceAndRoleIgnoreCase(Namespace namespace, String role); Streamable findByNamespace(Namespace namespace); diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceRepository.java index 121b5927a..3c363eb44 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/NamespaceRepository.java @@ -9,17 +9,13 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import io.micrometer.observation.annotation.Observed; +import org.eclipse.openvsx.entities.Namespace; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.Repository; import org.springframework.data.util.Streamable; -import org.eclipse.openvsx.entities.Namespace; - -import java.time.LocalDateTime; public interface NamespaceRepository extends Repository { - @Observed Namespace findByNameIgnoreCase(String name); Namespace findByPublicId(String publicId); diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java index 8b7b06144..d32591c73 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/PersonalAccessTokenRepository.java @@ -9,10 +9,10 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import org.springframework.data.repository.Repository; -import org.springframework.data.util.Streamable; import org.eclipse.openvsx.entities.PersonalAccessToken; import org.eclipse.openvsx.entities.UserData; +import org.springframework.data.repository.Repository; +import org.springframework.data.util.Streamable; public interface PersonalAccessTokenRepository extends Repository { diff --git a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java index e84ecc1fb..2ab62074c 100644 --- a/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java +++ b/server/src/main/java/org/eclipse/openvsx/repositories/RepositoryService.java @@ -9,7 +9,8 @@ ********************************************************************************/ package org.eclipse.openvsx.repositories; -import io.micrometer.observation.annotation.Observed; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.json.QueryRequest; import org.eclipse.openvsx.json.VersionTargetPlatformsJson; @@ -39,6 +40,7 @@ public class RepositoryService { .and(Sort.by(Sort.Direction.ASC, "targetPlatform")) .and(Sort.by(Sort.Direction.DESC, "timestamp")); + private final ObservationRegistry observations; private final NamespaceRepository namespaceRepo; private final NamespaceJooqRepository namespaceJooqRepo; private final ExtensionRepository extensionRepo; @@ -60,6 +62,7 @@ public class RepositoryService { private final SignatureKeyPairRepository signatureKeyPairRepo; public RepositoryService( + ObservationRegistry observations, NamespaceRepository namespaceRepo, NamespaceJooqRepository namespaceJooqRepo, ExtensionRepository extensionRepo, @@ -80,6 +83,7 @@ public RepositoryService( MigrationItemRepository migrationItemRepo, SignatureKeyPairRepository signatureKeyPairRepo ) { + this.observations = observations; this.namespaceRepo = namespaceRepo; this.namespaceJooqRepo = namespaceJooqRepo; this.extensionRepo = extensionRepo; @@ -101,9 +105,8 @@ public RepositoryService( this.signatureKeyPairRepo = signatureKeyPairRepo; } - @Observed public Namespace findNamespace(String name) { - return namespaceRepo.findByNameIgnoreCase(name); + return Observation.createNotStarted("RepositoryService#findNamespace", observations).observe(() -> namespaceRepo.findByNameIgnoreCase(name)); } public Namespace findNamespaceByPublicId(String publicId) { @@ -119,7 +122,7 @@ public long countNamespaces() { } public Extension findExtension(String name, Namespace namespace) { - return extensionRepo.findByNameIgnoreCaseAndNamespace(name, namespace); + return Observation.createNotStarted("RepositoryService#findExtension", observations).observe(() -> extensionRepo.findByNameIgnoreCaseAndNamespace(name, namespace)); } public Extension findExtension(String name, String namespace) { @@ -130,13 +133,14 @@ public Extension findExtensionByPublicId(String publicId) { return extensionRepo.findByPublicId(publicId); } - @Observed public Streamable findActiveExtensions(Namespace namespace) { return extensionRepo.findByNamespaceAndActiveTrueOrderByNameAsc(namespace); } public Streamable findExtensions(Collection extensionIds) { - return extensionRepo.findByIdIn(extensionIds); +// return Observation.createNotStarted("RepositoryService#findExtensions", observations).observe(() -> { + return extensionRepo.findByIdIn(extensionIds); +// }); } public Streamable findExtensions(Namespace namespace) { @@ -160,7 +164,7 @@ public long countExtensions() { } public long countExtensions(String name, String namespace) { - return extensionRepo.countByNameIgnoreCaseAndNamespaceNameIgnoreCase(name, namespace); + return Observation.createNotStarted("RepositoryService#countExtensions", observations).observe(() -> extensionRepo.countByNameIgnoreCaseAndNamespaceNameIgnoreCase(name, namespace)); } public int getMaxExtensionDownloadCount() { @@ -168,7 +172,7 @@ public int getMaxExtensionDownloadCount() { } public ExtensionVersion findVersion(String version, String targetPlatform, Extension extension) { - return extensionVersionRepo.findByVersionAndTargetPlatformAndExtension(version, targetPlatform, extension); + return Observation.createNotStarted("RepositoryService#findVersion", observations).observe(() -> extensionVersionRepo.findByVersionAndTargetPlatformAndExtension(version, targetPlatform, extension)); } public ExtensionVersion findVersion(String version, String targetPlatform, String extensionName, String namespace) { @@ -180,7 +184,7 @@ public Streamable findVersions(Extension extension) { } public Streamable findVersions(String version, Extension extension) { - return extensionVersionRepo.findByVersionAndExtension(version, extension); + return Observation.createNotStarted("RepositoryService#findVersions", observations).observe(() -> extensionVersionRepo.findByVersionAndExtension(version, extension)); } public Streamable findActiveVersions(Extension extension) { @@ -200,7 +204,7 @@ public Page findActiveVersionStringsSorted(String namespace, String exte } public List findVersionStringsSorted(Extension extension, String targetPlatform, boolean onlyActive) { - return extensionVersionJooqRepo.findVersionStringsSorted(extension.getId(), targetPlatform, onlyActive, MAX_VERSIONS); + return Observation.createNotStarted("RepositoryService#findVersionStringsSorted", observations).observe(() -> extensionVersionJooqRepo.findVersionStringsSorted(extension.getId(), targetPlatform, onlyActive, MAX_VERSIONS)); } public Map> findActiveVersionStringsSorted(Collection extensionIds, String targetPlatform) { @@ -248,7 +252,9 @@ public FileResource findFileByTypeAndName(ExtensionVersion extVersion, String ty } public Streamable findDownloadsByStorageTypeAndName(String storageType, Collection names) { - return fileResourceRepo.findByTypeAndStorageTypeAndNameIgnoreCaseIn(DOWNLOAD, storageType, names); +// return Observation.createNotStarted("RepositoryService#findDownloadsByStorageTypeAndName", observations).observe(() -> { + return fileResourceRepo.findByTypeAndStorageTypeAndNameIgnoreCaseIn(DOWNLOAD, storageType, names); +// }); } public Streamable findFilesByType(String type) { @@ -264,7 +270,7 @@ public FileResource findFileByType(ExtensionVersion extVersion, String type) { } public List findFilesByType(Collection extVersions, Collection types) { - return fileResourceJooqRepo.findByType(extVersions, types); + return Observation.createNotStarted("RepositoryService#findFilesByType", observations).observe(() -> fileResourceJooqRepo.findByType(extVersions, types)); } public Streamable findActiveReviews(Extension extension) { @@ -300,18 +306,17 @@ public long countUsers() { } public NamespaceMembership findMembership(UserData user, Namespace namespace) { - return membershipRepo.findByUserAndNamespace(user, namespace); + return Observation.createNotStarted("RepositoryService#findMembership", observations).observe(() -> membershipRepo.findByUserAndNamespace(user, namespace)); } public boolean isVerified(Namespace namespace, UserData user) { - return membershipJooqRepo.isVerified(namespace, user); + return Observation.createNotStarted("RepositoryService#isVerified", observations).observe(() -> membershipJooqRepo.isVerified(namespace, user)); } public Streamable findMemberships(Namespace namespace, String role) { return membershipRepo.findByNamespaceAndRoleIgnoreCase(namespace, role); } - @Observed public long countMemberships(Namespace namespace, String role) { return membershipRepo.countByNamespaceAndRoleIgnoreCase(namespace, role); } @@ -333,7 +338,7 @@ public long countActiveAccessTokens(UserData user) { } public PersonalAccessToken findAccessToken(String value) { - return tokenRepo.findByValue(value); + return Observation.createNotStarted("RepositoryService#findAccessToken", observations).observe(() -> tokenRepo.findByValue(value)); } public PersonalAccessToken findAccessToken(long id) { @@ -349,7 +354,9 @@ public Streamable findPersistedLogsAfter(LocalDateTime dateTime) { } public List findAllSucceededAzureDownloadCountProcessedItemsByNameIn(List names) { - return downloadCountRepo.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(names); +// return Observation.createNotStarted("RepositoryService#findAllSucceededAzureDownloadCountProcessedItemsByNameIn", observations).observe(() -> { + return downloadCountRepo.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(names); +// }); } public List findActiveExtensionsByPublicId(Collection publicIds, String... namespacesToExclude) { @@ -505,7 +512,7 @@ public Streamable findFileResources(Namespace namespace) { } public SignatureKeyPair findActiveKeyPair() { - return signatureKeyPairRepo.findByActiveTrue(); + return Observation.createNotStarted("RepositoryService#findActiveKeyPair", observations).observe(() -> signatureKeyPairRepo.findByActiveTrue()); } public Streamable findVersions() { @@ -582,7 +589,9 @@ public ExtensionVersion findExtensionVersion(String namespace, String extension, } public ExtensionVersion findLatestVersionForAllUrls(Extension extension, String targetPlatform, boolean onlyPreRelease, boolean onlyActive) { - return extensionVersionJooqRepo.findLatestForAllUrls(extension, targetPlatform, onlyPreRelease, onlyActive); + return Observation.createNotStarted("RepositoryService#findLatestVersionForAllUrls", observations).observe(() -> { + return extensionVersionJooqRepo.findLatestForAllUrls(extension, targetPlatform, onlyPreRelease, onlyActive); + }); } public ExtensionVersion findLatestVersion(Extension extension, String targetPlatform, boolean onlyPreRelease, boolean onlyActive) { diff --git a/server/src/main/java/org/eclipse/openvsx/search/SearchUtilService.java b/server/src/main/java/org/eclipse/openvsx/search/SearchUtilService.java index 9145e409e..3b668a68e 100644 --- a/server/src/main/java/org/eclipse/openvsx/search/SearchUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/search/SearchUtilService.java @@ -10,6 +10,8 @@ package org.eclipse.openvsx.search; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.entities.Extension; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.stereotype.Component; @@ -26,10 +28,16 @@ public class SearchUtilService implements ISearchService { private final DatabaseSearchService databaseSearchService; private final ElasticSearchService elasticSearchService; + private final ObservationRegistry observations; - public SearchUtilService(DatabaseSearchService databaseSearchService, ElasticSearchService elasticSearchService) { + public SearchUtilService( + DatabaseSearchService databaseSearchService, + ElasticSearchService elasticSearchService, + ObservationRegistry observations + ) { this.databaseSearchService = databaseSearchService; this.elasticSearchService = elasticSearchService; + this.observations = observations; } public boolean isEnabled() { @@ -71,7 +79,9 @@ public void updateSearchEntries(List extensions) { @Override public void updateSearchEntriesAsync(List extensions) { - getImplementation().updateSearchEntriesAsync(extensions); +// Observation.createNotStarted("SearchUtilService#updateSearchEntriesAsync", observations).observe(() -> { + getImplementation().updateSearchEntriesAsync(extensions); +// }); } @Override diff --git a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java index 2a23bc91e..7bc5ba302 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureBlobStorageService.java @@ -16,7 +16,6 @@ import com.azure.storage.blob.models.BlobHttpHeaders; import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.blob.models.CopyStatusType; -import io.micrometer.observation.annotation.Observed; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.entities.FileResource; @@ -182,7 +181,6 @@ protected String getBlobName(FileResource resource) { } @Override - @Observed public URI getNamespaceLogoLocation(Namespace namespace) { var blobName = getBlobName(namespace); if (StringUtils.isEmpty(serviceEndpoint)) { diff --git a/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountProcessor.java b/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountProcessor.java index dd7553d5e..b4eb1f4e2 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountProcessor.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountProcessor.java @@ -10,6 +10,8 @@ package org.eclipse.openvsx.storage; import com.google.common.collect.Lists; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.eclipse.openvsx.cache.CacheService; @@ -38,73 +40,88 @@ public class AzureDownloadCountProcessor { private final RepositoryService repositories; private final CacheService cache; private final SearchUtilService search; + private final ObservationRegistry observations; public AzureDownloadCountProcessor( EntityManager entityManager, RepositoryService repositories, CacheService cache, - SearchUtilService search + SearchUtilService search, + ObservationRegistry observations ) { this.entityManager = entityManager; this.repositories = repositories; this.cache = cache; this.search = search; + this.observations = observations; } @Transactional public void persistProcessedItem(String name, LocalDateTime processedOn, int executionTime, boolean success) { - var processedItem = new AzureDownloadCountProcessedItem(); - processedItem.setName(name); - processedItem.setProcessedOn(processedOn); - processedItem.setExecutionTime(executionTime); - processedItem.setSuccess(success); - entityManager.persist(processedItem); +// Observation.createNotStarted("AzureDownloadCountProcessor#persistProcessedItem", observations).observe(() -> { + var processedItem = new AzureDownloadCountProcessedItem(); + processedItem.setName(name); + processedItem.setProcessedOn(processedOn); + processedItem.setExecutionTime(executionTime); + processedItem.setSuccess(success); + entityManager.persist(processedItem); +// }); } public Map processDownloadCounts(Map files) { - return repositories.findDownloadsByStorageTypeAndName(STORAGE_AZURE, files.keySet()).stream() - .map(fileResource -> new AbstractMap.SimpleEntry<>(fileResource, files.get(fileResource.getName().toUpperCase()))) - .collect(Collectors.groupingBy( - e -> e.getKey().getExtension().getExtension().getId(), - Collectors.summingInt(Map.Entry::getValue) - )); +// return Observation.createNotStarted("AzureDownloadCountProcessor#processDownloadCounts", observations).observe(() -> { + return repositories.findDownloadsByStorageTypeAndName(STORAGE_AZURE, files.keySet()).stream() + .map(fileResource -> new AbstractMap.SimpleEntry<>(fileResource, files.get(fileResource.getName().toUpperCase()))) + .collect(Collectors.groupingBy( + e -> e.getKey().getExtension().getExtension().getId(), + Collectors.summingInt(Map.Entry::getValue) + )); +// }); } @Transactional public List increaseDownloadCounts(Map extensionDownloads) { - var extensions = repositories.findExtensions(extensionDownloads.keySet()).toList(); - extensions.forEach(extension -> { - var downloads = extensionDownloads.get(extension.getId()); - extension.setDownloadCount(extension.getDownloadCount() + downloads); - }); - - return extensions; +// return Observation.createNotStarted("AzureDownloadCountProcessor#increaseDownloadCounts", observations).observe(() -> { + var extensions = repositories.findExtensions(extensionDownloads.keySet()).toList(); + extensions.forEach(extension -> { + var downloads = extensionDownloads.get(extension.getId()); + extension.setDownloadCount(extension.getDownloadCount() + downloads); + }); + + return extensions; +// }); } @Transactional //needs transaction for lazy-loading versions public void evictCaches(List extensions) { - extensions.forEach(extension -> { - extension = entityManager.merge(extension); - cache.evictExtensionJsons(extension); - cache.evictLatestExtensionVersion(extension); - }); +// Observation.createNotStarted("AzureDownloadCountProcessor#evictCaches", observations).observe(() -> { + extensions.forEach(extension -> { + extension = entityManager.merge(extension); + cache.evictExtensionJsons(extension); + cache.evictLatestExtensionVersion(extension); + }); +// }); } public void updateSearchEntries(List extensions) { - logger.info(">> updateSearchEntries"); - var activeExtensions = extensions.stream() - .filter(Extension::isActive) - .collect(Collectors.toList()); - - logger.info("total active extensions: {}", activeExtensions.size()); - var parts = Lists.partition(activeExtensions, 100); - logger.info("partitions: {} | partition size: 100", parts.size()); - - parts.forEach(search::updateSearchEntriesAsync); - logger.info("<< updateSearchEntries"); +// Observation.createNotStarted("AzureDownloadCountProcessor#updateSearchEntries", observations).observe(() -> { + logger.info(">> updateSearchEntries"); + var activeExtensions = extensions.stream() + .filter(Extension::isActive) + .collect(Collectors.toList()); + + logger.info("total active extensions: {}", activeExtensions.size()); + var parts = Lists.partition(activeExtensions, 100); + logger.info("partitions: {} | partition size: 100", parts.size()); + + parts.forEach(search::updateSearchEntriesAsync); + logger.info("<< updateSearchEntries"); +// }); } public List processedItems(List blobNames) { - return repositories.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(blobNames); +// return Observation.createNotStarted("AzureDownloadCountProcessor#processedItems", observations).observe(() -> { + return repositories.findAllSucceededAzureDownloadCountProcessedItemsByNameIn(blobNames); +// }); } } diff --git a/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountService.java b/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountService.java index 380f892d6..57a5e0e03 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/AzureDownloadCountService.java @@ -15,9 +15,12 @@ import com.azure.storage.blob.BlobContainerClientBuilder; import com.azure.storage.blob.models.BlobItem; import com.azure.storage.blob.models.BlobListDetails; +import com.azure.storage.blob.models.BlobStorageException; import com.azure.storage.blob.models.ListBlobsOptions; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.util.TempFile; import org.jobrunr.jobs.annotations.Job; @@ -25,6 +28,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; import org.springframework.web.util.UriUtils; @@ -52,6 +56,7 @@ public class AzureDownloadCountService { protected final Logger logger = LoggerFactory.getLogger(AzureDownloadCountService.class); private final AzureDownloadCountProcessor processor; + private final ObservationRegistry observations; private BlobContainerClient containerClient; private ObjectMapper objectMapper; private Pattern blobItemNamePattern; @@ -71,21 +76,27 @@ public class AzureDownloadCountService { @Value("${ovsx.storage.azure.blob-container:openvsx-resources}") String storageBlobContainer; - public AzureDownloadCountService(AzureDownloadCountProcessor processor) { + public AzureDownloadCountService( + AzureDownloadCountProcessor processor, + ObservationRegistry observations + ) { this.processor = processor; + this.observations = observations; } /** * Indicates whether the download service is enabled by application config. */ public boolean isEnabled() { - var logsEnabled = !StringUtils.isEmpty(logsServiceEndpoint); - var storageEnabled = !StringUtils.isEmpty(storageServiceEndpoint); - if(logsEnabled && !storageEnabled) { - logger.warn("The ovsx.storage.azure.service-endpoint value must be set to enable AzureDownloadCountService"); - } +// return Observation.createNotStarted("AzureDownloadCountService#isEnabled", observations).observe(() -> { + var logsEnabled = !StringUtils.isEmpty(logsServiceEndpoint); + var storageEnabled = !StringUtils.isEmpty(storageServiceEndpoint); + if (logsEnabled && !storageEnabled) { + logger.warn("The ovsx.storage.azure.service-endpoint value must be set to enable AzureDownloadCountService"); + } - return logsEnabled && storageEnabled; + return logsEnabled && storageEnabled; +// }); } /** @@ -94,126 +105,160 @@ public boolean isEnabled() { @Job(name = "Update Download Counts", retries = 0) @Recurring(id = "update-download-counts", cron = "0 5 * * * *", zoneId = "UTC") public void updateDownloadCounts() { - if (!isEnabled()) { - return; - } +// Observation.createNotStarted("AzureDownloadCountService#updateDownloadCounts", observations).observe(() -> { + if (!isEnabled()) { + return; + } - logger.info(">> updateDownloadCounts"); - var maxExecutionTime = LocalDateTime.now().withMinute(55); - var blobs = listBlobs(); - var iterableByPage = blobs.iterableByPage(); - - var stopWatch = new StopWatch(); - while(iterableByPage != null) { - PagedResponse response = null; - var iterator = iterableByPage.iterator(); - if(iterator.hasNext()) { - response = iterator.next(); - var blobNames = getBlobNames(response.getValue()); - blobNames.removeAll(processor.processedItems(blobNames)); - for (var name : blobNames) { - if(LocalDateTime.now().isAfter(maxExecutionTime)) { - var nextJobRunTime = LocalDateTime.now().plusHours(1).withMinute(5); - logger.info("Failed to process all download counts within timeslot, next job run is at {}", nextJobRunTime); - logger.info("<< updateDownloadCounts"); - return; - } + logger.info(">> updateDownloadCounts"); + var maxExecutionTime = LocalDateTime.now().withMinute(55); + var blobs = listBlobs(); + var iterableByPage = blobs.iterableByPage(); + + var stopWatch = new StopWatch(); + while (iterableByPage != null) { + PagedResponse response = null; + var iterator = iterableByPage.iterator(); + if (iterator.hasNext()) { + response = iterator.next(); + var blobNames = getBlobNames(response.getValue()); + var processedItems = processor.processedItems(blobNames); +// processedItems.forEach(this::deleteBlob); + blobNames.removeAll(processedItems); + for (var name : blobNames) { + if (LocalDateTime.now().isAfter(maxExecutionTime)) { + var nextJobRunTime = LocalDateTime.now().plusHours(1).withMinute(5); + logger.info("Failed to process all download counts within timeslot, next job run is at {}", nextJobRunTime); + logger.info("<< updateDownloadCounts"); + return; + } - var processedOn = LocalDateTime.now(); - var success = false; - stopWatch.start(); - try { - var files = processBlobItem(name); - if(!files.isEmpty()) { - var extensionDownloads = processor.processDownloadCounts(files); - var updatedExtensions = processor.increaseDownloadCounts(extensionDownloads); - processor.evictCaches(updatedExtensions); - processor.updateSearchEntries(updatedExtensions); + var processedOn = LocalDateTime.now(); + var success = false; + stopWatch.start(); + try { + var files = processBlobItem(name); + if (!files.isEmpty()) { + var extensionDownloads = processor.processDownloadCounts(files); + var updatedExtensions = processor.increaseDownloadCounts(extensionDownloads); + processor.evictCaches(updatedExtensions); + processor.updateSearchEntries(updatedExtensions); + } + + success = true; + } catch (Exception e) { + logger.error("Failed to process BlobItem: " + name, e); } - success = true; - } catch (Exception e) { - logger.error("Failed to process BlobItem: " + name, e); + stopWatch.stop(); + var executionTime = (int) stopWatch.getLastTaskTimeMillis(); + processor.persistProcessedItem(name, processedOn, executionTime, success); +// if(success) { +// deleteBlob(name); +// } } - - stopWatch.stop(); - var executionTime = (int) stopWatch.getLastTaskTimeMillis(); - processor.persistProcessedItem(name, processedOn, executionTime, success); } + + var continuationToken = response != null ? response.getContinuationToken() : ""; + iterableByPage = !StringUtils.isEmpty(continuationToken) ? blobs.iterableByPage(continuationToken) : null; } - var continuationToken = response != null ? response.getContinuationToken() : ""; - iterableByPage = !StringUtils.isEmpty(continuationToken) ? blobs.iterableByPage(continuationToken) : null; - } + logger.info("<< updateDownloadCounts"); +// }); + } - logger.info("<< updateDownloadCounts"); + private void deleteBlob(String blobName) { + try { + getContainerClient().getBlobClient(blobName).delete(); + } catch(BlobStorageException e) { + if(e.getStatusCode() != HttpStatus.NOT_FOUND.value()) { + // 404 indicates that the file is already deleted + // so only throw an exception for other status codes + throw e; + } + } } private Map processBlobItem(String blobName) { - try ( - var downloadsTempFile = downloadBlobItem(blobName); - var reader = Files.newBufferedReader(downloadsTempFile.getPath()) - ) { - return reader.lines() - .map(line -> { - try { - return getObjectMapper().readTree(line); - } catch (JsonProcessingException e) { - throw new RuntimeException(e); - } - }) - .filter(node -> { - var operationName = node.get("operationName").asText(); - var statusCode = node.get("statusCode").asInt(); - var uri = node.get("uri").asText(); - return operationName.equals("GetBlob") && statusCode == 200 && uri.endsWith(".vsix"); - }).map(node -> { - var uri = node.get("uri").asText(); - var pathParams = uri.substring(storageServiceEndpoint.length()).split("/"); - return new AbstractMap.SimpleEntry<>(pathParams, node.get("time").asText()); - }) - .filter(entry -> storageBlobContainer.equals(entry.getKey()[1])) - .map(entry -> { - var pathParams = entry.getKey(); - var fileName = UriUtils.decode(pathParams[pathParams.length - 1], StandardCharsets.UTF_8).toUpperCase(); - return new AbstractMap.SimpleEntry<>(fileName, 1); - }) - .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(Map.Entry::getValue))); - } catch (IOException e) { - throw new RuntimeException(e); - } +// return Observation.createNotStarted("AzureDownloadCountService#processBlobItem", observations).observe(() -> { + try ( + var downloadsTempFile = downloadBlobItem(blobName); + var reader = Files.newBufferedReader(downloadsTempFile.getPath()) + ) { + return reader.lines() + .map(line -> { + try { + return getObjectMapper().readTree(line); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + }) + .filter(node -> { + var operationName = node.get("operationName").asText(); + var statusCode = node.get("statusCode").asInt(); + var uri = node.get("uri").asText(); + return operationName.equals("GetBlob") && statusCode == 200 && uri.endsWith(".vsix"); + }).map(node -> { + var uri = node.get("uri").asText(); + var pathParams = uri.substring(storageServiceEndpoint.length()).split("/"); + return new AbstractMap.SimpleEntry<>(pathParams, node.get("time").asText()); + }) + .filter(entry -> storageBlobContainer.equals(entry.getKey()[1])) + .map(entry -> { + var pathParams = entry.getKey(); + var fileName = UriUtils.decode(pathParams[pathParams.length - 1], StandardCharsets.UTF_8).toUpperCase(); + return new AbstractMap.SimpleEntry<>(fileName, 1); + }) + .collect(Collectors.groupingBy(Map.Entry::getKey, Collectors.summingInt(Map.Entry::getValue))); + } catch (IOException e) { + throw new RuntimeException(e); + } +// }); } private TempFile downloadBlobItem(String blobName) throws IOException { - var downloadsTempFile = new TempFile("azure-downloads-", ".json"); - getContainerClient().getBlobClient(blobName).downloadToFile(downloadsTempFile.getPath().toAbsolutePath().toString(), true); - return downloadsTempFile; +// return Observation.createNotStarted("AzureDownloadCountService#downloadBlobItem", observations).observe(() -> { +// TempFile downloadsTempFile = null; +// try { + var downloadsTempFile = new TempFile("azure-downloads-", ".json"); +// } catch (IOException e) { +// // TODO add `throws IOException` to `downloadBlobItem` method signature when reverting Observations +// // TODO remove try catch around `downloadsTempFile` +// throw new RuntimeException(e); +// } + getContainerClient().getBlobClient(blobName).downloadToFile(downloadsTempFile.getPath().toAbsolutePath().toString(), true); + return downloadsTempFile; +// }); } private List getBlobNames(List items) { - var blobNames = new ArrayList(); - for(var item : items) { - var name = item.getName(); - if(isCorrectName(name)) { - blobNames.add(name); +// return Observation.createNotStarted("AzureDownloadCountService#getBlobNames", observations).observe(() -> { + var blobNames = new ArrayList(); + for (var item : items) { + var name = item.getName(); + if (isCorrectName(name)) { + blobNames.add(name); + } } - } - return blobNames; + return blobNames; +// }); } private PagedIterable listBlobs() { - var details = new BlobListDetails() - .setRetrieveCopy(false) - .setRetrieveMetadata(false) - .setRetrieveDeletedBlobs(false) - .setRetrieveTags(false) - .setRetrieveSnapshots(false) - .setRetrieveUncommittedBlobs(false) - .setRetrieveVersions(false); - - var options = new ListBlobsOptions().setMaxResultsPerPage(100).setDetails(details); - return getContainerClient().listBlobs(options, Duration.ofMinutes(5)); +// return Observation.createNotStarted("AzureDownloadCountService#listBlobs", observations).observe(() -> { + var details = new BlobListDetails() + .setRetrieveCopy(false) + .setRetrieveMetadata(false) + .setRetrieveDeletedBlobs(false) + .setRetrieveTags(false) + .setRetrieveSnapshots(false) + .setRetrieveUncommittedBlobs(false) + .setRetrieveVersions(false); + + var options = new ListBlobsOptions().setMaxResultsPerPage(100).setDetails(details); + return getContainerClient().listBlobs(options, Duration.ofMinutes(5)); +// }); } private BlobContainerClient getContainerClient() { diff --git a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java index f4f010f6e..1b9e04265 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/GoogleCloudStorageService.java @@ -13,17 +13,15 @@ import com.google.cloud.storage.BlobInfo; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; -import io.micrometer.observation.annotation.Observed; import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.entities.Namespace; -import org.eclipse.openvsx.util.TargetPlatform; import org.eclipse.openvsx.util.TempFile; import org.eclipse.openvsx.util.UrlUtil; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.util.Pair; import org.springframework.stereotype.Component; -import org.apache.commons.lang3.StringUtils; import java.io.FileOutputStream; import java.io.IOException; @@ -177,7 +175,6 @@ protected String getObjectId(FileResource resource) { } @Override - @Observed public URI getNamespaceLogoLocation(Namespace namespace) { if (StringUtils.isEmpty(bucketId)) { throw new IllegalStateException("Cannot determine location of file " diff --git a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java index 737e09923..2d77f17ed 100644 --- a/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java +++ b/server/src/main/java/org/eclipse/openvsx/storage/StorageUtilService.java @@ -10,7 +10,8 @@ package org.eclipse.openvsx.storage; import com.google.common.collect.Maps; -import io.micrometer.observation.annotation.Observed; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import jakarta.transaction.Transactional; import org.apache.commons.lang3.StringUtils; @@ -53,6 +54,7 @@ public class StorageUtilService implements IStorageService { private final SearchUtilService search; private final CacheService cache; private final EntityManager entityManager; + private final ObservationRegistry observations; /** Determines which external storage service to use in case multiple services are configured. */ @Value("${ovsx.storage.primary-service:}") @@ -69,7 +71,8 @@ public StorageUtilService( AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, - EntityManager entityManager + EntityManager entityManager, + ObservationRegistry observations ) { this.repositories = repositories; this.googleStorage = googleStorage; @@ -78,6 +81,7 @@ public StorageUtilService( this.search = search; this.cache = cache; this.entityManager = entityManager; + this.observations = observations; } public boolean shouldStoreExternally(FileResource resource) { @@ -214,7 +218,6 @@ public URI getLocation(FileResource resource) { } @Override - @Observed public URI getNamespaceLogoLocation(Namespace namespace) { switch (namespace.getLogoStorageType()) { case STORAGE_GOOGLE: @@ -259,17 +262,19 @@ private String getFileUrl(String name, ExtensionVersion extVersion, String serve * Returns URLs for the given file types as a map of ExtensionVersion.id by a map of type by file URL, to be used in JSON response data. */ public Map> getFileUrls(Collection extVersions, String serverUrl, String... types) { - var type2Url = extVersions.stream() - .map(ev -> new AbstractMap.SimpleEntry>(ev.getId(), Maps.newLinkedHashMapWithExpectedSize(types.length))) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - var resources = repositories.findFilesByType(extVersions, Arrays.asList(types)); - for (var resource : resources) { - var extVersion = resource.getExtension(); - type2Url.get(extVersion.getId()).put(resource.getType(), getFileUrl(resource.getName(), extVersion, serverUrl)); - } - - return type2Url; + return Observation.createNotStarted("StorageUtilService#getFileUrls", observations).observe(() -> { + var type2Url = extVersions.stream() + .map(ev -> new AbstractMap.SimpleEntry>(ev.getId(), Maps.newLinkedHashMapWithExpectedSize(types.length))) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + + var resources = repositories.findFilesByType(extVersions, Arrays.asList(types)); + for (var resource : resources) { + var extVersion = resource.getExtension(); + type2Url.get(extVersion.getId()).put(resource.getType(), getFileUrl(resource.getName(), extVersion, serverUrl)); + } + + return type2Url; + }); } @Transactional diff --git a/server/src/main/java/org/eclipse/openvsx/util/ArchiveUtil.java b/server/src/main/java/org/eclipse/openvsx/util/ArchiveUtil.java index 37c4d1501..d553bc288 100644 --- a/server/src/main/java/org/eclipse/openvsx/util/ArchiveUtil.java +++ b/server/src/main/java/org/eclipse/openvsx/util/ArchiveUtil.java @@ -9,6 +9,9 @@ ********************************************************************************/ package org.eclipse.openvsx.util; +import io.micrometer.observation.Observation; +import io.micrometer.observation.ObservationRegistry; + import java.io.IOException; import java.util.zip.ZipEntry; import java.util.zip.ZipException; @@ -28,23 +31,27 @@ public static ZipEntry getEntryIgnoreCase(ZipFile archive, String entryName) { .orElse(null); } - public static byte[] readEntry(ZipFile archive, String entryName) { - var entry = archive.getEntry(entryName); - if (entry == null) - return null; - return readEntry(archive, entry); + public static byte[] readEntry(ZipFile archive, String entryName, ObservationRegistry observations) { + return Observation.createNotStarted("ArchiveUtil#readEntry", observations).observe(() -> { + var entry = archive.getEntry(entryName); + if (entry == null) + return null; + return readEntry(archive, entry, observations); + }); } - public static byte[] readEntry(ZipFile archive, ZipEntry entry) { - try { - if (entry.getSize() > MAX_ENTRY_SIZE) - throw new ErrorResultException("The file " + entry.getName() + " exceeds the size limit of 32 MB."); - return archive.getInputStream(entry).readAllBytes(); - } catch (ZipException exc) { - throw new ErrorResultException("Could not read zip file: " + exc.getMessage(), exc); - } catch (IOException exc) { - throw new RuntimeException(exc); - } + public static byte[] readEntry(ZipFile archive, ZipEntry entry, ObservationRegistry observations) { + return Observation.createNotStarted("ArchiveUtil#readEntry", observations).observe(() -> { + try { + if (entry.getSize() > MAX_ENTRY_SIZE) + throw new ErrorResultException("The file " + entry.getName() + " exceeds the size limit of 32 MB."); + return archive.getInputStream(entry).readAllBytes(); + } catch (ZipException exc) { + throw new ErrorResultException("Could not read zip file: " + exc.getMessage(), exc); + } catch (IOException exc) { + throw new RuntimeException(exc); + } + }); } } \ No newline at end of file diff --git a/server/src/main/java/org/eclipse/openvsx/util/VersionService.java b/server/src/main/java/org/eclipse/openvsx/util/VersionService.java index e982564e1..3df3eeeb4 100644 --- a/server/src/main/java/org/eclipse/openvsx/util/VersionService.java +++ b/server/src/main/java/org/eclipse/openvsx/util/VersionService.java @@ -9,18 +9,14 @@ * ****************************************************************************** */ package org.eclipse.openvsx.util; -import io.micrometer.observation.annotation.Observed; -import org.eclipse.openvsx.entities.Extension; import org.eclipse.openvsx.entities.ExtensionVersion; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Component; -import jakarta.persistence.EntityManager; -import jakarta.transaction.Transactional; import java.util.List; -import static org.eclipse.openvsx.cache.CacheService.*; +import static org.eclipse.openvsx.cache.CacheService.CACHE_LATEST_EXTENSION_VERSION; +import static org.eclipse.openvsx.cache.CacheService.GENERATOR_LATEST_EXTENSION_VERSION; @Component public class VersionService { diff --git a/server/src/test/java/org/eclipse/openvsx/ExtensionProcessorTest.java b/server/src/test/java/org/eclipse/openvsx/ExtensionProcessorTest.java index cdff3e6e3..de3f9dcec 100644 --- a/server/src/test/java/org/eclipse/openvsx/ExtensionProcessorTest.java +++ b/server/src/test/java/org/eclipse/openvsx/ExtensionProcessorTest.java @@ -9,6 +9,7 @@ ********************************************************************************/ package org.eclipse.openvsx; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.entities.FileResource; import org.eclipse.openvsx.util.TempFile; import org.junit.jupiter.api.Test; @@ -25,7 +26,7 @@ class ExtensionProcessorTest { void testTodoTree() throws Exception { try ( var file = writeToTempFile("util/todo-tree.zip"); - var processor = new ExtensionProcessor(file) + var processor = new ExtensionProcessor(file, ObservationRegistry.NOOP) ) { assertThat(processor.getNamespace()).isEqualTo("Gruntfuggly"); assertThat(processor.getExtensionName()).isEqualTo("todo-tree"); @@ -50,7 +51,7 @@ void testTodoTree() throws Exception { void testChangelog() throws Exception { try ( var file = writeToTempFile("util/changelog.zip"); - var processor = new ExtensionProcessor(file) + var processor = new ExtensionProcessor(file, ObservationRegistry.NOOP) ) { checkResource(processor, FileResource.CHANGELOG, "CHANGELOG.md"); } @@ -60,7 +61,7 @@ void testChangelog() throws Exception { void testCapitalizedCaseForResources() throws Exception { try ( var file = writeToTempFile("util/with-capitalized-case.zip"); - var processor = new ExtensionProcessor(file) + var processor = new ExtensionProcessor(file, ObservationRegistry.NOOP) ) { checkResource(processor, FileResource.CHANGELOG, "Changelog.md"); checkResource(processor, FileResource.README, "Readme.md"); @@ -72,7 +73,7 @@ void testCapitalizedCaseForResources() throws Exception { void testMinorCaseForResources() throws Exception { try ( var file = writeToTempFile("util/with-minor-case.zip"); - var processor = new ExtensionProcessor(file) + var processor = new ExtensionProcessor(file, ObservationRegistry.NOOP) ) { checkResource(processor, FileResource.CHANGELOG, "changelog.md"); checkResource(processor, FileResource.README, "readme.md"); diff --git a/server/src/test/java/org/eclipse/openvsx/ExtensionValidatorTest.java b/server/src/test/java/org/eclipse/openvsx/ExtensionValidatorTest.java index 8e21445c7..876dd4ae9 100644 --- a/server/src/test/java/org/eclipse/openvsx/ExtensionValidatorTest.java +++ b/server/src/test/java/org/eclipse/openvsx/ExtensionValidatorTest.java @@ -9,17 +9,26 @@ ********************************************************************************/ package org.eclipse.openvsx; -import static org.assertj.core.api.Assertions.assertThat; - +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.entities.ExtensionVersion; import org.eclipse.openvsx.util.TargetPlatform; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import static org.assertj.core.api.Assertions.assertThat; +@ExtendWith(SpringExtension.class) public class ExtensionValidatorTest { + @Autowired + ExtensionValidator validator; + @Test public void testInvalidVersion1() { - var validator = new ExtensionValidator(); var issue = validator.validateExtensionVersion("latest"); assertThat(issue).isPresent(); assertThat(issue.get()) @@ -28,7 +37,6 @@ public void testInvalidVersion1() { @Test public void testInvalidVersion2() { - var validator = new ExtensionValidator(); var issue = validator.validateExtensionVersion("1/2"); assertThat(issue).isPresent(); assertThat(issue.get()) @@ -37,7 +45,6 @@ public void testInvalidVersion2() { @Test public void testInvalidTargetPlatform() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform("debian-x64"); extension.setVersion("1.0.0"); @@ -49,7 +56,6 @@ public void testInvalidTargetPlatform() { @Test public void testInvalidURL() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extension.setVersion("1.0.0"); @@ -62,7 +68,6 @@ public void testInvalidURL() { @Test public void testInvalidURL2() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extension.setVersion("1.0.0"); @@ -75,7 +80,6 @@ public void testInvalidURL2() { @Test public void testInvalidURL3() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extension.setVersion("1.0.0"); @@ -88,7 +92,6 @@ public void testInvalidURL3() { @Test public void testMailtoURL() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extension.setVersion("1.0.0"); @@ -99,7 +102,6 @@ public void testMailtoURL() { @Test public void testGitProtocol() { - var validator = new ExtensionValidator(); var extension = new ExtensionVersion(); extension.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL); extension.setVersion("1.0.0"); @@ -107,5 +109,17 @@ public void testGitProtocol() { var issues = validator.validateMetadata(extension); assertThat(issues).isEmpty(); } - + + @TestConfiguration + static class TestConfig { + @Bean + ExtensionValidator extensionValidator(ObservationRegistry observations) { + return new ExtensionValidator(observations); + } + + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } + } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java index 4138a4af9..a17d49f76 100644 --- a/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/RegistryAPITest.java @@ -12,7 +12,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; -import io.micrometer.observation.annotation.Observed; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import org.eclipse.openvsx.adapter.VSCodeIdService; import org.eclipse.openvsx.cache.CacheService; @@ -2356,7 +2356,8 @@ LocalRegistryService localRegistryService( StorageUtilService storageUtil, EclipseService eclipse, CacheService cache, - ExtensionVersionIntegrityService integrityService + ExtensionVersionIntegrityService integrityService, + ObservationRegistry observations ) { return new LocalRegistryService( entityManager, @@ -2369,7 +2370,8 @@ LocalRegistryService localRegistryService( storageUtil, eclipse, cache, - integrityService + integrityService, + observations ); } @@ -2378,14 +2380,15 @@ ExtensionService extensionService( RepositoryService repositories, SearchUtilService search, CacheService cache, - PublishExtensionVersionHandler publishHandler + PublishExtensionVersionHandler publishHandler, + ObservationRegistry observations ) { - return new ExtensionService(repositories, search, cache, publishHandler); + return new ExtensionService(repositories, search, cache, publishHandler, observations); } @Bean - ExtensionValidator extensionValidator() { - return new ExtensionValidator(); + ExtensionValidator extensionValidator(ObservationRegistry observations) { + return new ExtensionValidator(observations); } @Bean @@ -2396,7 +2399,8 @@ StorageUtilService storageUtilService( AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, - EntityManager entityManager + EntityManager entityManager, + ObservationRegistry observations ) { return new StorageUtilService( repositories, @@ -2405,7 +2409,8 @@ StorageUtilService storageUtilService( azureDownloadCountService, search, cache, - entityManager + entityManager, + observations ); } @@ -2422,6 +2427,11 @@ LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator( return new LatestExtensionVersionCacheKeyGenerator(); } + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } + @Bean PublishExtensionVersionHandler publishExtensionVersionHandler( PublishExtensionVersionService service, @@ -2430,7 +2440,8 @@ PublishExtensionVersionHandler publishExtensionVersionHandler( RepositoryService repositories, JobRequestScheduler scheduler, UserService users, - ExtensionValidator validator + ExtensionValidator validator, + ObservationRegistry observations ) { return new PublishExtensionVersionHandler( service, @@ -2439,7 +2450,8 @@ PublishExtensionVersionHandler publishExtensionVersionHandler( repositories, scheduler, users, - validator + validator, + observations ); } } diff --git a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java index 470457bb7..9736af14d 100644 --- a/server/src/test/java/org/eclipse/openvsx/UserAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/UserAPITest.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.function.Consumer; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import org.eclipse.openvsx.cache.CacheService; @@ -579,6 +580,11 @@ TokenService tokenService( LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java index 82f1b8618..c6c03d017 100644 --- a/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java @@ -23,6 +23,7 @@ import java.util.*; import java.util.stream.Collectors; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import com.fasterxml.jackson.core.JsonProcessingException; @@ -958,9 +959,10 @@ UserService userService( RepositoryService repositories, StorageUtilService storageUtil, CacheService cache, - ExtensionValidator validator + ExtensionValidator validator, + ObservationRegistry observations ) { - return new UserService(entityManager, repositories, storageUtil, cache, validator); + return new UserService(entityManager, repositories, storageUtil, cache, validator, observations); } @Bean @@ -971,7 +973,8 @@ StorageUtilService storageUtilService( AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, - EntityManager entityManager + EntityManager entityManager, + ObservationRegistry observations ) { return new StorageUtilService( repositories, @@ -980,7 +983,8 @@ StorageUtilService storageUtilService( azureDownloadCountService, search, cache, - entityManager + entityManager, + observations ); } @@ -993,6 +997,11 @@ VersionService getVersionService() { LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } } } diff --git a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java index 7a2030428..120f41036 100644 --- a/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java +++ b/server/src/test/java/org/eclipse/openvsx/admin/AdminAPITest.java @@ -12,6 +12,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.*; import org.eclipse.openvsx.adapter.VSCodeIdService; import org.eclipse.openvsx.cache.CacheService; @@ -1260,7 +1261,8 @@ LocalRegistryService localRegistryService( StorageUtilService storageUtil, EclipseService eclipse, CacheService cache, - ExtensionVersionIntegrityService integrityService + ExtensionVersionIntegrityService integrityService, + ObservationRegistry observations ) { return new LocalRegistryService( entityManager, @@ -1273,23 +1275,30 @@ LocalRegistryService localRegistryService( storageUtil, eclipse, cache, - integrityService + integrityService, + observations ); } + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } + @Bean ExtensionService extensionService( RepositoryService repositories, SearchUtilService search, CacheService cache, - PublishExtensionVersionHandler publishHandler + PublishExtensionVersionHandler publishHandler, + ObservationRegistry observations ) { - return new ExtensionService(repositories, search, cache, publishHandler); + return new ExtensionService(repositories, search, cache, publishHandler, observations); } @Bean - ExtensionValidator extensionValidator() { - return new ExtensionValidator(); + ExtensionValidator extensionValidator(ObservationRegistry observations) { + return new ExtensionValidator(observations); } @Bean @@ -1300,7 +1309,8 @@ StorageUtilService storageUtilService( AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, - EntityManager entityManager + EntityManager entityManager, + ObservationRegistry observations ) { return new StorageUtilService( repositories, @@ -1309,7 +1319,8 @@ StorageUtilService storageUtilService( azureDownloadCountService, search, cache, - entityManager + entityManager, + observations ); } diff --git a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java index 272c1c01f..4c7f0eeca 100644 --- a/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/cache/CacheServiceTest.java @@ -9,6 +9,7 @@ * ****************************************************************************** */ package org.eclipse.openvsx.cache; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.ExtensionService; import org.eclipse.openvsx.LocalRegistryService; import org.eclipse.openvsx.UserService; diff --git a/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java b/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java index 670ff0190..96632836a 100644 --- a/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/eclipse/EclipseServiceTest.java @@ -18,6 +18,7 @@ import java.io.InputStreamReader; import java.util.Map; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import org.eclipse.openvsx.ExtensionService; @@ -293,15 +294,21 @@ TransactionTemplate transactionTemplate() { return new MockTransactionTemplate(); } + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } + @Bean EclipseService eclipseService( TokenService tokens, TransactionTemplate transactions, ExtensionService extensions, EntityManager entityManager, - RestTemplate restTemplate + RestTemplate restTemplate, + ObservationRegistry observations ) { - return new EclipseService(tokens, transactions, extensions, entityManager, restTemplate); + return new EclipseService(tokens, transactions, extensions, entityManager, restTemplate, observations); } @Bean @@ -309,14 +316,15 @@ ExtensionService extensionService( RepositoryService repositories, SearchUtilService search, CacheService cache, - PublishExtensionVersionHandler publishHandler + PublishExtensionVersionHandler publishHandler, + ObservationRegistry observations ) { - return new ExtensionService(repositories, search, cache, publishHandler); + return new ExtensionService(repositories, search, cache, publishHandler, observations); } @Bean - ExtensionValidator extensionValidator() { - return new ExtensionValidator(); + ExtensionValidator extensionValidator(ObservationRegistry observations) { + return new ExtensionValidator(observations); } @Bean @@ -327,7 +335,8 @@ StorageUtilService storageUtilService( AzureDownloadCountService azureDownloadCountService, SearchUtilService search, CacheService cache, - EntityManager entityManager + EntityManager entityManager, + ObservationRegistry observations ) { return new StorageUtilService( repositories, @@ -336,7 +345,8 @@ StorageUtilService storageUtilService( azureDownloadCountService, search, cache, - entityManager + entityManager, + observations ); } diff --git a/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java b/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java index ef8e47433..4d66f65af 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/DatabaseSearchServiceTest.java @@ -10,6 +10,7 @@ package org.eclipse.openvsx.search; +import io.micrometer.observation.ObservationRegistry; import jakarta.persistence.EntityManager; import org.eclipse.openvsx.cache.LatestExtensionVersionCacheKeyGenerator; import org.eclipse.openvsx.entities.*; @@ -348,5 +349,10 @@ RelevanceService relevanceService(RepositoryService repositories) { LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java b/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java index 946c99ebf..dbc51e1b5 100644 --- a/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java +++ b/server/src/test/java/org/eclipse/openvsx/search/ElasticSearchServiceTest.java @@ -9,6 +9,7 @@ ********************************************************************************/ package org.eclipse.openvsx.search; +import io.micrometer.observation.ObservationRegistry; import org.eclipse.openvsx.cache.LatestExtensionVersionCacheKeyGenerator; import org.eclipse.openvsx.entities.*; import org.eclipse.openvsx.repositories.RepositoryService; @@ -298,6 +299,11 @@ RelevanceService relevanceService(RepositoryService repositories) { LatestExtensionVersionCacheKeyGenerator latestExtensionVersionCacheKeyGenerator() { return new LatestExtensionVersionCacheKeyGenerator(); } + + @Bean + ObservationRegistry observationRegistry() { + return ObservationRegistry.NOOP; + } } } \ No newline at end of file diff --git a/server/src/test/java/org/eclipse/openvsx/util/ArchiveUtilTest.java b/server/src/test/java/org/eclipse/openvsx/util/ArchiveUtilTest.java index 1afe98f7b..1bc0bb3aa 100644 --- a/server/src/test/java/org/eclipse/openvsx/util/ArchiveUtilTest.java +++ b/server/src/test/java/org/eclipse/openvsx/util/ArchiveUtilTest.java @@ -13,6 +13,7 @@ import java.util.zip.ZipFile; +import io.micrometer.observation.ObservationRegistry; import org.junit.jupiter.api.Test; public class ArchiveUtilTest { @@ -24,9 +25,9 @@ public void testTodoTree() throws Exception { try ( var archive = new ZipFile(packageUrl.getPath()); ) { - var packageJson = ArchiveUtil.readEntry(archive, "extension/package.json"); + var packageJson = ArchiveUtil.readEntry(archive, "extension/package.json", ObservationRegistry.NOOP); assertThat(packageJson.length).isEqualTo(44712); - var icon = ArchiveUtil.readEntry(archive, "extension/resources/todo-tree.png"); + var icon = ArchiveUtil.readEntry(archive, "extension/resources/todo-tree.png", ObservationRegistry.NOOP); assertThat(icon.length).isEqualTo(8854); } }