SGF-468 - Improve coordination between the PoolFactoryBean and ClientCacheFactoryBean when configuring and resolving the GemFire DistributedSystem.
(cherry picked from commit 770038a3aa20c4a989689a5883f98716b4d925aa) Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
@@ -16,11 +16,14 @@
|
||||
|
||||
package org.springframework.data.gemfire.client;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.CoreMatchers.nullValue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
@@ -41,6 +44,7 @@ import org.junit.rules.ExpectedException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.data.gemfire.TestUtils;
|
||||
import org.springframework.data.gemfire.test.support.CollectionUtils;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
|
||||
import com.gemstone.gemfire.cache.client.Pool;
|
||||
@@ -51,7 +55,9 @@ import com.gemstone.gemfire.cache.client.PoolFactory;
|
||||
* of the PoolFactoryBean class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Rule
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.rules.ExpectedException
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.springframework.data.gemfire.client.PoolFactoryBean
|
||||
* @see com.gemstone.gemfire.cache.client.Pool
|
||||
@@ -89,7 +95,7 @@ public class PoolFactoryBeanTest {
|
||||
}
|
||||
|
||||
@Override void doDistributedSystemConnect(Properties properties) {
|
||||
assertSame(gemfireProperties, properties);
|
||||
assertThat(properties, is(equalTo(gemfireProperties)));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -180,7 +186,8 @@ public class PoolFactoryBeanTest {
|
||||
|
||||
Properties expectedGemfireProperties = new Properties();
|
||||
|
||||
expectedGemfireProperties.setProperty("name", "testResolveGemfireProperties");
|
||||
expectedGemfireProperties.setProperty("name", "resolveGemfirePropertiesTest");
|
||||
|
||||
clientCacheFactoryBean.setProperties(expectedGemfireProperties);
|
||||
|
||||
PoolFactoryBean poolFactoryBean = new PoolFactoryBean();
|
||||
@@ -189,7 +196,40 @@ public class PoolFactoryBeanTest {
|
||||
|
||||
Properties resolvedGemfireProperties = poolFactoryBean.resolveGemfireProperties();
|
||||
|
||||
assertSame(expectedGemfireProperties, resolvedGemfireProperties);
|
||||
assertThat(resolvedGemfireProperties, is(equalTo(expectedGemfireProperties)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMergedGemfireProperties() {
|
||||
BeanFactory mockBeanFactory = mock(BeanFactory.class, "MockBeanFactory");
|
||||
|
||||
ClientCacheFactoryBean clientCacheFactoryBean = new ClientCacheFactoryBean();
|
||||
|
||||
when(mockBeanFactory.getBean(eq(ClientCacheFactoryBean.class))).thenReturn(clientCacheFactoryBean);
|
||||
|
||||
Properties poolGemfireProperties = new Properties();
|
||||
Properties expectedGemfireProperties = new Properties();
|
||||
|
||||
poolGemfireProperties.setProperty("name", "resolveMergedGemfirePropertiesTest");
|
||||
poolGemfireProperties.setProperty("log-level", "warning");
|
||||
|
||||
CollectionUtils.mergePropertiesIntoMap(poolGemfireProperties, expectedGemfireProperties);
|
||||
|
||||
expectedGemfireProperties.setProperty("log-level", "config");
|
||||
expectedGemfireProperties.setProperty("durableClientId", "123");
|
||||
expectedGemfireProperties.setProperty("durableClientTimeout", "60");
|
||||
|
||||
clientCacheFactoryBean.setProperties(expectedGemfireProperties);
|
||||
|
||||
PoolFactoryBean poolFactoryBean = new PoolFactoryBean();
|
||||
|
||||
poolFactoryBean.setBeanFactory(mockBeanFactory);
|
||||
poolFactoryBean.setProperties(poolGemfireProperties);
|
||||
|
||||
Properties resolvedGemfireProperties = poolFactoryBean.resolveGemfireProperties();
|
||||
|
||||
assertThat(resolvedGemfireProperties.getProperty("log-level"), is(equalTo("config")));
|
||||
assertThat(resolvedGemfireProperties, is(equalTo(expectedGemfireProperties)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -203,7 +243,10 @@ public class PoolFactoryBeanTest {
|
||||
|
||||
poolFactoryBean.setBeanFactory(mockBeanFactory);
|
||||
|
||||
assertNull(poolFactoryBean.resolveGemfireProperties());
|
||||
Properties gemfireProperties = poolFactoryBean.resolveGemfireProperties();
|
||||
|
||||
assertThat(gemfireProperties, is(notNullValue()));
|
||||
assertThat(gemfireProperties.isEmpty(), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2012 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.config;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Matchers.isNotNull;
|
||||
import static org.mockito.Matchers.same;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.springframework.beans.PropertyValues;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
||||
import org.w3c.dom.Element;
|
||||
import org.w3c.dom.NodeList;
|
||||
|
||||
/**
|
||||
* The ClientCacheParserTest class is a test suite of test cases testing the contract and functionality of the
|
||||
* ClientCacheParser class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @since 1.8.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClientCacheParserTest {
|
||||
|
||||
@Mock
|
||||
private Element mockElement;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("all")
|
||||
public void beanClassEqualsClientCacheFactoryBean() {
|
||||
assertThat((Class<ClientCacheFactoryBean>) new ClientCacheParser().getBeanClass(mockElement),
|
||||
is(equalTo(ClientCacheFactoryBean.class)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doParseSetsPropertiesAndRegistersClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor() {
|
||||
NodeList mockNodeList = mock(NodeList.class);
|
||||
|
||||
when(mockNodeList.getLength()).thenReturn(0);
|
||||
when(mockElement.getAttribute(eq("durable-client-id"))).thenReturn("123");
|
||||
when(mockElement.getAttribute(eq("durable-client-timeout"))).thenReturn("60");
|
||||
when(mockElement.getAttribute(eq("keep-alive"))).thenReturn("false");
|
||||
when(mockElement.getAttribute(eq("pool-name"))).thenReturn("testPool");
|
||||
when(mockElement.getAttribute(eq("ready-for-events"))).thenReturn(null);
|
||||
when(mockElement.getChildNodes()).thenReturn(mockNodeList);
|
||||
|
||||
final AtomicReference<BeanDefinition> beanDefinitionReference = new AtomicReference<BeanDefinition>(null);
|
||||
|
||||
final BeanDefinitionRegistry mockRegistry = mock(BeanDefinitionRegistry.class);
|
||||
|
||||
doAnswer(new Answer<Void>() {
|
||||
public Void answer(InvocationOnMock invocation) throws Throwable {
|
||||
BeanDefinition beanFactoryPostProcessorBeanDefinition =
|
||||
invocation.getArgumentAt(1, BeanDefinition.class);
|
||||
|
||||
if (ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor.class.getName().equals(
|
||||
beanFactoryPostProcessorBeanDefinition.getBeanClassName())) {
|
||||
beanDefinitionReference.compareAndSet(null, beanFactoryPostProcessorBeanDefinition);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}).when(mockRegistry).registerBeanDefinition(isNotNull(String.class), any(BeanDefinition.class));
|
||||
|
||||
when(mockRegistry.containsBeanDefinition(anyString())).thenReturn(false);
|
||||
|
||||
BeanDefinitionBuilder clientCacheBuilder = BeanDefinitionBuilder.genericBeanDefinition();
|
||||
|
||||
ClientCacheParser clientCacheParser = new ClientCacheParser() {
|
||||
@Override protected BeanDefinitionRegistry getRegistry(ParserContext parserContext) {
|
||||
return mockRegistry;
|
||||
}
|
||||
};
|
||||
|
||||
clientCacheParser.doParse(mockElement, null, clientCacheBuilder);
|
||||
|
||||
BeanDefinition clientCacheBeanDefinition = clientCacheBuilder.getBeanDefinition();
|
||||
|
||||
assertThat(clientCacheBeanDefinition, is(notNullValue()));
|
||||
|
||||
PropertyValues propertyValues = clientCacheBeanDefinition.getPropertyValues();
|
||||
|
||||
assertThat(propertyValues, is(notNullValue()));
|
||||
assertThat((String) propertyValues.getPropertyValue("durableClientId").getValue(), is(equalTo("123")));
|
||||
assertThat((String) propertyValues.getPropertyValue("durableClientTimeout").getValue(), is(equalTo("60")));
|
||||
assertThat((String) propertyValues.getPropertyValue("keepAlive").getValue(), is(equalTo("false")));
|
||||
assertThat((String) propertyValues.getPropertyValue("poolName").getValue(), is(equalTo("testPool")));
|
||||
assertThat(propertyValues.getPropertyValue("readyForEvents"), is(nullValue()));
|
||||
assertThat(beanDefinitionReference.get(), is(notNullValue()));
|
||||
|
||||
verify(mockElement, times(1)).getAttribute(eq("durable-client-id"));
|
||||
verify(mockElement, times(1)).getAttribute(eq("durable-client-timeout"));
|
||||
verify(mockElement, times(1)).getAttribute(eq("keep-alive"));
|
||||
verify(mockElement, times(1)).getAttribute(eq("pool-name"));
|
||||
verify(mockElement, times(1)).getAttribute(eq("ready-for-events"));
|
||||
verify(mockRegistry, times(1)).registerBeanDefinition(isNotNull(String.class),
|
||||
same(beanDefinitionReference.get()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessDynamicRegionSupportParsesPoolName() {
|
||||
when(mockElement.getAttribute(eq("pool-name"))).thenReturn("testPool");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
|
||||
|
||||
new ClientCacheParser().postProcessDynamicRegionSupport(mockElement, builder);
|
||||
|
||||
BeanDefinition beanDefinition = builder.getBeanDefinition();
|
||||
|
||||
assertThat(beanDefinition, is(notNullValue()));
|
||||
assertThat(beanDefinition.getPropertyValues(), is(notNullValue()));
|
||||
assertThat(beanDefinition.getPropertyValues().getPropertyValue("poolName"), is(notNullValue()));
|
||||
assertThat((String) beanDefinition.getPropertyValues().getPropertyValue("poolName").getValue(), is(equalTo("testPool")));
|
||||
|
||||
verify(mockElement, times(1)).getAttribute(eq("pool-name"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2012 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.config;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||
import org.springframework.beans.factory.config.RuntimeBeanReference;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.data.gemfire.client.ClientCacheFactoryBean;
|
||||
import org.springframework.data.gemfire.client.PoolFactoryBean;
|
||||
|
||||
/**
|
||||
* The ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessorTest class is a test suite of test cases testing
|
||||
* the contract and functionality of the ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor class.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.mockito.Mock
|
||||
* @see org.mockito.Mockito
|
||||
* @see org.mockito.runners.MockitoJUnitRunner
|
||||
* @see org.springframework.data.gemfire.config.ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor
|
||||
* @since 1.0.0
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessorTest {
|
||||
|
||||
protected static final String PROPERTIES_PROPERTY_NAME_SHORTCUT =
|
||||
ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor.PROPERTIES_PROPERTY_NAME;
|
||||
|
||||
private ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor beanFactoryPostProcessor =
|
||||
new ClientCachePoolGemfirePropertiesSyncingBeanFactoryPostProcessor();
|
||||
|
||||
@Mock
|
||||
private ConfigurableListableBeanFactory mockBeanFactory;
|
||||
|
||||
protected String getBeanReference(Object reference) {
|
||||
return (reference instanceof RuntimeBeanReference ? ((RuntimeBeanReference) reference).getBeanName() : null);
|
||||
}
|
||||
|
||||
protected BeanDefinition newBeanDefinition(Class<?> type) {
|
||||
return BeanDefinitionBuilder.genericBeanDefinition(type).getBeanDefinition();
|
||||
}
|
||||
|
||||
protected <T> T[] toArray(T... array) {
|
||||
return array;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessSyncsGemfireProperties() {
|
||||
BeanDefinition objectBean = newBeanDefinition(Object.class);
|
||||
BeanDefinition clientCacheBean = newBeanDefinition(ClientCacheFactoryBean.class);
|
||||
|
||||
clientCacheBean.getPropertyValues().addPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT,
|
||||
new RuntimeBeanReference("gemfireProperties"));
|
||||
|
||||
BeanDefinition poolBean = newBeanDefinition(PoolFactoryBean.class);
|
||||
|
||||
when(mockBeanFactory.getBeanDefinitionNames()).thenReturn(toArray("objectBean", "clientCacheBean", "poolBean"));
|
||||
when(mockBeanFactory.getBeanDefinition(eq("objectBean"))).thenReturn(objectBean);
|
||||
when(mockBeanFactory.getBeanDefinition(eq("clientCacheBean"))).thenReturn(clientCacheBean);
|
||||
when(mockBeanFactory.getBeanDefinition(eq("poolBean"))).thenReturn(poolBean);
|
||||
|
||||
assertThat(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT), is(nullValue()));
|
||||
|
||||
beanFactoryPostProcessor.postProcessBeanFactory(mockBeanFactory);
|
||||
|
||||
assertThat(getBeanReference(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT).getValue()),
|
||||
is(equalTo("gemfireProperties")));
|
||||
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinitionNames();
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("objectBean"));
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("clientCacheBean"));
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("poolBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessDoesNotSyncGemfirePropertiesWhenNoClientCacheFactoryBeanIsDeclared() {
|
||||
BeanDefinition poolBean = newBeanDefinition(PoolFactoryBean.class);
|
||||
|
||||
when(mockBeanFactory.getBeanDefinitionNames()).thenReturn(toArray("poolBean"));
|
||||
when(mockBeanFactory.getBeanDefinition(eq("poolBean"))).thenReturn(poolBean);
|
||||
|
||||
assertThat(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT), is(nullValue()));
|
||||
|
||||
beanFactoryPostProcessor.postProcessBeanFactory(mockBeanFactory);
|
||||
|
||||
assertThat(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT), is(nullValue()));
|
||||
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinitionNames();
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("poolBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessDoesNotSyncGemfirePropertiesWhenNoPoolFactoryBeanIsDeclared() {
|
||||
BeanDefinition clientCacheBean = newBeanDefinition(ClientCacheFactoryBean.class);
|
||||
|
||||
clientCacheBean.getPropertyValues().addPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT,
|
||||
new RuntimeBeanReference("gemfireProperties"));
|
||||
|
||||
when(mockBeanFactory.getBeanDefinitionNames()).thenReturn(toArray("clientCacheBean"));
|
||||
when(mockBeanFactory.getBeanDefinition(eq("clientCacheBean"))).thenReturn(clientCacheBean);
|
||||
|
||||
beanFactoryPostProcessor.postProcessBeanFactory(mockBeanFactory);
|
||||
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinitionNames();
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("clientCacheBean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void postProcessDoesNotSyncWhenGemfirePropertiesAreUnspecified() {
|
||||
BeanDefinition clientCacheBean = newBeanDefinition(ClientCacheFactoryBean.class);
|
||||
BeanDefinition poolBean = newBeanDefinition(PoolFactoryBean.class);
|
||||
|
||||
when(mockBeanFactory.getBeanDefinitionNames()).thenReturn(toArray("clientCacheBean", "poolBean"));
|
||||
when(mockBeanFactory.getBeanDefinition(eq("clientCacheBean"))).thenReturn(clientCacheBean);
|
||||
when(mockBeanFactory.getBeanDefinition(eq("poolBean"))).thenReturn(poolBean);
|
||||
|
||||
assertThat(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT), is(nullValue()));
|
||||
|
||||
beanFactoryPostProcessor.postProcessBeanFactory(mockBeanFactory);
|
||||
|
||||
assertThat(poolBean.getPropertyValues().getPropertyValue(PROPERTIES_PROPERTY_NAME_SHORTCUT), is(nullValue()));
|
||||
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinitionNames();
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("clientCacheBean"));
|
||||
verify(mockBeanFactory, times(1)).getBeanDefinition(eq("poolBean"));
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user