SLING-8012 - Extract an HTL runtime bundle from the existing HTL modules
* refactored code so that compilation becomes completely optional, depending
on which modules are available at runtime
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SightlyCompilationUnit.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SightlyCompilationUnit.java
new file mode 100644
index 0000000..d998310
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SightlyCompilationUnit.java
@@ -0,0 +1,53 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.scripting.sightly.impl.engine.compiled;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.sling.commons.compiler.CompilationUnit;
+
+class SightlyCompilationUnit implements CompilationUnit {
+
+ private String fqcn;
+ private String sourceCode;
+
+ SightlyCompilationUnit(String sourceCode, String fqcn) {
+ this.sourceCode = sourceCode;
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public Reader getSource() throws IOException {
+ return new InputStreamReader(IOUtils.toInputStream(sourceCode, "UTF-8"), StandardCharsets.UTF_8);
+ }
+
+ @Override
+ public String getMainClassName() {
+ return fqcn;
+ }
+
+ @Override
+ public long getLastModified() {
+ return System.currentTimeMillis();
+ }
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingHTLMasterCompiler.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingHTLMasterCompiler.java
new file mode 100644
index 0000000..b004579
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingHTLMasterCompiler.java
@@ -0,0 +1,512 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.scripting.sightly.impl.engine.compiled;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.ScriptException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.sling.api.resource.Resource;
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.api.scripting.SlingScriptHelper;
+import org.apache.sling.commons.classloader.ClassLoaderWriter;
+import org.apache.sling.commons.classloader.DynamicClassLoaderManager;
+import org.apache.sling.commons.compiler.JavaCompiler;
+import org.apache.sling.commons.compiler.Options;
+import org.apache.sling.scripting.api.ScriptNameAware;
+import org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider;
+import org.apache.sling.scripting.sightly.SightlyException;
+import org.apache.sling.scripting.sightly.compiler.CompilationResult;
+import org.apache.sling.scripting.sightly.compiler.CompilationUnit;
+import org.apache.sling.scripting.sightly.compiler.CompilerMessage;
+import org.apache.sling.scripting.sightly.compiler.SightlyCompiler;
+import org.apache.sling.scripting.sightly.impl.engine.ResourceBackedPojoChangeMonitor;
+import org.apache.sling.scripting.sightly.impl.engine.SightlyCompiledScript;
+import org.apache.sling.scripting.sightly.impl.engine.SightlyEngineConfiguration;
+import org.apache.sling.scripting.sightly.impl.engine.SightlyScriptEngine;
+import org.apache.sling.scripting.sightly.impl.utils.BindingsUtils;
+import org.apache.sling.scripting.sightly.impl.utils.Patterns;
+import org.apache.sling.scripting.sightly.impl.utils.ScriptUtils;
+import org.apache.sling.scripting.sightly.java.compiler.GlobalShadowCheckBackendCompiler;
+import org.apache.sling.scripting.sightly.java.compiler.JavaClassBackendCompiler;
+import org.apache.sling.scripting.sightly.java.compiler.JavaEscapeUtils;
+import org.apache.sling.scripting.sightly.render.RenderContext;
+import org.apache.sling.scripting.sightly.render.RenderUnit;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+ service = SlingHTLMasterCompiler.class
+)
+public class SlingHTLMasterCompiler {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SlingHTLMasterCompiler.class);
+
+ @Reference(policyOption = ReferencePolicyOption.GREEDY)
+ private DynamicClassLoaderManager dynamicClassLoaderManager;
+
+ @Reference(policyOption = ReferencePolicyOption.GREEDY)
+ private ClassLoaderWriter classLoaderWriter;
+
+ @Reference(policyOption = ReferencePolicyOption.GREEDY)
+ private SightlyCompiler sightlyCompiler;
+
+ @Reference(policyOption = ReferencePolicyOption.GREEDY)
+ private JavaCompiler javaCompiler;
+
+ @Reference(policyOption = ReferencePolicyOption.GREEDY)
+ private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
+
+ @Reference
+ private SightlyEngineConfiguration sightlyEngineConfiguration;
+
+ @Reference
+ private ResourceBackedPojoChangeMonitor resourceBackedPojoChangeMonitor;
+
+ private static final String NO_SCRIPT = "NO_SCRIPT";
+ private static final Pattern MANGLED_CHAR_PATTERN = Pattern.compile("(.*)(__[0-9a-f]{4}__)(.*)");
+ private static final char[] ambiguousSymbols = new char[]{'-', '_', '.'};
+ static final String SIGHTLY_CONFIG_FILE = "/sightly.config";
+
+ private final Map<String, Lock> compilationLocks = new HashMap<>();
+ private Options options;
+
+
+ @Activate
+ void activate() {
+ LOGGER.info("Activating {}", getClass().getName());
+
+ String version = System.getProperty("java.specification.version");
+ options = new Options();
+ options.put(Options.KEY_GENERATE_DEBUG_INFO, true);
+ options.put(Options.KEY_SOURCE_VERSION, version);
+ options.put(Options.KEY_TARGET_VERSION, version);
+ options.put(Options.KEY_CLASS_LOADER_WRITER, classLoaderWriter);
+ options.put(Options.KEY_FORCE_COMPILATION, true);
+
+
+ InputStream is;
+ boolean newVersion = true;
+ String versionInfo = null;
+ String newVersionString = sightlyEngineConfiguration.getEngineVersion();
+ try {
+ is = classLoaderWriter.getInputStream(SIGHTLY_CONFIG_FILE);
+ if (is != null) {
+ versionInfo = IOUtils.toString(is, "UTF-8");
+ if (newVersionString.equals(versionInfo)) {
+ newVersion = false;
+ } else {
+ LOGGER.info("Detected stale classes generated by Apache Sling Scripting HTL engine version {}.", versionInfo);
+ }
+ IOUtils.closeQuietly(is);
+ }
+ } catch (IOException e) {
+ // do nothing; if we didn't find any previous version information we're considering our version to be new
+ }
+ if (newVersion) {
+ OutputStream os = classLoaderWriter.getOutputStream(SIGHTLY_CONFIG_FILE);
+ try {
+ IOUtils.write(sightlyEngineConfiguration.getEngineVersion(), os, "UTF-8");
+ } catch (IOException e) {
+ // ignore
+ } finally {
+ IOUtils.closeQuietly(os);
+ }
+ String scratchFolder = sightlyEngineConfiguration.getScratchFolder();
+ boolean scratchFolderDeleted = classLoaderWriter.delete(scratchFolder);
+ if (scratchFolderDeleted) {
+ if (StringUtils.isNotEmpty(versionInfo)) {
+ LOGGER.info("Deleted stale classes generated by Apache Sling Scripting HTL engine version {}.", versionInfo);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method returns an Object instance based on a {@link Resource}-backed class that is either found through regular classloading
+ * mechanisms or on-the-fly compilation. In case the requested class does not denote a fully qualified class name, this service will
+ * try to find the class through Sling's resource resolution mechanism and compile the class on-the-fly if required.
+ *
+ * @param renderContext the render context
+ * @param className name of class to use for object instantiation
+ * @return object instance of the requested class or {@code null} if the specified class is not backed by a {@link Resource}
+ */
+ public Object getResourceBackedUseObject(RenderContext renderContext, String className) {
+ LOGGER.debug("Attempting to load class {}.", className);
+ try {
+ if (className.contains(".")) {
+ Resource pojoResource = getPOJOFromFQCN(scriptingResourceResolverProvider.getRequestScopedResourceResolver(), className);
+ if (pojoResource != null) {
+ return getUseObjectAndRecompileIfNeeded(pojoResource);
+ }
+ } else {
+ Resource pojoResource = ScriptUtils.resolveScript(
+ scriptingResourceResolverProvider.getRequestScopedResourceResolver(),
+ renderContext,
+ className + ".java"
+ );
+ if (pojoResource != null) {
+ return getUseObjectAndRecompileIfNeeded(pojoResource);
+ }
+ }
+ } catch (Exception e) {
+ throw new SightlyException("Cannot obtain an instance for class " + className + ".", e);
+ }
+ return null;
+ }
+
+ public SightlyCompiledScript compileHTLScript(final SightlyScriptEngine engine,
+ final Reader script,
+ final ScriptContext scriptContext) throws ScriptException {
+ ClassLoader old = Thread.currentThread().getContextClassLoader();
+ Thread.currentThread().setContextClassLoader(dynamicClassLoaderManager.getDynamicClassLoader());
+ try {
+ String sName = NO_SCRIPT;
+ if (script instanceof ScriptNameAware) {
+ sName = ((ScriptNameAware) script).getScriptName();
+ }
+ if (sName.equals(NO_SCRIPT)) {
+ sName = getScriptName(scriptContext);
+ }
+ final String scriptName = sName;
+ CompilationUnit compilationUnit = new CompilationUnit() {
+ @Override
+ public String getScriptName() {
+ return scriptName;
+ }
+
+ @Override
+ public Reader getScriptReader() {
+ return script;
+ }
+ };
+ GlobalShadowCheckBackendCompiler shadowCheckBackendCompiler = null;
+ SlingJavaImportsAnalyser importsAnalyser = new SlingJavaImportsAnalyser(scriptingResourceResolverProvider);
+ JavaClassBackendCompiler javaClassBackendCompiler = new JavaClassBackendCompiler(importsAnalyser);
+ if (scriptContext != null) {
+ Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+ Set<String> globals = bindings.keySet();
+ shadowCheckBackendCompiler =
+ new GlobalShadowCheckBackendCompiler(javaClassBackendCompiler, globals);
+ }
+ CompilationResult result =
+ shadowCheckBackendCompiler == null ? sightlyCompiler.compile(compilationUnit, javaClassBackendCompiler) :
+ sightlyCompiler.compile(compilationUnit, shadowCheckBackendCompiler);
+ if (result.getWarnings().size() > 0) {
+ for (CompilerMessage warning : result.getWarnings()) {
+ LOGGER.warn("Script {} {}:{}: {}", warning.getScriptName(), warning.getLine(), warning.getColumn(),
+ warning.getMessage());
+ }
+ }
+ if (result.getErrors().size() > 0) {
+ CompilerMessage error = result.getErrors().get(0);
+ throw new ScriptException(error.getMessage(), error.getScriptName(), error.getLine(), error.getColumn());
+ }
+ SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, scriptName);
+ String javaSourceCode = javaClassBackendCompiler.build(sourceIdentifier);
+ Object renderUnit = compileSource(sourceIdentifier, javaSourceCode);
+ if (renderUnit instanceof RenderUnit) {
+ return new SightlyCompiledScript(engine, (RenderUnit) renderUnit);
+ } else {
+ throw new SightlyException("Expected a RenderUnit.");
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(old);
+ }
+ }
+
+ public ClassLoader getClassLoader() {
+ return classLoaderWriter.getClassLoader();
+ }
+
+ /**
+ * Given a {@code fullyQualifiedClassName} and optionally a sub-package that should be stripped ({@code slashSubpackage}), this
+ * method will try to locate a {@code Resource} in the repository that provides the source code for the Java class.
+ *
+ * @param resolver a resource resolver with access to the script paths
+ * @param fullyQualifiedClassName the FQCN
+ * @return the {@code Resource} backing the class, or {@code null} if one cannot be found
+ */
+ Resource getPOJOFromFQCN(ResourceResolver resolver, String fullyQualifiedClassName) {
+ StringBuilder pathElements = new StringBuilder("/");
+ String[] classElements = StringUtils.split(fullyQualifiedClassName, '.');
+ for (int i = 0; i < classElements.length; i++) {
+ String classElem = classElements[i];
+ Matcher matcher = MANGLED_CHAR_PATTERN.matcher(classElem);
+ if (matcher.matches()) {
+ String group = matcher.group(2);
+ char unmangled = JavaEscapeUtils.unmangle(group);
+ classElem = classElem.replaceAll(group, Character.toString(unmangled));
+ while (matcher.find()) {
+ group = matcher.group(2);
+ unmangled = JavaEscapeUtils.unmangle(group);
+ classElem = classElem.replaceAll(group, Character.toString(unmangled));
+ }
+ } else {
+ int underscoreIndex = classElem.indexOf('_');
+ if (underscoreIndex > -1) {
+ if (underscoreIndex == classElem.length() - 1) {
+ classElem = classElem.substring(0, classElem.length() -1);
+ } else if (underscoreIndex == 0 && !Character.isJavaIdentifierStart(classElem.charAt(1))){
+ classElem = classElem.substring(1);
+ }
+ }
+ }
+ pathElements.append(classElem);
+ if (i < classElements.length - 1) {
+ pathElements.append("/");
+ }
+ }
+ Set<String> possiblePOJOPaths = getPossiblePojoPaths(pathElements.toString() + ".java");
+ for (String possiblePath : possiblePOJOPaths) {
+ Resource r = resolver.getResource(possiblePath);
+ if (r != null) {
+ return r;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * For a JCR path obtained from expanding a generated class name this method generates all the alternative path names that can be
+ * obtained by expanding the mentioned class' name.
+ *
+ * @param originalPath one of the possible paths
+ * @return a {@link Set} containing all the alternative paths if symbol replacement was needed; otherwise the set will contain just
+ * the {@code originalPath}
+ */
+ private static Set<String> getPossiblePojoPaths(String originalPath) {
+ Set<String> possiblePaths = new LinkedHashSet<>();
+ possiblePaths.add(originalPath);
+ Map<Integer, Character> chars = new HashMap<>();
+ for (char symbol : ambiguousSymbols) {
+ String pathCopy = originalPath.substring(0, originalPath.lastIndexOf("/"));
+ int actualIndex = 0;
+ boolean firstPass = true;
+ while (pathCopy.indexOf(symbol) != -1) {
+ int pos = pathCopy.indexOf(symbol);
+ actualIndex += pos;
+ if (!firstPass) {
+ actualIndex += 1;
+ }
+ chars.put(actualIndex, symbol);
+ pathCopy = pathCopy.substring(pos + 1);
+ firstPass = false;
+ }
+ }
+ if (chars.size() > 0) {
+ ArrayList<char[]> possibleArrangements = new ArrayList<>();
+ populateArray(possibleArrangements, new char[chars.size()], 0);
+ Integer[] indexes = chars.keySet().toArray(new Integer[0]);
+ for (char[] arrangement : possibleArrangements) {
+ char[] possiblePath = originalPath.toCharArray();
+ for (int i = 0; i < arrangement.length; i++) {
+ char currentSymbol = arrangement[i];
+ int currentIndex = indexes[i];
+ possiblePath[currentIndex] = currentSymbol;
+ }
+ possiblePaths.add(new String(possiblePath));
+ }
+ }
+ return possiblePaths;
+ }
+
+ /**
+ * Given an initial array with its size equal to the number of elements of a needed arrangement, this method will generate all
+ * the possible arrangements of values for this array in the provided {@code arrayCollection}. The values with which the array is
+ * populated are the {@link #ambiguousSymbols} characters.
+ *
+ * @param arrayCollection the collection that will store the arrays
+ * @param symbolsArrangementArray an initial array that will be used for collecting the results
+ * @param index the initial index of the array that will be populated (needed for recursion purposes; start with 0 for the initial call)
+ */
+ private static void populateArray(ArrayList<char[]> arrayCollection, char[] symbolsArrangementArray, int
+ index) {
+ if (symbolsArrangementArray.length > 0) {
+ if (index == symbolsArrangementArray.length) {
+ arrayCollection.add(symbolsArrangementArray.clone());
+ } else {
+ for (char symbol : ambiguousSymbols) {
+ symbolsArrangementArray[index] = symbol;
+ populateArray(arrayCollection, symbolsArrangementArray, index + 1);
+ }
+ }
+ }
+ }
+
+ /**
+ * Compiles a class using the passed fully qualified class name and its source code.
+ *
+ * @param sourceIdentifier the source identifier
+ * @param sourceCode the source code from which to generate the class
+ * @return object instance of the class to compile
+ */
+ private Object compileSource(SourceIdentifier sourceIdentifier, String sourceCode) {
+ Lock lock;
+ final String fqcn = sourceIdentifier.getFullyQualifiedClassName();
+ synchronized (compilationLocks) {
+ lock = compilationLocks.get(fqcn);
+ if (lock == null) {
+ lock = new ReentrantLock();
+ compilationLocks.put(fqcn, lock);
+ }
+ }
+ lock.lock();
+ try {
+ if (sightlyEngineConfiguration.keepGenerated()) {
+ String path = "/" + fqcn.replaceAll("\\.", "/") + ".java";
+ OutputStream os = classLoaderWriter.getOutputStream(path);
+ IOUtils.write(sourceCode, os, "UTF-8");
+ IOUtils.closeQuietly(os);
+ }
+ String[] sourceCodeLines = sourceCode.split("\\r\\n|[\\n\\x0B\\x0C\\r\\u0085\\u2028\\u2029]");
+ boolean foundPackageDeclaration = false;
+ for (String line : sourceCodeLines) {
+ Matcher matcher = Patterns.JAVA_PACKAGE_DECLARATION.matcher(line);
+ if (matcher.matches()) {
+ /*
+ * This matching might return false positives like:
+ * // package a.b.c;
+ *
+ * where from a syntactic point of view the source code doesn't have a package declaration and the expectancy is that our
+ * SightlyJavaCompilerService will add one.
+ */
+ foundPackageDeclaration = true;
+ break;
+ }
+ }
+
+ if (!foundPackageDeclaration) {
+ sourceCode = "package " + sourceIdentifier.getPackageName() + ";\n" + sourceCode;
+ }
+
+ org.apache.sling.commons.compiler.CompilationUnit
+ compilationUnit = new SightlyCompilationUnit(sourceCode, fqcn);
+ long start = System.currentTimeMillis();
+ org.apache.sling.commons.compiler.CompilationResult
+ compilationResult = javaCompiler.compile(new org.apache.sling.commons.compiler.CompilationUnit[]{compilationUnit}, options);
+ long end = System.currentTimeMillis();
+ List<org.apache.sling.commons.compiler.CompilerMessage> errors = compilationResult.getErrors();
+ if (errors != null && errors.size() > 0) {
+ throw new SightlyException(createErrorMsg(errors));
+ }
+ if (compilationResult.didCompile()) {
+ LOGGER.debug("Class {} was compiled in {}ms.", fqcn, end - start);
+ }
+ /*
+ * the class loader might have become dirty, so let the {@link ClassLoaderWriter} decide which class loader to return
+ */
+ return classLoaderWriter.getClassLoader().loadClass(fqcn).newInstance();
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | IOException e) {
+ throw new SightlyException(e);
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ private Object getUseObjectAndRecompileIfNeeded(Resource pojoResource)
+ throws IOException, InstantiationException, IllegalAccessException, ClassNotFoundException {
+ SourceIdentifier sourceIdentifier = new SourceIdentifier(sightlyEngineConfiguration, pojoResource.getPath());
+ long sourceLastModifiedDateFromCache =
+ resourceBackedPojoChangeMonitor.getLastModifiedDateForJavaUseObject(pojoResource.getPath());
+ long classLastModifiedDate = classLoaderWriter.getLastModified("/" + sourceIdentifier.getFullyQualifiedClassName()
+ .replaceAll("\\.", "/") + ".class");
+ if (sourceLastModifiedDateFromCache == 0) {
+ // first access; let's check the real last modified date of the source
+ long sourceLastModifiedDate = pojoResource.getResourceMetadata().getModificationTime();
+ resourceBackedPojoChangeMonitor.recordLastModifiedTimestamp(pojoResource.getPath(), sourceLastModifiedDate);
+ if (classLastModifiedDate < 0 || sourceLastModifiedDate > classLastModifiedDate) {
+ return compileSource(sourceIdentifier, IOUtils.toString(pojoResource.adaptTo(InputStream.class), "UTF-8"));
+ } else {
+ return classLoaderWriter.getClassLoader().loadClass(sourceIdentifier.getFullyQualifiedClassName()).newInstance();
+ }
+ } else {
+ if (sourceLastModifiedDateFromCache > classLastModifiedDate) {
+ return compileSource(sourceIdentifier, IOUtils.toString(pojoResource.adaptTo(InputStream.class), "UTF-8"));
+ } else {
+ return classLoaderWriter.getClassLoader().loadClass(sourceIdentifier.getFullyQualifiedClassName()).newInstance();
+ }
+ }
+ }
+
+ //---------------------------------- private -----------------------------------
+ private String createErrorMsg(List<org.apache.sling.commons.compiler.CompilerMessage> errors) {
+ final StringBuilder buffer = new StringBuilder();
+ buffer.append("Compilation errors in ");
+ buffer.append(errors.get(0).getFile());
+ buffer.append(":");
+ StringBuilder errorsBuffer = new StringBuilder();
+ boolean duplicateVariable = false;
+ for (final org.apache.sling.commons.compiler.CompilerMessage e : errors) {
+ if (!duplicateVariable) {
+ if (e.getMessage().contains("Duplicate local variable")) {
+ duplicateVariable = true;
+ buffer.append(" Maybe you defined more than one identical block elements without defining a different variable for "
+ + "each one?");
+ }
+ }
+ errorsBuffer.append("\nLine ");
+ errorsBuffer.append(e.getLine());
+ errorsBuffer.append(", column ");
+ errorsBuffer.append(e.getColumn());
+ errorsBuffer.append(" : ");
+ errorsBuffer.append(e.getMessage());
+ }
+ buffer.append(errorsBuffer);
+ return buffer.toString();
+ }
+
+ private String getScriptName(ScriptContext scriptContext) {
+ if (scriptContext != null) {
+ Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE);
+ String scriptName = (String) bindings.get(ScriptEngine.FILENAME);
+ if (scriptName != null && !"".equals(scriptName)) {
+ return scriptName;
+ }
+ SlingScriptHelper sling = BindingsUtils.getHelper(bindings);
+ if (sling != null) {
+ return sling.getScript().getScriptResource().getPath();
+ }
+ }
+ return NO_SCRIPT;
+ }
+
+}
diff --git a/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingJavaImportsAnalyser.java b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingJavaImportsAnalyser.java
new file mode 100644
index 0000000..2e67ae9
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SlingJavaImportsAnalyser.java
@@ -0,0 +1,47 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ Licensed to the Apache Software Foundation (ASF) under one
+ ~ or more contributor license agreements. See the NOTICE file
+ ~ distributed with this work for additional information
+ ~ regarding copyright ownership. The ASF licenses this file
+ ~ to you under the Apache License, Version 2.0 (the
+ ~ "License"); you may not use this file except in compliance
+ ~ with the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing,
+ ~ software distributed under the License is distributed on an
+ ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ ~ KIND, either express or implied. See the License for the
+ ~ specific language governing permissions and limitations
+ ~ under the License.
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
+package org.apache.sling.scripting.sightly.impl.engine.compiled;
+
+import org.apache.sling.scripting.api.resource.ScriptingResourceResolverProvider;
+import org.apache.sling.scripting.sightly.java.compiler.JavaEscapeUtils;
+import org.apache.sling.scripting.sightly.java.compiler.JavaImportsAnalyzer;
+
+/**
+ * This custom imports analyser makes sure that no import statements are generated for repository-based use objects, since these are
+ * not compiled ahead of the HTL scripts.
+ */
+class SlingJavaImportsAnalyser implements JavaImportsAnalyzer {
+
+ private ScriptingResourceResolverProvider scriptingResourceResolverProvider;
+
+ public SlingJavaImportsAnalyser(ScriptingResourceResolverProvider scriptingResourceResolverProvider) {
+ this.scriptingResourceResolverProvider = scriptingResourceResolverProvider;
+ }
+
+ @Override
+ public boolean allowImport(String importedClass) {
+ for (String searchPath : scriptingResourceResolverProvider.getRequestScopedResourceResolver().getSearchPath()) {
+ String subPackage = JavaEscapeUtils.makeJavaPackage(searchPath);
+ if (importedClass.startsWith(subPackage)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}