/** * Copyright (c) Codice Foundation * <p/> * This is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation, either version 3 of the * License, or any later version. * <p/> * 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 * Lesser General Public License for more details. A copy of the GNU Lesser General Public License * is distributed along with this program and can be found at * <http://www.gnu.org/licenses/lgpl.html>. */ package ddf.catalog.resource.impl; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import org.apache.cxf.jaxrs.client.WebClient; import org.apache.tika.metadata.HttpHeaders; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.MethodRule; import org.junit.rules.TestWatchman; import org.junit.runners.model.FrameworkMethod; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import junit.framework.Assert; import ddf.catalog.operation.ResourceResponse; import ddf.catalog.resource.Resource; import ddf.catalog.resource.ResourceNotFoundException; import ddf.mime.MimeTypeMapper; import ddf.mime.MimeTypeResolver; import ddf.mime.custom.CustomMimeTypeResolver; import ddf.mime.mapper.MimeTypeMapperImpl; import ddf.mime.tika.TikaMimeTypeResolver; public class ResourceReaderTest { private static final Logger LOGGER = LoggerFactory.getLogger(ResourceReaderTest.class); private static final String TEST_PATH = "/src/test/resources/data/"; private static final String DEFAULT_MIME_TYPE = "application/octet-stream"; private static final String JPEG_MIME_TYPE = "image/jpeg"; private static final String VIDEO_MIME_TYPE = "video/mpeg"; private static final String MP4_MIME_TYPE = "video/mp4"; private static final String CUSTOM_MIME_TYPE = "image/xyz"; private static final String CUSTOM_FILE_EXTENSION = "xyz"; private static final String JPEG_FILE_NAME_1 = "flower.jpg"; private static final String MPEG_FILE_NAME_1 = "test.mpeg"; private static final String MP4_FILE_NAME_1 = "sample.mp4"; private static final String PPT_FILE_NAME_1 = "MissionPlan.ppt"; private static final String PPTX_FILE_NAME_1 = "MissionPlan.pptx"; private static final String HTTP_SCHEME = "http"; private static final String HTTP_SCHEME_PLUS_SEP = "http://"; private static final String FILE_SCHEME_PLUS_SEP = "file:///"; private static final String ABSOLUTE_PATH = new File(".").getAbsolutePath(); private static final String HOST = "127.0.0.1"; private static final String BAD_FILE_NAME = "mydata?uri=63f30ff4dc85436ea507fceeb1396940_blahblahblah&this=that"; private static final String BYTES_TO_SKIP = "BytesToSkip"; @Rule public MethodRule watchman = new TestWatchman() { public void starting(FrameworkMethod method) { LOGGER.debug("*************************** STARTING: {} **************************\n" + method.getName()); } public void finished(FrameworkMethod method) { LOGGER.debug( "*************************** END: {} **************************\n" + method .getName()); } }; private MimeTypeMapper mimeTypeMapper; private CustomMimeTypeResolver customResolver; private WebClient mockWebClient = mock(WebClient.class); private static InputStream getBinaryData() { byte[] sampleBytes = {65, 66, 67, 68, 69}; return new ByteArrayInputStream(sampleBytes); } @Before public void setUp() { MimeTypeResolver tikaResolver = new TikaMimeTypeResolver(); this.customResolver = new CustomMimeTypeResolver(); List<MimeTypeResolver> resolvers = new ArrayList<MimeTypeResolver>(); resolvers.add(tikaResolver); resolvers.add(this.customResolver); this.mimeTypeMapper = new MimeTypeMapperImpl(resolvers); } @Test public void testURLResourceReaderBadQualifier() { URLResourceReader resourceReader = new TestURLResourceReader(mimeTypeMapper); String filePath = TEST_PATH + MPEG_FILE_NAME_1; HashMap<String, Serializable> arguments = new HashMap<String, Serializable>(); try { LOGGER.info("Getting resource: " + filePath); URI uri = new URI(FILE_SCHEME_PLUS_SEP + filePath); resourceReader.retrieveResource(uri, arguments); } catch (IOException e) { LOGGER.info("Successfully caught expected IOException"); fail(); } catch (ResourceNotFoundException e) { LOGGER.info("Caught unexpected ResourceNotFoundException"); assert (true); } catch (URISyntaxException e) { LOGGER.info("Caught unexpected URISyntaxException"); fail(); } } @Test public void testReadJPGFile() { String filePath = ABSOLUTE_PATH + TEST_PATH + JPEG_FILE_NAME_1; verifyFile(filePath, JPEG_FILE_NAME_1, JPEG_MIME_TYPE); } @Test public void testReadMPEGFile() { String filePath = ABSOLUTE_PATH + TEST_PATH + MPEG_FILE_NAME_1; verifyFile(filePath, MPEG_FILE_NAME_1, VIDEO_MIME_TYPE); } @Test public void testReadMP4File() { String filePath = ABSOLUTE_PATH + TEST_PATH + MP4_FILE_NAME_1; verifyFile(filePath, MP4_FILE_NAME_1, MP4_MIME_TYPE); } @Test public void testReadPPTFile() { String filePath = ABSOLUTE_PATH + TEST_PATH + PPT_FILE_NAME_1; verifyFile(filePath, PPT_FILE_NAME_1, "application/vnd.ms-powerpoint"); } @Test public void testReadPPTXFile() { String filePath = ABSOLUTE_PATH + TEST_PATH + PPTX_FILE_NAME_1; verifyFile(filePath, PPTX_FILE_NAME_1, "application/vnd.openxmlformats-officedocument.presentationml.presentation"); } @Test public void testReadFileWithUnknownExtension() { String filePath = ABSOLUTE_PATH + TEST_PATH + "UnknownExtension.hugh"; verifyFile(filePath, "UnknownExtension.hugh", DEFAULT_MIME_TYPE); } @Test public void testReadFileWithNoExtension() { String filePath = ABSOLUTE_PATH + TEST_PATH + "JpegWithoutExtension"; verifyFile(filePath, "JpegWithoutExtension", JPEG_MIME_TYPE); } @Test public void testReadFileWithCustomExtension() { // Add custom file extension to mime type mapping to custom mime type // resolver this.customResolver .setCustomMimeTypes(new String[] {CUSTOM_FILE_EXTENSION + "=" + CUSTOM_MIME_TYPE}); String filePath = ABSOLUTE_PATH + TEST_PATH + "CustomExtension.xyz"; verifyFile(filePath, "CustomExtension.xyz", CUSTOM_MIME_TYPE); } @Test public void testJpegWithUnknownExtension() { String filePath = ABSOLUTE_PATH + TEST_PATH + "JpegWithUnknownExtension.hugh"; verifyFile(filePath, "JpegWithUnknownExtension.hugh", JPEG_MIME_TYPE); } @Test public void testJpegWithCustomExtension() { // Add custom file extension to mime type mapping to custom mime type // resolver this.customResolver .setCustomMimeTypes(new String[] {CUSTOM_FILE_EXTENSION + "=" + CUSTOM_MIME_TYPE}); String filePath = ABSOLUTE_PATH + TEST_PATH + "JpegWithCustomExtension.xyz"; verifyFile(filePath, "JpegWithCustomExtension.xyz", CUSTOM_MIME_TYPE); } @Test public void testJpegWithOverriddenExtension() { // Override/redefine .jpg file extension to custom mime type mapping of // "image/xyz" this.customResolver.setCustomMimeTypes(new String[] {"jpg=" + CUSTOM_MIME_TYPE}); String filePath = ABSOLUTE_PATH + TEST_PATH + JPEG_FILE_NAME_1; verifyFile(filePath, JPEG_FILE_NAME_1, CUSTOM_MIME_TYPE); } @Test public void testURLResourceIOException() { URLResourceReader resourceReader = new URLResourceReader(mimeTypeMapper); String filePath = "JUMANJI!!!!"; HashMap<String, Serializable> arguments = new HashMap<String, Serializable>(); try { LOGGER.info("Getting resource: " + filePath); URI uri = new URI(FILE_SCHEME_PLUS_SEP + filePath); resourceReader.retrieveResource(uri, arguments); } catch (IOException e) { LOGGER.info("Successfully caught IOException"); fail(); } catch (ResourceNotFoundException e) { LOGGER.info("Caught ResourceNotFoundException"); assert (true); } catch (URISyntaxException e) { LOGGER.info("Caught unexpected URISyntaxException"); fail(); } } @Test public void testUrlToNonExistentFile() { URLResourceReader resourceReader = new URLResourceReader(mimeTypeMapper); String filePath = ABSOLUTE_PATH + TEST_PATH + "NonExistentFile.jpg"; HashMap<String, Serializable> arguments = new HashMap<String, Serializable>(); try { LOGGER.info("Getting resource: " + filePath); File file = new File(filePath); URI uri = file.toURI(); resourceReader.retrieveResource(uri, arguments); } catch (IOException e) { LOGGER.info("Successfully caught IOException"); fail(); } catch (ResourceNotFoundException e) { LOGGER.info("Caught ResourceNotFoundException"); assert (true); } } @Test public void testHTTPReturnsFileNameWithoutPath() throws URISyntaxException, IOException, ResourceNotFoundException { URI uri = new URI(HTTP_SCHEME_PLUS_SEP + HOST + TEST_PATH + JPEG_FILE_NAME_1); verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE); uri = new URI(FILE_SCHEME_PLUS_SEP + ABSOLUTE_PATH + TEST_PATH + JPEG_FILE_NAME_1); verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE); } @Test public void testNameInContentDisposition() throws URISyntaxException, IOException, ResourceNotFoundException { URI uri = new URI(HTTP_SCHEME_PLUS_SEP + HOST + TEST_PATH + BAD_FILE_NAME); Response mockResponse = mock(Response.class); when(mockWebClient.get()).thenReturn(mockResponse); MultivaluedMap<String, Object> map = new MultivaluedHashMap<>(); map.put(HttpHeaders.CONTENT_DISPOSITION, Arrays.<Object>asList("inline; filename=\"" + JPEG_FILE_NAME_1 + "\"")); when(mockResponse.getHeaders()).thenReturn(map); when(mockResponse.getStatus()).thenReturn(Response.Status.OK.getStatusCode()); when(mockResponse.getEntity()).thenReturn(getBinaryData()); ResourceResponse response = verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE, null); // verify that we got the entire resource Assert.assertEquals(5, response.getResource().getByteArray().length); } @Test public void testRetrievingPartialContent() throws URISyntaxException, IOException, ResourceNotFoundException { URI uri = new URI(HTTP_SCHEME_PLUS_SEP + HOST + TEST_PATH + BAD_FILE_NAME); Response mockResponse = mock(Response.class); when(mockWebClient.get()).thenReturn(mockResponse); MultivaluedMap<String, Object> map = new MultivaluedHashMap<>(); map.put(HttpHeaders.CONTENT_DISPOSITION, Arrays.<Object>asList("inline; filename=\"" + JPEG_FILE_NAME_1 + "\"")); when(mockResponse.getHeaders()).thenReturn(map); when(mockResponse.getStatus()).thenReturn(Response.Status.OK.getStatusCode()); when(mockResponse.getEntity()).thenReturn(getBinaryData()); String bytesToSkip = "2"; ResourceResponse response = verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE, bytesToSkip); // verify that we got the entire resource Assert.assertEquals(3, response.getResource().getByteArray().length); } @Test public void testUnquotedNameInContentDisposition() throws URISyntaxException, IOException, ResourceNotFoundException { URI uri = new URI(HTTP_SCHEME_PLUS_SEP + HOST + TEST_PATH + BAD_FILE_NAME); Response mockResponse = mock(Response.class); when(mockWebClient.get()).thenReturn(mockResponse); MultivaluedMap<String, Object> map = new MultivaluedHashMap<>(); map.put(HttpHeaders.CONTENT_DISPOSITION, Arrays.<Object>asList("inline; filename=" + JPEG_FILE_NAME_1)); when(mockResponse.getHeaders()).thenReturn(map); when(mockResponse.getStatus()).thenReturn(Response.Status.OK.getStatusCode()); when(mockResponse.getEntity()).thenReturn(getBinaryData()); verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE, null); } @Test public void testUnquotedNameEndingSemicolonInContentDisposition() throws URISyntaxException, IOException, ResourceNotFoundException { URI uri = new URI(HTTP_SCHEME_PLUS_SEP + HOST + TEST_PATH + BAD_FILE_NAME); Response mockResponse = mock(Response.class); when(mockWebClient.get()).thenReturn(mockResponse); MultivaluedMap<String, Object> map = new MultivaluedHashMap<>(); map.put(HttpHeaders.CONTENT_DISPOSITION, Arrays.<Object>asList("inline;filename=" + JPEG_FILE_NAME_1 + ";")); when(mockResponse.getHeaders()).thenReturn(map); when(mockResponse.getStatus()).thenReturn(Response.Status.OK.getStatusCode()); when(mockResponse.getEntity()).thenReturn(getBinaryData()); verifyFileFromURLResourceReader(uri, JPEG_FILE_NAME_1, JPEG_MIME_TYPE, null); } @Test public void testURLResourceReaderQualifierSet() { URLResourceReader resourceReader = new URLResourceReader(mimeTypeMapper); Set<String> qualifiers = resourceReader.getSupportedSchemes(); assert (qualifiers != null); assert (qualifiers.contains(HTTP_SCHEME)); assert (qualifiers.size() == 3); } private void verifyFile(String filePath, String filename, String expectedMimeType) { URLResourceReader resourceReader = new URLResourceReader(mimeTypeMapper); HashMap<String, Serializable> arguments = new HashMap<String, Serializable>(); try { LOGGER.info("Getting resource: " + filePath); // Test using the URL ResourceReader File file = new File(filePath); URI uri = file.toURI(); LOGGER.info("URI: " + uri.toString()); ResourceResponse resourceResponse = resourceReader.retrieveResource(uri, arguments); Resource resource = resourceResponse.getResource(); assert (resource != null); LOGGER.info("MimeType: " + resource.getMimeType()); LOGGER.info("Got resource: " + resource.getName()); String name = resource.getName(); assertNotNull(name); assertThat(name, is(filename)); assertThat(resource.getMimeType().toString(), containsString(expectedMimeType)); } catch (IOException e) { LOGGER.info("Caught unexpected IOException"); fail(); } catch (ResourceNotFoundException e) { LOGGER.info("Caught unexpected ResourceNotFoundException", e); fail(); } } private void verifyFileFromURLResourceReader(URI uri, String filename, String expectedMimeType) throws URISyntaxException, IOException, ResourceNotFoundException { Response mockResponse = mock(Response.class); when(mockWebClient.get()).thenReturn(mockResponse); MultivaluedMap<String, Object> map = new MultivaluedHashMap<>(); map.put(HttpHeaders.CONTENT_DISPOSITION, Arrays.<Object>asList("inline; filename=\"" + filename + "\"")); when(mockResponse.getHeaders()).thenReturn(map); when(mockResponse.getStatus()).thenReturn(Response.Status.OK.getStatusCode()); when(mockResponse.getEntity()).thenReturn(getBinaryData()); verifyFileFromURLResourceReader(uri, filename, expectedMimeType, null); } // Create arguments, adding bytesToSkip if present, and call doVerification private ResourceResponse verifyFileFromURLResourceReader(URI uri, String filename, String expectedMimeType, String bytesToSkip) throws URISyntaxException, IOException, ResourceNotFoundException { Map<String, Serializable> arguments = new HashMap<String, Serializable>(); if (bytesToSkip != null) { arguments.put(BYTES_TO_SKIP, bytesToSkip); } return doVerification(uri, filename, expectedMimeType, arguments); } private ResourceResponse doVerification(URI uri, String filename, String expectedMimeType, Map<String, Serializable> arguments) throws URISyntaxException, IOException, ResourceNotFoundException { URLResourceReader resourceReader = new TestURLResourceReader(mimeTypeMapper); // Test using the URL ResourceReader LOGGER.info("URI: " + uri.toString()); ResourceResponse resourceResponse = resourceReader.retrieveResource(uri, arguments); Resource resource = resourceResponse.getResource(); assert (resource != null); LOGGER.info("MimeType: " + resource.getMimeType()); LOGGER.info("Got resource: " + resource.getName()); String name = resource.getName(); assertNotNull(name); assertThat(name, is(filename)); assertTrue(resource.getMimeType().toString().contains(expectedMimeType)); return resourceResponse; } private class TestURLResourceReader extends URLResourceReader { public TestURLResourceReader(MimeTypeMapper mimeTypeMapper) { super(mimeTypeMapper); } @Override protected WebClient getWebClient(String uri) { return mockWebClient; } } }