Skip to content

Commit

Permalink
fix context usage (#166)
Browse files Browse the repository at this point in the history
* fix context usage

* fix not found response

* remove the whitespace
  • Loading branch information
wistefan authored Jul 31, 2024
1 parent c30ed34 commit b4323b7
Show file tree
Hide file tree
Showing 7 changed files with 695 additions and 636 deletions.
366 changes: 187 additions & 179 deletions src/main/java/org/fiware/mintaka/context/LdContextCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,184 +39,192 @@
@RequiredArgsConstructor

Check warning on line 39 in src/main/java/org/fiware/mintaka/context/LdContextCache.java

View workflow job for this annotation

GitHub Actions / spotbugs

EI_EXPOSE_REP2

new org.fiware.mintaka.context.LdContextCache(ContextProperties) may expose internal representation by storing an externally mutable object into LdContextCache.contextProperties
Raw output
This code stores a reference to an externally mutable object into the internal representation of the object.  If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.
public class LdContextCache {

private final ContextProperties contextProperties;

private URL coreContextUrl;
private Object coreContext;

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

@PostConstruct
public void initDefaultContext() {
try {
coreContextUrl = new URL(contextProperties.getDefaultUrl());
coreContext = JsonUtils.fromURLJavaNet(coreContextUrl);
} catch (IOException e) {
throw new ContextRetrievalException("Invalid core context configured.", e, contextProperties.getDefaultUrl());
}
}

/**
* Get context from the given url. Will be cached.
*
* @param url - url to get the context from
* @return the context
*/
@Cacheable
public Object getContextFromURL(URL url) {
try {
if (url.toURI().equals(coreContextUrl.toURI())) {
return coreContext;
}
return JsonUtils.fromURLJavaNet(url);
} catch (IOException e) {
throw new ContextRetrievalException(String.format("Was not able to retrieve context from %s.", url), e, url.toString());
} catch (URISyntaxException uriSyntaxException) {
throw new IllegalArgumentException(String.format("Received an invalid url: %s", url), uriSyntaxException);
}
}

/**
* Expand all given attributes with the given contexts.
*
* @param stringsToExpand - strings to be expanded
* @param contextUrls - urls of contexts to be used for expansion
* @return list of expanded attribute-ids
*/
public List<String> expandStrings(List<String> stringsToExpand, List<URL> contextUrls) {
Map contextMap = (Map) getContext(contextUrls);

return stringsToExpand.stream()
.map(stringToExpand -> expandString(stringToExpand, contextMap))
.collect(Collectors.toList());
}

/**
* Expand the given string with the provided contexts.
*
* @param stringToExpand - string to be expanded
* @param contextUrls - urls of contexts to be used for expansion
* @return the expanded attribute-id
*/
public String expandString(String stringToExpand, List<URL> contextUrls) {
return expandString(stringToExpand, (Map) getContext(contextUrls));
}

private String expandString(String stringToExpand, Map contextMap) {
String jsonLdString = getJsonLdString(stringToExpand);
try {
Map jsonLdObject = (Map) JsonUtils.fromString(jsonLdString);
jsonLdObject.put(JsonLdConsts.CONTEXT, contextMap.get(JsonLdConsts.CONTEXT));
return getIdFromJsonLDObject(jsonLdObject);
} catch (IOException e) {
throw new StringExpansionException(String.format("Was not able expand %s.", jsonLdString), e);
}
}

/**
* Retrieve the context as a JsonDocument
*
* @param contextURLs - either be a (URL)String, a URL or a list of urls/urlstrings.
* @return the context
*/
public Document getContextDocument(Object contextURLs) {
try {
Object context = getContext(contextURLs);
return JsonDocument.of(new ByteArrayInputStream(OBJECT_MAPPER.writeValueAsString(context).getBytes(StandardCharsets.UTF_8)));
} catch (JsonLdError | JsonProcessingException e) {
throw new IllegalArgumentException(String.format("No valid context available via %s", contextURLs), e);
}
}

/**
* Get the context from the given object. Should either be a (URL)String, a URL or a list of urls/urlstrings.
* We use the Json-ld-java lib for retrieval, since the titanium lib is not able to combine context objects.
*
* @param contextURLs - either be a (URL)String, a URL or a list of urls/urlstrings.
* @return the context
*/
private Object getContext(Object contextURLs) {
if (contextURLs instanceof List) {
return Map.of(JsonLdConsts.CONTEXT, ((List) contextURLs).stream()
.map(this::getContext)
.map(contextMap -> ((Map<String, Object>) contextMap).get(JsonLdConsts.CONTEXT))
.map(contextObject -> {
// follow potential list-contexts
if (contextObject instanceof List) {
return getContext((List) contextObject);
} else {
return contextObject;
}
})
.flatMap(map -> ((Map<String, Object>) map).entrySet().stream())
.collect(Collectors.toMap(e -> ((Map.Entry<String, Object>) e).getKey(), e -> ((Map.Entry<String, Object>) e).getValue(), (e1, e2) -> e2)));
} else if (contextURLs instanceof URL) {
return getContextFromURL((URL) contextURLs);
} else if (contextURLs instanceof String) {
return getContextFromURL((String) contextURLs);
} else if (contextURLs instanceof URI) {
return getContextFromURL(contextURLs.toString());
}
throw new ContextRetrievalException(String.format("Did not receive a valid context: %s.", contextURLs), contextURLs.toString());
}


/**
* Get the context from the given url
*
* @param urlString - string containing the url
* @return the context
*/
private Object getContextFromURL(String urlString) {
try {
return getContextFromURL(new URL(urlString));
} catch (MalformedURLException e) {
throw new ContextRetrievalException(String.format("Was not able to convert %s to URL.", urlString), e, urlString);
}
}


/**
* Extract the context urls from the link header. CORE_CONTEXT will be automatically added.
*
* @param headerString - content of the link header
* @return list of context urls, will either be only the core context or the core-context + the header context
*/
public List<URL> getContextURLsFromLinkHeader(String headerString) {

Optional<String> linkedContextString = Optional.empty();

if (headerString != null && !headerString.isEmpty() && !headerString.isBlank()) {
linkedContextString = Optional.of(headerString.split(";")[0].replace("<", "").replace(">", ""));
}

return linkedContextString
.map(lCS -> {
try {
return new URL(lCS);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Context url is invalid.", e);
}
})
.map(url -> List.of(url, coreContextUrl)).orElse(List.of(coreContextUrl));
}

// extract the Id from the expanded object
private String getIdFromJsonLDObject(Map<String, Object> jsonLdObject) {
Map<String, Object> expandedObject = (Map<String, Object>) JsonLdProcessor.expand(jsonLdObject)
.stream()
.findFirst()
.orElseThrow(() -> new StringExpansionException(String.format("Was not able to get an expanded object for %s.", jsonLdObject)));
Set<String> expandedKeys = expandedObject.keySet();
if (expandedKeys.size() != 1) {
throw new StringExpansionException(String.format("Was not able to correctly expand key. Got multiple keys: %s", expandedKeys));
}
return expandedKeys.iterator().next();
}

// create a json object for json-ld api to be used for extending the key.
private String getJsonLdString(String string) {
return String.format("{\"%s\":\"\"}", string);
}
private final ContextProperties contextProperties;

private URL coreContextUrl;
private Object coreContext;

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

@PostConstruct
public void initDefaultContext() {
try {
coreContextUrl = new URL(contextProperties.getDefaultUrl());
coreContext = JsonUtils.fromURLJavaNet(coreContextUrl);
} catch (IOException e) {
throw new ContextRetrievalException("Invalid core context configured.", e, contextProperties.getDefaultUrl());
}
}

/**
* Get context from the given url. Will be cached.
*
* @param url - url to get the context from
* @return the context
*/
@Cacheable
public Object getContextFromURL(URL url) {
try {
if (url.toURI().equals(coreContextUrl.toURI())) {
return coreContext;
}
return JsonUtils.fromURLJavaNet(url);
} catch (IOException e) {
throw new ContextRetrievalException(String.format("Was not able to retrieve context from %s.", url), e, url.toString());
} catch (URISyntaxException uriSyntaxException) {
throw new IllegalArgumentException(String.format("Received an invalid url: %s", url), uriSyntaxException);
}
}

/**
* Expand all given attributes with the given contexts.
*
* @param stringsToExpand - strings to be expanded
* @param contextUrls - urls of contexts to be used for expansion
* @return list of expanded attribute-ids
*/
public List<String> expandStrings(List<String> stringsToExpand, List<URL> contextUrls) {
Map contextMap = (Map) getContext(contextUrls);

return stringsToExpand.stream()
.map(stringToExpand -> expandString(stringToExpand, contextMap))
.collect(Collectors.toList());
}

/**
* Expand the given string with the provided contexts.
*
* @param stringToExpand - string to be expanded
* @param contextUrls - urls of contexts to be used for expansion
* @return the expanded attribute-id
*/
public String expandString(String stringToExpand, List<URL> contextUrls) {
return expandString(stringToExpand, (Map) getContext(contextUrls));
}

private String expandString(String stringToExpand, Map contextMap) {
String jsonLdString = getJsonLdString(stringToExpand);
try {
Map jsonLdObject = (Map) JsonUtils.fromString(jsonLdString);
jsonLdObject.put(JsonLdConsts.CONTEXT, contextMap.get(JsonLdConsts.CONTEXT));
return getIdFromJsonLDObject(jsonLdObject);
} catch (IOException e) {
throw new StringExpansionException(String.format("Was not able expand %s.", jsonLdString), e);
}
}

/**
* Retrieve the context as a JsonDocument
*
* @param contextURLs - either be a (URL)String, a URL or a list of urls/urlstrings.
* @return the context
*/
public Document getContextDocument(Object contextURLs) {
try {
Object context = getContext(contextURLs);
return JsonDocument.of(new ByteArrayInputStream(OBJECT_MAPPER.writeValueAsString(context).getBytes(StandardCharsets.UTF_8)));
} catch (JsonLdError | JsonProcessingException e) {
throw new IllegalArgumentException(String.format("No valid context available via %s", contextURLs), e);
}
}

/**
* Get the context from the given object. Should either be a (URL)String, a URL or a list of urls/urlstrings.
* We use the Json-ld-java lib for retrieval, since the titanium lib is not able to combine context objects.
*
* @param contextURLs - either be a (URL)String, a URL or a list of urls/urlstrings.
* @return the context
*/
private Object getContext(Object contextURLs) {
if (contextURLs instanceof List) {
var m = Map.of(JsonLdConsts.CONTEXT, ((List) contextURLs).stream()
.map(url -> getContext(url))
.map(contextMap -> ((Map<String, Object>) contextMap).get(JsonLdConsts.CONTEXT))
.map(contextObject -> {
// follow potential list-contexts
if (contextObject instanceof List) {
return getContext((List) contextObject);
}
return contextObject;
})
.map(co -> {
if (co instanceof Map<?, ?> coMap) {
if (coMap.containsKey(JsonLdConsts.CONTEXT)) {
return coMap.get(JsonLdConsts.CONTEXT);
}
}
return co;
})
.flatMap(map -> ((Map<String, Object>) map).entrySet().stream())
.collect(Collectors.toMap(e -> ((Map.Entry<String, Object>) e).getKey(), e -> ((Map.Entry<String, Object>) e).getValue(), (e1, e2) -> e2)));
return m;
} else if (contextURLs instanceof URL) {
return getContextFromURL((URL) contextURLs);
} else if (contextURLs instanceof String) {
return getContextFromURL((String) contextURLs);
} else if (contextURLs instanceof URI) {
return getContextFromURL(contextURLs.toString());
}
throw new ContextRetrievalException(String.format("Did not receive a valid context: %s.", contextURLs), contextURLs.toString());
}


/**
* Get the context from the given url
*
* @param urlString - string containing the url
* @return the context
*/
private Object getContextFromURL(String urlString) {
try {
return getContextFromURL(new URL(urlString));
} catch (MalformedURLException e) {
throw new ContextRetrievalException(String.format("Was not able to convert %s to URL.", urlString), e, urlString);
}
}


/**
* Extract the context urls from the link header. CORE_CONTEXT will be automatically added.
*
* @param headerString - content of the link header
* @return list of context urls, will either be only the core context or the core-context + the header context
*/
public List<URL> getContextURLsFromLinkHeader(String headerString) {

Optional<String> linkedContextString = Optional.empty();

if (headerString != null && !headerString.isEmpty() && !headerString.isBlank()) {
linkedContextString = Optional.of(headerString.split(";")[0].replace("<", "").replace(">", ""));
}

return linkedContextString
.map(lCS -> {
try {
return new URL(lCS);
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Context url is invalid.", e);
}
})
.map(url -> List.of(url, coreContextUrl)).orElse(List.of(coreContextUrl));
}

// extract the Id from the expanded object
private String getIdFromJsonLDObject(Map<String, Object> jsonLdObject) {
Map<String, Object> expandedObject = (Map<String, Object>) JsonLdProcessor.expand(jsonLdObject)
.stream()
.findFirst()
.orElseThrow(() -> new StringExpansionException(String.format("Was not able to get an expanded object for %s.", jsonLdObject)));
Set<String> expandedKeys = expandedObject.keySet();
if (expandedKeys.size() != 1) {
throw new StringExpansionException(String.format("Was not able to correctly expand key. Got multiple keys: %s", expandedKeys));
}
return expandedKeys.iterator().next();
}

// create a json object for json-ld api to be used for extending the key.
private String getJsonLdString(String string) {
return String.format("{\"%s\":\"\"}", string);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public void serialize(EntityTemporalVO value, JsonGenerator gen, SerializerProvi
throw new JacksonConversionException("Was not able to deserialize the retrieved object.", e);
} catch (JsonLdError jsonLdError) {
log.error("Was not able to deserialize object", jsonLdError);
throw new JacksonConversionException(jsonLdError.getMessage());
throw new JacksonConversionException(jsonLdError.getMessage());
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/java/org/fiware/mintaka/exception/ErrorType.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum ErrorType {
INVALID_REQUEST(HttpStatus.BAD_REQUEST, "https://uri.etsi.org/ngsi-ld/errors/InvalidRequest"),
BAD_REQUEST_DATA(HttpStatus.BAD_REQUEST, "https://uri.etsi.org/ngsi-ld/errors/BadRequestData"),
OPERATION_NOT_SUPPORTED(HttpStatus.UNPROCESSABLE_ENTITY, "https://uri.etsi.org/ngsi-ld/errors/OperationNotSupported"),
RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound "),
RESOURCE_NOT_FOUND(HttpStatus.NOT_FOUND, "https://uri.etsi.org/ngsi-ld/errors/ResourceNotFound"),
INTERNAL_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "https://uri.etsi.org/ngsi-ld/errors/InternalError"),
TOO_COMPLEX_QUERY(HttpStatus.FORBIDDEN, "https://uri.etsi.org/ngsi-ld/errors/TooComplexQuery"),
TOO_MANY_RESULTS(HttpStatus.FORBIDDEN, "https://uri.etsi.org/ngsi-ld/errors/TooManyResults "),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
*/
@Produces
@Singleton
@Requires(classes = {InvalidTimeRelationException.class, ExceptionHandler.class})
@Requires(classes = {JacksonConversionException.class, ExceptionHandler.class})
@Slf4j
public class JacksonConversionExceptionHandler extends NGSICompliantExceptionHandler<JacksonConversionException>{

Expand Down
Loading

0 comments on commit b4323b7

Please sign in to comment.