package com.walker.infrastructure.utils;
import com.walker.infrastructure.ApplicationRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* JAR自定义组件部署者
*
* 此组件能够把打包好的jar文件中的所有文件(或者指定文件)解压到指定的地方,
* 通常是classpath下面。打包的文件是符合标准的*.jar,但里面具体内容并不一定
* 是编译好的class也能是一些资源文件,如:图片、js、css等。
*
* 最初编写的目的是把用户web项目lib中的组件内容解压的系统webroot目录中。
* @author shikeying
* @date 2013-8-29
*
*/
public abstract class JarDeployer {
private static final Logger logger = LoggerFactory.getLogger(JarDeployer.class);
/**
* 部署jar的前缀,有默认值,也可设置
*/
public static String DEPLOY_JAR_PREFIX = "walkersoft-resource-";
public static final String DEPLOY_FILENAME = "walker-deploy.properties";
/**
* 尚未部署的应用jar集合
*/
public static final List DEPLOY_WAIT_FILES = new ArrayList();
/**
* 已经部署过的jar列表,key = jarName, value = 解压时间毫秒值
*/
public static final Map DEPLOYED_FILES = new TreeMap();
/**
* 返回系统classpath
的根路径
* 如:d:/webapp/demo/WEB-INF/classes/
*/
public static final String classpathAbsolute = PathSolver.classpathAbsolute;
/**
* 返回系统web应用程序的根路径,如:d:/webapp/demo/
*/
public static final String webappRootAbsolute = PathSolver.webappRootAbsolute;
/**
* 返回web应用程序lib路径,如:d:/webapp/demo/WEB-INF/lib/
*/
public static final String webappLibAbsolute = PathSolver.webappLibAbsolute;
public static final String CLASSES_PATH = "classes/";
public static final String WEBINF_PATH = "WEB-INF/";
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// instance variable
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
private String jarName; // jar名字,不包括路径
private JarDeployer(){}
public void setJarName(String jarName) {
this.jarName = jarName;
}
/**
* 创建web项目发布者实现对象,从lib目录中找jar包,并解压到webroot中
* @param jarName
* @return
*/
public static final JarDeployer getWebappLibInstance(String jarName){
JarDeployer webappLibJarDeployer = new WebAppLibJarDeployer();
webappLibJarDeployer.setJarName(jarName);
return webappLibJarDeployer;
}
/**
* 解压并部署文件
* 系统会把 webapp/WEB-INF/lib 路径中的jar文件中的所有内容解压到 webapp/下面。
* @return
*/
public Object deploy(){
String _srcPath = getSourcePath();
String _destPath = getDestinationPath();
logger.debug("##############################################");
logger.debug("getSourcePath() = " + _srcPath);
logger.debug("getDestinationPath() = " + _destPath);
logger.debug("##############################################");
if(_srcPath != null && _destPath != null){
return deploy(_srcPath, _destPath);
}
return null;
}
/**
* 解压并部署文件
* 系统会把输入路径中的jar文件中的所有内容解压到目的地。
* @param srcPath 源路径,如:d:/src
* @param destinationPath 目的地路径,如:d:/webroot
* @return
*/
public Object deploy(String srcPath, String destinationPath){
String _srcPath = getSourcePath();
String _destPath = getDestinationPath();
logger.debug("deploy --> getSourcePath() = " +_srcPath);
if(_srcPath == null)
_srcPath = srcPath;
if(_srcPath == null)
throw new NullPointerException("srcPath is required.");
if(_destPath == null)
_destPath = destinationPath;
if(_destPath == null)
throw new NullPointerException("destinationPath is required.");
if(!_srcPath.trim().endsWith("/")){
_srcPath += "/";
}
_srcPath += jarName; // jar文件完整路径
_srcPath = FileSystemUtils.trimFileSchema(_srcPath);
_destPath = FileSystemUtils.trimFileSchema(_destPath);
logger.debug("要解压的jar文件是:" + _srcPath);
logger.debug("目的路径 :" + _destPath);
try{
decompress(_srcPath, _destPath);
} catch(Error e){
throw new Error("部署文件失败! \t可能文件不存在,或者不符合jar文件规范。", e);
}
return "success";
}
public synchronized void decompress(String fileName,String outputPath){
if(!outputPath.endsWith(File.separator)){
outputPath += File.separator;
}
File dir = new File(outputPath);
if(!dir.exists()){
dir.mkdirs();
}
JarFile jf = null;
String outFileName = null;
try {
jf = new JarFile(fileName);
for(Enumeration e = jf.entries();e.hasMoreElements();){
JarEntry je = e.nextElement();
if(je == null) continue;
// jar中的META-INF目录忽略
if(je.getName().indexOf("META-INF") >= 0){
logger.debug("忽略META-INF目录");
continue;
}
outFileName = outputPath + je.getName();
File f = new File(outFileName);
if(je.isDirectory()){
if(!f.exists()) f.mkdirs();
}else{
File pf = f.getParentFile();
if(!pf.exists()){
pf.mkdirs();
}
InputStream in = null;
OutputStream out = null;
try{
in = jf.getInputStream(je);
if(in == null){
logger.error("......InputStream is null,未发现对象,je: " + je.getName());
continue;
}
out = new BufferedOutputStream(new FileOutputStream(f));
byte[] buffer = new byte[2048];
int nBytes = 0;
while((nBytes = in.read(buffer)) > 0 ){
out.write(buffer,0,nBytes);
}
} catch(Exception ex){
ex.printStackTrace();
logger.error("复制文件发生错误: " + je.getName() + ", " + ex.getMessage());
continue;
} finally {
if(out != null){
out.flush();
out.close();
}
if(in != null){
in.close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
String s = "解压" + outFileName + "出错, "+e.getMessage();
logger.error(s);
throw new Error(s, e);
}finally{
if(jf != null){
try {
logger.info("jar file is closed: " + jf.getName());
jf.close();
// File jar = new File(jf.getName());
// if(jar.exists()){
// jar.delete();
// }
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/**
* 返回原始目标路径,即:要读取JAR文件的路径
* @return
*/
public abstract String getSourcePath();
/**
* 返回目的路径,即:要拷贝文件的路径
* @return
*/
public abstract String getDestinationPath();
private static class PathSolver {
static String classpathAbsolute = getClasspathAbsolute();
static String webappRootAbsolute = getWebAppRootAbsolute(classpathAbsolute);
static String webappLibAbsolute = getWebAppLibAbsolute(classpathAbsolute);
static final String getClasspathAbsolute(){
ClassPathResource cpr = new ClassPathResource(".");
try {
return FileSystemUtils.trimFileSchema(cpr.getURL().toString());
} catch (IOException e) {
// TODO Auto-generated catch block
logger.error(e.getMessage());
throw new Error("classpath solver error! cause = " + e);
}
}
static final String getWebAppRootAbsolute(String classpathAbs){
if(StringUtils.isNotEmpty(classpathAbs)){
return classpathAbs.replaceFirst(WEBINF_PATH + CLASSES_PATH, "");
}
return null;
}
static final String getWebAppLibAbsolute(String classpathAbs){
if(StringUtils.isNotEmpty(classpathAbs)){
return classpathAbs.replaceFirst(CLASSES_PATH, "lib/");
}
return null;
}
}
private static class WebAppLibJarDeployer extends JarDeployer{
private String sourcePath;
private String destinationPath;
public WebAppLibJarDeployer(){}
@Override
public String getSourcePath() {
// TODO Auto-generated method stub
if(this.sourcePath == null){
// int classesStartInx = JarDeployer.classpathAbsolute.lastIndexOf(CLASSES_PATH);
// int classesEndInx = classesStartInx + 8;
// StringBuilder p = new StringBuilder(JarDeployer.classpathAbsolute);
// p.delete(classesStartInx, classesEndInx);
// p.append("lib/");
// this.sourcePath = p.toString();
this.sourcePath = combinLibByClasspath();
}
return this.sourcePath;
}
/**
* 原始返回目的拷贝路径,如:d:/webapp/demo/,但目前由于需要拷贝freemarker模板文件,
* 因此先改为:d:/webapp/demo/WEB-INF/pages/ftl/
*/
@Override
public String getDestinationPath() {
if(this.destinationPath == null){
// int webinfStartInx = JarDeployer.classpathAbsolute.lastIndexOf(WEBINF_PATH);
// this.destinationPath = JarDeployer.classpathAbsolute.substring(0, webinfStartInx);
int classesStartInx = JarDeployer.classpathAbsolute.lastIndexOf(CLASSES_PATH);
this.destinationPath = JarDeployer.classpathAbsolute.substring(0, classesStartInx)
+ "pages/ftl/";
}
return this.destinationPath;
}
}
/**
* 返回WEB应用程序的lib目录的绝对路径
* 如:d:/test/webapp/WEB-INF/lib/
* @return
*/
public static String getWebLibPath(){
return FileSystemUtils.trimFileSchema(combinLibByClasspath());
}
static String combinLibByClasspath(){
int classesStartInx = JarDeployer.classpathAbsolute.lastIndexOf(CLASSES_PATH);
int classesEndInx = classesStartInx + 8;
StringBuilder p = new StringBuilder(JarDeployer.classpathAbsolute);
p.delete(classesStartInx, classesEndInx);
p.append("lib/");
return p.toString();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 部署jar相关代码,2014-11-06
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/**
* 部署的状态,默认:未部署
*/
private static AtomicBoolean deployedStatus = new AtomicBoolean(false);
/**
* 返回部署状态,如果已经部署成功,返回true
* @return
*/
public static final boolean getDeployedStatus(){
return deployedStatus.get();
}
/**
* 检查部署情况,如果不存在classpath:walker-deploy.properties文件说明初始化,需要直接跳转到部署页面;
* 如果已经存在了,还需要检查lib下面的相关jar是否与文件中的对应,因为有可能会新添加文件;
*
*/
public static boolean checkDeployStatus(String deployJarPrefix){
List availableJars = getMatchDeployedJars(deployJarPrefix);
if(StringUtils.isEmptyList(availableJars)){
// 如果没有可用的jar,就不再提示用户。
deployedStatus.compareAndSet(false, true);
return deployedStatus.get();
}
ClassPathResource deployFile = new ClassPathResource(JarDeployer.DEPLOY_FILENAME);
if(deployFile.exists()){
// 存在部署文件
Properties deployProperties = new Properties();
try {
deployProperties.load(deployFile.getInputStream());
} catch (IOException e) {
throw new ApplicationRuntimeException("加载文件:'"+JarDeployer.DEPLOY_FILENAME+"' 出现异常", e);
}
// 如果存在部署文件,而且存在内容,需要与现有lib中jar比对
// 如果配置文件中记录了,比较时间戳,时间戳变化了也要重新部署。
String jarName = null;
for(File jarFile : availableJars){
jarName = jarFile.getName();
if(deployProperties.containsKey(jarName)){
// long recordTime = Long.parseLong(deployProperties.getProperty(jarName));
// 时间间隔在30秒之内都视为部署成功,因为写磁盘时间和java记录时间有延时。
// 2-不好判断,因为拷贝写入jar的时间和程序运行后部署记录的时间没任何关系
// if(Math.abs(recordTime - jarFile.lastModified()) >= 30*1000){
// JarDeployer.DEPLOY_WAIT_FILES.add(jarFile);
// }
logger.debug("=============> 找到了一个匹配的(部署过)jar: " + jarName);
JarDeployer.DEPLOYED_FILES.put(jarName, Long.parseLong(deployProperties.getProperty(jarName)));
} else {
logger.debug("-------------> 找到一个未更新的jar,记录并在后续解压:" + jarName);
JarDeployer.DEPLOY_WAIT_FILES.add(jarFile);
}
}
// 仍然存在需要更新的,如:刚添加了新jar
if(JarDeployer.DEPLOY_WAIT_FILES.size() > 0){
return false;
} else {
deployedStatus.compareAndSet(false, true);
return deployedStatus.get();
}
} else {
// 不存在部署文件
try {
createEmptyFile(JarDeployer.classpathAbsolute + JarDeployer.DEPLOY_FILENAME);
for(File jarFile : availableJars){
JarDeployer.DEPLOY_WAIT_FILES.add(jarFile);
}
return false;
} catch (IOException e) {
throw new ApplicationRuntimeException("创建部署文件失败,系统启动没有成功", e);
}
}
}
/**
* 创建空文件
* @param filepath
* @throws IOException
*/
public static final void createEmptyFile(String filepath) throws IOException{
File file = new File(filepath);
if(!file.exists()){
file.createNewFile();
} else
System.out.println("file exist: " + filepath);
}
private static File[] getAppLibFiles(){
File appLibFolder = new File(JarDeployer.webappLibAbsolute);
return appLibFolder.listFiles();
}
/**
* 返回找到的应用可部署的jar集合
* @param deployJarPrefix 可部署的jar文件名前缀,如:walkersoft-resource-
* @return
*/
public static List getMatchDeployedJars(String deployJarPrefix){
File[] jarList = getAppLibFiles();
if(jarList != null){
List result = new ArrayList(8);
for(File f : jarList){
// 因为都是jar文件,不会存在其他
if(f.getName().startsWith(deployJarPrefix)){
logger.debug("找到了匹配的jar: " + f.getName());
result.add(f);
}
}
return result;
}
return null;
}
/**
* 更新jar部署包的时间戳,并记录到文件。
* @throws IOException
*/
public static final void updateDeployedJarTimestamp() throws Exception{
if(DEPLOY_WAIT_FILES.size() > 0){
// 加载现有的记录更新配置文件
ClassPathResource deployFile = new ClassPathResource(JarDeployer.DEPLOY_FILENAME);
Properties deployProperties = new Properties();
try {
deployProperties.load(deployFile.getInputStream());
} catch (IOException e) {
throw new ApplicationRuntimeException("加载文件:'"+JarDeployer.DEPLOY_FILENAME+"' 出现异常", e);
}
long timestamp = 0;
for(File f : DEPLOY_WAIT_FILES){
JarDeployer jarDeployer = JarDeployer.getWebappLibInstance(f.getName());
jarDeployer.deploy();
timestamp = System.currentTimeMillis();
// 部署时间戳记录到文件
deployProperties.setProperty(f.getName(), String.valueOf(timestamp));
// 系统缓存'已部署文件'列表
JarDeployer.DEPLOYED_FILES.put(f.getName(), timestamp);
}
// 持久化更新文件
deployProperties.store(new FileWriter(new File(classpathAbsolute + DEPLOY_FILENAME)), "更新记录");
}
// 从系统'等待部署'列表中删除
// removeWaitDeployList(f.getName());
DEPLOY_WAIT_FILES.clear();
deployedStatus.compareAndSet(false, true);
}
/**
* 返回给定jar包中特定属性文件的内容,可能会有多个,因此返回集合
* @param jarFileName jar包的文件名,全路径
* @param resourcePrefix 指定属性文件的前缀,如:app_
* @return
*/
public static final List getJarRootResources(String jarFileName, String resourcePrefix){
if(StringUtils.isEmpty(jarFileName)) return null;
List propertiesList = new ArrayList(2);
JarFile jf = null;
try {
jf = new JarFile(jarFileName);
JarEntry je = null;
Properties properties = null;
for(Enumeration e = jf.entries();e.hasMoreElements();){
je = e.nextElement();
if(je == null) continue;
if(je.isDirectory()) continue;
if(je.getName().startsWith(resourcePrefix)){
logger.debug("找到了需要的entry: " + je.getName());
properties = new Properties();
properties.load(jf.getInputStream(je));
propertiesList.add(properties);
} else
continue;
}
return propertiesList;
} catch (IOException e) {
throw new ApplicationRuntimeException("加载JAR中的文件出现错误: " + jarFileName, e);
} finally {
if(jf != null){
try {
jf.close();
} catch (IOException e) {
}
}
}
}
// /**
// * 返回规则的系统路径,去掉前面的文件协议:file:/或者file:///
// * 因为发现JAVA中的File
对象不认带协议的文件路径。
// * @param path
// * @return
// */
// private static String getFileSystemPath(String path){
// if(path.startsWith("file:/")){
// path = path.substring(6);
// } else if(path.startsWith("file:///")){
// path = path.substring(8);
// }
// return path;
// }
/**
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* test code
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* @param args
*/
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
// ClassPathResource cpr = new ClassPathResource("com/walker/infrastructure/utils/JarDeployer.class");
// ClassPathResource cpr = new ClassPathResource(".");
// System.out.println("file path = " + cpr.getPath());
// System.out.println("file path = " + JarDeployer.classpathAbsolute);
// JarDeployer jarDeployer = JarDeployer.getWebappLibInstance("walker-nanoreport-web-resource-0.1.1.jar");
// System.out.println("resource = " + jarDeployer.getSourcePath());
// System.out.println("destinat = " + jarDeployer.getDestinationPath());
// jarDeployer.deploy();
// System.out.println(JarDeployer.getWebLibPath());
System.out.println(JarDeployer.classpathAbsolute);
System.out.println(JarDeployer.webappRootAbsolute);
// d:/logs/walkersoft-flow-v1.0.jar
JarDeployer.getJarRootResources("d:/logs/walkersoft-flow-v1.0.jar", "app_");
}
}