Author: radu Date: Mon Feb 16 15:00:22 2015 New Revision: 1660132 URL: http://svn.apache.org/r1660132 Log: SLING-4423 - Add support for URI Manipulation options
* added support for URI manipulation options according to the language specification * modified Expression such that object is not immutable (helps when transforming the same expression over and over by filters) * defined and ExpressionContext such that filters can choose to process and expression based on this context Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/PathInfo.java sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/ sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/PathInfoTest.java Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/expression/Expression.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/runtime/RenderContextImpl.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/AttributePlugin.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TextPlugin.java sling/trunk/contrib/scripting/sightly/testing-content/pom.xml sling/trunk/contrib/scripting/sightly/testing-content/src/main/resources/SLING-INF/sightlytck.json sling/trunk/contrib/scripting/sightly/testing/pom.xml Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/expression/Expression.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/expression/Expression.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/expression/Expression.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/expression/Expression.java Mon Feb 16 15:00:22 2015 @@ -44,7 +44,7 @@ public class Expression { * @return - the expression options */ public Map<String, ExpressionNode> getOptions() { - return Collections.unmodifiableMap(options); + return options; } /** @@ -70,7 +70,7 @@ public class Expression { * @param removedOptions the options to be removed * @return a copy where the mention options are no longer present */ - public Expression removeOptions(String ... removedOptions) { + public Expression withRemovedOptions(String... removedOptions) { HashMap<String, ExpressionNode> newOptions = new HashMap<String, ExpressionNode>(options); for (String option : removedOptions) { newOptions.remove(option); @@ -79,6 +79,27 @@ public class Expression { } /** + * Removes the given options from this expression. + * + * @param removedOptions the options to be removed + */ + public void removeOptions(String... removedOptions) { + for (String option : removedOptions) { + options.remove(option); + } + } + + /** + * Removes the given option from this expression. + * + * @param option the option to be removed + * @return the option, or {@code null} if the option doesn't exist + */ + public ExpressionNode removeOption(String option) { + return options.remove(option); + } + + /** * Return a copy, but with the specified node as root * @param node the new root * @return a copy with a new root Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/CompilerContext.java Mon Feb 16 15:00:22 2015 @@ -20,6 +20,7 @@ package org.apache.sling.scripting.sight import org.apache.sling.scripting.sightly.impl.compiler.Syntax; import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; import org.apache.sling.scripting.sightly.impl.plugin.MarkupContext; import org.apache.sling.scripting.sightly.impl.compiler.util.SymbolGenerator; @@ -42,9 +43,9 @@ public class CompilerContext { return symbolGenerator.next(hint); } - public Expression adjustToContext(Expression expression, MarkupContext context) { + public Expression adjustToContext(Expression expression, MarkupContext context, ExpressionContext expressionContext) { if (!expression.getOptions().containsKey(Syntax.CONTEXT_OPTION)) { - return expressionWrapper.adjustToContext(expression, context); + return expressionWrapper.adjustToContext(expression, context, expressionContext); } return expression; } Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/compiler/frontend/ExpressionWrapper.java Mon Feb 16 15:00:22 2015 @@ -23,15 +23,15 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Map; import org.apache.sling.scripting.sightly.impl.compiler.Syntax; -import org.apache.sling.scripting.sightly.impl.filter.Filter; import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression; import org.apache.sling.scripting.sightly.impl.compiler.expression.ExpressionNode; import org.apache.sling.scripting.sightly.impl.compiler.expression.node.BinaryOperation; import org.apache.sling.scripting.sightly.impl.compiler.expression.node.BinaryOperator; import org.apache.sling.scripting.sightly.impl.compiler.expression.node.StringConstant; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; +import org.apache.sling.scripting.sightly.impl.filter.Filter; import org.apache.sling.scripting.sightly.impl.plugin.MarkupContext; /** @@ -48,7 +48,7 @@ public class ExpressionWrapper { Collections.sort(this.filters); } - public Expression transform(Interpolation interpolation, MarkupContext markupContext) { + public Expression transform(Interpolation interpolation, MarkupContext markupContext, ExpressionContext expressionContext) { ArrayList<ExpressionNode> nodes = new ArrayList<ExpressionNode>(); HashMap<String, ExpressionNode> options = new HashMap<String, ExpressionNode>(); for (Fragment fragment : interpolation.getFragments()) { @@ -56,8 +56,8 @@ public class ExpressionWrapper { nodes.add(new StringConstant(fragment.getText())); } else { Expression expression = fragment.getExpression(); + nodes.add(adjustToContext(expression, markupContext, expressionContext).getRoot()); options.putAll(expression.getOptions()); - nodes.add(transformExpr(expression, markupContext).getRoot()); } } ExpressionNode root = join(nodes); @@ -68,51 +68,29 @@ public class ExpressionWrapper { return new Expression(root, options); } - private Expression applyFilters(Expression expression) { + private Expression applyFilters(Expression expression, ExpressionContext expressionContext) { Expression result = expression; for (Filter filter : filters) { - result = filter.apply(result); + result = filter.apply(result, expressionContext); } return result; } - public Expression adjustToContext(Expression expression, MarkupContext markupContext) { - if (expression.containsOption(Syntax.CONTEXT_OPTION)) { - return expression; - } - Map<String, ExpressionNode> opt = addDefaultContext(Collections.<String, ExpressionNode>emptyMap(), markupContext); - Expression result = applyFilters(new Expression(expression.getRoot(), opt)); - return expression.withNode(result.getRoot()); + public Expression adjustToContext(Expression expression, MarkupContext context, ExpressionContext expressionContext) { + if (context != null && !expression.containsOption(Syntax.CONTEXT_OPTION)) { + expression.getOptions().put(Syntax.CONTEXT_OPTION, new StringConstant(context.getName())); + } + return applyFilters(expression, expressionContext); } private ExpressionNode join(List<ExpressionNode> nodes) { if (nodes.isEmpty()) { return StringConstant.EMPTY; } - ExpressionNode root = nodes.get(0); - for (int i = 1; i < nodes.size(); i++) { - ExpressionNode node = nodes.get(i); + ExpressionNode root = nodes.remove(0); + for (ExpressionNode node : nodes) { root = new BinaryOperation(BinaryOperator.CONCATENATE, root, node); } return root; } - - private Expression transformExpr(Expression expression, MarkupContext markupContext) { - expression = addDefaultContext(expression, markupContext); - return applyFilters(expression); - } - - private Expression addDefaultContext(Expression expression, MarkupContext context) { - return new Expression(expression.getRoot(), addDefaultContext(expression.getOptions(), context)); - } - - private Map<String, ExpressionNode> addDefaultContext(Map<String, ExpressionNode> options, MarkupContext context) { - if (context == null || options.containsKey(Syntax.CONTEXT_OPTION)) { - return options; - } - HashMap<String, ExpressionNode> newOptions = new HashMap<String, ExpressionNode>(options); - newOptions.put(Syntax.CONTEXT_OPTION, new StringConstant(context.getName())); - return newOptions; - } - } Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/runtime/RenderContextImpl.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/runtime/RenderContextImpl.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/runtime/RenderContextImpl.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/engine/runtime/RenderContextImpl.java Mon Feb 16 15:00:22 2015 @@ -26,6 +26,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -141,6 +142,14 @@ public class RenderContextImpl implement public Map toMap(Object object) { if (object instanceof Map) { return (Map) object; + } else if (object instanceof Record) { + Map<String, Object> map = new HashMap<String, Object>(); + Record record = (Record) object; + Set<String> properties = record.getPropertyNames(); + for (String property : properties) { + map.put(property, record.getProperty(property)); + } + return map; } return Collections.emptyMap(); } Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java?rev=1660132&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/ExpressionContext.java Mon Feb 16 15:00:22 2015 @@ -0,0 +1,61 @@ +/******************************************************************************* + * 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.filter; + +import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression; + +/** + * Defines a context for the {@link Expression} that will be processed by a {@link Filter}. The context can then be used by filters to + * further enhance the decision mechanism for their processing. + */ +public enum ExpressionContext { + + // Plugin contexts + PLUGIN_DATA_SLY_USE, + PLUGIN_DATA_SLY_TEXT, + PLUGIN_DATA_SLY_ATTRIBUTE, + PLUGIN_DATA_SLY_ELEMENT, + PLUGIN_DATA_SLY_TEST, + PLUGIN_DATA_SLY_LIST, + PLUGIN_DATA_SLY_REPEAT, + PLUGIN_DATA_SLY_INCLUDE, + PLUGIN_DATA_SLY_RESOURCE, + PLUGIN_DATA_SLY_TEMPLATE, + PLUGIN_DATA_SLY_CALL, + PLUGIN_DATA_SLY_UNWRAP, + + // Markup contexts + ELEMENT, + TEXT, + ATTRIBUTE; + + private static final String PLUGIN_PREFIX = "PLUGIN_DATA_SLY_"; + + /** + * Retrieves the context for the plugin specified by {@code pluginName}. + * + * @param pluginName the name of the plugin for which to retrieve the context + * @return the context + * @throws IllegalArgumentException if the plugin identified by {@code pluginName} doesn't have a context associated + */ + public static ExpressionContext getContextForPlugin(String pluginName) { + return valueOf(PLUGIN_PREFIX + pluginName.toUpperCase()); + } + +} Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/Filter.java Mon Feb 16 15:00:22 2015 @@ -33,7 +33,7 @@ public interface Filter extends Comparab * @return a transformed expression. If the filter is not applicable * to the given expression, then the original expression shall be returned */ - Expression apply(Expression expression); + Expression apply(Expression expression, ExpressionContext expressionContext); /** * The priority with which filters are applied. This establishes order between filters. Filters with Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/FormatFilter.java Mon Feb 16 15:00:22 2015 @@ -50,15 +50,16 @@ public class FormatFilter extends Filter private static final Pattern PLACEHOLDER_REGEX = Pattern.compile("\\{\\d}"); @Override - public Expression apply(Expression expression) { + public Expression apply(Expression expression, ExpressionContext expressionContext) { //todo: if the expression is a string constant, we can produce the transformation at //compile time, with no need of a runtime function - if (!expression.containsOption(FORMAT_OPTION)) { + if (!expression.containsOption(FORMAT_OPTION) || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext + == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) { return expression; } ExpressionNode argNode = expression.getOption(FORMAT_OPTION); ExpressionNode formattedNode = new RuntimeCall(FORMAT_FUNCTION, expression.getRoot(), argNode); - return expression.withNode(formattedNode).removeOptions(FORMAT_OPTION); + return expression.withNode(formattedNode).withRemovedOptions(FORMAT_OPTION); } @Override Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/I18nFilter.java Mon Feb 16 15:00:22 2015 @@ -42,14 +42,15 @@ public class I18nFilter extends FilterCo public static final String LOCALE_OPTION = "locale"; @Override - public Expression apply(Expression expression) { - if (!expression.containsOption(I18N_OPTION)) { + public Expression apply(Expression expression, ExpressionContext expressionContext) { + if (!expression.containsOption(I18N_OPTION) || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext + == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) { return expression; } ExpressionNode hint = option(expression, HINT_OPTION); ExpressionNode locale = option(expression, LOCALE_OPTION); ExpressionNode translation = new RuntimeCall(FUNCTION, expression.getRoot(), locale, hint); - return expression.withNode(translation).removeOptions(HINT_OPTION, LOCALE_OPTION); + return expression.withNode(translation).withRemovedOptions(HINT_OPTION, LOCALE_OPTION); } private ExpressionNode option(Expression expression, String optionName) { Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/JoinFilter.java Mon Feb 16 15:00:22 2015 @@ -49,13 +49,14 @@ public class JoinFilter extends FilterCo public static final String JOIN_FUNCTION = "join"; @Override - public Expression apply(Expression expression) { - if (!expression.containsOption(JOIN_OPTION)) { + public Expression apply(Expression expression, ExpressionContext expressionContext) { + if (!expression.containsOption(JOIN_OPTION) || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext + == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) { return expression; } ExpressionNode argumentNode = expression.getOption(JOIN_OPTION); ExpressionNode joinResult = new RuntimeCall(JOIN_FUNCTION, expression.getRoot(), argumentNode); - return expression.withNode(joinResult).removeOptions(JOIN_OPTION); + return expression.withNode(joinResult).withRemovedOptions(JOIN_OPTION); } @Override Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java?rev=1660132&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/URIManipulationFilter.java Mon Feb 16 15:00:22 2015 @@ -0,0 +1,349 @@ +package org.apache.sling.scripting.sightly.impl.filter; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +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.resource.ResourceUtil; +import org.apache.sling.scripting.sightly.extension.RuntimeExtension; +import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression; +import org.apache.sling.scripting.sightly.impl.compiler.expression.ExpressionNode; +import org.apache.sling.scripting.sightly.impl.compiler.expression.node.MapLiteral; +import org.apache.sling.scripting.sightly.impl.compiler.expression.node.RuntimeCall; +import org.apache.sling.scripting.sightly.impl.engine.extension.ExtensionUtils; +import org.apache.sling.scripting.sightly.impl.engine.runtime.RenderContextImpl; +import org.apache.sling.scripting.sightly.impl.utils.PathInfo; +import org.apache.sling.scripting.sightly.render.RenderContext; + +/** + * The {@code URIManipulationFilter} provides support for Sightly's URI Manipulation options according to the + * <a href="https://github.com/Adobe-Marketing-Cloud/sightly-spec/blob/1.1/SPECIFICATION.md">language specification</a> + */ +@Component +@Service({Filter.class, RuntimeExtension.class}) +@Properties({ + @Property(name = RuntimeExtension.NAME, value = URIManipulationFilter.URI_MANIPULATION_FUNCTION) +}) +public class URIManipulationFilter extends FilterComponent implements RuntimeExtension { + + public static final String URI_MANIPULATION_FUNCTION = "uriManipulation"; + + private static final String SCHEME = "scheme"; + private static final String DOMAIN = "domain"; + private static final String PATH = "path"; + private static final String APPEND_PATH = "appendPath"; + private static final String PREPEND_PATH = "prependPath"; + private static final String SELECTORS = "selectors"; + private static final String ADD_SELECTORS = "addSelectors"; + private static final String REMOVE_SELECTORS = "removeSelectors"; + private static final String EXTENSION = "extension"; + private static final String SUFFIX = "suffix"; + private static final String PREPEND_SUFFIX = "prependSuffix"; + private static final String APPEND_SUFFIX = "appendSuffix"; + private static final String FRAGMENT = "fragment"; + private static final String QUERY = "query"; + private static final String ADD_QUERY = "addQuery"; + private static final String REMOVE_QUERY = "removeQuery"; + + @Override + public Expression apply(Expression expression, ExpressionContext expressionContext) { + if ((expression.containsOption(SCHEME) || expression.containsOption(DOMAIN) || expression.containsOption(PATH) || expression + .containsOption(APPEND_PATH) || expression.containsOption(PREPEND_PATH) || expression.containsOption(SELECTORS) || + expression.containsOption(ADD_SELECTORS) || expression.containsOption(REMOVE_SELECTORS) || expression.containsOption + (EXTENSION) || expression.containsOption(SUFFIX) || expression.containsOption(PREPEND_SUFFIX) || expression + .containsOption(APPEND_SUFFIX) || expression.containsOption(FRAGMENT) || expression.containsOption(QUERY) || expression + .containsOption(ADD_QUERY) || expression.containsOption(REMOVE_QUERY)) && expressionContext != ExpressionContext + .PLUGIN_DATA_SLY_USE && expressionContext + != ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE && expressionContext != ExpressionContext.PLUGIN_DATA_SLY_CALL && + expressionContext != ExpressionContext.PLUGIN_DATA_SLY_RESOURCE) { + Map<String, ExpressionNode> uriOptions = new HashMap<String, ExpressionNode>(); + collectOptions(expression, uriOptions, SCHEME, DOMAIN, PATH, APPEND_PATH, PREPEND_PATH, SELECTORS, ADD_SELECTORS, + REMOVE_SELECTORS, EXTENSION, SUFFIX, PREPEND_SUFFIX, APPEND_SUFFIX, FRAGMENT, QUERY, ADD_QUERY, REMOVE_QUERY); + if (uriOptions.size() > 0) { + ExpressionNode translation = new RuntimeCall(URI_MANIPULATION_FUNCTION, expression.getRoot(), new MapLiteral(uriOptions)); + return expression.withNode(translation); + } + } + return expression; + } + + @Override + @SuppressWarnings("unchecked") + public Object call(RenderContext renderContext, Object... arguments) { + ExtensionUtils.checkArgumentCount(URI_MANIPULATION_FUNCTION, arguments, 2); + RenderContextImpl rci = (RenderContextImpl) renderContext; + String uriString = rci.toString(arguments[0]); + Map<String, Object> options = rci.toMap(arguments[1]); + if (uriString == null) { + return null; + } + StringBuilder sb = new StringBuilder(); + PathInfo pathInfo = new PathInfo(uriString); + uriAppender(sb, SCHEME, options, pathInfo.getScheme()); + if (sb.length() > 0) { + sb.append(":"); + sb.append(StringUtils.defaultIfEmpty(pathInfo.getBeginPathSeparator(), "//")); + } + if (sb.length() > 0) { + uriAppender(sb, DOMAIN, options, pathInfo.getHost()); + } else { + String domain = getOption(DOMAIN, options, pathInfo.getHost()); + if (StringUtils.isNotEmpty(domain)) { + sb.append("//").append(domain); + } + } + if (pathInfo.getPort() > -1) { + sb.append(":").append(pathInfo.getPort()); + } + String prependPath = getOption(PREPEND_PATH, options, StringUtils.EMPTY); + if (prependPath == null) { + prependPath = StringUtils.EMPTY; + } + if (StringUtils.isNotEmpty(prependPath)) { + if (sb.length() > 0 && !prependPath.startsWith("/")) { + prependPath = "/" + prependPath; + } + if (!prependPath.endsWith("/")) { + prependPath += "/"; + } + } + String path = getOption(PATH, options, pathInfo.getPath()); + if (StringUtils.isEmpty(path)) { + // if the path is forced to be empty don't remove the path + path = pathInfo.getPath(); + } + String appendPath = getOption(APPEND_PATH, options, StringUtils.EMPTY); + if (appendPath == null) { + appendPath = StringUtils.EMPTY; + } + if (StringUtils.isNotEmpty(appendPath)) { + if (!appendPath.startsWith("/")) { + appendPath = "/" + appendPath; + } + } + String newPath; + try { + newPath = new URI(prependPath + path + appendPath).normalize().getPath(); + } catch (URISyntaxException e) { + newPath = prependPath + path + appendPath; + } + if (sb.length() > 0 && sb.lastIndexOf("/") != sb.length() - 1 && StringUtils.isNotEmpty(newPath) && !newPath.startsWith("/")) { + sb.append("/"); + } + sb.append(newPath); + Set<String> selectors = pathInfo.getSelectors(); + handleSelectors(rci, selectors, options); + for (String selector : selectors) { + if (StringUtils.isNotBlank(selector) && !selector.contains(" ")) { + // make sure not to append empty or invalid selectors + sb.append(".").append(selector); + } + } + String extension = getOption(EXTENSION, options, pathInfo.getExtension()); + if (StringUtils.isNotEmpty(extension)) { + sb.append(".").append(extension); + } + + String prependSuffix = getOption(PREPEND_SUFFIX, options, StringUtils.EMPTY); + if (StringUtils.isNotEmpty(prependSuffix)) { + if (!prependSuffix.startsWith("/")) { + prependSuffix = "/" + prependSuffix; + } + if (!prependSuffix.endsWith("/")) { + prependSuffix += "/"; + } + } + String pathInfoSuffix = pathInfo.getSuffix(); + String suffix = getOption(SUFFIX, options, pathInfoSuffix == null ? StringUtils.EMPTY : pathInfoSuffix); + if (suffix == null) { + suffix = StringUtils.EMPTY; + } + String appendSuffix = getOption(APPEND_SUFFIX, options, StringUtils.EMPTY); + if (StringUtils.isNotEmpty(appendSuffix)) { + appendSuffix = "/" + appendSuffix; + } + String newSuffix = ResourceUtil.normalize(prependSuffix + suffix + appendSuffix); + if (StringUtils.isNotEmpty(newSuffix)) { + if (!newSuffix.startsWith("/")) { + sb.append("/"); + } + sb.append(newSuffix); + } + Map<String, Collection<String>> parameters = pathInfo.getParameters(); + handleParameters(rci, parameters, options); + if (!parameters.isEmpty()) { + sb.append("?"); + for (Map.Entry<String, Collection<String>> entry : parameters.entrySet()) { + for (String value : entry.getValue()) { + sb.append(entry.getKey()).append("=").append(value).append("&"); + } + } + // delete the last & + sb.deleteCharAt(sb.length() - 1); + } + String fragment = getOption(FRAGMENT, options, pathInfo.getFragment()); + if (StringUtils.isNotEmpty(fragment)) { + sb.append("#").append(fragment); + } + return sb.toString(); + } + + private void collectOptions(Expression expression, Map<String, ExpressionNode> collector, String... optionNames) { + for (String optionName : optionNames) { + if (expression.containsOption(optionName)) { + collector.put(optionName, expression.getOption(optionName)); + } + } + expression.removeOptions(optionNames); + } + + private void uriAppender(StringBuilder stringBuilder, String option, Map<String, Object> options, String defaultValue) { + String value = (String) options.get(option); + if (StringUtils.isNotEmpty(value)) { + stringBuilder.append(value); + } else { + if (StringUtils.isNotEmpty(defaultValue)) { + stringBuilder.append(defaultValue); + } + } + } + + private String getOption(String option, Map<String, Object> options, String defaultValue) { + if (options.containsKey(option)) { + return (String) options.get(option); + + } + return defaultValue; + } + + private void handleSelectors(RenderContextImpl rci, Set<String> selectors, Map<String, Object> options) { + if (options.containsKey(SELECTORS)) { + Object selectorsOption = options.get(SELECTORS); + if (selectorsOption == null) { + // we want to remove all selectors + selectors.clear(); + } else if (selectorsOption instanceof String) { + String selectorString = (String) selectorsOption; + String[] selectorsArray = selectorString.split("\\."); + replaceSelectors(selectors, selectorsArray); + } else if (selectorsOption instanceof Object[]) { + Object[] selectorsURIArray = (Object[]) selectorsOption; + String[] selectorsArray = new String[selectorsURIArray.length]; + int index = 0; + for (Object selector : selectorsURIArray) { + selectorsArray[index++] = rci.toString(selector); + } + replaceSelectors(selectors, selectorsArray); + } + } + Object addSelectorsOption = options.get(ADD_SELECTORS); + if (addSelectorsOption instanceof String) { + String selectorString = (String) addSelectorsOption; + String[] selectorsArray = selectorString.split("\\."); + addSelectors(selectors, selectorsArray); + } else if (addSelectorsOption instanceof Object[]) { + Object[] selectorsURIArray = (Object[]) addSelectorsOption; + String[] selectorsArray = new String[selectorsURIArray.length]; + int index = 0; + for (Object selector : selectorsURIArray) { + selectorsArray[index++] = rci.toString(selector); + } + addSelectors(selectors, selectorsArray); + } + Object removeSelectorsOption = options.get(REMOVE_SELECTORS); + if (removeSelectorsOption instanceof String) { + String selectorString = (String) removeSelectorsOption; + String[] selectorsArray = selectorString.split("\\."); + removeSelectors(selectors, selectorsArray); + } else if (removeSelectorsOption instanceof Object[]) { + Object[] selectorsURIArray = (Object[]) removeSelectorsOption; + String[] selectorsArray = new String[selectorsURIArray.length]; + int index = 0; + for (Object selector : selectorsURIArray) { + selectorsArray[index++] = rci.toString(selector); + } + removeSelectors(selectors, selectorsArray); + } + + } + + private void replaceSelectors(Set<String> selectors, String[] selectorsArray) { + selectors.clear(); + selectors.addAll(Arrays.asList(selectorsArray)); + } + + private void addSelectors(Set<String> selectors, String[] selectorsArray) { + selectors.addAll(Arrays.asList(selectorsArray)); + } + + private void removeSelectors(Set<String> selectors, String[] selectorsArray) { + selectors.removeAll(Arrays.asList(selectorsArray)); + } + + @SuppressWarnings("unchecked") + private void handleParameters(RenderContextImpl rci, Map<String, Collection<String>> parameters, Map<String, Object> options) { + if (options.containsKey(QUERY)) { + Object queryOption = options.get(QUERY); + parameters.clear(); + Map<String, Object> queryParameters = rci.toMap(queryOption); + for (Map.Entry<String, Object> entry : queryParameters.entrySet()) { + Object entryValue = entry.getValue(); + if (rci.isCollection(entryValue)) { + Collection<Object> collection = rci.toCollection(entryValue); + Collection<String> values = new ArrayList<String>(collection.size()); + for (Object o : collection) { + values.add(rci.toString(o)); + } + parameters.put(entry.getKey(), values); + } else { + Collection<String> values = new ArrayList<String>(1); + values.add(rci.toString(entryValue)); + parameters.put(entry.getKey(), values); + } + } + } + Object addQueryOption = options.get(ADD_QUERY); + if (addQueryOption != null) { + Map<String, Object> addParams = rci.toMap(addQueryOption); + for (Map.Entry<String, Object> entry : addParams.entrySet()) { + Object entryValue = entry.getValue(); + if (rci.isCollection(entryValue)) { + Collection<Object> collection = rci.toCollection(entryValue); + Collection<String> values = new ArrayList<String>(collection.size()); + for (Object o : collection) { + values.add(rci.toString(o)); + } + parameters.put(entry.getKey(), values); + } else { + Collection<String> values = new ArrayList<String>(1); + values.add(rci.toString(entryValue)); + parameters.put(entry.getKey(), values); + } + } + } + Object removeQueryOption = options.get(REMOVE_QUERY); + if (removeQueryOption != null) { + if (removeQueryOption instanceof String) { + parameters.remove(removeQueryOption); + } else if (removeQueryOption instanceof Object[]) { + Object[] removeQueryParamArray = (Object[]) removeQueryOption; + for (Object param : removeQueryParamArray) { + String paramString = rci.toString(param); + if (paramString != null) { + parameters.remove(paramString); + } + } + } + } + } +} Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/filter/XSSFilter.java Mon Feb 16 15:00:22 2015 @@ -39,7 +39,11 @@ public class XSSFilter extends FilterCom public static final String FUNCTION_NAME = "xss"; @Override - public Expression apply(Expression expression) { + public Expression apply(Expression expression, ExpressionContext expressionContext) { + if (expressionContext == ExpressionContext.PLUGIN_DATA_SLY_USE || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_TEMPLATE + || expressionContext == ExpressionContext.PLUGIN_DATA_SLY_CALL) { + return expression; + } ExpressionNode node = expression.getRoot(); Map<String, ExpressionNode> options = expression.getOptions(); ExpressionNode context = options.get(Syntax.CONTEXT_OPTION); Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/html/dom/MarkupHandler.java Mon Feb 16 15:00:22 2015 @@ -32,6 +32,7 @@ import org.apache.sling.scripting.sightl import org.apache.sling.scripting.sightly.impl.compiler.frontend.Fragment; import org.apache.sling.scripting.sightly.impl.compiler.frontend.Interpolation; import org.apache.sling.scripting.sightly.impl.compiler.ris.command.Patterns; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; import org.apache.sling.scripting.sightly.impl.filter.Filter; import org.apache.sling.scripting.sightly.impl.compiler.expression.Expression; import org.apache.sling.scripting.sightly.impl.compiler.expression.ExpressionNode; @@ -173,7 +174,7 @@ public class MarkupHandler { // Simplified algorithm for attribute output, which works when the interpolation is not of size 1. In this // case we are certain that the attribute value cannot be the boolean value true, so we can skip this test // altogether - Expression expression = expressionWrapper.transform(interpolation, getAttributeMarkupContext(name)); + Expression expression = expressionWrapper.transform(interpolation, getAttributeMarkupContext(name), ExpressionContext.ATTRIBUTE); String attrContent = symbolGenerator.next("attrContent"); String shouldDisplayAttr = symbolGenerator.next("shouldDisplayAttr"); stream.emit(new VariableBinding.Start(attrContent, expression.getRoot())); @@ -200,7 +201,7 @@ public class MarkupHandler { } private void emitSingleFragment(String name, Interpolation interpolation, PluginInvoke invoke) { - Expression valueExpression = expressionWrapper.transform(interpolation, null); //raw expression + Expression valueExpression = expressionWrapper.transform(interpolation, null, ExpressionContext.ATTRIBUTE); //raw expression String attrValue = symbolGenerator.next("attrValue"); //holds the raw attribute value String attrContent = symbolGenerator.next("attrContent"); //holds the escaped attribute value String isTrueVar = symbolGenerator.next("isTrueAttr"); // holds the comparison (attrValue == true) @@ -209,7 +210,8 @@ public class MarkupHandler { Expression contentExpression = valueExpression.withNode(new Identifier(attrValue)); ExpressionNode node = valueExpression.getRoot(); stream.emit(new VariableBinding.Start(attrValue, node)); //attrContent = <expr> - stream.emit(new VariableBinding.Start(attrContent, expressionWrapper.adjustToContext(contentExpression, markupContext).getRoot())); + stream.emit(new VariableBinding.Start(attrContent, expressionWrapper.adjustToContext(contentExpression, markupContext, + ExpressionContext.ATTRIBUTE).getRoot())); stream.emit( new VariableBinding.Start( shouldDisplayAttr, @@ -309,7 +311,7 @@ public class MarkupHandler { if (text != null) { out(text); } else { - outExprNode(expressionWrapper.transform(interpolation, context).getRoot()); + outExprNode(expressionWrapper.transform(interpolation, context, ExpressionContext.TEXT).getRoot()); } } @@ -383,8 +385,8 @@ public class MarkupHandler { PluginCallInfo callInfo = Syntax.parsePluginAttribute(name); if (callInfo != null) { Plugin plugin = obtainPlugin(callInfo.getName()); - Expression expr = expressionWrapper.transform( - expressionParser.parseInterpolation(value), null); + ExpressionContext expressionContext = ExpressionContext.getContextForPlugin(plugin.name()); + Expression expr = expressionWrapper.transform(expressionParser.parseInterpolation(value), null, expressionContext); PluginInvoke invoke = plugin.invoke(expr, callInfo, compilerContext); context.addPlugin(invoke, plugin.priority()); context.addPluginCall(name, callInfo, expr); Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/AttributePlugin.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/AttributePlugin.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/AttributePlugin.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/AttributePlugin.java Mon Feb 16 15:00:22 2015 @@ -47,6 +47,7 @@ import org.apache.sling.scripting.sightl import org.apache.sling.scripting.sightly.impl.compiler.common.DefaultPluginInvoke; import org.apache.sling.scripting.sightly.impl.compiler.frontend.CompilerContext; import org.apache.sling.scripting.sightly.impl.compiler.util.stream.PushStream; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; import org.apache.sling.scripting.sightly.impl.html.MarkupUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -351,6 +352,6 @@ public class AttributePlugin extends Plu //todo: this is not the indicated way to escape via XSS. Correct after modifying the compiler context API return new RuntimeCall("xss", node, new StringConstant(markupContext.getName()), hint); } - return compilerContext.adjustToContext(new Expression(node), markupContext).getRoot(); + return compilerContext.adjustToContext(new Expression(node), markupContext, ExpressionContext.ATTRIBUTE).getRoot(); } } Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/ElementPlugin.java Mon Feb 16 15:00:22 2015 @@ -32,6 +32,7 @@ import org.apache.sling.scripting.sightl import org.apache.sling.scripting.sightly.impl.compiler.common.DefaultPluginInvoke; import org.apache.sling.scripting.sightly.impl.compiler.frontend.CompilerContext; import org.apache.sling.scripting.sightly.impl.compiler.util.stream.PushStream; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; /** * Implementation for the {@code data-sly-element} plugin. @@ -50,7 +51,8 @@ public class ElementPlugin extends Plugi return new DefaultPluginInvoke() { - private final ExpressionNode node = compilerContext.adjustToContext(expression, MarkupContext.ELEMENT_NAME).getRoot(); + private final ExpressionNode node = compilerContext.adjustToContext(expression, MarkupContext.ELEMENT_NAME, ExpressionContext + .ELEMENT).getRoot(); private String tagVar = compilerContext.generateVariable("tagVar"); @Override Modified: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TextPlugin.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TextPlugin.java?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TextPlugin.java (original) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/plugin/TextPlugin.java Mon Feb 16 15:00:22 2015 @@ -29,6 +29,7 @@ import org.apache.sling.scripting.sightl import org.apache.sling.scripting.sightly.impl.compiler.common.DefaultPluginInvoke; import org.apache.sling.scripting.sightly.impl.compiler.frontend.CompilerContext; import org.apache.sling.scripting.sightly.impl.compiler.util.stream.PushStream; +import org.apache.sling.scripting.sightly.impl.filter.ExpressionContext; /** * The {@code data-sly-text} plugin. @@ -49,7 +50,7 @@ public class TextPlugin extends PluginCo public void beforeChildren(PushStream stream) { String variable = compilerContext.generateVariable("textContent"); stream.emit(new VariableBinding.Start(variable, - compilerContext.adjustToContext(expression, MarkupContext.TEXT).getRoot())); + compilerContext.adjustToContext(expression, MarkupContext.TEXT, ExpressionContext.TEXT).getRoot())); stream.emit(new OutVariable(variable)); stream.emit(VariableBinding.END); Patterns.beginStreamIgnore(stream); Added: sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/PathInfo.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/PathInfo.java?rev=1660132&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/PathInfo.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/main/java/org/apache/sling/scripting/sightly/impl/utils/PathInfo.java Mon Feb 16 15:00:22 2015 @@ -0,0 +1,230 @@ +/** + * **************************************************************************** + * 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 + * <p/> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p/> + * 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.utils; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.lang.StringUtils; +import org.apache.sling.scripting.sightly.SightlyException; + +/** + * The {@code PathInfo} class provides path processing methods useful for extracting the path of a request, the selectors applied to the + * path, the extension and query parameters. + */ +public class PathInfo { + + private URI uri; + private String path; + private Set<String> selectors; + private String selectorString; + private String extension; + private String suffix; + private Map<String, Collection<String>> parameters = new LinkedHashMap<String, Collection<String>>(); + + /** + * Creates a {@code PathInfo} object based on a request path. + * + * @param path the full normalized path (no '.', '..', or double slashes8) of the request, including the query parameters + * @throws NullPointerException if the supplied {@code path} is null + */ + public PathInfo(String path) { + if (path == null) { + throw new NullPointerException("The path parameter cannot be null."); + } + try { + uri = new URI(path); + } catch (URISyntaxException e) { + throw new SightlyException("The provided path does not represent a valid URI: " + path); + } + selectors = new LinkedHashSet<String>(); + String processingPath = path; + if (uri.getPath() != null) { + processingPath = uri.getPath(); + } + int lastDot = processingPath.lastIndexOf('.'); + if (lastDot > -1) { + String afterLastDot = processingPath.substring(lastDot + 1); + String[] parts = afterLastDot.split("/"); + extension = parts[0]; + if (parts.length > 1) { + // we have a suffix + StringBuilder suffixSB = new StringBuilder(); + for (int i = 1; i < parts.length; i++) { + suffixSB.append("/").append(parts[i]); + } + int hashIndex = suffixSB.indexOf("#"); + if (hashIndex > -1) { + suffix = suffixSB.substring(0, hashIndex); + } else { + suffix = suffixSB.toString(); + } + } + } + int firstDot = processingPath.indexOf('.'); + if (firstDot < lastDot) { + selectorString = processingPath.substring(firstDot + 1, lastDot); + String[] selectorsArray = selectorString.split("\\."); + selectors.addAll(Arrays.asList(selectorsArray)); + } + int pathLength = processingPath.length() - (selectorString == null ? 0 : selectorString.length() + 1) - (extension == null ? 0: + extension.length() + 1) - (suffix == null ? 0 : suffix.length()); + if (pathLength == processingPath.length()) { + this.path = processingPath; + } else { + this.path = processingPath.substring(0, pathLength); + } + String query = uri.getQuery(); + if (StringUtils.isNotEmpty(query)) { + String[] keyValuePairs = query.split("&"); + for (int i = 0; i < keyValuePairs.length; i++) { + String[] pair = keyValuePairs[i].split("="); + if (pair.length == 2) { + String param = pair[0]; + String value = pair[1]; + Collection<String> values = parameters.get(param); + if (values == null) { + values = new ArrayList<String>(); + parameters.put(param, values); + } + values.add(value); + } + } + } + } + + /** + * Returns the scheme of this path if the path corresponds to a URI and if the URI provides scheme information. + * + * @return the scheme or {@code null} if the path does not contain a scheme + */ + public String getScheme() { + return uri.getScheme(); + } + + /** + * Returns the path separator ("//") if the path defines an absolute URI. + * + * @return the path separator if the path is an absolute URI, {@code null} otherwise + */ + public String getBeginPathSeparator() { + if (uri.isAbsolute()) { + return "//"; + } + return null; + } + + /** + * Returns the host part of the path, if the path defines a URI. + * + * @return the host if the path defines a URI, {@code null} otherwise + */ + public String getHost() { + return uri.getHost(); + } + + /** + * Returns the port if the path defines a URI and if it contains port information. + * + * @return the port or -1 if no port is defined + */ + public int getPort() { + return uri.getPort(); + } + + /** + * Returns the path from which <i>{@code this}</i> object was built. + * + * @return the original path + */ + public String getFullPath() { + return uri.toString(); + } + + /** + * Returns the path identifying the resource, without any selectors, extension or query parameters. + * + * @return the path of the resource + */ + public String getPath() { + return path; + } + + /** + * Returns the selectors set. + * + * @return the selectors set; if there are no selectors the set will be empty + */ + public Set<String> getSelectors() { + return selectors; + } + + /** + * Returns the extension. + * + * @return the extension, if one exists, otherwise {@code null} + */ + public String getExtension() { + return extension; + } + + /** + * Returns the selector string. + * + * @return the selector string, if the path has selectors, otherwise {@code null} + */ + public String getSelectorString() { + return selectorString; + } + + /** + * Returns the suffix appended to the path. The suffix represents the path segment between the path's extension and the path's fragment. + * + * @return the suffix if the path contains one, {@code null} otherwise + */ + public String getSuffix() { + return suffix; + } + + /** + * Returns the fragment is this path defines a URI and it contains a fragment. + * + * @return the fragment, or {@code null} if one doesn't exist + */ + public String getFragment() { + return uri.getFragment(); + } + + /** + * Returns the URI parameters if the provided path defines a URI. + * @return the parameters map; can be empty if there are no parameters of if the path doesn't identify a URI + */ + public Map<String, Collection<String>> getParameters() { + return parameters; + } +} Added: sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/PathInfoTest.java URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/PathInfoTest.java?rev=1660132&view=auto ============================================================================== --- sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/PathInfoTest.java (added) +++ sling/trunk/contrib/scripting/sightly/engine/src/test/java/org/apache/sling/scripting/sightly/impl/utils/PathInfoTest.java Mon Feb 16 15:00:22 2015 @@ -0,0 +1,223 @@ +/******************************************************************************* + * 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.utils; + +import java.util.LinkedHashSet; +import java.util.Set; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class PathInfoTest { + + private static final String EMPTY = ""; + private static final String PATH = "test"; + private static final String ABSOLUTE_PATH = "/" + PATH; + private static final String SCHEME = "http"; + private static final String SCHEME_PATH_SEPARATOR = "//"; + private static final int PORT = 8080; + private static final String HOST = "www.example.com"; + private static final String SELECTOR_STRING = "a.b"; + private static final Set<String> SELECTOR_STRING_SET = new LinkedHashSet<String>() {{ + add("a"); + add("b"); + }}; + private static final String EXTENSION = "html"; + private static final String SUFFIX = "/suffix1/suffix2"; + private static final String FRAGMENT = "fragment"; + + private static final PathInfo emptyPathInfo = new PathInfo(EMPTY); + private static final PathInfo simplePath = new PathInfo(PATH); + private static final PathInfo pathWithExtension = new PathInfo(PATH + "." + EXTENSION); + private static final PathInfo pathWithSelectors = new PathInfo(PATH + "." + SELECTOR_STRING + "." + EXTENSION); + private static final PathInfo pathWithSelectorsSuffix = new PathInfo(PATH + "." + SELECTOR_STRING + "." + EXTENSION + SUFFIX); + private static final PathInfo pathWithScheme = new PathInfo(SCHEME + ":" + "//" + HOST); + private static final PathInfo pathWithSchemePath = new PathInfo(SCHEME + ":" + "//" + HOST + ABSOLUTE_PATH); + private static final PathInfo pathWithSchemePathExtension = new PathInfo(SCHEME + ":" + "//" + HOST + ABSOLUTE_PATH + "." + EXTENSION); + private static final PathInfo pathWithSchemePathExtensionSelectors = new PathInfo(SCHEME + ":" + "//" + HOST + ABSOLUTE_PATH + "." + + SELECTOR_STRING + "." + EXTENSION); + private static final PathInfo pathWithSchemePathExtensionSelectorsSuffix = new PathInfo(SCHEME + ":" + "//" + HOST + ABSOLUTE_PATH + + "." + SELECTOR_STRING + "." + EXTENSION + SUFFIX); + private static final PathInfo pathWithSchemePathExtensionSelectorsSuffixFragment = new PathInfo(SCHEME + ":" + "//" + HOST + + ABSOLUTE_PATH + "." + SELECTOR_STRING + "." + EXTENSION + SUFFIX + "#" + FRAGMENT); + private static final PathInfo pathWithSchemePortPathExtensionSelectorsSuffixFragment = new PathInfo(SCHEME + ":" + "//" + HOST + + ":" + PORT + ABSOLUTE_PATH + "." + SELECTOR_STRING + "." + EXTENSION + SUFFIX + "#" + FRAGMENT); + + @Test + public void testGetPath() throws Exception { + assertEquals(EMPTY, emptyPathInfo.getPath()); + assertEquals(PATH, simplePath.getPath()); + assertEquals(PATH, pathWithExtension.getPath()); + assertEquals(PATH, pathWithSelectors.getPath()); + assertEquals(PATH, pathWithSelectorsSuffix.getPath()); + assertEquals(EMPTY, pathWithScheme.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePath.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePathExtension.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePathExtensionSelectors.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePathExtensionSelectorsSuffix.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePathExtensionSelectorsSuffixFragment.getPath()); + assertEquals(ABSOLUTE_PATH, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getPath()); + } + + @Test + public void testGetSelectors() throws Exception { + assertEquals(0, emptyPathInfo.getSelectors().size()); + assertEquals(0, simplePath.getSelectors().size()); + assertEquals(0, pathWithExtension.getSelectors().size()); + assertEquals(SELECTOR_STRING_SET, pathWithSelectors.getSelectors()); + assertEquals(SELECTOR_STRING_SET, pathWithSelectorsSuffix.getSelectors()); + assertEquals(EMPTY, pathWithScheme.getPath()); + assertEquals(0, pathWithSchemePath.getSelectors().size()); + assertEquals(0, pathWithSchemePathExtension.getSelectors().size()); + assertEquals(SELECTOR_STRING_SET, pathWithSchemePathExtensionSelectors.getSelectors()); + assertEquals(SELECTOR_STRING_SET, pathWithSchemePathExtensionSelectorsSuffix.getSelectors()); + assertEquals(SELECTOR_STRING_SET, pathWithSchemePathExtensionSelectorsSuffixFragment.getSelectors()); + assertEquals(SELECTOR_STRING_SET, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getSelectors()); + } + + @Test + public void testGetSelectorString() throws Exception { + assertNull(emptyPathInfo.getSelectorString()); + assertNull(simplePath.getSelectorString()); + assertNull(pathWithExtension.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSelectors.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSelectorsSuffix.getSelectorString()); + assertNull(pathWithScheme.getSelectorString()); + assertNull(pathWithSchemePath.getSelectorString()); + assertNull(pathWithSchemePathExtension.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSchemePathExtensionSelectors.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSchemePathExtensionSelectorsSuffix.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSchemePathExtensionSelectorsSuffixFragment.getSelectorString()); + assertEquals(SELECTOR_STRING, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getSelectorString()); + } + + @Test + public void testGetExtension() throws Exception { + assertNull(emptyPathInfo.getExtension()); + assertNull(simplePath.getExtension()); + assertEquals(EXTENSION, pathWithExtension.getExtension()); + assertEquals(EXTENSION, pathWithSelectors.getExtension()); + assertEquals(EXTENSION, pathWithSelectorsSuffix.getExtension()); + assertNull(pathWithScheme.getExtension()); + assertNull(pathWithSchemePath.getExtension()); + assertEquals(EXTENSION, pathWithSchemePathExtension.getExtension()); + assertEquals(EXTENSION, pathWithSchemePathExtensionSelectors.getExtension()); + assertEquals(EXTENSION, pathWithSchemePathExtensionSelectorsSuffix.getExtension()); + assertEquals(EXTENSION, pathWithSchemePathExtensionSelectorsSuffixFragment.getExtension()); + assertEquals(EXTENSION, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getExtension()); + } + + @Test + public void testGetScheme() { + assertNull(emptyPathInfo.getScheme()); + assertNull(simplePath.getScheme()); + assertNull(pathWithExtension.getScheme()); + assertNull(pathWithSelectors.getScheme()); + assertNull(pathWithSelectorsSuffix.getScheme()); + assertEquals(SCHEME, pathWithScheme.getScheme()); + assertEquals(SCHEME, pathWithSchemePath.getScheme()); + assertEquals(SCHEME, pathWithSchemePathExtension.getScheme()); + assertEquals(SCHEME, pathWithSchemePathExtensionSelectors.getScheme()); + assertEquals(SCHEME, pathWithSchemePathExtensionSelectorsSuffix.getScheme()); + assertEquals(SCHEME, pathWithSchemePathExtensionSelectorsSuffixFragment.getScheme()); + assertEquals(SCHEME, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getScheme()); + } + + @Test + public void testGetBeginPathSeparator() { + assertNull(emptyPathInfo.getBeginPathSeparator()); + assertNull(simplePath.getBeginPathSeparator()); + assertNull(pathWithExtension.getBeginPathSeparator()); + assertNull(pathWithSelectors.getBeginPathSeparator()); + assertNull(pathWithSelectorsSuffix.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithScheme.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePath.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePathExtension.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePathExtensionSelectors.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePathExtensionSelectorsSuffix.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePathExtensionSelectorsSuffixFragment.getBeginPathSeparator()); + assertEquals(SCHEME_PATH_SEPARATOR, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getBeginPathSeparator()); + } + + @Test + public void testGetHost() { + assertNull(emptyPathInfo.getHost()); + assertNull(simplePath.getHost()); + assertNull(pathWithExtension.getHost()); + assertNull(pathWithSelectors.getHost()); + assertNull(pathWithSelectorsSuffix.getHost()); + assertEquals(HOST, pathWithScheme.getHost()); + assertEquals(HOST, pathWithSchemePath.getHost()); + assertEquals(HOST, pathWithSchemePathExtension.getHost()); + assertEquals(HOST, pathWithSchemePathExtensionSelectors.getHost()); + assertEquals(HOST, pathWithSchemePathExtensionSelectorsSuffix.getHost()); + assertEquals(HOST, pathWithSchemePathExtensionSelectorsSuffixFragment.getHost()); + assertEquals(HOST, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getHost()); + } + + @Test + public void testGetPort() { + assertEquals(-1, emptyPathInfo.getPort()); + assertEquals(-1, simplePath.getPort()); + assertEquals(-1, pathWithExtension.getPort()); + assertEquals(-1, pathWithSelectors.getPort()); + assertEquals(-1, pathWithSelectorsSuffix.getPort()); + assertEquals(-1, pathWithScheme.getPort()); + assertEquals(-1, pathWithSchemePath.getPort()); + assertEquals(-1, pathWithSchemePathExtension.getPort()); + assertEquals(-1, pathWithSchemePathExtensionSelectors.getPort()); + assertEquals(-1, pathWithSchemePathExtensionSelectorsSuffix.getPort()); + assertEquals(-1, pathWithSchemePathExtensionSelectorsSuffixFragment.getPort()); + assertEquals(PORT, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getPort()); + } + + @Test + public void testGetSuffix() { + assertNull(emptyPathInfo.getSuffix()); + assertNull(simplePath.getSuffix()); + assertNull(pathWithExtension.getSuffix()); + assertNull(pathWithSelectors.getSuffix()); + assertEquals(SUFFIX, pathWithSelectorsSuffix.getSuffix()); + assertNull(pathWithScheme.getSuffix()); + assertNull(pathWithSchemePath.getSuffix()); + assertNull(pathWithSchemePathExtension.getSuffix()); + assertNull(pathWithSchemePathExtensionSelectors.getSuffix()); + assertEquals(SUFFIX, pathWithSchemePathExtensionSelectorsSuffix.getSuffix()); + assertEquals(SUFFIX, pathWithSchemePathExtensionSelectorsSuffixFragment.getSuffix()); + assertEquals(SUFFIX, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getSuffix()); + } + + @Test + public void testGetFragment() { + assertNull(emptyPathInfo.getFragment()); + assertNull(simplePath.getFragment()); + assertNull(pathWithExtension.getFragment()); + assertNull(pathWithSelectors.getFragment()); + assertNull(pathWithSelectorsSuffix.getFragment()); + assertNull(pathWithScheme.getFragment()); + assertNull(pathWithSchemePath.getFragment()); + assertNull(pathWithSchemePathExtension.getFragment()); + assertNull(pathWithSchemePathExtensionSelectors.getFragment()); + assertNull(pathWithSchemePathExtensionSelectorsSuffix.getFragment()); + assertEquals(FRAGMENT, pathWithSchemePathExtensionSelectorsSuffixFragment.getFragment()); + assertEquals(FRAGMENT, pathWithSchemePortPathExtensionSelectorsSuffixFragment.getFragment()); + } +} Modified: sling/trunk/contrib/scripting/sightly/testing-content/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/testing-content/pom.xml?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/testing-content/pom.xml (original) +++ sling/trunk/contrib/scripting/sightly/testing-content/pom.xml Mon Feb 16 15:00:22 2015 @@ -85,7 +85,7 @@ <artifactItem> <groupId>io.sightly</groupId> <artifactId>io.sightly.tck</artifactId> - <version>1.1.0</version> + <version>1.1.1</version> <type>jar</type> <outputDirectory>${project.build.directory}/sightlytck/</outputDirectory> <includes>**/*.html,**/*.js,**/*.java</includes> Modified: sling/trunk/contrib/scripting/sightly/testing-content/src/main/resources/SLING-INF/sightlytck.json URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/testing-content/src/main/resources/SLING-INF/sightlytck.json?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/testing-content/src/main/resources/SLING-INF/sightlytck.json (original) +++ sling/trunk/contrib/scripting/sightly/testing-content/src/main/resources/SLING-INF/sightlytck.json Mon Feb 16 15:00:22 2015 @@ -10,6 +10,10 @@ "jcr:primaryType": "nt:unstructured", "sling:resourceType": "/sightlytck/scripts/exprlang/filters" }, + "filteroptions": { + "jcr:primaryType": "nt:unstructured", + "sling:resourceType": "/sightlytck/scripts/exprlang/filteroptions" + }, "strings": { "jcr:primaryType": "nt:unstructured", "sling:resourceType": "/sightlytck/scripts/exprlang/strings" Modified: sling/trunk/contrib/scripting/sightly/testing/pom.xml URL: http://svn.apache.org/viewvc/sling/trunk/contrib/scripting/sightly/testing/pom.xml?rev=1660132&r1=1660131&r2=1660132&view=diff ============================================================================== --- sling/trunk/contrib/scripting/sightly/testing/pom.xml (original) +++ sling/trunk/contrib/scripting/sightly/testing/pom.xml Mon Feb 16 15:00:22 2015 @@ -293,7 +293,7 @@ <dependency> <groupId>io.sightly</groupId> <artifactId>io.sightly.tck</artifactId> - <version>1.1.0</version> + <version>1.1.1</version> <scope>test</scope> </dependency>