/* * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others. * * 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. * * Contributors: * <a href="mailto:tdelprat@nuxeo.com">Tiry</a> */ package org.nuxeo.transientstore.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.Serializable; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.IOUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.nuxeo.common.Environment; import org.nuxeo.ecm.core.api.Blob; import org.nuxeo.ecm.core.api.impl.blob.StringBlob; import org.nuxeo.ecm.core.event.EventContext; import org.nuxeo.ecm.core.event.EventService; import org.nuxeo.ecm.core.event.impl.EventContextImpl; import org.nuxeo.ecm.core.transientstore.AbstractTransientStore; import org.nuxeo.ecm.core.transientstore.SimpleTransientStore; import org.nuxeo.ecm.core.transientstore.TransientStorageComponent; import org.nuxeo.ecm.core.transientstore.TransientStorageGCTrigger; import org.nuxeo.ecm.core.transientstore.api.MaximumTransientSpaceExceeded; import org.nuxeo.ecm.core.transientstore.api.TransientStore; import org.nuxeo.ecm.core.transientstore.api.TransientStoreService; import org.nuxeo.runtime.api.Framework; import org.nuxeo.runtime.test.runner.Deploy; import org.nuxeo.runtime.test.runner.Features; import org.nuxeo.runtime.test.runner.FeaturesRunner; import org.nuxeo.runtime.test.runner.RuntimeHarness; import com.google.inject.Inject; @RunWith(FeaturesRunner.class) @Features(TransientStoreFeature.class) @Deploy("org.nuxeo.ecm.core.event") public class TransientStorageComplianceFixture { @Inject RuntimeHarness harness; @Test public void verifyServiceDeclared() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); assertNotNull(tss); TransientStore ts = tss.getStore("testStore"); assertNotNull(ts); TransientStore ts2 = tss.getStore("microStore"); assertNotNull(ts2); TransientStore ts3 = tss.getStore("miniStore"); assertNotNull(ts3); } protected void putEntry(TransientStore ts, String id) { ts.putParameter(id, "A", String.valueOf(1)); ts.putParameter(id, "B", "b"); String content = "FakeContent"; Blob blob = new StringBlob(content); blob.setFilename("fake.txt"); blob.setMimeType("text/plain"); blob.setDigest(DigestUtils.md5Hex(content)); ts.putBlobs(id, Collections.singletonList(blob)); } @Test public void verifyStorage() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("testStore"); long size = ((AbstractTransientStore) ts).getStorageSize(); assertEquals(0, size); putEntry(ts, "1"); // check FS File cacheDir = ((AbstractTransientStore) ts).getCachingDirectory("1"); assertTrue(cacheDir.exists()); File[] cacheEntries = cacheDir.listFiles(); assertTrue(cacheEntries.length > 0); // check that entry is stored assertTrue(ts.exists("1")); assertFalse(ts.isCompleted("1")); assertEquals(1, ts.keySet().size()); assertTrue(ts.keySet().contains("1")); assertEquals(11, ts.getSize("1")); assertEquals("1", ts.getParameter("1", "A")); assertEquals("b", ts.getParameter("1", "B")); List<Blob> blobs = ts.getBlobs("1"); assertEquals(1, blobs.size()); Blob blob = blobs.get(0); assertEquals("fake.txt", blob.getFilename()); assertEquals("text/plain", blob.getMimeType()); assertEquals(DigestUtils.md5Hex("FakeContent"), blob.getDigest()); assertEquals("FakeContent", IOUtils.toString(blob.getStream())); size = ((AbstractTransientStore) ts).getStorageSize(); assertEquals(11, size); // update the entry Blob otherBlob = new StringBlob("FakeContent2"); otherBlob.setFilename("fake2.txt"); otherBlob.setMimeType("text/plain"); blobs.add(otherBlob); ts.putBlobs("1", blobs); // check update assertTrue(ts.exists("1")); assertEquals(1, ts.keySet().size()); assertTrue(ts.keySet().contains("1")); assertEquals(23, ts.getSize("1")); blobs = ts.getBlobs("1"); assertEquals(2, blobs.size()); assertEquals("fake.txt", blobs.get(0).getFilename()); assertEquals("fake2.txt", blobs.get(1).getFilename()); size = ((AbstractTransientStore) ts).getStorageSize(); assertEquals(23, size); // move to deletable entries // check that still here ts.release("1"); assertTrue(ts.exists("1")); assertEquals(1, ts.keySet().size()); assertTrue(ts.keySet().contains("1")); // check Remove ts.remove("1"); assertFalse(ts.exists("1")); assertEquals(0, ts.keySet().size()); size = ((AbstractTransientStore) ts).getStorageSize(); assertEquals(0, size); } @Test public void verifyNullCases() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("testStore"); // Non existing entry assertFalse(ts.exists("fakeEntry")); assertNull(ts.getParameters("fakeEntry")); assertNull(ts.getParameter("fakeEntry", "fakeParameter")); assertNull(ts.getBlobs("fakeEntry")); assertEquals(-1, ts.getSize("fakeEntry")); assertFalse(ts.isCompleted("fakeEntry")); // Entry with parameters only ts.putParameter("testEntry", "param1", "value"); assertTrue(ts.exists("testEntry")); Map<String, Serializable> params = ts.getParameters("testEntry"); assertNotNull(params); assertEquals(1, params.size()); assertNotNull(ts.getParameter("testEntry", "param1")); assertNull(ts.getParameter("testEntry", "param2")); List<Blob> blobs = ts.getBlobs("testEntry"); assertNotNull(blobs); assertTrue(blobs.isEmpty()); // Entry with blobs only ts.putBlobs("otherEntry", Collections.singletonList(new StringBlob("joe"))); assertTrue(ts.exists("otherEntry")); params = ts.getParameters("otherEntry"); assertNotNull(params); assertTrue(params.isEmpty()); blobs = ts.getBlobs("otherEntry"); assertNotNull(blobs); assertEquals(1, blobs.size()); } @Test(expected = MaximumTransientSpaceExceeded.class) public void verifyMaxSizeException() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("microStore"); putEntry(ts, "A"); } @Test public void verifyDeleteAfterUse() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("miniStore"); putEntry(ts, "1"); // check that entry is stored assertTrue(ts.exists("1")); assertEquals("1", ts.getParameter("1", "A")); assertEquals("b", ts.getParameter("1", "B")); List<Blob> blobs = ts.getBlobs("1"); assertEquals(1, blobs.size()); Blob blob = blobs.get(0); assertEquals("fake.txt", blob.getFilename()); assertEquals("text/plain", blob.getMimeType()); assertEquals("FakeContent", IOUtils.toString(blob.getStream())); // move to deletable entries // check that deleted ts.release("1"); assertFalse(ts.exists("1")); } @Test public void verifyDeleteOnGC() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("testStore"); putEntry(ts, "X"); // check that entry is stored assertTrue(ts.exists("X")); // check FS File cacheDir = ((AbstractTransientStore) ts).getCachingDirectory("X"); assertTrue(cacheDir.exists()); File[] cacheEntries = cacheDir.listFiles(); assertTrue(cacheEntries.length > 0); // do GC for no reason ts.doGC(); // entry is still here assertTrue(ts.exists("X")); // file is still here cacheEntries = cacheDir.listFiles(); assertTrue(cacheEntries.length > 0); // no remove the entry ts.remove("X"); // do GC ts.doGC(); // entry is gone assertFalse(ts.exists("X")); // cache dir is gone assertFalse(cacheDir.exists()); } @Test public void verifyDeleteOnGCEvent() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("testStore"); putEntry(ts, "X"); // check that entry is stored assertTrue(ts.exists("X")); // check FS File cacheDir = ((AbstractTransientStore) ts).getCachingDirectory("X"); assertTrue(cacheDir.exists()); File[] cacheEntries = cacheDir.listFiles(); assertTrue(cacheEntries.length > 0); // now remove the entry ts.remove("X"); EventContext evtCtx = new EventContextImpl(); Framework.getService(EventService.class).fireEvent(TransientStorageGCTrigger.EVENT, evtCtx); Thread.sleep(100); Framework.getService(EventService.class).waitForAsyncCompletion(); // entry is gone assertFalse(ts.exists("X")); // cache dir is gone assertFalse(cacheDir.exists()); } @Test public void verifyDeleteAfterUseGC() throws Exception { TransientStoreService tss = Framework.getService(TransientStoreService.class); TransientStore ts = tss.getStore("testStore"); putEntry(ts, "XXX"); // check that entry is stored assertTrue(ts.exists("XXX")); if (ts instanceof SimpleTransientStore) { assertNotNull(((SimpleTransientStore) ts).getL1Cache().get("XXX")); } // move to deletable entries // check that still here ts.release("XXX"); assertTrue(ts.exists("XXX")); if (ts instanceof SimpleTransientStore) { assertNull(((SimpleTransientStore) ts).getL1Cache().get("XXX")); assertNotNull(((SimpleTransientStore) ts).getL2Cache().get("XXX")); } // do GC ts.doGC(); // check still here assertTrue(ts.exists("XXX")); // empty the L2 cache for the in-memory implementation or remove entry for the Redis one if (ts instanceof SimpleTransientStore) { ((SimpleTransientStore) ts).getL2Cache().invalidate("XXX"); } else { ts.remove("XXX"); } // do GC ts.doGC(); // check no longer there assertFalse(ts.exists("XXX")); } @Test public void verifyStorePathCanBeSpecified() throws Exception { Framework.getProperties().setProperty("nuxeo.data.dir", Environment.getDefault().getData().getAbsolutePath()); TransientStoreService tss = Framework.getService(TransientStoreService.class); // Verify default behavior (store cache dir is in ${nuxeo.data.dir}/transientstores/{name} AbstractTransientStore ts = (AbstractTransientStore) tss.getStore("microStore"); assertEquals(Framework.expandVars("${nuxeo.data.dir}/transientstores/microStore"), ts.getCacheDir().getAbsolutePath()); // Verify when a path is given harness.deployContrib("org.nuxeo.ecm.core.cache.test", "testpath-store.xml"); // Call to register the cache. ((TransientStorageComponent)tss).applicationStarted(null); ts = (SimpleTransientStore) tss.getStore("testPath"); assertEquals(Framework.expandVars("${nuxeo.data.dir}/test"), ts.getCacheDir().getAbsolutePath()); } }