blob: 8c5e9edf1c149573cb0bced8c6ebf08f4ede3cf5 [file] [log] [blame]
/*******************************************************************************
* 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;
import java.io.Reader;
import java.io.StringReader;
import java.util.Set;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import javax.script.ScriptException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.api.scripting.SlingScriptConstants;
import org.apache.sling.api.scripting.SlingScriptHelper;
import org.apache.sling.scripting.api.AbstractSlingScriptEngine;
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.compiled.SourceIdentifier;
import org.apache.sling.scripting.sightly.impl.utils.BindingsUtils;
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.java.compiler.JavaImportsAnalyzer;
import org.apache.sling.scripting.sightly.java.compiler.RenderUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The HTL Script engine
*/
public class SightlyScriptEngine extends AbstractSlingScriptEngine implements Compilable {
private static final Logger LOGGER = LoggerFactory.getLogger(SightlyScriptEngine.class);
private static final String NO_SCRIPT = "NO_SCRIPT";
private SightlyCompiler sightlyCompiler;
private SightlyJavaCompilerService javaCompilerService;
private final SightlyEngineConfiguration configuration;
private final ScriptingResourceResolverProvider scriptingResourceResolverProvider;
private final SlingJavaImportsAnalyser importsAnalyser;
SightlyScriptEngine(ScriptEngineFactory scriptEngineFactory,
SightlyCompiler sightlyCompiler,
SightlyJavaCompilerService javaCompilerService,
SightlyEngineConfiguration configuration,
ScriptingResourceResolverProvider scriptingResourceResolverProvider) {
super(scriptEngineFactory);
this.sightlyCompiler = sightlyCompiler;
this.javaCompilerService = javaCompilerService;
this.configuration = configuration;
this.scriptingResourceResolverProvider = scriptingResourceResolverProvider;
importsAnalyser = new SlingJavaImportsAnalyser();
}
@Override
public CompiledScript compile(String script) throws ScriptException {
return compile(new StringReader(script));
}
@Override
public CompiledScript compile(final Reader script) throws ScriptException {
return internalCompile(script, null);
}
@Override
public Object eval(Reader reader, ScriptContext scriptContext) throws ScriptException {
checkArguments(reader, scriptContext);
try {
Object renderUnit = scriptContext.getAttribute("precompiled.unit", SlingScriptConstants.SLING_SCOPE);
SightlyCompiledScript compiledScript;
if (renderUnit instanceof RenderUnit) {
compiledScript = new SightlyCompiledScript(this, (RenderUnit) renderUnit);
} else {
compiledScript = internalCompile(reader, scriptContext);
}
return compiledScript.eval(scriptContext);
} catch (Exception e) {
throw new ScriptException(e);
}
}
private SightlyCompiledScript internalCompile(final Reader script, ScriptContext scriptContext) throws ScriptException {
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(((SightlyScriptEngineFactory) getFactory()).getClassLoader());
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;
}
};
JavaClassBackendCompiler javaClassBackendCompiler = new JavaClassBackendCompiler(importsAnalyser);
GlobalShadowCheckBackendCompiler shadowCheckBackendCompiler = null;
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(configuration, scriptName);
String javaSourceCode = javaClassBackendCompiler.build(sourceIdentifier);
Object renderUnit = javaCompilerService.compileSource(sourceIdentifier, javaSourceCode);
if (renderUnit instanceof RenderUnit) {
return new SightlyCompiledScript(this, (RenderUnit) renderUnit);
} else {
throw new SightlyException("Expected a RenderUnit.");
}
} finally {
Thread.currentThread().setContextClassLoader(old);
}
}
private void checkArguments(Reader reader, ScriptContext scriptContext) {
if (reader == null) {
throw new NullPointerException("Reader cannot be null");
}
if (scriptContext == null) {
throw new NullPointerException("ScriptContext cannot be null");
}
}
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;
}
/**
* 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 {
@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;
}
}
}