Groovy script gets cached after being executed by Sling - groovy

I was looking at the Sling documentation and noticed that Groovy scripts can be used to render a component, similar to the way JSP or HTL can be used.
Now, I'm not very fond of the idea and I think that the place of Groovy code should be in OSGi bundles but I decided to try and see what I can do with these scripts.
I created a simple component with a dialog that has a single property, text. Let's call this component simpleGroovy. Here's the rough structure (dialog details omitted for brevity).
/apps/example/core/components/simpleGroovy
|
|- cq:editConfig
|- dialog
|- simpleGroovy.groovy
The contents of simpleGroovy.groovy are something like this:
println "Hello Groovy! Rendering the contents of ${resource.path} since 2017"
As expected, the script executed just fine, GStrings and all.
However, when I change the script after making the first request, I keep getting the same output. If I rename the script and use a selector, I get an output matching the current state of the script. Until the first request when this also gets cached.
I'm curious as to the cause of this. I suppose the Groovy script gets compiled to a Java class at some point and that the class gets cached somewhere.
I'm on AEM 6.2 so I checked the contents /crx-quickstart/launchpad/felix/bundle305/data/classes (bundle 305 is the id of the org.apache.sling.commons.fsclassloader in my environment).
I can see the compiled classes for JSP and HTL scripts from my example app but there seems to be nothing related to my Groovy script in any of those folders.
I also looked up the ID of my Groovy Runtime (groovy-all) bundle but there's no data folder in there. However, restarting the Groovy Runtime bundle allows me to see the changes I made to my script.
Is there an easier way I could cause the Groovy script to be recompiled? What exactly gets cached and where does it sit?

sling uses Groovy provided GroovyScriptEngine, which stores compiled scripts in memory, see scriptCache.
I dont think there is anything available ootb to clear scriptCache. you will have to write your own groovy scriptengine and probably classloader. (not 100% sure about this though)

Related

Simple tag helper "suddenly" no longer renders properly (only in production, only for one page)

As the title states, I'm having a finicky issue with .NET Core 2.2. I'm using tag helpers all over, but on the specific page that I'm having trouble with, it's actually the most simple use-case of all:
<a class="logout" asp-page="/Admin/Logout">Logout</a>
Some relevant notes:
As of yesterday, it worked in all environments without any issues.
This morning I made some changes seemingly unrelated to this page, and published again.
In the published version (on Azure), the tag helpers for this page only don't render, but instead appear in the source code as literals. (e.g. <a asp-page="..."></a>)
Still works without issue locally.
Here is the directory structure. The page in question is /Admin/Index.cshtml:
And my _ViewImports.cshtml (which again, I haven't changed in months):
#using redacted
#namespace redacted.Pages
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
Explicitly adding the taghelper to the .chshtml file has solved this for me in the past. Unfortunately I am not aware of why this issue occurs randomly. In my specific use case,we were using custom and third party tag helpers which we suspect was causing the issues.
Try adding this line to the /Admin/Index.cshtml file:
#addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

Using chrome.runtime.sendMessage in an imported javascript file

I'm currently using jQuery's $.getScript within my content script to import more Javascript files into my content script. This works very well for me to get all my Javascript files imported, but I am running into an issue where I can't use chrome.runtime.sendMessage inside the imported javascript files to communicate with my background scripts, presumably because the function isn't recognized within a script that's been processed by $.getScript (please do correct me if I'm wrong).
In content.js (injected directly via the manifest file), I have the following code:
$.getScript(chrome.extension.getURL('js/angular-1.2.26-min.js'), function(data) {
$.getScript(chrome.extension.getURL('app/app.js'), function(data) {
$.getScript(chrome.extension.getURL('app/overview/overview-controller.js'), function(data) {
$.getScript(chrome.extension.getURL('js/angular-bootstrap.js'), function(data) {
})
})
})
})
And inside app/app.js (or any of the injected files), I try putting a sendMessage call anywhere, but nothing gets sent. (I log the onMessage event listener in the background)
chrome.runtime.sendMessage({msg: 'test'}, function(response) { alert('done') })
Note: I have also tried importing the Javascript files by sending a message to the background script to use chrome.tabs.executeScrip instead, but I need to be able to inject the javascript files only at a specific time and in a specific frame, so that doesn't help. I've also tried using the 3rd party executeScriptInFrame library but that doesn't seem to be working either. I run into "Blocked script execution in '{{URL}}' because the document's frame is sandboxed and the 'allow-scripts' permission is not set"
My questions:
Is there an effective solution to using chrome.runtime.sendMessage inside a script that's been injected using $.getScript?
Is there a way to use executeScript inside a content script?
Is there an effective way to inject content scripts into a particular frame? Again, from above -- I tried a third party library but ran into an issue regarding the frame's sandboxing. But this is strange since I am able to successfully inject content scripts to that frame when using the manifest to do it directly.
Thanks!
Well, that's an interesting question.
Most methods rely <script> injection, which adds code to the wrong (page) context that has no access to Chrome APIs. I assume this is how $.getScript works. So, this will not work as intended.
Another method is using eval(). According to the documentation, eval() is allowed (but discouraged) in Content Scripts. So you can, in principle, load the script file in a XHR / jQuery AJAX request and then eval() its contents. This should work.
Lastly, you could modify your content scripts only to execute if some condition is met (say, a variable is set), and so injecting into all frames of a tab should be less of a problem. This could potentially be messy though. Note that a content script can find itself in the iframe hierarchy, which may be useful.

Liferay 6.1.20 : Minimize and bundle theme Javascript

Is there a way to apply Liferay's built-in javascript minimizing and bundling capability to the javascript I've included in my theme? I have javascript.fast.load=true in portal-ext.properties and Liferay's javascript is getting bundled & minified in everything.jsp as expected. Also, all portlet javascript that is included via a portlet's liferay-portal.xml file is getting minified as expected. However, I've got many javascript files that are included in my theme because they are utilized on every page and I would like them to get minimized and bundled into everything.jsp along with all the Liferay portal javascript. I've tried the approach suggested by this question, but I think this will only work with a hook because the MinifyFilter will look for files to minify & bundle within the context of the portal web app, i.e. <TOMCAT>/webapps/ROOT. Is there a way I can specify a path to files in a different web app (the theme in this case) as the javascript.bundle.dir parameter? In other words, something like javascript.bundle.dir[javascript.jquery.files]=/<theme-path>/js. I've tried many variations and combinations of javascript.bundle.ids, javascript.bundle.dependencies, etc. to no avail. I know I can get around the problem by putting the javascript in a hook or putting it in portlet and embedding it in the theme but I'd really like to just keep the javascript in the theme. Is there a reasonable way to accomplish this?
There doesn't seem to be a good way to include javascript files from the theme with the minified and bundled Liferay javascript. While you can define a javascript bundle in portal-ext.properties that includes your files, you can't order the dependencies the way you need to in order to get everything to work using only configuration. You can configure the "everything" bundle to depend on your custom bundle but that's not very useful. It would be far more useful if you could configure Liferay to use your custom bundle as the new "everything" bundle and tell Liferay that your bundle depends on Liferay's "everything" bundle. However, the actual bundle ids that are included are hard-coded in Liferay's top_js.jspf file. So the only way to get everything to work would be to override Liferay's definition of javascript.everything.files to include both Liferay's files and your custom javascript. This doesn't seem like a very good solution since it tampers with Liferay's list of included javascript which would certainly be a pain when you need to upgrade Liferay. As Olaf suggested, you can minify and bundle the js yourself and just include it in the portal_normal template. That is a very reasonable solution and what I would normally recommend. Unfortunately I was in a situation where my customer was requesting that all the files be bundled in one file and we are not allowed to modify the build process.
Hook Workaround
There is a workaround using a hook that I don't necessarily recommend but it does accomplish the goal of getting all javascript minimized and bundled along with Liferay's javascript. The basic process is to move the javascript from the theme into a hook, configure a new bundle in portal-ext.properties that includes all of your files, then create a jsp hook for top_js.jspf that includes your new bundle instead of the hard-coded javascript.everything.files or javascript.barebones.files bundles. The steps are:
Move your javascript files into a hook project and place them under html/js. This will cause
the files to be copied to the javascript directory under the portal
web app, i.e. <TOMCAT_HOME>/ROOT/html/js. This is where the Liferay
MinifyFilter looks for javascript files to minify & bundle.
Define a javascript bundle in portal-ext.properties that references
all of your javascript files that need to be included in the bundle
created by the MinifyFilter. Your portal-ext.properties file should
look something like this:
minifier.enabled=true
javascript.fast.load=true
javascript.my.js.files =\
jquery.1.11.1,\
my-js-lib.js,\
my-other-js-lib.js
javascript.bundle.ids=\
javascript.barebone.files,\
javascript.everything.files,\
javascript.my.js.files
javascript.bundle.dir[javascript.my.js.files]=/html/js
# our bundle depends on all the files in the "everything" bundle
javascript.bundle.dependencies[javascript.my.js.files]=javascript.everything.files
Create a JSP hook for top_js.jspf. This file is under
<TOMCAT_HOME>/ROOT/html/common/themes. It is the file that includes
either the barebones.jsp or everything.jsp based on whether the user
is authenticated (if the user is authenticated they get
everything.jsp otherwise barebones.jsp is included). Replace the
references to the javascript.everything.files and/or
javascript.barebones.files bundles with a reference to your new
bundle based on your requirements. For example, if you only want to
include your javascript when the user is authenticated you just have
to replace references to javascript.everything.files.
Specifically, you make the following changes:
This line:
<script src="<%= HtmlUtil.escape(PortalUtil.getStaticResourceURL(request, themeDisplay.getCDNDynamicResourcesHost() + themeDisplay.getPathJavaScript() + "/everything.jsp", "minifierBundleId=javascript.everything.files", javaScriptLastModified)) %>" type="text/javascript"></script>
is changed to this:
<script src="<%= HtmlUtil.escape(PortalUtil.getStaticResourceURL(request, themeDisplay.getCDNDynamicResourcesHost() + themeDisplay.getPathJavaScript() + "/everything.jsp", "minifierBundleId=javascript.my.js.files", javaScriptLastModified)) %>" type="text/javascript"></script>
and this line:
javaScriptFiles = JavaScriptBundleUtil.getFileNames(PropsKeys.JAVASCRIPT_EVERYTHING_FILES);
is changed to this:
javaScriptFiles = JavaScriptBundleUtil.getFileNames("javascript.my.js.files");
* Non-Global Hook Caveat *
If you are putting the javascript and top_js.jspf hooks in a project with other hooks and the project is configured to use non-global jsp hooks, i.e. <custom-jsp-global>false</custom-jsp-global> the solution becomes more complicated. This is because setting <custom-jsp-global>true</custom-jsp-global> makes Liferay rename your hook jsp files rather than renaming the portal's jsp files. For example, if custom-jsp-global is set to true, which is the default setting, then when I make a hook for a page called top_js.jspf, the portal will rename the original top_js.jspf file to top_js.portal.jsp and my hook file will be used instead of the original. However, when custom-jsp-global is set to false then the original file stays intact and the jsp hook file is renamed to something that includes the name of the hook like top_js.my-hook.jspf. This is a problem when you're creating a hook for included files such as top_js.jspf because the file that includes top_js.jspf will still reference the old file, not the hook which is named top_js.my-hook.jspf. This means you have to also create a hook for the file that includes your hook. Likewise, if that file is included by another file you have to make hook for that file and so on until you reach the top level page. So, in the example of trying to create a hook for top_js.jspf we have to also do the following:
Create a hook for top_head.jspf and replace the reference to top_js.jspf with a reference to our hook, top_js.my-hook.jspf.
So this line
<%# include file="/html/common/themes/top_js.jspf" %>
becomes this
<%# include file="/html/common/themes/top_js.my-hook.jspf" %>
The top_head.jspf file is actually included by the theme in
portal_normal.vm using a Velocity variable that is initialized in
init.vm on the following line:
You need to assign $top_head_include to the top_head.my-hook.jspf hook in the theme's init_custom.vm, like this:
#set ($top_head_include = "$dir_include/common/themes/top_head.my-hook.jsp")
Your Theme has access to all of the HTML the portal generates. While you might need one extra file to be loaded (css gets minified for the whole theme anyway), you can easily add all of the (already) minified js files to your theme and include them in your templates/portal-normal.ftl implementation.
It would be as easy as having this section in portal-normal.ftl:
<head>
<title>${the_title} - ${company_name}</title>
<meta content="initial-scale=1.0, width=device-width" name="viewport" />
${theme.include(top_head_include)}
<script src="${javascript_folder}/my-minified-javascript.js"/>
</head>
Note: All but the <script> line is already in the default ftl file. This way you'll end up with two js files being loaded (the barebones or everything, plus your own), but that's not too bad. You can also add the minification to your theme's build process, so that you don't have to maintain the minified code manually.
Another alternative, which I haven't tried, is examining the use of Liferay's javascript minifier (e.g. in webapps/ROOT/html/common/themes/tom_js.jsp) to see how to utilize it to dynamically minify your files.
For completeness reason (maybe it helps someone else) I'm leaving my first answer here, which you couldn't use as you say in the first comment:
There's a section in portal.properties, to be overloaded in portal-ext.properties with this heading:
##
## JavaScript
##
#
# Set a list of JavaScript files that will be loaded automatically in
# /html/common/themes/top_js.jsp.
#
# There are two lists of files specified in the properties
# "javascript.barebone.files" and "javascript.everything.files".
#
# As the name suggests, the barebone list is the minimum list of JavaScript
# files required for most cases. The everything list includes everything
# else not listed in the barebone list.
#
# The two lists of files exist for performance reasons because
# unauthenticated users usually do not utilize all the JavaScript that is
# available. See the property "javascript.barebone.enabled" for more
# information on the logic of when the barebone list is used and when the
# everything list is used and how to customize that logic.
#
# The list of files are also merged and packed for further performance
# improvements. See the property "javascript.fast.load" for more details.
#
e.g. configure javascript.everything.files (the default is below that comment, for brevity I'm not copying that here)

grunt to build dynamic jade pages

I want to use grunt to build my node.js project (based on Kraken.js, but I've replaced dust with jade). I've installed grunt-contrib-jade. For jade files that contain no server side state this all works nicely and I get the HTML files output. However, where I have .jade files that contain logic and render server-side state it all goes wrong. For example, if I have h1 #{x.y} in my file the grunt output is cannot read property y of undefined. This makes complete sense as x is only defined at runtime.
So I'm now wondering do I just ignore my jade files in the grunt build and let the server process them all at runtime, or is there some alternative that I'm missing to 'pre-process' my .jade files to speed up execution?
Quoting an answer of mine (Would it benefit to pre-compile jade templates):
When Jade compiles the template, the template is cached. In production environment if you warm up the cache, then there is no need to pre-compile template. Even if you don't, the template will be cached after its first compilation.
In short: Compiling Jade template is useless. You'd better remove grunt-contrib-jade.

Can intern test JavaScript dependent on HTML markup?

I'd like to be able to test a widget in the same way that Dojo's DOH could.
An example of this is for example dijit tests e.g. https://github.com/dojo/dijit/blob/master/tests/Fieldset.html
DOH runs in the browser and can test if a declarative widget has been created, test properties on the widget etc
Is this possible with intern using runner.js or would I have to use webdriver and use an execute eval e.g. http://code.google.com/p/selenium/wiki/JsonWireProtocol#POST_/session/:sessionId/execute (which I really don't want to do)
Any examples I've seen on this rely on programatically creating the markup, using eval wouldn't achieve what I want to do
I would use PhantomJS for this. It looks like they have documentation for that here: https://github.com/theintern/intern/wiki/Using-Intern-with-PhantomJS

Resources