Permalink
Browse files

[KFI] Fix/batch actions (#195)

Extend batch copy, delete and move OData action responses with information about the changed content and the errors that occured during the operation.
  • Loading branch information...
iviczl authored and tusmester committed Oct 19, 2017
1 parent 4ebcc59 commit 3f9e5055be2054bfeb356de0af2195a2c5385147
@@ -1,10 +1,11 @@
using SenseNet.ContentRepository.i18n;
using System;
using SenseNet.ContentRepository;
using System;
using System.Collections.Generic;
using System.Linq;
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.i18n;
using SenseNet.ContentRepository.Storage;
using SenseNet.Diagnostics;
using System.Linq;
using SenseNet.Portal;
using SenseNet.Portal.OData;

namespace SenseNet.ApplicationModel
@@ -29,9 +30,9 @@ protected override string GetCallBackScript()

// =========================================================================== OData

public override bool IsODataOperation { get { return true; } }
public override bool IsODataOperation => true;

public override ActionParameter[] ActionParameters { get; } =
public override ActionParameter[] ActionParameters { get; } =
{
new ActionParameter("targetPath", typeof (string), true),
new ActionParameter("paths", typeof (object[]), true)
@@ -40,37 +41,77 @@ protected override string GetCallBackScript()
public override object Execute(Content content, params object[] parameters)
{
var targetPath = (string)parameters[0];
var exceptions = new List<Exception>();

var targetNode = Node.LoadNode(targetPath);
if (targetNode == null)
throw new ContentNotFoundException(targetPath);

var ids = parameters[1] as object[];
if (ids == null)
if (!(parameters[1] is object[] ids))
{
throw new InvalidOperationException("No content identifiers provided.");

foreach (var node in Node.LoadNodes(ids.Select(NodeIdentifier.Get)))
}

var results = new List<object>();
var errors = new List<ErrorContent>();
var identifiers = ids.Select(NodeIdentifier.Get).ToList();
var foundIdentifiers = new List<NodeIdentifier>();
var nodes = Node.LoadNodes(identifiers);

foreach (var node in nodes)
{
try
{
node?.CopyTo(targetNode);
// Collect already found identifiers in a separate list otherwise the error list
// would contain multiple errors for the same content.
foundIdentifiers.Add(NodeIdentifier.Get(node));

var copy = node.CopyToAndGetCopy(targetNode);
results.Add(new { copy.Id, copy.Path, copy.Name });
}
catch (Exception e)
{
exceptions.Add(e);

//TODO: we should log only relevant exceptions here and skip
// business logic-related errors, e.g. lack of permissions or
// existing target content path.
SnLog.WriteException(e);

errors.Add(new ErrorContent
{
Content = new {node?.Id, node?.Path, node?.Name},
Error = new Error
{
Code = "NotSpecified",
ExceptionType = e.GetType().FullName,
InnerError = new StackInfo {Trace = e.StackTrace},
Message = new ErrorMessage
{
Lang = System.Globalization.CultureInfo.CurrentUICulture.Name.ToLower(),
Value = e.Message
}
}
});
}
}

if (exceptions.Count > 0)
throw new Exception(string.Join(Environment.NewLine, exceptions.Select(e => e.Message)));
// iterating through the missing identifiers and making error items for them
errors.AddRange(identifiers.Where(id => !foundIdentifiers.Exists(f => f.Id == id.Id || f.Path == id.Path))
.Select(missing => new ErrorContent
{
Content = new {missing?.Id, missing?.Path},
Error = new Error
{
Code = "ResourceNotFound",
ExceptionType = "ContentNotFoundException",
InnerError = null,
Message = new ErrorMessage
{
Lang = System.Globalization.CultureInfo.CurrentUICulture.Name.ToLower(),
Value = string.Format(SNSR.GetString(SNSR.Exceptions.OData.ErrorContentNotFound),
missing?.Path)
}
}
}));

return null;
return BatchActionResponse.Create(results, errors, results.Count + errors.Count);
}
}
}
}
@@ -1,37 +1,28 @@
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.Schema;
using SenseNet.Portal.Virtualization;
using SenseNet.ContentRepository.i18n;
using System;
using System.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using SenseNet.ContentRepository;
using SenseNet.ContentRepository.i18n;
using SenseNet.ContentRepository.Schema;
using SenseNet.ContentRepository.Storage;
using SenseNet.Diagnostics;
using SenseNet.Portal;
using SenseNet.Portal.OData;
using SenseNet.Portal.Virtualization;

namespace SenseNet.ApplicationModel
{
public class DeleteBatchAction : ClientAction
{
public override string Callback
{
get
{
return this.Forbidden ? string.Empty : string.Format("{0};", GetCallBackScript());
}
set
{
base.Callback = value;
}
get => this.Forbidden ? string.Empty : $"{GetCallBackScript()};";
set => base.Callback = value;
}

private string _portletClientId;
public string PortletClientId
{
get
{
return _portletClientId ?? (_portletClientId = GetPortletClientId());
}
}
public string PortletClientId => _portletClientId ?? (_portletClientId = GetPortletClientId());

protected string GetPortletClientId()
{
var parameters = GetParameteres();
@@ -55,7 +46,7 @@ protected virtual string GetCallBackScript()
{
// original behavior for webforms action controls
if (PortalContext.Current.ActionName == "Explore")
redirectPath = this.Content.Path + "?action=Explore";
redirectPath = this.Content.Path + "?action=Explore";
}

return string.Format(
@@ -66,7 +57,7 @@ protected virtual string GetCallBackScript()
var contextpath = '{2}';
var redirectPath = '{3}';
SN.Util.CreateServerDialog('/Root/System/WebRoot/DeleteAction.aspx','{1}', {{paths:paths,ids:ids,contextpath:contextpath,batch:true,redirectPath:redirectPath}});",
PortletClientId,
PortletClientId,
SenseNetResourceManager.Current.GetString("ContentDelete", "DeleteStatusDialogTitle"),
this.Content.Path,
redirectPath
@@ -84,42 +75,81 @@ protected virtual string GetCallBackScript()
public override object Execute(Content content, params object[] parameters)
{
var permanent = parameters.Length > 1 && parameters[1] != null && (bool)parameters[1];
var exceptions = new List<Exception>();

// no need to throw an exception if no ids are provided: we simply do not have to delete anything
var ids = parameters[0] as object[];
if (ids == null)
if (!(parameters[0] is object[] ids))
return null;

foreach (var node in Node.LoadNodes(ids.Select(NodeIdentifier.Get)))
var results = new List<object>();
var errors = new List<ErrorContent>();
var identifiers = ids.Select(NodeIdentifier.Get).ToList();
var foundIdentifiers = new List<NodeIdentifier>();
var nodes = Node.LoadNodes(identifiers);

foreach (var node in nodes)
{
try
{
var gc = node as GenericContent;
if (gc != null)
{
gc.Delete(permanent);
}
else
// Collect already found identifiers in a separate list otherwise the error list
// would contain multiple errors for the same content.
foundIdentifiers.Add(NodeIdentifier.Get(node));

switch (node)
{
var ct = node as ContentType;
ct?.Delete();
case GenericContent gc:
gc.Delete(permanent);
break;
case ContentType ct:
ct.Delete();
break;
}

results.Add(new { node.Id, node.Path, node.Name });
}
catch (Exception e)
{
exceptions.Add(e);

//TODO: we should log only relevant exceptions here and skip
// business logic-related errors, e.g. lack of permissions or
// existing target content path.
SnLog.WriteException(e);

errors.Add(new ErrorContent
{
Content = new {node?.Id, node?.Path},
Error = new Error
{
Code = "NotSpecified",
ExceptionType = e.GetType().FullName,
InnerError = new StackInfo {Trace = e.StackTrace},
Message = new ErrorMessage
{
Lang = System.Globalization.CultureInfo.CurrentUICulture.Name.ToLower(),
Value = e.Message
}
}
});
}
}
if (exceptions.Count > 0)
throw new Exception(string.Join(Environment.NewLine, exceptions.Select(e => e.Message)));

return null;
// iterating through the missing identifiers and making error items for them
errors.AddRange(identifiers.Where(id => !foundIdentifiers.Exists(f => f.Id == id.Id || f.Path == id.Path))
.Select(missing => new ErrorContent
{
Content = new {missing?.Id, missing?.Path},
Error = new Error
{
Code = "ResourceNotFound",
ExceptionType = "ContentNotFoundException",
InnerError = null,
Message = new ErrorMessage
{
Lang = System.Globalization.CultureInfo.CurrentUICulture.Name.ToLower(),
Value = string.Format(SNSR.GetString(SNSR.Exceptions.OData.ErrorContentNotFound),
missing?.Path)
}
}
}));

return BatchActionResponse.Create(results, errors, results.Count + errors.Count);
}
}
}
}
Oops, something went wrong.

0 comments on commit 3f9e505

Please sign in to comment.