Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in / Register
Toggle navigation
S
spring-boot
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
DEMO
spring-boot
Commits
a38d2456
Commit
a38d2456
authored
Feb 24, 2017
by
Stephane Nicoll
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch '1.5.x'
parents
31643301
4aa99b95
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
406 additions
and
4 deletions
+406
-4
SpringValidatorAdapterWrapper.java
...boot/autoconfigure/web/SpringValidatorAdapterWrapper.java
+83
-0
WebMvcAutoConfiguration.java
...ework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
+85
-4
SpringValidatorAdapterWrapperTests.java
...autoconfigure/web/SpringValidatorAdapterWrapperTests.java
+152
-0
WebMvcAutoConfigurationTests.java
.../boot/autoconfigure/web/WebMvcAutoConfigurationTests.java
+86
-0
No files found.
spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/SpringValidatorAdapterWrapper.java
0 → 100644
View file @
a38d2456
/*
* Copyright 2012-2017 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
.
boot
.
autoconfigure
.
web
;
import
org.springframework.beans.BeansException
;
import
org.springframework.beans.factory.DisposableBean
;
import
org.springframework.beans.factory.InitializingBean
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.ApplicationContextAware
;
import
org.springframework.validation.Errors
;
import
org.springframework.validation.Validator
;
import
org.springframework.validation.beanvalidation.SpringValidatorAdapter
;
/**
* Wraps a {@link SpringValidatorAdapter} so that only the Spring's {@link Validator}
* type is exposed. This prevents such a bean to expose both the Spring and JSR-303
* validator contract at the same time.
*
* @author Stephane Nicoll
*/
class
SpringValidatorAdapterWrapper
implements
Validator
,
ApplicationContextAware
,
InitializingBean
,
DisposableBean
{
private
final
SpringValidatorAdapter
target
;
private
final
boolean
managed
;
SpringValidatorAdapterWrapper
(
SpringValidatorAdapter
target
,
boolean
managed
)
{
this
.
target
=
target
;
this
.
managed
=
managed
;
}
public
SpringValidatorAdapter
getTarget
()
{
return
this
.
target
;
}
@Override
public
boolean
supports
(
Class
<?>
clazz
)
{
return
this
.
target
.
supports
(
clazz
);
}
@Override
public
void
validate
(
Object
target
,
Errors
errors
)
{
this
.
target
.
validate
(
target
,
errors
);
}
@Override
public
void
setApplicationContext
(
ApplicationContext
applicationContext
)
throws
BeansException
{
if
(!
this
.
managed
&&
this
.
target
instanceof
ApplicationContextAware
)
{
((
ApplicationContextAware
)
this
.
target
).
setApplicationContext
(
applicationContext
);
}
}
@Override
public
void
afterPropertiesSet
()
throws
Exception
{
if
(!
this
.
managed
&&
this
.
target
instanceof
InitializingBean
)
{
((
InitializingBean
)
this
.
target
).
afterPropertiesSet
();
}
}
@Override
public
void
destroy
()
throws
Exception
{
if
(!
this
.
managed
&&
this
.
target
instanceof
DisposableBean
)
{
((
DisposableBean
)
this
.
target
).
destroy
();
}
}
}
spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration.java
View file @
a38d2456
...
...
@@ -43,11 +43,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import
org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication
;
import
org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type
;
import
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration
;
import
org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy
;
import
org.springframework.boot.context.properties.EnableConfigurationProperties
;
import
org.springframework.boot.validation.MessageInterpolatorFactory
;
import
org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter
;
import
org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter
;
import
org.springframework.boot.web.filter.OrderedRequestContextFilter
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.context.annotation.Import
;
...
...
@@ -62,9 +65,14 @@ import org.springframework.format.datetime.DateFormatter;
import
org.springframework.http.HttpHeaders
;
import
org.springframework.http.MediaType
;
import
org.springframework.http.converter.HttpMessageConverter
;
import
org.springframework.util.ClassUtils
;
import
org.springframework.util.StringUtils
;
import
org.springframework.validation.DefaultMessageCodesResolver
;
import
org.springframework.validation.MessageCodesResolver
;
import
org.springframework.validation.Validator
;
import
org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
;
import
org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean
;
import
org.springframework.validation.beanvalidation.SpringValidatorAdapter
;
import
org.springframework.web.accept.ContentNegotiationManager
;
import
org.springframework.web.bind.support.ConfigurableWebBindingInitializer
;
import
org.springframework.web.context.request.RequestContextListener
;
...
...
@@ -84,6 +92,7 @@ import org.springframework.web.servlet.config.annotation.ResourceChainRegistrati
import
org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration
;
import
org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurer
;
import
org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter
;
import
org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
;
import
org.springframework.web.servlet.handler.AbstractUrlHandlerMapping
;
...
...
@@ -119,13 +128,16 @@ import org.springframework.web.servlet.view.InternalResourceViewResolver;
WebMvcConfigurerAdapter
.
class
})
@ConditionalOnMissingBean
(
WebMvcConfigurationSupport
.
class
)
@AutoConfigureOrder
(
Ordered
.
HIGHEST_PRECEDENCE
+
10
)
@AutoConfigureAfter
(
DispatcherServletAutoConfiguration
.
class
)
@AutoConfigureAfter
({
DispatcherServletAutoConfiguration
.
class
,
ValidationAutoConfiguration
.
class
})
public
class
WebMvcAutoConfiguration
{
public
static
String
DEFAULT_PREFIX
=
""
;
public
static
String
DEFAULT_SUFFIX
=
""
;
private
static
final
String
JSR303_VALIDATOR_CLASS
=
"javax.validation.Validator"
;
@Bean
@ConditionalOnMissingBean
(
HiddenHttpMethodFilter
.
class
)
public
OrderedHiddenHttpMethodFilter
hiddenHttpMethodFilter
()
{
...
...
@@ -149,6 +161,8 @@ public class WebMvcAutoConfiguration {
private
static
final
Log
logger
=
LogFactory
.
getLog
(
WebMvcConfigurerAdapter
.
class
);
private
final
ApplicationContext
applicationContext
;
private
final
ResourceProperties
resourceProperties
;
private
final
WebMvcProperties
mvcProperties
;
...
...
@@ -157,20 +171,39 @@ public class WebMvcAutoConfiguration {
private
final
HttpMessageConverters
messageConverters
;
private
final
Validator
userDefinedValidator
;
final
ResourceHandlerRegistrationCustomizer
resourceHandlerRegistrationCustomizer
;
public
WebMvcAutoConfigurationAdapter
(
ResourceProperties
resourceProperties
,
WebMvcProperties
mvcProperties
,
ListableBeanFactory
beanFactory
,
HttpMessageConverters
messageConverters
,
public
WebMvcAutoConfigurationAdapter
(
ApplicationContext
applicationContext
,
ResourceProperties
resourceProperties
,
WebMvcProperties
mvcProperties
,
ListableBeanFactory
beanFactory
,
HttpMessageConverters
messageConverters
,
ObjectProvider
<
List
<
WebMvcConfigurer
>>
webMvcConfigurers
,
ObjectProvider
<
ResourceHandlerRegistrationCustomizer
>
resourceHandlerRegistrationCustomizerProvider
)
{
this
.
applicationContext
=
applicationContext
;
this
.
resourceProperties
=
resourceProperties
;
this
.
mvcProperties
=
mvcProperties
;
this
.
beanFactory
=
beanFactory
;
this
.
messageConverters
=
messageConverters
;
this
.
userDefinedValidator
=
findUserDefinedValidator
(
webMvcConfigurers
.
getIfAvailable
());
this
.
resourceHandlerRegistrationCustomizer
=
resourceHandlerRegistrationCustomizerProvider
.
getIfAvailable
();
}
private
static
Validator
findUserDefinedValidator
(
List
<
WebMvcConfigurer
>
webMvcConfigurers
)
{
if
(
webMvcConfigurers
!=
null
)
{
for
(
WebMvcConfigurer
webMvcConfigurer
:
webMvcConfigurers
)
{
Validator
validator
=
webMvcConfigurer
.
getValidator
();
if
(
validator
!=
null
)
{
return
validator
;
}
}
}
return
null
;
}
@Override
public
void
configureMessageConverters
(
List
<
HttpMessageConverter
<?>>
converters
)
{
converters
.
addAll
(
this
.
messageConverters
.
getConverters
());
...
...
@@ -266,6 +299,23 @@ public class WebMvcAutoConfiguration {
}
}
@Override
public
Validator
getValidator
()
{
// We want to make sure that the exposed 'mvcValidator' bean isn't going to
// expose the standard JSR-303 type
if
(
isJsr303Present
()
&&
this
.
userDefinedValidator
==
null
)
{
return
new
Jsr303ValidatorHandler
(
this
.
applicationContext
)
.
wrapJsr303Validator
();
}
return
null
;
// Keep default or user defined, if any
}
private
boolean
isJsr303Present
()
{
return
ClassUtils
.
isPresent
(
JSR303_VALIDATOR_CLASS
,
this
.
applicationContext
.
getClassLoader
());
}
private
<
T
>
Collection
<
T
>
getBeansOfType
(
Class
<
T
>
type
)
{
return
this
.
beanFactory
.
getBeansOfType
(
type
).
values
();
}
...
...
@@ -534,4 +584,35 @@ public class WebMvcAutoConfiguration {
}
static
class
Jsr303ValidatorHandler
{
private
final
ApplicationContext
applicationContext
;
Jsr303ValidatorHandler
(
ApplicationContext
applicationContext
)
{
this
.
applicationContext
=
applicationContext
;
}
public
Validator
wrapJsr303Validator
()
{
try
{
javax
.
validation
.
Validator
validator
=
this
.
applicationContext
.
getBean
(
javax
.
validation
.
Validator
.
class
);
if
(
validator
instanceof
LocalValidatorFactoryBean
)
{
return
new
SpringValidatorAdapterWrapper
(
(
LocalValidatorFactoryBean
)
validator
,
true
);
}
else
{
return
new
SpringValidatorAdapterWrapper
(
new
SpringValidatorAdapter
(
validator
),
false
);
}
}
catch
(
NoSuchBeanDefinitionException
ex
)
{
OptionalValidatorFactoryBean
factory
=
new
OptionalValidatorFactoryBean
();
MessageInterpolatorFactory
interpolatorFactory
=
new
MessageInterpolatorFactory
();
factory
.
setMessageInterpolator
(
interpolatorFactory
.
getObject
());
return
new
SpringValidatorAdapterWrapper
(
factory
,
false
);
}
}
}
}
spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/SpringValidatorAdapterWrapperTests.java
0 → 100644
View file @
a38d2456
/*
* Copyright 2012-2017 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
.
boot
.
autoconfigure
.
web
;
import
java.util.HashMap
;
import
javax.validation.constraints.Min
;
import
org.junit.After
;
import
org.junit.Test
;
import
org.springframework.context.ApplicationContext
;
import
org.springframework.context.annotation.AnnotationConfigApplicationContext
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.validation.MapBindingResult
;
import
org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
mockito
.
Matchers
.
any
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
times
;
import
static
org
.
mockito
.
Mockito
.
verify
;
/**
* Tests for {@link SpringValidatorAdapterWrapper}.
*
* @author Stephane Nicoll
*/
public
class
SpringValidatorAdapterWrapperTests
{
private
AnnotationConfigApplicationContext
context
;
@After
public
void
close
()
{
if
(
this
.
context
!=
null
)
{
this
.
context
.
close
();
}
}
@Test
public
void
wrapLocalValidatorFactoryBean
()
{
SpringValidatorAdapterWrapper
wrapper
=
load
(
LocalValidatorFactoryBeanConfig
.
class
);
assertThat
(
wrapper
.
supports
(
SampleData
.
class
)).
isTrue
();
MapBindingResult
errors
=
new
MapBindingResult
(
new
HashMap
<
String
,
Object
>(),
"test"
);
wrapper
.
validate
(
new
SampleData
(
40
),
errors
);
assertThat
(
errors
.
getErrorCount
()).
isEqualTo
(
1
);
}
@Test
public
void
wrapperInvokesCallbackOnNonManagedBean
()
{
load
(
NonManagedBeanConfig
.
class
);
LocalValidatorFactoryBean
validator
=
this
.
context
.
getBean
(
NonManagedBeanConfig
.
class
).
validator
;
verify
(
validator
,
times
(
1
)).
setApplicationContext
(
any
(
ApplicationContext
.
class
));
verify
(
validator
,
times
(
1
)).
afterPropertiesSet
();
verify
(
validator
,
times
(
0
)).
destroy
();
this
.
context
.
close
();
this
.
context
=
null
;
verify
(
validator
,
times
(
1
)).
destroy
();
}
@Test
public
void
wrapperDoesNotInvokeCallbackOnManagedBean
()
{
load
(
ManagedBeanConfig
.
class
);
LocalValidatorFactoryBean
validator
=
this
.
context
.
getBean
(
ManagedBeanConfig
.
class
).
validator
;
verify
(
validator
,
times
(
0
)).
setApplicationContext
(
any
(
ApplicationContext
.
class
));
verify
(
validator
,
times
(
0
)).
afterPropertiesSet
();
verify
(
validator
,
times
(
0
)).
destroy
();
this
.
context
.
close
();
this
.
context
=
null
;
verify
(
validator
,
times
(
0
)).
destroy
();
}
private
SpringValidatorAdapterWrapper
load
(
Class
<?>
config
)
{
AnnotationConfigApplicationContext
ctx
=
new
AnnotationConfigApplicationContext
();
ctx
.
register
(
config
);
ctx
.
refresh
();
this
.
context
=
ctx
;
return
this
.
context
.
getBean
(
SpringValidatorAdapterWrapper
.
class
);
}
@Configuration
static
class
LocalValidatorFactoryBeanConfig
{
@Bean
public
LocalValidatorFactoryBean
validator
()
{
return
new
LocalValidatorFactoryBean
();
}
@Bean
public
SpringValidatorAdapterWrapper
wrapper
()
{
return
new
SpringValidatorAdapterWrapper
(
validator
(),
true
);
}
}
@Configuration
static
class
NonManagedBeanConfig
{
private
final
LocalValidatorFactoryBean
validator
=
mock
(
LocalValidatorFactoryBean
.
class
);
@Bean
public
SpringValidatorAdapterWrapper
wrapper
()
{
return
new
SpringValidatorAdapterWrapper
(
this
.
validator
,
false
);
}
}
@Configuration
static
class
ManagedBeanConfig
{
private
final
LocalValidatorFactoryBean
validator
=
mock
(
LocalValidatorFactoryBean
.
class
);
@Bean
public
SpringValidatorAdapterWrapper
wrapper
()
{
return
new
SpringValidatorAdapterWrapper
(
this
.
validator
,
true
);
}
}
static
class
SampleData
{
@Min
(
42
)
private
int
counter
;
SampleData
(
int
counter
)
{
this
.
counter
=
counter
;
}
}
}
spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/WebMvcAutoConfigurationTests.java
View file @
a38d2456
...
...
@@ -27,6 +27,7 @@ import java.util.Map;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
javax.validation.ValidatorFactory
;
import
org.assertj.core.api.Condition
;
import
org.joda.time.DateTime
;
...
...
@@ -60,6 +61,9 @@ import org.springframework.test.web.servlet.MockMvc;
import
org.springframework.test.web.servlet.setup.MockMvcBuilders
;
import
org.springframework.util.ReflectionUtils
;
import
org.springframework.util.StringUtils
;
import
org.springframework.validation.Validator
;
import
org.springframework.validation.beanvalidation.LocalValidatorFactoryBean
;
import
org.springframework.validation.beanvalidation.SpringValidatorAdapter
;
import
org.springframework.web.accept.ContentNegotiationManager
;
import
org.springframework.web.bind.support.ConfigurableWebBindingInitializer
;
import
org.springframework.web.filter.HttpPutFormContentFilter
;
...
...
@@ -94,6 +98,7 @@ import org.springframework.web.servlet.view.AbstractView;
import
org.springframework.web.servlet.view.ContentNegotiatingViewResolver
;
import
static
org
.
assertj
.
core
.
api
.
Assertions
.
assertThat
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
request
.
MockMvcRequestBuilders
.
get
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
forwardedUrl
;
import
static
org
.
springframework
.
test
.
web
.
servlet
.
result
.
MockMvcResultMatchers
.
status
;
...
...
@@ -640,6 +645,55 @@ public class WebMvcAutoConfigurationTests {
}
}
@Test
public
void
validationNoJsr303ValidatorExposedByDefault
()
{
load
();
assertThat
(
this
.
context
.
getBeansOfType
(
ValidatorFactory
.
class
)).
isEmpty
();
assertThat
(
this
.
context
.
getBeansOfType
(
javax
.
validation
.
Validator
.
class
))
.
isEmpty
();
assertThat
(
this
.
context
.
getBeansOfType
(
Validator
.
class
)).
hasSize
(
1
);
}
@Test
public
void
validationCustomConfigurerTakesPrecedence
()
{
load
(
MvcValidator
.
class
);
assertThat
(
this
.
context
.
getBeansOfType
(
ValidatorFactory
.
class
)).
isEmpty
();
assertThat
(
this
.
context
.
getBeansOfType
(
javax
.
validation
.
Validator
.
class
))
.
isEmpty
();
assertThat
(
this
.
context
.
getBeansOfType
(
Validator
.
class
)).
hasSize
(
1
);
Validator
validator
=
this
.
context
.
getBean
(
Validator
.
class
);
assertThat
(
validator
).
isSameAs
(
this
.
context
.
getBean
(
MvcValidator
.
class
)
.
validator
);
}
@Test
public
void
validationJsr303CustomValidatorReusedAsSpringValidator
()
{
load
(
CustomValidator
.
class
);
assertThat
(
this
.
context
.
getBeansOfType
(
ValidatorFactory
.
class
)).
hasSize
(
1
);
assertThat
(
this
.
context
.
getBeansOfType
(
javax
.
validation
.
Validator
.
class
))
.
hasSize
(
1
);
assertThat
(
this
.
context
.
getBeansOfType
(
Validator
.
class
)).
hasSize
(
2
);
Validator
validator
=
this
.
context
.
getBean
(
"mvcValidator"
,
Validator
.
class
);
assertThat
(
validator
).
isInstanceOf
(
SpringValidatorAdapterWrapper
.
class
);
assertThat
(((
SpringValidatorAdapterWrapper
)
validator
).
getTarget
())
.
isSameAs
(
this
.
context
.
getBean
(
javax
.
validation
.
Validator
.
class
));
}
@Test
public
void
validationJsr303ValidatorExposedAsSpringValidator
()
{
load
(
Jsr303Validator
.
class
);
assertThat
(
this
.
context
.
getBeansOfType
(
ValidatorFactory
.
class
)).
isEmpty
();
assertThat
(
this
.
context
.
getBeansOfType
(
javax
.
validation
.
Validator
.
class
))
.
hasSize
(
1
);
assertThat
(
this
.
context
.
getBeansOfType
(
Validator
.
class
)).
hasSize
(
1
);
Validator
validator
=
this
.
context
.
getBean
(
Validator
.
class
);
assertThat
(
validator
).
isInstanceOf
(
SpringValidatorAdapterWrapper
.
class
);
SpringValidatorAdapter
target
=
((
SpringValidatorAdapterWrapper
)
validator
)
.
getTarget
();
assertThat
(
new
DirectFieldAccessor
(
target
).
getPropertyValue
(
"targetValidator"
))
.
isSameAs
(
this
.
context
.
getBean
(
javax
.
validation
.
Validator
.
class
));
}
private
void
load
(
Class
<?>
config
,
String
...
environment
)
{
this
.
context
=
new
AnnotationConfigEmbeddedWebApplicationContext
();
EnvironmentTestUtils
.
addEnvironment
(
this
.
context
,
environment
);
...
...
@@ -818,4 +872,36 @@ public class WebMvcAutoConfigurationTests {
}
@Configuration
protected
static
class
MvcValidator
extends
WebMvcConfigurerAdapter
{
private
final
Validator
validator
=
mock
(
Validator
.
class
);
@Override
public
Validator
getValidator
()
{
return
this
.
validator
;
}
}
@Configuration
static
class
Jsr303Validator
{
@Bean
public
javax
.
validation
.
Validator
jsr303Validator
()
{
return
mock
(
javax
.
validation
.
Validator
.
class
);
}
}
@Configuration
static
class
CustomValidator
{
@Bean
public
Validator
customValidator
()
{
return
new
LocalValidatorFactoryBean
();
}
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment