blob: 2fca84b1af4480d571c5cf7238aecc2baf45730b [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.core.impl;
import static org.apache.sling.api.scripting.SlingBindings.FLUSH;
import static org.apache.sling.api.scripting.SlingBindings.LOG;
import static org.apache.sling.api.scripting.SlingBindings.OUT;
import static org.apache.sling.api.scripting.SlingBindings.READER;
import static org.apache.sling.api.scripting.SlingBindings.REQUEST;
import static org.apache.sling.api.scripting.SlingBindings.RESOURCE;
import static org.apache.sling.api.scripting.SlingBindings.RESOLVER;
import static org.apache.sling.api.scripting.SlingBindings.RESPONSE;
import static org.apache.sling.api.scripting.SlingBindings.SLING;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.Writer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.script.Bindings;
import javax.script.Compilable;
import javax.script.CompiledScript;
import javax.script.Invocable;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import javax.script.ScriptException;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.apache.sling.api.SlingConstants;
import org.apache.sling.api.SlingException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.SyntheticResource;
import org.apache.sling.api.scripting.LazyBindings;
import org.apache.sling.api.scripting.ScriptEvaluationException;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.api.scripting.SlingScript;
import org.apache.sling.api.scripting.SlingScriptConstants;
import org.apache.sling.api.scripting.SlingScriptHelper;
import org.apache.sling.scripting.api.BindingsValuesProvider;
import org.apache.sling.scripting.api.CachedScript;
import org.apache.sling.scripting.api.ScriptCache;
import org.apache.sling.scripting.core.ScriptNameAwareReader;
import org.apache.sling.scripting.core.impl.helper.CachedScriptImpl;
import org.apache.sling.scripting.core.impl.helper.ProtectedBindings;
import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class DefaultSlingScript implements SlingScript, Servlet, ServletConfig {
/** The logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultSlingScript.class);
/** is defined on multiple files in this bundle*/
private static final long WARN_LIMIT_FOR_BVP_NANOS = (1000*1000); // 1 ms
private static final String BINDINGS_THRESHOLD_MESSAGE = "Adding the bindings of %s took %s microseconds which is above the hardcoded" +
" limit of %s microseconds; if this message appears often it indicates that this BindingsValuesProvider has an impact on " +
"general page rendering performance.";
/** Thread local containing the resource resolver. */
private static ThreadLocal<ResourceResolver> requestResourceResolver = new ThreadLocal<>();
/** The set of protected keys. */
private static final Set<String> PROTECTED_KEYS =
new HashSet<>(Arrays.asList(REQUEST, RESPONSE, READER, SLING, RESOURCE, RESOLVER, OUT, LOG));
/** The resource pointing to the script. */
private final Resource scriptResource;
/** The name of the script (the resource path) */
private final String scriptName;
/** The encoding of the script. */
private final String scriptEncoding;
/** The script engine for this script. */
private final ScriptEngine scriptEngine;
/** The servlet context. */
private ServletContext servletContext;
/** The init parameters for this servlet. */
private Dictionary<String, String> initParameters;
/** The current bundle context. */
private final BundleContext bundleContext;
/** The ScriptBindingsValuesProviders. */
private final Collection<BindingsValuesProvider> bindingsValuesProviders;
/** The cache for services. */
private final ServiceCache cache;
/* The cache for compiled scripts. */
private final ScriptCache scriptCache;
/**
* Constructor
* @param bundleContext The bundle context
* @param scriptResource The script resource
* @param scriptEngine The script engine
* @param bindingsValuesProviders additional bindings values providers
* @param cache serviceCache
*/
DefaultSlingScript(final BundleContext bundleContext,
final Resource scriptResource,
final ScriptEngine scriptEngine,
final Collection<BindingsValuesProvider> bindingsValuesProviders,
final ServiceCache cache,
final ScriptCache scriptCache) {
this.scriptResource = scriptResource;
this.scriptEngine = scriptEngine;
this.bundleContext = bundleContext;
this.bindingsValuesProviders = bindingsValuesProviders;
this.cache = cache;
this.scriptCache = scriptCache;
this.scriptName = this.scriptResource.getPath();
// Now know how to get the input stream, we still have to decide
// on the encoding of the stream's data. Primarily we assume it is
// UTF-8, which is a default in many places in JCR. Secondarily
// we try to get a jcr:encoding property besides the data property
// to provide a possible encoding
final ResourceMetadata meta = this.scriptResource.getResourceMetadata();
String encoding = meta.getCharacterEncoding();
if (encoding == null) {
encoding = "UTF-8";
}
this.scriptEncoding = encoding;
}
// ---------- SlingScript interface ----------------------------------------
/**
* @see org.apache.sling.api.scripting.SlingScript#getScriptResource()
*/
public Resource getScriptResource() {
final ResourceResolver resolver = requestResourceResolver.get();
if ( resolver == null ) {
// if we don't have a request resolver we directly return the script resource
return scriptResource;
}
return new LazyScriptResource(this.scriptName,
this.scriptResource.getResourceType(), resolver);
}
/**
* @see org.apache.sling.api.scripting.SlingScript#eval(org.apache.sling.api.scripting.SlingBindings)
* @throws ScriptEvaluationException
*/
public Object eval(SlingBindings props) {
return this.call(props, null);
}
// ---------- Servlet interface --------------------------------------------
private static final Integer[] SCOPES = { SlingScriptConstants.SLING_SCOPE, Integer.valueOf(100), Integer.valueOf(200) };
/**
* @see org.apache.sling.api.scripting.SlingScript#call(org.apache.sling.api.scripting.SlingBindings, java.lang.String, java.lang.Object[])
* @throws ScriptEvaluationException
*/
public Object call(SlingBindings props, String method, Object... args) {
Bindings bindings = null;
Reader reader = null;
boolean disposeScriptHelper = !props.containsKey(SLING);
ResourceResolver oldResolver = null;
try {
bindings = verifySlingBindings(props);
// use final variable for inner class!
final Bindings b = bindings;
// create script context
final ScriptContext ctx = new ScriptContext() {
private Bindings globalScope;
private Bindings engineScope = b;
private Writer writer = (Writer) b.get(OUT);
private Writer errorWriter = new LogWriter((Logger) b.get(LOG));
private Reader reader = (Reader)b.get(READER);
private Bindings slingScope = new LazyBindings();
/**
* @see javax.script.ScriptContext#setBindings(javax.script.Bindings, int)
*/
public void setBindings(final Bindings bindings, final int scope) {
switch (scope) {
case SlingScriptConstants.SLING_SCOPE : this.slingScope = bindings;
break;
case 100: if (bindings == null) throw new NullPointerException("Bindings for ENGINE scope is null");
this.engineScope = bindings;
break;
case 200: this.globalScope = bindings;
break;
default: throw new IllegalArgumentException("Invalid scope");
}
}
/**
* @see javax.script.ScriptContext#getBindings(int)
*/
public Bindings getBindings(final int scope) {
switch (scope) {
case SlingScriptConstants.SLING_SCOPE : return slingScope;
case 100: return this.engineScope;
case 200: return this.globalScope;
default:
throw new IllegalArgumentException("Invalid scope");
}
}
/**
* @see javax.script.ScriptContext#setAttribute(java.lang.String, java.lang.Object, int)
*/
public void setAttribute(final String name, final Object value, final int scope) {
if (name == null) throw new IllegalArgumentException("Name is null");
final Bindings bindings = getBindings(scope);
if (bindings != null) {
bindings.put(name, value);
}
}
/**
* @see javax.script.ScriptContext#getAttribute(java.lang.String, int)
*/
public Object getAttribute(final String name, final int scope) {
if (name == null) throw new IllegalArgumentException("Name is null");
final Bindings bindings = getBindings(scope);
if (bindings != null) {
return bindings.get(name);
}
return null;
}
/**
* @see javax.script.ScriptContext#removeAttribute(java.lang.String, int)
*/
public Object removeAttribute(final String name, final int scope) {
if (name == null) throw new IllegalArgumentException("Name is null");
final Bindings bindings = getBindings(scope);
if (bindings != null) {
return bindings.remove(name);
}
return null;
}
/**
* @see javax.script.ScriptContext#getAttribute(java.lang.String)
*/
public Object getAttribute(String name) {
if (name == null) throw new IllegalArgumentException("Name is null");
for (final int scope : SCOPES) {
final Bindings bindings = getBindings(scope);
if ( bindings != null ) {
final Object o = bindings.get(name);
if ( o != null ) {
return o;
}
}
}
return null;
}
/**
* @see javax.script.ScriptContext#getAttributesScope(java.lang.String)
*/
public int getAttributesScope(String name) {
if (name == null) throw new IllegalArgumentException("Name is null");
for (final int scope : SCOPES) {
if ((getBindings(scope) != null) && (getBindings(scope).containsKey(name))) {
return scope;
}
}
return -1;
}
/**
* @see javax.script.ScriptContext#getScopes()
*/
public List<Integer> getScopes() {
return Arrays.asList(SCOPES);
}
/**
* @see javax.script.ScriptContext#getWriter()
*/
public Writer getWriter() {
return this.writer;
}
/**
* @see javax.script.ScriptContext#getErrorWriter()
*/
public Writer getErrorWriter() {
return this.errorWriter;
}
/**
* @see javax.script.ScriptContext#setWriter(java.io.Writer)
*/
public void setWriter(Writer writer) {
this.writer = writer;
}
/**
* @see javax.script.ScriptContext#setErrorWriter(java.io.Writer)
*/
public void setErrorWriter(Writer writer) {
this.errorWriter = writer;
}
/**
* @see javax.script.ScriptContext#getReader()
*/
public Reader getReader() {
return this.reader;
}
/**
* @see javax.script.ScriptContext#setReader(java.io.Reader)
*/
public void setReader(Reader reader) {
this.reader = reader;
}
};
// set the current resource resolver if a request is available from the bindings
if ( props.getRequest() != null ) {
oldResolver = requestResourceResolver.get();
requestResourceResolver.set(props.getRequest().getResourceResolver());
}
// set the script resource resolver as an attribute
ctx.setAttribute(SlingScriptConstants.ATTR_SCRIPT_RESOURCE_RESOLVER,
this.scriptResource.getResourceResolver(), SlingScriptConstants.SLING_SCOPE);
reader = getScriptReader();
if ( method != null && !(this.scriptEngine instanceof Invocable)) {
reader = getWrapperReader(reader, method, args);
}
// evaluate the script
final Object result;
if (method == null && this.scriptEngine instanceof Compilable) {
CachedScript cachedScript = scriptCache.getScript(scriptName);
if (cachedScript == null) {
ScriptNameAwareReader snReader = new ScriptNameAwareReader(reader, scriptName);
CompiledScript compiledScript = ((Compilable) scriptEngine).compile(snReader);
cachedScript = new CachedScriptImpl(scriptName, compiledScript);
scriptCache.putScript(cachedScript);
LOGGER.debug("Adding {} to the script cache.", scriptName);
} else {
LOGGER.debug("Script {} was already cached.", scriptName);
}
result = cachedScript.getCompiledScript().eval(ctx);
} else {
result = scriptEngine.eval(reader, ctx);
}
// call method - if supplied and script engine supports direct invocation
if ( method != null && (this.scriptEngine instanceof Invocable)) {
try {
((Invocable)scriptEngine).invokeFunction(method, Arrays.asList(args).toArray());
} catch (NoSuchMethodException e) {
throw new ScriptEvaluationException(this.scriptName, "Method " + method + " not found in script.", e);
}
}
// optional flush the output channel
Object flushObject = bindings.get(FLUSH);
if (Boolean.TRUE.equals(flushObject)) {
ctx.getWriter().flush();
}
// allways flush the error channel
ctx.getErrorWriter().flush();
return result;
} catch (IOException ioe) {
throw new ScriptEvaluationException(this.scriptName, ioe.getMessage(),
ioe);
} catch (ScriptEvaluationException see) {
throw see;
} catch (ScriptException se) {
Throwable cause = (se.getCause() == null) ? se : se.getCause();
throw new ScriptEvaluationException(this.scriptName, se.getMessage(),
cause);
} finally {
if ( props.getRequest() != null ) {
requestResourceResolver.set(oldResolver);
}
// close the script reader (SLING-380)
if (reader != null) {
try {
reader.close();
} catch (IOException ignore) {
// don't care
}
}
// dispose of the SlingScriptHelper
if ( bindings != null && disposeScriptHelper ) {
final InternalScriptHelper helper = (InternalScriptHelper) bindings.get(SLING);
if ( helper != null ) {
helper.cleanup();
}
}
}
}
/**
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
*/
public void init(ServletConfig servletConfig) {
if (servletConfig != null) {
final Dictionary<String, String> params = new Hashtable<>(); // NOSONAR
for (Enumeration<?> ne = servletConfig.getInitParameterNames(); ne.hasMoreElements();) {
String name = String.valueOf(ne.nextElement());
String value = servletConfig.getInitParameter(name);
params.put(name, value);
}
this.initParameters = params;
this.servletContext = servletConfig.getServletContext();
}
}
/**
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
*/
public void service(ServletRequest req, ServletResponse res) {
final SlingHttpServletRequest request = (SlingHttpServletRequest) req;
try {
// prepare the properties for the script
final SlingBindings props = new SlingBindings();
props.setRequest((SlingHttpServletRequest) req);
props.setResponse((SlingHttpServletResponse) res);
// try to set content type (unless included)
if (request.getAttribute(SlingConstants.ATTR_INCLUDE_SERVLET_PATH) == null) {
final String contentType = request.getResponseContentType();
if (contentType != null) {
res.setContentType(contentType);
// only set the character encoding for text/ content types
// see SLING-679
if (contentType.startsWith("text/")) {
res.setCharacterEncoding("UTF-8");
}
} else {
LOGGER.debug("service: No response content type defined for request {}.", request.getRequestURI());
}
} else {
LOGGER.debug("service: Included request, not setting content type and encoding");
}
// evaluate the script now using the ScriptEngine
eval(props);
} catch (RuntimeException see) {
// log in the request progress tracker
logScriptError(request, see);
throw see;
} catch (Exception e) {
// log in the request progress tracker
logScriptError(request, e);
throw new SlingException("Cannot get DefaultSlingScript: "
+ e.getMessage(), e);
}
}
public ServletConfig getServletConfig() {
return this;
}
public String getServletInfo() {
return "Script " + scriptName;
}
/**
* @see javax.servlet.Servlet#destroy()
*/
public void destroy() {
initParameters = null;
servletContext = null;
}
// ---------- ServletConfig ------------------------------------------------
/**
* @see javax.servlet.ServletConfig#getInitParameter(java.lang.String)
*/
public String getInitParameter(String name) {
final Dictionary<String, String> params = initParameters;
return (params != null) ? params.get(name) : null;
}
/**
* @see javax.servlet.ServletConfig#getInitParameterNames()
*/
public Enumeration<String> getInitParameterNames() {
final Dictionary<String, String> params = initParameters;
return (params != null) ? params.keys() : null;
}
/**
* @see javax.servlet.ServletConfig#getServletContext()
*/
public ServletContext getServletContext() {
return servletContext;
}
/**
* @see javax.servlet.ServletConfig#getServletName()
*/
public String getServletName() {
return this.scriptName;
}
// ---------- internal -----------------------------------------------------
private Reader getScriptReader() throws IOException {
// access the value as a stream and return a buffered reader
// converting the stream data using UTF-8 encoding, which is
// the default encoding used
return new BufferedReader(new InputStreamReader(new LazyInputStream(this.scriptResource), this.scriptEncoding));
}
private Reader getWrapperReader(final Reader scriptReader, final String method, final Object... args) {
final StringBuilder buffer = new StringBuilder(method);
buffer.append('(');
for(Object o : args) {
buffer.append('"');
buffer.append(o);
buffer.append('"');
}
buffer.append(')');
final String msg = buffer.toString();
return new Reader() {
protected boolean doAppend = false;
protected StringReader methodReader = new StringReader(msg);
/**
* @see java.io.Reader#close()
*/
@Override
public void close() throws IOException {
scriptReader.close();
}
@Override
public int read(char[] cbuf, int start, int len) throws IOException {
if ( doAppend ) {
return methodReader.read(cbuf, start, len);
}
int readLen = scriptReader.read(cbuf, start, len);
if ( readLen == -1 ) {
doAppend = true;
return this.read(cbuf, start, len);
}
return readLen;
}
@Override
public int read() throws IOException {
if ( doAppend ) {
return methodReader.read();
}
int value = scriptReader.read();
if ( value == -1 ) {
doAppend = true;
return methodReader.read();
}
return value;
}
@Override
public int read(char[] cbuf) throws IOException {
return this.read(cbuf, 0, cbuf.length);
}
@Override
public boolean ready() throws IOException {
return scriptReader.ready();
}
};
}
Bindings verifySlingBindings(final SlingBindings slingBindings) throws IOException {
final Bindings bindings = new LazyBindings();
final SlingHttpServletRequest request = slingBindings.getRequest();
// check sling object
Object slingObject = slingBindings.get(SLING);
if (slingObject == null) {
if ( request != null ) {
slingObject = new InternalScriptHelper(this.bundleContext, this, request, slingBindings.getResponse(), this.cache);
} else {
slingObject = new InternalScriptHelper(this.bundleContext, this, this.cache);
}
} else if (!(slingObject instanceof SlingScriptHelper) ) {
throw fail(SLING, "Wrong type");
}
final SlingScriptHelper sling = (SlingScriptHelper)slingObject;
bindings.put(SLING, sling);
if (request != null) {
final SlingHttpServletResponse response = slingBindings.getResponse();
if (response == null) {
throw fail(RESPONSE, "Missing or wrong type");
}
Object resourceObject = slingBindings.get(RESOURCE);
if (resourceObject != null && !(resourceObject instanceof Resource)) {
throw fail(RESOURCE, "Wrong type");
}
Object resolverObject = slingBindings.get(RESOLVER);
if (resolverObject != null && !(resolverObject instanceof ResourceResolver)) {
throw fail(RESOLVER, "Wrong type");
}
Object writerObject = slingBindings.get(OUT);
if (writerObject != null && !(writerObject instanceof PrintWriter)) {
throw fail(OUT, "Wrong type");
}
// if there is a provided sling script helper, check arguments
if (slingBindings.get(SLING) != null) {
if (sling.getRequest() != request) {
throw fail(REQUEST,
"Not the same as request field of SlingScriptHelper");
}
if (sling.getResponse() != response) {
throw fail(RESPONSE,
"Not the same as response field of SlingScriptHelper");
}
if (resourceObject != null
&& sling.getRequest().getResource() != resourceObject) {
throw fail(RESOURCE,
"Not the same as resource of the SlingScriptHelper request");
}
if (resolverObject != null && sling.getRequest().getResourceResolver() != resolverObject) {
throw fail(RESOLVER,
"Not the same as the resource resolver of the SlingScriptHelper request's resolver");
}
if (writerObject != null
&& sling.getResponse().getWriter() != writerObject) {
throw fail(OUT,
"Not the same as writer of the SlingScriptHelper response");
}
}
// set base variables when executing inside a request
bindings.put(REQUEST, sling.getRequest());
bindings.put(READER, sling.getRequest().getReader());
bindings.put(RESPONSE, sling.getResponse());
bindings.put(RESOURCE, sling.getRequest().getResource());
bindings.put(RESOLVER, sling.getRequest().getResourceResolver());
bindings.put(OUT, sling.getResponse().getWriter());
}
Object logObject = slingBindings.get(LOG);
if (logObject == null) {
logObject = LoggerFactory.getLogger(getLoggerName());
} else if (!(logObject instanceof Logger)) {
throw fail(LOG, "Wrong type");
}
bindings.put(LOG, logObject);
// copy non-base variables
for (Map.Entry<String, Object> entry : slingBindings.entrySet()) {
if (!bindings.containsKey(entry.getKey())) {
bindings.put(entry.getKey(), entry.getValue());
}
}
if (!bindingsValuesProviders.isEmpty()) {
Set<String> protectedKeys = new HashSet<>();
protectedKeys.addAll(PROTECTED_KEYS);
ProtectedBindings protectedBindings = new ProtectedBindings(bindings, protectedKeys);
long inclusionStart = System.nanoTime();
for (BindingsValuesProvider provider : bindingsValuesProviders) {
long start = System.nanoTime();
provider.addBindings(protectedBindings);
long stop = System.nanoTime();
LOGGER.trace("Invoking addBindings() of {} took {} nanoseconds",
provider.getClass().getName(), stop-start);
if (stop-start > WARN_LIMIT_FOR_BVP_NANOS) {
// SLING-11182 - make this work with older implementations of the Sling API
if (request != null && request.getRequestProgressTracker() != null) {
request.getRequestProgressTracker().log(String.format(BINDINGS_THRESHOLD_MESSAGE, provider.getClass().getName(), (stop-start)/1000, WARN_LIMIT_FOR_BVP_NANOS/1000));
} else {
if (LOGGER.isInfoEnabled()) {
LOGGER.info(String.format(BINDINGS_THRESHOLD_MESSAGE, provider.getClass().getName(), (stop-start)/1000,
WARN_LIMIT_FOR_BVP_NANOS/1000));
}
}
}
}
// SLING-11182 - make this work with older implementations of the Sling API
if (request != null && request.getRequestProgressTracker() != null) {
long duration = (System.nanoTime() - inclusionStart) / 1000;
request.getRequestProgressTracker().log("Adding bindings took " + duration + " microseconds");
}
}
return bindings;
}
private ScriptEvaluationException fail(String variableName, String message) {
return new ScriptEvaluationException(this.scriptName, variableName + ": "
+ message);
}
private String getLoggerName() {
String name = scriptName;
name = name.substring(1); // cut-off leading slash
name = name.replace('.', '$'); // extension separator as part of name
name = name.replace('/', '.'); // hierarchy defined by dot
return name;
}
/**
* Logs the error caused by executing the script in the request progress
* tracker.
*/
private void logScriptError(SlingHttpServletRequest request,
Throwable throwable) {
String message = throwable.getMessage();
if (message != null) {
message = throwable.getMessage().replace('\n', '/');
} else {
message = throwable.toString();
}
request.getRequestProgressTracker().log("SCRIPT ERROR: {0}", message);
}
/**
* Input stream wrapper which acquires the underlying input stream lazily.
* This ensures that the input stream is only fetched from the repository
* if it is really used by the script engines.
*/
public static final class LazyInputStream extends InputStream {
/** The script resource which is adapted to an inputm stream. */
private final Resource resource;
/** The input stream created on demand, null if not used */
private InputStream delegatee;
public LazyInputStream(final Resource resource) {
this.resource = resource;
}
/**
* Closes the input stream if acquired otherwise does nothing.
*/
@Override
public void close() throws IOException {
if (delegatee != null) {
delegatee.close();
}
}
@Override
public int available() throws IOException {
return getStream().available();
}
@Override
public int read() throws IOException {
return getStream().read();
}
@Override
public int read(byte[] b) throws IOException {
return getStream().read(b);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
return getStream().read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
return getStream().skip(n);
}
@Override
public boolean markSupported() {
try {
return getStream().markSupported();
} catch (IOException ioe) {
// ignore
}
return false;
}
@Override
public synchronized void mark(int readlimit) {
try {
getStream().mark(readlimit);
} catch (IOException ioe) {
// ignore
}
}
@Override
public synchronized void reset() throws IOException {
getStream().reset();
}
/** Actually retrieves the input stream from the underlying JCR Value */
private InputStream getStream() throws IOException {
if (delegatee == null) {
delegatee = this.resource.adaptTo(InputStream.class);
if (delegatee == null) {
throw new IOException("Cannot get a stream to the script resource "
+ this.resource.getPath());
}
}
return delegatee;
}
}
/**
* This is a lazy implementation of the script resource which
* just returns the path, resource type and resource resolver directly.
*/
private static final class LazyScriptResource extends ResourceWrapper {
private final String path;
private final String resourceType;
private final ResourceResolver resolver;
private Resource delegatee;
public LazyScriptResource(final String path, final String resourceType, final ResourceResolver resolver) {
super(null); // NOSONAR
this.path = path;
this.resourceType = resourceType;
this.resolver = resolver;
}
@Override
public Resource getResource() {
if (this.delegatee == null) {
this.delegatee = this.resolver.getResource(this.path);
if (this.delegatee == null) {
this.delegatee = new SyntheticResource(resolver, this.path,
this.resourceType);
}
}
return this.delegatee;
}
/**
* @see org.apache.sling.api.resource.Resource#getPath()
*/
@Override
public String getPath() {
return this.path;
}
/**
* @see org.apache.sling.api.resource.Resource#getResourceType()
*/
@Override
public String getResourceType() {
return this.resourceType;
}
/**
* @see org.apache.sling.api.resource.Resource#getResourceResolver()
*/
@Override
public ResourceResolver getResourceResolver() {
return this.resolver;
}
}
}