com.alipay.sofa.ark.bootstrap.ClasspathLauncher Maven / Gradle / Ivy
/*
* 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.
*/
package com.alipay.sofa.ark.bootstrap;
import com.alipay.sofa.ark.common.util.*;
import com.alipay.sofa.ark.exception.ArkRuntimeException;
import com.alipay.sofa.ark.loader.*;
import com.alipay.sofa.ark.loader.archive.JarFileArchive;
import com.alipay.sofa.ark.spi.archive.*;
import com.alipay.sofa.ark.spi.constant.Constants;
import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
import static com.alipay.sofa.ark.spi.constant.Constants.ARK_CONF_BASE_DIR;
import static com.alipay.sofa.ark.spi.constant.Constants.SUREFIRE_BOOT_CLASSPATH;
import static com.alipay.sofa.ark.spi.constant.Constants.SUREFIRE_BOOT_CLASSPATH_SPLIT;
/**
* @author ruoshan
* @since 0.1.0
*/
public class ClasspathLauncher extends ArkLauncher {
public ClasspathLauncher(ClassPathArchive classPathArchive) {
super(classPathArchive);
}
public static class ClassPathArchive implements ExecutableArchive {
public static final String FILE_IN_JAR = "!/";
private final String className;
private final String methodName;
private final URL[] urls;
protected final URLClassLoader urlClassLoader;
private File arkConfBaseDir;
public ClassPathArchive(String className, String methodName, URL[] urls) throws IOException {
AssertUtils.isFalse(StringUtils.isEmpty(className),
"Entry class name must be specified.");
this.className = className;
this.methodName = methodName;
this.urls = urls;
List classpath = getConfClasspath();
classpath.addAll(Arrays.asList(this.urls));
urlClassLoader = new URLClassLoader(classpath.toArray(new URL[] {}), null);
}
public List filterUrls(String resource) throws Exception {
List urlList = new ArrayList<>();
Enumeration enumeration = urlClassLoader.findResources(resource);
while (enumeration.hasMoreElements()) {
URL resourceUrl = enumeration.nextElement();
String resourceFile = resourceUrl.getFile();
String jarFile = resourceFile.substring(0,
resourceFile.length() - resource.length() - FILE_IN_JAR.length());
urlList.add(new URL(jarFile));
}
return urlList;
}
@Override
public ContainerArchive getContainerArchive() throws Exception {
ContainerArchive archive = getJarContainerArchive();
if (archive == null) {
archive = createDirectoryContainerArchive();
}
if (archive == null) {
throw new ArkRuntimeException("No Ark Container Jar File Found.");
}
return archive;
}
protected ContainerArchive getJarContainerArchive() throws Exception {
List urlList = filterUrls(Constants.ARK_CONTAINER_MARK_ENTRY);
if (urlList.isEmpty()) {
return null;
}
if (urlList.size() > 1) {
throw new ArkRuntimeException("Duplicate Container Jar File Found.");
}
return new JarContainerArchive(new JarFileArchive(FileUtils.file(urlList.get(0)
.getFile())));
}
@Override
public List getBizArchives() throws Exception {
List urlList = filterUrls(Constants.ARK_BIZ_MARK_ENTRY);
List bizArchives = new LinkedList<>();
if (className != null && methodName != null) {
bizArchives.add(createDirectoryBizModuleArchive());
}
for (URL url : urlList) {
bizArchives
.add(new JarBizArchive(new JarFileArchive(FileUtils.file(url.getFile()))));
}
return bizArchives;
}
@Override
public List getPluginArchives() throws Exception {
List urlList = filterUrls(Constants.ARK_PLUGIN_MARK_ENTRY);
List pluginArchives = new ArrayList<>();
for (URL url : urlList) {
pluginArchives.add(new JarPluginArchive(new JarFileArchive(FileUtils.file(url
.getFile()))));
}
return pluginArchives;
}
@Override
public List getConfClasspath() throws IOException {
List urls = new ArrayList<>();
if (arkConfBaseDir == null) {
arkConfBaseDir = deduceArkConfBaseDir();
}
scanConfClasspath(arkConfBaseDir, urls);
return urls;
}
private void scanConfClasspath(File arkConfBaseDir, List classpath) throws IOException {
if (arkConfBaseDir == null || arkConfBaseDir.isFile()
|| arkConfBaseDir.listFiles() == null) {
return;
}
classpath.add(arkConfBaseDir.toURI().toURL());
for (File subFile : arkConfBaseDir.listFiles()) {
scanConfClasspath(subFile, classpath);
}
}
private File deduceArkConfBaseDir() {
File arkConfDir = null;
try {
URLClassLoader tempClassLoader = new URLClassLoader(urls);
Class entryClass = tempClassLoader.loadClass(className);
String classLocation = ClassUtils.getCodeBase(entryClass);
if (classLocation.startsWith("file:")) {
classLocation = classLocation.substring("file:".length());
}
File file = classLocation == null ? null : FileUtils.file(classLocation);
while (file != null) {
arkConfDir = FileUtils
.file(file.getPath() + File.separator + ARK_CONF_BASE_DIR);
if (arkConfDir.exists() && arkConfDir.isDirectory()) {
break;
}
file = file.getParentFile();
}
} catch (Throwable throwable) {
throw new ArkRuntimeException(throwable);
}
// return 'conf/' directory or null
return arkConfDir == null ? null : arkConfDir.getParentFile();
}
@Override
public URL getUrl() {
throw new RuntimeException("unreachable invocation.");
}
@Override
public Manifest getManifest() {
throw new RuntimeException("unreachable invocation.");
}
@Override
public List getNestedArchives(EntryFilter filter) {
throw new RuntimeException("unreachable invocation.");
}
@Override
public Archive getNestedArchive(Entry entry) {
throw new RuntimeException("unreachable invocation.");
}
@Override
public InputStream getInputStream(ZipEntry zipEntry) {
throw new RuntimeException("unreachable invocation.");
}
@Override
public Iterator iterator() {
throw new RuntimeException("unreachable invocation.");
}
protected BizArchive createDirectoryBizModuleArchive() {
return new DirectoryBizArchive(className, methodName, filterBizUrls(urls));
}
protected ContainerArchive createDirectoryContainerArchive() {
URL[] candidates;
if (fromSurefire(urls)) {
candidates = parseClassPathFromSurefireBoot(getSurefireBooterJar(urls));
} else {
candidates = urls;
}
URL[] filterUrls = filterURLs(candidates);
return filterUrls == null ? null : new DirectoryContainerArchive(filterUrls);
}
protected boolean fromSurefire(URL[] urls) {
if (urls.length <= 4) {
for (URL url : urls) {
if (url.getFile().contains(Constants.SUREFIRE_BOOT_JAR)) {
return true;
}
}
}
return false;
}
private URL getSurefireBooterJar(URL[] urls) {
for (URL url : urls) {
if (url.getFile().contains(Constants.SUREFIRE_BOOT_JAR)) {
return url;
}
}
return null;
}
/**
* this method is used to choose jar file which is contained in sofa-ark-all.jar
*
* @return
*/
protected URL[] filterURLs(URL[] urls) {
Set arkContainerJarMarkers = DirectoryContainerArchive
.getArkContainerJarMarkers();
Set containerClassPath = new HashSet<>();
for (String marker : arkContainerJarMarkers) {
for (URL url : urls) {
if (url.getPath().contains(marker)) {
containerClassPath.add(url);
}
}
}
return arkContainerJarMarkers.size() != containerClassPath.size() ? null
: containerClassPath.toArray(new URL[] {});
}
/**
* this method is used to eliminate agent classpath and biz classpath
*
* @param urls
* @return
*/
protected URL[] filterBizUrls(URL[] urls) {
URL[] agentClassPath = ClassLoaderUtils.getAgentClassPath();
List urlList;
try {
urlList = filterUrls(Constants.ARK_BIZ_MARK_ENTRY);
} catch (Throwable throwable) {
// ignore
urlList = Collections.emptyList();
}
List bizURls = new ArrayList<>();
boolean isAgent;
for (URL url : urls) {
isAgent = false;
for (URL agentUrl : agentClassPath) {
if (url.equals(agentUrl)) {
isAgent = true;
break;
}
}
if (!isAgent && !urlList.contains(url)) {
bizURls.add(url);
}
}
return bizURls.toArray(new URL[] {});
}
/**
* when execute mvn test, the classpath would be recorded in a MANIFEST.MF file ,
* including a surefire boot jar.
*
* @param surefireBootJar
* @return
*/
protected URL[] parseClassPathFromSurefireBoot(URL surefireBootJar) {
AssertUtils.assertNotNull(surefireBootJar, "SurefireBooter jar should not be null.");
try (JarFile jarFile = new JarFile(surefireBootJar.getFile())) {
String[] classPath = jarFile.getManifest().getMainAttributes()
.getValue(SUREFIRE_BOOT_CLASSPATH).split(SUREFIRE_BOOT_CLASSPATH_SPLIT);
List urls = new ArrayList<>();
for (String path : classPath) {
urls.add(new URL(path));
}
return urls.toArray(new URL[] {});
} catch (IOException ex) {
throw new ArkRuntimeException("Parse classpath failed from surefire boot jar.", ex);
}
}
}
}