SGF-196 - Support adding CacheListeners, CacheLoaders and CacheWriters, along with other mutable Region attributes to an existing Region.
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import com.gemstone.gemfire.cache.AttributesMutator;
|
||||
import com.gemstone.gemfire.cache.Cache;
|
||||
import com.gemstone.gemfire.cache.CacheListener;
|
||||
import com.gemstone.gemfire.cache.CacheLoader;
|
||||
import com.gemstone.gemfire.cache.CacheWriter;
|
||||
import com.gemstone.gemfire.cache.CustomExpiry;
|
||||
import com.gemstone.gemfire.cache.EvictionAttributesMutator;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionAttributes;
|
||||
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventQueue;
|
||||
import com.gemstone.gemfire.cache.wan.GatewaySender;
|
||||
|
||||
/**
|
||||
* The LookupRegionFactoryBeanTest class is a test suite of test cases testing the contract and functionality
|
||||
* of the LookupRegionFactoryBean class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.LookupRegionFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.AttributesMutator
|
||||
* @see com.gemstone.gemfire.cache.Cache
|
||||
* @see com.gemstone.gemfire.cache.EvictionAttributesMutator
|
||||
* @see com.gemstone.gemfire.cache.Region
|
||||
* @since 1.7.0
|
||||
*/
|
||||
public class LookupRegionFactoryBeanTest {
|
||||
|
||||
protected AsyncEventQueue mockAsyncEventQueue(final String id) {
|
||||
AsyncEventQueue mockQueue = mock(AsyncEventQueue.class, String.format("MockAsyncEventQueue.%1$s", id));
|
||||
when(mockQueue.getId()).thenReturn(id);
|
||||
return mockQueue;
|
||||
}
|
||||
|
||||
protected GatewaySender mockGatewaySender(final String id) {
|
||||
GatewaySender mockGatewaySender = mock(GatewaySender.class, String.format("MockGatewaySender.%1$s", id));
|
||||
when(mockGatewaySender.getId()).thenReturn(id);
|
||||
return mockGatewaySender;
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAfterPropertiesSet() throws Exception {
|
||||
Cache mockCache = mock(Cache.class, "testAfterPropertiesSet.MockCache");
|
||||
|
||||
Region<Object, Object> mockRegion = mock(Region.class, "testAfterPropertiesSet.MockRegion");
|
||||
|
||||
RegionAttributes<Object, Object> mockRegionAttributes = mock(RegionAttributes.class,
|
||||
"testAfterPropertiesSet.MockRegionAttributes");
|
||||
|
||||
EvictionAttributesMutator mockEvictionAttributesMutator = mock(EvictionAttributesMutator.class,
|
||||
"testAfterPropertiesSet.EvictionAttributesMutator");
|
||||
|
||||
AttributesMutator<Object, Object> mockAttributesMutator = mock(AttributesMutator.class,
|
||||
"testAfterPropertiesSet.MockAttributesMutator");
|
||||
|
||||
when(mockCache.getRegion(eq("Example"))).thenReturn(mockRegion);
|
||||
when(mockRegion.getFullPath()).thenReturn("/Example");
|
||||
when(mockRegion.getName()).thenReturn("Example");
|
||||
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
|
||||
when(mockRegionAttributes.getStatisticsEnabled()).thenReturn(true);
|
||||
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
|
||||
when(mockAttributesMutator.getEvictionAttributesMutator()).thenReturn(mockEvictionAttributesMutator);
|
||||
|
||||
AsyncEventQueue mockAsyncEventQueueOne = mockAsyncEventQueue("AEQ1");
|
||||
AsyncEventQueue mockAsyncEventQueueTwo = mockAsyncEventQueue("AEQ2");
|
||||
|
||||
CacheListener mockCacheListenerZero = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.0");
|
||||
CacheListener mockCacheListenerOne = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.1");
|
||||
CacheListener mockCacheListenerTwo = mock(CacheListener.class, "testAfterPropertiesSet.MockCacheListener.2");
|
||||
|
||||
CacheLoader mockCacheLoader = mock(CacheLoader.class, "testAfterPropertiesSet.MockCacheLoader");
|
||||
|
||||
CacheWriter mockCacheWriter = mock(CacheWriter.class, "testAfterPropertiesSet.MockCacheWriter");
|
||||
|
||||
CustomExpiry mockCustomExpiryTti = mock(CustomExpiry.class, "testAfterPropertiesSet.MockCustomExpiry.TTI");
|
||||
CustomExpiry mockCustomExpiryTtl = mock(CustomExpiry.class, "testAfterPropertiesSet.MockCustomExpiry.TTL");
|
||||
|
||||
ExpirationAttributes mockExpirationAttributesEntryTti = mock(ExpirationAttributes.class,
|
||||
"testAfterPropertiesSet.MockExpirationAttributes.Entry.TTI");
|
||||
ExpirationAttributes mockExpirationAttributesEntryTtl = mock(ExpirationAttributes.class,
|
||||
"testAfterPropertiesSet.MockExpirationAttributes.Entry.TTL");
|
||||
ExpirationAttributes mockExpirationAttributesRegionTti = mock(ExpirationAttributes.class,
|
||||
"testAfterPropertiesSet.MockExpirationAttributes.Region.TTI");
|
||||
ExpirationAttributes mockExpirationAttributesRegionTtl = mock(ExpirationAttributes.class,
|
||||
"testAfterPropertiesSet.MockExpirationAttributes.Region.TTL");
|
||||
|
||||
GatewaySender mockGatewaySender = mockGatewaySender("GWS1");
|
||||
|
||||
LookupRegionFactoryBean factoryBean = new LookupRegionFactoryBean();
|
||||
|
||||
factoryBean.setAsyncEventQueues(new AsyncEventQueue[] { mockAsyncEventQueueOne, mockAsyncEventQueueTwo });
|
||||
factoryBean.setBeanName("Example");
|
||||
factoryBean.setCache(mockCache);
|
||||
factoryBean.setCacheLoader(mockCacheLoader);
|
||||
factoryBean.setCacheWriter(mockCacheWriter);
|
||||
factoryBean.setCloningEnabled(true);
|
||||
factoryBean.setCustomEntryIdleTimeout(mockCustomExpiryTti);
|
||||
factoryBean.setCustomEntryTimeToLive(mockCustomExpiryTtl);
|
||||
factoryBean.setEntryIdleTimeout(mockExpirationAttributesEntryTti);
|
||||
factoryBean.setEntryTimeToLive(mockExpirationAttributesEntryTtl);
|
||||
factoryBean.setGatewaySenders(new GatewaySender[] { mockGatewaySender });
|
||||
factoryBean.setEvictionMaximum(1000);
|
||||
factoryBean.setRegionIdleTimeout(mockExpirationAttributesRegionTti);
|
||||
factoryBean.setRegionTimeToLive(mockExpirationAttributesRegionTtl);
|
||||
factoryBean.setStatisticsEnabled(true);
|
||||
|
||||
factoryBean.setCacheListeners(new CacheListener[] {
|
||||
mockCacheListenerZero, mockCacheListenerOne, mockCacheListenerTwo
|
||||
});
|
||||
|
||||
factoryBean.afterPropertiesSet();
|
||||
|
||||
verify(mockAttributesMutator, times(1)).addAsyncEventQueueId(eq("AEQ1"));
|
||||
verify(mockAttributesMutator, times(1)).addAsyncEventQueueId(eq("AEQ2"));
|
||||
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerZero));
|
||||
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerOne));
|
||||
verify(mockAttributesMutator, times(1)).addCacheListener(same(mockCacheListenerTwo));
|
||||
verify(mockAttributesMutator, times(1)).setCacheLoader(same(mockCacheLoader));
|
||||
verify(mockAttributesMutator, times(1)).setCacheWriter(same(mockCacheWriter));
|
||||
verify(mockAttributesMutator, times(1)).setCloningEnabled(eq(true));
|
||||
verify(mockAttributesMutator, times(1)).setCustomEntryIdleTimeout(same(mockCustomExpiryTti));
|
||||
verify(mockAttributesMutator, times(1)).setCustomEntryTimeToLive(same(mockCustomExpiryTtl));
|
||||
verify(mockAttributesMutator, times(1)).setEntryIdleTimeout(same(mockExpirationAttributesEntryTti));
|
||||
verify(mockAttributesMutator, times(1)).setEntryTimeToLive(same(mockExpirationAttributesEntryTtl));
|
||||
verify(mockAttributesMutator, times(1)).addGatewaySenderId(eq("GWS1"));
|
||||
verify(mockEvictionAttributesMutator, times(1)).setMaximum(eq(1000));
|
||||
verify(mockAttributesMutator, times(1)).setRegionIdleTimeout(same(mockExpirationAttributesRegionTti));
|
||||
verify(mockAttributesMutator, times(1)).setRegionTimeToLive(same(mockExpirationAttributesRegionTtl));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified() throws Exception {
|
||||
Cache mockCache = mock(Cache.class, "testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockCache");
|
||||
|
||||
Region<Object, Object> mockRegion = mock(Region.class, "testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockRegion");
|
||||
|
||||
RegionAttributes<Object, Object> mockRegionAttributes = mock(RegionAttributes.class,
|
||||
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockRegionAttributes");
|
||||
|
||||
AttributesMutator mockAttributesMutator = mock(AttributesMutator.class,
|
||||
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockAttributesMutator");
|
||||
|
||||
ExpirationAttributes mockExpirationAttributesEntryTtl = mock(ExpirationAttributes.class,
|
||||
"testAfterPropertiesSetWhenRegionStatisticsDisabledAndExpirationSpecified.MockExpirationAttributes.Entry.TTL");
|
||||
|
||||
when(mockCache.getRegion(eq("Example"))).thenReturn(mockRegion);
|
||||
when(mockRegion.getFullPath()).thenReturn("/Example");
|
||||
when(mockRegion.getName()).thenReturn("Example");
|
||||
when(mockRegion.getAttributes()).thenReturn(mockRegionAttributes);
|
||||
when(mockRegion.getAttributesMutator()).thenReturn(mockAttributesMutator);
|
||||
when(mockRegionAttributes.getStatisticsEnabled()).thenReturn(false);
|
||||
|
||||
LookupRegionFactoryBean factoryBean = new LookupRegionFactoryBean();
|
||||
|
||||
factoryBean.setBeanName("Example");
|
||||
factoryBean.setCache(mockCache);
|
||||
factoryBean.setEntryTimeToLive(mockExpirationAttributesEntryTtl);
|
||||
//factoryBean.setStatisticsEnabled(true);
|
||||
|
||||
assertTrue(factoryBean.isStatisticsEnabled());
|
||||
|
||||
try {
|
||||
factoryBean.afterPropertiesSet();
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
assertEquals("Statistics for Region '/Example' must be enabled to change Entry & Region TTL/TTI Expiration settings",
|
||||
expected.getMessage());
|
||||
throw expected;
|
||||
}
|
||||
finally {
|
||||
verify(mockAttributesMutator, never()).setEntryTimeToLive(any(ExpirationAttributes.class));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
import com.gemstone.gemfire.cache.CacheListener;
|
||||
import com.gemstone.gemfire.cache.CacheLoader;
|
||||
import com.gemstone.gemfire.cache.CacheLoaderException;
|
||||
import com.gemstone.gemfire.cache.CacheWriter;
|
||||
import com.gemstone.gemfire.cache.CacheWriterException;
|
||||
import com.gemstone.gemfire.cache.CustomExpiry;
|
||||
import com.gemstone.gemfire.cache.DataPolicy;
|
||||
import com.gemstone.gemfire.cache.EntryEvent;
|
||||
import com.gemstone.gemfire.cache.ExpirationAction;
|
||||
import com.gemstone.gemfire.cache.ExpirationAttributes;
|
||||
import com.gemstone.gemfire.cache.LoaderHelper;
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
import com.gemstone.gemfire.cache.RegionEvent;
|
||||
import com.gemstone.gemfire.cache.asyncqueue.AsyncEvent;
|
||||
import com.gemstone.gemfire.cache.asyncqueue.AsyncEventListener;
|
||||
import com.gemstone.gemfire.cache.util.CacheListenerAdapter;
|
||||
import com.gemstone.gemfire.management.internal.cli.util.spring.StringUtils;
|
||||
|
||||
/**
|
||||
* The LookupRegionMutationIntegrationTest class is a test suite of test cases testing the contract and integrated
|
||||
* functionality between natively-defined GemFire Cache Regions and SDG's Region lookup functionality combined with
|
||||
* Region attribute(s) mutation.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.springframework.data.gemfire.LookupRegionFactoryBean
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @since 1.7.0
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class LookupRegionMutationIntegrationTest {
|
||||
|
||||
@Resource(name = "Example")
|
||||
private Region<?, ?> example;
|
||||
|
||||
protected void assertGemFireComponent(Object gemfireComponent, String expectedName) {
|
||||
assertNotNull("The GemFire component must not be null!", gemfireComponent);
|
||||
assertEquals(expectedName, gemfireComponent.toString());
|
||||
}
|
||||
|
||||
protected void assertExpirationAttributes(ExpirationAttributes expirationAttributes,
|
||||
String description, int expectedTimeout, ExpirationAction expectedAction) {
|
||||
assertNotNull(String.format("ExpirationAttributes for '%1$s' must not be null!", description), expirationAttributes);
|
||||
assertEquals(expectedAction, expirationAttributes.getAction());
|
||||
assertEquals(expectedTimeout, expirationAttributes.getTimeout());
|
||||
}
|
||||
|
||||
protected void assertCacheListeners(CacheListener[] cacheListeners, Collection<String> expectedCacheListenerNames) {
|
||||
if (!expectedCacheListenerNames.isEmpty()) {
|
||||
assertNotNull("CacheListeners must not be null!", cacheListeners);
|
||||
assertEquals(expectedCacheListenerNames.size(), cacheListeners.length);
|
||||
assertTrue(toStrings(cacheListeners).containsAll(expectedCacheListenerNames));
|
||||
}
|
||||
}
|
||||
|
||||
protected Collection<String> toStrings(Object[] objects) {
|
||||
List<String> cacheListenerNames = new ArrayList<String>(objects.length);
|
||||
|
||||
for (Object object : objects) {
|
||||
cacheListenerNames.add(object.toString());
|
||||
}
|
||||
|
||||
return cacheListenerNames;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionConfiguration() {
|
||||
assertNotNull("'/Example' Region was not properly initialized!", example);
|
||||
assertEquals("Example", example.getName());
|
||||
assertEquals("/Example", example.getFullPath());
|
||||
assertNotNull(example.getAttributes());
|
||||
assertEquals(DataPolicy.REPLICATE, example.getAttributes().getDataPolicy());
|
||||
assertEquals(13, example.getAttributes().getInitialCapacity());
|
||||
assertEquals(0.85f, example.getAttributes().getLoadFactor(), 0.0f);
|
||||
assertCacheListeners(example.getAttributes().getCacheListeners(), Arrays.asList("A", "B"));
|
||||
assertGemFireComponent(example.getAttributes().getCacheLoader(), "C");
|
||||
assertGemFireComponent(example.getAttributes().getCacheWriter(), "D");
|
||||
assertExpirationAttributes(example.getAttributes().getRegionTimeToLive(), "Region TTL",
|
||||
120, ExpirationAction.LOCAL_DESTROY);
|
||||
assertExpirationAttributes(example.getAttributes().getRegionIdleTimeout(), "Region TTI",
|
||||
60, ExpirationAction.INVALIDATE);
|
||||
assertExpirationAttributes(example.getAttributes().getEntryTimeToLive(), "Entry TTL",
|
||||
30, ExpirationAction.DESTROY);
|
||||
assertGemFireComponent(example.getAttributes().getCustomEntryIdleTimeout(), "E");
|
||||
assertNotNull(example.getAttributes().getGatewaySenderIds());
|
||||
assertEquals(1, example.getAttributes().getGatewaySenderIds().size());
|
||||
assertEquals("GWS", example.getAttributes().getGatewaySenderIds().iterator().next());
|
||||
assertNotNull(example.getAttributes().getAsyncEventQueueIds());
|
||||
assertEquals(1, example.getAttributes().getAsyncEventQueueIds().size());
|
||||
assertEquals("AEQ", example.getAttributes().getAsyncEventQueueIds().iterator().next());
|
||||
}
|
||||
|
||||
protected static interface Nameable extends BeanNameAware {
|
||||
String getName();
|
||||
void setName(String name);
|
||||
}
|
||||
|
||||
protected static abstract class AbstractNameable implements Nameable {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(final String name) {
|
||||
if (!StringUtils.hasText(this.name)) {
|
||||
setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TestAsyncEventListener extends AbstractNameable implements AsyncEventListener {
|
||||
|
||||
@Override public boolean processEvents(List<AsyncEvent> events) {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
@Override public void close() { }
|
||||
}
|
||||
|
||||
public static final class TestCacheListener<K, V> extends CacheListenerAdapter<K, V> implements Nameable {
|
||||
|
||||
private String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanName(final String name) {
|
||||
if (!StringUtils.hasText(this.name)) {
|
||||
setName(name);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getName();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class TestCacheLoader<K, V> extends AbstractNameable implements CacheLoader<K, V> {
|
||||
|
||||
@Override
|
||||
public V load(LoaderHelper<K, V> helper) throws CacheLoaderException {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() { }
|
||||
}
|
||||
|
||||
public static final class TestCacheWriter<K, V> extends AbstractNameable implements CacheWriter<K, V> {
|
||||
|
||||
@Override public void beforeUpdate(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override public void beforeCreate(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override public void beforeDestroy(EntryEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override public void beforeRegionDestroy(RegionEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override public void beforeRegionClear(RegionEvent<K, V> event) throws CacheWriterException { }
|
||||
|
||||
@Override public void close() { }
|
||||
}
|
||||
|
||||
public static final class TestCustomExpiry<K, V> extends AbstractNameable implements CustomExpiry<K, V> {
|
||||
|
||||
@Override public ExpirationAttributes getExpiry(Region.Entry<K, V> entry) {
|
||||
throw new UnsupportedOperationException("Not Implemented!");
|
||||
}
|
||||
|
||||
@Override public void close() { }
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user