no.hasmac.jsonld.loader.DefaultHttpLoader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hasmac-json-ld Show documentation
Show all versions of hasmac-json-ld Show documentation
A more performant JSON-LD 1.1 Processor & API forked from Titanium JSON-LD.
The newest version!
/*
* Copyright 2020 APICATALOG and HASMAC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package no.hasmac.jsonld.loader;
import no.hasmac.jsonld.JsonLdError;
import no.hasmac.jsonld.JsonLdErrorCode;
import no.hasmac.jsonld.document.Document;
import no.hasmac.jsonld.http.HttpClient;
import no.hasmac.jsonld.http.HttpResponse;
import no.hasmac.jsonld.http.ProfileConstants;
import no.hasmac.jsonld.http.link.Link;
import no.hasmac.jsonld.http.media.MediaType;
import no.hasmac.jsonld.uri.UriResolver;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
class DefaultHttpLoader implements DocumentLoader {
private static final Logger LOGGER = Logger.getLogger(DefaultHttpLoader.class.getName());
public static final int MAX_REDIRECTIONS = 10;
private static final String PLUS_JSON = "+json";
private final int maxRedirections;
private final HttpClient httpClient;
private final DocumentResolver resolver;
public DefaultHttpLoader(HttpClient httpClient) {
this(httpClient, MAX_REDIRECTIONS);
}
public DefaultHttpLoader(HttpClient httpClient, int maxRedirections) {
this.httpClient = httpClient;
this.maxRedirections = maxRedirections;
this.resolver = new DocumentResolver();
}
@Override
public Document loadDocument(final URI uri, final DocumentLoaderOptions options) throws JsonLdError {
try {
URI targetUri = uri;
MediaType contentType = null;
URI contextUri = null;
for (int redirection = 0; redirection < maxRedirections; redirection++) {
// 2.
try (HttpResponse response = httpClient.send(targetUri, getAcceptHeader(options.getRequestProfile()))) {
// 3.
if (response.statusCode() == 301
|| response.statusCode() == 302
|| response.statusCode() == 303
|| response.statusCode() == 307
) {
final Optional location = response.location();
if (location.isPresent()) {
targetUri = UriResolver.resolveAsUri(targetUri, location.get());
continue;
}
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "Header location is required for code [" + response.statusCode() + "].");
}
if (response.statusCode() != 200) {
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "Unexpected response code [" + response.statusCode() + "]");
}
final Optional contentTypeValue = response.contentType();
if (contentTypeValue.isPresent()) {
contentType = MediaType.of(contentTypeValue.get());
}
final Collection linkValues = response.links();
if (linkValues != null && !linkValues.isEmpty()) {
// 4.
if (contentType == null
|| (!MediaType.JSON.match(contentType)
&& !contentType.subtype().toLowerCase().endsWith(PLUS_JSON))
) {
final URI baseUri = targetUri;
Optional alternate =
linkValues.stream()
.flatMap(l -> Link.of(l, baseUri).stream())
.filter(l -> l.relations().contains("alternate")
&& l.type().isPresent()
&& MediaType.JSON_LD.match(l.type().get())
)
.findFirst();
if (alternate.isPresent()) {
targetUri = alternate.get().target();
continue;
}
}
// 5.
if (contentType != null
&& !MediaType.JSON_LD.match(contentType)
&& (MediaType.JSON.match(contentType)
|| contentType.subtype().toLowerCase().endsWith(PLUS_JSON))
) {
final URI baseUri = targetUri;
final List contextUris =
linkValues.stream()
.flatMap(l -> Link.of(l, baseUri).stream())
.filter(l -> l.relations().contains(ProfileConstants.CONTEXT))
.collect(Collectors.toList());
if (contextUris.size() > 1) {
throw new JsonLdError(JsonLdErrorCode.MULTIPLE_CONTEXT_LINK_HEADERS);
} else if (contextUris.size() == 1) {
contextUri = contextUris.get(0).target();
}
}
}
if (contentType == null) {
LOGGER.log(Level.WARNING, "GET on URL [{0}] does not return content-type header. Trying application/json.", uri);
contentType = MediaType.JSON;
}
return resolve(contentType, targetUri, contextUri, response);
}
}
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, "Too many redirections");
} catch (IOException e) {
throw new JsonLdError(JsonLdErrorCode.LOADING_DOCUMENT_FAILED, e);
}
}
public static String getAcceptHeader() {
return getAcceptHeader(null);
}
public static String getAcceptHeader(final Collection profiles) {
final StringBuilder builder = new StringBuilder();
builder.append(MediaType.JSON_LD.toString());
if (profiles != null && !profiles.isEmpty()) {
builder.append(";profile=\"");
builder.append(String.join(" ", profiles));
builder.append("\"");
}
builder.append(',');
builder.append(MediaType.JSON.toString());
builder.append(";q=0.9,*/*;q=0.1");
return builder.toString();
}
private Document resolve(
final MediaType type,
final URI targetUri,
final URI contextUrl,
final HttpResponse response) throws JsonLdError, IOException {
final DocumentReader reader = resolver.getReader(type);
try (final InputStream is = response.body()) {
final Document remoteDocument = reader.read(is);
remoteDocument.setDocumentUrl(targetUri);
remoteDocument.setContextUrl(contextUrl);
return remoteDocument;
}
}
/**
* Set fallback content-type used when received content-type is not supported.
* e.g. setFallbackContentType(MediaType.JSON_LD)
*
* @param fallbackContentType a content type that overrides unsupported received content-type
*/
public void setFallbackContentType(MediaType fallbackContentType) {
this.resolver.setFallbackContentType(fallbackContentType);
}
}