Skip to content

Commit

Permalink
Added uploading blocked pages list files [#112]
Browse files Browse the repository at this point in the history
  • Loading branch information
mcpierce committed Apr 21, 2021
1 parent de1551b commit 3000955
Show file tree
Hide file tree
Showing 28 changed files with 900 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.adaptors;
package org.comixedproject.adaptors.csv;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVPrinter;
import org.apache.commons.csv.CSVRecord;
import org.springframework.stereotype.Component;

/**
Expand All @@ -44,16 +44,32 @@ public class CsvAdaptor {
* @return the CSV data
* @throws IOException if an error occurs
*/
public <T> byte[] encodeRecords(List<T> records, CsvRowHandler<T> handler) throws IOException {
final ByteArrayOutputStream result = new ByteArrayOutputStream();
final OutputStreamWriter output = new OutputStreamWriter(result);
final CSVPrinter printer =
public <T> byte[] encodeRecords(List<T> records, CsvRowEncoder<T> handler) throws IOException {
final var result = new ByteArrayOutputStream();
final var output = new OutputStreamWriter(result);
final var printer =
new CSVPrinter(output, CSVFormat.DEFAULT.withHeader(handler.createRow(0, null)));
for (int index = 0; index < records.size(); index++) {

for (var index = 0; index < records.size(); index++) {
printer.printRecord(handler.createRow(index + 1, records.get(index)));
}
printer.flush();
printer.close();
return result.toByteArray();
}

public void decodeRecords(
final InputStream inputStream, final String[] header, CsvRowDecoder handler)
throws IOException {
final List<CSVRecord> records =
CSVFormat.DEFAULT.withHeader(header).parse(new InputStreamReader(inputStream)).getRecords();

for (var index = 0; index < records.size(); index++) {
log.info("Processing row: index={}", index);
final CSVRecord row = records.get(index);
List<String> values = new ArrayList<>();
for (var which = 0; which < row.size(); which++) values.add(row.get(which));
handler.processRow(index, values);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2021, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.adaptors.csv;

import java.util.List;

/**
* <code>CsvRowDecoder</code> defines a type that processes a single row of data from a CSV file
* being processed.
*
* @author Darryl L. Pierce
*/
@FunctionalInterface
public interface CsvRowDecoder {
/**
* Processes a single row from the CSV file.
*
* @param index the row number
* @param row the row
*/
public void processRow(final int index, final List<String> row);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.adaptors;
package org.comixedproject.adaptors.csv;

/**
* <code>CsvRowHandler</code> defines a type that processes a single row of data for a CSV file
* <code>CsvRowEncoder</code> defines a type that processes a single row of data for a CSV file
* being created.
*
* @param <T> the type for each row
* @author Darryl L. Pierce
*/
@FunctionalInterface
public interface CsvRowHandler<T> {
public interface CsvRowEncoder<T> {
/**
* Takes a row from the data model and returns a row for generated document.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.RandomStringUtils;
import org.comixedproject.adaptors.csv.CsvAdaptor;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package org.comixedproject.adaptors.csv;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.comixedproject.model.blockedpage.BlockedPage;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class CsvAdaptorTest {
private static final String TEST_PAGE_LABEL = "The blocked page label";
private static final String TEST_PAGE_HASH = "The page hash";
private static final String TEST_PAGE_SNAPSHOT = "The blocked page content encoded";
private static final String LABEL = "LABEL";
private static final String HASH = "HASH";
private static final String SNAPSHOT = "SNAPSHOT";

@InjectMocks private CsvAdaptor adaptor;

private BlockedPage blockedPage =
new BlockedPage(TEST_PAGE_LABEL, TEST_PAGE_HASH, TEST_PAGE_SNAPSHOT);
private List<BlockedPage> blockedPages = new ArrayList<>();

@Test
public void testEncodeAndDecode() throws IOException {
blockedPages.add(blockedPage);

final byte[] encoded =
adaptor.encodeRecords(
blockedPages,
(index, model) -> {
if (index == 0) {
return new String[] {LABEL, HASH, SNAPSHOT};
} else {
return new String[] {model.getLabel(), model.getHash(), model.getSnapshot()};
}
});

assertNotNull(encoded);

adaptor.decodeRecords(
new ByteArrayInputStream(encoded),
new String[] {LABEL, HASH, SNAPSHOT},
(index, row) -> {
if (index == 0) {
assertEquals(LABEL, row.get(0));
assertEquals(HASH, row.get(1));
assertEquals(SNAPSHOT, row.get(2));
} else {
assertEquals(TEST_PAGE_LABEL, row.get(0));
assertEquals(TEST_PAGE_HASH, row.get(1));
assertEquals(TEST_PAGE_SNAPSHOT, row.get(2));
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

/**
* <code>BlockedPageController</code> provides endpoints for working with instances of {@link
Expand Down Expand Up @@ -164,4 +166,22 @@ public DownloadDocument downloadFile() throws IOException {
log.info("Downloading blocked page file");
return this.blockedPageService.createFile();
}

/**
* Processes an uploaded blocked page file.
*
* @param file the uploaded file
* @return the updated blocked page list
* @throws BlockedPageException if a service exception occurs
* @throws IOException if a file exception occurs
*/
@PostMapping(value = "/api/pages/blocked/file", produces = MediaType.APPLICATION_JSON_VALUE)
@AuditableEndpoint
@JsonView(View.BlockedPageList.class)
@PreAuthorize("hasRole('ADMIN')")
public List<BlockedPage> uploadFile(final MultipartFile file)
throws BlockedPageException, IOException {
log.info("Received uploaded blocked page file: {}", file.getOriginalFilename());
return this.blockedPageService.uploadFile(file.getInputStream());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import org.comixedproject.model.blockedpage.BlockedPage;
import org.comixedproject.model.comic.Page;
Expand All @@ -42,6 +43,7 @@
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.multipart.MultipartFile;

@RunWith(MockitoJUnitRunner.class)
public class BlockedPageControllerTest {
Expand All @@ -59,6 +61,8 @@ public class BlockedPageControllerTest {
@Mock private BlockedPage blockedPageRecord;
@Mock private Page page;
@Mock private DownloadDocument downloadDocument;
@Mock private MultipartFile uploadedFile;
@Mock private InputStream inputStream;

@Before
public void setUp() {
Expand Down Expand Up @@ -244,4 +248,31 @@ public void testDownloadFile() throws IOException {

Mockito.verify(blockedPageService, Mockito.times(1)).createFile();
}

@Test(expected = IOException.class)
public void testUploadFileServiceException() throws BlockedPageException, IOException {
Mockito.when(uploadedFile.getInputStream()).thenReturn(inputStream);
Mockito.when(blockedPageService.uploadFile(Mockito.any(InputStream.class)))
.thenThrow(IOException.class);

try {
controller.uploadFile(uploadedFile);
} finally {
Mockito.verify(blockedPageService, Mockito.times(1)).uploadFile(inputStream);
}
}

@Test
public void testUploadFile() throws BlockedPageException, IOException {
Mockito.when(uploadedFile.getInputStream()).thenReturn(inputStream);
Mockito.when(blockedPageService.uploadFile(Mockito.any(InputStream.class)))
.thenReturn(blockedPageList);

final List<BlockedPage> response = controller.uploadFile(uploadedFile);

assertNotNull(response);
assertSame(blockedPageList, response);

Mockito.verify(blockedPageService, Mockito.times(1)).uploadFile(inputStream);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
package org.comixedproject.service.blockedpage;

import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
import java.util.List;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.lang.time.DateFormatUtils;
import org.comixedproject.adaptors.CsvAdaptor;
import org.comixedproject.adaptors.csv.CsvAdaptor;
import org.comixedproject.model.blockedpage.BlockedPage;
import org.comixedproject.model.net.DownloadDocument;
import org.comixedproject.repositories.blockedpage.BlockedPageRepository;
Expand Down Expand Up @@ -167,4 +168,39 @@ public DownloadDocument createFile() throws IOException {
"text/csv",
content);
}

/**
* Processes a received file of blocked pages.
*
* @param inputStream the data stream
* @return the updated list of blocked pages
* @throws IOException if an error occurs with the data stream.
*/
@Transactional
public List<BlockedPage> uploadFile(final InputStream inputStream) throws IOException {
this.csvAdaptor.decodeRecords(
inputStream,
new String[] {PAGE_LABEL_HEADER, PAGE_HASH_HEADER, PAGE_SNAPSHOT_HEADER},
(index, row) -> {
if (index > 0) {
final String label = row.get(0);
final String hash = row.get(1);
final String snapshot = row.get(2);

log.debug("Checking if blocked page already exists: hash={}", hash);
final BlockedPage blockedPage = this.blockedPageRepository.findByHash(hash);
if (blockedPage == null) {
log.debug("Creating new blocked page record");
this.doSaveRecord(label, hash, snapshot);
}
}
});
return this.blockedPageRepository.findAll();
}

@Transactional
public void doSaveRecord(final String label, final String hash, final String snapshot) {
final var blockedPage = new BlockedPage(label, hash, snapshot);
this.blockedPageRepository.save(blockedPage);
}
}

0 comments on commit 3000955

Please sign in to comment.