Skip to content
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
8 changes: 6 additions & 2 deletions common/src/main/java/com/highgo/cloud/util/SshUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ public static String getCurrentDirPath() {
if (path.contains(".jar!/")) {
path = path.substring(0, path.indexOf(".jar!/") + 4);
}
File file = new File(path);
File file = new File(java.net.URI.create(path).getPath());
path = file.getParentFile().getAbsolutePath();
return path;
}
Expand Down Expand Up @@ -681,7 +681,11 @@ public static void copyDirFilesFromJar(Class clazz, String dirPath) throws IOExc
}
} else {
// ide运行
File file = new File(url.getPath());
File file = new File(java.net.URI.create(url.toString()));
if (file.isFile()) {
SshUtil.copy(dirPath);
return;
}
List<File> allFileList = new ArrayList<>();
getAllFile(file, allFileList);

Expand Down
189 changes: 189 additions & 0 deletions tests/regression/ResourceFileRegression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.io.File;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/** Full SshUtil, real Spring resource resolution, and nonempty private files. */
public final class ResourceFileRegression {
private static final String XML_PATH = "monitor/ivory/alert-rule.xml";
private static final String BINARY_PATH = "monitor/ivory/dashboards/chart.bin";
private static final byte[] BINARY = {0, 1, -1, 10, 13, 42};
private static int passed;
private static int failed;

private static void require(boolean condition, String message) {
if (!condition) {
throw new AssertionError(message);
}
}

private static void verifyBytes(Path target, byte[] expected) throws Exception {
require(Files.isRegularFile(target), "Missing copied file: " + target);
require(Arrays.equals(expected, Files.readAllBytes(target)),
"Copied bytes differ: " + target);
}

private static void verifyXml(Path target) throws Exception {
Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.parse(target.toFile());
require("alert-rule".equals(doc.getDocumentElement().getNodeName()),
"Wrong XML root");
NodeList rules = doc.getElementsByTagName("rule");
require(rules.getLength() == 4, "Expected four official rules");
String[] names = {"CPULow", "CPUHigh", "MemoryLow", "MemoryHigh"};
String[] thresholds = {"30", "80", "30", "80"};
for (int i = 0; i < names.length; i++) {
Element rule = (Element) rules.item(i);
require(names[i].equals(rule.getAttribute("name")), "Wrong rule name");
Element threshold = (Element) rule.getElementsByTagName("threshold").item(0);
require(thresholds[i].equals(threshold.getAttribute("value")),
"Wrong threshold");
}
}

private static URL resourceUrl(Path resources, Path work, boolean jar) throws Exception {
if (!jar) {
return resources.toUri().toURL();
}
Path archive = work.resolve("application-resources.jar");
try (OutputStream output = Files.newOutputStream(archive);
JarOutputStream zip = new JarOutputStream(output);
Stream<Path> paths = Files.walk(resources)) {
for (Path file : paths.collect(Collectors.toList())) {
String relative = resources.relativize(file).toString().replace(File.separatorChar, '/');
String entry = "BOOT-INF/classes/" + relative;
if (Files.isDirectory(file) && !entry.endsWith("/")) {
entry += "/";
}
zip.putNextEntry(new JarEntry(entry));
if (Files.isRegularFile(file)) {
Files.copy(file, zip);
}
zip.closeEntry();
}
}
return new URL("jar:" + archive.toUri().toURL() + "!/BOOT-INF/classes/");
}

private static void runCase(String name, String requested, boolean directory,
boolean jar, String[] args) {
ClassLoader previous = Thread.currentThread().getContextClassLoader();
try {
Path privateRoot = Paths.get(args[1]).toRealPath();
Path classes = Paths.get(args[2]).toRealPath();
Path realSpring = Paths.get(args[3]).toRealPath();
byte[] officialXml = Files.readAllBytes(Paths.get(args[4]).toRealPath());
require(Files.isDirectory(privateRoot) && privateRoot.getParent() != null,
"Main must supply a private non-root directory");
Path work = Files.createTempDirectory(privateRoot, "ivory-resource-file-").toRealPath();
require(work.getParent().equals(privateRoot), "Invalid private fixture boundary");
Path library = Files.createDirectory(work.resolve("library"));
Path resources = Files.createDirectory(work.resolve("resources"));
Path springCopy = library.resolve("spring-core.jar");
Files.copy(realSpring, springCopy);
verifyBytes(springCopy, Files.readAllBytes(realSpring));
Files.copy(classes.resolve("ResourcePathMarker.class"),
resources.resolve("ResourcePathMarker.class"));
Path sourceXml = resources.resolve(XML_PATH);
Path sourceBinary = resources.resolve(BINARY_PATH);
Files.createDirectories(sourceBinary.getParent());
Files.write(sourceXml, officialXml);
Files.write(sourceBinary, BINARY);
require(!library.startsWith(resources) && !resources.startsWith(library),
"Source and destination must be separate private trees");

List<URL> urls = new ArrayList<>();
urls.add(resourceUrl(resources, work, jar));
urls.add(classes.toUri().toURL());
urls.add(springCopy.toUri().toURL());
for (int i = 5; i < args.length; i++) {
urls.add(Paths.get(args[i]).toRealPath().toUri().toURL());
}
ClassLoader platform = ClassLoader.getSystemClassLoader().getParent();
try (URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[0]), platform)) {
Thread.currentThread().setContextClassLoader(loader);
Class<?> ssh = Class.forName("com.highgo.cloud.util.SshUtil", true, loader);
Class<?> marker = Class.forName("ResourcePathMarker", true, loader);
require(marker.getClassLoader() == loader, "Fixture marker delegated unexpectedly");
String destination = (String) ssh.getMethod("getCurrentDirPath").invoke(null);
require(Paths.get(destination).toRealPath().equals(library),
"Copy must target the private Spring CodeSource parent");
ssh.getMethod("copyDirFilesFromJar", Class.class, String.class)
.invoke(null, marker, requested);
if (directory || XML_PATH.equals(requested)) {
verifyBytes(library.resolve(XML_PATH), officialXml);
verifyXml(library.resolve(XML_PATH));
}
if (directory || BINARY_PATH.equals(requested)) {
verifyBytes(library.resolve(BINARY_PATH), BINARY);
}
verifyBytes(sourceXml, officialXml);
verifyBytes(sourceBinary, BINARY);
Thread.currentThread().setContextClassLoader(previous);
}
passed++;
System.out.println("PASS " + name);
} catch (Throwable error) {
failed++;
System.err.println("FAIL " + name);
Throwable cause = error instanceof InvocationTargetException
? ((InvocationTargetException) error).getCause() : error;
cause.printStackTrace(System.err);
} finally {
Thread.currentThread().setContextClassLoader(previous);
}
// Main owns private-root cleanup after all real JAR handles close.
}

public static void main(String[] args) throws Exception {
if (args.length < 6 || !("ide".equals(args[0]) || "jar".equals(args[0]))) {
throw new IllegalArgumentException(
"ide|jar privateRoot classes realSpring.jar officialAlertXml otherDependency.jar ...");
}
boolean jar = "jar".equals(args[0]);
if (jar && File.separatorChar != '/') {
throw new UnsupportedOperationException(
"Existing JAR branch compares native Paths separators with BOOT-INF/classes/; "
+ "run this control on Unix, do not count it as a Windows pass or skip");
}
runCase("official single XML resource", XML_PATH, false, jar, args);
runCase("single binary resource", BINARY_PATH, false, jar, args);
runCase("nonempty recursive directory control", "monitor/ivory", true, jar, args);
System.out.println("RESULT passed=" + passed + " failed=" + failed);
if (failed != 0) {
throw new AssertionError("Regression failures: " + failed);
}
}
}
22 changes: 22 additions & 0 deletions tests/regression/ResourcePathMarker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** An application class whose real class loader owns the local resource fixture. */
public final class ResourcePathMarker {
private ResourcePathMarker() {
}
}
118 changes: 118 additions & 0 deletions tests/regression/ResourcePathRegression.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/** Real class-loader and filesystem regression for the complete production SshUtil. */
public final class ResourcePathRegression {
private static int passed;
private static int failed;

private static void require(boolean condition, String message) {
if (!condition) {
throw new AssertionError(message);
}
}

private static void runCase(String name, String suffix, boolean resourceWalk, String[] args) {
try {
Path privateRoot = Paths.get(args[0]).toRealPath();
Path classes = Paths.get(args[1]).toRealPath();
Path realSpring = Paths.get(args[2]).toRealPath();
require(Files.isDirectory(privateRoot) && privateRoot.getParent() != null,
"Main must supply a private non-root directory");
Path work = Files.createTempDirectory(privateRoot, "ivory-resource-path-").toRealPath();
require(work.getParent().equals(privateRoot), "Unexpected private fixture boundary");
Path springHome = Files.createDirectory(work.resolve(suffix));
Path springFixture = springHome.resolve("spring-core.jar");
// Copy the existing real dependency byte-for-byte only into this
// runtime fixture; no dependency source or archive is modified.
Files.copy(realSpring, springFixture);
require(Files.size(realSpring) == Files.size(springFixture), "Incomplete dependency copy");
Path resourceHome = Files.createDirectory(work.resolve("resources-" + suffix));
Files.createDirectories(resourceHome.resolve("monitor/ivory"));
Files.copy(classes.resolve("ResourcePathMarker.class"),
resourceHome.resolve("ResourcePathMarker.class"));

List<URL> urls = new ArrayList<>();
urls.add(resourceHome.toUri().toURL());
urls.add(classes.toUri().toURL());
urls.add(springFixture.toUri().toURL());
for (int i = 3; i < args.length; i++) {
urls.add(Paths.get(args[i]).toRealPath().toUri().toURL());
}
// Do not delegate to the system application's Spring copy: the
// production code must see the real encoded fixture CodeSource.
ClassLoader platform = ClassLoader.getSystemClassLoader().getParent();
try (URLClassLoader loader = new URLClassLoader(urls.toArray(new URL[0]), platform)) {
Class<?> ssh = Class.forName("com.highgo.cloud.util.SshUtil", true, loader);
if (resourceWalk) {
Class<?> marker = Class.forName("ResourcePathMarker", true, loader);
ssh.getMethod("copyDirFilesFromJar", Class.class, String.class)
.invoke(null, marker, "monitor/ivory");
// Empty but valid classpath directories exercise the real
// IDE traversal without writing into the dependency cache.
try (Stream<Path> entries = Files.list(springHome)) {
require(entries.count() == 1, "Empty traversal unexpectedly copied files");
}
} else {
String actual = (String) ssh.getMethod("getCurrentDirPath").invoke(null);
require(Paths.get(actual).toAbsolutePath().normalize().equals(springHome),
"CodeSource directory was not decoded exactly once: " + actual
+ " expected " + springHome);
}
}
passed++;
System.out.println("PASS " + name);
} catch (Throwable error) {
failed++;
System.err.println("FAIL " + name);
Throwable cause = error instanceof InvocationTargetException
? ((InvocationTargetException) error).getCause() : error;
cause.printStackTrace(System.err);
}
// Main owns cleanup of its private root after the JVM has released JARs.
}

public static void main(String[] args) throws Exception {
if (args.length < 4) {
throw new IllegalArgumentException(
"privateRoot classes realSpringCore.jar otherRealDependency.jar ...");
}
Path root = Paths.get(args[0]).toRealPath();
require(root.toUri().getRawPath().equals(root.toUri().getPath()),
"Use Main's private ASCII path without spaces/percent escapes so plain controls stay plain");
runCase("CodeSource directory with spaces", "library space", false, args);
runCase("CodeSource directory with Unicode", "库目录", false, args);
runCase("plain CodeSource control", "plain", false, args);
runCase("literal plus CodeSource control", "literal+plus", false, args);
runCase("classpath directory with spaces", "resource space", true, args);
runCase("plain classpath directory control", "resource-plain", true, args);
System.out.println("RESULT passed=" + passed + " failed=" + failed);
if (failed != 0) {
throw new AssertionError("Regression failures: " + failed);
}
}
}