diff --git a/code/internal/+openminds/+internal/+resolver/LinkResolverRegistry.m b/code/internal/+openminds/+internal/+resolver/LinkResolverRegistry.m index 1234145b..166514ce 100644 --- a/code/internal/+openminds/+internal/+resolver/LinkResolverRegistry.m +++ b/code/internal/+openminds/+internal/+resolver/LinkResolverRegistry.m @@ -15,23 +15,33 @@ end methods - function addLinkResolver(obj, resolver) - % Add a resolver instance to the registry (no duplicates by handle). + function addLinkResolver(obj, resolver, options) + % Add a resolver to the registry, at most one per IRI prefix. + % + % By default a resolver whose prefix is already registered is + % ignored, so registering at startup is idempotent. Pass + % Replace=true to swap the registered resolver for this one, + % keeping its position: a library that reconfigures, for example + % with a new server or credentials, re-registers rather than + % mutating the resolver it registered earlier. arguments obj (1,1) openminds.internal.resolver.LinkResolverRegistry resolver (1,1) {mustBeA(resolver, "openminds.interface.LinkResolver")} + options.Replace (1,1) logical = false end - if obj.hasResolverForPrefix(resolver.IRIPrefix) - % Already registered - return - end + existingIndex = obj.indexOfPrefix(resolver.IRIPrefix); - obj.LinkResolvers{end+1} = resolver; + if isempty(existingIndex) + obj.LinkResolvers{end+1} = resolver; + elseif options.Replace + obj.LinkResolvers{existingIndex} = resolver; + end + % Otherwise already registered; keep the existing resolver. end function tf = hasResolverForPrefix(obj, iriPrefix) - tf = any( cellfun(@(r) r.IRIPrefix == iriPrefix, obj.LinkResolvers) ); + tf = ~isempty( obj.indexOfPrefix(iriPrefix) ); end function resolver = getLinkResolver(obj, IRI) @@ -70,6 +80,11 @@ function reset(obj) end methods (Access = private) + function index = indexOfPrefix(obj, iriPrefix) + % Position of the resolver registered for a prefix, or empty. + index = find( cellfun(@(r) r.IRIPrefix == iriPrefix, obj.LinkResolvers), 1 ); + end + function promoteResolver(obj, index) % promoteResolver - Reorder registry so the resolver at index is first arguments diff --git a/code/internal/+openminds/registerLinkResolver.m b/code/internal/+openminds/registerLinkResolver.m index 98c90fea..041024b0 100644 --- a/code/internal/+openminds/registerLinkResolver.m +++ b/code/internal/+openminds/registerLinkResolver.m @@ -1,14 +1,25 @@ -function registerLinkResolver(linkResolver) +function registerLinkResolver(linkResolver, options) % registerLinkResolver - Register a link resolver in the link resolver registry % +% openminds.registerLinkResolver(linkResolver) registers a resolver for +% the identifier prefix it declares. Registering a prefix that is +% already registered does nothing, so calling this from library startup +% code is idempotent. +% +% openminds.registerLinkResolver(linkResolver, Replace=true) replaces +% the resolver registered for that prefix with this one. Use this when +% a library reconfigures, for example connecting to a different server, +% and needs its new resolver to take effect. +% % See also: % openminds.interface.LinkResolver % openminds.internal.resolver.InstanceResolver arguments linkResolver (1,1) {mustBeA(linkResolver, "openminds.interface.LinkResolver")} + options.Replace (1,1) logical = false end resolverRegistry = openminds.internal.resolver.LinkResolverRegistry.instance(); - resolverRegistry.addLinkResolver(linkResolver); + resolverRegistry.addLinkResolver(linkResolver, "Replace", options.Replace); end diff --git a/tools/tests/unitTests/ResolverTest.m b/tools/tests/unitTests/ResolverTest.m index 6f06f838..154b7cc8 100644 --- a/tools/tests/unitTests/ResolverTest.m +++ b/tools/tests/unitTests/ResolverTest.m @@ -140,6 +140,33 @@ function testResolverRegistryPromotesUsedResolver(testCase) testCase.verifyEqual(registry.LinkResolvers{1}, mockResolver); end + function testReplaceRegisteredResolver(testCase) + % A library that reconfigures needs its new resolver to take + % effect. Without Replace the first registration wins; with it, + % the resolver for that prefix is swapped and keeps its position. + + registry = openminds.internal.resolver.LinkResolverRegistry.instance(); + + firstResolver = ommtest.helper.mock.MockLinkResolver(); + secondResolver = ommtest.helper.mock.MockLinkResolver(); + openminds.registerLinkResolver(firstResolver); + + openminds.registerLinkResolver(secondResolver); + registered = registry.getLinkResolver("https://mock.io/x"); + testCase.verifyTrue(registered == firstResolver, ... + 'Without Replace, the first registration should win.') + + openminds.registerLinkResolver(secondResolver, "Replace", true); + registered = registry.getLinkResolver("https://mock.io/x"); + testCase.verifyTrue(registered == secondResolver, ... + 'With Replace, the new resolver should take over the prefix.') + + testCase.verifyEqual( ... + sum(cellfun(@(r) isa(r, 'ommtest.helper.mock.MockLinkResolver'), ... + registry.LinkResolvers)), 1, ... + 'Replacing should not add a second resolver for the prefix.') + end + function testNoDuplicateResolvers(testCase) % Test that duplicate resolvers are not added resolver = ommtest.helper.mock.MockLinkResolver();