Skip to content

Commit

Permalink
fix(fdc3) - AddContextListener fix if the channel has not been set
Browse files Browse the repository at this point in the history
  • Loading branch information
lilla28 authored and kruplm committed Sep 12, 2024
1 parent 71203f8 commit 26933d3
Show file tree
Hide file tree
Showing 28 changed files with 1,068 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Morgan Stanley makes this available to you under the Apache License,
* Version 2.0 (the "License"). You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

using Finos.Fdc3;

namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;

internal sealed class AddContextListenerRequest
{
/// <summary>
/// Instance id of the app that sent the request.
/// </summary>
public string Fdc3InstanceId { get; set; }

/// <summary>
/// Type of the context that the listener should listen on.
/// </summary>
public string? ContextType { get; set; }

/// <summary>
/// The id of the channel, that the current listener is listening on.
/// </summary>
public string ChannelId { get; set; }

/// <summary>
/// The type of the channel that the current listener listens on.
/// </summary>
public ChannelType ChannelType { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Morgan Stanley makes this available to you under the Apache License,
* Version 2.0 (the "License"). You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;

internal sealed class AddContextListenerResponse
{
/// <summary>
/// The generated id of the context listener
/// </summary>
public string? Id { get; set; }

/// <summary>
/// Indicates that exception was thrown during the execution.
/// </summary>
public string? Error { get; set; }

/// <summary>
/// Indicates if the execution was successful.
/// </summary>
public bool Success { get; set; }

public static AddContextListenerResponse Failure(string error) => new() { Error = error, Success = false };
public static AddContextListenerResponse Added(string id) => new() { Id = id, Success = true };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Morgan Stanley makes this available to you under the Apache License,
* Version 2.0 (the "License"). You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/


namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;

internal sealed class RemoveContextListenerRequest
{
/// <summary>
/// Id of the instance that sent the request.
/// </summary>
public string Fdc3InstanceId { get; set; }

/// <summary>
/// Id of the context listener.
/// </summary>
public string ListenerId { get; set; }

/// <summary>
/// Indicates the type of the context for the subscription.
/// </summary>
public string? ContextType { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Morgan Stanley makes this available to you under the Apache License,
* Version 2.0 (the "License"). You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;

internal sealed class RemoveContextListenerResponse
{
/// <summary>
/// Indicates that error was thrown during the execution of the request.
/// </summary>
public string? Error { get; set; }

/// <summary>
/// Indicates the state of the request.
/// </summary>
public bool Success { get; set; }

public static RemoveContextListenerResponse Failure(string error) => new() {Error = error, Success = false};
public static RemoveContextListenerResponse Executed() => new() { Success = true};
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Contracts;

/// <summary>
/// Indicates the state of the IntentListener which was sent to the DesktopAgent backend.
/// Indicates the state of the Listener which was sent to the DesktopAgent backend.
/// </summary>
internal enum SubscribeState
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,9 @@ public static class Fdc3DesktopAgentErrors
/// Indicates that no user channel set was configured.
/// </summary>
public const string NoUserChannelSetFound = $"{nameof(NoUserChannelSetFound)}";

/// <summary>
/// Indicates that the listener was not found for execution.
/// </summary>
public const string ListenerNotFound = $"{nameof(ListenerNotFound)}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ internal class Fdc3DesktopAgent : IFdc3DesktopAgentBridge
private readonly ConcurrentDictionary<Guid, Fdc3App> _runningModules = new();
private readonly ConcurrentDictionary<Guid, RaisedIntentRequestHandler> _raisedIntentResolutions = new();
private readonly ConcurrentDictionary<StartRequest, TaskCompletionSource<IModuleInstance>> _pendingStartRequests = new();
private readonly ConcurrentDictionary<Guid, List<ContextListener>> _contextListeners = new();
private IAsyncDisposable? _subscription;
private readonly object _contextListenerLock = new();

public Fdc3DesktopAgent(
IAppDirectory appDirectory,
Expand Down Expand Up @@ -102,7 +104,7 @@ public Fdc3DesktopAgent(
{
if (_logger.IsEnabled(LogLevel.Warning))
{
_logger.LogWarning(exception, $"{channelId} is already registed as service endpoint.");
_logger.LogWarning(exception, $"{channelId} is already registered as service endpoint.");
}

return userChannel;
Expand Down Expand Up @@ -133,7 +135,7 @@ public async ValueTask AddPrivateChannel(Func<string, PrivateChannel> addPrivate
{
if (_logger.IsEnabled(LogLevel.Warning))
{
_logger.LogWarning(exception, $"{privateChannelId} is already registed as service endpoint.");
_logger.LogWarning(exception, $"{privateChannelId} is already registered as service endpoint.");
}
}
catch (Exception exception)
Expand Down Expand Up @@ -174,7 +176,7 @@ public async ValueTask<CreateAppChannelResponse> AddAppChannel(Func<string, AppC
{
if (_logger.IsEnabled(LogLevel.Warning))
{
_logger.LogWarning(exception, $"{request.ChannelId} is already registed as service endpoint.");
_logger.LogWarning(exception, $"{request.ChannelId} is already registered as service endpoint.");
}
}
catch (Exception exception)
Expand Down Expand Up @@ -232,6 +234,13 @@ public async Task StopAsync(CancellationToken cancellationToken)
_userChannels.Clear();
_privateChannels.Clear();
_appChannels.Clear();
lock (_contextListenerLock)
{
_contextListeners.Clear();
}

_pendingStartRequests.Clear();
_raisedIntentResolutions.Clear();
}

public bool FindChannel(string channelId, ChannelType channelType)
Expand Down Expand Up @@ -642,6 +651,72 @@ public async ValueTask<GetAppMetadataResponse> GetAppMetadata(GetAppMetadataRequ
}
}

public ValueTask<AddContextListenerResponse?> AddContextListener(AddContextListenerRequest? request)
{
if (request == null)
{
return ValueTask.FromResult<AddContextListenerResponse?>(AddContextListenerResponse.Failure(Fdc3DesktopAgentErrors.PayloadNull));
}

if (!Guid.TryParse(request.Fdc3InstanceId, out var originFdc3InstanceId) || !_runningModules.TryGetValue(originFdc3InstanceId, out _))
{
return ValueTask.FromResult<AddContextListenerResponse?>(AddContextListenerResponse.Failure(Fdc3DesktopAgentErrors.MissingId));
}

lock (_contextListenerLock)
{
var contextListener = new ContextListener(
request.ContextType,
request.ChannelId,
request.ChannelType);

_contextListeners.AddOrUpdate(
originFdc3InstanceId,
_ => new List<ContextListener> { contextListener },
(_, contextListeners) =>
{
contextListeners.Add(contextListener);
return contextListeners;
});

return ValueTask.FromResult<AddContextListenerResponse?>(AddContextListenerResponse.Added(contextListener.Id.ToString()));
}
}

public ValueTask<RemoveContextListenerResponse?> RemoveContextListener(RemoveContextListenerRequest? request)
{
if (request == null)
{
return ValueTask.FromResult<RemoveContextListenerResponse?>(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.PayloadNull));
}

lock (_contextListenerLock)
{
if (!Guid.TryParse(request.Fdc3InstanceId, out var originFdc3InstanceId)
|| !_runningModules.TryGetValue(originFdc3InstanceId, out _)
|| !_contextListeners.TryGetValue(originFdc3InstanceId, out var listeners)
|| request.ListenerId == null
|| !Guid.TryParse(request.ListenerId, out var listenerId))
{
return ValueTask.FromResult<RemoveContextListenerResponse?>(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.MissingId));
}

var listener = listeners.FirstOrDefault(x => x.Id == listenerId && x.ContextType == request.ContextType);
if (listener == null)
{
return ValueTask.FromResult<RemoveContextListenerResponse?>(RemoveContextListenerResponse.Failure(Fdc3DesktopAgentErrors.ListenerNotFound));
}

listeners.Remove(listener);
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("ContextListener has been successfully unsubscribed.");
}

return ValueTask.FromResult<RemoveContextListenerResponse?>(RemoveContextListenerResponse.Executed());
}
}

public async ValueTask<RaiseIntentResult<RaiseIntentResponse>> RaiseIntent(RaiseIntentRequest? request)
{
if (request == null)
Expand Down Expand Up @@ -1218,7 +1293,12 @@ private Task RemoveModuleAsync(IModuleInstance instance)
return Task.CompletedTask;
}

if (!_runningModules.TryRemove(new(fdc3InstanceId!), out _)) //At this point the fdc3InstanceId shouldn't be null
if (!Guid.TryParse(fdc3InstanceId, out var id))
{
return Task.CompletedTask;
}

if (!_runningModules.TryRemove(id, out _)) //At this point the fdc3InstanceId shouldn't be null
{
_logger.LogError($"Could not remove the closed window with instanceId: {fdc3InstanceId}.");
}
Expand All @@ -1228,6 +1308,19 @@ private Task RemoveModuleAsync(IModuleInstance instance)
taskCompletionSource.SetException(ThrowHelper.TargetInstanceUnavailable());
}

lock (_contextListenerLock)
{
if (!_contextListeners.TryRemove(id, out _))
{
_logger.LogError($"Could not remove the registered context listeners of id: {fdc3InstanceId}.");
}
}

if (!_raisedIntentResolutions.TryRemove(id, out _))
{
_logger.LogError($"Could not remove the stored intent resolutions of id: {fdc3InstanceId} which raised the intents.");
}

return Task.CompletedTask;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
* and limitations under the License.
*/

using System.Threading.Tasks.Dataflow;
using Finos.Fdc3;
using MorganStanley.ComposeUI.Messaging.Protocol.Messages;

namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent;

Expand All @@ -36,6 +34,8 @@ internal static class Fdc3Topic
internal static string GetInfo => TopicRoot + "getInfo";
internal static string FindInstances => TopicRoot + "findInstances";
internal static string GetAppMetadata => TopicRoot + "getAppMetadata";
internal static string AddContextListener => TopicRoot + "addContextListener";
internal static string RemoveContextListener => TopicRoot + "removeContextListener";

//IntentListeners will be listening at this endpoint
internal static string RaiseIntentResolution(string intent, string instanceId)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Morgan Stanley makes this available to you under the Apache License,
* Version 2.0 (the "License"). You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

using Finos.Fdc3;

namespace MorganStanley.ComposeUI.Fdc3.DesktopAgent.Infrastructure.Internal;

internal class ContextListener
{
private readonly string? _contextType;
private readonly Guid _instanceId;
private string? _channelId;
private ChannelType? _channelType;

public Guid Id => _instanceId;
public string? ContextType => _contextType;

public ContextListener(string? contextType, string channelId, ChannelType channelType)
{
_contextType = contextType;
_channelId = channelId;
_channelType = channelType;
_instanceId = Guid.NewGuid();
}
}
Loading

0 comments on commit 26933d3

Please sign in to comment.