Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/UnitLoader.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/UnitLoader.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/UnitLoader.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/UnitLoader.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,448 @@ +/******************************************************************************* + * 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.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import javax.script.Bindings; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringEscapeUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingConstants; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.resource.LoginException; +import org.apache.sling.api.resource.PersistenceException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.scripting.sightly.SightlyException; +import org.apache.sling.scripting.sightly.impl.compiled.CompilationOutput; +import org.apache.sling.scripting.sightly.impl.compiled.JavaClassBackend; +import org.apache.sling.scripting.sightly.impl.compiler.SightlyJavaCompilerService; +import org.apache.sling.scripting.sightly.impl.compiler.SightlyParsingException; +import org.apache.sling.scripting.sightly.impl.compiler.SightlyCompilerService; +import org.apache.sling.scripting.sightly.impl.compiler.util.GlobalShadowCheckBackend; +import org.apache.sling.scripting.sightly.impl.engine.compiled.JavaClassTemplate; +import org.apache.sling.scripting.sightly.impl.engine.compiled.SourceIdentifier; +import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl; +import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderUnit; +import org.apache.sling.scripting.sightly.impl.engine.runtime.SightlyRenderException; +import org.apache.sling.settings.SlingSettingsService; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventConstants; +import org.osgi.service.event.EventHandler; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Create rendering units from resources. + */ +@Component +@Service({UnitLoader.class, EventHandler.class}) +@Properties({ + @Property( + name = EventConstants.EVENT_TOPIC, + value = {SlingConstants.TOPIC_RESOURCE_ADDED, SlingConstants.TOPIC_RESOURCE_CHANGED, SlingConstants.TOPIC_RESOURCE_REMOVED} + ), + @Property( + name = EventConstants.EVENT_FILTER, + value = "(|(" + SlingConstants.PROPERTY_PATH + "=/apps/**/*." + SightlyScriptEngineFactory.EXTENSION + ")(" + + SlingConstants.PROPERTY_PATH + "=/libs/**/*." + SightlyScriptEngineFactory.EXTENSION + "))" + ) +}) +public class UnitLoader implements EventHandler { + + public static final String DEFAULT_REPO_BASE_PATH = "/var/classes"; + private static final Logger log = LoggerFactory.getLogger(UnitLoader.class); + private static final String CLASS_NAME_PREFIX = "SightlyJava_"; + private static final String MAIN_TEMPLATE_PATH = "templates/compiled_unit_template.txt"; + private static final String CHILD_TEMPLATE_PATH = "templates/subtemplate.txt"; + + private static final String NT_FOLDER = "nt:folder"; + private static final String NT_FILE = "nt:file"; + private static final String NT_RESOURCE = "nt:resource"; + private static final String JCR_PRIMARY_TYPE = "jcr:primaryType"; + private static final String JCR_CONTENT = "jcr:content"; + private static final String JCR_DATA = "jcr:data"; + private static final String JCR_LASTMODIFIED = "jcr:lastModified"; + private static final String JCR_ENCODING = "jcr:encoding"; + + private String mainTemplate; + private String childTemplate; + private Map<String, Long> slyScriptsMap = new ConcurrentHashMap<String, Long>(); + + private final Map<String, Lock> activeWrites = new HashMap<String, Lock>(); + + @Reference + private SightlyCompilerService sightlyCompilerService = null; + + @Reference + private SightlyJavaCompilerService sightlyJavaCompilerService = null; + + @Reference + private SightlyEngineConfiguration sightlyEngineConfiguration = null; + + @Reference + private ResourceResolverFactory rrf = null; + + @Reference + private SlingSettingsService slingSettings = null; + + private static long getLastModifiedDate(ResourceResolver resolver, String path) { + try { + Resource ntResource = getNtResource(resolver, path); + if (ntResource != null) { + ValueMap ntResourceProperties = ntResource.adaptTo(ValueMap.class); + /** + * make sure to use 0L for the default value; otherwise we get an Integer + * overflow due to the long value stored in JCR + */ + return ntResourceProperties.get(JCR_LASTMODIFIED, 0L); + } + } catch (Exception e) { + log.error("Error while reading last modification date: ", e); + } + return 0L; + } + + private static Resource getNtResource(ResourceResolver resolver, String path) { + Resource resource = resolver.getResource(path); + if (resource != null) { + if (path.endsWith(JCR_CONTENT) && resource.isResourceType(NT_RESOURCE)) { + return resource; + } else { + Resource ntResource = resource.getChild(JCR_CONTENT); + if (ntResource != null && ntResource.isResourceType(NT_RESOURCE)) { + return ntResource; + } + } + } + return null; + } + + /** + * Create a render unit from the given resource + * + * @param scriptResource the resource + * @param bindings the bindings + * @param renderContext the rendering context + * @return the render unit + */ + public RenderUnit createUnit(Resource scriptResource, Bindings bindings, RenderContextImpl renderContext) { + Lock lock = null; + ResourceResolver adminResolver = null; + try { + SourceIdentifier sourceIdentifier = obtainIdentifier(scriptResource); + adminResolver = rrf.getAdministrativeResourceResolver(null); + Object obj; + ValueMap templateProperties = adminResolver.getResource(scriptResource.getPath()).getChild(JCR_CONTENT).adaptTo(ValueMap.class); + String encoding = templateProperties.get(JCR_ENCODING, sightlyEngineConfiguration.getEncoding()); + SlingHttpServletResponse response = (SlingHttpServletResponse) bindings.get(SlingBindings.RESPONSE); + response.setCharacterEncoding(encoding); + if (needsUpdate(adminResolver, sourceIdentifier)) { + synchronized (activeWrites) { + String sourceFullPath = sourceIdentifier.getSourceFullPath(); + lock = activeWrites.get(sourceFullPath); + if (lock == null) { + lock = new ReentrantLock(); + activeWrites.put(sourceFullPath, lock); + } + lock.lock(); + } + createClass(adminResolver, sourceIdentifier, bindings, encoding, renderContext); + Resource javaClassResource = adminResolver.getResource(sourceIdentifier.getSourceFullPath()); + obj = sightlyJavaCompilerService.compileSource(javaClassResource, sourceIdentifier.getFullyQualifiedName()); + } else { + Resource javaClassResource = adminResolver.getResource(sourceIdentifier.getSourceFullPath()); + obj = sightlyJavaCompilerService.getInstance(javaClassResource, sourceIdentifier.getFullyQualifiedName()); + } + if (!(obj instanceof RenderUnit)) { + throw new SightlyRenderException("Class is not a RenderUnit instance"); + } + return (RenderUnit) obj; + } catch (LoginException e) { + throw new SightlyRenderException("Unable to create a RenderUnit.", e); + } finally { + if (adminResolver != null) { + adminResolver.close(); + } + if (lock != null) { + lock.unlock(); + } + } + } + + @Override + public void handleEvent(Event event) { + String path = (String) event.getProperty(SlingConstants.PROPERTY_PATH); + String topic = event.getTopic(); + if (SlingConstants.TOPIC_RESOURCE_ADDED.equals(topic) || SlingConstants.TOPIC_RESOURCE_CHANGED.equals(topic)) { + slyScriptsMap.put(path, Calendar.getInstance().getTimeInMillis()); + } else if (SlingConstants.TOPIC_RESOURCE_REMOVED.equals(topic)) { + slyScriptsMap.remove(path); + } + } + + @Activate + @SuppressWarnings("unused") + protected void activate(ComponentContext componentContext) { + mainTemplate = resourceFile(componentContext, MAIN_TEMPLATE_PATH); + childTemplate = resourceFile(componentContext, CHILD_TEMPLATE_PATH); + String basePath = + DEFAULT_REPO_BASE_PATH + "/" + slingSettings.getSlingId() + "/sightly/"; + ResourceResolver adminResolver = null; + try { + adminResolver = rrf.getAdministrativeResourceResolver(null); + Resource basePathResource; + if ((basePathResource = adminResolver.getResource(basePath)) != null) { + for (Resource resource : basePathResource.getChildren()) { + if (!resource.getName().equals(sightlyEngineConfiguration.getEngineVersion())) { + adminResolver.delete(resource); + } + } + if (adminResolver.hasChanges()) { + adminResolver.commit(); + } + } + } catch (Exception e) { + log.error("Cannot delete stale Sightly Java classes.", e); + } finally { + if (adminResolver != null) { + adminResolver.close(); + } + } + } + + private synchronized void writeSource(ResourceResolver resolver, String sourceFullPath, String source) { + try { + String sourceParentPath = ResourceUtil.getParent(sourceFullPath); + Map<String, Object> sourceFolderProperties = new HashMap<String, Object>(); + sourceFolderProperties.put(JCR_PRIMARY_TYPE, NT_FOLDER); + createResource(resolver, sourceParentPath, sourceFolderProperties, NT_FOLDER, true, false); + + Map<String, Object> sourceFileProperties = new HashMap<String, Object>(); + sourceFileProperties.put(JCR_PRIMARY_TYPE, NT_FILE); + createResource(resolver, sourceFullPath, sourceFileProperties, null, false, false); + + Map<String, Object> ntResourceProperties = new HashMap<String, Object>(); + ntResourceProperties.put(JCR_PRIMARY_TYPE, NT_RESOURCE); + ntResourceProperties.put(JCR_DATA, new ByteArrayInputStream(source.getBytes())); + ntResourceProperties.put(JCR_LASTMODIFIED, Calendar.getInstance()); + createResource(resolver, sourceFullPath + "/" + JCR_CONTENT, ntResourceProperties, NT_RESOURCE, true, true); + log.debug("Successfully written Java source file to repository: {}", sourceFullPath); + } catch (PersistenceException e) { + log.error("Repository error while writing Java source file: " + sourceFullPath, e); + } + } + + private SourceIdentifier obtainIdentifier(Resource resource) { + String basePath = + DEFAULT_REPO_BASE_PATH + "/" + slingSettings.getSlingId() + "/sightly/" + sightlyEngineConfiguration.getEngineVersion(); + return new SourceIdentifier(resource, CLASS_NAME_PREFIX, basePath); + } + + private void createClass(ResourceResolver resolver, SourceIdentifier identifier, Bindings bindings, String encoding, + RenderContextImpl renderContext) { + String scriptSource = null; + try { + Resource scriptResource = resolver.getResource(identifier.getResource().getPath()); + if (scriptResource != null) { + scriptSource = IOUtils.toString(scriptResource.adaptTo(InputStream.class), encoding); + String javaSourceCode = obtainResultSource(scriptSource, identifier, bindings, renderContext); + writeSource(resolver, identifier.getSourceFullPath(), javaSourceCode); + } + } catch (SightlyParsingException e) { + String offendingInput = e.getOffendingInput(); + if (StringUtils.isNotEmpty(offendingInput)) { + offendingInput = StringEscapeUtils.unescapeHtml(offendingInput.trim()); + int errorLine = getLineWhereErrorOccurred(scriptSource, offendingInput); + throw new SightlyParsingException("Parsing error in template " + identifier.getResource().getPath() + " at line " + + errorLine + ":\n" + offendingInput + "\n"); + } else { + throw e; + } + + } catch (IOException e) { + throw new SightlyRenderException(e); + } + } + + private String obtainResultSource(String scriptSource, SourceIdentifier identifier, Bindings bindings, RenderContextImpl renderContext) { + JavaClassTemplate classTemplate = newMainTemplate(); + classTemplate.setClassName(identifier.getClassName()); + classTemplate.setPackageName(identifier.getPackageName()); + CompilationOutput compilationOutput = obtainOutput(scriptSource, bindings, renderContext); + processCompilationResult(compilationOutput, classTemplate); + return classTemplate.toString(); + } + + private CompilationOutput obtainOutput(String source, Bindings bindings, RenderContextImpl renderContext) { + JavaClassBackend backend = new JavaClassBackend(); + sightlyCompilerService.compile(source, new GlobalShadowCheckBackend(backend, bindings.keySet()), renderContext); + return backend.build(); + } + + private void processCompilationResult(CompilationOutput result, JavaClassTemplate mainTemplate) { + mainTemplate.writeMainBody(result.getMainBody()); + for (Map.Entry<String, CompilationOutput> entry : result.getSubTemplates().entrySet()) { + JavaClassTemplate childTemplate = newChildTemplate(); + processCompilationResult(entry.getValue(), childTemplate); + mainTemplate.writeSubTemplate(entry.getKey(), childTemplate.toString()); + } + } + + private JavaClassTemplate newMainTemplate() { + return new JavaClassTemplate(mainTemplate); + } + + private JavaClassTemplate newChildTemplate() { + return new JavaClassTemplate(childTemplate); + } + + private String resourceFile(ComponentContext componentContext, String path) { + InputStream inputStream = null; + try { + URL url = componentContext.getBundleContext().getBundle().getEntry(path); + if (url == null) { + throw new SightlyException("No bundle resource resides at " + path); + } + inputStream = componentContext.getBundleContext().getBundle().getEntry(path).openStream(); + return IOUtils.toString(inputStream); + } catch (IOException e) { + throw new SightlyException("Java class templates could not be found"); + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + log.error("Error when closing bundle resource stream", e); + } + } + } + } + + private int getLineWhereErrorOccurred(String documentFragment, String offendingInput) { + int offendingInputIndex = documentFragment.indexOf(offendingInput); + String textBeforeError = documentFragment.substring(0, offendingInputIndex); + int line = 0; + int newLine = 0; + while (textBeforeError.length() > 0 && newLine != -1) { + newLine = textBeforeError.indexOf("\n"); + if (newLine != -1) { + line++; + textBeforeError = textBeforeError.substring(newLine + 1, textBeforeError.length()); + } + } + return ++line; + } + + private boolean needsUpdate(ResourceResolver resolver, SourceIdentifier sourceIdentifier) { + if (sightlyEngineConfiguration.isDevMode()) { + return true; + } + String javaPath = sourceIdentifier.getSourceFullPath(); + String slyPath = sourceIdentifier.getResource().getPath(); + Long javaFileDate = getLastModifiedDate(resolver, javaPath); + if (javaFileDate != 0) { + + Long slyScriptChangeDate = slyScriptsMap.get(slyPath); + if (slyScriptChangeDate != null) { + if (slyScriptChangeDate < javaFileDate) { + return false; + } + } else { + slyScriptsMap.put(slyPath, Calendar.getInstance().getTimeInMillis()); + } + return true; + } + slyScriptsMap.put(slyPath, Calendar.getInstance().getTimeInMillis()); + return true; + } + + private Resource createResource(ResourceResolver resolver, String path, Map<String, Object> resourceProperties, String intermediateType, + boolean autoCommit, boolean forceOverwrite) throws PersistenceException { + Resource rsrc = resolver.getResource(path); + if (rsrc == null || forceOverwrite) { + final int lastPos = path.lastIndexOf('/'); + final String name = path.substring(lastPos + 1); + + final Resource parentResource; + if (lastPos == 0) { + parentResource = resolver.getResource("/"); + } else { + final String parentPath = path.substring(0, lastPos); + Map<String, Object> parentProperties = new HashMap<String, Object>(); + parentProperties.put(JCR_PRIMARY_TYPE, intermediateType); + parentResource = createResource(resolver, parentPath, parentProperties, intermediateType, autoCommit, false); + } + if (autoCommit) { + resolver.refresh(); + } + if (forceOverwrite) { + Resource resource = resolver.getResource(parentResource, name); + if (resource != null) { + resolver.delete(resource); + } + } + try { + rsrc = resolver.create(parentResource, name, resourceProperties); + if (autoCommit) { + resolver.commit(); + resolver.refresh(); + rsrc = resolver.getResource(parentResource, name); + } + } catch (PersistenceException pe) { + resolver.revert(); + resolver.refresh(); + rsrc = resolver.getResource(parentResource, name); + if (rsrc == null) { + rsrc = resolver.create(parentResource, name, resourceProperties); + } + } finally { + if (autoCommit) { + resolver.commit(); + resolver.refresh(); + rsrc = resolver.getResource(parentResource, name); + } + } + } + return rsrc; + } + +}
Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/UnitLoader.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/JavaClassTemplate.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/JavaClassTemplate.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/JavaClassTemplate.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/JavaClassTemplate.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,70 @@ +/******************************************************************************* + * 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; + +/** + * Template for generated Java classes. + */ +public class JavaClassTemplate { + + + private String classTemplate; + + private static final String MAIN_BODY = "MainBody"; + private static final String CLASS_NAME = "ClassName"; + private static final String PACKAGE_NAME = "PackageName"; + private static final String TEMPLATE_INIT = "SubTemplateMapInit"; + private static final String NAME = "Name"; + + private StringBuilder templateInitBuilder = new StringBuilder(); + + public JavaClassTemplate(String template) { + this.classTemplate = template; + } + + public void writeMainBody(String content) { + setPart(MAIN_BODY, content); + } + + public void writeSubTemplate(String name, String content) { + templateInitBuilder.append(insertPart(NAME, content, name)); + } + + public void setClassName(String name) { + setPart(CLASS_NAME, name); + } + + public void setPackageName(String name) { + setPart(PACKAGE_NAME, name); + } + + @Override + public String toString() { + return insertPart(TEMPLATE_INIT, classTemplate, templateInitBuilder.toString()); + } + + private void setPart(String partName, String content) { + classTemplate = insertPart(partName, classTemplate, content); + } + + private String insertPart(String partName, String original, String content) { + String id = "##" + partName + "##"; + return original.replace(id, content); + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/JavaClassTemplate.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,105 @@ +/******************************************************************************* + * 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.commons.lang.StringUtils; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; + +/** + * Identifies a Java source file in a JCR repository. + */ +public class SourceIdentifier { + + private final String className; + private final Resource resource; + private final String packageName; + private final String sourceFileName; + private final String sourceFullPath; + private final String fullyQualifiedName; + + public SourceIdentifier(Resource resource, String classNamePrefix, String basePath) { + this.resource = resource; + this.className = buildClassName(resource, classNamePrefix); + this.packageName = buildPackageName(resource); + this.sourceFileName = buildSourceFileName(this.className); + this.sourceFullPath = buildSourceFullPath(resource, basePath, this.sourceFileName); + this.fullyQualifiedName = buildFullyQualifiedName(packageName, className); + } + + public String getClassName() { + return className; + } + + public Resource getResource() { + return resource; + } + + public String getPackageName() { + return packageName; + } + + public String getSourceFileName() { + return sourceFileName; + } + + public String getSourceFullPath() { + return sourceFullPath; + } + + public String getFullyQualifiedName() { + return fullyQualifiedName; + } + + private String buildFullyQualifiedName(String packageName, String className) { + return packageName + "." + className; + } + + private String buildClassName(Resource resource, String classNamePrefix) { + String scriptName = ResourceUtil.getName(resource.getPath()); + scriptName = scriptName.substring(0, scriptName.lastIndexOf(getExtension(scriptName))); + String className = classNamePrefix + scriptName; + return className.replaceAll("-", "_").replaceAll("\\.", "_"); + } + + private String buildPackageName(Resource resource) { + return ResourceUtil.getParent(resource.getPath()) + .replaceAll("/", ".") + .substring(1) + .replaceAll("-", "_"); + } + + private String buildSourceFileName(String className) { + return className + ".java"; + } + + private String buildSourceFullPath(Resource resource, String basePath, String sourceFileName) { + String sourceParentPath = basePath + ResourceUtil.getParent(resource.getPath()); + return sourceParentPath + "/" + sourceFileName; + } + + private String getExtension(String scriptName) { + if (StringUtils.isEmpty(scriptName)) { + return null; + } + int lastDotIndex = scriptName.lastIndexOf('.'); + return scriptName.substring(lastDotIndex); + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/compiled/SourceIdentifier.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ExtensionUtils.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ExtensionUtils.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ExtensionUtils.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ExtensionUtils.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,43 @@ +/******************************************************************************* + * 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.extension; + +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.extension.RuntimeExtensionException; + +/** + * Helper class for {@link RuntimeExtension} implementations. + */ +public class ExtensionUtils { + + /** + * Helper method for checking if the number of arguments passed to a {@link RuntimeExtension} are equal to what the extension requires. + * + * @param extensionName the name of the extension + * @param arguments the arguments array + * @param count the expected number or arguments + * @throws RuntimeExtensionException if the number of supplied arguments differs from what's expected + */ + public static void checkArgumentCount(String extensionName, Object[] arguments, int count) { + if (arguments.length != count) { + throw new RuntimeExtensionException(String.format("Extension %s requires %d arguments", extensionName, count)); + } + } + +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ExtensionUtils.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/I18nRuntimeExtension.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/I18nRuntimeExtension.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/I18nRuntimeExtension.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/I18nRuntimeExtension.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,99 @@ +/******************************************************************************* + * 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.extension; + +import java.util.Enumeration; +import java.util.Locale; +import java.util.ResourceBundle; + +import javax.script.Bindings; + +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.i18n.ResourceBundleProvider; +import org.apache.sling.scripting.sightly.extension.ExtensionInstance; +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.impl.filter.I18nFilter; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component +@Service(RuntimeExtension.class) +@Properties({ + @Property(name = RuntimeExtension.SCR_PROP_NAME, value = I18nFilter.FUNCTION) +}) +public class I18nRuntimeExtension implements RuntimeExtension { + + private static final Logger LOG = LoggerFactory.getLogger(I18nRuntimeExtension.class); + + @Override + public ExtensionInstance provide(final RenderContext renderContext) { + + return new ExtensionInstance() { + @Override + public Object call(Object... arguments) { + ExtensionUtils.checkArgumentCount(I18nFilter.FUNCTION, arguments, 3); + String text = renderContext.toString(arguments[0]); + String locale = renderContext.toString(arguments[1]); + String hint = renderContext.toString(arguments[2]); + return get(text, locale, hint); + } + + private String get(String text, String locale, String hint) { + final Bindings bindings = renderContext.getBindings(); + final SlingScriptHelper slingScriptHelper = (SlingScriptHelper) bindings.get(SlingBindings.SLING); + final SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST); + final ResourceBundleProvider resourceBundleProvider = slingScriptHelper.getService(ResourceBundleProvider.class); + if (resourceBundleProvider != null) { + String key = text; + if (StringUtils.isNotEmpty(hint)) { + key += " ((" + hint + "))"; + } + if (StringUtils.isEmpty(locale)) { + Enumeration<Locale> requestLocales = request.getLocales(); + while (requestLocales.hasMoreElements()) { + Locale l = requestLocales.nextElement(); + ResourceBundle resourceBundle = resourceBundleProvider.getResourceBundle(l); + if (resourceBundle != null && resourceBundle.containsKey(key)) { + return resourceBundle.getString(key); + } + } + } else { + Locale l = new Locale(locale); + ResourceBundle resourceBundle = resourceBundleProvider.getResourceBundle(l); + if (resourceBundle != null && resourceBundle.containsKey(key)) { + return resourceBundle.getString(key); + } + } + } + LOG.warn("No translation found for string '{}' using expression provided locale '{}' or default locale '{}'", + new String[] {text, locale, resourceBundleProvider.getDefaultLocale().getLanguage()}); + return text; + } + + }; + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/I18nRuntimeExtension.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/IncludeRuntimeExtension.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/IncludeRuntimeExtension.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/IncludeRuntimeExtension.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/IncludeRuntimeExtension.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,131 @@ +/******************************************************************************* + * 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.extension; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Map; + +import javax.script.Bindings; +import javax.servlet.Servlet; + +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.api.servlets.ServletResolver; +import org.apache.sling.scripting.sightly.extension.ExtensionInstance; +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.impl.engine.runtime.SightlyRenderException; +import org.apache.sling.scripting.sightly.impl.plugin.IncludePlugin; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Component +@Service(RuntimeExtension.class) +@Properties({ + @Property(name = RuntimeExtension.SCR_PROP_NAME, value = IncludePlugin.FUNCTION) +}) +@SuppressWarnings("unused") +/** + * Runtime support for including resources in a Sightly script through {@code data-sly-include}. For more details check the implementation + * of the {@link org.apache.sling.scripting.sightly.impl.plugin.IncludePlugin}. + */ +public class IncludeRuntimeExtension implements RuntimeExtension { + + private static final Logger LOG = LoggerFactory.getLogger(IncludeRuntimeExtension.class); + private static final String OPTION_FILE = "file"; + private static final String OPTION_PREPEND_PATH = "prependPath"; + private static final String OPTION_APPEND_PATH = "appendPath"; + + + @Override + public ExtensionInstance provide(final RenderContext renderContext) { + + return new ExtensionInstance() { + + private final Bindings bindings = renderContext.getBindings(); + + @Override + public Object call(Object... arguments) { + ExtensionUtils.checkArgumentCount(IncludePlugin.FUNCTION, arguments, 2); + String originalPath = renderContext.toString(arguments[0]); + Map options = (Map) arguments[1]; + String path = buildPath(originalPath, options); + if (path == null) { + throw new SightlyRenderException("Path for include is empty"); + } + StringWriter output = new StringWriter(); + includeScript(path, new PrintWriter(output)); + return output.toString(); + + } + + private String buildPath(String path, Map options) { + if (StringUtils.isEmpty(path)) { + path = (String) options.get(OPTION_FILE); + } + if (StringUtils.isEmpty(path)) { + return null; + } + String prependPath = (String) options.get(OPTION_PREPEND_PATH); + String appendPath = (String) options.get(OPTION_APPEND_PATH); + if (StringUtils.isNotEmpty(prependPath)) { + path = prependPath + path; + } + if (StringUtils.isNotEmpty(appendPath)) { + path = path + appendPath; + } + return path; + } + + private void includeScript(String script, PrintWriter out) { + if (StringUtils.isEmpty(script)) { + LOG.error("Script path cannot be empty"); + } else { + SlingScriptHelper slingScriptHelper = (SlingScriptHelper) bindings.get(SlingBindings.SLING); + ServletResolver servletResolver = slingScriptHelper.getService(ServletResolver.class); + if (servletResolver != null) { + SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST); + Servlet servlet = servletResolver.resolveServlet(request.getResource(), script); + if (servlet != null) { + try { + SlingHttpServletResponse response = (SlingHttpServletResponse) bindings.get(SlingBindings.RESPONSE); + PrintWriterResponseWrapper resWrapper = new PrintWriterResponseWrapper(out, response); + servlet.service(request, resWrapper); + } catch (Exception e) { + LOG.error("Failed to include script {}", script, e); + } + } else { + LOG.error("Failed to locate script {}", script); + } + } else { + LOG.error("Sling ServletResolver service is unavailable, failed to include {}", script); + } + } + } + }; + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/IncludeRuntimeExtension.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/PrintWriterResponseWrapper.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/PrintWriterResponseWrapper.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/PrintWriterResponseWrapper.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/PrintWriterResponseWrapper.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,50 @@ +/******************************************************************************* + * 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.extension; + +import java.io.IOException; +import java.io.PrintWriter; + +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper; + +/** + * Wrapper response to redirect the output into a specified print writer + */ +public class PrintWriterResponseWrapper extends SlingHttpServletResponseWrapper { + + private final PrintWriter writer; + + /** + * Create a wrapper for the supplied wrappedRequest + * + * @param writer - the base writer + * @param wrappedResponse - the wrapped response + */ + public PrintWriterResponseWrapper(PrintWriter writer, SlingHttpServletResponse wrappedResponse) { + super(wrappedResponse); + this.writer = writer; + } + + @Override + public PrintWriter getWriter() throws IOException { + return writer; + } + +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/PrintWriterResponseWrapper.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ResourceRuntimeExtension.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ResourceRuntimeExtension.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ResourceRuntimeExtension.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ResourceRuntimeExtension.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,241 @@ +/******************************************************************************* + * 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.extension; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Map; + +import javax.script.Bindings; +import javax.servlet.RequestDispatcher; + +import org.apache.commons.lang.StringUtils; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.SlingHttpServletResponse; +import org.apache.sling.api.request.RequestDispatcherOptions; +import org.apache.sling.api.resource.NonExistingResource; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.scripting.sightly.extension.ExtensionInstance; +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.impl.plugin.ResourcePlugin; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runtime support for including resources in a Sightly script through {@code data-sly-resource}. For more details check the implementation + * of the {@link org.apache.sling.scripting.sightly.impl.plugin.ResourcePlugin}. + */ +@Component +@Service(RuntimeExtension.class) +@Properties( + @Property(name = RuntimeExtension.SCR_PROP_NAME, value = ResourcePlugin.FUNCTION) +) +@SuppressWarnings("unused") +public class ResourceRuntimeExtension implements RuntimeExtension { + + private static final Logger LOG = LoggerFactory.getLogger(ResourceRuntimeExtension.class); + private static final String OPTION_RESOURCE_TYPE = "resourceType"; + private static final String OPTION_PATH = "path"; + private static final String OPTION_PREPEND_PATH = "prependPath"; + private static final String OPTION_APPEND_PATH = "appendPath"; + private static final String OPTION_SELECTORS = "selectors"; + private static final String OPTION_REMOVE_SELECTORS = "removeSelectors"; + private static final String OPTION_ADD_SELECTORS = "addSelectors"; + private static final String OPTION_REPLACE_SELECTORS = "replaceSelectors"; + + @Override + public ExtensionInstance provide(final RenderContext renderContext) { + return new ExtensionInstance() { + + private final Bindings bindings = renderContext.getBindings(); + + @Override + @SuppressWarnings("unchecked") + public Object call(Object... arguments) { + ExtensionUtils.checkArgumentCount(ResourcePlugin.FUNCTION, arguments, 2); + return provideResource(arguments[0], (Map<String, Object>) arguments[1]); + } + + private String provideResource(Object pathObj, Map<String, Object> options) { + Map<String, Object> opts = new HashMap<String, Object>(options); + String path = buildPath(pathObj, opts); + String resourceType = getAndRemoveOption(opts, OPTION_RESOURCE_TYPE); + handleSelectors(path, opts); + String dispatcherOptions = createDispatcherOptions(opts); + StringWriter writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + includeResource(printWriter, path, dispatcherOptions, resourceType); + return writer.toString(); + } + + private void handleSelectors(String path, Map<String, Object> options) { + String selectors = getAndRemoveOption(options, OPTION_SELECTORS); + if (StringUtils.isNotEmpty(selectors)) { + // handle the selectors option + options.put(OPTION_ADD_SELECTORS, selectors); + options.put(OPTION_REPLACE_SELECTORS, " "); + } else { + if (options.containsKey(OPTION_REMOVE_SELECTORS)) { + String removeSelectors = getAndRemoveOption(options, OPTION_REMOVE_SELECTORS); + if (StringUtils.isEmpty(removeSelectors)) { + options.put(OPTION_REPLACE_SELECTORS, " "); + } else { + String currentSelectors = getSelectorsFromPath(path); + if (StringUtils.isEmpty(currentSelectors)) { + currentSelectors = ((SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST)).getRequestPathInfo() + .getSelectorString(); + } + if (StringUtils.isNotEmpty(currentSelectors)) { + options.put(OPTION_REPLACE_SELECTORS, " "); + String addSelectors = currentSelectors.replace(removeSelectors, "").replaceAll("\\.\\.", "\\."); + if (addSelectors.startsWith(".")) { + addSelectors = addSelectors.substring(1); + } + if (addSelectors.endsWith(".")) { + addSelectors = addSelectors.substring(0, addSelectors.length() - 1); + } + options.put(OPTION_ADD_SELECTORS, addSelectors); + } + } + } + } + } + + private String buildPath(Object pathObj, Map<String, Object> options) { + String path = coerceString(pathObj); + String prependPath = getAndRemoveOption(options, OPTION_PREPEND_PATH); + String appendPath = getAndRemoveOption(options, OPTION_APPEND_PATH); + if (StringUtils.isEmpty(path)) { + path = getOption(options, OPTION_PATH); + } + if (StringUtils.isNotEmpty(prependPath)) { + path = prependPath + "/" + path; + } + if (StringUtils.isNotEmpty(appendPath)) { + path = path + "/" + appendPath; + } + + return path; + } + + private String createDispatcherOptions(Map<String, Object> options) { + if (options == null || options.isEmpty()) { + return null; + } + StringBuilder buffer = new StringBuilder(); + boolean hasPreceding = false; + for (Map.Entry<String, Object> option : options.entrySet()) { + if (hasPreceding) { + buffer.append(", "); + } + String key = option.getKey(); + buffer.append(key).append("="); + String strVal = coerceString(option.getValue()); + if (strVal == null) { + strVal = ""; + } + buffer.append(strVal); + hasPreceding = true; + } + return buffer.toString(); + } + + private String coerceString(Object obj) { + if (obj instanceof String) { + return (String) obj; + } + return null; + } + + private String getOption(Map<String, Object> options, String property) { + return (String) options.get(property); + } + + private String getAndRemoveOption(Map<String, Object> options, String property) { + return (String) options.remove(property); + } + + private String getSelectorsFromPath(String path) { + String filePath; + if (path.contains("/")) { + filePath = path.substring(path.lastIndexOf('/') + 1, path.length()); + } else { + filePath = path; + } + String[] parts = filePath.split("\\."); + if (parts.length > 2) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < parts.length - 1; i++) { + sb.append(parts[i]); + if (i != parts.length - 2) { + sb.append("."); + } + } + if (sb.length() > 0) { + return sb.toString(); + } + } + return null; + } + + private void includeResource(PrintWriter out, String script, String dispatcherOptions, String resourceType) { + if (StringUtils.isEmpty(script)) { + LOG.error("Script path cannot be empty"); + } else { + SlingHttpServletResponse customResponse = new PrintWriterResponseWrapper(out, + (SlingHttpServletResponse) bindings.get(SlingBindings.RESPONSE)); + SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST); + script = normalizePath(request, script); + + Resource includeRes = request.getResourceResolver().resolve(script); + if (includeRes instanceof NonExistingResource || includeRes.isResourceType(Resource.RESOURCE_TYPE_NON_EXISTING)) { + includeRes = new SyntheticResource(request.getResourceResolver(), script, resourceType); + } + try { + RequestDispatcherOptions opts = new RequestDispatcherOptions(dispatcherOptions); + if (StringUtils.isNotEmpty(resourceType)) { + opts.setForceResourceType(resourceType); + } + RequestDispatcher dispatcher = request.getRequestDispatcher(includeRes, opts); + dispatcher.include(request, customResponse); + } catch (Exception e) { + LOG.error("Failed to include resource {}", script, e); + } + } + } + + private String normalizePath(SlingHttpServletRequest request, String path) { + if (!path.startsWith("/")) { + path = request.getResource().getPath() + "/" + path; + } + return ResourceUtil.normalize(path); + } + + }; + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/ResourceRuntimeExtension.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/XSSRuntimeExtension.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/XSSRuntimeExtension.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/XSSRuntimeExtension.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/XSSRuntimeExtension.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,239 @@ +/******************************************************************************* + * 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.extension; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +import javax.script.Bindings; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.scripting.sightly.extension.ExtensionInstance; +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.extension.RuntimeExtensionException; +import org.apache.sling.scripting.sightly.impl.compiler.CompilerException; +import org.apache.sling.scripting.sightly.impl.filter.XSSFilter; +import org.apache.sling.scripting.sightly.impl.html.MarkupUtils; +import org.apache.sling.scripting.sightly.impl.plugin.MarkupContext; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.apache.sling.xss.XSSAPI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Runtime support for XSS filtering + */ +@Component +@Service(RuntimeExtension.class) +@Properties( + @Property(name = RuntimeExtension.SCR_PROP_NAME, value = XSSFilter.FUNCTION_NAME) +) +public class XSSRuntimeExtension implements RuntimeExtension { + + private static final Set<String> elementNameWhiteList = new HashSet<String>(); + private static final Logger LOG = LoggerFactory.getLogger(XSSRuntimeExtension.class); + private static final Pattern VALID_ATTRIBUTE = Pattern.compile("^[a-zA-Z_:][\\-a-zA-Z0-9_:\\.]*$"); + + @Override + public ExtensionInstance provide(final RenderContext renderContext) { + + final XSSAPI xssapi = obtainAPI(renderContext.getBindings()); + + return new ExtensionInstance() { + @Override + public Object call(Object... arguments) { + if (arguments.length < 2) { + throw new RuntimeExtensionException( + String.format("Extension %s requires at least %d arguments", XSSFilter.FUNCTION_NAME, 2)); + } + Object original = arguments[0]; + Object option = arguments[1]; + Object hint = null; + if (arguments.length >= 3) { + hint = arguments[2]; + } + MarkupContext markupContext = null; + if (option != null && option instanceof String) { + String name = (String) option; + markupContext = MarkupContext.lookup(name); + } + if (markupContext == MarkupContext.UNSAFE) { + return original; + } + if (markupContext == null) { + LOG.warn("Expression context {} is invalid, expression will be replaced by the empty string", option); + return ""; + } + String text = renderContext.toString(original); + return applyXSSFilter(text, hint, markupContext); + } + + private String applyXSSFilter(String text, Object hint, MarkupContext xssContext) { + if (xssContext.equals(MarkupContext.ATTRIBUTE) && hint instanceof String) { + String attributeName = (String) hint; + MarkupContext attrMarkupContext = getAttributeMarkupContext(attributeName); + return applyXSSFilter(text, attrMarkupContext); + } + return applyXSSFilter(text, xssContext); + } + + private String applyXSSFilter(String text, MarkupContext xssContext) { + switch (xssContext) { + case ATTRIBUTE: + return xssapi.encodeForHTMLAttr(text); + case COMMENT: + case TEXT: + return xssapi.encodeForHTML(text); + case ATTRIBUTE_NAME: + return escapeAttributeName(text); + case NUMBER: + return xssapi.getValidLong(text, 0).toString(); + case URI: + return xssapi.getValidHref(text); + case SCRIPT_TOKEN: + case SCRIPT_COMMENT: + return xssapi.getValidJSToken(text, ""); + case STYLE_TOKEN: + return xssapi.getValidStyleToken(text, ""); + case SCRIPT_STRING: + return xssapi.encodeForJSString(text); + case ELEMENT_NAME: + return escapeElementName(text); + case HTML: + return xssapi.filterHTML(text); + } + return text; //todo: apply the rest of XSS filters + } + }; + } + + private String escapeElementName(String original) { + original = original.trim(); + if (elementNameWhiteList.contains(original.toLowerCase())) { + return original; + } + return ""; + } + + private XSSAPI obtainAPI(Bindings bindings) { + SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get("request"); + if (request == null) { + throw new CompilerException("Cannot obtain request from bindings"); + } + return request.adaptTo(XSSAPI.class); + } + + private MarkupContext getAttributeMarkupContext(String attributeName) { + if ("src".equalsIgnoreCase(attributeName) || "href".equalsIgnoreCase(attributeName)) { + return MarkupContext.URI; + } + return MarkupContext.ATTRIBUTE; + } + + private String escapeAttributeName(String attributeName) { + if (attributeName == null) { + return null; + } + attributeName = attributeName.trim(); + if (matchPattern(VALID_ATTRIBUTE, attributeName) && !MarkupUtils.isSensitiveAttribute(attributeName)) { + return attributeName; + } + return null; + } + + private boolean matchPattern(Pattern pattern, String str) { + return pattern.matcher(str).matches(); + } + + static { + elementNameWhiteList.add("section"); + elementNameWhiteList.add("nav"); + elementNameWhiteList.add("article"); + elementNameWhiteList.add("aside"); + elementNameWhiteList.add("h1"); + elementNameWhiteList.add("h2"); + elementNameWhiteList.add("h3"); + elementNameWhiteList.add("h4"); + elementNameWhiteList.add("h5"); + elementNameWhiteList.add("h6"); + elementNameWhiteList.add("header"); + elementNameWhiteList.add("footer"); + elementNameWhiteList.add("address"); + elementNameWhiteList.add("main"); + elementNameWhiteList.add("p"); + elementNameWhiteList.add("pre"); + elementNameWhiteList.add("blockquote"); + elementNameWhiteList.add("ol"); + elementNameWhiteList.add("li"); + elementNameWhiteList.add("dl"); + elementNameWhiteList.add("dt"); + elementNameWhiteList.add("dd"); + elementNameWhiteList.add("figure"); + elementNameWhiteList.add("figcaption"); + elementNameWhiteList.add("div"); + elementNameWhiteList.add("a"); + elementNameWhiteList.add("em"); + elementNameWhiteList.add("strong"); + elementNameWhiteList.add("small"); + elementNameWhiteList.add("s"); + elementNameWhiteList.add("cite"); + elementNameWhiteList.add("q"); + elementNameWhiteList.add("dfn"); + elementNameWhiteList.add("abbbr"); + elementNameWhiteList.add("data"); + elementNameWhiteList.add("time"); + elementNameWhiteList.add("code"); + elementNameWhiteList.add("var"); + elementNameWhiteList.add("samp"); + elementNameWhiteList.add("kbd"); + elementNameWhiteList.add("sub"); + elementNameWhiteList.add("sup"); + elementNameWhiteList.add("i"); + elementNameWhiteList.add("b"); + elementNameWhiteList.add("u"); + elementNameWhiteList.add("mark"); + elementNameWhiteList.add("ruby"); + elementNameWhiteList.add("rt"); + elementNameWhiteList.add("rp"); + elementNameWhiteList.add("bdi"); + elementNameWhiteList.add("bdo"); + elementNameWhiteList.add("span"); + elementNameWhiteList.add("br"); + elementNameWhiteList.add("wbr"); + elementNameWhiteList.add("ins"); + elementNameWhiteList.add("del"); + elementNameWhiteList.add("table"); + elementNameWhiteList.add("caption"); + elementNameWhiteList.add("colgroup"); + elementNameWhiteList.add("col"); + elementNameWhiteList.add("tbody"); + elementNameWhiteList.add("thead"); + elementNameWhiteList.add("tfoot"); + elementNameWhiteList.add("tr"); + elementNameWhiteList.add("td"); + elementNameWhiteList.add("th"); + } + +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/XSSRuntimeExtension.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/ClassUseProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/ClassUseProvider.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/ClassUseProvider.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/ClassUseProvider.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,120 @@ +/******************************************************************************* + * 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.extension.use; + +import java.util.HashMap; +import java.util.Map; + +import javax.script.Bindings; +import javax.servlet.ServletRequest; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.scripting.SlingBindings; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.commons.classloader.DynamicClassLoaderManager; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.apache.sling.scripting.sightly.use.ProviderOutcome; +import org.apache.sling.scripting.sightly.use.UseProvider; +import org.osgi.framework.Constants; + +/** + * Interprets the identifier as a class name and tries to load that class + * using a dynamic class loader + */ +@Component( + metatype = true, + label = "Apache Sling Scripting Sightly Class Use Provider", + description = "The Class Use Provider is responsible for instantiating Use-API objects that are adaptable from Resource or " + + "SlingHttpServletRequest." +) +@Service(UseProvider.class) +@Properties({ + @Property( + name = Constants.SERVICE_RANKING, + label = "Service Ranking", + description = "The Service Ranking value acts as the priority with which this Use Provider is queried to return an " + + "Use-object. A higher value represents a higher priority.", + intValue = 80, + propertyPrivate = false + ) +}) +public class ClassUseProvider implements UseProvider { + + @Override + public ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments) { + Bindings globalBindings = renderContext.getBindings(); + Bindings bindings = UseProviderUtils.merge(globalBindings, arguments); + SlingScriptHelper sling = (SlingScriptHelper) bindings.get(SlingBindings.SLING); + Resource resource = (Resource) bindings.get(SlingBindings.RESOURCE); + final SlingHttpServletRequest request = (SlingHttpServletRequest) bindings.get(SlingBindings.REQUEST); + Map<String, Object> overrides = setRequestAttributes(request, arguments); + + DynamicClassLoaderManager classLoaderManager = sling.getService(DynamicClassLoaderManager.class); + Object obj; + try { + Class<?> cls = classLoaderManager.getDynamicClassLoader().loadClass(identifier); + obj = resource.adaptTo(cls); + if (obj == null) { + obj = request.adaptTo(cls); + } + } catch (ClassNotFoundException e) { + obj = null; + } finally { + resetRequestAttribute(request, overrides); + } + return ProviderOutcome.notNullOrFailure(obj); + } + + private Map<String, Object> setRequestAttributes(ServletRequest request, Bindings arguments) { + Map<String, Object> overrides = new HashMap<String, Object>(); + for (Map.Entry<String, Object> entry : arguments.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + Object oldValue = request.getAttribute(key); + if (oldValue != null) { + overrides.put(key, oldValue); + } else { + overrides.put(key, NULL); + } + request.setAttribute(key, value); + } + return overrides; + } + + private void resetRequestAttribute(ServletRequest request, Map<String, Object> overrides) { + for (Map.Entry<String, Object> entry : overrides.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value == NULL) { + request.removeAttribute(key); + } else { + request.setAttribute(key, value); + } + } + } + + private static final Object NULL = new Object(); + +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/ClassUseProvider.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/PojoUseProvider.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/PojoUseProvider.java?rev=1642281&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/PojoUseProvider.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/PojoUseProvider.java Fri Nov 28 10:18:01 2014 @@ -0,0 +1,94 @@ +/******************************************************************************* + * 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.extension.use; + +import javax.script.Bindings; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.api.scripting.SlingScriptHelper; +import org.apache.sling.scripting.sightly.ResourceResolution; +import org.apache.sling.scripting.sightly.impl.compiler.SightlyJavaCompilerService; +import org.apache.sling.scripting.sightly.pojo.Use; +import org.apache.sling.scripting.sightly.render.RenderContext; +import org.apache.sling.scripting.sightly.use.ProviderOutcome; +import org.apache.sling.scripting.sightly.use.UseProvider; +import org.osgi.framework.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provider which instantiates POJOs. + */ +@Component( + metatype = true, + label = "Apache Sling Scripting Sightly POJO Use Provider", + description = "The POJO Use Provider is responsible for instantiating Use-API objects that optionally can implement the org" + + ".apache.sling.scripting.sightly.use.Use interface." +) +@Service(UseProvider.class) +@Properties({ + @Property( + name = Constants.SERVICE_RANKING, + label = "Service Ranking", + description = "The Service Ranking value acts as the priority with which this Use Provider is queried to return an " + + "Use-object. A higher value represents a higher priority.", + intValue = 90, + propertyPrivate = false + ) +}) +public class PojoUseProvider implements UseProvider { + + private final Logger LOG = LoggerFactory.getLogger(PojoUseProvider.class); + + @Reference + private SightlyJavaCompilerService sightlyJavaCompilerService = null; + + @Override + public ProviderOutcome provide(String identifier, RenderContext renderContext, Bindings arguments) { + Bindings globalBindings = renderContext.getBindings(); + Bindings bindings = UseProviderUtils.merge(globalBindings, arguments); + SlingScriptHelper sling = UseProviderUtils.getHelper(bindings); + ResourceResolverFactory rrf = sling.getService(ResourceResolverFactory.class); + ResourceResolver adminResolver = null; + try { + adminResolver = rrf.getAdministrativeResourceResolver(null); + Resource resource = ResourceResolution.resolveComponentForRequest(adminResolver, sling.getRequest()); + Object result = sightlyJavaCompilerService.getInstance(resource, identifier); + if (result instanceof Use) { + ((Use) result).init(bindings); + } + return ProviderOutcome.notNullOrFailure(result); + } catch (Exception e) { + LOG.error(String.format("Can't instantiate %s POJO.", identifier), e); + return ProviderOutcome.failure(); + } finally { + if (adminResolver != null) { + adminResolver.close(); + } + } + } +} Propchange: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/extension/use/PojoUseProvider.java ------------------------------------------------------------------------------ svn:mime-type = text/plain