Fix new Sonar smells
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2013-2019 the original author or authors.
|
||||
* Copyright 2013-2020 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.http.inbound;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
@@ -88,8 +89,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
|
||||
public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<ContextRefreshedEvent>, DestructionAwareBeanPostProcessor {
|
||||
|
||||
private static final Method HANDLE_REQUEST_METHOD = ReflectionUtils.findMethod(HttpRequestHandler.class,
|
||||
"handleRequest", HttpServletRequest.class, HttpServletResponse.class);
|
||||
private static final Method HANDLE_REQUEST_METHOD =
|
||||
ReflectionUtils.findMethod(HttpRequestHandler.class, "handleRequest", HttpServletRequest.class,
|
||||
HttpServletResponse.class);
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
@@ -170,21 +172,13 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin
|
||||
CrossOrigin crossOrigin = ((BaseHttpInboundEndpoint) handler).getCrossOrigin();
|
||||
if (crossOrigin != null) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
for (String origin : crossOrigin.getOrigin()) {
|
||||
config.addAllowedOrigin(origin);
|
||||
}
|
||||
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
|
||||
config.addAllowedMethod(requestMethod.name());
|
||||
}
|
||||
for (String header : crossOrigin.getAllowedHeaders()) {
|
||||
config.addAllowedHeader(header);
|
||||
}
|
||||
for (String header : crossOrigin.getExposedHeaders()) {
|
||||
config.addExposedHeader(header);
|
||||
}
|
||||
if (crossOrigin.getAllowCredentials() != null) {
|
||||
config.setAllowCredentials(crossOrigin.getAllowCredentials());
|
||||
}
|
||||
config.setAllowedOrigins(Arrays.asList(crossOrigin.getOrigin()));
|
||||
config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
|
||||
config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
|
||||
config.setAllowCredentials(crossOrigin.getAllowCredentials());
|
||||
if (crossOrigin.getMaxAge() != -1) {
|
||||
config.setMaxAge(crossOrigin.getMaxAge());
|
||||
}
|
||||
@@ -194,7 +188,9 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
|
||||
for (NameValueExpression<String> headerExpression : mappingInfo.getHeadersCondition().getExpressions()) {
|
||||
for (NameValueExpression<String> headerExpression :
|
||||
mappingInfo.getHeadersCondition().getExpressions()) {
|
||||
|
||||
if (!headerExpression.isNegated()) {
|
||||
config.addAllowedHeader(headerExpression.getName());
|
||||
}
|
||||
@@ -218,7 +214,7 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin
|
||||
return null;
|
||||
}
|
||||
|
||||
Map<String, Object> requestMappingAttributes = new HashMap<String, Object>();
|
||||
Map<String, Object> requestMappingAttributes = new HashMap<>();
|
||||
requestMappingAttributes.put("name", endpoint.getComponentName());
|
||||
requestMappingAttributes.put("value", requestMapping.getPathPatterns());
|
||||
requestMappingAttributes.put("path", requestMapping.getPathPatterns());
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2019 the original author or authors.
|
||||
* Copyright 2015-2020 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.
|
||||
@@ -18,17 +18,15 @@ package org.springframework.integration.http.inbound;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
@@ -39,8 +37,7 @@ import org.springframework.web.servlet.HandlerInterceptor;
|
||||
*
|
||||
* @since 4.2
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@SpringJUnitConfig
|
||||
@DirtiesContext
|
||||
public class CrossOriginTests {
|
||||
|
||||
@@ -49,7 +46,7 @@ public class CrossOriginTests {
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
this.request = new MockHttpServletRequest();
|
||||
this.request.setMethod("GET");
|
||||
@@ -91,7 +88,7 @@ public class CrossOriginTests {
|
||||
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] { "*" });
|
||||
assertThat(config.getAllowCredentials()).isTrue();
|
||||
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] { "*" });
|
||||
assertThat(config.getExposedHeaders()).isNull();
|
||||
assertThat(config.getExposedHeaders()).isEmpty();
|
||||
assertThat(config.getMaxAge()).isEqualTo(new Long(1800));
|
||||
}
|
||||
|
||||
@@ -122,7 +119,7 @@ public class CrossOriginTests {
|
||||
assertThat(config.getAllowedOrigins().toArray()).isEqualTo(new String[] { "*" });
|
||||
assertThat(config.getAllowCredentials()).isTrue();
|
||||
assertThat(config.getAllowedHeaders().toArray()).isEqualTo(new String[] { "*" });
|
||||
assertThat(config.getExposedHeaders()).isNull();
|
||||
assertThat(config.getExposedHeaders()).isEmpty();
|
||||
assertThat(config.getMaxAge()).isEqualTo(new Long(1800));
|
||||
}
|
||||
|
||||
|
||||
@@ -95,29 +95,7 @@ public class MockIntegrationContext implements BeanFactoryAware {
|
||||
this.beans.entrySet()
|
||||
.stream()
|
||||
.filter(e -> names == null || names.contains(e.getKey()))
|
||||
.forEach(e -> {
|
||||
Object endpoint = this.beanFactory.getBean(e.getKey());
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
|
||||
SmartLifecycle lifecycle = null;
|
||||
if (endpoint instanceof SmartLifecycle && ((SmartLifecycle) endpoint).isRunning()) {
|
||||
lifecycle = (SmartLifecycle) endpoint;
|
||||
lifecycle.stop();
|
||||
}
|
||||
if (endpoint instanceof SourcePollingChannelAdapter) {
|
||||
directFieldAccessor.setPropertyValue("source", e.getValue());
|
||||
}
|
||||
else if (endpoint instanceof ReactiveStreamsConsumer) {
|
||||
Tuple2<?, ?> value = (Tuple2<?, ?>) e.getValue();
|
||||
directFieldAccessor.setPropertyValue(HANDLER, value.getT1());
|
||||
directFieldAccessor.setPropertyValue("subscriber", value.getT2());
|
||||
}
|
||||
else if (endpoint instanceof IntegrationConsumer) {
|
||||
directFieldAccessor.setPropertyValue(HANDLER, e.getValue());
|
||||
}
|
||||
if (lifecycle != null && lifecycle.isAutoStartup()) {
|
||||
lifecycle.start();
|
||||
}
|
||||
});
|
||||
.forEach(e -> resetBean(this.beanFactory.getBean(e.getKey()), e.getValue()));
|
||||
|
||||
if (!ObjectUtils.isEmpty(beanNames)) {
|
||||
for (String name : beanNames) {
|
||||
@@ -129,6 +107,29 @@ public class MockIntegrationContext implements BeanFactoryAware {
|
||||
}
|
||||
}
|
||||
|
||||
private void resetBean(Object endpoint, Object handler) {
|
||||
DirectFieldAccessor directFieldAccessor = new DirectFieldAccessor(endpoint);
|
||||
SmartLifecycle lifecycle = null;
|
||||
if (endpoint instanceof SmartLifecycle && ((SmartLifecycle) endpoint).isRunning()) {
|
||||
lifecycle = (SmartLifecycle) endpoint;
|
||||
lifecycle.stop();
|
||||
}
|
||||
if (endpoint instanceof SourcePollingChannelAdapter) {
|
||||
directFieldAccessor.setPropertyValue("source", handler);
|
||||
}
|
||||
else if (endpoint instanceof ReactiveStreamsConsumer) {
|
||||
Tuple2<?, ?> value = (Tuple2<?, ?>) handler;
|
||||
directFieldAccessor.setPropertyValue(HANDLER, value.getT1());
|
||||
directFieldAccessor.setPropertyValue("subscriber", value.getT2());
|
||||
}
|
||||
else if (endpoint instanceof IntegrationConsumer) {
|
||||
directFieldAccessor.setPropertyValue(HANDLER, handler);
|
||||
}
|
||||
if (lifecycle != null && lifecycle.isAutoStartup()) {
|
||||
lifecycle.start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace the real {@link MessageSource} in the {@link SourcePollingChannelAdapter} bean
|
||||
* with provided {@link MessageSource} instance.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2019 the original author or authors.
|
||||
* Copyright 2017-2020 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.
|
||||
@@ -17,6 +17,7 @@
|
||||
package org.springframework.integration.webflux.inbound;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
@@ -83,8 +84,8 @@ import org.springframework.web.server.WebHandler;
|
||||
public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping
|
||||
implements ApplicationListener<ContextRefreshedEvent>, DestructionAwareBeanPostProcessor {
|
||||
|
||||
private static final Method HANDLER_METHOD = ReflectionUtils.findMethod(WebHandler.class,
|
||||
"handle", ServerWebExchange.class);
|
||||
private static final Method HANDLER_METHOD =
|
||||
ReflectionUtils.findMethod(WebHandler.class, "handle", ServerWebExchange.class);
|
||||
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
@@ -98,7 +99,6 @@ public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappi
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
|
||||
if (isHandler(bean.getClass())) {
|
||||
unregisterMapping(getMappingForEndpoint((WebFluxInboundEndpoint) bean));
|
||||
@@ -148,21 +148,13 @@ public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappi
|
||||
CrossOrigin crossOrigin = ((BaseHttpInboundEndpoint) handler).getCrossOrigin();
|
||||
if (crossOrigin != null) {
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
for (String origin : crossOrigin.getOrigin()) {
|
||||
config.addAllowedOrigin(origin);
|
||||
}
|
||||
for (RequestMethod requestMethod : crossOrigin.getMethod()) {
|
||||
config.addAllowedMethod(requestMethod.name());
|
||||
}
|
||||
for (String header : crossOrigin.getAllowedHeaders()) {
|
||||
config.addAllowedHeader(header);
|
||||
}
|
||||
for (String header : crossOrigin.getExposedHeaders()) {
|
||||
config.addExposedHeader(header);
|
||||
}
|
||||
if (crossOrigin.getAllowCredentials() != null) {
|
||||
config.setAllowCredentials(crossOrigin.getAllowCredentials());
|
||||
}
|
||||
config.setAllowedOrigins(Arrays.asList(crossOrigin.getOrigin()));
|
||||
config.setAllowedHeaders(Arrays.asList(crossOrigin.getAllowedHeaders()));
|
||||
config.setExposedHeaders(Arrays.asList(crossOrigin.getExposedHeaders()));
|
||||
config.setAllowCredentials(crossOrigin.getAllowCredentials());
|
||||
if (crossOrigin.getMaxAge() != -1) {
|
||||
config.setMaxAge(crossOrigin.getMaxAge());
|
||||
}
|
||||
@@ -172,7 +164,9 @@ public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappi
|
||||
}
|
||||
}
|
||||
if (CollectionUtils.isEmpty(config.getAllowedHeaders())) {
|
||||
for (NameValueExpression<String> headerExpression : mappingInfo.getHeadersCondition().getExpressions()) {
|
||||
for (NameValueExpression<String> headerExpression :
|
||||
mappingInfo.getHeadersCondition().getExpressions()) {
|
||||
|
||||
if (!headerExpression.isNegated()) {
|
||||
config.addAllowedHeader(headerExpression.getName());
|
||||
}
|
||||
|
||||
@@ -31,11 +31,13 @@ import org.springframework.ws.transport.WebServiceMessageSender;
|
||||
* The spec for a {@link MarshallingWebServiceOutboundGateway}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
*/
|
||||
public class MarshallingWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
MarshallingWsOutboundGatewaySpec, MarshallingWebServiceOutboundGateway> {
|
||||
public class MarshallingWsOutboundGatewaySpec extends
|
||||
BaseWsOutboundGatewaySpec<MarshallingWsOutboundGatewaySpec, MarshallingWebServiceOutboundGateway> {
|
||||
|
||||
protected MarshallingWsOutboundGatewaySpec(WebServiceTemplate template) {
|
||||
this.template = template;
|
||||
@@ -56,12 +58,13 @@ public class MarshallingWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
* {@link WebServiceTemplate} is not provided.
|
||||
*
|
||||
*/
|
||||
public static class MarshallingWsOutboundGatewayNoTemplateSpec extends BaseWsOutboundGatewaySpec<
|
||||
MarshallingWsOutboundGatewayNoTemplateSpec, MarshallingWebServiceOutboundGateway> {
|
||||
public static class MarshallingWsOutboundGatewayNoTemplateSpec
|
||||
extends BaseWsOutboundGatewaySpec<MarshallingWsOutboundGatewayNoTemplateSpec,
|
||||
MarshallingWebServiceOutboundGateway> {
|
||||
|
||||
protected Marshaller gatewayMarshaller;
|
||||
protected Marshaller gatewayMarshaller; // NOSONAR
|
||||
|
||||
protected Unmarshaller gatewayUnmarshaller;
|
||||
protected Unmarshaller gatewayUnmarshaller; // NOSONAR
|
||||
|
||||
/**
|
||||
* Configure the marshaller to use.
|
||||
|
||||
@@ -18,9 +18,7 @@ package org.springframework.integration.ws.dsl;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.springframework.integration.ws.MarshallingWebServiceOutboundGateway;
|
||||
import org.springframework.integration.ws.SimpleWebServiceOutboundGateway;
|
||||
import org.springframework.ws.WebServiceMessage;
|
||||
import org.springframework.ws.WebServiceMessageFactory;
|
||||
import org.springframework.ws.client.core.FaultMessageResolver;
|
||||
import org.springframework.ws.client.core.SourceExtractor;
|
||||
@@ -32,11 +30,13 @@ import org.springframework.ws.transport.WebServiceMessageSender;
|
||||
* The spec for a {@link SimpleWebServiceOutboundGateway}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
*/
|
||||
public class SimpleWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
SimpleWsOutboundGatewaySpec, SimpleWebServiceOutboundGateway> {
|
||||
public class SimpleWsOutboundGatewaySpec
|
||||
extends BaseWsOutboundGatewaySpec<SimpleWsOutboundGatewaySpec, SimpleWebServiceOutboundGateway> {
|
||||
|
||||
protected SourceExtractor<?> sourceExtractor; // NOSONAR
|
||||
|
||||
@@ -55,11 +55,11 @@ public class SimpleWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a flag to return the whole {@link WebServiceMessage} or build the
|
||||
* {@code payload} based on {@link WebServiceMessage}
|
||||
* Specify a flag to return the whole {@link org.springframework.ws.WebServiceMessage} or build the
|
||||
* {@code payload} based on {@link org.springframework.ws.WebServiceMessage}
|
||||
* and populated headers according {@code headerMapper} configuration.
|
||||
* Defaults to extract payload.
|
||||
* @param extract build payload or return a whole {@link WebServiceMessage}
|
||||
* @param extract build payload or return a whole {@link org.springframework.ws.WebServiceMessage}
|
||||
* @return the spec.
|
||||
*/
|
||||
public SimpleWsOutboundGatewaySpec extractPayload(boolean extract) {
|
||||
@@ -91,12 +91,12 @@ public class SimpleWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
}
|
||||
|
||||
/**
|
||||
* Spec for a {@link MarshallingWebServiceOutboundGateway} where an external
|
||||
* Spec for a {@link SimpleWebServiceOutboundGateway} where an external
|
||||
* {@link WebServiceTemplate} is not provided.
|
||||
*
|
||||
*/
|
||||
public static class SimpleWsOutboundGatewayNoTemplateSpec extends BaseWsOutboundGatewaySpec<
|
||||
SimpleWsOutboundGatewayNoTemplateSpec, SimpleWebServiceOutboundGateway> {
|
||||
public static class SimpleWsOutboundGatewayNoTemplateSpec
|
||||
extends BaseWsOutboundGatewaySpec<SimpleWsOutboundGatewayNoTemplateSpec, SimpleWebServiceOutboundGateway> {
|
||||
|
||||
protected SourceExtractor<?> sourceExtractor; // NOSONAR
|
||||
|
||||
@@ -154,11 +154,11 @@ public class SimpleWsOutboundGatewaySpec extends BaseWsOutboundGatewaySpec<
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a flag to return the whole {@link WebServiceMessage} or build the
|
||||
* {@code payload} based on {@link WebServiceMessage}
|
||||
* Specify a flag to return the whole {@link org.springframework.ws.WebServiceMessage} or build the
|
||||
* {@code payload} based on {@link org.springframework.ws.WebServiceMessage}
|
||||
* and populated headers according {@code headerMapper} configuration.
|
||||
* Defaults to extract payload.
|
||||
* @param extract build payload or return a whole {@link WebServiceMessage}
|
||||
* @param extract build payload or return a whole {@link org.springframework.ws.WebServiceMessage}
|
||||
* @return the spec.
|
||||
*/
|
||||
public SimpleWsOutboundGatewayNoTemplateSpec extractPayload(boolean extract) {
|
||||
|
||||
@@ -16,16 +16,15 @@
|
||||
|
||||
package org.springframework.integration.ws.dsl;
|
||||
|
||||
import org.springframework.integration.ws.dsl.MarshallingWsOutboundGatewaySpec.MarshallingWsOutboundGatewayNoTemplateSpec;
|
||||
import org.springframework.integration.ws.dsl.SimpleWsOutboundGatewaySpec.SimpleWsOutboundGatewayNoTemplateSpec;
|
||||
import org.springframework.oxm.Marshaller;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
import org.springframework.ws.client.core.WebServiceTemplate;
|
||||
|
||||
/**
|
||||
* Factory class for web service components.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.3
|
||||
*
|
||||
*/
|
||||
@@ -41,7 +40,7 @@ public final class Ws {
|
||||
|
||||
/**
|
||||
* Create an instance with the provided {@link Marshaller} (which must also implement
|
||||
* {@link Unmarshaller}).
|
||||
* {@link org.springframework.oxm.Unmarshaller}).
|
||||
* @param marshaller the marshaller.
|
||||
* @return the spec.
|
||||
*/
|
||||
@@ -63,8 +62,9 @@ public final class Ws {
|
||||
* Create an instance with a default {@link WebServiceTemplate}.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static MarshallingWsOutboundGatewayNoTemplateSpec marshallingOutboundGateway() {
|
||||
return new MarshallingWsOutboundGatewayNoTemplateSpec();
|
||||
public static MarshallingWsOutboundGatewaySpec.MarshallingWsOutboundGatewayNoTemplateSpec
|
||||
marshallingOutboundGateway() {
|
||||
return new MarshallingWsOutboundGatewaySpec.MarshallingWsOutboundGatewayNoTemplateSpec();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,8 +80,8 @@ public final class Ws {
|
||||
* Create an instance.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SimpleWsOutboundGatewayNoTemplateSpec simpleOutboundGateway() {
|
||||
return new SimpleWsOutboundGatewayNoTemplateSpec();
|
||||
public static SimpleWsOutboundGatewaySpec.SimpleWsOutboundGatewayNoTemplateSpec simpleOutboundGateway() {
|
||||
return new SimpleWsOutboundGatewaySpec.SimpleWsOutboundGatewayNoTemplateSpec();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user