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
188 changes: 188 additions & 0 deletions lib/src/main/java/org/koppe/epub/client/AuthorAdapter.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
package org.koppe.epub.client;

import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.koppe.epub.client.cache.CacheType;
import org.koppe.epub.client.dto.AuthorDto;
import org.koppe.epub.client.dto.PagedRequestDto;
import org.koppe.epub.client.exceptions.ApiCallException;
import org.koppe.epub.client.exceptions.BadRequestException;
import org.koppe.epub.client.exceptions.ForbiddenException;
import org.koppe.epub.client.exceptions.NotFoundException;
import org.koppe.epub.client.exceptions.ServerErrorException;
import org.koppe.epub.client.exceptions.SessionExpiredException;
import org.koppe.epub.client.exceptions.UnexpectedStatusException;
import org.koppe.epub.client.http.AuthorQueryBuilder;
import org.koppe.epub.client.http.HttpQuery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -32,7 +37,14 @@ class AuthorAdapter {
* Client to execute requests
*/
private final EpubClient client;
/**
* Object mapper
*/
private final ObjectMapper mapper = new ObjectMapper();
/**
* Executor for running parallel threads
*/
private final ExecutorService executor = Executors.newSingleThreadExecutor();

/**
* Default constructor
Expand Down Expand Up @@ -102,4 +114,180 @@ protected AuthorAdapter(EpubClient client) {
return dto;
}

// #region get author
/**
* Queries api for author with given id.
*
* @param jwt JWT to authenticate at the api
* @param authorId Id of the author to query
* @param query Defines attributes the api should return, for example whether
* books should be returned as well
* @return Queried author or null, if no such author exists
* @throws ApiCallException General wrapper for all unexpected api errors
* @throws SessionExpiredException If the jwt has expired
*/
protected @Nullable AuthorDto getAuthorById(@NotNull String jwt, long authorId, @Nullable HttpQuery query)
throws ApiCallException, SessionExpiredException {
if (jwt == null || jwt.isBlank()) {
logger.info("Invalid jwt given");
throw new IllegalArgumentException("Missing jwt");
}

Object cached = client.checkCache(CacheType.AUTHORS, (Long) authorId);
if (cached != null && (cached instanceof AuthorDto)) {
logger.info("Author with given id already cached");
return (AuthorDto) cached;
}

logger.info("Retrieving authors for id {}", authorId);
Request.Builder builder = new Request.Builder()
.url(String.format("%s/authors/%s%s", client.url(), "" + authorId,
(query != null ? query.toQueryString() : "")))
.get();

client.addHeaders(builder, jwt, EpubClient.APPLICATION_JSON_STRING, EpubClient.APPLICATION_JSON_STRING);
logger.debug("Executing request");

AuthorDto dto = null;
try {
dto = client.executeRequest(builder.build(), AuthorDto.class, query, true);
} catch (BadRequestException | ForbiddenException
| ServerErrorException | UnexpectedStatusException | IOException e) {
logger.info("Request failed with an exception", e);
throw new ApiCallException(null, e);
} catch (SessionExpiredException ex) {
logger.info("Jwt expired");
throw ex;
} catch (NotFoundException ex) {
logger.info("Author with given id not found");
return null;
}

if (dto == null) {
logger.info("No author found");
return null;
}

client.cacheValue(CacheType.AUTHORS, dto.getId(), dto);
return dto;
}

// #region delete author
/**
* Deletes author with given id. If deleteWithBooks is set to true, all epubs
* assoicated with the given author are deleted as well. Use with caution!
*
* @param jwt JWT to authenticate at the api with
* @param authorId Id of the author to be deleted
* @param deleteWithBooks If set to true, all epubs associated with the author
* are deleted as well. USE WITH CAUTION.
* @return The deleted author or null, if no author with given id exists
* @throws IllegalArgumentException If no jwt is given
* @throws SessionExpiredException If the session has expired
* @throws ApiCallException If an unexpected error occurred during the
* api call.
* @throws BadRequestException If the server returned 400
*/
protected @Nullable AuthorDto deleteAuthor(@NotNull String jwt, long authorId, boolean deleteWithBooks)
throws IllegalArgumentException, SessionExpiredException, ApiCallException, BadRequestException {
if (jwt == null || jwt.isBlank()) {
logger.info("Invalid jwt given");
throw new IllegalArgumentException("Missing jwt");
}

logger.info("Building query to delete author with id {}", authorId);
if (deleteWithBooks)
logger.warn("Deleting associated epubs as well");

HttpQuery query = new AuthorQueryBuilder().deleteEpubsAsWell(deleteWithBooks).build();
Request.Builder builder = new Request.Builder()
.url(String.format("%s/authors/%s%s", client.url(), "" + authorId, query.toQueryString()))
.delete();

client.addHeaders(builder, jwt, EpubClient.APPLICATION_JSON_STRING, EpubClient.APPLICATION_JSON_STRING);

AuthorDto dto = null;
try {
dto = client.executeRequest(builder.build(), AuthorDto.class, query, false);
} catch (SessionExpiredException e) {
logger.info("Session has expired", e);
throw e;
} catch (BadRequestException e) {
logger.info("Author has not been deleted", e);
throw e;
} catch (ForbiddenException | ServerErrorException | UnexpectedStatusException | IOException e) {
logger.info("Exception occurred while querying the api", e);
throw new ApiCallException(null, e);
} catch (NotFoundException e) {
logger.info("Author with given id does not exist");
return null;
}

if (dto == null) {
logger.info("No dto received");
return null;
}
logger.info("Successfully deleted author {}", dto);
client.removeFromCache(CacheType.AUTHORS, dto.getId());

return dto;
}

// #region get all authors
/**
*
* @param jwt
* @param query
* @return
* @throws IllegalArgumentException
* @throws ApiCallException
* @throws SessionExpiredException
*/
public @Nullable PagedRequestDto<AuthorDto> getAllAuthors(@NotNull String jwt, @Nullable HttpQuery query)
throws IllegalArgumentException, ApiCallException, SessionExpiredException {
if (jwt == null || jwt.isBlank()) {
logger.info("No jwt given");
throw new IllegalArgumentException("Missing jwt");
}

if (query == null) {
logger.info("No query given, initialising default query");
query = new AuthorQueryBuilder().page(0).pageSize(1000).build();
}

if (query.get("page") == null)
query.overwrite("page", (Long) 0L);
if (query.get("page_size") == null)
query.overwrite("page_size", (Long) 1000L);

Request.Builder builer = new Request.Builder()
.url(String.format("%s/authors%s", client.url(), query.toQueryString()))
.get();
client.addHeaders(builer, jwt, EpubClient.APPLICATION_JSON_STRING, EpubClient.APPLICATION_JSON_STRING);
logger.info("Querying api for all authors");

PagedRequestDto<AuthorDto> authors = null;
try {
authors = client.executeRequestPaged(builer.build(), AuthorDto.class, query, true);
} catch (BadRequestException | ForbiddenException | NotFoundException
| ServerErrorException | UnexpectedStatusException | IOException e) {
logger.info("Unexpected status returned by api", e);
throw new ApiCallException(null, e);
} catch (SessionExpiredException ex) {
logger.info("Session has expired");
throw ex;
}

if (authors == null || authors.getContent() == null) {
logger.info("No authors found");
return null;
}

final var finalAuthors = authors;
logger.info("Caching all authors threaded");
executor.submit(
() -> finalAuthors.getContent().forEach(a -> client.cacheValue(CacheType.AUTHORS, a.getId(), a)));
return authors;
}

}
94 changes: 93 additions & 1 deletion lib/src/main/java/org/koppe/epub/client/EpubClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import tools.jackson.databind.ObjectMapper;

/**
Expand Down Expand Up @@ -1013,6 +1014,87 @@ private void uploadEpub(@NotNull String jwt, @NotNull String uploadGuid, @NotNul
authors = new AuthorAdapter(this);
return authors.addAuthor(jwt, author);
}
// #endregion add author

// #region get author
/**
* Returns the author with given id. Query defines what the backend should add
* into the AuthorDto (for example if epubs should be returned as well.)
* Example of getting author with id 1, as well as all epubs associated with it:
*
* <pre>
* {@code
* HttpQuery query = new AuthorQueryBuilder().withEpubs(true).build();
* AuthorDto dto = client.getAuthor("admin", "admin", 1L, query);
* }
* </pre>
*
* @param username Username to authenticate at the api
* @param password Password to authenticate at the api
* @param authorId Id of the author to return
* @param query Query for getting author.
* @return Requested author.
* @throws IllegalArgumentException If username or password are missing
* @throws ApiCallException General api error wrapper
* @throws SessionExpiredException If client could not authenticate
*/
public @Nullable AuthorDto getAuthor(@NotNull String username, @NotNull String password, long authorId,
@Nullable HttpQuery query) throws IllegalArgumentException, ApiCallException, SessionExpiredException {
return getAuthor(getNewJwt(username, password), authorId, query);
}

/**
* Returns the author with given id. Query defines what the backend should add
* into the AuthorDto (for example if epubs should be returned as well.)
* Requires cached credentials.
* Example of getting author with id 1, as well as all epubs associated with it:
*
* <pre>
* {@code
* HttpQuery query = new AuthorQueryBuilder().withEpubs(true).build();
* AuthorDto dto = client.getAuthor(1L, query);
* }
* </pre>
*
* @param authorId Id of the author to return
* @param query Query for getting author.
* @return Requested author.
* @throws CacheMissException
* @throws ApiCallException General api error wrapper
* @throws SessionExpiredException If client could not authenticate
*/
public @Nullable AuthorDto getAuthor(long authorId, @Nullable HttpQuery query)
throws CacheMissException, ApiCallException, SessionExpiredException {
return getAuthor(getCurrentJwt(), authorId, query);
}

/**
* Returns the author with given id. Query defines what the backend should add
* into the AuthorDto (for example if epubs should be returned as well.)
* Example of getting author with id 1, as well as all epubs associated with it:
*
* <pre>
* {@code
* HttpQuery query = new AuthorQueryBuilder().withEpubs(true).build();
* AuthorDto dto = client.getAuthor("jwt-123", 1L, query);
* }
* </pre>
*
* @param jwt JWT to authenticate at the api
* @param authorId Id of the author to return
* @param query Query for getting author.
* @return Requested author.
* @throws IllegalArgumentException If username or password are missing
* @throws ApiCallException General api error wrapper
* @throws SessionExpiredException If client could not authenticate
*/
private @Nullable AuthorDto getAuthor(@NotNull String jwt, long authorId, @Nullable HttpQuery query)
throws ApiCallException, SessionExpiredException {
if (authors == null)
authors = new AuthorAdapter(this);
return authors.getAuthorById(jwt, authorId, query);
}
// #endregion get author

// #region register cache
/**
Expand Down Expand Up @@ -1128,7 +1210,17 @@ private String getNewJwt(@NotNull String username, @NotNull String password)
logger.info("Expecting void, returning");
return null;
}
String body = response.body().string();
ResponseBody resp = response.body();
if (resp == null) {
logger.info("No response body");
return null;
}
String body = resp.string();
if (body == null || body.isBlank()) {
logger.info("No response body");
return null;
}

dto = mapper.readValue(body, type);
break;
case 204:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.koppe.epub.client.cache;

import org.koppe.epub.client.dto.AuthorDto;

public class AuthorCache extends AbstractCache<Long, AuthorDto> {

}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,16 @@ public static EditionCache newDefaultEditionCache() {
case CREDENTIALS -> newDefaultCredentialCache(client);
case EPUBS -> newDefaultEpubCache();
case EDITIONS -> newDefaultEditionCache();
case AUTHORS -> newDefaultAuthorCache();
default -> null;
};
}

public static AuthorCache newDefaultAuthorCache() {
AuthorCache cache = new AuthorCache();
cache.setRetention(10, TimeUnit.MINUTES);
cache.setMaxElements(100);

return cache;
}
}
1 change: 1 addition & 0 deletions lib/src/main/java/org/koppe/epub/client/dto/AuthorDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ public class AuthorDto {
private String description;
private List<EpubDto> epubs;
private List<TagDto> tags;
private List<GenreDto> genres;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.koppe.epub.client.http;

import lombok.NoArgsConstructor;

/**
* Builder for queries associated with authors
*/
@NoArgsConstructor
public class AuthorQueryBuilder extends AbstractQueryBuilder {

/**
* If set to true and given to a delete query, all epubs the given author is
* associated with are deleted as well. Use with caution!
*
* @param delete Set to true to delete all epubs associated with the given
* author as well.
* @return This builder
*/
public AuthorQueryBuilder deleteEpubsAsWell(boolean delete) {
getBuilder().addParam(Boolean.class, "with_epubs", delete);
return this;
}

public AuthorQueryBuilder withEpubs(boolean w) {
getBuilder().addParam(Boolean.class, "with_epubs", w);
return this;
}
}
Loading
Loading