diff --git a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should display dead-ended fragment correct #0.png b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should display dead-ended fragment correct #0.png index 41dd23fa697..7212a287a2e 100644 Binary files a/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should display dead-ended fragment correct #0.png and b/designer/client/cypress/e2e/__image_snapshots__/electron/Linux/Fragment should display dead-ended fragment correct #0.png differ diff --git a/designer/client/src/components/graph/node-modal/editors/expression/Table/TableEditor.tsx b/designer/client/src/components/graph/node-modal/editors/expression/Table/TableEditor.tsx index 6db395f3fa1..4d38ca5ffe9 100644 --- a/designer/client/src/components/graph/node-modal/editors/expression/Table/TableEditor.tsx +++ b/designer/client/src/components/graph/node-modal/editors/expression/Table/TableEditor.tsx @@ -17,7 +17,6 @@ import { PopoverPosition } from "@mui/material/Popover/Popover"; import i18next from "i18next"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import ValidationLabels from "../../../../../modals/ValidationLabels"; -import { useTypeOptions } from "../../../fragment-input-definition/FragmentInputDefinition"; import { EditorProps, ExtendedEditor } from "../Editor"; import "@glideapps/glide-data-grid/dist/index.css"; import { CellMenu, DeleteColumnMenuItem, DeleteRowMenuItem, ResetColumnWidthMenuItem } from "./CellMenu"; @@ -31,6 +30,10 @@ import { TypesMenu } from "./TypesMenu"; import { customRenderers } from "./customRenderers"; import { isDatePickerCell } from "./customCells"; import type { GetRowThemeCallback } from "@glideapps/glide-data-grid/src/internal/data-grid/render/data-grid-render.cells"; +import { useSelector } from "react-redux"; +import { getProcessDefinitionData } from "../../../../../../reducers/selectors/settings"; +import ProcessUtils from "../../../../../../common/ProcessUtils"; +import { find, head, orderBy } from "lodash"; const SUPPORTED_TYPES = [ "java.lang.String", @@ -87,6 +90,27 @@ const emptySelection = { rows: CompactSelection.empty(), }; +export function useTableEditorTypeOptions() { + const definitionData = useSelector(getProcessDefinitionData); + + const typeOptions = useMemo( + () => + definitionData?.classes?.map((type) => ({ + value: type.refClazzName as SupportedType, + label: ProcessUtils.humanReadableType(type), + })), + [definitionData?.classes], + ); + + const orderedTypeOptions = useMemo(() => orderBy(typeOptions, (item) => [item.label, item.value], ["asc"]), [typeOptions]); + + const defaultTypeOption = useMemo(() => find(typeOptions, { label: "String" }) || head(typeOptions), [typeOptions]); + return { + orderedTypeOptions, + defaultTypeOption, + }; +} + export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: EditorProps) => { const tableDateContext = useTableState(expressionObj); const [{ rows, columns }, dispatch, rawExpression] = tableDateContext; @@ -97,7 +121,7 @@ export const Table = ({ expressionObj, onValueChange, className, fieldErrors }: } }, [expressionObj.expression, onValueChange, rawExpression]); - const { defaultTypeOption, orderedTypeOptions } = useTypeOptions(); + const { defaultTypeOption, orderedTypeOptions } = useTableEditorTypeOptions(); const supportedTypes = useMemo(() => orderedTypeOptions.filter(({ value }) => SUPPORTED_TYPES.includes(value)), [orderedTypeOptions]); useEffect(() => { diff --git a/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx b/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx index 54f66ba61ee..c0aa32d74b1 100644 --- a/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx +++ b/designer/client/src/components/graph/node-modal/fragment-input-definition/FragmentInputDefinition.tsx @@ -14,13 +14,13 @@ interface Props extends Omit, "readOnly isEditMode?: boolean; } -export function useTypeOptions() { +export function useFragmentInputDefinitionTypeOptions() { const definitionData = useSelector(getProcessDefinitionData); + const typeOptions = useMemo( () => definitionData?.classes?.map((type) => ({ - // TODO: Instead of using type assertion type, set refClazzName as a union of available clazzNames - value: type.refClazzName as Value, + value: type.display as string, label: ProcessUtils.humanReadableType(type), })), [definitionData?.classes], @@ -40,7 +40,7 @@ export default function FragmentInputDefinition(props: Props): JSX.Element { const { node, setProperty, isEditMode, showValidation } = passProps; const readOnly = !isEditMode; - const { orderedTypeOptions, defaultTypeOption } = useTypeOptions(); + const { orderedTypeOptions, defaultTypeOption } = useFragmentInputDefinitionTypeOptions(); const addField = useCallback(() => { addElement("parameters", getDefaultFields(defaultTypeOption.value)); diff --git a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala index 457b17a8871..e992a79ad7a 100644 --- a/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala +++ b/designer/server/src/main/scala/pl/touk/nussknacker/ui/definition/AlignedComponentsDefinitionProvider.scala @@ -59,6 +59,7 @@ object AlignedComponentsDefinitionProvider { new BuiltInComponentsDefinitionsPreparer(designerModelData.modelData.componentsUiConfig), new FragmentComponentDefinitionExtractor( designerModelData.modelData.modelClassLoader.classLoader, + designerModelData.modelData.modelDefinitionWithClasses.classDefinitions.all, designerModelData.modelData.componentsUiConfig.groupName, designerModelData.modelData.determineDesignerWideId ), diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala index d38d98406e9..7d1ab6de70f 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/DefinitionsServiceSpec.scala @@ -296,6 +296,7 @@ class DefinitionsServiceSpec extends AnyFunSuite with Matchers with PatientScala new BuiltInComponentsDefinitionsPreparer(ComponentsUiConfigParser.parse(model.modelConfig)), new FragmentComponentDefinitionExtractor( getClass.getClassLoader, + model.modelDefinitionWithClasses.classDefinitions.all, Some(_), DesignerWideComponentId.default(processingType.stringify, _) ), diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala index 2474ffddf17..39f18a8263b 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/EdgeTypesPreparerTest.scala @@ -22,6 +22,7 @@ class EdgeTypesPreparerTest extends AnyFunSuite with Matchers with ValidatedValu test("return edge types for fragment, filters, switches and components with multiple inputs") { val sampleFragmentDef = new FragmentComponentDefinitionExtractor( getClass.getClassLoader, + Set.empty, Some(_), DesignerWideComponentId.default(Streaming.stringify, _) ) diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala index 7fd8e20e53a..b76aac15e75 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentGroupsPreparerSpec.scala @@ -190,6 +190,7 @@ class ComponentGroupsPreparerSpec new BuiltInComponentsDefinitionsPreparer(new ComponentsUiConfig(Map.empty, groupNameMapping)), new FragmentComponentDefinitionExtractor( getClass.getClassLoader, + Set.empty, Some(_), DesignerWideComponentId.default("Streaming", _) ), diff --git a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala index ed293efcf95..6e166048a48 100644 --- a/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala +++ b/designer/server/src/test/scala/pl/touk/nussknacker/ui/definition/component/ComponentsUsageHelperTest.scala @@ -176,7 +176,7 @@ class ComponentsUsageHelperTest extends AnyFunSuite with Matchers with TableDriv val alignedComponentsDefinitionProvider = new AlignedComponentsDefinitionProvider( new BuiltInComponentsDefinitionsPreparer(new ComponentsUiConfig(Map.empty, Map.empty)), - new FragmentComponentDefinitionExtractor(getClass.getClassLoader, Some(_), determineDesignerWideId), + new FragmentComponentDefinitionExtractor(getClass.getClassLoader, Set.empty, Some(_), determineDesignerWideId), modelDefinition, ProcessingMode.UnboundedStream ) diff --git a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/FlinkProcessCompilerDataFactory.scala b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/FlinkProcessCompilerDataFactory.scala index dd56b848c8f..e515bec72df 100644 --- a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/FlinkProcessCompilerDataFactory.scala +++ b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/FlinkProcessCompilerDataFactory.scala @@ -9,7 +9,7 @@ import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, ProcessConfigCr import pl.touk.nussknacker.engine.api.{JobData, MetaData, ProcessListener, ProcessVersion} import pl.touk.nussknacker.engine.compile._ import pl.touk.nussknacker.engine.compile.nodecompilation.LazyParameterCreationStrategy -import pl.touk.nussknacker.engine.definition.clazz.ClassDefinitionSet +import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinition, ClassDefinitionSet} import pl.touk.nussknacker.engine.definition.globalvariables.ExpressionConfigDefinition import pl.touk.nussknacker.engine.definition.model.{ModelDefinition, ModelDefinitionWithClasses} import pl.touk.nussknacker.engine.dict.DictServicesFactoryLoader @@ -137,13 +137,18 @@ class FlinkProcessCompilerDataFactory( modelDefinitionWithTypes.modelDefinition.expressionConfig, modelDefinitionWithTypes.classDefinitions ) - val adjustedDefinitions = adjustDefinitions(modelDefinitionWithTypes.modelDefinition, definitionContext) + val adjustedDefinitions = adjustDefinitions( + modelDefinitionWithTypes.modelDefinition, + definitionContext, + modelDefinitionWithTypes.classDefinitions.all + ) (ModelDefinitionWithClasses(adjustedDefinitions), dictRegistry) } protected def adjustDefinitions( originalModelDefinition: ModelDefinition, - definitionContext: ComponentDefinitionContext + definitionContext: ComponentDefinitionContext, + classDefinitions: Set[ClassDefinition] ): ModelDefinition = originalModelDefinition private def loadDictRegistry(userCodeClassLoader: ClassLoader) = { diff --git a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFlinkProcessCompilerDataFactory.scala b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFlinkProcessCompilerDataFactory.scala index 6cd907bc57c..2ef9887d07e 100644 --- a/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFlinkProcessCompilerDataFactory.scala +++ b/engine/flink/executor/src/main/scala/pl/touk/nussknacker/engine/process/compiler/StubbedFlinkProcessCompilerDataFactory.scala @@ -10,6 +10,7 @@ import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, ProcessConfigCr import pl.touk.nussknacker.engine.api.typed.ReturningType import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess +import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition import pl.touk.nussknacker.engine.definition.component.dynamic.DynamicComponentDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.component.methodbased.MethodBasedComponentDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.component.{ @@ -42,7 +43,8 @@ abstract class StubbedFlinkProcessCompilerDataFactory( override protected def adjustDefinitions( originalModelDefinition: ModelDefinition, - definitionContext: ComponentDefinitionContext + definitionContext: ComponentDefinitionContext, + classDefinitions: Set[ClassDefinition] ): ModelDefinition = { val usedSourceIds = process.allStartNodes .map(_.head.data) @@ -61,7 +63,8 @@ abstract class StubbedFlinkProcessCompilerDataFactory( } val fragmentParametersDefinitionExtractor = new FragmentParametersDefinitionExtractor( - definitionContext.userCodeClassLoader + definitionContext.userCodeClassLoader, + classDefinitions ) val fragmentSourceDefinitionPreparer = new StubbedFragmentSourceDefinitionPreparer( fragmentParametersDefinitionExtractor diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompiler.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompiler.scala index 13cd8b5a6f9..0d19e5b80b4 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompiler.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompiler.scala @@ -352,7 +352,7 @@ object ProcessValidator { val nodeCompiler = new NodeCompiler( modelDefinition, - new FragmentParametersDefinitionExtractor(classLoader), + new FragmentParametersDefinitionExtractor(classLoader, definitionWithTypes.classDefinitions.all), expressionCompiler, classLoader, Seq.empty, diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala index f80282eba93..a75d21914f3 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/ProcessCompilerData.scala @@ -53,7 +53,7 @@ object ProcessCompilerData { // for testing environment it's important to take classloader from user jar val nodeCompiler = new NodeCompiler( definitionWithTypes.modelDefinition, - new FragmentParametersDefinitionExtractor(userCodeClassLoader), + new FragmentParametersDefinitionExtractor(userCodeClassLoader, definitionWithTypes.classDefinitions.all), expressionCompiler, userCodeClassLoader, listeners, diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala index 92daaf69c35..4c502316fff 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidator.scala @@ -3,6 +3,7 @@ package pl.touk.nussknacker.engine.compile.nodecompilation import cats.data.Validated.{Valid, invalid, invalidNel, valid} import cats.data.{NonEmptyList, Validated, ValidatedNel} import cats.implicits.toTraverseOps +import org.apache.commons.lang3.ClassUtils import pl.touk.nussknacker.engine.api.NodeId import pl.touk.nussknacker.engine.api.context.ProcessCompilationError._ import pl.touk.nussknacker.engine.api.context.{PartSubGraphCompilationError, ProcessCompilationError, ValidationContext} @@ -14,9 +15,12 @@ import pl.touk.nussknacker.engine.api.parameter.{ ValueInputWithDictEditor, ValueInputWithFixedValuesProvided } -import pl.touk.nussknacker.engine.api.typed.typing.{Typed, Unknown} +import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown} import pl.touk.nussknacker.engine.api.validation.Validations.validateVariableName import pl.touk.nussknacker.engine.compile.ExpressionCompiler +import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.permittedTypesForEditors +import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition +import pl.touk.nussknacker.engine.definition.fragment.FragmentParameterTypingParser import pl.touk.nussknacker.engine.graph.expression.Expression import pl.touk.nussknacker.engine.graph.expression.Expression.Language import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.{FragmentClazzRef, FragmentParameter} @@ -29,6 +33,8 @@ import pl.touk.nussknacker.engine.graph.node.{ } import pl.touk.nussknacker.engine.language.dictWithLabel.DictKeyWithLabelExpressionParser +import scala.util.Try + object FragmentParameterValidator { val permittedTypesForEditors: List[FragmentClazzRef] = List( @@ -37,6 +43,10 @@ object FragmentParameterValidator { FragmentClazzRef[java.lang.Long] ) +} + +case class FragmentParameterValidator(classDefinitions: Set[ClassDefinition] = Set.empty) { + // This method doesn't fully validate valueEditor (see ValueEditorValidator.validateAndGetEditor comments) def validateAgainstClazzRefAndGetEditor( valueEditor: ParameterValueInput, @@ -147,9 +157,11 @@ object FragmentParameterValidator { validateNonEmptyDictId(dictId, fragmentParameter.name).andThen(_ => dictionaries.get(dictId) match { case Some(dictDefinition) => - val fragmentParameterTypingResult = fragmentParameter.typ - .toRuntimeClass(classLoader) - .map(Typed(_)) + val fragmentParameterTypingParser = new FragmentParameterTypingParser(classLoader, classDefinitions) + val fragmentParameterTypingResult = fragmentParameterTypingParser + .parseClassNameToTypingResult( + fragmentParameter.typ.refClazzName + ) .getOrElse(Unknown) val dictValueType = dictDefinition.valueType(dictId) diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala index f27793e2795..f82f638f67b 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeCompiler.scala @@ -14,7 +14,6 @@ import pl.touk.nussknacker.engine.api.parameter.ParameterName import pl.touk.nussknacker.engine.api.process.{ComponentUseCase, Source} import pl.touk.nussknacker.engine.api.typed.ReturningType import pl.touk.nussknacker.engine.api.typed.typing.{TypingResult, Unknown} -import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator.validateParameterNames import pl.touk.nussknacker.engine.compile.nodecompilation.NodeCompiler.NodeCompilationResult import pl.touk.nussknacker.engine.compile.{ ComponentExecutorFactory, @@ -100,6 +99,8 @@ class NodeCompiler( new DynamicNodeValidator(expressionCompiler, globalVariablesPreparer, parametersEvaluator) private val builtInNodeCompiler = new BuiltInNodeCompiler(expressionCompiler) + private val fragmentParameterValidator = FragmentParameterValidator(fragmentDefinitionExtractor.classDefinitions) + def compileSource( nodeData: SourceNodeData )(implicit jobData: JobData, nodeId: NodeId): NodeCompilationResult[Source] = nodeData match { @@ -165,7 +166,7 @@ class NodeCompiler( ) } - val parameterNameValidation = validateParameterNames(parameterDefinitions.value) + val parameterNameValidation = fragmentParameterValidator.validateParameterNames(parameterDefinitions.value) // by relying on name for the field names used on FE, we display the same errors under all fields with the // duplicated name @@ -195,7 +196,7 @@ class NodeCompiler( val fixedValuesErrors = fragmentInputDefinition.parameters .map { param => - FragmentParameterValidator.validateFixedExpressionValues( + fragmentParameterValidator.validateFixedExpressionValues( param, validationContext, expressionCompiler @@ -206,7 +207,7 @@ class NodeCompiler( val dictValueEditorErrors = fragmentInputDefinition.parameters .map { param => - FragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader) + fragmentParameterValidator.validateValueInputWithDictEditor(param, expressionConfig.dictionaries, classLoader) } .sequence .map(_ => ()) diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeDataValidator.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeDataValidator.scala index 70266bba303..cca96566d05 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeDataValidator.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/compile/nodecompilation/NodeDataValidator.scala @@ -45,7 +45,10 @@ class NodeDataValidator(modelData: ModelData) { private val compiler = new NodeCompiler( modelData.modelDefinition, - new FragmentParametersDefinitionExtractor(modelData.modelClassLoader.classLoader), + new FragmentParametersDefinitionExtractor( + modelData.modelClassLoader.classLoader, + modelData.modelDefinitionWithClasses.classDefinitions.all + ), expressionCompiler, modelData.modelClassLoader.classLoader, Seq.empty, diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala index c3c426f20a3..e0bd07073c7 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentComponentDefinitionExtractor.scala @@ -10,6 +10,7 @@ import pl.touk.nussknacker.engine.api.component.{ } import pl.touk.nussknacker.engine.api.{FragmentSpecificData, NodeId} import pl.touk.nussknacker.engine.canonicalgraph.CanonicalProcess +import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition import pl.touk.nussknacker.engine.definition.component.{ ComponentDefinitionWithImplementation, ComponentImplementationInvoker @@ -18,11 +19,12 @@ import pl.touk.nussknacker.engine.util.MetaDataExtractor class FragmentComponentDefinitionExtractor( classLoader: ClassLoader, + classDefinitions: Set[ClassDefinition], translateGroupName: ComponentGroupName => Option[ComponentGroupName], determineDesignerWideId: ComponentId => DesignerWideComponentId ) { - val parametersExtractor = new FragmentParametersDefinitionExtractor(classLoader) + val parametersExtractor = new FragmentParametersDefinitionExtractor(classLoader, classDefinitions) def extractFragmentComponentDefinition( fragment: CanonicalProcess, diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParameterTypingParser.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParameterTypingParser.scala new file mode 100644 index 00000000000..989e8c5ea13 --- /dev/null +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParameterTypingParser.scala @@ -0,0 +1,45 @@ +package pl.touk.nussknacker.engine.definition.fragment + +import org.apache.commons.lang3.ClassUtils +import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult} +import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition + +import scala.util.Try + +class FragmentParameterTypingParser(classLoader: ClassLoader, classDefinitions: Set[ClassDefinition]) { + + def parseClassNameToTypingResult(className: String): Try[TypingResult] = { + /* + TODO: Write this parser in a way that handles arbitrary depth expressions + One should not use regexes for doing so and rather build AST + */ + def resolveInnerClass(simpleClassName: String): TypingResult = + classDefinitions + .find(classDefinition => classDefinition.clazzName.display == simpleClassName) + .fold( + // This is fallback - it may be removed and `ClassNotFound` exception may be thrown here after cleaning up the mess with `FragmentClazzRef` class + Typed(ClassUtils.getClass(classLoader, simpleClassName)) + ) { classDefinition => + classDefinition.clazzName + } + + val mapPattern = "Map\\[(.+),(.+)\\]".r + val listPattern = "List\\[(.+)\\]".r + val setPattern = "Set\\[(.+)\\]".r + + Try(className match { + case mapPattern(x, y) => + val resolvedFirstTypeParam = resolveInnerClass(x) + val resolvedSecondTypeParam = resolveInnerClass(y) + Typed.genericTypeClass[java.util.Map[_, _]](List(resolvedFirstTypeParam, resolvedSecondTypeParam)) + case listPattern(x) => + val resolvedTypeParam = resolveInnerClass(x) + Typed.genericTypeClass[java.util.List[_]](List(resolvedTypeParam)) + case setPattern(x) => + val resolvedTypeParam = resolveInnerClass(x) + Typed.genericTypeClass[java.util.Set[_]](List(resolvedTypeParam)) + case simpleClassName => resolveInnerClass(simpleClassName) + }) + } + +} diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParametersDefinitionExtractor.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParametersDefinitionExtractor.scala index 79352d1f719..b595499009a 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParametersDefinitionExtractor.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/fragment/FragmentParametersDefinitionExtractor.scala @@ -5,6 +5,7 @@ import cats.data.Validated.{Invalid, Valid} import cats.data.{Writer, WriterT} import cats.implicits.{catsKernelStdMonoidForList, toTraverseOps} import cats.instances.list._ +import org.apache.commons.lang3.ClassUtils import pl.touk.nussknacker.engine.api.NodeId import pl.touk.nussknacker.engine.api.component.ParameterConfig import pl.touk.nussknacker.engine.api.context.PartSubGraphCompilationError @@ -13,6 +14,7 @@ import pl.touk.nussknacker.engine.api.definition._ import pl.touk.nussknacker.engine.api.parameter.ValueInputWithDictEditor import pl.touk.nussknacker.engine.api.typed.typing.{Typed, TypingResult, Unknown} import pl.touk.nussknacker.engine.compile.nodecompilation.FragmentParameterValidator +import pl.touk.nussknacker.engine.definition.clazz.{ClassDefinition, ClassDefinitionSet} import pl.touk.nussknacker.engine.definition.component.parameter.ParameterData import pl.touk.nussknacker.engine.definition.component.parameter.defaults.{ DefaultValueDeterminerChain, @@ -28,11 +30,18 @@ import pl.touk.nussknacker.engine.graph.expression.Expression.Language import pl.touk.nussknacker.engine.graph.node.FragmentInputDefinition.FragmentParameter import pl.touk.nussknacker.engine.graph.node.{FragmentInput, FragmentInputDefinition} +import scala.util.Try + /* * This class doesn't validate the parameters' initialValue and valueEditor (e.g. values can be of incorrect type), as it would require ExpressionCompiler, ValidationContext and declared dictionaries. * They are validated separately when creating fragment in NodeCompiler.compileSource, but if they are not validated it is not a breaking issue anyway as a process using these incorrect values will fail validation. */ -class FragmentParametersDefinitionExtractor(classLoader: ClassLoader) { +class FragmentParametersDefinitionExtractor( + classLoader: ClassLoader, + val classDefinitions: Set[ClassDefinition] = Set.empty +) { + + private val fragmentParameterTypingParser = new FragmentParameterTypingParser(classLoader, classDefinitions) def extractParametersDefinition( fragmentInput: FragmentInput @@ -73,7 +82,7 @@ class FragmentParametersDefinitionExtractor(classLoader: ClassLoader) { val (extractedEditor, validationErrors) = fragmentParameter.valueEditor .map(editor => - FragmentParameterValidator.validateAgainstClazzRefAndGetEditor( + FragmentParameterValidator(classDefinitions).validateAgainstClazzRefAndGetEditor( valueEditor = editor, initialValue = fragmentParameter.initialValue, refClazz = fragmentParameter.typ, @@ -133,10 +142,9 @@ class FragmentParametersDefinitionExtractor(classLoader: ClassLoader) { private def getParamTypingResult( fragmentParameter: FragmentParameter - )(implicit nodeId: NodeId): Writer[List[PartSubGraphCompilationError], TypingResult] = - fragmentParameter.typ - .toRuntimeClass(classLoader) - .map(Typed(_)) + )(implicit nodeId: NodeId): Writer[List[PartSubGraphCompilationError], TypingResult] = { + fragmentParameterTypingParser + .parseClassNameToTypingResult(fragmentParameter.typ.refClazzName) .map(Writer.value[List[PartSubGraphCompilationError], TypingResult]) .getOrElse( Writer @@ -145,5 +153,6 @@ class FragmentParametersDefinitionExtractor(classLoader: ClassLoader) { List(FragmentParamClassLoadError(fragmentParameter.name, fragmentParameter.typ.refClazzName, nodeId.id)) ) ) + } } diff --git a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/test/ModelDataTestInfoProvider.scala b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/test/ModelDataTestInfoProvider.scala index 16c7a45babd..d2c0b815744 100644 --- a/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/test/ModelDataTestInfoProvider.scala +++ b/scenario-compiler/src/main/scala/pl/touk/nussknacker/engine/definition/test/ModelDataTestInfoProvider.scala @@ -24,7 +24,10 @@ class ModelDataTestInfoProvider(modelData: ModelData) extends TestInfoProvider w private lazy val nodeCompiler = new NodeCompiler( modelData.modelDefinition, - new FragmentParametersDefinitionExtractor(modelData.modelClassLoader.classLoader), + new FragmentParametersDefinitionExtractor( + modelData.modelClassLoader.classLoader, + modelData.modelDefinitionWithClasses.classDefinitions.all + ), expressionCompiler, modelData.modelClassLoader.classLoader, Seq.empty, diff --git a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidatorTest.scala b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidatorTest.scala index 359e7c096fd..93d4f501417 100644 --- a/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidatorTest.scala +++ b/scenario-compiler/src/test/scala/pl/touk/nussknacker/engine/compile/nodecompilation/FragmentParameterValidatorTest.scala @@ -23,7 +23,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers { forAll(permittedTypesForEditors) { fragmentParameterType: FragmentClazzRef => { val dictId = "someDictId" - val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor( + val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor( valueEditor = ValueInputWithDictEditor(dictId, allowOtherValue = false), initialValue = None, refClazz = fragmentParameterType, @@ -39,7 +39,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers { forAll(permittedTypesForEditors) { fragmentParameterType: FragmentClazzRef => { val fixedValuesList = List(FixedExpressionValue("someExpression", "someLabel")) - val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor( + val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor( valueEditor = ValueInputWithFixedValuesProvided(fixedValuesList, allowOtherValue = false), initialValue = None, refClazz = fragmentParameterType, @@ -55,7 +55,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers { val paramName = ParameterName("someParamName") val invalidParameterType = FragmentClazzRef[java.lang.Double] val nodeIds = Set("someNodeId") - val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor( + val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor( valueEditor = ValueInputWithDictEditor("someDictId", allowOtherValue = false), initialValue = None, refClazz = invalidParameterType, @@ -71,7 +71,7 @@ class FragmentParameterValidatorTest extends AnyFunSuite with Matchers { val paramName = ParameterName("someParamName") val invalidParameterType = FragmentClazzRef[java.lang.Double] val nodeIds = Set("someNodeId") - val result = FragmentParameterValidator.validateAgainstClazzRefAndGetEditor( + val result = FragmentParameterValidator().validateAgainstClazzRefAndGetEditor( valueEditor = ValueInputWithFixedValuesProvided( List(FixedExpressionValue("someExpression", "someLabel")), allowOtherValue = false diff --git a/utils/flink-components-testkit/src/main/scala/pl/touk/nussknacker/engine/flink/util/test/FlinkProcessCompilerDataFactoryWithTestComponents.scala b/utils/flink-components-testkit/src/main/scala/pl/touk/nussknacker/engine/flink/util/test/FlinkProcessCompilerDataFactoryWithTestComponents.scala index 15fcd878c7b..57e7cdd376f 100644 --- a/utils/flink-components-testkit/src/main/scala/pl/touk/nussknacker/engine/flink/util/test/FlinkProcessCompilerDataFactoryWithTestComponents.scala +++ b/utils/flink-components-testkit/src/main/scala/pl/touk/nussknacker/engine/flink/util/test/FlinkProcessCompilerDataFactoryWithTestComponents.scala @@ -8,6 +8,7 @@ import pl.touk.nussknacker.engine.api._ import pl.touk.nussknacker.engine.api.component.{ComponentAdditionalConfig, DesignerWideComponentId} import pl.touk.nussknacker.engine.api.namespaces.NamingStrategy import pl.touk.nussknacker.engine.api.process._ +import pl.touk.nussknacker.engine.definition.clazz.ClassDefinition import pl.touk.nussknacker.engine.definition.component.ComponentDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.globalvariables.GlobalVariableDefinitionWithImplementation import pl.touk.nussknacker.engine.definition.model.ModelDefinition @@ -61,7 +62,8 @@ object FlinkProcessCompilerDataFactoryWithTestComponents { override protected def adjustDefinitions( originalModelDefinition: ModelDefinition, - definitionContext: ComponentDefinitionContext + definitionContext: ComponentDefinitionContext, + classDefinitions: Set[ClassDefinition] ): ModelDefinition = { val testComponents = ComponentDefinitionWithImplementation.forList(