de.hybris.platform.media.storage.impl.DefaultLocalMediaFileCacheServiceIntegrationTest.java Source code

Java tutorial

Introduction

Here is the source code for de.hybris.platform.media.storage.impl.DefaultLocalMediaFileCacheServiceIntegrationTest.java

Source

/*
 * [y] hybris Platform
 *
 * Copyright (c) 2000-2013 hybris AG
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of hybris
 * ("Confidential Information"). You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the
 * license agreement you entered into with hybris.
 * 
 *  
 */
package de.hybris.platform.media.storage.impl;

import static org.fest.assertions.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import de.hybris.bootstrap.annotations.IntegrationTest;
import de.hybris.platform.core.Registry;
import de.hybris.platform.jalo.media.Media;
import de.hybris.platform.jalo.media.MediaFolder;
import de.hybris.platform.media.storage.LocalMediaFileCacheService;
import de.hybris.platform.media.storage.LocalMediaFileCacheService.StreamGetter;
import de.hybris.platform.media.storage.MediaStorageConfigService.MediaFolderConfig;
import de.hybris.platform.media.storage.impl.DefaultMediaStorageConfigService.DefaultSettingKeys;
import de.hybris.platform.regioncache.CacheController;
import de.hybris.platform.regioncache.key.CacheKey;
import de.hybris.platform.regioncache.region.CacheRegion;
import de.hybris.platform.servicelayer.ServicelayerBaseTest;
import de.hybris.platform.util.MediaUtil;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.annotation.Nullable;
import javax.annotation.Resource;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;
import org.fest.assertions.Assertions;
import org.fest.assertions.GenericAssert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.CountingInputStream;

@IntegrationTest
public class DefaultLocalMediaFileCacheServiceIntegrationTest extends ServicelayerBaseTest {
    private static final Logger LOG = Logger.getLogger(DefaultLocalMediaFileCacheServiceIntegrationTest.class);
    private static final String TENANT_ID = Registry.getCurrentTenantNoFallback().getTenantID();
    private static final String CACHE_FOLDER = "cache";
    private static final String FOLDER_QUALIFIER = "fooBar";
    private static final String FOLDER_PATH = FOLDER_QUALIFIER;
    private static final String MEDIA_LOCATION_IN_STORAGE = FOLDER_PATH + "/h94/h05/12345";

    @Resource(name = "defaultCacheController")
    private CacheController cacheController;
    @Resource(name = "localMediaFileCacheService")
    private LocalMediaFileCacheService localMediaFileCacheService;
    @Mock
    private MediaFolderConfig folderConfig;
    @Mock
    private Media media;
    @Mock
    private MediaFolder mediaFolder;
    @Mock
    private InputStream inputStream;
    @Mock
    private StreamGetter streamGetter;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        given(media.getFolder()).willReturn(mediaFolder);
        given(media.getDataPK()).willReturn(Long.valueOf(12345));
        given(folderConfig.getFolderQualifier()).willReturn(FOLDER_QUALIFIER);
        given(Integer.valueOf(folderConfig.getHashingDepth())).willReturn(Integer.valueOf(0));
        given(mediaFolder.getPath()).willReturn(FOLDER_QUALIFIER);
        given(Integer.valueOf(inputStream.read(any(byte[].class)))).willReturn(Integer.valueOf(1),
                Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1), Integer.valueOf(1),
                Integer.valueOf(1), Integer.valueOf(-1));
        given(streamGetter.getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE)).willReturn(inputStream);
        given(folderConfig.getParameter(DefaultSettingKeys.LOCAL_CACHE_ROOT_FOLDER_KEY.getKey(), String.class,
                CACHE_FOLDER)).willReturn(CACHE_FOLDER);
    }

    @After
    public void cleanUp() {
        try {
            final CacheRegion mediaCacheRegion = getMediaCacheRegion();
            final Collection<CacheKey> allKeys = mediaCacheRegion.getAllKeys();
            for (final CacheKey cacheKey : allKeys) {
                if (TENANT_ID.equalsIgnoreCase(cacheKey.getTenantId())) {
                    mediaCacheRegion.remove(cacheKey, false);
                }
            }

            FileUtils.deleteDirectory(new File(MediaUtil.getLocalStorageDataDir() + "/cache"));
        } catch (final IOException e) {
            LOG.error("Cannot clean out testing cache directory");
        }
    }

    private CacheRegion getMediaCacheRegion() {
        final Optional<CacheRegion> mediaCacheRegion = Iterables.tryFind(cacheController.getRegions(),
                new Predicate<CacheRegion>() {

                    @Override
                    public boolean apply(@Nullable final CacheRegion input) {
                        return (input instanceof MediaCacheRegion);
                    }
                });
        assertThat(mediaCacheRegion.isPresent()).isTrue();
        return mediaCacheRegion.get();
    }

    @Test
    public void shouldCacheFileOnlyOnceWhenRequestingMediaWithTheSameLocationMultipleTimes() throws IOException {
        // given
        final StreamContainer container = new StreamContainer();

        try {
            // when
            runThreadsWithLatch(500, new CacheTestExecutor() {

                @Override
                public void execute() {
                    final InputStream stream = localMediaFileCacheService.storeOrGetAsStream(folderConfig,
                            MEDIA_LOCATION_IN_STORAGE, streamGetter);
                    if (stream == null) {
                        fail("Got null stream");
                    } else {
                        container.addStream(stream);
                    }
                }
            });
        } finally {
            for (final InputStream stream : container.getStreams()) {
                closeStream(stream);
            }
        }

        // then
        assertThat(getMediaCacheRegion().get(new DefaultLocalMediaFileCacheService.MediaCacheKey(TENANT_ID,
                CACHE_FOLDER, MEDIA_LOCATION_IN_STORAGE))).isNotNull();
        CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasOneFileInCache();
    }

    @Test
    public void shouldNotRemoveFilesAfterInvalidationUntilAllClientsCloseTheStreams() throws IOException {
        // given
        final StreamContainer container = new StreamContainer();

        try {
            // given
            runThreadsWithLatch(500, new CacheTestExecutor() {

                @Override
                public void execute() {
                    final InputStream stream = localMediaFileCacheService.storeOrGetAsStream(folderConfig,
                            MEDIA_LOCATION_IN_STORAGE, streamGetter);
                    if (stream == null) {
                        fail("Got null stream");
                    } else {
                        container.addStream(stream);
                    }
                }
            });
            localMediaFileCacheService.removeFromCache(folderConfig, MEDIA_LOCATION_IN_STORAGE);

            // then 
            assertThat(container.getStreams()).hasSize(500);
            assertThat(getMediaCacheRegion().get(new DefaultLocalMediaFileCacheService.MediaCacheKey(TENANT_ID,
                    CACHE_FOLDER, MEDIA_LOCATION_IN_STORAGE))).isNull();
            CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasOneFileInCache();
        } finally {
            for (final InputStream stream : container.getStreams()) {
                closeStream(stream);
            }
        }

        // now file from disk cache should be removed (after closing all streams)
        CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasNoFileInCache();
    }

    private class StreamContainer {
        private final List<InputStream> streams = new ArrayList<InputStream>();

        public synchronized void addStream(final InputStream stream) {
            streams.add(stream);
        }

        public List<InputStream> getStreams() {
            return ImmutableList.<InputStream>copyOf(streams);
        }
    }

    @Test
    public void shouldStoreStreamInLocalCacheIfItDoesntExistsAndReturnItAsFileInputStream() throws IOException {
        // given
        InputStream stream = null;

        try {
            // when
            stream = localMediaFileCacheService.storeOrGetAsStream(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                    streamGetter);

            // then
            verify(streamGetter, times(1)).getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE);
            assertThat(stream).isNotNull();
            assertThat(getStreamNumBytesForStream(stream)).isEqualTo(
                    getStreamNumBytesForStream(streamGetter.getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE)));
        } finally {
            closeStream(stream);
        }
    }

    @Test
    public void shouldReturnFileInputStreamOfAlreadyCachedStream() throws IOException {
        // given (store initially)
        InputStream stream1 = null;
        InputStream stream2 = null;
        try {
            stream1 = localMediaFileCacheService.storeOrGetAsStream(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                    streamGetter);

            // when
            stream2 = localMediaFileCacheService.storeOrGetAsStream(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                    streamGetter);

            // then
            verify(streamGetter, times(1)).getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE);
            assertThat(stream2).isNotNull();
            assertThat(getStreamNumBytesForStream(stream2)).isEqualTo(
                    getStreamNumBytesForStream(streamGetter.getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE)));
        } finally {
            closeStream(stream1);
            closeStream(stream2);
        }
    }

    @Test
    public void shouldStoreStreamInLocalCacheIfItDoesntExistsAndReturnItAsRegularFile() {
        // when
        final File file = localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                streamGetter);

        // then
        verify(streamGetter, times(1)).getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE);
        assertThat(file).isNotNull();
    }

    @Test
    public void shouldReturnRegularFileOfAlreadyCachedStream() {
        // given (store initially)
        localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE, streamGetter);

        // when
        final File file = localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                streamGetter);

        // then
        verify(streamGetter, times(1)).getStream(folderConfig, MEDIA_LOCATION_IN_STORAGE);
        assertThat(file).isNotNull();
    }

    @Test
    public void shouldRemoveCachedFileFromDiskWhenDirectDeleteWasCalled() throws IOException {
        // given (store initially) 
        InputStream stream = null;
        try {
            stream = localMediaFileCacheService.storeOrGetAsStream(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                    streamGetter);
        } catch (final IllegalStateException e) {
            fail("Stream from cached should be backed by existing file in disk cache");
        } finally {
            closeStream(stream);
        }

        // when
        localMediaFileCacheService.removeFromCache(folderConfig, MEDIA_LOCATION_IN_STORAGE);

        // then
        CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasNoFileInCache();
    }

    @Test
    public void shouldWriteEvictionMarkerFileForCachedFileTakenAsFileByTheClientInsteadRemovingFileOnEviction()
            throws Exception {
        // given
        localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE, streamGetter);

        // when
        localMediaFileCacheService.removeFromCache(folderConfig, MEDIA_LOCATION_IN_STORAGE);

        // then
        CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasOneFileInCache();
        CachedFileAssert.assertThat(MEDIA_LOCATION_IN_STORAGE).hasEvictionMarker();
    }

    @Test
    public void shouldEvictExistingCacheUnitWhenItsUnderlyingFileWasDeletedAccidentally() throws Exception {
        // given
        final File file = localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                streamGetter);

        // when (Don't do this in real life)
        file.delete();

        // then
        final File newFile = localMediaFileCacheService.storeOrGetAsFile(folderConfig, MEDIA_LOCATION_IN_STORAGE,
                streamGetter);
        assertThat(file.getAbsolutePath()).isNotEqualTo(newFile.getAbsolutePath());
    }

    private static class CachedFileAssert extends GenericAssert<CachedFileAssert, String> {
        public CachedFileAssert(final String actual) {
            super(CachedFileAssert.class, actual);
        }

        public static CachedFileAssert assertThat(final String actual) {
            return new CachedFileAssert(actual);
        }

        public CachedFileAssert hasOneFileInCache() {
            final File[] files = findCachedFiles();
            Assertions.assertThat(files).hasSize(1);
            return this;
        }

        public CachedFileAssert hasNoFileInCache() {
            final File[] files = findCachedFiles();
            Assertions.assertThat(files).isEmpty();
            return this;
        }

        public CachedFileAssert hasEvictionMarker() {
            final File[] markers = findMarkersForCachedFile();
            Assertions.assertThat(markers).isNotEmpty();
            Assertions.assertThat(markers).hasSize(1);
            return this;
        }

        private File[] findCachedFiles() {
            final File dir = new File(MediaUtil.getLocalStorageDataDir(), "/cache/fooBar");
            final FileFilter fileFilter = new WildcardFileFilter(getMediaId(actual) + "*.bin");
            final File[] files = dir.listFiles(fileFilter);
            return files;
        }

        private File[] findMarkersForCachedFile() {
            final File dir = new File(MediaUtil.getLocalStorageDataDir(), "/cache/fooBar");
            final FileFilter fileFilter = new WildcardFileFilter(getMediaId(actual) + "*.bin.EVICTED");
            final File[] files = dir.listFiles(fileFilter);
            return files;
        }

        private String getMediaId(final String location) {
            return new String(Base64.encode(location.getBytes()));
        }
    }

    private long getStreamNumBytesForStream(final InputStream stream) throws IOException {
        CountingInputStream cis = null;
        try {
            cis = new CountingInputStream(stream);
            return cis.getCount();
        } finally {
            closeStream(cis);
        }
    }

    private void closeStream(final InputStream stream) throws IOException {
        if (stream != null) {
            IOUtils.closeQuietly(stream);
        }
    }

    private void runThreadsWithLatch(final int numThreads, final CacheTestExecutor executor) {
        try {
            final CountDownLatch latch = new CountDownLatch(numThreads);
            for (int i = 0; i < numThreads; i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            executor.execute();
                        } finally {
                            latch.countDown();
                        }
                    }

                }).start();

            }
            latch.await(10, TimeUnit.SECONDS);
        } catch (final InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private interface CacheTestExecutor {
        void execute();
    }
}