Skip to content

Commit

Permalink
1823 null pointer exception when importing storyweaver e pub (#1838)
Browse files Browse the repository at this point in the history
  • Loading branch information
Souvik-Cyclic authored Aug 23, 2024
2 parents 031a2a1 + 35b8e36 commit 20b1336
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 66 deletions.
18 changes: 18 additions & 0 deletions src/main/java/ai/elimu/service/storybook/StoryBookEPubService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ai.elimu.service.storybook;

import org.springframework.stereotype.Service;

import static java.util.Objects.isNull;

@Service
public class StoryBookEPubService {

public boolean isTableOfContentsFileHtmlLike(String fileName) {
if (isNull(fileName) || fileName.isBlank()) {
return false;
}

return fileName.endsWith(".xhtml") || fileName.endsWith(".html");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@
import ai.elimu.dao.StoryBookContributionEventDao;
import ai.elimu.dao.StoryBookDao;
import ai.elimu.dao.StoryBookParagraphDao;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.apache.logging.log4j.Logger;
import ai.elimu.model.content.StoryBook;
import ai.elimu.model.content.StoryBookChapter;
import ai.elimu.model.content.StoryBookParagraph;
Expand All @@ -21,6 +15,7 @@
import ai.elimu.model.contributor.StoryBookContributionEvent;
import ai.elimu.model.v2.enums.ReadingLevel;
import ai.elimu.model.v2.enums.content.ImageFormat;
import ai.elimu.service.storybook.StoryBookEPubService;
import ai.elimu.util.DiscordHelper;
import ai.elimu.util.ImageColorHelper;
import ai.elimu.util.ImageHelper;
Expand All @@ -29,24 +24,11 @@
import ai.elimu.util.epub.EPubMetadataExtractionHelper;
import ai.elimu.util.epub.EPubParagraphExtractionHelper;
import ai.elimu.web.context.EnvironmentContextLoaderListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
Expand All @@ -59,42 +41,64 @@
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.ByteArrayMultipartFileEditor;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Controller
@RequestMapping("/content/storybook/create-from-epub")
public class StoryBookCreateFromEPubController {

private final Logger logger = LogManager.getLogger();

@Autowired
private StoryBookDao storyBookDao;

@Autowired
private StoryBookContributionEventDao storyBookContributionEventDao;

@Autowired
private ImageDao imageDao;

@Autowired
private ImageContributionEventDao imageContributionEventDao;

@Autowired
private StoryBookChapterDao storyBookChapterDao;

@Autowired
private StoryBookParagraphDao storyBookParagraphDao;

@Autowired
private StoryBookEPubService storyBookEPubService;

@RequestMapping(method = RequestMethod.GET)
public String handleRequest(Model model) {
logger.info("handleRequest");

StoryBook storyBook = new StoryBook();
model.addAttribute("storyBook", storyBook);

model.addAttribute("timeStart", System.currentTimeMillis());

return "content/storybook/create-from-epub";
}

@RequestMapping(method = RequestMethod.POST)
public String handleSubmit(
StoryBook storyBook,
Expand All @@ -105,28 +109,28 @@ public String handleSubmit(
HttpSession session
) throws IOException {
logger.info("handleSubmit");

Image storyBookCoverImage = null;

List<StoryBookChapter> storyBookChapters = new ArrayList<>();

List<StoryBookParagraph> storyBookParagraphs = new ArrayList<>();

if (multipartFile.isEmpty()) {
result.rejectValue("bytes", "NotNull");
} else {
String contentType = multipartFile.getContentType();
logger.info("contentType: " + contentType);

String name = multipartFile.getName();
logger.info("name: " + name);

String originalFilename = multipartFile.getOriginalFilename();
logger.info("originalFilename: " + originalFilename);

long size = multipartFile.getSize();
logger.info("size: " + size + " (" + (size / 1024) + "kB)");

byte[] ePubBytes = multipartFile.getBytes();
logger.info("ePubBytes.length: " + (ePubBytes.length / 1024 / 1024) + "MB");

Expand Down Expand Up @@ -215,7 +219,7 @@ public String handleSubmit(
throw new IllegalArgumentException("The TOC file was not found");
} else {
List<String> chapterReferences = null;
if (tableOfContentsFile.getName().endsWith(".xhtml")) {
if (storyBookEPubService.isTableOfContentsFileHtmlLike(tableOfContentsFile.getName())) {
// StoryBookProvider#GLOBAL_DIGITAL_LIBRARY or StoryBookProvider#LETS_READ_ASIA
chapterReferences = EPubChapterExtractionHelper.extractChapterReferencesFromTableOfContentsFile(tableOfContentsFile);
} else if (tableOfContentsFile.getName().endsWith(".ncx")) {
Expand All @@ -239,16 +243,16 @@ public String handleSubmit(
File chapterImageFile = null;
if (chapterImageReference.startsWith("http://") || chapterImageReference.startsWith("https://")) {
// Download the file

URL sourceUrl = new URL(chapterImageReference);

String tmpDir = System.getProperty("java.io.tmpdir");
logger.info("tmpDir: " + tmpDir);
File tmpDirElimuAi = new File(tmpDir, "elimu-ai");
logger.info("tmpDirElimuAi: " + tmpDirElimuAi);
logger.info("tmpDirElimuAi.mkdir(): " + tmpDirElimuAi.mkdir());
chapterImageFile = new File(tmpDirElimuAi, "chapter-image");

logger.warn("Downloading image from " + sourceUrl + " and storing at " + chapterImageFile);
int connectionTimeout = 1000 * 10; // 1000 milliseconds x 10
int readTimeout = 1000 * 10; // 1000 milliseconds x 10
Expand Down Expand Up @@ -321,14 +325,14 @@ public String handleSubmit(
}
}
}

if (result.hasErrors()) {
return "content/storybook/create-from-epub";
} else {
// Store the StoryBook in the database
storyBook.setTimeLastUpdate(Calendar.getInstance());
storyBookDao.create(storyBook);

StoryBookContributionEvent storyBookContributionEvent = new StoryBookContributionEvent();
storyBookContributionEvent.setContributor((Contributor) session.getAttribute("contributor"));
storyBookContributionEvent.setTimestamp(Calendar.getInstance());
Expand All @@ -337,19 +341,19 @@ public String handleSubmit(
storyBookContributionEvent.setComment("Uploaded ePUB file (🤖 auto-generated comment)");
storyBookContributionEvent.setTimeSpentMs(System.currentTimeMillis() - Long.valueOf(request.getParameter("timeStart")));
storyBookContributionEventDao.create(storyBookContributionEvent);

// Store the StoryBook's cover image in the database, and assign it to the StoryBook
storyBookCoverImage.setTitle("storybook-" + storyBook.getId() + "-cover");
imageDao.create(storyBookCoverImage);
storeImageContributionEvent(storyBookCoverImage, session, request);
storyBook.setCoverImage(storyBookCoverImage);
storyBookDao.update(storyBook);

// Store the StoryBookChapters in the database
int chapterSortOrder = 0;
for (StoryBookChapter storyBookChapter : storyBookChapters) {
storyBookChapter.setStoryBook(storyBook);

// Get the paragraphs associated with this chapter
List<StoryBookParagraph> storyBookParagraphsAssociatedWithChapter = new ArrayList<>();
for (StoryBookParagraph storyBookParagraph : storyBookParagraphs) {
Expand All @@ -358,7 +362,7 @@ public String handleSubmit(
}
}
logger.info("storyBookParagraphsAssociatedWithChapter.size(): " + storyBookParagraphsAssociatedWithChapter.size());

// Exclude chapters containing book metadata
boolean isMetadata = false;
for (StoryBookParagraph storyBookParagraph : storyBookParagraphsAssociatedWithChapter) {
Expand All @@ -382,24 +386,24 @@ public String handleSubmit(
if (isMetadata) {
continue;
}

// Update the chapter's sort order (in case any of the previous chapters were excluded)
storyBookChapter.setSortOrder(chapterSortOrder);

// Store the chapter's image (if any)
Image chapterImage = storyBookChapter.getImage();
if (chapterImage != null) {
chapterImage.setTitle("storybook-" + storyBook.getId() + "-ch-" + (storyBookChapter.getSortOrder() + 1));
imageDao.create(chapterImage);
storeImageContributionEvent(chapterImage, session, request);
}

// Only store the chapter if it has an image or at least one paragraph
if ((chapterImage != null) || (!storyBookParagraphsAssociatedWithChapter.isEmpty())) {
storyBookChapterDao.create(storyBookChapter);
chapterSortOrder++;
}

// Store the chapter's paragraphs in the database
for (StoryBookParagraph storyBookParagraph : storyBookParagraphsAssociatedWithChapter) {
storyBookParagraph.setStoryBookChapter(storyBookChapter);
Expand All @@ -422,7 +426,7 @@ public String handleSubmit(
logger.info("predictedReadingLevel: " + predictedReadingLevel);
storyBook.setReadingLevel(predictedReadingLevel);
storyBookDao.update(storyBook);

if (!EnvironmentContextLoaderListener.PROPERTIES.isEmpty()) {
String contentUrl = "https://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/content/storybook/edit/" + storyBook.getId();
String embedThumbnailUrl = null;
Expand All @@ -437,11 +441,11 @@ public String handleSubmit(
embedThumbnailUrl
);
}

return "redirect:/content/storybook/edit/" + storyBook.getId();
}
}

/**
* See http://www.mkyong.com/spring-mvc/spring-mvc-failed-to-convert-property-value-in-file-upload-form/
* <p></p>
Expand All @@ -453,15 +457,15 @@ protected void initBinder(HttpServletRequest request, ServletRequestDataBinder b
logger.info("initBinder");
binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
}

/**
* Unzip the contents of the ePUB file to a temporary folder.
*/
private List<File> unzipFiles(byte[] ePubBytes, String originalFilename) {
logger.info("unzipFiles");

List<File> unzippedFiles = new ArrayList<>();

String tmpDir = System.getProperty("java.io.tmpdir");
logger.info("tmpDir: " + tmpDir);
File tmpDirElimuAi = new File(tmpDir, "elimu-ai");
Expand All @@ -477,27 +481,27 @@ private List<File> unzipFiles(byte[] ePubBytes, String originalFilename) {
ZipEntry zipEntry = null;
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
logger.info("zipEntry: " + zipEntry);

// E.g. unzipDestinationDirectory + "/" + "META-INF/container.xml"
File unzipDestinationFile = new File(unzipDestinationDirectory + File.separator + zipEntry.toString());
logger.info("unzipDestinationFile: " + unzipDestinationFile);

// Create intermediate folders if they do not already exist, e.g. "META-INF/", "content/" or "OEBPS/"
File parentDirectory = unzipDestinationFile.getParentFile();
logger.info("parentDirectory: " + parentDirectory);
if (!parentDirectory.exists()) {
boolean parentDirectoriesWereCreated = parentDirectory.mkdirs();
logger.info("parentDirectory.mkdirs(): " + parentDirectoriesWereCreated);
}

// Write file to disk
FileOutputStream fileOutputStream = new FileOutputStream(unzipDestinationFile);
int length;
while ((length = zipInputStream.read(buffer)) > 0) {
fileOutputStream.write(buffer, 0, length);
}
fileOutputStream.close();

logger.info("unzipDestinationFile.exists(): " + unzipDestinationFile.exists());
unzippedFiles.add(unzipDestinationFile);
}
Expand All @@ -508,13 +512,13 @@ private List<File> unzipFiles(byte[] ePubBytes, String originalFilename) {
} catch (IOException ex) {
logger.error(ex);
}

return unzippedFiles;
}

private void storeImageContributionEvent(Image image, HttpSession session, HttpServletRequest request) {
logger.info("storeImageContributionEvent");

ImageContributionEvent imageContributionEvent = new ImageContributionEvent();
imageContributionEvent.setContributor((Contributor) session.getAttribute("contributor"));
imageContributionEvent.setTimestamp(Calendar.getInstance());
Expand All @@ -528,7 +532,7 @@ private void storeImageContributionEvent(Image image, HttpSession session, HttpS
String contentUrl = "https://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/content/multimedia/image/edit/" + image.getId();
String embedThumbnailUrl = "https://" + EnvironmentContextLoaderListener.PROPERTIES.getProperty("content.language").toLowerCase() + ".elimu.ai/image/" + image.getId() + "_r" + image.getRevisionNumber() + "." + image.getImageFormat().toString().toLowerCase();
DiscordHelper.sendChannelMessage(
"Image created: " + contentUrl,
"Image created: " + contentUrl,
"\"" + image.getTitle() + "\"",
"Comment: \"" + imageContributionEvent.getComment() + "\"",
null,
Expand Down
Loading

0 comments on commit 20b1336

Please sign in to comment.