package com.atlassian.labs.speakeasy.commonjs; import com.atlassian.labs.speakeasy.commonjs.descriptor.CommonJsModulesDescriptor; import com.atlassian.labs.speakeasy.commonjs.util.IterableTreeMap; import com.atlassian.plugin.Plugin; import com.atlassian.plugin.util.PluginUtils; import com.google.common.base.Predicate; import com.google.common.collect.MapMaker; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import org.apache.commons.io.IOUtils; import org.osgi.framework.Bundle; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.*; import static com.atlassian.labs.speakeasy.commonjs.util.ModuleUtil.determineLastModified; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Sets.newHashSet; import static java.util.Collections.unmodifiableSet; /** * */ public class CommonJsModules { @XmlElement private final Map<String, Module> modules = new MapMaker().makeMap(); @XmlElement private final Set<String> externalModuleDependencies; @XmlElement private final Set<String> publicModuleIds = Sets.newHashSet(); private final Plugin plugin; private final Bundle pluginBundle; private final String location; private final Set<String> resources = Sets.newHashSet(); @XmlAttribute private final String moduleKey; @XmlAttribute private final String description; @XmlAttribute private final String pluginKey; @XmlAttribute private final String pluginName; public CommonJsModules(CommonJsModulesDescriptor descriptor, Bundle pluginBundle, String location) { this.pluginBundle = pluginBundle; this.location = location.endsWith("/") ? location : location + "/"; this.plugin = descriptor.getPlugin(); this.pluginKey = plugin.getKey(); this.pluginName = plugin.getName(); this.moduleKey = descriptor.getKey(); this.description = descriptor.getDescription() != null ? descriptor.getDescription() : ""; this.externalModuleDependencies = unmodifiableSet(scan()); this.publicModuleIds.addAll(descriptor.getExplicitPublicModules()); } public Module getModule(String id) { return modules.get(id); } public Set<String> getModuleIds() { return new HashSet<String>(modules.keySet()); } public Set<String> getPublicModuleIds() { return publicModuleIds; } public Set<String> getResources() { return resources; } public String getModuleContents(String moduleName) { Module scannedModule = modules.get(moduleName); if (scannedModule == null) { throw new IllegalArgumentException("Module not found: " + moduleName); } URL url = null; if (!Boolean.getBoolean(PluginUtils.ATLASSIAN_DEV_MODE)) { url = plugin.getResource(getMinifiedModulePath(moduleName)); } if (url == null) { url = plugin.getResource(scannedModule.getPath()); } long lastModified = determineLastModified(url); if (scannedModule.getLastModified() > 0 && scannedModule.getLastModified() < lastModified) { modules.put(moduleName, scanModule(moduleName, url)); } return readModule(url); } private String readModule(URL url) { InputStream in = null; try { in = url.openStream(); return IOUtils.toString(in); } catch (IOException e) { throw new RuntimeException("Unable to read module: " + url, e); } finally { IOUtils.closeQuietly(in); } } private String getMinifiedModulePath(String moduleName) { StringBuilder sb = new StringBuilder(); sb.append(location); if (!location.endsWith("/")) { sb.append("/"); } sb.append(moduleName); sb.append("-min"); sb.append(".js"); return sb.toString(); } private Set<String> scan() { Set<String> allDeps = new HashSet<String>(); for (String fullPath : findModulePaths(pluginBundle)) { String modulePath = fullPath.substring(location.length()); String moduleName = modulePath.substring(0, modulePath.lastIndexOf(".")); URL moduleUrl = pluginBundle.getEntry(fullPath); Module module = scanModule(moduleName, moduleUrl); allDeps.addAll(module.getDependencies()); modules.put(moduleName, module); if (module.getJsDoc().getAttribute("public") != null) { publicModuleIds.add(moduleName); } } allDeps.removeAll(modules.keySet()); return allDeps; } private Module scanModule(String moduleName, URL moduleUrl) { return new Module(moduleName, moduleUrl.getPath(), determineLastModified(moduleUrl), readModule(moduleUrl)); } private Iterable<String> findModulePaths(Bundle bundle) { Set<String> modulePaths = newHashSet(); for (String path : com.atlassian.labs.speakeasy.util.BundleUtil.scanForPaths(bundle, location, new Predicate<String>() { public boolean apply(String path) { return !path.contains("-min."); } })) { if (path.endsWith(".js") || path.endsWith(".mu") || path.endsWith(".host")) { modulePaths.add(location + path); } else { resources.add(path); } } return modulePaths; } public String getModulePath(String id) { return modules.get(id).getPath(); } public Iterable<Module> getIterableModules() { return new IterableTreeMap<String,Module>(modules); } public Iterable<Module> getIterablePublicModules() { return new IterableTreeMap<String,Module>(Maps.filterValues(modules, new Predicate<Module>() { public boolean apply(Module input) { return input.getJsDoc().getAttribute("public") != null || publicModuleIds.contains(input.getId()); } })); } public Set<String> getExternalModuleDependencies() { return externalModuleDependencies; } public String getPluginKey() { return pluginKey; } }