org.zodiac.plugin.extension.resources.resolver.PluginResourceWebMvcResolver Maven / Gradle / Ivy
package org.zodiac.plugin.extension.resources.resolver;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.resource.AbstractResourceResolver;
import org.springframework.web.servlet.resource.ResourceResolverChain;
import org.zodiac.plugin.factory.PluginRegistryInfo;
import org.zodiac.plugin.factory.process.pipe.loader.PluginResource;
import org.zodiac.plugin.realize.BasePlugin;
import org.zodiac.sdk.toolkit.constants.CharPool;
import org.zodiac.sdk.toolkit.constants.StringPool;
import org.zodiac.sdk.toolkit.util.lang.StrUtil;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 插件资源WebMvc发现者。
*/
public class PluginResourceWebMvcResolver extends AbstractResourceResolver {
private final static Logger logger = LoggerFactory.getLogger(PluginResourceWebMvcResolver.class);
private final static String RESOLVED_RESOURCE_CACHE_KEY_PREFIX = "resolvedPluginResource:";
PluginResourceWebMvcResolver() {
}
@Override
protected Resource resolveResourceInternal(HttpServletRequest request, String requestPath,
List extends Resource> locations, ResourceResolverChain chain) {
int startOffset = (requestPath.startsWith("/") ? 1 : 0);
int endOffset = requestPath.indexOf('/', 1);
if (endOffset != -1) {
String pluginId = requestPath.substring(startOffset, endOffset);
String partialPath = requestPath.substring(endOffset + 1);
PluginStaticResource pluginResource = PluginResourceResolvers.getPluginStaticResource(pluginId);
if (pluginResource == null) {
return null;
}
String key = computeKey(request, requestPath);
/*先判断缓存中是否存在。*/
Resource resource = pluginResource.getCacheResource(key);
if (resource != null) {
return resource;
}
/*从classpath 获取资源*/
resource = resolveClassPath(pluginResource, partialPath);
if (resource != null) {
pluginResource.putCacheResource(key, resource);
return resource;
}
/*从外置文件路径获取资源*/
resource = resolveFilePath(pluginResource, partialPath);
if (resource != null) {
pluginResource.putCacheResource(key, resource);
return resource;
}
return null;
}
return chain.resolveResource(request, requestPath, locations);
}
@Override
protected String resolveUrlPathInternal(String resourceUrlPath, List extends Resource> locations,
ResourceResolverChain chain) {
return super.resolveUrlPath(resourceUrlPath, locations, chain);
}
/**
* 计算key。
*
* @param request request
* @param requestPath 请求路径
* @return 返回key
*/
protected String computeKey(HttpServletRequest request, String requestPath) {
StringBuilder key = new StringBuilder(RESOLVED_RESOURCE_CACHE_KEY_PREFIX);
key.append(requestPath);
if (request != null) {
String codingKey = getContentCodingKey(request);
if (StrUtil.isNotBlank(codingKey)) {
key.append("+encoding=").append(codingKey);
}
}
return key.toString();
}
/**
* 解决 ClassPath 的资源文件。也就是插件中定义的classpath:/xx/xx/
配置。
*
* @param pluginResource 插件资源配置Bean
* @param partialPath 部分路径
* @return 资源。没有发现则返回null
*/
private Resource resolveClassPath(PluginStaticResource pluginResource, String partialPath) {
Set classPaths = pluginResource.getClassPaths();
if (classPaths == null || classPaths.isEmpty()) {
return null;
}
PluginRegistryInfo pluginRegistryInfo = pluginResource.getPluginRegistryInfo();
BasePlugin basePlugin = pluginRegistryInfo.getBasePlugin();
if (basePlugin == null) {
return null;
}
ClassLoader pluginClassLoader = pluginRegistryInfo.getPluginClassLoader();
for (String classPath : classPaths) {
try {
PluginResource resource = new PluginResource(classPath + partialPath, pluginRegistryInfo);
resource.setClassLoader(pluginClassLoader);
if (resource.exists()) {
return resource;
}
} catch (Exception e) {
logger.debug("Get static resources of classpath '{}' error.", classPath, e);
}
}
return null;
}
/**
* 解决插件中配置的绝对文件路径的文件资源。也就是插件中定义的file:D://xx/xx/
配置。
*
* @param pluginResource 插件资源配置Bean
* @param partialPath 部分路径
* @return 资源。没有发现则返回null
*/
private Resource resolveFilePath(PluginStaticResource pluginResource, String partialPath) {
Set filePaths = pluginResource.getFilePaths();
if (filePaths == null || filePaths.isEmpty()) {
return null;
}
for (String filePath : filePaths) {
Path fullPath = Paths.get(filePath + partialPath);
if (!Files.exists(fullPath)) {
continue;
}
try {
FileUrlResource fileUrlResource = new FileUrlResource(fullPath.toString());
if (fileUrlResource.exists()) {
return fileUrlResource;
}
} catch (Exception e) {
logger.debug("Get static resources of path '{}' error.", fullPath, e);
}
}
return null;
}
/**
* 根据请求获取内容code key。
*
* @param request request
* @return key
*/
private String getContentCodingKey(HttpServletRequest request) {
String header = request.getHeader(HttpHeaders.ACCEPT_ENCODING);
if (StrUtil.isBlank(header)) {
return null;
}
return Arrays.stream(StringUtils.tokenizeToStringArray(header, StringPool.COMMA)).map(token -> {
int index = token.indexOf(CharPool.SEMICOLON);
return (index >= 0 ? token.substring(0, index) : token).trim().toLowerCase();
}).sorted().collect(Collectors.joining(StringPool.COMMA));
}
}