/* * Copyright 2010-2013 the original author or authors. * * 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. */ package org.springframework.data.gemfire.snapshot; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertThat; import java.io.File; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import javax.annotation.Resource; import org.apache.geode.cache.Region; import org.apache.geode.cache.snapshot.SnapshotFilter; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; import org.springframework.data.gemfire.repository.sample.Person; import org.springframework.data.gemfire.snapshot.event.ExportSnapshotApplicationEvent; import org.springframework.data.gemfire.snapshot.event.ImportSnapshotApplicationEvent; import org.springframework.data.gemfire.snapshot.event.SnapshotApplicationEvent; import org.springframework.data.gemfire.test.support.FileSystemUtils; import org.springframework.data.gemfire.test.support.ThreadUtils; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.util.Assert; import org.springframework.util.StringUtils; /** * The SnapshotApplicationEventTriggeredImportsExportsIntegrationTest class is a test suite of test cases testing * the effects of the SnapshotServiceFactoryBean using Spring ApplicationEvents to trigger imports and exports * of GemFire Cache Region data. * * @author John Blum * @see org.junit.Test * @see org.junit.runner.RunWith * @see org.springframework.test.context.ContextConfiguration * @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner * @see org.springframework.data.gemfire.snapshot.event.ExportSnapshotApplicationEvent * @see org.springframework.data.gemfire.snapshot.event.ImportSnapshotApplicationEvent * @see org.springframework.data.gemfire.snapshot.event.SnapshotApplicationEvent * @see org.springframework.data.gemfire.snapshot.SnapshotServiceFactoryBean * @see org.springframework.data.gemfire.repository.sample.Person * @see org.apache.geode.cache.Region * @since 1.7.0 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @SuppressWarnings("unused") public class SnapshotApplicationEventTriggeredImportsExportsIntegrationTest { protected static final AtomicLong ID_SEQUENCE = new AtomicLong(0l); protected static File snapshotsDirectory; @Autowired private ApplicationEventPublisher eventPublisher; @Resource(name = "Doe") private Region<Long, Person> doe; @Resource(name = "EveryoneElse") private Region<Long, Person> everyoneElse; @Resource(name = "Handy") private Region<Long, Person> handy; @Resource(name = "People") private Region<Long, Person> people; @BeforeClass public static void setupBeforeClass() throws Exception { snapshotsDirectory = new File(new File(FileSystemUtils.WORKING_DIRECTORY, "gemfire"), "snapshots"); assertThat(snapshotsDirectory.isDirectory() || snapshotsDirectory.mkdirs(), is(true)); File peopleSnapshotFile = new File(snapshotsDirectory, "people.snapshot"); File nonHandyNonDoeSnapshotFile = new File(snapshotsDirectory, "nonHandyNonDoePeople.snapshot"); assertThat(peopleSnapshotFile.isFile() || peopleSnapshotFile.createNewFile(), is(true)); assertThat(nonHandyNonDoeSnapshotFile.isFile() || nonHandyNonDoeSnapshotFile.createNewFile(), is(true)); } @AfterClass public static void tearDownAfterClass() { FileSystemUtils.deleteRecursive(snapshotsDirectory.getParentFile()); } protected void assertPeople(Region<Long, Person> targetRegion, Person... people) { assertThat(targetRegion.size(), is(equalTo(people.length))); for (Person person : people) { assertPerson(person, targetRegion.get(person.getId())); } } protected void assertPerson(Person expectedPerson, Person actualPerson) { assertThat(String.format("Expected (%1$s); but was (%2$s)", expectedPerson, actualPerson), actualPerson, is(notNullValue())); assertThat(actualPerson.getId(), is(equalTo(expectedPerson.getId()))); assertThat(actualPerson.getFirstname(), is(equalTo(expectedPerson.getFirstname()))); assertThat(actualPerson.getLastname(), is(equalTo(expectedPerson.getLastname()))); } protected Person createPerson(String firstName, String lastName) { return new Person(ID_SEQUENCE.incrementAndGet(), firstName, lastName); } protected Person put(Region<Long, Person> targetRegion, Person person) { targetRegion.putIfAbsent(person.getId(), person); return person; } protected void wait(final int seconds, final int expectedDoeSize, final int expectedEveryoneSize, final int expectedHandySize) { ThreadUtils.timedWait(TimeUnit.SECONDS.toMillis(seconds), 500, new ThreadUtils.WaitCondition() { @Override public boolean waiting() { return (doe.size() < expectedDoeSize && everyoneElse.size() < expectedEveryoneSize && handy.size() < expectedHandySize); } }); } @Test @SuppressWarnings("unchecked") public void exportsTriggeringImportsOnSnapshotApplicationEvents() { Person jonDoe = put(people, createPerson("Jon", "Doe")); Person janeDoe = put(people, createPerson("Jane", "Doe")); Person jackBlack = put(people, createPerson("Jack", "Black")); Person jackHandy = put(people, createPerson("Jack", "Handy")); Person joeDirt = put(people, createPerson("Joe", "Dirt")); SnapshotApplicationEvent event = new ExportSnapshotApplicationEvent<Long, Person>(this, people.getFullPath()); eventPublisher.publishEvent(event); wait(5, 2, 2, 1); assertPeople(doe, jonDoe, janeDoe); assertPeople(everyoneElse, jackBlack, joeDirt); assertPeople(handy, jackHandy); Person cookieDoe = put(people, createPerson("Cookie", "Doe")); Person pieDoe = put(people, createPerson("Pie", "Doe")); Person sourDoe = put(people, createPerson("Sour", "Doe")); Person randyHandy = put(people, createPerson("Randy", "Handy")); Person sandyHandy = put(people, createPerson("Sandy", "Handy")); Person jackHill = put(people, createPerson("Jack", "Hill")); Person jillHill = put(people, createPerson("Jill", "Hill")); eventPublisher.publishEvent(event); wait(10, 5, 4, 3); assertPeople(doe, jonDoe, janeDoe, cookieDoe, pieDoe, sourDoe); assertPeople(everyoneElse, jackBlack, joeDirt, jackHill, jillHill); assertPeople(handy, jackHandy, randyHandy, sandyHandy); Person bobDoe = put(people, createPerson("Bob", "Doe")); Person mandyHandy = put(people, createPerson("Mandy", "Handy")); Person imaPigg = put(people, createPerson("Ima", "Pigg")); Person benDover = put(people, createPerson("Ben", "Dover")); eventPublisher.publishEvent(event); wait(15, 6, 6, 4); assertPeople(doe, jonDoe, janeDoe, cookieDoe, pieDoe, sourDoe, bobDoe); assertPeople(everyoneElse, jackBlack, joeDirt, jackHill, jillHill, imaPigg, benDover); assertPeople(handy, jackHandy, randyHandy, sandyHandy, mandyHandy); } protected static class LastNameSnapshotFilter implements SnapshotFilter<Long, Person> { private final String lastName; public LastNameSnapshotFilter(String lastName) { Assert.hasText(lastName, "'lastName' must be specified"); this.lastName = lastName; } protected String getLastName() { Assert.state(StringUtils.hasText(lastName), "'lastName' was not properly initialized"); return lastName; } @Override public boolean accept(Map.Entry<Long, Person> entry) { return accept(entry.getValue()); } public boolean accept(Person person) { return getLastName().equalsIgnoreCase(person.getLastname()); } } protected static class NotLastNameSnapshotFilter extends LastNameSnapshotFilter { public NotLastNameSnapshotFilter(String lastName) { super(lastName); } @Override public boolean accept(final Map.Entry<Long, Person> entry) { return !super.accept(entry); } } protected static class SnapshotImportsMonitor { @Autowired private ApplicationEventPublisher eventPublisher; private static final Map<File, Long> snapshotFileLastModifiedMap = new ConcurrentHashMap<File, Long>(2); @Scheduled(fixedDelay = 1000) @SuppressWarnings("unchecked") public void processSnapshots() { boolean triggerEvent = false; for (File snapshotFile : nullSafeArray(snapshotsDirectory.listFiles(FileSystemUtils.FileOnlyFilter.INSTANCE))) { triggerEvent |= isUnprocessedSnapshotFile(snapshotFile); } if (triggerEvent) { eventPublisher.publishEvent(new ImportSnapshotApplicationEvent<Long, Person>(this)); } } protected File[] nullSafeArray(File... files) { return (files != null ? files : new File[0]); } protected boolean isUnprocessedSnapshotFile(File snapshotFile) { Long lastModified = snapshotFile.lastModified(); Long previousLastModified = snapshotFileLastModifiedMap.get(snapshotFile); previousLastModified = (previousLastModified != null ? previousLastModified : lastModified); snapshotFileLastModifiedMap.put(snapshotFile, lastModified); return !previousLastModified.equals(lastModified); } } }