it.tidalwave.northernwind.frontend.media.impl.DefaultMetadataCacheTest.java Source code

Java tutorial

Introduction

Here is the source code for it.tidalwave.northernwind.frontend.media.impl.DefaultMetadataCacheTest.java

Source

/*
 * #%L
 * *********************************************************************************************************************
 *
 * NorthernWind - lightweight CMS
 * http://northernwind.tidalwave.it - git clone https://bitbucket.org/tidalwave/northernwind-src.git
 * %%
 * Copyright (C) 2011 - 2016 Tidalwave s.a.s. (http://tidalwave.it)
 * %%
 * *********************************************************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
 * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations under the License.
 *
 * *********************************************************************************************************************
 *
 * $Id$
 *
 * *********************************************************************************************************************
 * #L%
 */
package it.tidalwave.northernwind.frontend.media.impl;

import javax.annotation.Nonnull;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import org.imajine.image.EditableImage;
import org.imajine.image.metadata.EXIF;
import org.imajine.image.metadata.IPTC;
import org.imajine.image.metadata.TIFF;
import org.imajine.image.metadata.XMP;
import org.springframework.context.ApplicationContext;
import it.tidalwave.util.Id;
import it.tidalwave.northernwind.core.model.ResourceFile;
import it.tidalwave.northernwind.core.model.ResourceProperties;
import it.tidalwave.northernwind.frontend.media.impl.DefaultMetadataCache.ExpirableMetadata;
import lombok.extern.slf4j.Slf4j;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import it.tidalwave.northernwind.util.test.TestHelper;
import static org.mockito.Mockito.*;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.CoreMatchers.*;

/***********************************************************************************************************************
 *
 * @author  Fabrizio Giudici
 * @version $Id$
 *
 **********************************************************************************************************************/
@Slf4j
public class DefaultMetadataCacheTest {
    private final TestHelper helper = new TestHelper(this);

    private ApplicationContext context;

    private DefaultMetadataCache underTest;

    private MetadataLoader metadataLoader;

    private EditableImage image;

    private TIFF tiff;

    private EXIF exif;

    private IPTC iptc;

    private XMP xmp;

    private Id mediaId;

    private ResourceProperties siteNodeProperties;

    private ResourceFile mediaFile;

    private ZonedDateTime initialTime;

    private Clock mockClock;

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @BeforeMethod
    public void setup() throws Exception {
        context = helper.createSpringContext();
        underTest = context.getBean(DefaultMetadataCache.class);
        underTest.setClock(() -> mockClock);
        metadataLoader = context.getBean(MetadataLoader.class);
        mediaFile = mock(ResourceFile.class);
        tiff = new TIFF();
        exif = new EXIF();
        iptc = new IPTC();
        xmp = new XMP();
        image = new ImageTestBuilder().withTiff(tiff).withExif(exif).withIptc(iptc).withXmp(xmp).build();
        siteNodeProperties = mock(ResourceProperties.class);
        mediaId = new Id("mediaId");

        when(metadataLoader.findMediaResourceFile(same(siteNodeProperties), eq(mediaId))).thenReturn(mediaFile);

        // Don't use 'thenReturn(new DefaultMetadata(image))' as a new instance must be created each time
        when(metadataLoader.loadMetadata(same(mediaFile))).thenAnswer(new Answer<DefaultMetadata>() {
            @Override
            public DefaultMetadata answer(final @Nonnull InvocationOnMock invocation) {
                return new DefaultMetadata("media.jpg", image);
            }
        });

        assertThat(underTest.getMedatataExpirationTime(),
                is(DefaultMetadataCache.DEFAULT_METADATA_EXPIRATION_TIME));
        initialTime = Instant.ofEpochMilli(1369080000000L).atZone(ZoneId.of("GMT"));
        setTime(initialTime);
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Test
    public void must_correctly_load_medatada_when_not_in_cache() throws Exception {
        // when
        final Metadata metadata = underTest.findMetadataById(mediaId, siteNodeProperties);
        // then
        final ZonedDateTime expectedExpirationTime = initialTime.plusSeconds(underTest.getMedatataExpirationTime());
        // FIXME: the validity of loaded data must be moved to the loader test
        assertThat(metadata.getDirectory(TIFF.class), sameInstance(tiff));
        assertThat(metadata.getDirectory(EXIF.class), sameInstance(exif));
        assertThat(metadata.getDirectory(IPTC.class), sameInstance(iptc));
        assertThat(metadata.getDirectory(XMP.class), sameInstance(xmp));

        final ExpirableMetadata expirableMetadata = underTest.metadataMapById.get(mediaId);
        assertThat(expirableMetadata, is(notNullValue()));
        assertThat(expirableMetadata.getMetadata(), sameInstance(metadata));
        assertThat(expirableMetadata.getCreationTime(), is(initialTime));
        assertThat(expirableMetadata.getExpirationTime(), is(expectedExpirationTime));

        verify(metadataLoader, times(1)).loadMetadata(eq(mediaFile));
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Test
    public void must_cache_the_same_instance_within_expiration_time_without_checking_for_file_modification()
            throws Exception {
        // given
        final Metadata metadata = underTest.findMetadataById(mediaId, siteNodeProperties);
        final ZonedDateTime expectedExpirationTime = initialTime.plusSeconds(underTest.getMedatataExpirationTime());

        for (ZonedDateTime now = initialTime; now.isBefore(
                expectedExpirationTime); now = now.plusSeconds(underTest.getMedatataExpirationTime() / 100)) {
            setTime(now);
            // when
            final Metadata metadata2 = underTest.findMetadataById(mediaId, siteNodeProperties);
            // then
            assertThat(metadata2, is(sameInstance(metadata)));

            final ExpirableMetadata expirableMetadata = underTest.metadataMapById.get(mediaId);
            assertThat(expirableMetadata, is(notNullValue()));
            assertThat(expirableMetadata.getMetadata(), sameInstance(metadata2));
            assertThat(expirableMetadata.getCreationTime(), is(initialTime));
            assertThat(expirableMetadata.getExpirationTime(), is(expectedExpirationTime));
            log.info(">>>> next expiration time: {}", expirableMetadata.getExpirationTime());
        }

        verify(metadataLoader, times(1)).loadMetadata(eq(mediaFile));
        verify(mediaFile, times(0)).getLatestModificationTime();
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Test
    public void must_check_file_modification_after_expiration_time_and_still_keep_in_cache_when_no_modifications()
            throws Exception {
        // given
        ZonedDateTime nextExpectedExpirationTime = initialTime.plusSeconds(underTest.getMedatataExpirationTime());
        when(mediaFile.getLatestModificationTime()).thenReturn(initialTime.minusNanos(1));

        final Metadata metadata = underTest.findMetadataById(mediaId, siteNodeProperties);

        for (int count = 1; count <= 10; count++) {
            final ZonedDateTime now = nextExpectedExpirationTime.plusNanos(1);
            setTime(now);
            nextExpectedExpirationTime = now.plusSeconds(underTest.getMedatataExpirationTime());
            // when
            final Metadata metadata2 = underTest.findMetadataById(mediaId, siteNodeProperties);
            // then
            assertThat(metadata2, is(sameInstance(metadata)));

            final ExpirableMetadata expirableMetadata = underTest.metadataMapById.get(mediaId);
            assertThat(expirableMetadata, is(notNullValue()));
            assertThat(expirableMetadata.getMetadata(), sameInstance(metadata2));
            assertThat(expirableMetadata.getCreationTime(), is(initialTime));
            assertThat(expirableMetadata.getExpirationTime(), is(nextExpectedExpirationTime));
            log.info(">>>> next expiration time: {}", expirableMetadata.getExpirationTime());

            verify(metadataLoader, times(1)).loadMetadata(eq(mediaFile));
            verify(mediaFile, times(count)).getLatestModificationTime();
        }
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    @Test
    public void must_reload_metadata_after_expiration_time_when_file_has_been_changed() throws Exception {
        // given
        ZonedDateTime nextExpectedExpirationTime = initialTime.plusSeconds(underTest.getMedatataExpirationTime());
        final Metadata metadata = underTest.findMetadataById(mediaId, siteNodeProperties);

        for (int count = 1; count < 10; count++) {
            final ZonedDateTime now = nextExpectedExpirationTime.plusNanos(1);
            setTime(now);
            when(mediaFile.getLatestModificationTime()).thenReturn(now.plusNanos(1));
            nextExpectedExpirationTime = now.plusSeconds(underTest.getMedatataExpirationTime());
            // when
            final Metadata metadata2 = underTest.findMetadataById(mediaId, siteNodeProperties);
            // then
            assertThat(metadata2, is(not(sameInstance(metadata))));

            final ExpirableMetadata expirableMetadata = underTest.metadataMapById.get(mediaId);
            assertThat(expirableMetadata, is(notNullValue()));
            assertThat(expirableMetadata.getMetadata(), sameInstance(metadata2));
            assertThat(expirableMetadata.getCreationTime(), is(now));
            assertThat(expirableMetadata.getExpirationTime(), is(nextExpectedExpirationTime));
            log.info(">>>> next expiration time: {}", expirableMetadata.getExpirationTime());

            verify(metadataLoader, times(count + 1)).loadMetadata(eq(mediaFile));
            verify(mediaFile, times(count)).getLatestModificationTime();
        }
    }

    /*******************************************************************************************************************
     *
     ******************************************************************************************************************/
    private void setTime(final @Nonnull ZonedDateTime dateTime) {
        mockClock = Clock.fixed(dateTime.toInstant(), dateTime.getZone());
        log.info("==== Time set to           {}", ZonedDateTime.now(mockClock));
    }
}