Skip to content

Commit

Permalink
feat: Better support for extensions of resolving/dereferencing.
Browse files Browse the repository at this point in the history
  • Loading branch information
peacekeeper committed Aug 18, 2021
1 parent 16c6697 commit 3f144b5
Show file tree
Hide file tree
Showing 39 changed files with 1,384 additions and 915 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.slf4j.LoggerFactory;
import uniresolver.ResolutionException;
import uniresolver.driver.Driver;
import uniresolver.result.ResolveRepresentationResult;
import uniresolver.result.ResolveResult;
import uniresolver.util.HttpBindingUtil;

Expand Down Expand Up @@ -43,7 +44,7 @@ public HttpDriver() {
}

@Override
public ResolveResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
public ResolveRepresentationResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {

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

Expand Down Expand Up @@ -109,7 +110,7 @@ public ResolveResult resolveRepresentation(DID did, Map<String, Object> resoluti

// execute HTTP request and read response

ResolveResult resolveRepresentationResult = null;
ResolveRepresentationResult resolveRepresentationResult = null;

if (log.isDebugEnabled()) log.debug("Driver request for DID " + did + " to " + uriString + " with Accept: header " + acceptMediaTypesString);

Expand Down Expand Up @@ -139,15 +140,15 @@ public ResolveResult resolveRepresentation(DID did, Map<String, Object> resoluti
}

if (httpStatusCode == 404 && resolveRepresentationResult == null) {
resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult(ResolveResult.ERROR_NOTFOUND, httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")", accept);
throw new ResolutionException(ResolveResult.ERROR_NOTFOUND, httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")");
}

if (httpStatusCode == 406 && resolveRepresentationResult == null) {
resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult(ResolveResult.ERROR_REPRESENTATIONNOTSUPPORTED, httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")", accept);
throw new ResolutionException(ResolveResult.ERROR_REPRESENTATIONNOTSUPPORTED, httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")");
}

if (httpStatusCode != 200 && resolveRepresentationResult == null) {
resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult(ResolveResult.ERROR_INTERNALERROR, "Driver cannot retrieve result for " + did + ": " + httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")", accept);
throw new ResolutionException(ResolveResult.ERROR_INTERNALERROR, "Driver cannot retrieve result for " + did + ": " + httpStatusCode + " " + httpStatusMessage + " (" + httpBodyString + ")");
}

if (resolveRepresentationResult != null && resolveRepresentationResult.isErrorResult()) {
Expand All @@ -166,11 +167,11 @@ public ResolveResult resolveRepresentation(DID did, Map<String, Object> resoluti
throw new ResolutionException("Driver cannot retrieve resolve result for " + did + " from " + uriString + ": " + ex.getMessage(), ex);
}

if (log.isDebugEnabled()) log.debug("Driver retrieved resolve result for " + did + " (" + uriString + "): " + resolveRepresentationResult);
if (log.isInfoEnabled()) log.info("Driver retrieved resolve result for " + did + " (" + uriString + "): " + resolveRepresentationResult);

// done

return resolveRepresentationResult;
return (ResolveRepresentationResult) resolveRepresentationResult;
}

@Override
Expand Down
32 changes: 21 additions & 11 deletions driver/src/main/java/uniresolver/driver/Driver.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package uniresolver.driver;

import foundation.identity.did.DID;
import foundation.identity.did.representations.Representations;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uniresolver.ResolutionException;
import uniresolver.result.ResolveResult;
import uniresolver.util.ResolveResultUtil;
import uniresolver.result.ResolveDataModelResult;
import uniresolver.result.ResolveRepresentationResult;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

Expand All @@ -17,20 +19,28 @@ public interface Driver {

static final Logger log = LoggerFactory.getLogger(Driver.class);

default public ResolveResult resolve(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
default public ResolveDataModelResult resolve(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
if (log.isDebugEnabled()) log.debug("Driver: resolve(" + did + ") with options: " + resolutionOptions);
ResolveResult resolveRepresentationResult = this.resolveRepresentation(did, resolutionOptions);
ResolveResult resolveResult = resolveRepresentationResult == null ? null : ResolveResultUtil.convertToResolveResult(resolveRepresentationResult);
return resolveResult;

String accept = (String) resolutionOptions.get("accept");
if (accept != null) throw new ResolutionException("Driver: Unexpected 'accept' provided in 'resolutionOptions' for resolve().");

Map<String, Object> resolveRepresentationResolutionOptions = Map.of("accept", Representations.DEFAULT_MEDIA_TYPE);
ResolveRepresentationResult resolveRepresentationResult = this.resolveRepresentation(did, resolveRepresentationResolutionOptions);

return resolveRepresentationResult == null ? null : resolveRepresentationResult.toResolveDataModelResult();
}

default public ResolveResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
default public ResolveRepresentationResult resolveRepresentation(DID did, Map<String, Object> resolutionOptions) throws ResolutionException {
if (log.isDebugEnabled()) log.debug("Driver: resolveRepresentation(" + did + ") with options: " + resolutionOptions);

String accept = (String) resolutionOptions.get("accept");
if (accept == null) throw new ResolutionException("Driver: No 'accept' provided in 'resolutionOptions' for resolveRepresentation().");
ResolveResult resolveResult = this.resolve(did, resolutionOptions);
ResolveResult resolveRepresentationResult = resolveResult == null ? null : ResolveResultUtil.convertToResolveRepresentationResult(resolveResult, accept);
return resolveRepresentationResult;

Map<String, Object> resolveDataModelResolutionOptions = new HashMap<>(resolutionOptions);
resolveDataModelResolutionOptions.remove("accept");
ResolveDataModelResult resolveDataModelResult = this.resolve(did, resolveDataModelResolutionOptions);

return resolveDataModelResult == null ? null : resolveDataModelResult.toResolveRepresentationResult(accept);
}

default public Map<String, Object> properties() throws ResolutionException {
Expand Down
58 changes: 43 additions & 15 deletions driver/src/main/java/uniresolver/driver/servlet/ResolveServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import org.springframework.http.MediaType;
import uniresolver.ResolutionException;
import uniresolver.driver.util.HttpBindingServerUtil;
import uniresolver.result.ResolveRepresentationResult;
import uniresolver.result.ResolveResult;
import uniresolver.util.HttpBindingUtil;

import javax.servlet.Servlet;
import javax.servlet.http.HttpServlet;
Expand Down Expand Up @@ -73,38 +75,64 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t

// invoke the driver

ResolveResult resolveRepresentationResult;
ResolveRepresentationResult resolveRepresentationResult;

try {

resolveRepresentationResult = InitServlet.getDriver().resolveRepresentation(DID.fromString(didString), resolutionOptions);
if (resolveRepresentationResult == null) throw new ResolutionException(ResolveResult.ERROR_NOTFOUND, "Driver: No resolve result for " + didString);
} catch (Exception ex) {

if (log.isWarnEnabled()) log.warn("Driver: Resolve problem for " + didString + ": " + ex.getMessage(), ex);

if (ex instanceof ResolutionException) {
resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult((ResolutionException) ex, accept);
} else {
resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult(ResolveResult.ERROR_INTERNALERROR, "Driver: Resolve problem for " + didString + ": " + ex.getMessage(), accept);
if (! (ex instanceof ResolutionException)) {
ex = new ResolutionException(ResolveResult.ERROR_INTERNALERROR, "Driver: esolve problem for " + didString + ": " + ex.getMessage());
}

ServletUtil.sendResponse(response, HttpBindingServerUtil.httpStatusCodeForResolveResult(resolveRepresentationResult), null, HttpBindingServerUtil.toHttpBodyResolveResult(resolveRepresentationResult));
return;
resolveRepresentationResult = ResolveRepresentationResult.makeErrorResult((ResolutionException) ex, accept);
}

if (log.isInfoEnabled()) log.info("Driver: Resolve result for " + didString + ": " + resolveRepresentationResult);

// no resolve result?
// write resolve result

if (resolveRepresentationResult == null || resolveRepresentationResult.getDidDocumentStream() == null) {
for (MediaType acceptMediaType : httpAcceptMediaTypes) {

resolveRepresentationResult = ResolveResult.makeErrorResolveRepresentationResult(ResolveResult.ERROR_NOTFOUND, "Driver: No resolve result for " + didString, accept);
ServletUtil.sendResponse(response, HttpServletResponse.SC_NOT_FOUND, null, HttpBindingServerUtil.toHttpBodyResolveResult(resolveRepresentationResult));
return;
}
if (HttpBindingServerUtil.isMediaTypeAcceptable(acceptMediaType, ResolveResult.MEDIA_TYPE)) {

// write resolve result
ServletUtil.sendResponse(
response,
HttpBindingServerUtil.httpStatusCodeForResult(resolveRepresentationResult),
ResolveResult.MEDIA_TYPE,
HttpBindingServerUtil.toHttpBodyResolveRepresentationResult(resolveRepresentationResult));
return;
} else {

// determine representation media type

String representationMediaType = HttpBindingUtil.representationMediaTypeForMediaType(acceptMediaType.toString());
if (representationMediaType != null) {
if (log.isDebugEnabled()) log.debug("Supporting HTTP media type " + acceptMediaType + " via DID document representation media type " + representationMediaType);
} else {
if (log.isDebugEnabled()) log.debug("Not supporting HTTP media type " + acceptMediaType);
continue;
}

ServletUtil.sendResponse(
response,
HttpBindingServerUtil.httpStatusCodeForResult(resolveRepresentationResult),
resolveRepresentationResult.getContentType(),
resolveRepresentationResult.getDidDocumentStream()
);
return;
}
}

ServletUtil.sendResponse(response, HttpBindingServerUtil.httpStatusCodeForResolveResult(resolveRepresentationResult), ResolveResult.MEDIA_TYPE, HttpBindingServerUtil.toHttpBodyResolveResult(resolveRepresentationResult));
ServletUtil.sendResponse(
response,
HttpServletResponse.SC_NOT_ACCEPTABLE,
null,
"Not acceptable media types " + httpAcceptHeader);
return;
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package uniresolver.driver.util;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.ObjectMapper;
import foundation.identity.did.representations.Representations;
import org.apache.commons.codec.binary.Hex;
Expand All @@ -10,37 +9,53 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import uniresolver.result.DereferenceResult;
import uniresolver.result.ResolveRepresentationResult;
import uniresolver.result.ResolveResult;
import uniresolver.result.Result;
import uniresolver.util.HttpBindingUtil;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.*;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

public class HttpBindingServerUtil {

private static final Logger log = LoggerFactory.getLogger(HttpBindingServerUtil.class);

private static final ObjectMapper objectMapper = new ObjectMapper();
private static final ObjectMapper objectMapper = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.USE_DEFAULTS);

static {
objectMapper.setSerializationInclusion(JsonInclude.Include.USE_DEFAULTS);
}

public static String toHttpBodyResolveResult(ResolveResult resolveResult) throws IOException {
public static String toHttpBodyResolveRepresentationResult(ResolveRepresentationResult resolveRepresentationResult) throws IOException {
if (log.isDebugEnabled()) log.debug("Serializing resolve result to HTTP body.");
if (resolveResult.getDidDocument() != null) throw new IllegalArgumentException("Cannot serialize abstract DID document.");
Map<String, Object> json = new HashMap<>();
json.put("didResolutionMetadata", resolveResult.getDidResolutionMetadata());
json.put("didDocumentMetadata", resolveResult.getDidDocumentMetadata());
if (resolveResult.getDidDocumentStream() == null || resolveResult.getDidDocumentStream().length == 0) {
Map<String, Object> json = new LinkedHashMap<>();
json.put("didResolutionMetadata", resolveRepresentationResult.getDidResolutionMetadata());
if (resolveRepresentationResult.getDidDocumentStream() == null || resolveRepresentationResult.getDidDocumentStream().length == 0) {
json.put("didDocument", null);
} else if (isJson(resolveResult.getDidDocumentStream())) {
json.put("didDocument", objectMapper.readValue(new ByteArrayInputStream(resolveResult.getDidDocumentStream()), LinkedHashMap.class));
} else if (isJson(resolveRepresentationResult.getDidDocumentStream())) {
json.put("didDocument", objectMapper.readValue(new ByteArrayInputStream(resolveRepresentationResult.getDidDocumentStream()), LinkedHashMap.class));
} else {
json.put("didDocument", Hex.encodeHexString(resolveRepresentationResult.getDidDocumentStream()));
}
json.put("didDocumentMetadata", resolveRepresentationResult.getDidDocumentMetadata());
return objectMapper.writeValueAsString(json);
}

public static String toHttpBodyDereferenceResult(DereferenceResult dereferenceResult) throws IOException {
if (log.isDebugEnabled()) log.debug("Serializing dereference result to HTTP body.");
Map<String, Object> json = new LinkedHashMap<>();
json.put("dereferencingMetadata", dereferenceResult.getDereferencingMetadata());
if (dereferenceResult.getContentStream() == null || dereferenceResult.getContentStream().length == 0) {
json.put("content", null);
} else if (isJson(dereferenceResult.getContentStream())) {
json.put("content", objectMapper.readValue(new ByteArrayInputStream(dereferenceResult.getContentStream()), LinkedHashMap.class));
} else {
json.put("didDocument", Hex.encodeHexString(resolveResult.getDidDocumentStream()));
json.put("content", Hex.encodeHexString(dereferenceResult.getContentStream()));
}
json.put("contentMetadata", dereferenceResult.getContentMetadata());
return objectMapper.writeValueAsString(json);
}

Expand All @@ -55,29 +70,31 @@ public static String acceptForHttpAcceptMediaTypes(List<MediaType> httpAcceptMed
return Representations.DEFAULT_MEDIA_TYPE;
}

public static boolean isMediaTypeAcceptable(MediaType acceptMediaType, MediaType contentType) {
public static boolean isMediaTypeAcceptable(MediaType acceptMediaType, String mediaTypeString) {
if (mediaTypeString == null) throw new NullPointerException();
MediaType mediaType = MediaType.valueOf(mediaTypeString);
boolean acceptable = false;
if (acceptMediaType.includes(contentType))
if (acceptMediaType.includes(mediaType))
acceptable = true;
else if (acceptMediaType.getType().equals(contentType.getType()) && contentType.getSubtype().endsWith("+" + acceptMediaType.getSubtype()))
else if (acceptMediaType.getType().equals(mediaType.getType()) && mediaType.getSubtype().endsWith("+" + acceptMediaType.getSubtype()))
acceptable = true;
if (!acceptMediaType.isWildcardType() && !acceptMediaType.isWildcardSubtype()) {
if (acceptMediaType.getParameters() != null) {
acceptable &= Objects.equals(acceptMediaType.getParameter("profile"), contentType.getParameter("profile"));
acceptable &= Objects.equals(acceptMediaType.getParameter("profile"), mediaType.getParameter("profile"));
}
}
if (log.isDebugEnabled()) log.debug("Checking if content type " + contentType + " is acceptable for " + acceptMediaType + ": " + acceptable);
if (log.isDebugEnabled()) log.debug("Checking if media type " + mediaType + " is acceptable for " + acceptMediaType + ": " + acceptable);
return acceptable;
}

public static int httpStatusCodeForResolveResult(ResolveResult resolveResult) {
if (ResolveResult.ERROR_NOTFOUND.equals(resolveResult.getError()))
public static int httpStatusCodeForResult(Result result) {
if (ResolveResult.ERROR_NOTFOUND.equals(result.getError()))
return HttpStatus.SC_NOT_FOUND;
else if (ResolveResult.ERROR_INVALIDDID.equals(resolveResult.getError()))
else if (ResolveResult.ERROR_INVALIDDID.equals(result.getError()))
return HttpStatus.SC_BAD_REQUEST;
else if (ResolveResult.ERROR_REPRESENTATIONNOTSUPPORTED.equals(resolveResult.getError()))
else if (ResolveResult.ERROR_REPRESENTATIONNOTSUPPORTED.equals(result.getError()))
return HttpStatus.SC_NOT_ACCEPTABLE;
else if (resolveResult.isErrorResult())
else if (result.isErrorResult())
return HttpStatus.SC_INTERNAL_SERVER_ERROR;
else
return HttpStatus.SC_OK;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package uniresolver.examples;

import uniresolver.client.ClientUniResolver;
import uniresolver.result.ResolveDataModelResult;
import uniresolver.result.ResolveRepresentationResult;
import uniresolver.result.ResolveResult;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

Expand All @@ -16,8 +19,8 @@ public static void main(String[] args) throws Exception {
ClientUniResolver uniResolver = new ClientUniResolver();
uniResolver.setResolveUri("http://localhost:8080/1.0/identifiers/");

ResolveResult resolveResult = uniResolver.resolve("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions);
System.out.println(resolveResult.toJson());
System.out.println(resolveResult.getDidDocument().toJson());
ResolveRepresentationResult resolveRepresentationResult = uniResolver.resolveRepresentation("did:sov:WRfXPg8dantKVubE3HX8pw", resolveOptions);
System.out.println(resolveRepresentationResult.toJson());
System.out.println(new String(resolveRepresentationResult.getDidDocumentStream(), StandardCharsets.UTF_8));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import foundation.identity.did.DID;
import info.weboftrust.btctxlookup.bitcoinconnection.BlockcypherAPIBitcoinConnection;
import uniresolver.driver.did.btcr.DidBtcrDriver;
import uniresolver.result.ResolveDataModelResult;
import uniresolver.result.ResolveResult;

import java.util.HashMap;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import foundation.identity.did.DID;
import uniresolver.driver.did.sov.DidSovDriver;
import uniresolver.result.ResolveDataModelResult;
import uniresolver.result.ResolveResult;

import java.util.HashMap;
Expand Down
3 changes: 1 addition & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,7 @@

<!-- Dependency Versions -->

<key-formats-java.version>0.1.1</key-formats-java.version>
<did-common-java.version>0.3-SNAPSHOT</did-common-java.version>
<did-common-java.version>0.4-SNAPSHOT</did-common-java.version>

<eclipse.jetty.version>9.4.18.v20190429</eclipse.jetty.version>
<slf4j.version>1.7.25</slf4j.version>
Expand Down
Loading

0 comments on commit 3f144b5

Please sign in to comment.