Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
dbeuchler committed Mar 1, 2021
2 parents 73154ad + 77a766d commit 7d51c6b
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 32 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.2.1
5.3.0
9 changes: 9 additions & 0 deletions src/Moryx.AbstractionLayer/Activities/Activity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using Moryx.AbstractionLayer.Capabilities;
using Moryx.Tools;

namespace Moryx.AbstractionLayer
{
Expand Down Expand Up @@ -40,6 +41,11 @@ public abstract class Activity : IActivity
///
public ActivityResult Result { get; set; }

/// <summary>
/// Name of the activity
/// </summary>
public string Name { get; set; }

#endregion

/// <summary>
Expand All @@ -48,6 +54,9 @@ public abstract class Activity : IActivity
internal Activity()
{
Tracing = new Tracing();

var displayName = GetType().GetDisplayName();
Name = string.IsNullOrEmpty(displayName) ? GetType().Name : displayName;
}

/// <inheritdoc />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ public class IdentifierShortCut : BindingResolverBase
/// <inheritdoc />
protected override object Resolve(object source)
{
return (source as IIdentifiableObject)?.Identity.Identifier;
return (source as IIdentifiableObject)?.Identity?.Identifier;
}

/// <inheritdoc />
Expand Down
13 changes: 13 additions & 0 deletions src/Moryx.AbstractionLayer/Tasks/INamedTaskStep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Moryx.AbstractionLayer
{
/// <summary>
/// Additional interface for named task steps
/// </summary>
public interface INamedTaskStep
{
/// <summary>
/// Name of the task step
/// </summary>
string Name { get; set; }
}
}
17 changes: 15 additions & 2 deletions src/Moryx.AbstractionLayer/Tasks/TaskStep.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Reflection;
using System.Runtime.Serialization;
using Moryx.Serialization;
using Moryx.Tools;
using Moryx.Workflows;
using Moryx.Workflows.Transitions;
using Moryx.Workflows.WorkplanSteps;
Expand All @@ -19,7 +20,7 @@ namespace Moryx.AbstractionLayer
/// <typeparam name="TParam">Type of the parameters object</typeparam>
/// <typeparam name="TProcParam">Intermediate type object for the parameter of the activity</typeparam>
[DataContract(IsReference = true)]
public abstract class TaskStep<TActivity, TProcParam, TParam> : WorkplanStepBase, ITaskStep<TParam>
public abstract class TaskStep<TActivity, TProcParam, TParam> : WorkplanStepBase, ITaskStep<TParam>, INamedTaskStep
where TActivity : IActivity<TProcParam>, new()
where TProcParam : IParameters
where TParam : TProcParam, new()
Expand All @@ -32,13 +33,22 @@ public abstract class TaskStep<TActivity, TProcParam, TParam> : WorkplanStepBase
[DataMember, EntrySerialize]
public TParam Parameters { get; set; }

/// <inheritdoc />
string INamedTaskStep.Name { get; set; }

/// <inheritdoc />
public override string Name => ((INamedTaskStep)this).Name;

/// <summary>
/// Instantiate task and provide possible results
/// </summary>
protected TaskStep()
{
Parameters = new TParam();

var displayName = GetType().GetDisplayName();
((INamedTaskStep)this).Name = string.IsNullOrEmpty(displayName) ? GetType().Name : displayName;

var resultEnum = typeof(TActivity).GetCustomAttribute<ActivityResultsAttribute>().ResultEnum;
OutputDescriptions = DescriptionsFromEnum(resultEnum);
Outputs = new IConnector[OutputDescriptions.Length];
Expand Down Expand Up @@ -94,7 +104,10 @@ protected override TransitionBase Instantiate(IWorkplanContext context)

// Create transition
var indexResolver = _indexResolver ?? (_indexResolver = TransitionBase.CreateIndexResolver(OutputDescriptions));
return new TaskTransition<TActivity>(Parameters, indexResolver);
return new TaskTransition<TActivity>(Parameters, indexResolver)
{
Name = Name
};
}
}

Expand Down
9 changes: 9 additions & 0 deletions src/Moryx.AbstractionLayer/Tasks/TaskTransition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public class TaskTransition<TActivity> : TransitionBase, IObservableTransition,
/// </summary>
private readonly IIndexResolver _indexResolver;

/// <summary>
/// Name of the task
/// </summary>
internal string Name { get; set; }

/// <summary>
/// Create a new instance of the <see cref="TaskTransition{T}"/>
/// </summary>
Expand Down Expand Up @@ -96,6 +101,10 @@ public IActivity CreateActivity(IProcess process)
Process = process
};

// Set name of activity
if (activity is Activity activityClass)
activityClass.Name = Name;

// Link objects
process.AddActivity(activity);

Expand Down
24 changes: 8 additions & 16 deletions src/Moryx.Products.Management/Implementation/RecipeStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,18 +133,16 @@ private static List<IWorkplanStep> LoadSteps(WorkplanEntity workplan, IDictionar
}

// Restore parameters from JSON
var taskStep = step as ITaskStep<IParameters>;
if (taskStep != null)
{
if (step is ITaskStep<IParameters> taskStep)
JsonConvert.PopulateObject(stepEntity.Parameters, taskStep.Parameters);
}

// Restore Subworkplan if necessary
var subworkplanStep = step as ISubworkplanStep;
if (subworkplanStep != null)
{
if (step is ISubworkplanStep subworkplanStep)
subworkplanStep.Workplan = LoadWorkplan(stepEntity.SubWorkplan);
}

// Restore step name
if (step is INamedTaskStep namedTaskStep && !string.IsNullOrEmpty(stepEntity.Name))
namedTaskStep.Name = stepEntity.Name;

// Link inputs and outputs
step.Inputs = RestoreReferences(stepEntity, ConnectorRole.Input, connectors);
Expand Down Expand Up @@ -290,18 +288,12 @@ private static IDictionary<long, StepEntity> SaveSteps(IUnitOfWork uow, Workplan
}

// Task steps need parameters
var taskStep = step as ITaskStep<IParameters>;
if (taskStep != null)
{
if (step is ITaskStep<IParameters> taskStep)
stepEntity.Parameters = JsonConvert.SerializeObject(taskStep.Parameters);
}

// Subworkplan steps and need a reference to the workplan
var subworkPlanStep = step as ISubworkplanStep;
if (subworkPlanStep != null)
{
if (step is ISubworkplanStep subworkPlanStep)
stepEntity.SubWorkplanId = subworkPlanStep.WorkplanId;
}

stepEntities[step.Id] = stepEntity;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using Moryx.AbstractionLayer.Identity;
using Moryx.AbstractionLayer.Products;
using Moryx.AbstractionLayer.Recipes;
using Moryx.Container;
Expand Down Expand Up @@ -473,20 +472,21 @@ public long SaveType(IProductType modifiedInstance)
{
using (var uow = Factory.Create())
{
var entity = SaveProduct(uow, modifiedInstance);
var productSaverContext = new ProductPartsSaverContext(uow);
var entity = SaveProduct(productSaverContext, modifiedInstance);

uow.SaveChanges();

return entity.Id;
}
}

private ProductTypeEntity SaveProduct(IUnitOfWork uow, IProductType modifiedInstance)
private ProductTypeEntity SaveProduct(ProductPartsSaverContext saverContext, IProductType modifiedInstance)
{
var strategy = TypeStrategies[modifiedInstance.GetType().Name];

// Get or create entity
var repo = uow.GetRepository<IProductTypeEntityRepository>();
var repo = saverContext.GetRepository<IProductTypeEntityRepository>();
var identity = (ProductIdentity)modifiedInstance.Identity;
ProductTypeEntity typeEntity;
var entities = repo.Linq
Expand All @@ -508,16 +508,17 @@ private ProductTypeEntity SaveProduct(IUnitOfWork uow, IProductType modifiedInst
// Check if we need to create a new version
if (typeEntity.CurrentVersion == null || typeEntity.CurrentVersion.State != (int)modifiedInstance.State || strategy.HasChanged(modifiedInstance, typeEntity.CurrentVersion))
{
var version = uow.GetRepository<IProductPropertiesRepository>().Create();
var version = saverContext.GetRepository<IProductPropertiesRepository>().Create();
version.State = (int)modifiedInstance.State;
typeEntity.SetCurrentVersion(version);
}

strategy.SaveType(modifiedInstance, typeEntity.CurrentVersion);
saverContext.EntityCache.Add(new ProductIdentity(typeEntity.Identifier,typeEntity.Revision),typeEntity);

// And nasty again!
var type = modifiedInstance.GetType();
var linkRepo = uow.GetRepository<IPartLinkRepository>();
var linkRepo = saverContext.GetRepository<IPartLinkRepository>();
foreach (var linkStrategy in LinkStrategies[strategy.TargetType.Name].Values)
{
var property = type.GetProperty(linkStrategy.PropertyName);
Expand All @@ -532,7 +533,7 @@ private ProductTypeEntity SaveProduct(IUnitOfWork uow, IProductType modifiedInst
linkEntity.Parent = typeEntity;
linkStrategy.SavePartLink(link, linkEntity);
EntityIdListener.Listen(linkEntity, link);
linkEntity.Child = GetPartEntity(uow, link);
linkEntity.Child = GetPartEntity(saverContext, link);
}
else if (linkEntity != null && link == null) // link was removed
{
Expand All @@ -542,7 +543,7 @@ private ProductTypeEntity SaveProduct(IUnitOfWork uow, IProductType modifiedInst
else if (linkEntity != null && link != null) // link was modified
{
linkStrategy.SavePartLink(link, linkEntity);
linkEntity.Child = GetPartEntity(uow, link);
linkEntity.Child = GetPartEntity(saverContext, link);
}
// else: link was null and is still null

Expand Down Expand Up @@ -574,17 +575,26 @@ where links.All(l => l.Id != link.Id)
linkEntity = typeEntity.Parts.First(p => p.Id == link.Id);
}
linkStrategy.SavePartLink(link, linkEntity);
linkEntity.Child = GetPartEntity(uow, link);
linkEntity.Child = GetPartEntity(saverContext, link);
}
}
}

return typeEntity;
}

private ProductTypeEntity GetPartEntity(IUnitOfWork uow, IProductPartLink link)
private ProductTypeEntity GetPartEntity(ProductPartsSaverContext saverContext, IProductPartLink link)
{
return link.Product.Id == 0 ? SaveProduct(uow, link.Product) : uow.GetEntity<ProductTypeEntity>(link.Product);
if (saverContext.EntityCache.ContainsKey((ProductIdentity) link.Product.Identity))
{
var part = saverContext.EntityCache[(ProductIdentity) link.Product.Identity];
EntityIdListener.Listen(part,link.Product);
return part;
}

if (link.Product.Id == 0)
return SaveProduct(saverContext, link.Product);
return saverContext.UnitOfWork.GetEntity<ProductTypeEntity>(link.Product);
}

/// <summary>
Expand Down Expand Up @@ -804,7 +814,24 @@ private class LinkStrategyCache : Dictionary<string, IDictionary<string, IProduc
IDictionary<string, IProductLinkStrategy> IDictionary<string, IDictionary<string, IProductLinkStrategy>>.this[string key]
{
get { return ContainsKey(key) ? this[key] : (this[key] = new Dictionary<string, IProductLinkStrategy>()); }
set { /*You can net set the internal cache*/ }
set { /*You can net set the internal cache*/ }
}
}

private class ProductPartsSaverContext
{
public IUnitOfWork UnitOfWork { get; }
public IDictionary<ProductIdentity, ProductTypeEntity> EntityCache { get; }

public ProductPartsSaverContext(IUnitOfWork uow)
{
UnitOfWork = uow;
EntityCache = new Dictionary<ProductIdentity, ProductTypeEntity>();
}

public T GetRepository<T>() where T : class, IRepository
{
return UnitOfWork.GetRepository<T>();
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions src/Tests/Moryx.Products.IntegrationTests/ProductStorageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,48 @@ private static WatchType SetupProduct(string watchName, string identifierPrefix,
return watch;
}

[Test]
public void PartLinksWithTheSameIdentifierAreOnlySavedOnce()
{
//Arrange
var watch = new WatchType
{
Name = "watch",
Identity = new ProductIdentity("223",1),
Needles = new List<NeedlePartLink>
{
new NeedlePartLink
{
Role = NeedleRole.Minutes,
Product = new NeedleType
{
Identity = new ProductIdentity("222", 0),
Name = "name"
}
},
new NeedlePartLink
{
Role = NeedleRole.Seconds,
Product = new NeedleType
{
Identity = new ProductIdentity("222", 0),
Name = "name"
}
}
}
};

//Act
_storage.SaveType(watch);
var minuteNeedle = watch.Needles.Find(t => t.Role == NeedleRole.Minutes);
var secondsNeedle = watch.Needles.Find(t => t.Role == NeedleRole.Seconds);

//Assert
Assert.AreNotEqual(minuteNeedle.Product.Id, 0, "Id of Needle for minutes was 0");
Assert.AreNotEqual(secondsNeedle.Product.Id, 0, "Id of Needle for seconds was 0");
Assert.AreEqual(secondsNeedle.Product.Id, minuteNeedle.Product.Id, "Both needles must have the same Id since they are the same product");
}

[Test]
public void SaveWatchProduct()
{
Expand Down

0 comments on commit 7d51c6b

Please sign in to comment.