Skip to content
Closed
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 @@ -209,18 +209,21 @@ private String getPossibleJarResource(VaadinContext context,
URL viteGenerated = resourceProvider
.getApplicationResource(FrontendUtils.VITE_GENERATED_CONFIG);

// If jar!/ exists 2 times for vite.generated.ts then we are
// running from a jar
// If vite.generated.ts is inside 2 archives then we are running from
// a jar, as the jar of flow-server is inside the jar of the
// application
boolean runningFromJar = viteGenerated != null
&& countInstances(viteGenerated.getPath(), "jar!/") >= 2;
&& countArchiveLevels(viteGenerated.getPath()) >= 2;

// As we now know that we are running from a jar we can accept a
// build info with a single jar in the path, so look at those first
List<URL> candidates = runningFromJar ? resources.stream()
.sorted(Comparator.comparingInt(
url -> countInstances(url.getPath(), "jar!/") == 1 ? 0
: 1))
.toList() : resources;
// As we now know that we are running from a jar, the file of the
// application is the one in the outermost archive, so look at the
// least nested ones first
List<URL> candidates = runningFromJar
? resources.stream()
.sorted(Comparator.comparingInt(
url -> countArchiveLevels(url.getPath())))
.toList()
: resources;

for (URL candidate : candidates) {
String content = FrontendUtils
Expand All @@ -236,7 +239,7 @@ private String getPossibleJarResource(VaadinContext context,
// The file is only known to be the right one when it was
// picked by the rule for a packaged application
boolean confidentPick = runningFromJar
&& countInstances(candidate.getPath(), "jar!/") == 1;
&& countArchiveLevels(candidate.getPath()) == 1;
if (candidates.size() > 1 && !confidentPick) {
String warningMessage = String.format(
"Unable to fully determine correct flow-build-info.%n"
Expand Down Expand Up @@ -276,13 +279,27 @@ private boolean isProductionModeTokenFile(String content) {
}
}

/**
* Counts inside how many archives the resource at the given path is.
* <p>
* Both the {@code app.jar!/} separator used for a jar opened from the file
* system and the {@code app.jar/!} separator that Spring Boot 3.2 and newer
* use for an archive nested in the jar of the application are counted, as a
* path may contain one of each:
* {@code nested:/app.jar/!BOOT-INF/lib/flow-server.jar!/vite.generated.ts}.
*
* @param path
* the path of the resource, not {@code null}
* @return the number of archives the resource is inside of, {@code 0} if it
* is not inside one
*/
private int countArchiveLevels(String path) {
return countInstances(path, "jar!/") + countInstances(path, "jar/!");
}

/**
* Counts how many times {@code value} occurs as a non-overlapping substring
* within {@code input}.
* <p>
* Used to determine how many nested {@code jar!/} segments appear in a
* resource path, which indicates whether the resource is packaged inside
* one or several jars.
*
* @param input
* the string to search within, not {@code null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,37 @@
"The token file of the application should be used instead of the one of a dependency");
}

@Test
void create_nestedJarsOfSpringBoot_tokenFileOfTheApplicationIsUsed()
throws IOException {
VaadinContext context = Mockito.mock(VaadinContext.class);

Check warning on line 290 in flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "mock".

See more on https://sonarcloud.io/project/issues?id=vaadin_flow&issues=AaCRfN025ux-jae3bjUt&open=AaCRfN025ux-jae3bjUt&pullRequest=25680
VaadinConfig config = Mockito.mock(VaadinConfig.class);

Check warning on line 291 in flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "mock".

See more on https://sonarcloud.io/project/issues?id=vaadin_flow&issues=AaCRfN025ux-jae3bjUu&open=AaCRfN025ux-jae3bjUu&pullRequest=25680
ResourceProvider resourceProvider = mockResourceProvider(config,
context);

// Spring Boot 3.2 and newer separate the jar of the application from
// the archive nested in it with '/!' instead of '!/'
Mockito.when(resourceProvider

Check warning on line 297 in flow-server/src/test/java/com/vaadin/flow/server/startup/DefaultApplicationConfigurationFactoryTest.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use a static import for "when".

See more on https://sonarcloud.io/project/issues?id=vaadin_flow&issues=AaCRfN025ux-jae3bjUv&open=AaCRfN025ux-jae3bjUv&pullRequest=25680
.getApplicationResource(FrontendUtils.VITE_GENERATED_CONFIG))
.thenReturn(new URL("file", "", -1,
"nested:/opt/app.jar/!BOOT-INF/lib/flow-server.jar!/"
+ FrontendUtils.VITE_GENERATED_CONFIG));

mockClassPathTokenFiles(resourceProvider, mockTokenFileUrl(
"nested:/opt/app.jar/!BOOT-INF/lib/addon.jar!/",
"{ \"productionMode\": true, \"externalStatsUrl\": \"http://addon/stats.json\" }"),
mockTokenFileUrl("file:/opt/app.jar!/",
"{ \"productionMode\": true, \"externalStatsUrl\": \"http://application/stats.json\" }"));

DefaultApplicationConfigurationFactory factory = new DefaultApplicationConfigurationFactory();
ApplicationConfiguration configuration = factory.create(context);

assertEquals("http://application/stats.json",
configuration.getStringProperty(Constants.EXTERNAL_STATS_URL,
null),
"The token file of the application should be used instead of the one of a dependency");
}

@Test
void create_unparseableTokenFileInsideJar_tokenFileIsIgnored()
throws IOException {
Expand Down
Loading