Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apn disable dynamic property access #1

Closed
wants to merge 16 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,16 @@ public class ExpressionConfig implements Serializable {

private final boolean disableMethodExecutionForUnknown;

private final boolean dynamicPropertyAccessAllowed;

public ExpressionConfig(Map<String, WithCategories<Object>> globalProcessVariables, List<WithCategories<String>> globalImports, List<Class<?>> additionalClasses) {
this(globalProcessVariables, globalImports, additionalClasses, new LanguageConfiguration(List$.MODULE$.empty()), true, true, Collections.emptyMap(), false, true);
this(globalProcessVariables, globalImports, additionalClasses, new LanguageConfiguration(List$.MODULE$.empty()), true, true, Collections.emptyMap(), false, true, false);
}

public ExpressionConfig(Map<String, WithCategories<Object>> globalProcessVariables, List<WithCategories<String>> globalImports,
List<Class<?>> additionalClasses, LanguageConfiguration languages, boolean optimizeCompilation, boolean strictTypeChecking,
Map<String, WithCategories<DictDefinition>> dictionaries, boolean hideMetaVariable, boolean disableMethodExecutionForUnknown) {
Map<String, WithCategories<DictDefinition>> dictionaries, boolean hideMetaVariable, boolean disableMethodExecutionForUnknown,
boolean dynamicPropertyAccessAllowed) {
this.globalProcessVariables = globalProcessVariables;
this.globalImports = globalImports;
this.additionalClasses = additionalClasses;
Expand All @@ -47,6 +50,7 @@ public ExpressionConfig(Map<String, WithCategories<Object>> globalProcessVariabl
this.dictionaries = dictionaries;
this.hideMetaVariable = hideMetaVariable;
this.disableMethodExecutionForUnknown = disableMethodExecutionForUnknown;
this.dynamicPropertyAccessAllowed = dynamicPropertyAccessAllowed;
}

public Map<String, WithCategories<Object>> getGlobalProcessVariables() {
Expand Down Expand Up @@ -85,6 +89,10 @@ public boolean isDisableMethodExecutionForUnknown() {
return disableMethodExecutionForUnknown;
}

public boolean isDynamicPropertyAccessAllowed() {
return dynamicPropertyAccessAllowed;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -98,13 +106,14 @@ public boolean equals(Object o) {
Objects.equals(languages, that.languages) &&
Objects.equals(dictionaries, that.dictionaries) &&
Objects.equals(hideMetaVariable, that.hideMetaVariable) &&
Objects.equals(disableMethodExecutionForUnknown, that.disableMethodExecutionForUnknown);
Objects.equals(disableMethodExecutionForUnknown, that.disableMethodExecutionForUnknown) &&
Objects.equals(dynamicPropertyAccessAllowed, that.dynamicPropertyAccessAllowed);
}

@Override
public int hashCode() {
return Objects.hash(globalProcessVariables, globalImports, additionalClasses, languages, optimizeCompilation,
strictTypeChecking, dictionaries, hideMetaVariable, disableMethodExecutionForUnknown);
strictTypeChecking, dictionaries, hideMetaVariable, disableMethodExecutionForUnknown, dynamicPropertyAccessAllowed);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ case class ExpressionConfig(globalProcessVariables: Map[String, WithCategories[A
hideMetaVariable: Boolean = false,
strictMethodsChecking: Boolean = true,
staticMethodInvocationsChecking: Boolean = false,
disableMethodExecutionForUnknown: Boolean = false
disableMethodExecutionForUnknown: Boolean = false,
dynamicPropertyAccessAllowed: Boolean = false
)

object ExpressionConfig {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import pl.touk.nussknacker.engine.graph.expression.Expression
class SpelBenchmarkSetup(expression: String, vars: Map[String, AnyRef]) {

private val expressionDefinition = ExpressionDefinition(globalVariables = Map(), globalImports = Nil, additionalClasses = List(),
languages = LanguageConfiguration.default, optimizeCompilation = true,
strictTypeChecking = true, dictionaries = Map.empty, hideMetaVariable = false, strictMethodsChecking = true, staticMethodInvocationsChecking = false, disableMethodExecutionForUnknown = false)
languages = LanguageConfiguration.default, optimizeCompilation = true, strictTypeChecking = true, dictionaries = Map.empty, hideMetaVariable = false,
strictMethodsChecking = true, staticMethodInvocationsChecking = false, disableMethodExecutionForUnknown = false, dynamicPropertyAccessAllowed = false)

private val expressionCompiler = ExpressionCompiler.withOptimization(
getClass.getClassLoader, new SimpleDictRegistry(Map.empty), expressionDefinition, settings = ClassExtractionSettings.Default, typeDefinitionSet = TypeDefinitionSet.empty)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,8 @@ class AvroSchemaSpelExpressionSpec extends FunSpec with Matchers {

private def parse[T:TypeTag](expr: String, validationCtx: ValidationContext) : ValidatedNel[ExpressionParseError, TypedExpression] = {
SpelExpressionParser.default(getClass.getClassLoader, new SimpleDictRegistry(Map(dictId -> EmbeddedDictDefinition(Map("key1" -> "value1")))), enableSpelForceCompile = true,
strictTypeChecking = true, Nil, Standard, strictMethodsChecking = true, staticMethodInvocationsChecking = false, TypeDefinitionSet.empty, disableMethodExecutionForUnknown = false)(ClassExtractionSettings.Default).parse(expr, validationCtx, Typed.fromDetailedType[T])
strictTypeChecking = true, Nil, Standard, strictMethodsChecking = true, staticMethodInvocationsChecking = false, TypeDefinitionSet.empty, disableMethodExecutionForUnknown = false,
dynamicPropertyAccessAllowed = false)(ClassExtractionSettings.Default).parse(expr, validationCtx, Typed.fromDetailedType[T])
}

private def wrapWithRecordSchema(fieldsDefinition: String) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ object ExpressionCompiler {
private def default(loader: ClassLoader, dictRegistry: DictRegistry, expressionConfig: ExpressionDefinition[ObjectMetadata],
optimizeCompilation: Boolean, settings: ClassExtractionSettings, typeDefinitionSet: TypeDefinitionSet): ExpressionCompiler = {
val defaultParsers = Seq(
SpelExpressionParser.default(loader, dictRegistry, optimizeCompilation, expressionConfig.strictTypeChecking,
expressionConfig.globalImports, SpelExpressionParser.Standard, expressionConfig.strictMethodsChecking,
expressionConfig.staticMethodInvocationsChecking, typeDefinitionSet, expressionConfig.disableMethodExecutionForUnknown)(settings),
SpelExpressionParser.default(loader, dictRegistry, optimizeCompilation, expressionConfig.strictTypeChecking,
expressionConfig.globalImports, SpelExpressionParser.Template, expressionConfig.strictMethodsChecking,
expressionConfig.staticMethodInvocationsChecking, typeDefinitionSet, expressionConfig.disableMethodExecutionForUnknown)(settings),
SpelExpressionParser.default(loader, dictRegistry, optimizeCompilation, expressionConfig.strictTypeChecking, expressionConfig.globalImports,
SpelExpressionParser.Standard, expressionConfig.strictMethodsChecking, expressionConfig.staticMethodInvocationsChecking,
typeDefinitionSet, expressionConfig.disableMethodExecutionForUnknown, expressionConfig.dynamicPropertyAccessAllowed)(settings),
SpelExpressionParser.default(loader, dictRegistry, optimizeCompilation, expressionConfig.strictTypeChecking, expressionConfig.globalImports,
SpelExpressionParser.Template, expressionConfig.strictMethodsChecking, expressionConfig.staticMethodInvocationsChecking,
typeDefinitionSet, expressionConfig.disableMethodExecutionForUnknown, expressionConfig.dynamicPropertyAccessAllowed)(settings),
SqlExpressionParser)
val parsersSeq = defaultParsers ++ expressionConfig.languages.expressionParsers
val parsers = parsersSeq.map(p => p.languageId -> p).toMap
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ object ProcessDefinitionExtractor {
expressionConfig.hideMetaVariable,
expressionConfig.strictMethodsChecking,
expressionConfig.staticMethodInvocationsChecking,
expressionConfig.disableMethodExecutionForUnknown
expressionConfig.disableMethodExecutionForUnknown,
expressionConfig.dynamicPropertyAccessAllowed
), settings)
}

Expand Down Expand Up @@ -153,7 +154,8 @@ object ProcessDefinitionExtractor {
definition.expressionConfig.hideMetaVariable,
definition.expressionConfig.strictMethodsChecking,
definition.expressionConfig.staticMethodInvocationsChecking,
definition.expressionConfig.disableMethodExecutionForUnknown
definition.expressionConfig.disableMethodExecutionForUnknown,
definition.expressionConfig.dynamicPropertyAccessAllowed
)
ProcessDefinition(
definition.services.mapValuesNow(_.objectDefinition),
Expand All @@ -170,6 +172,7 @@ object ProcessDefinitionExtractor {
case class ExpressionDefinition[+T <: ObjectMetadata](globalVariables: Map[String, T], globalImports: List[String], additionalClasses: List[Class[_]],
languages: LanguageConfiguration, optimizeCompilation: Boolean, strictTypeChecking: Boolean,
dictionaries: Map[String, DictDefinition], hideMetaVariable: Boolean, strictMethodsChecking: Boolean,
staticMethodInvocationsChecking: Boolean, disableMethodExecutionForUnknown: Boolean)
staticMethodInvocationsChecking: Boolean, disableMethodExecutionForUnknown: Boolean,
dynamicPropertyAccessAllowed: Boolean)

}
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,8 @@ object SpelExpressionParser extends LazyLogging {
strictMethodsChecking: Boolean,
staticMethodInvocationsChecking: Boolean,
typeDefinitionSet: TypeDefinitionSet,
disableMethodExecutionForUnknown: Boolean)
disableMethodExecutionForUnknown: Boolean,
dynamicPropertyAccessAllowed: Boolean)
(implicit classExtractionSettings: ClassExtractionSettings): SpelExpressionParser = {
val functions = Map(
"today" -> classOf[LocalDate].getDeclaredMethod("now"),
Expand All @@ -227,7 +228,7 @@ object SpelExpressionParser extends LazyLogging {
val commonSupertypeFinder = new CommonSupertypeFinder(classResolutionStrategy, strictTypeChecking)
val evaluationContextPreparer = new EvaluationContextPreparer(classLoader, imports, propertyAccessors, functions)
val validator = new SpelExpressionValidator(new Typer(classLoader, commonSupertypeFinder, new KeysDictTyper(dictRegistry),
strictMethodsChecking, staticMethodInvocationsChecking, typeDefinitionSet, evaluationContextPreparer, disableMethodExecutionForUnknown))
strictMethodsChecking, staticMethodInvocationsChecking, typeDefinitionSet, evaluationContextPreparer, disableMethodExecutionForUnknown, dynamicPropertyAccessAllowed))
new SpelExpressionParser(parser, validator, dictRegistry, enableSpelForceCompile, flavour, evaluationContextPreparer)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ private[spel] class Typer(classLoader: ClassLoader, commonSupertypeFinder: Commo
staticMethodInvocationsChecking: Boolean,
typeDefinitionSet: TypeDefinitionSet,
evaluationContextPreparer: EvaluationContextPreparer,
disableMethodExecutionForUnknown: Boolean
disableMethodExecutionForUnknown: Boolean,
dynamicPropertyAccessAllowed: Boolean
)(implicit settings: ClassExtractionSettings) extends LazyLogging {

import ast.SpelAst._
Expand Down Expand Up @@ -102,6 +103,25 @@ private[spel] class Typer(classLoader: ClassLoader, commonSupertypeFinder: Commo
throw new SpelCompilationException(node, e)
}

def typeUnion(e: Indexer, possibleTypes: Set[SingleTypingResult]): NodeTypingResult = {
val typedPossibleTypes = possibleTypes.map(possibleType => typeIndexer(e, possibleType)).toList

val typingResult = typedPossibleTypes.sequence.map(_.map(_.finalResult.typingResult).toSet).map(typingResults => Typed.apply(typingResults))
typingResult.map(toResult)
}

@tailrec
def typeIndexer(e: Indexer, typingResult: TypingResult): NodeTypingResult = {
typingResult match {
case TypedClass(clazz, param :: Nil) if clazz.isAssignableFrom(classOf[java.util.List[_]]) => valid(param)
case TypedClass(clazz, keyParam :: valueParam :: Nil) if clazz.isAssignableFrom(classOf[java.util.Map[_, _]]) => valid(valueParam)
case d: TypedDict => dictTyper.typeDictValue(d, e).map(toResult)
case TypedUnion(possibleTypes) => typeUnion(e, possibleTypes)
case TypedTaggedValue(underlying, _) => typeIndexer(e, underlying)
case _ => if(dynamicPropertyAccessAllowed) valid(Unknown) else invalid("Dynamic property access is not allowed")
}
}

catchUnexpectedErrors(node match {

case e: Assign => invalid("Value modifications are not supported")
Expand Down Expand Up @@ -129,13 +149,10 @@ private[spel] class Typer(classLoader: ClassLoader, commonSupertypeFinder: Commo
//TODO: what should be here?
case e: Identifier => valid(Unknown)
//TODO: what should be here?
case e: Indexer =>
current.stack match {
case TypingResultWithContext(TypedClass(clazz, param :: Nil), _) :: Nil if clazz.isAssignableFrom(classOf[java.util.List[_]]) => valid(param)
case TypingResultWithContext(TypedClass(clazz, keyParam :: valueParam :: Nil), _):: Nil if clazz.isAssignableFrom(classOf[java.util.Map[_, _]]) => valid(valueParam)
case TypingResultWithContext((d: TypedDict), _) :: Nil => dictTyper.typeDictValue(d, e).map(typ => toResult(typ))
case _ => valid(Unknown)
}
case e: Indexer => current.stack.headOption match {
case None => invalid("Cannot do indexing here")
case Some(result) => typeIndexer(e, result.typingResult)
}

case e: BooleanLiteral => valid(Typed[Boolean])
case e: IntLiteral => valid(Typed[java.lang.Integer])
Expand Down Expand Up @@ -397,7 +414,8 @@ private[spel] class Typer(classLoader: ClassLoader, commonSupertypeFinder: Commo
Invalid(NonEmptyList.of(ExpressionParseError(message)))

def withDictTyper(dictTyper: SpelDictTyper) =
new Typer(classLoader, commonSupertypeFinder, dictTyper, strictMethodsChecking = strictMethodsChecking, staticMethodInvocationsChecking, typeDefinitionSet, evaluationContextPreparer, disableMethodExecutionForUnknown)
new Typer(classLoader, commonSupertypeFinder, dictTyper, strictMethodsChecking = strictMethodsChecking,
staticMethodInvocationsChecking, typeDefinitionSet, evaluationContextPreparer, disableMethodExecutionForUnknown, dynamicPropertyAccessAllowed)

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ object ProcessDefinitionBuilder {
ProcessDefinition(Map.empty, Map.empty, Map.empty, Map.empty, Map.empty, ObjectDefinition.noParam,
ExpressionDefinition(Map.empty, List.empty, List.empty, languages = LanguageConfiguration(List.empty),
optimizeCompilation = true, strictTypeChecking = true, dictionaries = Map.empty, hideMetaVariable = false,
strictMethodsChecking = true, staticMethodInvocationsChecking = false, disableMethodExecutionForUnknown = false), ClassExtractionSettings.Default)
strictMethodsChecking = true, staticMethodInvocationsChecking = false, disableMethodExecutionForUnknown = false,
dynamicPropertyAccessAllowed = false), ClassExtractionSettings.Default)

def withEmptyObjects(definition: ProcessDefinition[ObjectDefinition]): ProcessDefinition[ObjectWithMethodDef] = {

Expand All @@ -36,7 +37,8 @@ object ProcessDefinitionBuilder {
definition.expressionConfig.hideMetaVariable,
definition.expressionConfig.strictMethodsChecking,
definition.expressionConfig.staticMethodInvocationsChecking,
definition.expressionConfig.disableMethodExecutionForUnknown
definition.expressionConfig.disableMethodExecutionForUnknown,
definition.expressionConfig.dynamicPropertyAccessAllowed
)

ProcessDefinition(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class InterpreterSpec extends FunSuite with Matchers {
})))

override def expressionConfig(processObjectDependencies: ProcessObjectDependencies): ExpressionConfig = super.expressionConfig(processObjectDependencies)
.copy(languages = LanguageConfiguration(List(LiteralExpressionParser)))
.copy(languages = LanguageConfiguration(List(LiteralExpressionParser)), dynamicPropertyAccessAllowed = true)
}

val definitions = ProcessDefinitionExtractor.extractObjectWithMethods(configCreator, api.process.ProcessObjectDependencies(ConfigFactory.empty(), ObjectNamingProvider(getClass.getClassLoader)))
Expand Down
Loading