Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Genesys Backend
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
18
Issues
18
List
Boards
Labels
Service Desk
Milestones
Operations
Operations
Incidents
Packages & Registries
Packages & Registries
Container Registry
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Genesys PGR
Genesys Backend
Commits
5f111e36
Commit
5f111e36
authored
Jul 19, 2018
by
Matija Obreza
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Imported MeController from the Catalog
parent
c9618708
Changes
7
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
322 additions
and
21 deletions
+322
-21
src/main/java/org/genesys/catalog/server/controller/api/ApiExceptionHandler.java
...ys/catalog/server/controller/api/ApiExceptionHandler.java
+21
-4
src/main/java/org/genesys/catalog/server/controller/api/v0/MeController.java
...enesys/catalog/server/controller/api/v0/MeController.java
+250
-0
src/main/java/org/genesys2/server/model/impl/Crop.java
src/main/java/org/genesys2/server/model/impl/Crop.java
+21
-0
src/main/java/org/genesys2/server/service/UserService.java
src/main/java/org/genesys2/server/service/UserService.java
+4
-0
src/main/java/org/genesys2/server/service/impl/UserServiceImpl.java
...ava/org/genesys2/server/service/impl/UserServiceImpl.java
+6
-0
src/main/java/org/genesys2/server/servlet/controller/api/v0/CropsController.java
...ys2/server/servlet/controller/api/v0/CropsController.java
+4
-4
src/main/java/org/genesys2/spring/config/WebConfiguration.java
...ain/java/org/genesys2/spring/config/WebConfiguration.java
+16
-13
No files found.
src/main/java/org/genesys/catalog/server/controller/api/ApiExceptionHandler.java
View file @
5f111e36
...
...
@@ -15,15 +15,18 @@
*/
package
org.genesys.catalog.server.controller.api
;
import
org.apache.commons.logging.Log
;
import
org.apache.commons.logging.LogFactory
;
import
javax.servlet.http.HttpServletRequest
;
import
org.genesys.catalog.exceptions.InvalidApiUsageException
;
import
org.genesys.catalog.exceptions.NotFoundElement
;
import
org.slf4j.Logger
;
import
org.slf4j.LoggerFactory
;
import
org.springframework.dao.ConcurrencyFailureException
;
import
org.springframework.http.HttpStatus
;
import
org.springframework.http.converter.HttpMessageNotReadableException
;
import
org.springframework.security.access.AccessDeniedException
;
import
org.springframework.security.authentication.AuthenticationCredentialsNotFoundException
;
import
org.springframework.web.HttpRequestMethodNotSupportedException
;
import
org.springframework.web.bind.annotation.ControllerAdvice
;
import
org.springframework.web.bind.annotation.ExceptionHandler
;
import
org.springframework.web.bind.annotation.ResponseBody
;
...
...
@@ -35,11 +38,11 @@ import org.springframework.web.context.request.WebRequest;
*
* @author Matija Obreza
*/
@ControllerAdvice
(
basePackages
=
{
"org.genesys.catalog.server.controller.api
.v0
"
})
@ControllerAdvice
(
basePackages
=
{
"org.genesys.catalog.server.controller.api
"
,
"org.genesys2.server.servlet.controller.api
"
})
public
class
ApiExceptionHandler
{
/** The log. */
protected
final
Log
LOG
=
LogFactory
.
getLog
(
getClass
());
protected
final
Log
ger
LOG
=
LoggerFactory
.
getLogger
(
getClass
());
// @ResponseStatus(code = HttpStatus.NOT_FOUND)
// @ExceptionHandler(NoSuchAccessionException.class)
...
...
@@ -125,6 +128,20 @@ public class ApiExceptionHandler {
return
new
ApiError
<>(
e
);
}
/**
* Handle request method fail.
*
* @param req the req
* @param e the e
* @return the model and view
*/
@ResponseStatus
(
HttpStatus
.
METHOD_NOT_ALLOWED
)
@ExceptionHandler
(
value
=
{
HttpRequestMethodNotSupportedException
.
class
})
public
ApiError
<
Exception
>
handleRequestMethodFail
(
final
HttpServletRequest
req
,
final
HttpRequestMethodNotSupportedException
e
)
{
LOG
.
warn
(
"Request method {} not supported for URL {}"
,
e
.
getMethod
(),
req
.
getRequestURL
(),
e
);
return
new
ApiError
<>(
e
);
}
/**
* Handle server error.
*
...
...
src/main/java/org/genesys/catalog/server/controller/api/v0/MeController.java
0 → 100644
View file @
5f111e36
/*
* Copyright 2018 Global Crop Diversity Trust
*
* 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.genesys.catalog.server.controller.api.v0
;
import
java.io.IOException
;
import
java.util.UUID
;
import
org.apache.commons.lang3.StringUtils
;
import
org.genesys.blocks.security.SecurityContextUtil
;
import
org.genesys.blocks.security.UserException
;
import
org.genesys.blocks.security.lockout.AccountLockoutManager
;
import
org.genesys.catalog.model.Partner
;
import
org.genesys.catalog.model.dataset.Dataset
;
import
org.genesys.catalog.model.filters.DatasetFilter
;
import
org.genesys.catalog.model.filters.DescriptorFilter
;
import
org.genesys.catalog.model.filters.DescriptorListFilter
;
import
org.genesys.catalog.model.filters.PartnerFilter
;
import
org.genesys.catalog.model.traits.Descriptor
;
import
org.genesys.catalog.model.traits.DescriptorList
;
import
org.genesys.catalog.server.controller.api.FilteredPage
;
import
org.genesys.catalog.service.DatasetService
;
import
org.genesys.catalog.service.DescriptorListService
;
import
org.genesys.catalog.service.DescriptorService
;
import
org.genesys.catalog.service.PartnerService
;
import
org.genesys.catalog.service.ShortFilterService
;
import
org.genesys2.server.model.impl.User
;
import
org.genesys2.server.service.UserService
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.data.domain.Page
;
import
org.springframework.data.domain.PageRequest
;
import
org.springframework.data.domain.Sort
;
import
org.springframework.security.access.prepost.PreAuthorize
;
import
org.springframework.security.authentication.LockedException
;
import
org.springframework.security.crypto.password.PasswordEncoder
;
import
org.springframework.security.oauth2.provider.token.ConsumerTokenServices
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.PostMapping
;
import
org.springframework.web.bind.annotation.RequestBody
;
import
org.springframework.web.bind.annotation.RequestMapping
;
import
org.springframework.web.bind.annotation.RequestParam
;
import
org.springframework.web.bind.annotation.RestController
;
/**
* The Class MeController.
*
* @author Andrey Lugovskoy.
*/
@RestController
@RequestMapping
(
MeController
.
API_URL
)
@PreAuthorize
(
"isAuthenticated()"
)
public
class
MeController
{
/** The Constant API_URL. */
public
static
final
String
API_URL
=
"/api/v0/me"
;
@Autowired
private
PartnerService
partnerService
;
@Autowired
private
DatasetService
datasetService
;
@Autowired
private
DescriptorService
descriptorService
;
@Autowired
private
DescriptorListService
descriptorListService
;
@Autowired
private
ConsumerTokenServices
consumerTokenServices
;
@Autowired
private
PasswordEncoder
passwordEncoder
;
@Autowired
private
AccountLockoutManager
lockoutManager
;
@Autowired
private
UserService
userService
;
/** The short filter service. */
@Autowired
protected
ShortFilterService
shortFilterService
;
/**
* Removes the token.
*
* @param token the token
* @return the object
*/
@PostMapping
(
value
=
"/token"
,
consumes
=
"text/plain"
)
public
Object
removeToken
(
@RequestBody
final
String
token
)
{
consumerTokenServices
.
revokeToken
(
token
);
return
token
;
}
/**
* Gets the profile.
*
* @return the profile
*/
@GetMapping
(
value
=
"/profile"
)
public
User
getProfile
()
{
final
User
currentUser
=
SecurityContextUtil
.
getMe
();
return
userService
.
getUser
(
UUID
.
fromString
(
currentUser
.
getUuid
()));
}
/**
* Change password.
*
* @param oldPassword the old password
* @param newPassword the new password
* @return the string
* @throws UserException the user exception
*/
@PostMapping
(
value
=
"/password"
)
public
String
changePassword
(
@RequestParam
(
name
=
"old"
,
required
=
true
)
final
String
oldPassword
,
@RequestParam
(
name
=
"new"
,
required
=
true
)
final
String
newPassword
)
throws
UserException
{
final
User
currentUser
=
userService
.
getUser
(
UUID
.
fromString
(
SecurityContextUtil
.
getMe
().
getUuid
()));
if
(
currentUser
.
isAccountLocked
())
{
throw
new
LockedException
(
"Too many failed login attempts."
);
}
if
(
passwordEncoder
.
matches
(
oldPassword
,
currentUser
.
getPassword
()))
{
lockoutManager
.
handleSuccessfulLogin
(
currentUser
.
getUsername
());
// need to reload the record (different versions)
userService
.
changePassword
(
userService
.
getUser
(
UUID
.
fromString
(
currentUser
.
getUuid
())),
newPassword
);
return
"OK"
;
}
else
{
lockoutManager
.
handleFailedLogin
(
currentUser
.
getUsername
());
throw
new
UserException
(
"Your old password was entered incorrectly. Please enter it again."
);
}
}
/**
* My partners.
*
* @param page the page
* @param pageSize the page size
* @param direction the direction
* @param sort the sort
* @param partnerFilter the partner filter
* @return the page
*/
@PostMapping
(
value
=
"/partners"
)
public
Page
<
Partner
>
myPartners
(
@RequestParam
(
name
=
"p"
,
required
=
false
,
defaultValue
=
"0"
)
final
int
page
,
@RequestParam
(
name
=
"l"
,
required
=
false
,
defaultValue
=
"50"
)
final
int
pageSize
,
@RequestParam
(
name
=
"d"
,
required
=
false
,
defaultValue
=
"ASC"
)
final
Sort
.
Direction
direction
,
@RequestParam
(
name
=
"s"
,
required
=
false
,
defaultValue
=
"name"
)
final
String
[]
sort
,
@RequestBody
final
PartnerFilter
partnerFilter
)
{
return
partnerService
.
listPartnersForCurrentUser
(
partnerFilter
,
new
PageRequest
(
page
,
Integer
.
min
(
pageSize
,
100
),
direction
,
sort
));
}
/**
* My datasets.
*
* @param page the page
* @param pageSize the page size
* @param direction the direction
* @param sort the sort
* @param filterCode short filter code -- overrides filter in body
* @param filter the filter
* @return the page
* @throws IOException
*/
@PostMapping
(
value
=
"/datasets"
)
public
FilteredPage
<
Dataset
>
myDatasets
(
@RequestParam
(
name
=
"p"
,
required
=
false
,
defaultValue
=
"0"
)
final
int
page
,
@RequestParam
(
name
=
"l"
,
required
=
false
,
defaultValue
=
"50"
)
final
int
pageSize
,
@RequestParam
(
name
=
"d"
,
required
=
false
,
defaultValue
=
"DESC"
)
final
Sort
.
Direction
direction
,
@RequestParam
(
name
=
"s"
,
required
=
false
,
defaultValue
=
"lastModifiedDate"
)
final
String
[]
sort
,
@RequestParam
(
name
=
"f"
,
required
=
false
)
String
filterCode
,
@RequestBody
(
required
=
false
)
DatasetFilter
filter
)
throws
IOException
{
if
(
StringUtils
.
isNotBlank
(
filterCode
))
{
filter
=
shortFilterService
.
filterByCode
(
filterCode
,
DatasetFilter
.
class
);
}
else
{
filterCode
=
shortFilterService
.
getCode
(
filter
);
}
return
new
FilteredPage
<>(
filterCode
,
filter
,
datasetService
.
listDatasetsForCurrentUser
(
filter
,
new
PageRequest
(
page
,
Integer
.
min
(
pageSize
,
100
),
direction
,
sort
)));
}
/**
* My descriptors.
*
* @param page the page
* @param pageSize the page size
* @param direction the direction
* @param sort the sort
* @param filter the descriptor filter
* @return the page
* @throws IOException
*/
@PostMapping
(
value
=
"/descriptors"
)
public
FilteredPage
<
Descriptor
>
myDescriptors
(
@RequestParam
(
name
=
"p"
,
required
=
false
,
defaultValue
=
"0"
)
final
int
page
,
@RequestParam
(
name
=
"l"
,
required
=
false
,
defaultValue
=
"50"
)
final
int
pageSize
,
@RequestParam
(
name
=
"d"
,
required
=
false
,
defaultValue
=
"DESC"
)
final
Sort
.
Direction
direction
,
@RequestParam
(
name
=
"s"
,
required
=
false
,
defaultValue
=
"lastModifiedDate"
)
final
String
[]
sort
,
@RequestParam
(
name
=
"f"
,
required
=
false
)
String
filterCode
,
@RequestBody
(
required
=
false
)
DescriptorFilter
filter
)
throws
IOException
{
if
(
StringUtils
.
isNotBlank
(
filterCode
))
{
filter
=
shortFilterService
.
filterByCode
(
filterCode
,
DescriptorFilter
.
class
);
}
else
{
filterCode
=
shortFilterService
.
getCode
(
filter
);
}
return
new
FilteredPage
<>(
filterCode
,
filter
,
descriptorService
.
listDescriptorsForCurrentUser
(
filter
,
new
PageRequest
(
page
,
Integer
.
min
(
pageSize
,
100
),
direction
,
sort
)));
}
/**
* My descriptor lists.
*
* @param page the page
* @param pageSize the page size
* @param direction the direction
* @param sort the sort
* @param descriptorListFilter the descriptor list filter
* @return the page
* @throws IOException
*/
@PostMapping
(
value
=
"/descriptorlists"
)
public
FilteredPage
<
DescriptorList
>
myDescriptorLists
(
@RequestParam
(
name
=
"p"
,
required
=
false
,
defaultValue
=
"0"
)
final
int
page
,
@RequestParam
(
name
=
"l"
,
required
=
false
,
defaultValue
=
"50"
)
final
int
pageSize
,
@RequestParam
(
name
=
"d"
,
required
=
false
,
defaultValue
=
"DESC"
)
final
Sort
.
Direction
direction
,
@RequestParam
(
name
=
"s"
,
required
=
false
,
defaultValue
=
"lastModifiedDate"
)
final
String
[]
sort
,
@RequestParam
(
name
=
"f"
,
required
=
false
)
String
filterCode
,
@RequestBody
(
required
=
false
)
DescriptorListFilter
filter
)
throws
IOException
{
if
(
StringUtils
.
isNotBlank
(
filterCode
))
{
filter
=
shortFilterService
.
filterByCode
(
filterCode
,
DescriptorListFilter
.
class
);
}
else
{
filterCode
=
shortFilterService
.
getCode
(
filter
);
}
return
new
FilteredPage
<>(
filterCode
,
filter
,
descriptorListService
.
listDescriptorListsForCurrentUser
(
filter
,
new
PageRequest
(
page
,
Integer
.
min
(
pageSize
,
100
),
direction
,
sort
)));
}
}
src/main/java/org/genesys2/server/model/impl/Crop.java
View file @
5f111e36
...
...
@@ -110,6 +110,16 @@ public class Crop extends GlobalVersionedAuditedModel implements AclAwareModel {
public
void
setName
(
String
name
)
{
this
.
name
=
name
;
}
/// For compatibility with the Catalog
@Transient
public
String
getTitle
()
{
return
name
;
}
public
void
setTitle
(
String
title
)
{
setName
(
title
);
}
public
String
getShortName
()
{
return
shortName
;
...
...
@@ -119,6 +129,17 @@ public class Crop extends GlobalVersionedAuditedModel implements AclAwareModel {
this
.
shortName
=
shortName
;
}
/// For compatibility with the Catalog
@Transient
public
String
getCode
()
{
return
shortName
;
}
public
void
setCode
(
final
String
code
)
{
this
.
shortName
=
code
;
}
public
String
getDescription
()
{
return
description
;
}
...
...
src/main/java/org/genesys2/server/service/UserService.java
View file @
5f111e36
...
...
@@ -17,6 +17,7 @@
package
org.genesys2.server.service
;
import
java.util.List
;
import
java.util.UUID
;
import
org.genesys.blocks.security.UserException
;
import
org.genesys.blocks.security.model.BasicUser.AccountType
;
...
...
@@ -40,6 +41,8 @@ public interface UserService extends BasicUserService<UserRole, User> {
User
getUserByUuid
(
String
uuid
);
User
getUser
(
UUID
uuid
);
boolean
exists
(
String
username
);
Page
<
UserWrapper
>
listWrapped
(
int
startRow
,
int
pageSize
)
throws
UserException
;
...
...
@@ -77,4 +80,5 @@ public interface UserService extends BasicUserService<UserRole, User> {
* @throws UserException
*/
void
archiveUser
(
User
user
)
throws
UserException
;
}
src/main/java/org/genesys2/server/service/impl/UserServiceImpl.java
View file @
5f111e36
...
...
@@ -25,6 +25,7 @@ import java.util.Date;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Set
;
import
java.util.UUID
;
import
org.apache.commons.collections4.ListUtils
;
import
org.apache.commons.lang3.StringUtils
;
...
...
@@ -294,6 +295,11 @@ public class UserServiceImpl extends BasicUserServiceImpl<UserRole, User> implem
return
user
;
}
@Override
public
User
getUser
(
UUID
uuid
)
{
return
getUserByUuid
(
uuid
.
toString
());
}
@Override
public
boolean
exists
(
String
username
)
{
...
...
src/main/java/org/genesys2/server/servlet/controller/api/v0/CropsController.java
View file @
5f111e36
...
...
@@ -20,9 +20,6 @@ import java.util.List;
import
javax.xml.bind.ValidationException
;
import
net.sf.oval.ConstraintViolation
;
import
net.sf.oval.Validator
;
import
org.genesys2.server.exception.AuthorizationException
;
import
org.genesys2.server.model.genesys.Parameter
;
import
org.genesys2.server.model.impl.Crop
;
...
...
@@ -48,6 +45,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import
org.springframework.web.bind.annotation.RequestMethod
;
import
org.springframework.web.bind.annotation.ResponseBody
;
import
net.sf.oval.ConstraintViolation
;
import
net.sf.oval.Validator
;
@Controller
@PreAuthorize
(
"isAuthenticated()"
)
@RequestMapping
(
value
=
{
"/api/v0/crops"
,
"/json/v0/crops"
})
...
...
@@ -81,7 +81,7 @@ public class CropsController extends RestController {
* @return
* @throws ValidationException
*/
@RequestMapping
(
value
=
""
,
method
=
{
RequestMethod
.
PUT
,
RequestMethod
.
POST
},
produces
=
{
MediaType
.
APPLICATION_JSON_VALUE
})
@RequestMapping
(
value
=
{
""
,
"/create"
}
,
method
=
{
RequestMethod
.
PUT
,
RequestMethod
.
POST
},
produces
=
{
MediaType
.
APPLICATION_JSON_VALUE
})
public
@ResponseBody
Object
createCrop
(
@RequestBody
Crop
cropJson
)
throws
ValidationException
{
LOG
.
info
(
"Creating crop"
);
final
Validator
validator
=
new
Validator
();
...
...
src/main/java/org/genesys2/spring/config/WebConfiguration.java
View file @
5f111e36
...
...
@@ -68,8 +68,8 @@ import org.springframework.web.servlet.view.JstlView;
@Configuration
@EnableWebMvc
@EnableAspectJAutoProxy
@ComponentScan
(
basePackages
=
{
"org.genesys2.server.servlet.filter"
,
"org.genesys2.server.servlet.controller"
,
"org.genesys.catalog.serv
ice"
,
"org.genesys.catalog.server.util"
,
"org.genesys.catalog.serv
er.controller"
})
@ComponentScan
(
basePackages
=
{
"org.genesys2.server.servlet.filter"
,
"org.genesys2.server.servlet.controller"
,
"org.genesys.catalog.service"
,
"org.genesys.catalog.server.util"
,
"org.genesys.catalog.server.controller"
})
public
class
WebConfiguration
extends
WebMvcConfigurerAdapter
{
@Value
(
"${theme.defaultThemeName}"
)
...
...
@@ -170,39 +170,40 @@ public class WebConfiguration extends WebMvcConfigurerAdapter {
public
void
addCorsMappings
(
final
CorsRegistry
registry
)
{
registry
.
addMapping
(
"/api/**"
)
// .allowedOrigins(uiOrigin)
.
allowedMethods
(
"
*
"
);
.
allowedMethods
(
"
PUT"
,
"POST"
,
"GET"
,
"DELETE
"
);
// .allowedHeaders("header1", "header2", "header3")
// .exposedHeaders("header1", "header2")
// .allowCredentials(false).maxAge(3600);
registry
.
addMapping
(
"/html/**"
).
allowCredentials
(
false
).
allowedMethods
(
"GET"
,
"OPTIONS"
,
"HEAD"
).
allowedOrigins
(
baseUrl
).
maxAge
(
3600
);
}
@Bean
public
CorsProcessor
corsProcessor
()
{
return
new
SameOriginCorsProcessor
();
}
/**
* This bean post-processor sets our {@link #corsProcessor()} on all AbstractHandlerMapping beans
* This bean post-processor sets our {@link #corsProcessor()} on all
* AbstractHandlerMapping beans
*/
@Bean
public
BeanPostProcessor
useCustomCorsProcessor
()
{
return
new
BeanPostProcessor
()
{
return
new
BeanPostProcessor
()
{
@Override
public
Object
postProcessBeforeInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
if
(
bean
instanceof
AbstractHandlerMapping
)
{
((
AbstractHandlerMapping
)
bean
).
setCorsProcessor
(
corsProcessor
());
((
AbstractHandlerMapping
)
bean
).
setCorsProcessor
(
corsProcessor
());
}
return
bean
;
}
}
@Override
public
Object
postProcessAfterInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
public
Object
postProcessAfterInitialization
(
Object
bean
,
String
beanName
)
throws
BeansException
{
return
bean
;
}
};
}
public
MappingJackson2HttpMessageConverter
jacksonMessageConverter
()
{
final
MappingJackson2HttpMessageConverter
messageConverter
=
new
MappingJackson2HttpMessageConverter
();
...
...
@@ -213,8 +214,10 @@ public class WebConfiguration extends WebMvcConfigurerAdapter {
mapper
.
disable
(
SerializationFeature
.
EAGER_SERIALIZER_FETCH
);
// deserialization
mapper
.
enable
(
DeserializationFeature
.
ACCEPT_EMPTY_STRING_AS_NULL_OBJECT
);
// Never ignore stuff we don't understand
mapper
.
enable
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
);
// // Never ignore stuff we don't understand
// mapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
// ignore stuff we don't understand
mapper
.
disable
(
DeserializationFeature
.
FAIL_ON_UNKNOWN_PROPERTIES
);
// explicit json views: every fields needs to be annotated, therefore enabled
mapper
.
enable
(
MapperFeature
.
DEFAULT_VIEW_INCLUSION
);
...
...
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