de.tsl2.nano.jarresolver.JarResolver Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.jarresolver Show documentation
Show all versions of tsl2.nano.jarresolver Show documentation
TSL2 Framework Runtime-Jar-Resolver (can load missed Classes through an intelligent package finding mechanism using maven)
/*
* File: $HeadURL$
* Id : $Id$
*
* created by: Tom
* created on: 28.05.2014
*
* Copyright: (c) Thomas Schneider 2014, all rights reserved
*/
package de.tsl2.nano.jarresolver;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.commons.logging.Log;
import de.tsl2.nano.core.AppLoader;
import de.tsl2.nano.core.ENV;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.execution.SystemUtil;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.MapUtil;
import de.tsl2.nano.core.util.NetUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
/**
* Resolves all given dependencies defined in jarresolver.properties or through main args. Uses maven - downloading
* maven, creating a dynamic pom.xml and downloading the dependencies through maven.
*
*
* Features:
* - installs maven by itself (through an internet connection)
* - transforms known class names (with package) to known jar-dependencies
* - creates dynamically a pom.xml holding all dependencies
* - loads all given dependencies through maven to the current path
*
* Example:
* JarResolver.main("ant-1.7.0", "org.hsqldb.jdbcDriver");
*
* In the example you see, that you are able to mix jar-names and class-names. If you use a class-name, JarResolver will
* resolve the default jar-version for you.
*
* For more informations, read comments on 'jarresolver.properties'.
*
* ALGORITHM:
* 1. class name
* a. try to find the package (starting with PACKAGE)
* b. if not found, try to find it through a part of a package, cutting the rest (at least two parts must retain e.g. org.company)
* c. if not found, use the first two parts as groupId and the third part as artifactId (e.g.: org.company/product)
* 2. artifact name
* a. find the package path through all PACKAGE values
* b. if not found, cut last part concatenated through '-' and try to find PACKAGE value again. if found, use that as groupId.
* c. if not found, use artifact name as group name
*
*
*
* @author Tom
* @version $Revision$
*/
public class JarResolver {
private static final Log LOG = LogFactory.getLog(JarResolver.class);
Properties props;
String mvnRoot;
String basedir;
/*
* for a description of these constants, see jarresolver.properties
*/
static final String URL_UPDATE_PROPERTIES = "default.update.url";
static final String URL_MVN_DOWNLOAD = "mvn.download.url";
static final String URL_MVN_REPOSITORY = "mvn.repository.url";
static final String DIR_LOCALREPOSITORY = "dir.local.repository";
static final String TMP_POM = "pom.template";
static final String JAR_DEPENDENCIES = "jar.dependencies";
static final String TMP_DEPENDENCY = "dependency.template";
static final String KEY_GROUPID = "groupId";
static final String KEY_ARTIFACTID = "artifactId";
static final String KEY_VERSION = "version";
/** version numbers from '-0.0' to '-999.999.999.999.999.Description' */
static final String REGEX_VERSION = "-\\d{1,3}([.]\\d{1,3}){1,3}[.]\\d{0,3}[.-]?[a-zA-Z]*";
static final String PRE_PACKAGE = "PACKAGE.";
static final String PACKAGE_EXCEPTION = "package.exception.regex";
static final String ONLY_LOAD_DEFINED = "only.load.defined.packages";
private static final String KEY_FINDJAR_INDEX = "findjar.index";
private static final String KEY_FINDJAR_CLASS = "findjar.class";
public JarResolver() {
this(null);
}
/**
* constructor
*/
public JarResolver(String baseDir) {
props = new Properties();
try {
props.load(ENV.getResource("jarresolver.properties"));
String updateUrl = props.getProperty(URL_UPDATE_PROPERTIES);
setBaseDir(baseDir);
if (updateUrl != null) {
try {
LOG.info("updating jarresolver.properties through " + updateUrl);
download(updateUrl, true, true);
} catch (Exception ex) {
//no problem - perhaps no network connection
LOG.warn("couldn't update jarresolver.properties from " + updateUrl);
}
}
} catch (IOException e) {
ManagedException.forward(e);
}
}
private void setBaseDir(String baseDir) {
this.basedir = baseDir != null ? baseDir : props.getProperty(DIR_LOCALREPOSITORY, ENV.getConfigPath());
LOG.info("basedir is: " + this.basedir);
}
/**
* installs given package. see {@link #start(String...)}
*
* @param packages
* @return see {@link #start(String...)}
*/
public static String install(String... packages) {
return new JarResolver().start(packages);
}
/**
* does the whole work
*
* @param deps dependency or package names
* @return information string about resolved dependencies. on error, string starts with FAILED
*/
public String start(String... deps) {
if (deps != null && deps.length > 0) {
prepareDependencies(deps);
}
loadMvn();
createMvnScript();
int result = loadDependencies();
return (result > 0 ? "FAILED: " : "") + props.getProperty(JAR_DEPENDENCIES);
}
/**
* loadDependencies
*
* @return mvn process exit value (0: Ok, >0 otherwise)
*/
private int loadDependencies() {
try {
System.setProperty("M2_HOME", new File(mvnRoot).getCanonicalPath());
} catch (IOException e) {
ManagedException.forward(e);
}
LOG.info("setting M2_HOME=" + System.getProperty("M2_HOME"));
String mvnRootRel = FileUtil.replaceToSystemSeparator(FileUtil.getRelativePath(mvnRoot, basedir));
File baseDirectory = new File(FileUtil.replaceToSystemSeparator(basedir));
Process process = null;
if (AppLoader.isWindows()) {
String script = "\\bin\\mvn.cmd";
new File(mvnRoot + script).setExecutable(true);
process = SystemUtil.execute(baseDirectory, "cmd", "/C", mvnRootRel + script, "install");
} else {
String script = "/bin/mvn";
new File(mvnRoot + script).setExecutable(true);
process = SystemUtil.execute(baseDirectory, mvnRootRel + script, "install");
}
if (process.exitValue() != 0) {
LOG.error("Process returned with: " + process.exitValue());
}
return process.exitValue();
}
private void createMvnScript() {
LOG.info("creating mavens pom.xml");
InputStream stream = ENV.getResource(TMP_POM);
String pom = String.valueOf(FileUtil.getFileData(stream, "UTF-8"));
Properties p = new Properties();
p.putAll(props);
p.put("dependencies", createDependencyInformation());
pom = StringUtil.insertProperties(pom, p);
FileUtil.writeBytes(pom.getBytes(), basedir + "pom.xml", false);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private String createDependencyInformation() {
String[] deps = getDependencies();
InputStream stream = ENV.getResource(TMP_DEPENDENCY);
String dependency = String.valueOf(FileUtil.getFileData(stream, "UTF-8"));
StringBuilder buf = new StringBuilder(deps.length * (dependency.length() + 20));
Map p;
String groupId, artifactId, version;
for (int i = 0; i < deps.length; i++) {
LOG.info("creating dependency '" + deps[i] + "'");
version = StringUtil.extract(deps[i], REGEX_VERSION);
//version should not be an empty string
if (Util.isEmpty(version)) {
version = null;
}
if (deps[i].contains("/")) {
groupId = StringUtil.substring(deps[i], null, "/");
artifactId = StringUtil.substring(deps[i], "/", version);
} else {
artifactId = StringUtil.substring(deps[i], null, version);
groupId = findPackage(deps[i], true);
//if no definition was found, try it with the artifactID itself
if (groupId == null) {
//if artifactId is a class-name itself, try to extract the third part of the package (org.company.product....)
if (BeanClass.isPublicClassName(artifactId)) {
if (Boolean.valueOf(props.getProperty("use.findjar.on.unknown", "true"))) {
String jarName = findJarOnline(artifactId);
if (jarName != null) {
artifactId = StringUtil.extract(jarName, "\\w+");
if (!Util.isEmpty(artifactId)) {
groupId = artifactId;
}
}
}
if (groupId == null) {//try it yourself
String cls = artifactId;
artifactId = StringUtil.substring(StringUtil.substring(cls, ".", null), ".", ".");
groupId = StringUtil.substring(cls, null, "." + artifactId);
}
} else {
groupId = artifactId;
}
}
}
version = !Util.isEmpty(version) ? version.substring(1) : "RELEASE";
p = MapUtil.asMap(KEY_GROUPID, groupId, KEY_ARTIFACTID, artifactId, KEY_VERSION, version);
buf.append(StringUtil.insertProperties(dependency, p));
}
return buf.toString();
}
private String[] getDependencies() {
String depStr = (String) props.get(JAR_DEPENDENCIES);
if (Util.isEmpty(depStr)) {
throw new IllegalArgumentException("no dependencies defined --> nothing to do!");
}
return depStr.split(",\\s*");
}
private void loadMvn() {
File mvnFile = download((String) props.get(URL_MVN_DOWNLOAD), false, false);
mvnRoot = mvnFile.getParent();
String extractedName = mvnRoot + "/" + StringUtil.substring(mvnFile.getName(), null, "-bin.zip");
if (!new File(extractedName + "/bin").exists()) {
FileUtil.extract(mvnFile.getPath(), mvnRoot + "/", ".*");
}
mvnRoot = extractedName;
}
/**
* downloads the given strUrl if a network connection is available
*
* @param strUrl network url to load
* @param flat if true, the file of that url will be put directly to the environment directory. otherwise the full
* path will be stored to the environment.
* @param overwrite if true, existing files will be overwritten
* @return downloaded local file
*/
protected File download(String strUrl, boolean flat, boolean overwrite) {
setBaseDir(this.basedir);
return NetUtil.download(strUrl, basedir, flat, overwrite);
}
/**
* combines dependencies from property file with start arguments and stores it back to the property
* {@link #JAR_DEPENDENCIES}.
*
* removes all packages matching the package-exception expression (package that should not be loaded through maven).
*
* @param deps start arguments
*/
private void prepareDependencies(String... deps) {
String jars = props.getProperty(JAR_DEPENDENCIES);
Boolean onlyLoadDefined = Boolean.valueOf(props.getProperty(ONLY_LOAD_DEFINED));
StringBuilder buf = new StringBuilder(jars != null ? jars : "");
String pck;
boolean addIt;
for (int i = 0; i < deps.length; i++) {
//if the parameter is a known package name, fill all dependent jar-files
pck = findPackage(deps[i], false);
if (pck != null) {
//if groupId only found through another artifact-definition, add this dep-name
deps[i] = pck.endsWith("/") ? pck + deps[i] : pck;
addIt = true;
} else {
addIt = !onlyLoadDefined;
}
if (addIt && !deps[i].matches(PACKAGE_EXCEPTION)) {
buf.append("," + deps[i]);
}
}
if (buf.length() == 0) {
throw new IllegalArgumentException("no dependencies defined --> nothing to do!");
}
jars = buf.toString();
if (jars.startsWith(",")) {
jars = jars.substring(1);
}
props.setProperty(JAR_DEPENDENCIES, jars);
}
/**
* convenience for extern calls, creating a new {@link JarResolver} instance and returning result of {@link #findPackage(String)}
* @param part
* @return
*/
public static String getPackage(String part) {
return new JarResolver().findPackage(part);
}
/**
* searches for the given expression part inside all package names and their values. if an entry looks like
* 'PACKAGE.org.pyhton=jython' and you search for 'python' or 'jython' you will get 'org.pyhton'
*
* @param part package or value part.
* @return package (group-id) name
*/
public String findPackage(String part) {
Set keys = props.stringPropertyNames();
//first, search in the keys
for (String k : keys) {
if (k.startsWith(PRE_PACKAGE) && k.contains(part))
return StringUtil.substring(k, PRE_PACKAGE, null);
}
//second: search in the values
Collection