package com.atlassian.labs.speakeasy.commonjs.descriptor;
import com.atlassian.labs.speakeasy.commonjs.CommonJsModules;
import com.atlassian.labs.speakeasy.commonjs.util.JsDoc;
import com.atlassian.labs.speakeasy.util.DefaultPluginModuleTracker;
import com.atlassian.labs.speakeasy.util.PluginModuleTracker;
import com.atlassian.plugin.ModuleDescriptor;
import com.atlassian.plugin.PluginAccessor;
import com.atlassian.plugin.event.PluginEventManager;
import com.google.common.collect.Sets;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceRegistration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import static com.google.common.collect.Sets.newHashSet;
import static org.apache.commons.collections.SetUtils.unmodifiableSet;
import static org.dom4j.DocumentHelper.createElement;
/**
*
*/
class GeneratedDescriptorsManager
{
private final CommonJsModulesDescriptor descriptor;
private final Bundle pluginBundle;
private final CommonJsModules modules;
private final DefaultPluginModuleTracker<CommonJsModules,CommonJsModulesDescriptor> modulesTracker;
private final Set<String> unresolvedExternalDependencies;
private final Set<String> resolvedExternalModules;
private Set<ServiceRegistration> registrations;
private final Logger log = LoggerFactory.getLogger(GeneratedDescriptorsManager.class);
GeneratedDescriptorsManager(Bundle pluginBundle, CommonJsModules modules, PluginAccessor pluginAccessor,
PluginEventManager pluginEventManager, CommonJsModulesDescriptor descriptor)
{
this.pluginBundle = pluginBundle;
this.modules = modules;
this.descriptor = descriptor;
this.unresolvedExternalDependencies = new HashSet<String>(modules.getExternalModuleDependencies());
this.resolvedExternalModules = new CopyOnWriteArraySet<String>();
modulesTracker = new DefaultPluginModuleTracker<CommonJsModules, CommonJsModulesDescriptor>(pluginAccessor, pluginEventManager,
CommonJsModulesDescriptor.class, new PluginModuleTracker.Customizer<CommonJsModules, CommonJsModulesDescriptor>()
{
public CommonJsModulesDescriptor adding(CommonJsModulesDescriptor descriptor)
{
if (descriptor.getModule() != null)
{
maybeRegisterDescriptors(descriptor);
return descriptor;
}
return null;
}
public void removed(CommonJsModulesDescriptor descriptor)
{
maybeUnregisterDescriptors(descriptor);
}
});
}
public Set<String> getUnresolvedExternalDependencies()
{
return unmodifiableSet(unresolvedExternalDependencies);
}
private synchronized void maybeRegisterDescriptors(CommonJsModulesDescriptor descriptor)
{
if (registrations == null)
{
if (unresolvedExternalDependencies.removeAll(descriptor.getModule().getPublicModuleIds()))
{
resolvedExternalModules.add(descriptor.getModulesWebResourceCompleteKey());
}
if (unresolvedExternalDependencies.isEmpty())
{
Set<ServiceRegistration> regs = new HashSet<ServiceRegistration>();
regs.add(registerBatchedModulesDescriptor());
regs.addAll(registerEachModuleDescriptor());
registrations = regs;
}
else
{
log.debug("Not exposing '{}' as there are unresolved module dependencies: {}", GeneratedDescriptorsManager.this.descriptor.getCompleteKey(), unresolvedExternalDependencies);
}
}
}
private synchronized void maybeUnregisterDescriptors(CommonJsModulesDescriptor descriptor)
{
unresolvedExternalDependencies.addAll(Sets.intersection(descriptor.getModule().getPublicModuleIds(), resolvedExternalModules));
if (!unresolvedExternalDependencies.isEmpty() && registrations != null)
{
// todo: try to resolve the dependency from the remaining descriptors instead of assuming it is gone
unregisterServiceRegistrations();
registrations = null;
}
}
public synchronized void close()
{
modulesTracker.close();
if (registrations != null)
{
unregisterServiceRegistrations();
}
registrations = null;
}
private void unregisterServiceRegistrations()
{
for (ServiceRegistration reg : registrations)
{
reg.unregister();
}
}
private Set<ServiceRegistration> registerEachModuleDescriptor()
{
Set<ServiceRegistration> registrations = new HashSet<ServiceRegistration>();
for (String id : modules.getModuleIds())
{
ModuleDescriptor webResourceModuleDescriptor = descriptor.createIndividualModuleDescriptor();
Element root = createElement("web-resource");
Element dep = root.addElement("dependency");
dep.setText(descriptor.getCompleteKey() + "-modules");
JsDoc jsDoc = modules.getModule(id).getJsDoc();
addAnnotatedContext(root, jsDoc);
Element jsTransform = getJsTransformation(root);
Element trans = jsTransform.addElement("transformer");
trans.addAttribute("key", "commonjs-module-entry");
trans.addAttribute("moduleId", id);
trans.addAttribute("fullModuleKey", descriptor.getCompleteKey());
Element res = root.addElement("resource");
res.addAttribute("type", "download");
res.addAttribute("name", id + ".js");
res.addAttribute("location", modules.getModulePath(id));
webResourceModuleDescriptor.init(descriptor.getPlugin(), createDescriptorElement(id, root));
ServiceRegistration reg =
pluginBundle.getBundleContext().registerService(ModuleDescriptor.class.getName(), webResourceModuleDescriptor, null);
registrations.add(reg);
}
return registrations;
}
private void addAnnotatedContext(Element root, JsDoc jsDoc)
{
String jsDocContext = jsDoc.getAttribute("context");
if (jsDocContext != null)
{
for (String ctxRaw : jsDocContext.split(","))
{
Element ctxElement = root.addElement("context");
ctxElement.setText(ctxRaw.trim());
}
}
}
private void addAnnotatedDependencies(Element root, String pluginKey, JsDoc jsDoc)
{
String jsDocDependencies = jsDoc.getAttribute("dependency");
if (jsDocDependencies != null)
{
for (String depRaw : jsDocDependencies.split(","))
{
Element depElement = root.addElement("dependency");
String dep = depRaw.trim();
if (!dep.contains(":"))
{
depElement.setText(pluginKey + ":" + dep);
}
else
{
depElement.setText(dep);
}
}
}
}
private Element getJsTransformation(Element root)
{
for (Element trans : new ArrayList<Element>(root.elements("transformation")))
{
if ("js".equals(trans.attributeValue("extension")))
{
return trans;
}
}
Element trans = root.addElement("transformation");
trans.addAttribute("extension", "js");
return trans;
}
private Element createDescriptorElement(String id, Element root)
{
root.addAttribute("key", id.replace('/', '_'));
if (log.isDebugEnabled())
{
StringWriter out = new StringWriter();
OutputFormat format = OutputFormat.createPrettyPrint();
try
{
new XMLWriter( out, format ).write(root);
}
catch (IOException e)
{
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
log.debug("Generated descriptor:\n" + out.toString());
}
return root;
}
private ServiceRegistration registerBatchedModulesDescriptor()
{
ModuleDescriptor webResourceModuleDescriptor = descriptor.createIndividualModuleDescriptor();
Element root = createElement("web-resource");
for (Element child : new HashSet<Element>(descriptor.getOriginalElement().elements()))
{
root.add(child.createCopy());
}
Element depElement = root.addElement("dependency");
depElement.setText("com.atlassian.labs.speakeasy-plugin:yabble");
Element jsTransform = getJsTransformation(root);
Element trans = jsTransform.addElement("transformer");
trans.addAttribute("key", "commonjs-module");
trans.addAttribute("fullModuleKey", descriptor.getCompleteKey());
for (String id : modules.getModuleIds())
{
Element res = root.addElement("resource");
res.addAttribute("type", "download");
res.addAttribute("name", id + ".js");
res.addAttribute("location", modules.getModulePath(id));
JsDoc jsDoc = modules.getModule(id).getJsDoc();
addAnnotatedDependencies(root, descriptor.getPluginKey(), jsDoc);
}
for (String dep : resolvedExternalModules)
{
Element extDep = root.addElement("dependency");
extDep.setText(dep);
}
for (String resourcePath : modules.getResources())
{
Element res = root.addElement("resource");
res.addAttribute("type", "download");
res.addAttribute("name", resourcePath);
res.addAttribute("location", descriptor.getLocation() + "/" + resourcePath);
}
Element muTrans = root.addElement("transformation");
muTrans.addAttribute("extension", "mu");
trans = muTrans.addElement("transformer");
trans.addAttribute("key", "template");
String generatedModuleKey = descriptor.getKey() + "-modules";
Element cssTrans = root.addElement("transformation");
cssTrans.addAttribute("extension", "css");
trans = cssTrans.addElement("transformer");
trans.addAttribute("key", "cssVariables");
trans.addAttribute("fullModuleKey", descriptor.getPluginKey() + ":" + generatedModuleKey);
webResourceModuleDescriptor.init(descriptor.getPlugin(),
createDescriptorElement(generatedModuleKey, root));
return pluginBundle.getBundleContext().registerService(ModuleDescriptor.class.getName(), webResourceModuleDescriptor, null);
}
}