* Injects javascript libraries needed to satisfy feature dependencies.
*/
protected void injectFeatureLibraries(Gadget gadget, Node headTag) throws GadgetException {
// TODO: If there isn't any js in the document, we can skip this. Unfortunately, that means
// both script tags (easy to detect) and event handlers (much more complex).
GadgetContext context = gadget.getContext();
// Set of extern libraries requested by the container
Set<String> externForcedLibs = defaultExternLibs;
// gather the libraries we'll need to generate the extern libs
String externParam = context.getParameter("libs");
if (StringUtils.isNotBlank(externParam)) {
externForcedLibs = Sets.newTreeSet(Arrays.asList(StringUtils.split(externParam, ':')));
}
if (!externForcedLibs.isEmpty()) {
String jsUrl = urlGenerator.getBundledJsUrl(externForcedLibs, context);
Element libsTag = headTag.getOwnerDocument().createElement("script");
libsTag.setAttribute("src", jsUrl);
headTag.appendChild(libsTag);
}
List<String> unsupported = Lists.newLinkedList();
List<FeatureResource> externForcedResources =
featureRegistry.getFeatureResources(context, externForcedLibs, unsupported);
if (!unsupported.isEmpty()) {
LOG.info("Unknown feature(s) in extern &libs=: " + unsupported.toString());
unsupported.clear();
}
// Get all resources requested by the gadget's requires/optional features.
Map<String, Feature> featureMap = gadget.getSpec().getModulePrefs().getFeatures();
List<String> gadgetFeatureKeys = Lists.newArrayList(gadget.getDirectFeatureDeps());
List<FeatureResource> gadgetResources =
featureRegistry.getFeatureResources(context, gadgetFeatureKeys, unsupported);
if (!unsupported.isEmpty()) {
List<String> requiredUnsupported = Lists.newLinkedList();
for (String notThere : unsupported) {
if (!featureMap.containsKey(notThere) || featureMap.get(notThere).getRequired()) {
// if !containsKey, the lib was forced with Gadget.addFeature(...) so implicitly req'd.
requiredUnsupported.add(notThere);
}
}
if (!requiredUnsupported.isEmpty()) {
throw new UnsupportedFeatureException(requiredUnsupported.toString());
}
}
// Inline or externalize the gadgetFeatureKeys
List<FeatureResource> inlineResources = Lists.newArrayList();
List<String> allRequested = Lists.newArrayList(gadgetFeatureKeys);
if (externalizeFeatures) {
Set<String> externGadgetLibs = Sets.newTreeSet(featureRegistry.getFeatures(gadgetFeatureKeys));
externGadgetLibs.removeAll(externForcedLibs);
if (!externGadgetLibs.isEmpty()) {
String jsUrl = urlGenerator.getBundledJsUrl(externGadgetLibs, context);
Element libsTag = headTag.getOwnerDocument().createElement("script");
libsTag.setAttribute("src", jsUrl);
headTag.appendChild(libsTag);
}
} else {
inlineResources.addAll(gadgetResources);
}
// Calculate inlineResources as all resources that are needed by the gadget to
// render, minus all those included through externResources.
// TODO: profile and if needed, optimize this a bit.
if (!externForcedLibs.isEmpty()) {
allRequested.addAll(externForcedLibs);
inlineResources.removeAll(externForcedResources);
}
// Precalculate the maximum length in order to avoid excessive garbage generation.
int size = 0;
for (FeatureResource resource : inlineResources) {
if (!resource.isExternal()) {
if (context.getDebug()) {
size += resource.getDebugContent().length();
} else {
size += resource.getContent().length();
}
}
}
String libraryConfig =
getLibraryConfig(gadget, featureRegistry.getFeatures(allRequested));
// Size has a small fudge factor added to it for delimiters and such.
StringBuilder inlineJs = new StringBuilder(size + libraryConfig.length() + INLINE_JS_BUFFER);
// Inline any libs that weren't extern. The ugly context switch between inline and external
// Js is needed to allow both inline and external scripts declared in feature.xml.
for (FeatureResource resource : inlineResources) {
String theContent = context.getDebug() ? resource.getDebugContent() : resource.getContent();
if (resource.isExternal()) {
if (inlineJs.length() > 0) {
Element inlineTag = headTag.getOwnerDocument().createElement("script");
headTag.appendChild(inlineTag);
inlineTag.appendChild(headTag.getOwnerDocument().createTextNode(inlineJs.toString()));