package org.smartly.commons.io.repository.deploy; import org.smartly.commons.cryptograph.GUID; import org.smartly.commons.io.repository.FileRepository; import org.smartly.commons.io.repository.Resource; import org.smartly.commons.lang.CharEncoding; import org.smartly.commons.logging.Level; import org.smartly.commons.logging.Logger; import org.smartly.commons.logging.util.LoggingUtils; import org.smartly.commons.util.*; import java.io.*; import java.net.URL; import java.net.URLDecoder; import java.util.*; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author angelo.geminiani */ public abstract class FileDeployer { // ------------------------------------------------------------------------ // Constants // ------------------------------------------------------------------------ private static final String CHARSET = CharEncoding.getDefault(); private static final String DIRECTIVE_VERSION = "[RT-VERSION]"; private static final String DIRECTIVE_VERSION_VALUE = GUID.create(); private static final String DIRECTIVE_DEBUG_APP = "[RT-DEBUG]"; private static final String DIRECTIVE_DEBUG_JS = "[RT-DEBUGJS]"; // ------------------------------------------------------------------------ // Variables // ------------------------------------------------------------------------ private final FileDeployerSettings _settings; private final String _startFolder; private final String _targetFolder; private boolean _overwrite; private String[] _always_overwrite_items; private String[] _never_overwrite_items; private final boolean _silent; // completly silent private final boolean _verbose; // log details private final boolean _debugApp; private final boolean _debugJs; // ------------------------------------------------------------------------ // Constructor // ------------------------------------------------------------------------ public FileDeployer(final String startFolder, final String targetFolder, final boolean verbose, final boolean debugApp, final boolean debugJs) { this(startFolder, targetFolder, false, verbose, debugApp, debugJs); } public FileDeployer(final String startFolder, final String targetFolder, final boolean silent, final boolean verbose, final boolean debugApp, final boolean debugJs) { this.logInfo("Creating FileDeployer '{0}'. " + "Start Folder: '{1}', Target Folder: '{2}'", this.getClass().getSimpleName(), startFolder, targetFolder); _settings = new FileDeployerSettings(_globalSettings); _startFolder = startFolder; _targetFolder = targetFolder; _overwrite = false; _always_overwrite_items = new String[0]; _never_overwrite_items = new String[0]; _silent = silent; _verbose = verbose; _debugApp = debugApp; _debugJs = debugJs; this.init(); } // ------------------------------------------------------------------------ // Public // ------------------------------------------------------------------------ public FileDeployerSettings getSettings() { return _settings; } public String getSourceFolder() { return _startFolder; } public String getTargetFolder() { return _targetFolder; } public boolean isOverwrite() { return _overwrite; } public void setOverwrite(boolean overwrite) { _overwrite = overwrite; } public String[] getAlwaysOverwriteItems() { return _always_overwrite_items; } public void setAlwaysOverwriteItems(final String[] value) { _always_overwrite_items = value; } public String[] getNeverOverwriteItems() { return _never_overwrite_items; } public void setNeverOverwriteItems(final String[] value) { _never_overwrite_items = value; } public void deployChildren() { this.deploy(_targetFolder, true); } public void deploy() { this.deploy(_targetFolder, false); } /** * Deploy content into target * * @param targetFolder Parent root. i.e. "c:\", "ftp://USERNAME:PASSWORD@host:21/myfolder/mysubfolder" * @param children Deploy only content of startFolder into targetFolder */ public void deploy(final String targetFolder, final boolean children) { //-- get resources and start deploy --// final List<FileItem> resources = this.loadResources(_startFolder); this.logStart(); for (final FileItem item : resources) { final String message = this.deploy(targetFolder, item, children); if (StringUtils.hasText(message)) { this.logInfo(message); } } } public abstract byte[] compress(final byte[] data, final String filename); public abstract byte[] compile(final byte[] data, final String filename); // ------------------------------------------------------------------------ // p r i v a t e // ------------------------------------------------------------------------ protected Logger getLogger() { return LoggingUtils.getLogger(this); } private void init() { _settings.getPreprocessorValues().put(DIRECTIVE_DEBUG_APP, _debugApp + ""); _settings.getPreprocessorValues().put(DIRECTIVE_DEBUG_JS, _debugJs + ""); } private void logStart() { if (!_silent) { this.getLogger().log(Level.INFO, FormatUtils.format( "FILE DEPLOYER: Running FileDeployer: {0}\n" + "\t Target Path: {1}, \n" + "\t Overwrite Target: {2}", this.getClass().getName(), _targetFolder, _overwrite)); } } private void logInfo(final String text, final Object... args) { if (_verbose) { this.getLogger().log(Level.INFO, FormatUtils.format( "FILE DEPLOYER: " + text, args)); } } private String deploy(final String targetFolder, final FileItem item, final boolean children) { String message = ""; final String filename = item.getFileName(); // check if file extension is not excluded if (this.isDeployable(filename)) { // targetPath is absolute file name of deployed file String targetPath; if (children) { targetPath = (new File(PathUtils.merge(targetFolder, PathUtils.subtract(_startFolder, filename)))).getAbsolutePath(); } else { targetPath = (new File(PathUtils.merge(targetFolder, filename))).getAbsolutePath(); } final String targetName = (new File(targetPath)).getName(); final boolean exists = PathUtils.exists(targetPath); //target.exists(); final boolean overwrite = this.isOverwritable(filename); final String ext = PathUtils.getFilenameExtension(filename, true); String compressedPath = null; Exception exc = null; int deployed = 0; if (!exists || overwrite) { if (!item.isDirectory()) { try { FileUtils.mkdirs(targetPath); final String packagename = item.getPackageName(); final InputStream in = this.read(packagename); if (null != in) { deployed = 1; byte[] binaryData = ByteUtils.getBytes(in); //-- pre-process file --// if (_settings.isPreProcessableExt(ext)) { // pre-process binaryData = this.preProcess(binaryData); } //-- compile file --// if (_settings.isCompilableExt(ext)) { // compile final byte[] compiledData = this.compile(binaryData, targetName); if (null != compiledData && compiledData.length > 0) { // replace data with compiled data binaryData = compiledData; final String outExt = _settings.getCompileFiles().get(ext); if (StringUtils.hasText(outExt) && !outExt.equalsIgnoreCase(ext)) { // change target file name targetPath = PathUtils.changeFileExtension(targetPath, outExt); } } } //-- deploy file --// FileUtils.copy(binaryData, new File(targetPath)); //-- compress file --// if (_settings.isCompressibleExt(ext(targetPath))) { // creates new minified file final byte[] compressedData = this.compress(binaryData, targetPath); if (null != compressedData && compressedData.length > 0) { compressedPath = _settings.getMiniFilename(targetPath); FileUtils.copy(compressedData, new File(compressedPath)); } } try { in.close(); } catch (Throwable ignored) { } } else { deployed = 0; } } catch (Exception ex) { exc = ex; deployed = -1; } } else { deployed = 2; } } // log deploy status if (deployed == 0) { message = FormatUtils.format( "FAULT! File '{0}' not deployed into '{1}': exists={2} and overwrite={3}", item.getPackageName(), targetPath, exists, overwrite); } else if (deployed == -1) { message = FormatUtils.format( "FAULT! File '{0}' not deployed into '{1}': {2}", item.getPackageName(), targetPath, exc); } else if (deployed == 2) { message = FormatUtils.format( "INFO! '{0}' is a Directory and has not been deployed.", item.getPackageName()); } else { message = FormatUtils.format( "SUCCESS! File '{0}' deployed into '{1}': exists={2} and overwrite={3}", item.getPackageName(), targetPath, exists, overwrite); if (StringUtils.hasLength(compressedPath)) { message += "\n\t"; message += FormatUtils.format("COMPRESSED File '{0}' into '{1}'", targetPath, compressedPath); } } } return message; } private String ext(final String file) { return PathUtils.getFilenameExtension(file, true); } private boolean isDeployable(final String item) { return !_settings.isExcluded(item); } private boolean isOverwritable(final String item) { if (!_overwrite) { if (!CollectionUtils.isEmpty(_always_overwrite_items)) { final String name = PathUtils.getFilename(item, true); for (final String pattern : _always_overwrite_items) { final String regex = pattern.replaceAll("\\*", ".*"); if (this.match(name, regex)) { return true; } } } } //-- never overwrite this items --// if (!CollectionUtils.isEmpty(_never_overwrite_items)) { final String name = PathUtils.getFilename(item, true); for (final String pattern : _never_overwrite_items) { final String regex = pattern.replaceAll("\\*", ".*"); if (this.match(name, regex)) { return false; } } } return _overwrite; } private InputStream read(final String packagename) throws FileNotFoundException { return ClassLoaderUtils.getResourceAsStream(packagename); } private byte[] preProcess(final byte[] text) throws UnsupportedEncodingException { String result = new String(text); if (StringUtils.hasText(result)) { final Set<String> keys = _settings.getPreprocessorValues().keySet(); for (final String key : keys) { if (result.contains(key)) { result = StringUtils.replace(result, key, _settings.getPreprocessorValues().get(key)); } } } return result.getBytes(CharEncoding.getDefault()); } private List<FileItem> loadResources(final String startFolder) { final List<FileItem> result = new LinkedList<FileItem>(); try { final String root = this.getRootFullPath(); final String folder = PathUtils.join(root, startFolder); this.logInfo("LOADING resources from Root: '{0}', " + "Folder: '{1}'", root, folder); //-- get children names --// final String[] children; if (PathUtils.isJar(folder)) { children = this.getResourcesFromJar(folder); } else { children = this.getResourcesFromRepository(folder); } //-- creates resource and add to list --// for (final String child : children) { result.add(new FileItem(this, root, child)); } this.logInfo("Created FileDeployer '{0}'. Resources: {1}", this.getClass().getSimpleName(), result.size()); } catch (Throwable t) { this.getLogger().severe(FormatUtils.format("Unable to Create FileDeployer: {0}", t)); } return result; } private boolean match(String text, String pattern) { if (!StringUtils.hasText(text)) { return false; } Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(text); return m.find(); } private String getRootFullPath() { final URL url = ClassLoaderUtils.getResource(null, this.getClass(), ""); return null != url ? url.getPath() : ""; } private String[] getResourcesFromRepository(final String path) throws IOException { final Set<String> result = new HashSet<String>(); final FileRepository repository = new FileRepository(path); final Resource[] children = repository.getResources(true); for (final Resource child : children) { result.add(child.getPath()); } return result.toArray(new String[result.size()]); } private String[] getResourcesFromJar(final String path) throws IOException { /* A JAR path */ final int jarIdx = path.indexOf("!"); if (jarIdx == -1) { return new String[0]; } // strin out checkpath final String checkpath = path.substring(jarIdx + 2); // strip out only the JAR file final String jarPath = path.substring(5, jarIdx); final File jarFile = new File(URLDecoder.decode( jarPath, CHARSET)); if (!jarFile.exists()) { return new String[0]; } final JarFile jar = new JarFile(jarFile); final Enumeration<JarEntry> entries = jar.entries(); //gives ALL entries in jar final Set<String> resNames = new HashSet<String>(); //avoid duplicates in case it is a subdirectory while (entries.hasMoreElements()) { final String name = entries.nextElement().getName(); if (name.startsWith(checkpath)) { //filter according to the path String entry = name.substring(checkpath.length()); if (StringUtils.hasText(entry)) { int checkSubdir = entry.indexOf("/"); if (checkSubdir >= 0) { // if it is a subdirectory, we just return the directory name //entry = entry.substring(0, checkSubdir); } final String resname = "jar:" + PathUtils.join(path, entry); resNames.add(resname); // debug logging this.getLogger().log(Level.FINER, FormatUtils.format("path='{0}', name='{1}', " + "entry='{2}', resname='{3}'", path, name, entry, resname)); } } } return resNames.toArray(new String[resNames.size()]); } // ------------------------------------------------------------------------ // S T A T I C // ------------------------------------------------------------------------ private static final List<FileDeployer> _deployers = Collections.synchronizedList(new LinkedList<FileDeployer>()); //-- global settings --// private static final FileDeployerSettings _globalSettings = new FileDeployerSettings(); { //-- init pre-processor values --// _globalSettings.getPreprocessorValues().put(DIRECTIVE_VERSION, DIRECTIVE_VERSION_VALUE); //-- add exclusions --// _globalSettings.getExcludeFiles().add(".class"); } public static FileDeployerSettings getGlobalSettings() { return _globalSettings; } public static Set<String> getPreProcessorFiles() { return _globalSettings.getPreProcessorFiles(); } public static Map<String, String> getPreprocessorValues() { return _globalSettings.getPreprocessorValues(); } public static Set<String> getCompressFiles() { return _globalSettings.getCompressFiles(); } public static Map<String, String> getCompileFiles() { return _globalSettings.getCompileFiles(); } /** * Returns minified file name or empty string if file is already a minified file. * * @param sourcePath Source File Name * @return Empty String or minified file name. If sourcePath is already a minified file, returns empty string. */ public static String getMiniFilename(final String sourcePath) { return getGlobalSettings().getMiniFilename(sourcePath); } public static void register(final FileDeployer deployer) { synchronized (_deployers) { if (!_deployers.contains(deployer)) { _deployers.add(deployer); } } } public static void deployAll() { synchronized (_deployers) { for (final FileDeployer deployer : _deployers) { deployer.deploy(); } // remove deployed _deployers.clear(); } } }