This is an automated email from the ASF dual-hosted git repository. martin_s pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/archiva-components.git
commit a054f7b9f952158da70fadd94eda448e36c0dc13 Author: Martin Stockhammer <[email protected]> AuthorDate: Thu Oct 17 11:58:20 2019 +0200 Adding expression evaluator --- expression-evaluator/.gitignore | 10 + expression-evaluator/pom.xml | 53 +++++ .../evaluator/DefaultExpressionEvaluator.java | 159 +++++++++++++++ .../components/evaluator/EvaluatorException.java | 50 +++++ .../components/evaluator/ExpressionEvaluator.java | 63 ++++++ .../components/evaluator/ExpressionSource.java | 37 ++++ .../sources/PropertiesExpressionSource.java | 65 +++++++ .../sources/SystemPropertyExpressionSource.java | 44 +++++ expression-evaluator/src/site/site.xml | 34 ++++ .../sources/DefaultExpressionEvaluatorTest.java | 214 +++++++++++++++++++++ 10 files changed, 729 insertions(+) diff --git a/expression-evaluator/.gitignore b/expression-evaluator/.gitignore new file mode 100644 index 0000000..7b6772c --- /dev/null +++ b/expression-evaluator/.gitignore @@ -0,0 +1,10 @@ +.idea/** +.project +.classpath +.settings +.java-version +target +.DS_Store +.site-content +out +*.iml diff --git a/expression-evaluator/pom.xml b/expression-evaluator/pom.xml new file mode 100644 index 0000000..fd69415 --- /dev/null +++ b/expression-evaluator/pom.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.archiva.components</groupId> + <artifactId>archiva-components</artifactId> + <version>3.0-SNAPSHOT</version> + </parent> + + <version>3.0-SNAPSHOT</version> + <artifactId>archiva-components-expression-evaluator</artifactId> + <name>Archiva Components :: Expression Evaluator</name> + + <properties> + <site.staging.base>${project.basedir}/..</site.staging.base> + </properties> + + + <url>${webUrl}/${project.artifactId}</url> + + <scm> + <url>${scmBrowseUrl}</url> + </scm> + + + <dependencies> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/DefaultExpressionEvaluator.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/DefaultExpressionEvaluator.java new file mode 100644 index 0000000..00cfef5 --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/DefaultExpressionEvaluator.java @@ -0,0 +1,159 @@ +package org.apache.archiva.components.evaluator; + +/* + * 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. + */ + +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * DefaultExpressionEvaluator + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public class DefaultExpressionEvaluator + implements ExpressionEvaluator +{ + private List<ExpressionSource> expressionSources; + + public DefaultExpressionEvaluator() + { + expressionSources = new ArrayList<>(); + } + + public void addExpressionSource( ExpressionSource source ) + { + expressionSources.add( source ); + } + + public String expand( String str ) + throws EvaluatorException + { + return recursiveExpand( str, new ArrayList<String>() ); + } + + private String recursiveExpand( String str, List<String> seenExpressions ) + throws EvaluatorException + { + if ( StringUtils.isEmpty( str ) ) + { + // Empty string. Fail fast. + return str; + } + + if ( str.indexOf( "${" ) < 0 ) + { + // Contains no potential expressions. Fail fast. + return str; + } + + if ( this.expressionSources.isEmpty() ) + { + throw new EvaluatorException( "Unable to expand expressions with empty ExpressionSource list." ); + } + + Pattern pat = Pattern.compile( "(?<=[^$]|^)(\\$\\{[^}]*\\})" ); + Matcher mat = pat.matcher( str ); + int offset = 0; + String expression; + String value; + StringBuilder expanded = new StringBuilder(); + + while ( mat.find( offset ) ) + { + expression = mat.group( 1 ); + + if ( seenExpressions.contains( expression ) ) + { + throw new EvaluatorException( "A recursive cycle has been detected with expression " + expression + "." ); + } + + seenExpressions.add( expression ); + + expanded.append( str.substring( offset, mat.start( 1 ) ) ); + value = findValue( expression ); + if ( value != null ) + { + String resolvedValue = recursiveExpand( value, seenExpressions ); + expanded.append( resolvedValue ); + } + else + { + expanded.append( expression ); + } + offset = mat.end( 1 ); + } + + expanded.append( str.substring( offset ) ); + + if ( expanded.indexOf( "$$" ) >= 0 ) + { + // Special case for escaped content. + return expanded.toString().replaceAll( "\\$\\$", "\\$" ); + } + else + { + // return expanded + return expanded.toString(); + } + } + + private String findValue( String expression ) + { + String newExpression = expression.trim(); + if ( newExpression.startsWith( "${" ) && newExpression.endsWith( "}" ) ) + { + newExpression = newExpression.substring( 2, newExpression.length() - 1 ); + } + + if ( StringUtils.isEmpty( newExpression ) ) + { + return null; + } + + String value = null; + Iterator it = this.expressionSources.iterator(); + while ( it.hasNext() ) + { + ExpressionSource source = (ExpressionSource) it.next(); + value = source.getExpressionValue( newExpression ); + if ( value != null ) + { + return value; + } + } + return null; + } + + public List getExpressionSourceList() + { + return this.expressionSources; + } + + public boolean removeExpressionSource( ExpressionSource source ) + { + return this.expressionSources.remove( source ); + } +} diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/EvaluatorException.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/EvaluatorException.java new file mode 100644 index 0000000..b2c68cc --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/EvaluatorException.java @@ -0,0 +1,50 @@ +package org.apache.archiva.components.evaluator; + +/* + * 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. + */ + +/** + * EvaluatorException + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public class EvaluatorException + extends Exception +{ + public EvaluatorException() + { + super(); + } + + public EvaluatorException( String message, Throwable cause ) + { + super( message, cause ); + } + + public EvaluatorException( String message ) + { + super( message ); + } + + public EvaluatorException( Throwable cause ) + { + super( cause ); + } +} diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionEvaluator.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionEvaluator.java new file mode 100644 index 0000000..0aeae57 --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionEvaluator.java @@ -0,0 +1,63 @@ +package org.apache.archiva.components.evaluator; + +/* + * 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. + */ + +import java.util.List; + +/** + * ExpressionEvaluator + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public interface ExpressionEvaluator +{ + /** + * Add a source for expression resolution. + * + * @param source the source to add. + */ + void addExpressionSource( ExpressionSource source ); + + /** + * Evaluate a string, and expand expressions as needed. + * + * @param str the expression + * @return the value of the expression + * @throws EvaluatorException if a problem occurs whilst evaluating + */ + String expand( String str ) + throws EvaluatorException; + + /** + * Get the List of expression sources. + * + * @return the list of expression sources. + */ + List getExpressionSourceList(); + + /** + * Remove a specific expression source. + * + * @param source the source to remove. + * @return true if expression source was removed. + */ + boolean removeExpressionSource( ExpressionSource source ); +} diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionSource.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionSource.java new file mode 100644 index 0000000..39d15cb --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/ExpressionSource.java @@ -0,0 +1,37 @@ +package org.apache.archiva.components.evaluator; + +/* + * 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. + */ + +/** + * ExpressionSource + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public interface ExpressionSource +{ + /** + * Gets a value for a provided Expression. + * + * @param expression the expression to attempt to get a value for. + * @return the value for the expression, or null if no value found. + */ + String getExpressionValue( String expression ); +} diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/PropertiesExpressionSource.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/PropertiesExpressionSource.java new file mode 100644 index 0000000..4f157cb --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/PropertiesExpressionSource.java @@ -0,0 +1,65 @@ +package org.apache.archiva.components.evaluator.sources; + +/* + * 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. + */ + +import org.apache.archiva.components.evaluator.ExpressionSource; + +import java.util.Properties; + +/** + * PropertiesExpressionSource + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + * + */ +public class PropertiesExpressionSource + implements ExpressionSource +{ + private Properties properties; + + public String getExpressionValue( String expression ) + { + if ( properties == null ) + { + throw new IllegalStateException( "Properties object has not been initialized." ); + } + + try + { + return properties.getProperty( expression ); + } + catch ( Exception e ) + { + return null; + } + } + + public Properties getProperties() + { + return properties; + } + + public void setProperties( Properties properties ) + { + this.properties = properties; + } + +} diff --git a/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/SystemPropertyExpressionSource.java b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/SystemPropertyExpressionSource.java new file mode 100644 index 0000000..522c973 --- /dev/null +++ b/expression-evaluator/src/main/java/org/apache/archiva/components/evaluator/sources/SystemPropertyExpressionSource.java @@ -0,0 +1,44 @@ +package org.apache.archiva.components.evaluator.sources; + +/* + * 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. + */ + +import org.apache.archiva.components.evaluator.ExpressionSource; + +/** + * SystemPropertyExpressionSource + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public class SystemPropertyExpressionSource + implements ExpressionSource +{ + public String getExpressionValue( String expression ) + { + try + { + return System.getProperty( expression ); + } + catch ( Exception e ) + { + return null; + } + } +} diff --git a/expression-evaluator/src/site/site.xml b/expression-evaluator/src/site/site.xml new file mode 100644 index 0000000..c3cfc21 --- /dev/null +++ b/expression-evaluator/src/site/site.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + ~ 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. + --> + +<project name="Expression Evaluator" > + + <publishDate format="yyyy-MM-dd" position="none" /> + + <body> + <menu ref="modules" /> + <menu ref="reports" /> + <menu ref="ASF" /> + <breadcrumbs> + <item name="Archiva Components" href="../index.html" /> + <item name="Expression Evaluator" href="index.html" /> + </breadcrumbs> + </body> +</project> diff --git a/expression-evaluator/src/test/java/org/apache/archiva/components/evaluator/sources/DefaultExpressionEvaluatorTest.java b/expression-evaluator/src/test/java/org/apache/archiva/components/evaluator/sources/DefaultExpressionEvaluatorTest.java new file mode 100644 index 0000000..423ea6f --- /dev/null +++ b/expression-evaluator/src/test/java/org/apache/archiva/components/evaluator/sources/DefaultExpressionEvaluatorTest.java @@ -0,0 +1,214 @@ +package org.apache.archiva.components.evaluator.sources; + +/* + * 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. + */ + +import junit.framework.TestCase; +import org.apache.archiva.components.evaluator.DefaultExpressionEvaluator; +import org.apache.archiva.components.evaluator.EvaluatorException; +import org.apache.archiva.components.evaluator.ExpressionEvaluator; +import org.apache.archiva.components.evaluator.sources.PropertiesExpressionSource; +import org.apache.archiva.components.evaluator.sources.SystemPropertyExpressionSource; + +import java.util.Properties; + +/** + * DefaultExpressionEvaluatorTest + * + * @author <a href="mailto:[email protected]">Joakim Erdfelt</a> + * + */ +public class DefaultExpressionEvaluatorTest + extends TestCase +{ + private ExpressionEvaluator evaluator; + + protected void setUp() + throws Exception + { + super.setUp(); + + evaluator = new DefaultExpressionEvaluator(); + } + + public void testSimple() + throws EvaluatorException + { + Properties props = new Properties(); + props.setProperty( "fruit", "apple" ); + + PropertiesExpressionSource propsSource = new PropertiesExpressionSource(); + propsSource.setProperties( props ); + evaluator.addExpressionSource( propsSource ); + + String expression = "${fruit}"; + String expected = "apple"; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testSimpleStartOfLine() + throws EvaluatorException + { + Properties props = new Properties(); + props.setProperty( "fruit", "apple" ); + + PropertiesExpressionSource propsSource = new PropertiesExpressionSource(); + propsSource.setProperties( props ); + evaluator.addExpressionSource( propsSource ); + + String expression = "${fruit} is good for you."; + String expected = "apple is good for you."; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testSimpleEndOfLine() + throws EvaluatorException + { + Properties props = new Properties(); + props.setProperty( "fruit", "apple" ); + + PropertiesExpressionSource propsSource = new PropertiesExpressionSource(); + propsSource.setProperties( props ); + evaluator.addExpressionSource( propsSource ); + + String expression = "watch out for the worm in the ${fruit}"; + String expected = "watch out for the worm in the apple"; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testSimpleSystemProperty() + throws EvaluatorException + { + evaluator.addExpressionSource( new SystemPropertyExpressionSource() ); + + String userHome = System.getProperty( "user.home" ); + String expression = "My HOME directory is ${user.home}"; + String expected = "My HOME directory is " + userHome; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testMultiExpression() + throws EvaluatorException + { + evaluator.addExpressionSource( new SystemPropertyExpressionSource() ); + + String userName = System.getProperty( "user.name" ); + String userHome = System.getProperty( "user.home" ); + String expression = "${user.name}'s home directory is ${user.home}"; + String expected = userName + "'s home directory is " + userHome; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + /** + * This use case was discovered by a user of archiva. + * The last expression doesn't get evaluated properly. + * <p/> + * The result (with the bug) was "2.0.4${prj.ver.suf}" + */ + public void testMultiExpressionVersionBug() + throws EvaluatorException + { + Properties props = new Properties(); + props.setProperty( "prj.ver.maj", "2" ); + props.setProperty( "prj.ver.min", "0" ); + props.setProperty( "prj.ver.inc", "4" ); + props.setProperty( "prj.ver.suf", "-SNAPSHOT" ); + + PropertiesExpressionSource propsSource = new PropertiesExpressionSource(); + propsSource.setProperties( props ); + evaluator.addExpressionSource( propsSource ); + + String expression = "${prj.ver.maj}.${prj.ver.min}.${prj.ver.inc}${prj.ver.suf}"; + String expected = "2.0.4-SNAPSHOT"; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testEscaping() + throws EvaluatorException + { + evaluator.addExpressionSource( new SystemPropertyExpressionSource() ); + + String userName = System.getProperty( "user.name" ); + String userHome = System.getProperty( "user.home" ); + String expression = "${user.name}'s home directory is ${user.home} (fetched via $${user.home} expression)"; + String expected = userName + "'s home directory is " + userHome + " (fetched via ${user.home} expression)"; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testRecursiveSimple() + throws EvaluatorException + { + PropertiesExpressionSource propsource = new PropertiesExpressionSource(); + Properties props = new Properties(); + + // Create intentional recursive lookup. + props.setProperty( "main.dir", "${target.dir}/classes" ); + props.setProperty( "target.dir", "./target" ); + + propsource.setProperties( props ); + + evaluator.addExpressionSource( propsource ); + evaluator.addExpressionSource( new SystemPropertyExpressionSource() ); + + String expression = "My classes directory is ${main.dir}"; + String expected = "My classes directory is ./target/classes"; + + String actual = evaluator.expand( expression ); + assertEquals( expected, actual ); + } + + public void testRecursiveCycle() + { + PropertiesExpressionSource propsource = new PropertiesExpressionSource(); + Properties props = new Properties(); + + // Create intentional recursive lookup. + props.setProperty( "main.dir", "${test.dir}/target/classes" ); + props.setProperty( "test.dir", "${main.dir}/target/test-classes" ); + + propsource.setProperties( props ); + + evaluator.addExpressionSource( propsource ); + evaluator.addExpressionSource( new SystemPropertyExpressionSource() ); + + try + { + evaluator.expand( "My main dir is ${main.dir}" ); + fail( "Should have thrown an EvaluatorException due to recursive cycle." ); + } + catch ( EvaluatorException e ) + { + // Expected path. + } + } +}
