mirror of
https://github.com/spring-projects/spring-boot.git
synced 2024-08-29 03:06:45 +08:00
Rename SpringApplicationLifecycle => Admin
Rename SpringApplicationLifecycle JMX beans to SpringApplicationAdmin and relocate to a dedicated package. Fixes gh-3124
This commit is contained in:
parent
f8f4bd6c95
commit
105039cdb2
@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.context;
|
||||
package org.springframework.boot.autoconfigure.admin;
|
||||
|
||||
import javax.management.MalformedObjectNameException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.admin.SpringApplicationAdminMXBean;
|
||||
import org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar;
|
||||
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
|
||||
import org.springframework.boot.context.SpringApplicationLifecycleMXBean;
|
||||
import org.springframework.boot.context.SpringApplicationLifecycleRegistrar;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
@ -35,23 +35,23 @@ import org.springframework.jmx.export.MBeanExporter;
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
* @see SpringApplicationLifecycleMXBean
|
||||
* @see SpringApplicationAdminMXBean
|
||||
*/
|
||||
@Configuration
|
||||
@AutoConfigureAfter(JmxAutoConfiguration.class)
|
||||
@ConditionalOnProperty(value = "spring.context.lifecycle.enabled", havingValue = "true", matchIfMissing = false)
|
||||
class SpringApplicationLifecycleAutoConfiguration {
|
||||
public class SpringApplicationAdminJmxAutoConfiguration {
|
||||
|
||||
/**
|
||||
* The property to use to customize the {@code ObjectName} of the application
|
||||
* lifecycle mbean.
|
||||
*/
|
||||
static final String JMX_NAME_PROPERTY = "spring.context.lifecycle.jmx-name";
|
||||
private static final String JMX_NAME_PROPERTY = "spring.application.admin.jmx-name";
|
||||
|
||||
/**
|
||||
* The default {@code ObjectName} of the application lifecycle mbean.
|
||||
*/
|
||||
static final String DEFAULT_JMX_NAME = "org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
|
||||
private static final String DEFAULT_JMX_NAME = "org.springframework.boot:type=SpringApplicationAdmin,name=springApplicationAdmin";
|
||||
|
||||
@Autowired(required = false)
|
||||
private MBeanExporter mbeanExporter;
|
||||
@ -60,14 +60,14 @@ class SpringApplicationLifecycleAutoConfiguration {
|
||||
private Environment environment;
|
||||
|
||||
@Bean
|
||||
public SpringApplicationLifecycleRegistrar springApplicationLifecycleRegistrar()
|
||||
public SpringApplicationAdminMXBeanRegistrar springApplicationLifecycleRegistrar()
|
||||
throws MalformedObjectNameException {
|
||||
String jmxName = this.environment
|
||||
.getProperty(JMX_NAME_PROPERTY, DEFAULT_JMX_NAME);
|
||||
if (this.mbeanExporter != null) { // Make sure to not register that MBean twice
|
||||
this.mbeanExporter.addExcludedBean(jmxName);
|
||||
}
|
||||
return new SpringApplicationLifecycleRegistrar(jmxName);
|
||||
return new SpringApplicationAdminMXBeanRegistrar(jmxName);
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingIni
|
||||
|
||||
# Auto Configure
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.MessageSourceAutoConfiguration,\
|
||||
@ -11,7 +12,6 @@ org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.cloud.CloudAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.context.SpringApplicationLifecycleAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
|
||||
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.autoconfigure.context;
|
||||
package org.springframework.boot.autoconfigure.admin;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
@ -37,13 +37,17 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringApplicationLifecycleAutoConfiguration}.
|
||||
* Tests for {@link SpringApplicationAdminJmxAutoConfiguration}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class SpringApplicationLifecycleAutoConfigurationTests {
|
||||
|
||||
public static final String ENABLE_LIFECYCLE_PROP = "spring.context.lifecycle.enabled=true";
|
||||
private static final String ENABLE_LIFECYCLE_PROP = "spring.context.lifecycle.enabled=true";
|
||||
|
||||
private static final String JMX_NAME_PROPERTY = "spring.application.admin.jmx-name";
|
||||
|
||||
private static final String DEFAULT_JMX_NAME = "org.springframework.boot:type=SpringApplicationAdmin,name=springApplicationAdmin";
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
@ -83,8 +87,7 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
|
||||
@Test
|
||||
public void registerWithCustomJmxName() throws InstanceNotFoundException {
|
||||
String customJmxName = "org.acme:name=FooBar";
|
||||
System.setProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY,
|
||||
customJmxName);
|
||||
System.setProperty(JMX_NAME_PROPERTY, customJmxName);
|
||||
try {
|
||||
load(ENABLE_LIFECYCLE_PROP);
|
||||
try {
|
||||
@ -97,12 +100,12 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
|
||||
this.mBeanServer.getObjectInstance(createDefaultObjectName());
|
||||
}
|
||||
finally {
|
||||
System.clearProperty(SpringApplicationLifecycleAutoConfiguration.JMX_NAME_PROPERTY);
|
||||
System.clearProperty(JMX_NAME_PROPERTY);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectName createDefaultObjectName() {
|
||||
return createObjectName(SpringApplicationLifecycleAutoConfiguration.DEFAULT_JMX_NAME);
|
||||
return createObjectName(DEFAULT_JMX_NAME);
|
||||
}
|
||||
|
||||
private ObjectName createObjectName(String jmxName) {
|
||||
@ -118,7 +121,7 @@ public class SpringApplicationLifecycleAutoConfigurationTests {
|
||||
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
|
||||
EnvironmentTestUtils.addEnvironment(applicationContext, environment);
|
||||
applicationContext.register(JmxAutoConfiguration.class,
|
||||
SpringApplicationLifecycleAutoConfiguration.class);
|
||||
SpringApplicationAdminJmxAutoConfiguration.class);
|
||||
applicationContext.refresh();
|
||||
this.context = applicationContext;
|
||||
}
|
@ -31,8 +31,8 @@ public class SampleApplication {
|
||||
public static void main(String[] args) throws Exception {
|
||||
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
|
||||
ObjectName name = new ObjectName(
|
||||
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
|
||||
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
|
||||
"org.springframework.boot:type=SpringApplicationAdmin,name=springApplicationAdmin");
|
||||
SpringApplicationAdmin mbean = new SpringApplicationAdmin();
|
||||
mbs.registerMBean(mbean, name);
|
||||
|
||||
// Flag the app as ready
|
||||
@ -51,7 +51,7 @@ public class SampleApplication {
|
||||
}
|
||||
}
|
||||
|
||||
public interface SpringApplicationLifecycleMXBean {
|
||||
public interface SpringApplicationAdminMXBean {
|
||||
|
||||
boolean isReady();
|
||||
|
||||
@ -59,7 +59,7 @@ public class SampleApplication {
|
||||
|
||||
}
|
||||
|
||||
static class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean {
|
||||
static class SpringApplicationAdmin implements SpringApplicationAdminMXBean {
|
||||
|
||||
private boolean ready;
|
||||
|
||||
|
@ -30,8 +30,8 @@ public class SampleApplication {
|
||||
public static void main(String[] args) throws Exception {
|
||||
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
|
||||
ObjectName name = new ObjectName(
|
||||
"org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle");
|
||||
SpringApplicationLifecycle mbean = new SpringApplicationLifecycle();
|
||||
"org.springframework.boot:type=SpringApplicationAdmin,name=springApplicationAdmin");
|
||||
SpringApplicationAdmin mbean = new SpringApplicationAdmin();
|
||||
mbs.registerMBean(mbean, name);
|
||||
|
||||
// Flag the app as ready
|
||||
@ -50,7 +50,7 @@ public class SampleApplication {
|
||||
}
|
||||
}
|
||||
|
||||
public interface SpringApplicationLifecycleMXBean {
|
||||
public interface SpringApplicationAdminMXBean {
|
||||
|
||||
boolean isReady();
|
||||
|
||||
@ -58,7 +58,7 @@ public class SampleApplication {
|
||||
|
||||
}
|
||||
|
||||
static class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean {
|
||||
static class SpringApplicationAdmin implements SpringApplicationAdminMXBean {
|
||||
|
||||
private boolean ready;
|
||||
|
||||
|
@ -32,39 +32,25 @@ import javax.management.remote.JMXServiceURL;
|
||||
import org.apache.maven.plugin.MojoExecutionException;
|
||||
|
||||
/**
|
||||
* A JMX client for the {@code SpringApplicationLifecycle} mbean. Permits to obtain
|
||||
* information about the lifecycle of a given Spring application.
|
||||
* A JMX client for the {@code SpringApplicationAdmin} MBean. Permits to obtain
|
||||
* information about a given Spring application.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class SpringApplicationLifecycleClient {
|
||||
class SpringApplicationAdminClient {
|
||||
|
||||
// Note: see SpringApplicationLifecycleAutoConfiguration
|
||||
static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Lifecycle,name=springApplicationLifecycle";
|
||||
// Note: see SpringApplicationAdminJmxAutoConfiguration
|
||||
static final String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=SpringApplicationAdmin,name=springApplicationAdmin";
|
||||
|
||||
private final MBeanServerConnection connection;
|
||||
|
||||
private final ObjectName objectName;
|
||||
|
||||
public SpringApplicationLifecycleClient(MBeanServerConnection connection,
|
||||
String jmxName) {
|
||||
public SpringApplicationAdminClient(MBeanServerConnection connection, String jmxName) {
|
||||
this.connection = connection;
|
||||
this.objectName = toObjectName(jmxName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connector for an {@link javax.management.MBeanServer} exposed on the
|
||||
* current machine and the current port. Security should be disabled.
|
||||
* @param port the port on which the mbean server is exposed
|
||||
* @return a connection
|
||||
* @throws IOException if the connection to that server failed
|
||||
*/
|
||||
public static JMXConnector createLocalJmxConnector(int port) throws IOException {
|
||||
String url = "service:jmx:rmi:///jndi/rmi://127.0.0.1:" + port + "/jmxrmi";
|
||||
JMXServiceURL serviceUrl = new JMXServiceURL(url);
|
||||
return JMXConnectorFactory.connect(serviceUrl, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the spring application managed by this instance is ready. Returns
|
||||
* {@code false} if the mbean is not yet deployed so this method should be repeatedly
|
||||
@ -123,4 +109,17 @@ class SpringApplicationLifecycleClient {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a connector for an {@link javax.management.MBeanServer} exposed on the
|
||||
* current machine and the current port. Security should be disabled.
|
||||
* @param port the port on which the mbean server is exposed
|
||||
* @return a connection
|
||||
* @throws IOException if the connection to that server failed
|
||||
*/
|
||||
public static JMXConnector connect(int port) throws IOException {
|
||||
String url = "service:jmx:rmi:///jndi/rmi://127.0.0.1:" + port + "/jmxrmi";
|
||||
JMXServiceURL serviceUrl = new JMXServiceURL(url);
|
||||
return JMXConnectorFactory.connect(serviceUrl, null);
|
||||
}
|
||||
|
||||
}
|
@ -59,7 +59,7 @@ public class StartMojo extends AbstractRunMojo {
|
||||
* spring application.
|
||||
*/
|
||||
@Parameter
|
||||
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
|
||||
private String jmxName = SpringApplicationAdminClient.DEFAULT_OBJECT_NAME;
|
||||
|
||||
/**
|
||||
* The port to use to expose the platform MBeanServer if the application needs to be
|
||||
@ -151,11 +151,11 @@ public class StartMojo extends AbstractRunMojo {
|
||||
|
||||
private void waitForSpringApplication(long wait, int maxAttempts)
|
||||
throws MojoExecutionException {
|
||||
SpringApplicationLifecycleClient helper = new SpringApplicationLifecycleClient(
|
||||
SpringApplicationAdminClient client = new SpringApplicationAdminClient(
|
||||
ManagementFactory.getPlatformMBeanServer(), this.jmxName);
|
||||
getLog().debug("Waiting for spring application to start...");
|
||||
for (int i = 0; i < maxAttempts; i++) {
|
||||
if (helper.isReady()) {
|
||||
if (client.isReady()) {
|
||||
return;
|
||||
}
|
||||
String message = "Spring application is not ready yet, waiting " + wait
|
||||
@ -227,7 +227,7 @@ public class StartMojo extends AbstractRunMojo {
|
||||
|
||||
private void doWaitForSpringApplication(MBeanServerConnection connection)
|
||||
throws IOException, MojoExecutionException, MojoFailureException {
|
||||
final SpringApplicationLifecycleClient client = new SpringApplicationLifecycleClient(
|
||||
final SpringApplicationAdminClient client = new SpringApplicationAdminClient(
|
||||
connection, this.jmxName);
|
||||
try {
|
||||
execute(this.wait, this.maxAttempts, new Callable<Boolean>() {
|
||||
@ -294,8 +294,7 @@ public class StartMojo extends AbstractRunMojo {
|
||||
@Override
|
||||
public JMXConnector call() throws Exception {
|
||||
try {
|
||||
return SpringApplicationLifecycleClient
|
||||
.createLocalJmxConnector(this.port);
|
||||
return SpringApplicationAdminClient.connect(this.port);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
if (hasCauseWithType(ex, ConnectException.class)) {
|
||||
|
@ -53,7 +53,7 @@ public class StopMojo extends AbstractMojo {
|
||||
* application.
|
||||
*/
|
||||
@Parameter
|
||||
private String jmxName = SpringApplicationLifecycleClient.DEFAULT_OBJECT_NAME;
|
||||
private String jmxName = SpringApplicationAdminClient.DEFAULT_OBJECT_NAME;
|
||||
|
||||
/**
|
||||
* The port to use to lookup the platform MBeanServer if the application has been
|
||||
@ -81,8 +81,7 @@ public class StopMojo extends AbstractMojo {
|
||||
|
||||
private void stopForkedProcess() throws IOException, MojoFailureException,
|
||||
MojoExecutionException {
|
||||
JMXConnector connector = SpringApplicationLifecycleClient
|
||||
.createLocalJmxConnector(this.jmxPort);
|
||||
JMXConnector connector = SpringApplicationAdminClient.connect(this.jmxPort);
|
||||
try {
|
||||
MBeanServerConnection connection = connector.getMBeanServerConnection();
|
||||
doStop(connection);
|
||||
@ -99,7 +98,7 @@ public class StopMojo extends AbstractMojo {
|
||||
private void doStop(MBeanServerConnection connection) throws IOException,
|
||||
MojoExecutionException {
|
||||
try {
|
||||
new SpringApplicationLifecycleClient(connection, this.jmxName).stop();
|
||||
new SpringApplicationAdminClient(connection, this.jmxName).stop();
|
||||
}
|
||||
catch (InstanceNotFoundException ex) {
|
||||
throw new MojoExecutionException(
|
||||
|
@ -14,16 +14,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context;
|
||||
package org.springframework.boot.admin;
|
||||
|
||||
/**
|
||||
* A simple MBean contract to control the lifecycle of a {@code SpringApplication} via
|
||||
* JMX. Intended for internal use only.
|
||||
* A MBean contract to control and monitor a running {@code SpringApplication} via JMX.
|
||||
* Intended for internal use only.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public interface SpringApplicationLifecycleMXBean {
|
||||
public interface SpringApplicationAdminMXBean {
|
||||
|
||||
/**
|
||||
* Specify if the application has fully started and is now ready.
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.context;
|
||||
package org.springframework.boot.admin;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
|
||||
@ -35,13 +35,13 @@ import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Register a {@link SpringApplicationLifecycleMXBean} implementation to the platform
|
||||
* Register a {@link SpringApplicationAdminMXBean} implementation to the platform
|
||||
* {@link MBeanServer}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 1.3.0
|
||||
*/
|
||||
public class SpringApplicationLifecycleRegistrar implements ApplicationContextAware,
|
||||
public class SpringApplicationAdminMXBeanRegistrar implements ApplicationContextAware,
|
||||
InitializingBean, DisposableBean, ApplicationListener<ApplicationReadyEvent> {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(SpringApplicationLifecycle.class);
|
||||
@ -52,7 +52,7 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw
|
||||
|
||||
private boolean ready = false;
|
||||
|
||||
public SpringApplicationLifecycleRegistrar(String name)
|
||||
public SpringApplicationAdminMXBeanRegistrar(String name)
|
||||
throws MalformedObjectNameException {
|
||||
this.objectName = new ObjectName(name);
|
||||
}
|
||||
@ -85,17 +85,17 @@ public class SpringApplicationLifecycleRegistrar implements ApplicationContextAw
|
||||
ManagementFactory.getPlatformMBeanServer().unregisterMBean(this.objectName);
|
||||
}
|
||||
|
||||
private class SpringApplicationLifecycle implements SpringApplicationLifecycleMXBean {
|
||||
private class SpringApplicationLifecycle implements SpringApplicationAdminMXBean {
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return SpringApplicationLifecycleRegistrar.this.ready;
|
||||
return SpringApplicationAdminMXBeanRegistrar.this.ready;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
logger.info("Application shutdown requested.");
|
||||
SpringApplicationLifecycleRegistrar.this.applicationContext.close();
|
||||
SpringApplicationAdminMXBeanRegistrar.this.applicationContext.close();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright 2012-2015 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Administration support for Spring Boot applications.
|
||||
*/
|
||||
package org.springframework.boot.admin;
|
||||
|
@ -29,6 +29,7 @@ import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.admin.SpringApplicationAdminMXBeanRegistrar;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@ -39,7 +40,7 @@ import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Tests for {@link SpringApplicationLifecycleRegistrar}.
|
||||
* Tests for {@link SpringApplicationAdminMXBeanRegistrar}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
@ -133,9 +134,9 @@ public class SpringApplicationLifecycleRegistrarTests {
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public SpringApplicationLifecycleRegistrar springApplicationLifecycle()
|
||||
public SpringApplicationAdminMXBeanRegistrar springApplicationLifecycle()
|
||||
throws MalformedObjectNameException {
|
||||
return new SpringApplicationLifecycleRegistrar(OBJECT_NAME);
|
||||
return new SpringApplicationAdminMXBeanRegistrar(OBJECT_NAME);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user