Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,15 @@
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -202,14 +205,21 @@ protected IExtendedIterator<KvinTuple> fetchInternal(List<URI> items, List<URI>
}
response = this.httpClient.execute(request);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() != 200) {
return NiceIterator.emptyIterator();
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
if (status == 404) {
return NiceIterator.emptyIterator();
}
String body = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : "";
throw new UncheckedIOException(new IOException("HTTP " + status + " while fetching values: " + body));
}
// converting json to kvin tuples
// TODO directly read from stream with pooled HTTP client
content = entity.getContent();
JsonFormatParser jsonParser = new JsonFormatParser(new ByteArrayInputStream(ByteStreams.toByteArray(content)));
return jsonParser.parse();
} catch (UncheckedIOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
Expand Down Expand Up @@ -262,8 +272,13 @@ private IExtendedIterator<URI> descendantsInternal(URI item, URI context, Long l
HttpGet httpGet = createHttpGet(getRequestUri.toString());
HttpResponse response = this.httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() != 200) {
return NiceIterator.emptyIterator();
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
if (status == 404) {
return NiceIterator.emptyIterator();
}
String body = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : "";
throw new UncheckedIOException(new IOException("HTTP " + status + " while fetching descendants: " + body));
}
// converting json to URI
return new NiceIterator<>() {
Expand Down Expand Up @@ -312,6 +327,8 @@ public void close() {
}
}
};
} catch (UncheckedIOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand All @@ -329,8 +346,13 @@ public IExtendedIterator<URI> properties(URI item, URI context) {
HttpGet httpGet = createHttpGet(getRequestUri.toString());
HttpResponse response = this.httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (response.getStatusLine().getStatusCode() != 200) {
return NiceIterator.emptyIterator();
int status = response.getStatusLine().getStatusCode();
if (status != 200) {
if (status == 404) {
return NiceIterator.emptyIterator();
}
String body = entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : "";
throw new UncheckedIOException(new IOException("HTTP " + status + " while fetching properties: " + body));
}
// converting json to URI
return new NiceIterator<>() {
Expand Down Expand Up @@ -383,6 +405,8 @@ public void close() {
}
}
};
} catch (UncheckedIOException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import net.enilink.komma.core.{URI, URIs}
import net.liftweb.common.Box.box2Iterable
import net.liftweb.common._
import net.liftweb.http.rest.RestHelper
import net.liftweb.http.{BadRequestResponse, InMemoryResponse, JsonResponse, LiftResponse, OkResponse, OutputStreamResponse, PlainTextResponse, Req, S}
import net.liftweb.http.{InMemoryResponse, JsonResponse, LiftResponse, OkResponse, OutputStreamResponse, Req, S}
import net.liftweb.json.Extraction.decompose
import net.liftweb.json.JsonAST._
import net.liftweb.json.JsonDSL._
Expand All @@ -48,7 +48,35 @@ class KvinService(path: List[String], store: Kvin) extends RestHelper with Logga
def responseHeaders: List[(String, String)] = CORS_HEADERS ::: S.getResponseHeaders(Nil)

object FailureResponse {
def apply(msg: String): PlainTextResponse = PlainTextResponse(msg, Nil, 400)
def apply(msg: String): LiftResponse = createErrorResponse(400, "INVALID_PAYLOAD", msg)
}

def createErrorResponse(status: Int, code: String, message: String, details: Box[String] = Empty): LiftResponse = {
val body = details.filter(_.nonEmpty).map { d =>
("code" -> code) ~ ("message" -> message) ~ ("details" -> d)
} openOr {
("code" -> code) ~ ("message" -> message)
}
JsonResponse(body, responseHeaders, S.responseCookies, status)
}

def createSuccessResponse(code: String = "OK", message: Box[String] = Empty, data: JObject = JObject(Nil)): LiftResponse = {
val baseFields = List(
JField("success", JBool(true)),
JField("status", JInt(200)),
JField("code", JString(code))
) ::: message.map(m => JField("message", JString(m))).toList
JsonResponse(JObject(baseFields ::: data.obj), responseHeaders, S.responseCookies, 200)
}

private def withServerErrorResponse(code: String)(f: => LiftResponse): LiftResponse = {
try {
f
} catch {
case e: Exception =>
logger.error(s"Request failed with code $code", e)
createErrorResponse(500, code, "Internal server error", Full(Option(e.getMessage).getOrElse(e.getClass.getSimpleName)))
}
}

protected def csvResponse_?(r: Req): Boolean = {
Expand Down Expand Up @@ -89,18 +117,27 @@ class KvinService(path: List[String], store: Kvin) extends RestHelper with Logga
case Failure(msg, _, _) => FailureResponse(msg)
case _ => OkResponse()
}
case list Get _ if list.endsWith("properties" :: Nil) => createJsonResponse(getProperties(path ++ list.dropRight(1)))
case list Get _ if list.endsWith("**" :: Nil) => createJsonResponse(getDescendants(path ++ list.dropRight(1)))
case list Get _ if list.endsWith("properties" :: Nil) =>
withServerErrorResponse("PROPERTIES_QUERY_FAILED") {
createJsonResponse(getProperties(path ++ list.dropRight(1)))
}
case list Get _ if list.endsWith("**" :: Nil) =>
withServerErrorResponse("DESCENDANTS_QUERY_FAILED") {
createJsonResponse(getDescendants(path ++ list.dropRight(1)))
}

case list Delete _ if list.endsWith("values" :: Nil) => createJsonResponse(deleteValues(path ++ list.dropRight(1)))
case list Delete _ if list.endsWith("values" :: Nil) =>
withServerErrorResponse("DELETE_VALUES_FAILED") {
createJsonResponse(deleteValues(path ++ list.dropRight(1)))
}
// case list Get _ => // TODO return RDF description
})

def serveValues(path: List[String], contentType: Box[String]): LiftResponse = {
val limit = S.param("limit") flatMap (v => tryo(v.toLong)) filter (_ > 0) openOr 10000L

if (limit > MAX_LIMIT) {
FailureResponse("The maximum limit is " + MAX_LIMIT + ". Please use multiple request if you require more data points.")
createErrorResponse(400, "LIMIT_TOO_LARGE", "The maximum limit is " + MAX_LIMIT + ". Please use multiple request if you require more data points.")
} else {
def filename(defaultExt: String) = S.param("filename") openOr "values." + defaultExt

Expand Down Expand Up @@ -222,8 +259,8 @@ class KvinService(path: List[String], store: Kvin) extends RestHelper with Logga
}
OutputStreamResponse(streamer, -1, ("Content-Type", "text/csv; charset=utf-8") ::
("Content-Disposition", s"""inline; filename=${filename("csv")}""") :: responseHeaders, S.responseCookies, 200)
case _ => BadRequestResponse()
} openOr BadRequestResponse()
case _ => createErrorResponse(400, "UNSUPPORTED_RESPONSE_TYPE", "Unsupported response type. Use application/json or text/csv.")
} openOr createErrorResponse(400, "UNSUPPORTED_RESPONSE_TYPE", "Unsupported response type. Use application/json or text/csv.")
response
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
Expand Down Expand Up @@ -280,6 +281,27 @@ public void shouldReturnPropertiesForItem() {
}
}

@Test
public void shouldThrowOnNon404FetchError() throws Exception {
doReturn(mockedResponse("{\"code\":\"INTERNAL\"}", 500)).when(httpClient).execute(any());

try {
kvinHttp.fetch(URIs.createURI("http://example.org/item1"), null, null, 10).toList();
Assert.fail("Expected UncheckedIOException for non-404 HTTP error");
} catch (UncheckedIOException e) {
String message = e.getMessage() != null ? e.getMessage() : String.valueOf(e.getCause());
Assert.assertTrue(message.contains("500") || (e.getCause() != null && String.valueOf(e.getCause().getMessage()).contains("500")));
}
}

@Test
public void shouldReturnEmptyOn404Properties() throws Exception {
doReturn(mockedResponse("", 404)).when(httpClient).execute(any());

var properties = kvinHttp.properties(URIs.createURI("http://example.org/item1"), null).toList();
Assert.assertTrue(properties.isEmpty());
}

private static List<KvinTuple> generateTuples(int numberOfItems, int numberOfProperties, int numberOfValues) {
return new KvinTupleGenerator()
.setStartTime(System.currentTimeMillis())
Expand Down
Loading
Loading