package org.smartly.packages;
import org.smartly.IConstants;
import org.smartly.Smartly;
import org.smartly.commons.io.repository.FileRepository;
import org.smartly.commons.io.repository.Resource;
import org.smartly.commons.io.repository.deploy.FileDeployer;
import org.smartly.commons.util.*;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class SmartlyPackageLoader {
private final String _root;
private final Map<String, AbstractPackage> _packages;
private List<AbstractPackage> _sortList;
private boolean _runned;
private AbstractPackage _modalPackage;
public SmartlyPackageLoader() throws IOException {
_root = PathUtils.join(Smartly.getHome(), IConstants.PATH_PACKAGES);
_packages = Collections.synchronizedMap(new HashMap<String, AbstractPackage>());
_runned = false;
_modalPackage = null;
// ensure dir exists
FileUtils.mkdirs(_root);
}
public Set<String> getPackageNames() {
return _packages.keySet();
}
public void register(final AbstractPackage instance) {
this.register(instance, null);
}
/**
* Programmatically registration of a package.
* Usually packages are loaded from package folder, but when launched from IDE you may
* find useful manual registration.
*
* @param instance Package Instance
*/
public void register(final AbstractPackage instance,
final Boolean modal) {
if (null != instance) {
final String key = instance.getId();
synchronized (_packages) {
if (!_packages.containsKey(key)) {
if (isModal(instance, modal)) {
if (null != _modalPackage) {
final String msg = FormatUtils.format("Modal Package already registered. Only one modal " +
"package is allowed. '{0}->{1}' will not be registered.",
key, instance.getClass().getCanonicalName());
this.warning(msg);
return;
}
_modalPackage = instance;
}
_packages.put(key, instance);
// ensure directory exists
this.ensureExists(instance);
this.info(FormatUtils.format("REGISTERED MODULE: {0}", key));
} else {
final AbstractPackage existing = _packages.get(key);
if (!existing.getClass().getCanonicalName().equalsIgnoreCase(instance.getClass().getCanonicalName())) {
final String msg = FormatUtils.format("TWO PACKAGES WITH SAME ID: " +
"Package '{0}' already exists and is of type '{1}'. Package ID must be unique. " +
"You are trying to register another package with same ID but of type '{2}'.",
key, existing.getClass().getCanonicalName(), instance.getClass().getCanonicalName());
this.warning(msg);
}
}
}
}
}
public void load() throws Exception {
if (_runned) {
return;
}
_runned = true;
//-- load from repository--//
this.loadPackages();
//-- run all packages in order --//
_sortList = this.sort();
this.load(_sortList);
}
public void ready() {
if (!CollectionUtils.isEmpty(_sortList)) {
for (final AbstractPackage item : _sortList) {
try {
//-- Modal package is last --//
if (!item.equals(_modalPackage)) {
item.ready();
}
} catch (Throwable t) {
this.severe(FormatUtils.format("ERROR CALLING METHOD 'ready()' FROM PACKAGE '{0}': {1}",
item.getId(), ExceptionUtils.getRealMessage(t)));
}
}
if (null != _modalPackage) {
this.info(FormatUtils.format(
"Smartly started [{0}]::{1} as MODAL.",
_modalPackage.getId(), _modalPackage.getClass().getName()));
_modalPackage.ready();
}
}
}
public void unload() {
if (!CollectionUtils.isEmpty(_sortList)) {
for (final AbstractPackage item : _sortList) {
try {
//-- Modal package is last --//
if (!item.equals(_modalPackage)) {
item.unload();
}
} catch (Throwable t) {
this.severe(FormatUtils.format("ERROR CALLING METHOD 'unload()' FROM PACKAGE '{0}': {1}",
item.getId(), ExceptionUtils.getRealMessage(t)));
}
}
if (null != _modalPackage) {
this.info(FormatUtils.format(
"Smartly stopped [{0}]::{1} as MODAL.",
_modalPackage.getId(), _modalPackage.getClass().getName()));
_modalPackage.unload();
}
}
}
// ------------------------------------------------------------------------
// p r i v a t e
// ------------------------------------------------------------------------
private void info(final String message) {
Smartly.getLogger().info(this, message);
}
private void warning(final String message) {
Smartly.getLogger().warning(this, message);
}
private void severe(final String message) {
Smartly.getLogger().severe(this, message);
}
private void severe(final String message, final Throwable error) {
Smartly.getLogger().severe(this, message, error);
}
private void loadPackages() throws IOException {
final FileRepository repository = new FileRepository(_root);
final Resource[] resources = repository.getResources(true);
for (final Resource resource : resources) {
if (null != resource) {
this.loadPackage(resource);
}
}
}
private void loadPackage(final Resource resource) {
try {
final String content = resource.getContent(Smartly.getCharset());
final JsonWrapper json = new JsonWrapper(content);
final String name = json.optString("name");
final String main = json.optString("main");
if (!StringUtils.hasText(main)) {
final String msg = FormatUtils.format(
"Unable to load Package '{0}': Missing 'main' attribute.",
name);
this.severe(msg);
} else {
this.registerPackageLauncher(name, main);
}
} catch (Throwable t) {
this.severe(FormatUtils.format("Unmanaged Exception loading Packages: '{0}'", t), t);
}
}
private void registerPackageLauncher(final String name, final String className) {
try {
final Class clazz = ClassLoaderUtils.forName(className);
if (null == clazz) {
throw new Exception("Class not found in current loader.");
}
final AbstractPackage launcher = (AbstractPackage) clazz.newInstance();
this.register(launcher);
} catch (Throwable t) {
this.severe(FormatUtils.format("Exception loading Package '{0}' from class '{1}': '{2}'",
name, className, t),
t);
}
}
private List<AbstractPackage> sort() {
final Collection<AbstractPackage> packages = _packages.values();
List<AbstractPackage> list = new ArrayList<AbstractPackage>(packages);
Collections.sort(list);
return list;
}
private void load(final List<AbstractPackage> list) {
for (final AbstractPackage item : list) {
try {
item.load();
this.info(FormatUtils.format("STARTED MODULE: {0}", item.getId()));
// flush deployers
FileDeployer.deployAll();
} catch (Throwable t) {
final String msg = FormatUtils.format("Error running Package '{0}': '{1}'", item.getId(), t);
this.severe(msg, t);
}
}
}
private void ensureExists(final AbstractPackage pkg) {
try {
final String packagePath = PathUtils.join(_root, pkg.getId());
FileUtils.mkdirs(packagePath);
final File packageJson = new File(PathUtils.join(packagePath, "package.json"));
if (!packageJson.exists()) {
// read default
this.copyDefault(packageJson, pkg);
}
} catch (Throwable t) {
this.severe(null, t);
}
}
private void copyDefault(final File packageJson, final AbstractPackage pkg) throws IOException {
final ClassLoader cl = Thread.currentThread().getContextClassLoader(); //this.getClass().getClassLoader();
final String path = PathUtils.getPackagePath(this.getClass());
final String filePath = PathUtils.join(path, packageJson.getName());
final InputStream is = cl.getResourceAsStream(filePath);
if (null != is) {
final byte[] content = ByteUtils.getBytes(is);
//format content with class data
final Map<String, String> data = new HashMap<String, String>();
data.put("name", pkg.getId());
data.put("main", pkg.getClass().getCanonicalName());
data.put("version", pkg.getVersion());
data.put("description", pkg.getDescription());
data.put("maintainer_mail", pkg.getMaintainerMail());
data.put("maintainer_url", pkg.getMaintainerUrl());
data.put("maintainer_name", pkg.getMaintainerName());
final String json = FormatUtils.formatTemplate(new String(content), "<", ">", data);
FileUtils.copy(json.getBytes(), packageJson);
} else {
this.warning(FormatUtils.format("RESOURCE '{0}' not found. " +
"Ensure you included it in your package distribution " +
"(i.e. check IDE settings for Compiler Options.)", filePath));
}
}
private static boolean isModal(final AbstractPackage instance, final Boolean modal) {
if (null == modal) {
return instance instanceof ISmartlyModalPackage;
} else {
return modal;
}
}
}