From 2e5d5dca01661fd8d2d9ede4b2b53f837cc6ce0d Mon Sep 17 00:00:00 2001 From: Gennadiy Dubina Date: Mon, 16 Jan 2017 16:31:56 +0200 Subject: [PATCH 1/4] use off-heap cache --- .../audio/CachedRemoteStreamProvider.java | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java index 79f19cb64..38b38ff35 100644 --- a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java +++ b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java @@ -1,12 +1,5 @@ package org.restcomm.media.resource.player.audio; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; import org.ehcache.Cache; @@ -15,7 +8,14 @@ import org.ehcache.config.builders.CacheManagerBuilder; import org.ehcache.config.builders.ResourcePoolsBuilder; import org.ehcache.config.units.MemoryUnit; -import org.ehcache.sizeof.annotations.IgnoreSizeOf; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; /** * Created by achikin on 5/9/16. @@ -26,58 +26,72 @@ public class CachedRemoteStreamProvider implements RemoteStreamProvider { private CacheManager cacheManager; - private ByteStreamCache.ISizeChangedListener sizeChangedListener; + private ConcurrentHashMap inProgress = new ConcurrentHashMap<>(); public CachedRemoteStreamProvider(int size) { log.info("Create AudioCache with size: " + size + "Mb"); cacheManager = CacheManagerBuilder.newCacheManagerBuilder() .withCache("preConfigured", - CacheConfigurationBuilder.newCacheConfigurationBuilder(URL.class, ByteStreamCache.class, - ResourcePoolsBuilder.newResourcePoolsBuilder().heap(size, MemoryUnit.MB)) + CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, byte[].class, + ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(size, MemoryUnit.MB)) .build()) .build(true); - sizeChangedListener = new ByteStreamCache.ISizeChangedListener() { - @Override - public void onSizeChanged(final URL uri, final ByteStreamCache self) { - log.debug("onSizeChanged for " + uri); - getCache().put(uri, self); - } - }; } - private Cache getCache() { - return cacheManager.getCache("preConfigured", URL.class, ByteStreamCache.class); + private Cache getCache() { + return cacheManager.getCache("preConfigured", String.class, byte[].class); } public InputStream getStream(URL uri) throws IOException { - Cache cache = getCache(); + String key = uri.toString(); + Cache cache = getCache(); + + byte[] stream = cache.get(key); + if (stream == null) { + stream = download(cache, uri); + } + + return new ByteArrayInputStream(stream); + } - ByteStreamCache stream = cache.get(uri); + private byte[] download(Cache cache, final URL uri) throws IOException { + String key = uri.toString(); + ByteStreamDownloader stream = inProgress.get(key); if (stream == null) { - stream = new ByteStreamCache(); - ByteStreamCache exists = cache.putIfAbsent(uri, stream); - if (exists != null) { - stream = exists; + stream = new ByteStreamDownloader(); + ByteStreamDownloader prev = inProgress.putIfAbsent(key, stream); + if (prev == null) { + //check bytes in cache again too, maybe it's already added + byte[] bytes = cache.get(key); + if (bytes != null) { + return bytes; + } + } else { + stream = prev; } } - return new ByteArrayInputStream(stream.getBytes(uri, sizeChangedListener)); + try { + byte[] bytes = stream.download(uri); + cache.putIfAbsent(key, bytes); + return bytes; + } finally { + inProgress.remove(key); + } } - private static class ByteStreamCache { + private static class ByteStreamDownloader { - @IgnoreSizeOf private Lock lock = new ReentrantLock(); - private volatile byte[] bytes; + volatile byte[] bytes; - public byte[] getBytes(final URL uri, final ISizeChangedListener listener) throws IOException { + public byte[] download(final URL uri) throws IOException { if (bytes == null) { lock.lock(); try { //need to check twice if (bytes == null) { bytes = IOUtils.toByteArray(uri.openStream()); - listener.onSizeChanged(uri, this); } } finally { lock.unlock(); @@ -85,9 +99,5 @@ public byte[] getBytes(final URL uri, final ISizeChangedListener listener) throw } return bytes; } - - interface ISizeChangedListener { - void onSizeChanged(URL uri, ByteStreamCache self); - } } } From c777a43fb3111f2589771e1f0781ed74379fb72d Mon Sep 17 00:00:00 2001 From: Gennadiy Dubina Date: Mon, 27 Feb 2017 16:06:11 +0200 Subject: [PATCH 2/4] remove bytes from progress object --- .../audio/CachedRemoteStreamProvider.java | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java index 38b38ff35..11648a1d2 100644 --- a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java +++ b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java @@ -72,7 +72,14 @@ private byte[] download(Cache cache, final URL uri) throws IOExc } try { byte[] bytes = stream.download(uri); - cache.putIfAbsent(key, bytes); + if (bytes != null) { + cache.putIfAbsent(key, bytes); + } else { + bytes = cache.get(key); + } + if (bytes == null) { + throw new IOException("No data for " + uri); + } return bytes; } finally { inProgress.remove(key); @@ -83,21 +90,24 @@ private static class ByteStreamDownloader { private Lock lock = new ReentrantLock(); - volatile byte[] bytes; + volatile boolean downloaded; public byte[] download(final URL uri) throws IOException { - if (bytes == null) { - lock.lock(); - try { - //need to check twice - if (bytes == null) { - bytes = IOUtils.toByteArray(uri.openStream()); - } - } finally { - lock.unlock(); + if (downloaded) { + return null; + } + lock.lock(); + try { + //need to check twice + if (downloaded) { + return null; } + byte[] bytes = IOUtils.toByteArray(uri.openStream()); + downloaded = bytes != null; + return bytes; + } finally { + lock.unlock(); } - return bytes; } } } From 1542b6c4a767be25e5b689c4de843ba660c0cd5f Mon Sep 17 00:00:00 2001 From: Gennadiy Dubina Date: Wed, 15 Mar 2017 00:47:24 +0200 Subject: [PATCH 3/4] use direct ByteBuffer to store audio files --- network/pom.xml | 2 +- pom.xml | 8 +- resources/mediaplayer/pom.xml | 12 +- .../audio/CachedRemoteStreamProvider.java | 122 ++++++------------ .../player/audio/RemoteStreamProvider.java | 1 + .../player/audio/wav/WavTrackCacheTest.java | 18 ++- 6 files changed, 63 insertions(+), 100 deletions(-) diff --git a/network/pom.xml b/network/pom.xml index da1589e47..009c7b6a5 100644 --- a/network/pom.xml +++ b/network/pom.xml @@ -27,7 +27,7 @@ io.netty netty-all - 4.0.34.Final + 4.1.9.Final diff --git a/pom.xml b/pom.xml index e5d612608..153cc6081 100644 --- a/pom.xml +++ b/pom.xml @@ -67,7 +67,7 @@ core client controls - docs + bootstrap @@ -167,7 +167,7 @@ core controls client - docs + bootstrap @@ -197,7 +197,7 @@ - + diff --git a/resources/mediaplayer/pom.xml b/resources/mediaplayer/pom.xml index d199c1296..14f973aa6 100644 --- a/resources/mediaplayer/pom.xml +++ b/resources/mediaplayer/pom.xml @@ -91,17 +91,19 @@ mbrola ${version.freetts} - - org.ehcache - ehcache - 3.0.1 - org.slf4j slf4j-log4j12 1.5.6 provided + + + io.netty + netty-buffer + 4.1.9.Final + + commons-io commons-io diff --git a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java index 11648a1d2..824231f9e 100644 --- a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java +++ b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/CachedRemoteStreamProvider.java @@ -1,21 +1,17 @@ package org.restcomm.media.resource.player.audio; +import com.google.common.cache.*; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.buffer.Unpooled; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; -import org.ehcache.Cache; -import org.ehcache.CacheManager; -import org.ehcache.config.builders.CacheConfigurationBuilder; -import org.ehcache.config.builders.CacheManagerBuilder; -import org.ehcache.config.builders.ResourcePoolsBuilder; -import org.ehcache.config.units.MemoryUnit; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; +import java.util.Map; +import java.util.concurrent.Callable; /** * Created by achikin on 5/9/16. @@ -24,90 +20,46 @@ public class CachedRemoteStreamProvider implements RemoteStreamProvider { private final static Logger log = Logger.getLogger(CachedRemoteStreamProvider.class); - private CacheManager cacheManager; - - private ConcurrentHashMap inProgress = new ConcurrentHashMap<>(); + private Cache cache; public CachedRemoteStreamProvider(int size) { log.info("Create AudioCache with size: " + size + "Mb"); - cacheManager = CacheManagerBuilder.newCacheManagerBuilder() - .withCache("preConfigured", - CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, byte[].class, - ResourcePoolsBuilder.newResourcePoolsBuilder().offheap(size, MemoryUnit.MB)) - .build()) - .build(true); - } - - private Cache getCache() { - return cacheManager.getCache("preConfigured", String.class, byte[].class); - } - - public InputStream getStream(URL uri) throws IOException { - String key = uri.toString(); - Cache cache = getCache(); - - byte[] stream = cache.get(key); - if (stream == null) { - stream = download(cache, uri); - } - - return new ByteArrayInputStream(stream); - } - - private byte[] download(Cache cache, final URL uri) throws IOException { - String key = uri.toString(); - ByteStreamDownloader stream = inProgress.get(key); - if (stream == null) { - stream = new ByteStreamDownloader(); - ByteStreamDownloader prev = inProgress.putIfAbsent(key, stream); - if (prev == null) { - //check bytes in cache again too, maybe it's already added - byte[] bytes = cache.get(key); - if (bytes != null) { - return bytes; + cache = CacheBuilder.newBuilder().maximumWeight(size * 1024L * 1024L).weigher(new Weigher() { + @Override + public int weigh(String s, ByteBuf byteBuf) { + return byteBuf.capacity(); + } + }).removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification removalNotification) { + ByteBuf buf = removalNotification.getValue(); + if (buf != null) { + buf.release(); } - } else { - stream = prev; } - } + }).build(); + } + + public InputStream getStream(final URL uri) throws IOException { + final String key = uri.toString(); try { - byte[] bytes = stream.download(uri); - if (bytes != null) { - cache.putIfAbsent(key, bytes); - } else { - bytes = cache.get(key); - } - if (bytes == null) { - throw new IOException("No data for " + uri); - } - return bytes; - } finally { - inProgress.remove(key); + ByteBuf buf = cache.get(key, new Callable() { + @Override + public ByteBuf call() throws Exception { + byte[] bytes = IOUtils.toByteArray(uri.openStream()); + return Unpooled.directBuffer(bytes.length).writeBytes(bytes); + } + }); + return new ByteBufInputStream(buf.retainedDuplicate(), true); + } catch (Throwable e) { + throw new IOException(e); } } - private static class ByteStreamDownloader { - - private Lock lock = new ReentrantLock(); - - volatile boolean downloaded; - - public byte[] download(final URL uri) throws IOException { - if (downloaded) { - return null; - } - lock.lock(); - try { - //need to check twice - if (downloaded) { - return null; - } - byte[] bytes = IOUtils.toByteArray(uri.openStream()); - downloaded = bytes != null; - return bytes; - } finally { - lock.unlock(); - } + public void dump() { + log.info("--- Cache dump ---"); + for (Map.Entry e : cache.asMap().entrySet()) { + log.info(e.getKey() + "; " + e.getValue().refCnt()); } } } diff --git a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/RemoteStreamProvider.java b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/RemoteStreamProvider.java index 944fc53a7..f60dcdccc 100644 --- a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/RemoteStreamProvider.java +++ b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/RemoteStreamProvider.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.URL; +import java.util.concurrent.ExecutionException; /** * Created by achikin on 5/9/16. diff --git a/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/wav/WavTrackCacheTest.java b/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/wav/WavTrackCacheTest.java index 461614552..9e5055432 100644 --- a/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/wav/WavTrackCacheTest.java +++ b/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/wav/WavTrackCacheTest.java @@ -23,7 +23,6 @@ import org.mockito.stubbing.Answer; import org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider; import org.restcomm.media.resource.player.audio.DirectRemoteStreamProvider; -import org.restcomm.media.resource.player.audio.wav.WavTrackImpl; import org.restcomm.media.spi.format.EncodingName; import org.restcomm.media.spi.format.Format; @@ -68,15 +67,19 @@ public void testCache() throws IOException, UnsupportedAudioFileException { WavTrackImpl track1 = new WavTrackImpl(url1, cache); assertEquals(expectedFormat.getName(), track1.getFormat().getName()); assertEquals(expectedDuration, track1.getDuration()); + track1.close(); WavTrackImpl track2 = new WavTrackImpl(url2, cache); assertEquals(expectedFormat.getName(), track2.getFormat().getName()); assertEquals(expectedDuration, track2.getDuration()); + track2.close(); WavTrackImpl track3 = new WavTrackImpl(url2, cache); assertEquals(expectedFormat.getName(), track3.getFormat().getName()); assertEquals(expectedDuration, track3.getDuration()); + track3.close(); + cache.dump(); verify(mockConnection).getInputStream(); } @@ -84,10 +87,10 @@ public void testCache() throws IOException, UnsupportedAudioFileException { public void testCacheOverflow() throws IOException, UnsupportedAudioFileException { //file size is 61712 bytes //1Mb cache contains have 15 full files - int cacheSize = 1; - double fileSize = 61712d; - int iteration = (int) Math.floor(cacheSize * 1024d * 1024d / fileSize) - 1; - + //int cacheSize = 1; + //double fileSize = 61712d; + //we have 4 segments in guava cache + int iteration = 8;//(int) Math.floor(cacheSize * 1024d * 1024d / fileSize) - 1; CachedRemoteStreamProvider cache = new CachedRemoteStreamProvider(1); for (int j = 0; j < 10; j++) { @@ -96,6 +99,7 @@ public void testCacheOverflow() throws IOException, UnsupportedAudioFileExceptio WavTrackImpl track = new WavTrackImpl(url, cache); assertEquals(expectedFormat.getName(), track.getFormat().getName()); assertEquals(expectedDuration, track.getDuration()); + track.close(); } } verify(mockConnection, Mockito.times(iteration)).getInputStream(); @@ -104,6 +108,7 @@ public void testCacheOverflow() throws IOException, UnsupportedAudioFileExceptio WavTrackImpl track = new WavTrackImpl(url, cache); assertEquals(expectedFormat.getName(), track.getFormat().getName()); assertEquals(expectedDuration, track.getDuration()); + track.close(); } verify(mockConnection, Mockito.times(2 * iteration)).getInputStream(); } @@ -118,14 +123,17 @@ public void testNoCache() throws IOException, UnsupportedAudioFileException { WavTrackImpl track1 = new WavTrackImpl(url1, noCache); assertEquals(expectedFormat.getName(), track1.getFormat().getName()); assertEquals(expectedDuration, track1.getDuration()); + track1.close(); WavTrackImpl track2 = new WavTrackImpl(url2, noCache); assertEquals(expectedFormat.getName(), track2.getFormat().getName()); assertEquals(expectedDuration, track2.getDuration()); + track2.close(); WavTrackImpl track3 = new WavTrackImpl(url2, noCache); assertEquals(expectedFormat.getName(), track3.getFormat().getName()); assertEquals(expectedDuration, track3.getDuration()); + track3.close(); verify(mockConnection, times(3)).getInputStream(); } From 82b05156b773159bf715d3387da653289e780b63 Mon Sep 17 00:00:00 2001 From: Gennadiy Dubina Date: Wed, 15 Mar 2017 10:30:21 +0200 Subject: [PATCH 4/4] add url pattern for cache --- .../configuration/XmlConfigurationLoader.java | 3 +- .../media/bootstrap/ioc/CoreModule.java | 4 +- .../provider/CachedRemoteStreamProvider.java | 24 --------- .../CachedRemoteStreamProvider.java | 33 ++++++++++++ .../DirectRemoteStreamProvider.java | 2 +- .../XmlConfigurationLoaderTest.java | 1 + .../ioc/RemoteStreamProviderTest.java | 54 +++++++++++++++++++ bootstrap/src/test/resources/mediaserver.xml | 1 + .../configuration/ResourcesConfiguration.java | 7 ++- .../audio/PatternRemoteStreamProvider.java | 30 +++++++++++ .../player/audio/PatternProviderTest.java | 52 ++++++++++++++++++ 11 files changed, 182 insertions(+), 29 deletions(-) delete mode 100644 bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/CachedRemoteStreamProvider.java create mode 100644 bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/CachedRemoteStreamProvider.java rename bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/{ => remotestream}/DirectRemoteStreamProvider.java (90%) create mode 100644 bootstrap/src/test/java/org/restcomm/media/bootstrap/ioc/RemoteStreamProviderTest.java create mode 100644 resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/PatternRemoteStreamProvider.java create mode 100644 resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/PatternProviderTest.java diff --git a/bootstrap/src/main/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoader.java b/bootstrap/src/main/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoader.java index c204877e2..b5f740c61 100644 --- a/bootstrap/src/main/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoader.java +++ b/bootstrap/src/main/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoader.java @@ -149,7 +149,8 @@ private static void configurePlayer(HierarchicalConfiguration src } dst.setPlayerCache( cache.getBoolean("cacheEnabled", ResourcesConfiguration.PLAYER_CACHE_ENABLED), - cache.getInt("cacheSize", ResourcesConfiguration.PLAYER_CACHE_SIZE) + cache.getInt("cacheSize", ResourcesConfiguration.PLAYER_CACHE_SIZE), + cache.getString("urlPattern", null) ); } diff --git a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/CoreModule.java b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/CoreModule.java index bca0f7482..c1f2da009 100644 --- a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/CoreModule.java +++ b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/CoreModule.java @@ -25,8 +25,8 @@ import org.restcomm.media.bootstrap.ioc.provider.AudioPlayerPoolProvider; import org.restcomm.media.bootstrap.ioc.provider.AudioRecorderFactoryProvider; import org.restcomm.media.bootstrap.ioc.provider.AudioRecorderPoolProvider; -import org.restcomm.media.bootstrap.ioc.provider.CachedRemoteStreamProvider; -import org.restcomm.media.bootstrap.ioc.provider.DirectRemoteStreamProvider; +import org.restcomm.media.bootstrap.ioc.provider.remotestream.CachedRemoteStreamProvider; +import org.restcomm.media.bootstrap.ioc.provider.remotestream.DirectRemoteStreamProvider; import org.restcomm.media.bootstrap.ioc.provider.DtlsSrtpServerProviderProvider; import org.restcomm.media.bootstrap.ioc.provider.DtmfDetectorFactoryProvider; import org.restcomm.media.bootstrap.ioc.provider.DtmfDetectorPoolProvider; diff --git a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/CachedRemoteStreamProvider.java b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/CachedRemoteStreamProvider.java deleted file mode 100644 index 216a67ff2..000000000 --- a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/CachedRemoteStreamProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.restcomm.media.bootstrap.ioc.provider; - -import org.restcomm.media.core.configuration.MediaServerConfiguration; - -import com.google.inject.Inject; -import com.google.inject.Provider; - -/** - * Created by achikin on 6/3/16. - */ -public class CachedRemoteStreamProvider implements Provider { - - private static org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider instance; - - @Inject - public CachedRemoteStreamProvider(MediaServerConfiguration config) { - instance = new org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider(config.getResourcesConfiguration().getPlayerCacheSize()); - } - - @Override - public org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider get() { - return instance; - } -} diff --git a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/CachedRemoteStreamProvider.java b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/CachedRemoteStreamProvider.java new file mode 100644 index 000000000..6e62ea251 --- /dev/null +++ b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/CachedRemoteStreamProvider.java @@ -0,0 +1,33 @@ +package org.restcomm.media.bootstrap.ioc.provider.remotestream; + +import org.apache.commons.lang3.StringUtils; +import org.restcomm.media.core.configuration.MediaServerConfiguration; + +import com.google.inject.Inject; +import com.google.inject.Provider; +import org.restcomm.media.resource.player.audio.*; +import org.restcomm.media.resource.player.audio.DirectRemoteStreamProvider; + +/** + * Created by achikin on 6/3/16. + */ +public class CachedRemoteStreamProvider implements Provider { + + private static org.restcomm.media.resource.player.audio.RemoteStreamProvider instance; + + @Inject + public CachedRemoteStreamProvider(MediaServerConfiguration config) { + String pattern = config.getResourcesConfiguration().getPlayerCacheUrlPattern(); + org.restcomm.media.resource.player.audio.RemoteStreamProvider cached = new org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider(config.getResourcesConfiguration().getPlayerCacheSize()); + if (StringUtils.isEmpty(pattern) || "*".equals(pattern) || ".*".equals(pattern)) { + instance = cached; + } else { + instance = new PatternRemoteStreamProvider(pattern, new DirectRemoteStreamProvider(), cached); + } + } + + @Override + public org.restcomm.media.resource.player.audio.RemoteStreamProvider get() { + return instance; + } +} diff --git a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/DirectRemoteStreamProvider.java b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/DirectRemoteStreamProvider.java similarity index 90% rename from bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/DirectRemoteStreamProvider.java rename to bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/DirectRemoteStreamProvider.java index 6810363e0..616a85418 100644 --- a/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/DirectRemoteStreamProvider.java +++ b/bootstrap/src/main/java/org/restcomm/media/bootstrap/ioc/provider/remotestream/DirectRemoteStreamProvider.java @@ -1,4 +1,4 @@ -package org.restcomm.media.bootstrap.ioc.provider; +package org.restcomm.media.bootstrap.ioc.provider.remotestream; import com.google.inject.Provider; diff --git a/bootstrap/src/test/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoaderTest.java b/bootstrap/src/test/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoaderTest.java index 1cc37b70e..7bf60b507 100644 --- a/bootstrap/src/test/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoaderTest.java +++ b/bootstrap/src/test/java/org/restcomm/media/bootstrap/configuration/XmlConfigurationLoaderTest.java @@ -107,6 +107,7 @@ public void testLoadConfiguration() throws Exception { Assert.assertEquals(100, resources.getPlayerCacheSize()); Assert.assertEquals(true, resources.getPlayerCacheEnabled()); + Assert.assertEquals(".*", resources.getPlayerCacheUrlPattern()); } /** diff --git a/bootstrap/src/test/java/org/restcomm/media/bootstrap/ioc/RemoteStreamProviderTest.java b/bootstrap/src/test/java/org/restcomm/media/bootstrap/ioc/RemoteStreamProviderTest.java new file mode 100644 index 000000000..afe41a0ee --- /dev/null +++ b/bootstrap/src/test/java/org/restcomm/media/bootstrap/ioc/RemoteStreamProviderTest.java @@ -0,0 +1,54 @@ +package org.restcomm.media.bootstrap.ioc; + +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.restcomm.media.core.configuration.MediaServerConfiguration; +import org.restcomm.media.resource.player.audio.RemoteStreamProvider; + +/** + * Created by hamsterksu on 3/15/17. + */ +public class RemoteStreamProviderTest { + + @Test + public void testWildcard1() { + + String[] patterns = new String[]{null, "*", ".*"}; + for (String pattern : patterns) { + final MediaServerConfiguration config = new MediaServerConfiguration(); + config.getResourcesConfiguration().setPlayerCache(true, 100, pattern); + final Injector injector = Guice.createInjector(new MgcpModule(), new MediaModule(), new CoreModule(config)); + + RemoteStreamProvider obj = injector.getInstance(RemoteStreamProvider.class); + + Assert.assertEquals(org.restcomm.media.resource.player.audio.CachedRemoteStreamProvider.class, obj.getClass()); + } + } + + @Test + public void testPattern() { + final MediaServerConfiguration config = new MediaServerConfiguration(); + config.getResourcesConfiguration().setPlayerCache(true, 100, "http://.*/static/*"); + final Injector injector = Guice.createInjector(new MgcpModule(), new MediaModule(), new CoreModule(config)); + + RemoteStreamProvider obj = injector.getInstance(RemoteStreamProvider.class); + + Assert.assertEquals(org.restcomm.media.resource.player.audio.PatternRemoteStreamProvider.class, obj.getClass()); + } + + @Test + public void testDirect() { + + final MediaServerConfiguration config = new MediaServerConfiguration(); + //not the java pattern, but it's suitable for us + config.getResourcesConfiguration().setPlayerCache(false, 100, "*"); + final Injector injector = Guice.createInjector(new MgcpModule(), new MediaModule(), new CoreModule(config)); + + RemoteStreamProvider obj = injector.getInstance(RemoteStreamProvider.class); + + Assert.assertEquals(org.restcomm.media.resource.player.audio.DirectRemoteStreamProvider.class, obj.getClass()); + } +} diff --git a/bootstrap/src/test/resources/mediaserver.xml b/bootstrap/src/test/resources/mediaserver.xml index 10d00a7b7..e1a309ff5 100644 --- a/bootstrap/src/test/resources/mediaserver.xml +++ b/bootstrap/src/test/resources/mediaserver.xml @@ -48,6 +48,7 @@ 100 true + .* diff --git a/core/src/main/java/org/restcomm/media/core/configuration/ResourcesConfiguration.java b/core/src/main/java/org/restcomm/media/core/configuration/ResourcesConfiguration.java index f9fdf6e81..84d3e83b7 100644 --- a/core/src/main/java/org/restcomm/media/core/configuration/ResourcesConfiguration.java +++ b/core/src/main/java/org/restcomm/media/core/configuration/ResourcesConfiguration.java @@ -38,6 +38,7 @@ public class ResourcesConfiguration { private int dtmfGeneratorToneVolume; private int dtmfGeneratorToneDuration; private int playerCacheSize; + private String playerCacheUrlPattern; public ResourcesConfiguration() { this.dtmfDetectorDbi = DTMF_DETECTOR_DBI; @@ -79,7 +80,7 @@ public void setDtmfGeneratorToneDuration(int dtmfGeneratorToneDuration) { this.dtmfGeneratorToneDuration = dtmfGeneratorToneDuration; } - public void setPlayerCache(boolean playerCacheEnabled, int playerCacheSize) { + public void setPlayerCache(boolean playerCacheEnabled, int playerCacheSize, String urlPattern) { if (!playerCacheEnabled) { this.playerCacheSize = 0; return; @@ -88,6 +89,7 @@ public void setPlayerCache(boolean playerCacheEnabled, int playerCacheSize) { throw new IllegalArgumentException("Player cache size cannot be negative"); } this.playerCacheSize = playerCacheSize; + this.playerCacheUrlPattern = urlPattern; } public int getPlayerCacheSize() { @@ -98,4 +100,7 @@ public boolean getPlayerCacheEnabled() { return this.playerCacheSize != 0; } + public String getPlayerCacheUrlPattern() { + return playerCacheUrlPattern; + } } diff --git a/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/PatternRemoteStreamProvider.java b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/PatternRemoteStreamProvider.java new file mode 100644 index 000000000..c70600ff1 --- /dev/null +++ b/resources/mediaplayer/src/main/java/org/restcomm/media/resource/player/audio/PatternRemoteStreamProvider.java @@ -0,0 +1,30 @@ +package org.restcomm.media.resource.player.audio; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.regex.Pattern; + +/** + * Created by gdubina on 15/03/17. + */ +public class PatternRemoteStreamProvider implements RemoteStreamProvider { + + private final Pattern urlPattern; + private final RemoteStreamProvider direct; + private final RemoteStreamProvider cached; + + public PatternRemoteStreamProvider(String urlRegexp, RemoteStreamProvider direct, RemoteStreamProvider cached) { + this.urlPattern = Pattern.compile(urlRegexp); + this.direct = direct; + this.cached = cached; + } + + @Override + public InputStream getStream(URL uri) throws IOException { + if (urlPattern.matcher(uri.toString()).matches()) { + return cached.getStream(uri); + } + return direct.getStream(uri); + } +} diff --git a/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/PatternProviderTest.java b/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/PatternProviderTest.java new file mode 100644 index 000000000..00d146922 --- /dev/null +++ b/resources/mediaplayer/src/test/java/org/restcomm/media/resource/player/audio/PatternProviderTest.java @@ -0,0 +1,52 @@ +package org.restcomm.media.resource.player.audio; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Created by hamsterksu on 30.06.16. + */ +public class PatternProviderTest { + + RemoteStreamProvider direct; + RemoteStreamProvider cached; + + private InputStream directIs = new ByteArrayInputStream(new byte[0]); + private InputStream cachedIs = new ByteArrayInputStream(new byte[0]); + + @Before + public void setUp() throws IOException, UnsupportedAudioFileException { + direct = mock(RemoteStreamProvider.class); + cached = mock(RemoteStreamProvider.class); + when(direct.getStream(Mockito.any())).thenReturn(directIs); + when(cached.getStream(Mockito.any())).thenReturn(cachedIs); + } + + @Test + public void testWildcard() throws IOException { + PatternRemoteStreamProvider patternProvider = new PatternRemoteStreamProvider(".*", direct, cached); + Assert.assertEquals(cachedIs, patternProvider.getStream(new URL("http://127.0.0.1/test.wav"))); + + patternProvider = new PatternRemoteStreamProvider("http://.*", direct, cached); + Assert.assertEquals(cachedIs, patternProvider.getStream(new URL("http://127.0.0.1/test.wav"))); + } + + @Test + public void testPattern() throws IOException, UnsupportedAudioFileException { + PatternRemoteStreamProvider patternProvider = new PatternRemoteStreamProvider(".*/static/.*", direct, cached); + Assert.assertEquals(cachedIs, patternProvider.getStream(new URL("http://127.0.0.1/static/test.wav"))); + Assert.assertEquals(directIs, patternProvider.getStream(new URL("http://127.0.0.1/static_test.wav"))); + } + +}