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_"); } }