diff --git a/pkg/model/activities/task.go b/pkg/model/activities/task.go index e4eab53..aff39ef 100644 --- a/pkg/model/activities/task.go +++ b/pkg/model/activities/task.go @@ -100,7 +100,7 @@ func (t *Task) LoadData(ctx context.Context) error { errs.E(err)) } - if err := dii[index].Subject().Structure().Update(v); err != nil { + if err := dii[index].Subject().Structure().Update(v.Structure().Get()); err != nil { return errs.New( errs.M("couldn't update input %q", dii[index].Name()), errs.C(errorClass, errs.OperationFailed), @@ -237,40 +237,52 @@ func (t *Task) updateOutputs(s scope.Scope) ([]*data.Parameter, error) { // Outputs returns a list of output parameters of the Task func (t *Task) Outputs() []*data.ItemAwareElement { - outputs := []*data.ItemAwareElement{} + return t.getParams(data.Output) +} - opp, _ := t.IoSpec.Parameters(data.Output) - for _, op := range opp { - outputs = append(outputs, &op.ItemAwareElement) +// BindOutgoing adds new outgoing data association. +func (t *Task) BindOutgoing(oa *data.Association) error { + return t.bindAssociation(oa, data.Output) +} + +// getParams returns a list of the Task parameters input or output according to +// direction dir. +func (t *Task) getParams(dir data.Direction) []*data.ItemAwareElement { + pp := []*data.ItemAwareElement{} + + params, _ := t.IoSpec.Parameters(dir) + for _, p := range params { + pp = append(pp, &p.ItemAwareElement) } - return outputs + return pp } -// BindOutgoing adds new outgoing data association. -func (t *Task) BindOutgoing(oa *data.Association) error { - if oa == nil { +// bindAssociation binds data association to the Task according to dir either +// input or output. +func (t *Task) bindAssociation(a *data.Association, dir data.Direction) error { + if a == nil { return errs.New( errs.M("couldn't bind empty association"), errs.C(errorClass, errs.EmptyNotAllowed)) } if slices.ContainsFunc( - t.dataAssociations[data.Output], - func(a *data.Association) bool { - return a.Id() == oa.Id() + t.dataAssociations[dir], + func(da *data.Association) bool { + return da.Id() == a.Id() }) { return errs.New( errs.M("association already binded"), errs.C(errorClass, errs.DuplicateObject), - errs.D("association_id", oa.Id())) + errs.D("association_id", a.Id())) } - // TODO: Consider checking existence of output parameter equal to - // oa source. + // TODO: Consider checking existence of parameter equal to + // a source or target. - t.dataAssociations[data.Output] = append( - t.dataAssociations[data.Output], oa) + t.dataAssociations[dir] = append( + t.dataAssociations[dir], a) return nil } @@ -279,42 +291,12 @@ func (t *Task) BindOutgoing(oa *data.Association) error { // Inputs returns list of input parameters's ItemAwareElements. func (t *Task) Inputs() []*data.ItemAwareElement { - inputs := []*data.ItemAwareElement{} - - ipp, _ := t.IoSpec.Parameters(data.Input) - for _, ip := range ipp { - inputs = append(inputs, &ip.ItemAwareElement) - } - - return inputs + return t.getParams(data.Input) } // BindIncoming adds new incoming data association to the Task. func (t *Task) BindIncoming(ia *data.Association) error { - if ia == nil { - return errs.New( - errs.M("couldn't bind empty association"), - errs.C(errorClass, errs.EmptyNotAllowed)) - } - - if slices.ContainsFunc( - t.dataAssociations[data.Input], - func(a *data.Association) bool { - return a.Id() == ia.Id() - }) { - return errs.New( - errs.M("association already binded"), - errs.C(errorClass, errs.DuplicateObject), - errs.D("association_id", ia.Id())) - } - - // TODO: Consider checking existence of input parameter equal to - // oa source. - - t.dataAssociations[data.Input] = append( - t.dataAssociations[data.Input], ia) - - return nil + return t.bindAssociation(ia, data.Input) } // ----------------------------------------------------------------------------- diff --git a/pkg/model/activities/task_test.go b/pkg/model/activities/task_test.go index bab57b0..8680103 100644 --- a/pkg/model/activities/task_test.go +++ b/pkg/model/activities/task_test.go @@ -3,6 +3,7 @@ package activities_test import ( "context" "fmt" + "reflect" "slices" "testing" @@ -11,6 +12,7 @@ import ( "github.com/dr-dobermann/gobpm/pkg/model/activities" "github.com/dr-dobermann/gobpm/pkg/model/data" "github.com/dr-dobermann/gobpm/pkg/model/data/values" + dataobjects "github.com/dr-dobermann/gobpm/pkg/model/data_objects" "github.com/dr-dobermann/gobpm/pkg/model/flow" "github.com/dr-dobermann/gobpm/pkg/model/foundation" "github.com/dr-dobermann/gobpm/pkg/model/options" @@ -80,41 +82,105 @@ func TestTaskData(t *testing.T) { task, err := activities.NewTask( "test", data.WithProperties(props...), - activities.WithoutParams()) + activities.WithSet("input set", "", + data.Input, data.DefaultSet, + []*data.Parameter{ + data.MustParameter("y param", + data.MustItemAwareElement( + data.MustItemDefinition( + values.NewVariable(100.500), + foundation.WithId("y")), + data.ReadyDataState)), + }), + activities.WithSet("output set", "", + data.Output, data.DefaultSet, + []*data.Parameter{ + data.MustParameter( + "y param", + data.MustItemAwareElement( + data.MustItemDefinition( + values.NewVariable(0.0), + foundation.WithId("y")), + nil)), + })) + require.NoError(t, err) + + // set task data path + dp, err := scope.NewDataPath("/task") require.NoError(t, err) s := mockscope.NewMockScope(t) - s.On("LoadData", task, mock.AnythingOfType("*data.Property"), mock.AnythingOfType("*data.Property")). + s.On("LoadData", task, + mock.AnythingOfType("*data.Property"), + mock.AnythingOfType("*data.Property"), + mock.AnythingOfType("*data.Parameter")). Return( func(ndl scope.NodeDataLoader, dd ...data.Data) error { for _, d := range dd { - t.Log(" >> got data: ", d.Name()) - - p, ok := d.(*data.Property) - if !ok { - return fmt.Errorf("couldn't convert data %q to Property", d.Name()) + t.Log(" >> got data: ", d.Name(), " = ", d.Value().Get()) + + switch dv := d.(type) { + case *data.Property: + if idx := slices.IndexFunc( + props, + func(prop *data.Property) bool { + return dv.Id() == prop.Id() + }); idx == -1 { + return fmt.Errorf("couldn't find property %q", d.Name()) + } + case *data.Parameter: + if dv.Name() != "y param" { + return fmt.Errorf("invalid parameter name") + } + default: + return fmt.Errorf("TEST: invalid type of data: %s", + reflect.TypeOf(d).String()) } - if idx := slices.IndexFunc( - props, - func(prop *data.Property) bool { - return p.Id() == prop.Id() - }); idx == -1 { - return fmt.Errorf("couldn't find property %q", d.Name()) - } } return nil }) + s.EXPECT(). + GetDataById(dp, "y"). + Return(data.MustItemAwareElement( + data.MustItemDefinition( + values.NewVariable(23.02), + foundation.WithId("y")), + data.ReadyDataState), nil) - dp, err := scope.NewDataPath("/task") + err = task.RegisterData(dp, s) require.NoError(t, err) - err = task.RegisterData(dp, s) + // add association to DataObject + inpDO, err := dataobjects.New( + "input data object", + data.MustItemDefinition( + values.NewVariable(23.02), + foundation.WithId("y")), + data.ReadyDataState) + require.NoError(t, err) + require.NoError(t, inpDO.AssociateTarget(task, nil)) + + outDO, err := dataobjects.New( + "output data object", + data.MustItemDefinition( + values.NewVariable(11.09), + foundation.WithId("y")), + nil) require.NoError(t, err) + require.NoError(t, outDO.AssociateSource(task, []string{"y"}, nil)) require.NoError(t, task.LoadData(context.Background())) + + // check input parameters + inParams, err := task.IoSpec.Parameters(data.Input) + require.NoError(t, err) + require.Len(t, inParams, 1) + require.Equal(t, 23.02, inParams[0].Subject().Structure().Get()) + require.NoError(t, task.UploadData(context.Background(), s)) + require.Equal(t, 23.02, outDO.Subject().Structure().Get()) }) t.Run("data associations", diff --git a/pkg/model/data/association.go b/pkg/model/data/association.go index a688e71..e533388 100644 --- a/pkg/model/data/association.go +++ b/pkg/model/data/association.go @@ -132,7 +132,7 @@ func NewAssociation( return aCfg.newAssociation() } -// UpdateSource updates source with a new value. +// UpdateSource updates association source and target with a new value. func (a *Association) UpdateSource( ctx context.Context, iDef *ItemDefinition, @@ -143,6 +143,7 @@ func (a *Association) UpdateSource( errs.C(errorClass, errs.EmptyNotAllowed)) } + // find correlated source ItemAwareElement iae, ok := a.sources[iDef.Id()] if !ok { return errs.New( @@ -152,11 +153,13 @@ func (a *Association) UpdateSource( errs.D("source_id", iDef.Id())) } + // update source and its status if err := iae.Value().Update(iDef.structure.Get()); err != nil { return errs.New( errs.M("source updating failed"), errs.C(errorClass, errs.OperationFailed), errs.E(err), + errs.D("source_id", iDef.Id()), errs.D("association_id", a.Id())) } @@ -168,7 +171,17 @@ func (a *Association) UpdateSource( errs.D("source_id", iae.ItemDefinition().Id())) } - if err := a.target.UpdateState(UnavailableDataState); err != nil { + // update target and its status + if err := a.target.Value().Update(iDef.structure.Get()); err != nil { + return errs.New( + errs.M("target updating failed"), + errs.C(errorClass, errs.OperationFailed), + errs.E(err), + errs.D("target_id", a.target.subject.Id()), + errs.D("association_id", a.Id())) + } + + if err := a.target.UpdateState(ReadyDataState); err != nil { return errs.New( errs.M("association target state update failed"), errs.C(errorClass, errs.OperationFailed), diff --git a/pkg/model/data/association_test.go b/pkg/model/data/association_test.go index aa07392..567c279 100644 --- a/pkg/model/data/association_test.go +++ b/pkg/model/data/association_test.go @@ -98,7 +98,7 @@ func TestAssociations(t *testing.T) { foundation.WithId("source"))) require.NoError(t, err) - require.False(t, a.IsReady()) + require.True(t, a.IsReady()) v, err = a.Value(context.Background())