log
parameter as a reply to
- * the log entry represented by inReplyTo
.
- * @return The saved log entry.
- * @throws LogbookException E.g. due to invalid log entry data.
- */
- private LogEntry save(LogEntry log, LogEntry inReplyTo) throws LogbookException {
-
- try {
- MultivaluedMaplogId
- *
- * @param logId LogEntry id
- * @return LogEntry object
- */
- @Override
- public LogEntry getLog(Long logId) {
- return findLogById(logId);
- }
-
-
- @Override
- public LogEntry findLogById(Long logId) {
- try {
- return OlogObjectMappers.logEntryDeserializer.readValue(
- service
- .path("logs")
- .path(logId.toString())
- .accept(MediaType.APPLICATION_JSON).get(String.class), OlogLog.class);
- } catch (JsonProcessingException e) {
- return null;
- }
- }
-
- @Override
- public Listnull
.
- * @param password Password, must not be null
.
- * @throws Exception if the login fails, e.g. bad credentials or service off-line.
- */
- public void authenticate(String username, String password) throws Exception {
- try {
- ClientResponse clientResponse = service.path("login")
- .type(MediaType.APPLICATION_JSON)
- .post(ClientResponse.class, OlogObjectMappers.logEntrySerializer.writeValueAsString(new LoginCredentials(username, password)));
- if (clientResponse.getStatus() == Status.UNAUTHORIZED.getStatusCode()) {
- throw new Exception("Failed to login: user unauthorized");
- } else if (clientResponse.getStatus() != Status.OK.getStatusCode()) {
- throw new Exception("Failed to login, got HTTP status " + clientResponse.getStatus());
- }
- } catch (Exception e) {
- logger.log(Level.SEVERE, "Failed to log in to Olog service", e);
- throw e;
- }
- }
-
- @Override
- public String serviceInfo() {
- ClientResponse clientResponse = service.path("").get(ClientResponse.class);
- return clientResponse.getEntity(String.class);
- }
-
- @Override
- public SearchResult getArchivedEntries(long id){
- try {
- final OlogSearchResult ologSearchResult = OlogObjectMappers.logEntryDeserializer.readValue(
- service.path("logs/archived/" + id)
- .header(OLOG_CLIENT_INFO_HEADER, CLIENT_INFO)
- .accept(MediaType.APPLICATION_JSON)
- .get(String.class),
- OlogSearchResult.class);
- return SearchResult.of(new ArrayList<>(ologSearchResult.getLogs()),
- ologSearchResult.getHitCount());
- } catch (UniformInterfaceException | ClientHandlerException | IOException e) {
- logger.log(Level.WARNING, "failed to retrieve archived log entries", e);
- throw new RuntimeException(e);
- }
- }
-
- @Override
- public Collectionlog
parameter as a reply to
+ * the log entry represented by inReplyTo
.
+ * @return The saved log entry.
+ * @throws LogbookException E.g. due to invalid log entry data, or if attachment content type
+ * cannot be determined.
+ */
+ private LogEntry save(LogEntry log, LogEntry inReplyTo) throws LogbookException {
+ try {
+ MultivaluedMaplogId
+ *
+ * @param logId LogEntry id
+ * @return LogEntry object
+ */
+ @Override
+ public LogEntry getLog(Long logId) {
+ return findLogById(logId);
+ }
+
+ @Override
+ public LogEntry findLogById(Long logId) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(Preferences.olog_url + "/logs/" + logId))
+ .header("Content-Type", CONTENT_TYPE_JSON)
+ .GET()
+ .build();
+
+ try {
+ HttpResponsenull
.
+ * @param password Password, must not be null
.
+ * @throws Exception if the login fails, e.g. bad credentials or service off-line.
+ */
+ public void authenticate(String userName, String password) throws Exception {
+
+ String stringBuilder = Preferences.olog_url +
+ "/login";
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(stringBuilder))
+ .header("Content-Type", CONTENT_TYPE_JSON)
+ .POST(HttpRequest.BodyPublishers.ofString(OBJECT_MAPPER.writeValueAsString(new LoginCredentials(userName, password))))
+ .build();
+ HttpResponse
* TODO the service might be less confusing if it simply returned the registered
* {@link LogFactory}'s and then has the calling code submit logging request to
* the clients.
- *
- * @author Kunal Shroff
*
+ * @author Kunal Shroff
*/
public class LogService {
- /** Suggested logger for all log book related code. */
+ /**
+ * Suggested logger for all log book related code.
+ */
public static final Logger logger = Logger.getLogger(LogService.class.getPackageName());
- static final java.lang.String SERVICE_NAME = "LoggingService";
-
- public static final String AUTHENTICATION_SCOPE = "Logbook";
-
private static LogService logService;
private ServiceLoader
+ * Utility class for the purpose of creating a multipart HTTP request body. Supports text (String) and
+ * binary (files), or a mix.
+ * Inspired by this example.
+ *
+ * Each added part is appended as a byte array representation to a {@link ByteArrayOutputStream}.
+ * When all parts have been added, client code must call {@link #getBytes()} to acquire the body used in the call to a server,
+ * and must call {@link #getContentType()} to be able to set the correct Content-Type header of the request.
+ * non-null
file.
+ * @throws RuntimeException if the file does not exist or cannot be read.
+ */
+ public void addFilePart(File file) {
+ if (file == null){
+ throw new RuntimeException("File part must not be null");
+ }
+ else if(!file.exists() || !file.canRead()){
+ throw new RuntimeException("File " + file.getAbsolutePath() + " does not exist or cannot be read");
+ }
+ StringBuilder stringBuilder = new StringBuilder();
+ // Default generic content type...
+ String contentType = "application/octet-stream";
+ try {
+ // ... but try to determine something more specific
+ String probedType = Files.probeContentType(file.toPath());
+ if(probedType != null){
+ contentType = probedType;
+ }
+ } catch (IOException e) {
+ Logger.getLogger(HttpRequestMultipartBody.class.getName()).log(Level.WARNING, "Unable to determine content type of file " + file.getAbsolutePath(), e);
+ }
+ stringBuilder.append("\r\n--").append(boundary).append("\r\nContent-Disposition: form-data; name=\"").append("files").append("\"; filename=\"").append(file.getName()).append("\"");
+ stringBuilder.append("\r\nContent-Type: ").append(contentType).append("\r\n\r\n");
+
+ byteArrayOutputStream.writeBytes(stringBuilder.toString().getBytes(StandardCharsets.UTF_8));
+ try {
+ Files.copy(file.toPath(), byteArrayOutputStream);
+ } catch (IOException e) {
+ Logger.getLogger(HttpRequestMultipartBody.class.getName()).log(Level.WARNING, "Failed to copy content of file part", e);
+ }
+ }
+
+ /**
+ * @return The body of the multipart request.
+ */
+ public byte[] getBytes() {
+ // Add last boundary
+ byteArrayOutputStream.writeBytes(("\r\n--" + boundary + "--").getBytes(StandardCharsets.UTF_8));
+ return byteArrayOutputStream.toByteArray();
+ }
+}
diff --git a/core/util/src/test/java/org/phoebus/util/http/HttpRequestMultipartBodyTest.java b/core/util/src/test/java/org/phoebus/util/http/HttpRequestMultipartBodyTest.java
new file mode 100644
index 0000000000..2c68990f45
--- /dev/null
+++ b/core/util/src/test/java/org/phoebus/util/http/HttpRequestMultipartBodyTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 European Spallation Source ERIC.
+ */
+
+package org.phoebus.util.http;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class HttpRequestMultipartBodyTest {
+
+ @Test
+ public void testContentType() {
+ String contentType = new HttpRequestMultipartBody().getContentType();
+ // Assume generated random boundary string is at least 50 chars
+ assertTrue(("multipart/form-data; boundary=".length() + 50) < contentType.length());
+ }
+
+ @Test
+ public void testBody() throws IOException {
+ HttpRequestMultipartBody httpRequestMultipartBody = new HttpRequestMultipartBody();
+ String boundary = httpRequestMultipartBody.getContentType().substring(httpRequestMultipartBody.getContentType().indexOf("=") + 1);
+
+ httpRequestMultipartBody.addTextPart("fieldName", "{\"json\":\"content\"}", "application/json");
+
+ File file = File.createTempFile("prefix", "tmp");
+ file.deleteOnExit();
+ FileOutputStream fileOutputStream = new FileOutputStream(file);
+ fileOutputStream.write("fileContent".getBytes(StandardCharsets.UTF_8));
+ fileOutputStream.flush();
+ fileOutputStream.close();
+
+ httpRequestMultipartBody.addFilePart(file);
+
+ String body = new String(httpRequestMultipartBody.getBytes());
+
+ String expected = "\r\n--" + boundary +
+ "\r\nContent-Disposition: form-data; name=\"fieldName\"" +
+ "\r\nContent-Type: application/json" +
+ "\r\n\r\n" +
+ "{\"json\":\"content\"}" +
+ "\r\n--" + boundary +
+ "\r\nContent-Disposition: form-data; name=\"files\"; filename=\"" + file.getName() + "\"" +
+ "\r\nContent-Type: application/octet-stream" +
+ "\r\n\r\n" +
+ "fileContent" +
+ "\r\n--" + boundary + "--";
+
+ assertEquals(expected, body);
+ }
+}