Skip to content

Commit

Permalink
implement lists for hosted repository
Browse files Browse the repository at this point in the history
  • Loading branch information
stklcode committed Aug 14, 2024
1 parent 65b787f commit a2816cc
Show file tree
Hide file tree
Showing 7 changed files with 120 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ FluentAsset upload(String vendor, String project, String version, String sourceT

Content getPackagesJson() throws IOException;

Content getListJson(String filter) throws IOException;

Content getProviderJson(String vendor, String project) throws IOException;

Content getPackageJson(String vendor, String project) throws IOException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public Response handle(@Nonnull final Context context) throws Exception {
case PACKAGES:
return HttpResponses.ok(hostedFacet.getPackagesJson());
case LIST:
throw new IllegalStateException("Unsupported assetKind: " + assetKind);
return responseFor(hostedFacet.getListJson(context.getRequest().getParameters().get("filter")));
case PROVIDER:
return responseFor(hostedFacet.getProviderJson(getVendorToken(context), getProjectToken(context)));
case PACKAGE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import javax.inject.Named;
import java.io.IOException;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.google.common.base.Preconditions.checkNotNull;

Expand All @@ -38,6 +40,8 @@ public class ComposerHostedFacetImpl
extends FacetSupport
implements ComposerHostedFacet
{
private static final Pattern FILTER_PATTERN = Pattern.compile("\\s*(?<vendor>[*a-zA-Z0-9_.-]+)/(?<project>[*a-zA-Z0-9_.-]+)\\s*");

private final ComposerJsonProcessor composerJsonProcessor;

@Inject
Expand Down Expand Up @@ -69,6 +73,18 @@ public Content getPackagesJson() throws IOException {
return composerJsonProcessor.generatePackagesFromComponents(getRepository(), content().components());
}

@Override
public Content getListJson(String filter) throws IOException {
FluentQuery<FluentComponent> components;
if (filter == null || filter.isEmpty()) {
components = content().components();
} else {
components = queryComponents(filter);
}

return composerJsonProcessor.generateListFromComponents(components);
}

@Override
public Content getProviderJson(final String vendor, final String project) throws IOException {
Optional<Content> content = content().get(ComposerPathUtils.buildProviderPath(vendor, project));
Expand Down Expand Up @@ -125,6 +141,24 @@ private FluentQuery<FluentComponent> queryComponents(final String vendor, final
);
}

private FluentQuery<FluentComponent> queryComponents(final String filter) {
Matcher m = FILTER_PATTERN.matcher(filter);
if (m.matches()) {
String vendor = m.group("vendor").replaceAll("\\*+", "%");
String project = m.group("project").replaceAll("\\*+", "%");

return content()
.components()
.byFilter(
"namespace LIKE #{filterParams.vendor} AND name LIKE #{filterParams.project}",
ImmutableMap.of("vendor", vendor, "project", project)
);
} else {
// invalid filter pattern
return null;
}
}

private ComposerContentFacet content() {
return getRepository().facet(ComposerContentFacet.class);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*/
package org.sonatype.nexus.repository.composer.internal;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.hash.Hashing;
Expand All @@ -31,6 +32,7 @@
import org.sonatype.nexus.repository.view.Payload;
import org.sonatype.nexus.repository.view.payloads.StringPayload;

import javax.annotation.Nullable;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
Expand Down Expand Up @@ -195,6 +197,27 @@ private Content buildPackagesJson(final Repository repository, final Set<String>
return new Content(new StringPayload(mapper.writeValueAsString(packagesJson), ContentTypes.APPLICATION_JSON));
}

/**
* Generates a list.json file based on the components provided.
*
* @param components Components to process
* @return JSON list with package names
*/
public Content generateListFromComponents(@Nullable final FluentQuery<FluentComponent> components) throws IOException {
Set<String> packages = new HashSet<>();
if (components != null) {
Continuation<FluentComponent> comps = components.browse(PAGE_SIZE, null);
while (!comps.isEmpty()) {
comps.stream().map(comp -> comp.namespace() + "/" + comp.name()).forEach(packages::add);
comps = components.browse(PAGE_SIZE, comps.nextContinuationToken());
}
}

Map<String, Object> packagesJson = singletonMap(PACKAGE_NAMES_KEY, packages.stream().sorted().collect(Collectors.toList()));

return new Content(new StringPayload(mapper.writeValueAsString(packagesJson), ContentTypes.APPLICATION_JSON));
}

/**
* Rewrites the provider JSON so that source entries are removed and dist entries are pointed back to Nexus.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.sonatype.nexus.repository.view.ConfigurableViewFacet
import org.sonatype.nexus.repository.view.Router
import org.sonatype.nexus.repository.view.ViewFacet

import static org.sonatype.nexus.repository.composer.AssetKind.LIST
import static org.sonatype.nexus.repository.composer.AssetKind.PACKAGE
import static org.sonatype.nexus.repository.composer.AssetKind.PACKAGES
import static org.sonatype.nexus.repository.composer.AssetKind.PROVIDER
Expand Down Expand Up @@ -94,6 +95,18 @@ class ComposerHostedRecipe
.handler(downloadHandler)
.create())

builder.route(listMatcher()
.handler(timingHandler)
.handler(assetKindHandler.rcurry(LIST))
.handler(securityHandler)
.handler(exceptionHandler)
.handler(handlerContributor)
.handler(conditionalRequestHandler)
.handler(partialFetchHandler)
.handler(contentHeadersHandler)
.handler(downloadHandler)
.create())

builder.route(providerMatcher()
.handler(timingHandler)
.handler(assetKindHandler.rcurry(PROVIDER))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@
import org.sonatype.nexus.repository.Repository;
import org.sonatype.nexus.repository.composer.AssetKind;
import org.sonatype.nexus.repository.composer.ComposerHostedFacet;
import org.sonatype.nexus.repository.view.Content;
import org.sonatype.nexus.repository.view.Context;
import org.sonatype.nexus.repository.view.Payload;
import org.sonatype.nexus.repository.view.Response;
import org.sonatype.nexus.repository.view.*;
import org.sonatype.nexus.repository.view.matchers.token.TokenMatcher;

import org.junit.Before;
Expand All @@ -32,8 +29,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.when;
import static org.sonatype.nexus.repository.composer.AssetKind.*;
import static org.sonatype.nexus.repository.composer.internal.recipe.ComposerRecipeSupport.NAME_TOKEN;
Expand Down Expand Up @@ -73,7 +68,10 @@ public class ComposerHostedDownloadHandlerTest
private Content content;

@Mock
private Payload payload;
private Request request;

@Mock
private Parameters parameters;

@Mock
private AttributesMap attributes;
Expand All @@ -85,6 +83,8 @@ public void setUp() {
when(repository.facet(ComposerHostedFacet.class)).thenReturn(composerHostedFacet);
when(context.getRepository()).thenReturn(repository);
when(context.getAttributes()).thenReturn(attributes);
when(context.getRequest()).thenReturn(request);
when(request.getParameters()).thenReturn(parameters);
when(attributes.require(TokenMatcher.State.class)).thenReturn(state);
when(state.getTokens()).thenReturn(tokens);
}
Expand Down Expand Up @@ -142,11 +142,24 @@ public void testHandlePackageAbsent() throws Exception {
assertThat(response.getPayload(), is(nullValue()));
}


@Test
public void testHandleList() {
public void testHandleList() throws Exception {
when(attributes.require(AssetKind.class)).thenReturn(LIST);
IllegalStateException e = assertThrows(IllegalStateException.class, () -> underTest.handle(context));
assertEquals("Unsupported assetKind: " + LIST, e.getMessage());

// No filter
when(parameters.get("filter")).thenReturn(null);
when(composerHostedFacet.getListJson(null)).thenReturn(content);
Response response = underTest.handle(context);
assertThat(response.getStatus().getCode(), is(200));
assertThat(response.getPayload(), is(content));

// With filter
when(parameters.get("filter")).thenReturn("test/*");
when(composerHostedFacet.getListJson("test/*")).thenReturn(content);
response = underTest.handle(context);
assertThat(response.getStatus().getCode(), is(200));
assertThat(response.getPayload(), is(content));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.sonatype.nexus.repository.content.fluent.FluentComponent;
import org.sonatype.nexus.repository.content.fluent.FluentComponents;
import org.sonatype.nexus.repository.content.fluent.FluentQuery;
import org.sonatype.nexus.repository.content.fluent.internal.FluentComponentQueryImpl;
import org.sonatype.nexus.repository.view.Content;
import org.sonatype.nexus.repository.view.Payload;

Expand Down Expand Up @@ -99,6 +100,29 @@ public void testGetPackagesJson() throws Exception {
assertThat(underTest.getPackagesJson(), is(content));
}

@Test
public void testGetListJson() throws Exception {
// Without filter
when(composerJsonProcessor.generateListFromComponents(components)).thenReturn(content);
assertThat(underTest.getListJson(null), is(content));

// With filter
FluentQuery<FluentComponent> query = mock(FluentComponentQueryImpl.class);
when(components.byFilter("namespace LIKE #{filterParams.vendor} AND name LIKE #{filterParams.project}",
ImmutableMap.of("vendor", "test", "project", "%"))).thenReturn(query);
when(composerJsonProcessor.generateListFromComponents(query)).thenReturn(content);
assertThat(underTest.getListJson("test/*"), is(content));

when(components.byFilter("namespace LIKE #{filterParams.vendor} AND name LIKE #{filterParams.project}",
ImmutableMap.of("vendor", "%abc%", "project", "pr0_j3cT"))).thenReturn(query);
when(composerJsonProcessor.generateListFromComponents(query)).thenReturn(content);
assertThat(underTest.getListJson("*abc**/pr0_j3cT"), is(content));

// Invalid filter
when(composerJsonProcessor.generateListFromComponents(null)).thenReturn(content);
assertThat(underTest.getListJson("In\\al1d"), is(content));
}

@Test
public void testGetProviderJson() throws Exception {
when(composerContentFacet.get(PROVIDER_PATH)).thenReturn(Optional.of(content));
Expand Down

0 comments on commit a2816cc

Please sign in to comment.