Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adopt to new web platform specifier extension resource url format #758

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public interface IVSCodeService {

ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaultPageSize);

ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path);
ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path);

String download(String namespace, String extension, String version, String targetPlatform);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.entities.Extension;
import org.eclipse.openvsx.entities.ExtensionVersion;
import org.eclipse.openvsx.entities.FileResource;
Expand Down Expand Up @@ -386,15 +387,20 @@ public String download(String namespace, String extension, String version, Strin
}

@Override
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path) {
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path) {
if(isBuiltInExtensionNamespace(namespaceName)) {
return new ResponseEntity<>(("Built-in extension namespace '" + namespaceName + "' not allowed").getBytes(StandardCharsets.UTF_8), null, HttpStatus.BAD_REQUEST);
}

var extVersions = repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName);
var extVersion = extVersions.stream().max(Comparator.<ExtensionVersion, Boolean>comparing(ExtensionVersion::isUniversalTargetPlatform)
.thenComparing(ExtensionVersion::getTargetPlatform))
.orElse(null);
ExtensionVersion extVersion;
if(StringUtils.isEmpty(targetPlatform)) {
var extVersions = repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName);
extVersion = extVersions.stream().max(Comparator.<ExtensionVersion, Boolean>comparing(ExtensionVersion::isUniversalTargetPlatform)
.thenComparing(ExtensionVersion::getTargetPlatform))
.orElse(null);
} else {
extVersion = repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName);
}

if (extVersion == null) {
throw new NotFoundException();
Expand All @@ -412,7 +418,7 @@ public ResponseEntity<byte[]> browse(String namespaceName, String extensionName,

return exactMatch != null
? browseFile(exactMatch, namespaceName, extensionName, extVersion.getTargetPlatform(), version)
: browseDirectory(resources, namespaceName, extensionName, version, path);
: browseDirectory(resources, namespaceName, extensionName, targetPlatform, version, path);
}

private ResponseEntity<byte[]> browseFile(
Expand Down Expand Up @@ -454,12 +460,16 @@ private ResponseEntity<byte[]> browseDirectory(
List<FileResource> resources,
String namespaceName,
String extensionName,
String targetPlatform,
String version,
String path
) {
if(!path.isEmpty() && !path.endsWith("/")) {
path += "/";
}
if(StringUtils.isNotEmpty(targetPlatform)) {
version += "+" + targetPlatform;
}

var urls = new HashSet<String>();
var baseUrl = UrlUtil.createApiUrl(UrlUtil.getBaseUrl(), "vscode", "unpkg", namespaceName, extensionName, version);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Strings;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.UpstreamProxyService;
import org.eclipse.openvsx.UrlConfigService;
import org.eclipse.openvsx.util.HttpHeadersUtil;
Expand Down Expand Up @@ -76,7 +77,11 @@ public ExtensionQueryResult extensionQuery(ExtensionQueryParam param, int defaul
}

@Override
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String path) {
public ResponseEntity<byte[]> browse(String namespaceName, String extensionName, String version, String targetPlatform, String path) {
if(StringUtils.isNotEmpty(targetPlatform)) {
version += "+" + targetPlatform;
}

var urlTemplate = urlConfigService.getUpstreamUrl() + "/vscode/unpkg/{namespace}/{extension}/{version}";
var uriVariables = new HashMap<>(Map.of(
"namespace", namespaceName,
Expand Down
19 changes: 17 additions & 2 deletions server/src/main/java/org/eclipse/openvsx/adapter/VSCodeAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
********************************************************************************/
package org.eclipse.openvsx.adapter;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.openvsx.util.NotFoundException;
import org.eclipse.openvsx.util.TargetPlatform;
import org.eclipse.openvsx.util.UrlUtil;
Expand Down Expand Up @@ -129,12 +130,26 @@ public ResponseEntity<byte[]> browse(
HttpServletRequest request,
@PathVariable String namespaceName,
@PathVariable String extensionName,
@PathVariable String version
@PathVariable String version,
@RequestParam(required = false) String target
) {
if(StringUtils.isEmpty(target)) {
var index = version.lastIndexOf('+');
if(index >= 0 && index + 1 < version.length()) {
target = version.substring(index + 1);
if(TargetPlatform.isValid(target)) {
version = version.substring(0, index);
}
}
}
if(StringUtils.isNotEmpty(target) && !TargetPlatform.isValid(target)) {
target = null;
}

var path = UrlUtil.extractWildcardPath(request);
for (var service : getVSCodeServices()) {
try {
return service.browse(namespaceName, extensionName, version, path);
return service.browse(namespaceName, extensionName, version, target, path);
} catch (NotFoundException exc) {
// Try the next registry
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public interface ExtensionVersionRepository extends Repository<ExtensionVersion,

ExtensionVersion findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String targetPlatform, String extensionName, String namespace);

ExtensionVersion findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue(String version, String targetPlatform, String extensionName, String namespace);

Streamable<ExtensionVersion> findByVersionAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCase(String version, String extensionName, String namespace);

Streamable<ExtensionVersion> findByPublishedWithUser(UserData user);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,10 @@ public List<ExtensionVersion> findActiveExtensionVersionsByVersion(String versio
return extensionVersionJooqRepo.findAllActiveByVersionAndExtensionNameAndNamespaceName(version, extensionName, namespaceName);
}

public ExtensionVersion findActiveExtensionVersionByVersion(String version, String targetPlatform, String extensionName, String namespaceName) {
return extensionVersionRepo.findByVersionAndTargetPlatformAndExtensionNameIgnoreCaseAndExtensionNamespaceNameIgnoreCaseAndActiveTrue(version, targetPlatform, extensionName, namespaceName);
}

public List<ExtensionVersion> findActiveExtensionVersionsByExtensionName(String targetPlatform, String extensionName, String namespaceName) {
return extensionVersionJooqRepo.findAllActiveByExtensionNameAndNamespaceName(targetPlatform, extensionName, namespaceName);
}
Expand Down
163 changes: 163 additions & 0 deletions server/src/test/java/org/eclipse/openvsx/adapter/VSCodeAPITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,80 @@ public void testBrowseTopDir() throws Exception {
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4/extension/\"]"));
}

@Test
public void testBrowseTopDirTargetPlatform() throws Exception {
var version = "1.3.4";
var targetPlatform = "linux-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);

Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "<xml></xml>".getBytes(StandardCharsets.UTF_8));
var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8));
var readmeResource = mockFileResource(17, extVersion, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8));
var changelogResource = mockFileResource(18, extVersion, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8));
var licenseResource = mockFileResource(19, extVersion, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8));
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8));

Mockito.when(repositories.findResourceFileResources(1L, ""))
.thenReturn(List.of(vsixResource, manifestResource, readmeResource, changelogResource, licenseResource, iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}?target={targetPlatform}", namespaceName, extensionName, version, targetPlatform))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4+linux-x64/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4+linux-x64/extension/\"]"));
}

@Test
public void testBrowseTopDirVersionTargetPlatform() throws Exception {
var version = "1.3.4-rc-1+armhf";
var targetPlatform = "darwin-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);

Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var vsixResource = mockFileResource(15, extVersion, "extension.vsixmanifest", RESOURCE, STORAGE_DB, "<xml></xml>".getBytes(StandardCharsets.UTF_8));
var manifestResource = mockFileResource(16, extVersion, "extension/package.json", RESOURCE, STORAGE_DB, "{\"package\":\"json\"}".getBytes(StandardCharsets.UTF_8));
var readmeResource = mockFileResource(17, extVersion, "extension/README.md", RESOURCE, STORAGE_DB, "README".getBytes(StandardCharsets.UTF_8));
var changelogResource = mockFileResource(18, extVersion, "extension/CHANGELOG.md", RESOURCE, STORAGE_DB, "CHANGELOG".getBytes(StandardCharsets.UTF_8));
var licenseResource = mockFileResource(19, extVersion, "extension/LICENSE.txt", RESOURCE, STORAGE_DB, "LICENSE".getBytes(StandardCharsets.UTF_8));
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, "ICON128".getBytes(StandardCharsets.UTF_8));

Mockito.when(repositories.findResourceFileResources(1L, ""))
.thenReturn(List.of(vsixResource, manifestResource, readmeResource, changelogResource, licenseResource, iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}+{targetPlatform}", namespaceName, extensionName, version, targetPlatform))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(content().json("[\"http://localhost/vscode/unpkg/foo/bar/1.3.4-rc-1+armhf+darwin-x64/extension.vsixmanifest\",\"http://localhost/vscode/unpkg/foo/bar/1.3.4-rc-1+armhf+darwin-x64/extension/\"]"));
}

@Test
public void testBrowseVsixManifest() throws Exception {
var version = "1.3.4";
Expand Down Expand Up @@ -601,6 +675,95 @@ public void testBrowseIcon() throws Exception {
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconTargetPlatform() throws Exception {
var version = "1.3.4";
var targetPlatform = "win32-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}?target={targetPlatform}", namespaceName, extensionName, version, "extension/images/icon128.png", targetPlatform))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconVersionTargetPlatform() throws Exception {
var version = "1.3.4-ga+armhf";
var targetPlatform = "alpine-x64";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(targetPlatform);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionByVersion(version, targetPlatform, extensionName, namespaceName))
.thenReturn(extVersion);

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}+{targetPlatform}/{path}", namespaceName, extensionName, version, targetPlatform, "extension/images/icon128.png"))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testBrowseIconVersionInvalidTargetPlatform() throws Exception {
var version = "1.3.4-ga+armhf";
var extensionName = "bar";
var namespaceName = "foo";
var namespace = new Namespace();
namespace.setName(namespaceName);
var extension = new Extension();
extension.setId(0L);
extension.setName(extensionName);
extension.setNamespace(namespace);
var extVersion = new ExtensionVersion();
extVersion.setId(1L);
extVersion.setVersion(version);
extVersion.setTargetPlatform(TargetPlatform.NAME_UNIVERSAL);
extVersion.setExtension(extension);
Mockito.when(repositories.findActiveExtensionVersionsByVersion(version, extensionName, namespaceName))
.thenReturn(List.of(extVersion));

var content = "ICON128".getBytes(StandardCharsets.UTF_8);
var iconResource = mockFileResource(20, extVersion, "extension/images/icon128.png", RESOURCE, STORAGE_DB, content);
Mockito.when(repositories.findResourceFileResources(1L, "extension/images/icon128.png"))
.thenReturn(List.of(iconResource));

mockMvc.perform(get("/vscode/unpkg/{namespaceName}/{extensionName}/{version}/{path}", namespaceName, extensionName, version, "extension/images/icon128.png"))
.andExpect(status().isOk())
.andExpect(content().bytes(content));
}

@Test
public void testDownload() throws Exception {
mockExtensionVersion();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ void testExecuteQueries() {
() -> repositories.topNamespaceExtensionVersions(NOW, 1),
() -> repositories.findFileResourcesByExtensionVersionIdAndType(LONG_LIST, STRING_LIST),
() -> repositories.findActiveExtensionVersionsByVersion("version", "extensionName", "namespaceName"),
() -> repositories.findActiveExtensionVersionByVersion("version", "targetPlatform", "extensionName", "namespaceName"),
() -> repositories.findResourceFileResources(1L, "prefix"),
() -> repositories.findActiveExtensionVersions(LONG_LIST, "targetPlatform"),
() -> repositories.findActiveExtension("name", "namespaceName"),
Expand Down