55import java .util .Collection ;
66import java .util .HashMap ;
77import java .util .Map ;
8+ import java .util .Objects ;
89import java .util .Set ;
910import java .util .function .Function ;
1011
1112import com .apicatalog .cid .document .IdentifierDocument ;
1213import com .apicatalog .cid .document .VerificationMethod ;
1314
1415/**
15- * Retrieves a verification method from {@link IdentifierDocument}.
16+ * Resolves a {@link VerificationMethod} referenced from an
17+ * {@link IdentifierDocument}, optionally resolving the controller document from
18+ * a {@link URI}.
19+ *
20+ * <p>
21+ * Supported verification relationship IRIs:
22+ * </p>
23+ * <ul>
24+ * <li><code>https://w3id.org/security#authentication</code></li>
25+ * <li><code>https://w3id.org/security#assertionMethod</code></li>
26+ * <li><code>https://w3id.org/security#keyAgreementMethod</code></li>
27+ * <li><code>https://w3id.org/security#capabilityInvocationMethod</code></li>
28+ * <li><code>https://w3id.org/security#capabilityDelegationMethod</code></li>
29+ * </ul>
1630 */
1731public class VerificationMethodResolver {
1832
33+ /** Supported verification relationships → document accessors. */
1934 protected final static Map <URI , Function <IdentifierDocument , Set <VerificationMethod >>> RELS ;
2035
2136 static {
@@ -34,52 +49,140 @@ public class VerificationMethodResolver {
3449
3550 protected final Collection <IdentifierDocumentResolver > resolvers ;
3651
37- public VerificationMethodResolver (Collection <IdentifierDocumentResolver > resolvers ) {
52+ /**
53+ * Creates a resolver that can delegate to the given
54+ * {@link IdentifierDocumentResolver}s when a controller document must be
55+ * fetched.
56+ *
57+ * @param resolvers non-empty collection of resolvers
58+ * @throws NullPointerException if {@code resolvers} is {@code null}
59+ * @throws IllegalArgumentException if {@code resolvers} is empty
60+ */
61+ public VerificationMethodResolver (final Collection <IdentifierDocumentResolver > resolvers ) {
62+ Objects .requireNonNull (resolvers , "resolvers must not be null" );
63+ if (resolvers .isEmpty ()) {
64+ throw new IllegalArgumentException ("resolvers must not be empty" );
65+ }
3866 this .resolvers = resolvers ;
3967 }
4068
41- public VerificationMethod retrieve (final URI methodId , final URI relation ) {
42-
43- try {
44- // remove fragment
45- final URI documentUri = new URI (methodId .getScheme (), methodId .getSchemeSpecificPart (), null );
46-
47- // resolve controller document
48- final IdentifierDocument document = resolvers .stream ()
49- .filter (r -> r .isAccepted (documentUri ))
50- .findFirst ()
51- .map (r -> r .resolve (documentUri ))
52- .orElseThrow (() -> new IllegalArgumentException ("INVALID_CONTROLLER_DOCUMENT" ));
53-
54- if (!document .id ().equals (documentUri )) {
55- throw new IllegalArgumentException ("INVALID_CONTROLLER_DOCUMENT_ID" );
56- }
57-
58- final Function <IdentifierDocument , Set <VerificationMethod >> methodProvider = RELS .get (relation );
59-
60- if (methodProvider == null ) {
61- throw new IllegalArgumentException ("INVALID_RELATIONSHIP_FOR_VERIFICATION_METHOD" );
62- }
69+ public VerificationMethod resolve (final URI methodId , final Set <VerificationMethod > methods , final IdentifierDocument document ) throws VerificationMethodException {
70+ final VerificationMethod method = methods .stream ()
71+ .filter (m -> methodId .equals (m .id ()))
72+ .findFirst ()
73+ .orElseThrow (() -> new VerificationMethodException (
74+ VerificationMethodException .Code .INVALID_VERIFICATION_METHOD ,
75+ "Verification method not found: " + methodId ));
76+
77+ if (!document .id ().equals (method .controller ())) {
78+ throw new VerificationMethodException (
79+ VerificationMethodException .Code .INVALID_VERIFICATION_METHOD ,
80+ "Verification method controller mismatch, expected " + document .id () + " but got " + method .controller ());
81+ }
6382
64- Set <VerificationMethod > methods = methodProvider .apply (document );
83+ return method ;
84+ }
6585
66- if (methods == null || methods .isEmpty ()) {
67- throw new IllegalArgumentException ("INVALID_VERIFICATION_METHOD" );
68- }
86+ /**
87+ * Resolves a verification method by {@code methodId} within the supplied
88+ * {@code document}, constrained by the verification {@code relation}.
89+ *
90+ * @param methodId the verification method identifier (must not be {@code null})
91+ * @param relation the verification relationship IRI (must not be {@code null})
92+ * @param document the controller document that lists the method (must not be
93+ * {@code null})
94+ * @return the matching {@link VerificationMethod}
95+ *
96+ * @throws VerificationMethodException with one of:
97+ * <ul>
98+ * <li>{@code INVALID_RELATIONSHIP_FOR_VERIFICATION_METHOD}</li>
99+ * <li>{@code INVALID_VERIFICATION_METHOD}</li>
100+ * </ul>
101+ * @throws NullPointerException if any argument is {@code null}
102+ */
103+ public VerificationMethod resolve (final URI methodId , final URI relation , final IdentifierDocument document ) throws VerificationMethodException {
104+
105+ Objects .requireNonNull (methodId , "methodId must not be null" );
106+ Objects .requireNonNull (relation , "relation must not be null" );
107+ Objects .requireNonNull (document , "document must not be null" );
108+ Objects .requireNonNull (document .id (), "document.id() must not be null" );
109+
110+ final Function <IdentifierDocument , Set <VerificationMethod >> methodProvider = RELS .get (relation );
111+
112+ if (methodProvider == null ) {
113+ throw new VerificationMethodException (
114+ VerificationMethodException .Code .INVALID_RELATIONSHIP_FOR_VERIFICATION_METHOD ,
115+ "Unsupported verification relationship: " + relation );
116+ }
69117
70- final VerificationMethod method = methods .stream ()
71- .filter (m -> methodId .equals (m .id ()))
72- .findFirst ()
73- .orElseThrow (() -> new IllegalArgumentException ("INVALID_VERIFICATION_METHOD" ));
118+ final Set <VerificationMethod > methods = methodProvider .apply (document );
119+ if (methods == null || methods .isEmpty ()) {
120+ throw new VerificationMethodException (
121+ VerificationMethodException .Code .INVALID_VERIFICATION_METHOD ,
122+ "No verification methods for relation: " + relation );
123+ }
74124
75- if (!documentUri .equals (method .controller ())) {
76- throw new IllegalArgumentException ("INVALID_VERIFICATION_METHOD" );
77- }
125+ return resolve (methodId , methods , document );
126+ }
78127
79- return method ;
128+ /**
129+ * Resolves a verification method by {@code methodId} and {@code relation},
130+ * fetching the controller document using the first
131+ * {@link IdentifierDocumentResolver} that
132+ * {@link IdentifierDocumentResolver#isAccepted(URI) accepts} the derived
133+ * document URI.
134+ *
135+ * <p>
136+ * The document URI is derived from {@code methodId} by removing its fragment.
137+ * If no resolver accepts the URI, or the resolved document’s {@code id} does
138+ * not equal the derived URI, resolution fails.
139+ * </p>
140+ *
141+ * @param methodId the verification method identifier (must not be {@code null})
142+ * @param relation the verification relationship IRI (must not be {@code null})
143+ * @return the matching {@link VerificationMethod}
144+ *
145+ * @throws VerificationMethodException with one of:
146+ * <ul>
147+ * <li>{@code INVALID_METHOD_ID}</li>
148+ * <li>{@code INVALID_CONTROLLER_DOCUMENT}</li>
149+ * <li>{@code INVALID_CONTROLLER_DOCUMENT_ID}</li>
150+ * <li>{@code INVALID_RELATIONSHIP_FOR_VERIFICATION_METHOD}</li>
151+ * <li>{@code INVALID_VERIFICATION_METHOD}</li>
152+ * </ul>
153+ * @throws NullPointerException if any argument is {@code null}
154+ */
155+ public VerificationMethod resolve (final URI methodId , final URI relation ) throws VerificationMethodException {
156+
157+ Objects .requireNonNull (methodId , "methodId must not be null" );
158+ Objects .requireNonNull (relation , "relation must not be null" );
159+
160+ final URI documentUri ;
161+ try {
162+ // Strip fragment to obtain the controller document identifier
163+ documentUri = new URI (methodId .getScheme (), methodId .getSchemeSpecificPart (), null );
80164
81165 } catch (URISyntaxException e ) {
82- throw new IllegalArgumentException (e );
166+ throw new VerificationMethodException (
167+ VerificationMethodException .Code .INVALID_METHOD_ID ,
168+ "Invalid methodId, failed to derive controller document URI" ,
169+ e );
170+ }
171+
172+ final IdentifierDocument document = resolvers .stream ()
173+ .filter (r -> r .isAccepted (documentUri ))
174+ .findFirst ()
175+ .map (r -> r .resolve (documentUri ))
176+ .orElseThrow (() -> new VerificationMethodException (
177+ VerificationMethodException .Code .INVALID_CONTROLLER_DOCUMENT ,
178+ "No resolver accepted controller document: " + documentUri ));
179+
180+ if (!documentUri .equals (document .id ())) {
181+ throw new VerificationMethodException (
182+ VerificationMethodException .Code .INVALID_CONTROLLER_DOCUMENT_ID ,
183+ "Controller document id mismatch, expected " + documentUri + " but got " + document .id ());
83184 }
185+
186+ return resolve (methodId , relation , document );
84187 }
85188}
0 commit comments