Skip to content

Commit d06ec45

Browse files
committed
feat: Better support for resolving and dereferencing.
1 parent 9535b66 commit d06ec45

44 files changed

Lines changed: 1350 additions & 645 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 101 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,176 @@
11
package uniresolver.driver.http;
22

3-
import java.io.IOException;
4-
import java.io.UnsupportedEncodingException;
5-
import java.net.URI;
6-
import java.net.URLEncoder;
7-
import java.util.Collections;
8-
import java.util.HashMap;
9-
import java.util.List;
10-
import java.util.Map;
11-
import java.util.regex.Matcher;
12-
import java.util.regex.Pattern;
13-
14-
import foundation.identity.did.DIDDocument;
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import foundation.identity.did.DID;
5+
import foundation.identity.did.parser.ParserException;
156
import org.apache.http.HttpEntity;
167
import org.apache.http.client.HttpClient;
178
import org.apache.http.client.methods.CloseableHttpResponse;
189
import org.apache.http.client.methods.HttpGet;
10+
import org.apache.http.entity.ContentType;
1911
import org.apache.http.impl.client.HttpClients;
12+
import org.apache.http.protocol.HTTP;
2013
import org.apache.http.util.EntityUtils;
2114
import org.slf4j.Logger;
2215
import org.slf4j.LoggerFactory;
23-
24-
import com.fasterxml.jackson.databind.ObjectMapper;
25-
2616
import uniresolver.ResolutionException;
2717
import uniresolver.driver.Driver;
2818
import uniresolver.result.ResolveResult;
19+
import uniresolver.util.HttpBindingUtil;
2920

30-
public class HttpDriver implements Driver {
21+
import java.io.IOException;
22+
import java.net.URI;
23+
import java.net.URLEncoder;
24+
import java.nio.charset.Charset;
25+
import java.nio.charset.StandardCharsets;
26+
import java.util.*;
27+
import java.util.regex.Matcher;
28+
import java.util.regex.Pattern;
3129

32-
public static final String MIME_TYPES = ResolveResult.MIME_TYPE + "," + DIDDocument.MIME_TYPE_JSON_LD + "," + "application/ld+json";
30+
public class HttpDriver implements Driver {
3331

34-
private static Logger log = LoggerFactory.getLogger(HttpDriver.class);
32+
private static final Logger log = LoggerFactory.getLogger(HttpDriver.class);
3533

3634
private static final ObjectMapper objectMapper = new ObjectMapper();
3735

38-
public static final HttpClient DEFAULT_HTTP_CLIENT = HttpClients.createDefault();
39-
public static final URI DEFAULT_RESOLVE_URI = null;
40-
public static final URI DEFAULT_PROPERTIES_URI = null;
41-
public static final Pattern DEFAULT_PATTERN = null;
42-
public static final List<String> DEFAULT_TEST_IDENTIFIERS = Collections.emptyList();
43-
44-
private HttpClient httpClient = DEFAULT_HTTP_CLIENT;
45-
private URI resolveUri = DEFAULT_RESOLVE_URI;
46-
private URI propertiesUri = DEFAULT_PROPERTIES_URI;
47-
private Pattern pattern = DEFAULT_PATTERN;
48-
private List<String> testIdentifiers = DEFAULT_TEST_IDENTIFIERS;
36+
private HttpClient httpClient = HttpClients.createDefault();
37+
private URI resolveUri = null;
38+
private URI propertiesUri = null;
39+
private Pattern pattern = null;
40+
private List<String> testIdentifiers = Collections.emptyList();
4941

5042
public HttpDriver() {
5143

5244
}
5345

5446
@Override
55-
public ResolveResult resolve(String identifier) throws ResolutionException {
47+
public ResolveResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
5648

5749
if (this.getPattern() == null || this.getResolveUri() == null) return null;
5850

59-
// match identifier
51+
// match DID
6052

61-
String matchedIdentifier = null;
53+
DID matchedDid = null;
6254

6355
if (this.getPattern() != null) {
6456

65-
Matcher matcher = this.getPattern().matcher(identifier);
57+
Matcher matcher = this.getPattern().matcher(did.getDidString());
6658

6759
if (! matcher.matches()) {
68-
69-
if (log.isDebugEnabled()) log.debug("Skipping identifier " + identifier + " - does not match pattern " + this.getPattern());
60+
if (log.isDebugEnabled()) log.debug("Skipping identifier " + did + " - does not match pattern " + this.getPattern());
7061
return null;
7162
} else {
72-
73-
if (log.isDebugEnabled()) log.debug("Identifier " + identifier + " matches pattern " + this.getPattern() + " with " + matcher.groupCount() + " groups");
63+
if (log.isDebugEnabled()) log.debug("Identifier " + did + " matches pattern " + this.getPattern() + " with " + matcher.groupCount() + " groups");
7464
}
7565

7666
if (matcher.groupCount() > 0) {
7767

78-
identifier = "";
79-
for (int i=1; i<=matcher.groupCount(); i++) if (matcher.group(i) != null) identifier += matcher.group(i);
68+
String matchedDidString = "";
69+
for (int i=1; i<=matcher.groupCount(); i++) if (matcher.group(i) != null) matchedDidString += matcher.group(i);
70+
try {
71+
matchedDid = DID.fromString(matchedDidString);
72+
} catch (ParserException ex) {
73+
throw new ResolutionException("Cannot parse matched DID " + matchedDidString + ": " + ex.getMessage(), ex);
74+
}
8075
}
8176
}
8277

83-
if (matchedIdentifier == null) matchedIdentifier = identifier;
84-
85-
if (log.isDebugEnabled()) log.debug("Matched identifier: " + matchedIdentifier);
78+
if (matchedDid == null) matchedDid = did;
79+
if (log.isDebugEnabled()) log.debug("Matched DID: " + matchedDid);
8680

87-
// URL-encode identifier
81+
// URL-encode DID
8882

89-
String urlEncodedIdentifier;
90-
91-
try {
83+
String urlEncodedDid = URLEncoder.encode(matchedDid.getDidString(), StandardCharsets.UTF_8);
9284

93-
urlEncodedIdentifier = URLEncoder.encode(matchedIdentifier, "UTF-8");
94-
} catch (UnsupportedEncodingException ex) {
95-
96-
throw new ResolutionException(ex.getMessage(), ex);
97-
}
98-
99-
// prepare HTTP request
85+
// set HTTP URI
10086

10187
String uriString = this.getResolveUri().toString();
10288

10389
if (uriString.contains("$1")) {
10490

105-
uriString = uriString.replace("$1", matchedIdentifier);
91+
uriString = uriString.replace("$1", matchedDid.getDidString());
10692
} else if (uriString.contains("$2")) {
10793

108-
uriString = uriString.replace("$2", urlEncodedIdentifier);
94+
uriString = uriString.replace("$2", urlEncodedDid);
10995
} else {
11096

11197
if (! uriString.endsWith("/")) uriString += "/";
112-
uriString += matchedIdentifier;
98+
uriString += matchedDid.getDidString();
11399
}
114100

101+
// set Accept header
102+
103+
String accept = (String) resolutionOptions.get("accept");
104+
if (accept == null) throw new ResolutionException("No 'accept' provided in 'resolutionOptions' for resolveRepresentation().");
105+
106+
List<String> acceptMediaTypes = Arrays.asList(ResolveResult.MEDIA_TYPE, accept);
107+
String acceptMediaTypesString = String.join(",", acceptMediaTypes);
108+
109+
// prepare HTTP request
110+
115111
HttpGet httpGet = new HttpGet(URI.create(uriString));
116-
httpGet.addHeader("Accept", MIME_TYPES);
112+
httpGet.addHeader("Accept", acceptMediaTypesString);
117113

118-
// execute HTTP request
114+
// execute HTTP request and read response
119115

120-
ResolveResult resolveResult;
116+
ResolveResult resolveResult = null;
121117

122-
if (log.isDebugEnabled()) log.debug("Request for identifier " + identifier + " to: " + uriString);
118+
if (log.isDebugEnabled()) log.debug("Driver request for DID " + did + " to: " + uriString);
123119

124120
try (CloseableHttpResponse httpResponse = (CloseableHttpResponse) this.getHttpClient().execute(httpGet)) {
125121

122+
// execute HTTP request
123+
124+
HttpEntity httpEntity = httpResponse.getEntity();
126125
int statusCode = httpResponse.getStatusLine().getStatusCode();
127126
String statusMessage = httpResponse.getStatusLine().getReasonPhrase();
127+
ContentType contentType = ContentType.get(httpResponse.getEntity());
128+
Charset charset = contentType != null ? contentType.getCharset() : HTTP.DEF_CONTENT_CHARSET;
128129

129-
if (log.isDebugEnabled()) log.debug("Response status from " + uriString + ": " + statusCode + " " + statusMessage);
130+
if (log.isDebugEnabled()) log.debug("Driver response status from " + uriString + ": " + statusCode + " " + statusMessage);
131+
if (log.isDebugEnabled()) log.debug("Driver response content type from " + uriString + ": " + contentType + " / " + charset);
130132

131-
if (statusCode == 404) return null;
133+
// read result
132134

133-
HttpEntity httpEntity = httpResponse.getEntity();
134-
String httpBody = EntityUtils.toString(httpEntity);
135+
byte[] httpBodyBytes = EntityUtils.toByteArray(httpEntity);
136+
String httpBodyString = new String(httpBodyBytes, charset);
135137
EntityUtils.consume(httpEntity);
136138

137-
if (log.isDebugEnabled()) log.debug("Response body from " + uriString + ": " + httpBody);
139+
if (log.isDebugEnabled()) log.debug("Driver response body from " + uriString + ": " + httpBodyString);
138140

139-
if (httpResponse.getStatusLine().getStatusCode() > 200) {
141+
if (contentType != null && isResolveContentType(contentType)) {
142+
resolveResult = HttpBindingUtil.fromHttpBodyResolveResult(httpBodyString);
143+
}
140144

141-
if (log.isWarnEnabled()) log.warn("Cannot retrieve RESOLVE RESULT for " + identifier + " from " + uriString + ": " + httpBody);
142-
throw new ResolutionException(httpBody);
145+
if (statusCode == 404 && resolveResult == null) {
146+
resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.notFound, statusCode + " " + statusMessage + " (" + httpBodyString + ")");
143147
}
144148

145-
try {
149+
if (statusCode == 406 && resolveResult == null) {
150+
resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.representationNotSupported, statusCode + " " + statusMessage + " (" + httpBodyString + ")");
151+
}
146152

147-
resolveResult = ResolveResult.fromJson(httpBody);
148-
} catch (Exception ex) {
153+
if (statusCode != 200 && resolveResult == null) {
154+
resolveResult = ResolveResult.makeErrorResult(ResolveResult.Error.internalError, "Cannot retrieve result for " + did + ": " + statusCode + " " + statusMessage + " (" + httpBodyString + ")");
155+
if (log.isWarnEnabled()) log.warn("Driver cannot retrieve result for " + did + " from " + uriString + ": " + resolveResult.getError() + " - " + resolveResult.getErrorMessage());
156+
}
149157

150-
if (log.isWarnEnabled()) log.warn("No RESOLVE RESULT. Maybe DID DOCUMENT: " + httpBody + " (" + ex.getMessage());
151-
resolveResult = ResolveResult.build(DIDDocument.fromJson(httpBody));
158+
if (resolveResult != null && resolveResult.isErrorResult()) {
159+
throw new ResolutionException(resolveResult);
152160
}
153-
} catch (IOException ex) {
154161

155-
throw new ResolutionException("Cannot retrieve RESOLVE RESULT for " + identifier + " from " + uriString + ": " + ex.getMessage(), ex);
162+
if (resolveResult == null) {
163+
resolveResult = HttpBindingUtil.fromHttpBodyDidDocument(httpBodyBytes, contentType);
164+
}
165+
} catch (ResolutionException ex) {
166+
167+
throw ex;
168+
} catch (Exception ex) {
169+
170+
throw new ResolutionException("Driver cannot retrieve resolve result for " + did + " from " + uriString + ": " + ex.getMessage(), ex);
156171
}
157172

158-
if (log.isDebugEnabled()) log.debug("Retrieved RESOLVE RESULT for " + identifier + " (" + uriString + "): " + resolveResult);
173+
if (log.isDebugEnabled()) log.debug("Driver retrieved resolve result for " + did + " (" + uriString + "): " + resolveResult);
159174

160175
// done
161176

@@ -251,72 +266,69 @@ public List<String> testIdentifiers() throws ResolutionException {
251266
return this.getTestIdentifiers();
252267
}
253268

269+
/*
270+
* Helper methods
271+
*/
272+
273+
private static final ContentType RESOLVE_RESULT_CONTENT_TYPE = ContentType.parse(ResolveResult.MEDIA_TYPE);
274+
275+
private static boolean isResolveContentType(ContentType contentType) {
276+
return RESOLVE_RESULT_CONTENT_TYPE.getMimeType().equals(contentType.getMimeType()) && RESOLVE_RESULT_CONTENT_TYPE.getParameter("profile").equals(contentType.getParameter("profile"));
277+
}
278+
254279
/*
255280
* Getters and setters
256281
*/
257282

258283
public HttpClient getHttpClient() {
259-
260284
return this.httpClient;
261285
}
262286

263287
public void setHttpClient(HttpClient httpClient) {
264-
265288
this.httpClient = httpClient;
266289
}
267290

268291
public URI getResolveUri() {
269-
270292
return this.resolveUri;
271293
}
272294

273295
public void setResolveUri(URI resolveUri) {
274-
275296
this.resolveUri = resolveUri;
276297
}
277298

278299
public void setResolveUri(String resolveUri) {
279-
280300
this.resolveUri = URI.create(resolveUri);
281301
}
282302

283303
public URI getPropertiesUri() {
284-
285304
return this.propertiesUri;
286305
}
287306

288307
public void setPropertiesUri(URI propertiesUri) {
289-
290308
this.propertiesUri = propertiesUri;
291309
}
292310

293311
public void setPropertiesUri(String propertiesUri) {
294-
295312
this.propertiesUri = URI.create(propertiesUri);
296313
}
297314

298315
public Pattern getPattern() {
299-
300316
return this.pattern;
301317
}
302318

303319
public void setPattern(Pattern pattern) {
304-
305320
this.pattern = pattern;
306321
}
307322

308323
public void setPattern(String pattern) {
309-
310324
this.pattern = Pattern.compile(pattern);
311325
}
312326

313327
public List<String> getTestIdentifiers() {
314-
315328
return this.testIdentifiers;
316329
}
317330

318331
public void setTestIdentifiers(List<String> testIdentifiers) {
319-
320332
this.testIdentifiers = testIdentifiers;
321333
}
322334
}
Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,37 @@
11
package uniresolver.driver;
22

3+
import foundation.identity.did.DID;
4+
import uniresolver.ResolutionException;
5+
import uniresolver.result.ResolveResult;
6+
import uniresolver.util.ResolveResultUtil;
7+
38
import java.util.Collections;
49
import java.util.List;
510
import java.util.Map;
611

7-
import uniresolver.ResolutionException;
8-
import uniresolver.result.ResolveResult;
9-
1012
public interface Driver {
1113

1214
public static final String PROPERTIES_MIME_TYPE = "application/json";
1315

14-
public ResolveResult resolve(String identifier) throws ResolutionException;
16+
default public ResolveResult resolve(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
17+
ResolveResult resolveRepresentationResult = this.resolveRepresentation(did, resolutionOptions);
18+
ResolveResult resolveResult = ResolveResultUtil.convertToResolveResult(resolveRepresentationResult);
19+
return resolveResult;
20+
}
1521

16-
default public Map<String, Object> properties() throws ResolutionException {
22+
default public ResolveResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
23+
ResolveResult resolveResult = this.resolve(did, resolutionOptions);
24+
String accept = (String) resolutionOptions.get("accept");
25+
if (accept == null) throw new ResolutionException("No 'accept' provided in 'resolutionOptions' for resolveRepresentation().");
26+
ResolveResult resolveRepresentationResult = ResolveResultUtil.convertToResolveRepresentationResult(resolveResult, accept);
27+
return resolveRepresentationResult;
28+
}
1729

30+
default public Map<String, Object> properties() throws ResolutionException {
1831
return Collections.emptyMap();
1932
}
2033

2134
default public List<String> testIdentifiers() throws ResolutionException {
22-
2335
return Collections.emptyList();
2436
}
2537
}

0 commit comments

Comments
 (0)