Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
memoriav
Memobase 2020
libraries
Mapper Service Configuration
Commits
dc4fcbdb
Commit
dc4fcbdb
authored
Nov 30, 2020
by
Jonas Waeber
Browse files
Refactor imports & package names.
parent
f38973eb
Pipeline
#18070
passed with stage
in 1 minute and 55 seconds
Changes
36
Pipelines
2
Hide whitespace changes
Inline
Side-by-side
build.gradle
View file @
dc4fcbdb
...
...
@@ -48,7 +48,7 @@ dependencies {
//compile group: 'org.elasticsearch.client', name: 'elasticsearch-rest-high-level-client', version: '7.1.0'
implementation
"org.apache.logging.log4j:log4j-api:${log4jV}"
implementation
'org.memobase:memobase-service-utilities:
0.16.0
'
implementation
'org.memobase:memobase-service-utilities:
2.0.2
'
// YAML Parser
implementation
'org.snakeyaml:snakeyaml-engine:2.1'
...
...
src/main/kotlin/builder/DigitalObject.kt
View file @
dc4fcbdb
...
...
@@ -20,13 +20,15 @@ package ch.memobase.builder
import
ch.memobase.helpers.StringHelpers
import
ch.memobase.mapping.KEYS
import
ch.memobase.rdf.NS
import
ch.memobase.rdf.RICO
import
org.apache.jena.rdf.model.Resource
import
org.memobase.rdf.NS
import
org.memobase.rdf.RICO
class
DigitalObject
(
sourceId
:
String
,
recordSetId
:
String
,
institutionId
:
String
,
count
:
Int
)
:
Instantiation
(
institutionId
)
{
class
DigitalObject
(
sourceId
:
String
,
recordSetId
:
String
,
institutionId
:
String
,
count
:
Int
)
:
Instantiation
(
institutionId
)
{
private
val
id
=
recordSetId
+
"-"
+
StringHelpers
.
normalizeId
(
sourceId
)
+
"-"
+
count
override
val
resource
:
Resource
=
model
.
createResource
(
NS
.
mbdo
+
id
)
init
{
addRdfType
(
RICO
.
Instantiation
)
resource
.
addProperty
(
RICO
.
type
,
"digitalObject"
)
...
...
src/main/kotlin/builder/Instantiation.kt
View file @
dc4fcbdb
...
...
@@ -18,7 +18,7 @@
package
ch.memobase.builder
import
org
.memobase.rdf.RICO
import
ch
.memobase.rdf.RICO
abstract
class
Instantiation
(
institutionId
:
String
)
:
RecordResource
(
institutionId
)
{
...
...
src/main/kotlin/builder/PhysicalObject.kt
View file @
dc4fcbdb
...
...
@@ -20,9 +20,9 @@ package ch.memobase.builder
import
ch.memobase.helpers.StringHelpers
import
ch.memobase.mapping.KEYS
import
ch.memobase.rdf.NS
import
ch.memobase.rdf.RICO
import
org.apache.jena.rdf.model.Resource
import
org.memobase.rdf.NS
import
org.memobase.rdf.RICO
class
PhysicalObject
(
sourceId
:
String
,
recordSetId
:
String
,
institutionId
:
String
,
count
:
Int
)
:
Instantiation
(
institutionId
)
{
private
val
id
=
recordSetId
+
"-"
+
StringHelpers
.
normalizeId
(
sourceId
)
+
"-"
+
count
...
...
src/main/kotlin/builder/Record.kt
View file @
dc4fcbdb
...
...
@@ -20,11 +20,11 @@ package ch.memobase.builder
import
ch.memobase.helpers.StringHelpers
import
ch.memobase.mapping.KEYS
import
ch.memobase.rdf.MB
import
ch.memobase.rdf.NS
import
ch.memobase.rdf.RDA
import
ch.memobase.rdf.RICO
import
org.apache.jena.rdf.model.Resource
import
org.memobase.rdf.NS
import
org.memobase.rdf.RDA
import
org.memobase.rdf.RICO
import
rdf.MB
class
Record
(
sourceId
:
String
,
...
...
src/main/kotlin/builder/RecordResource.kt
View file @
dc4fcbdb
...
...
@@ -19,19 +19,19 @@
package
ch.memobase.builder
import
ch.memobase.mapping.KEYS
import
ch.memobase.rdf.DC
import
ch.memobase.rdf.EBUCORE
import
ch.memobase.rdf.NS
import
ch.memobase.rdf.RDA
import
ch.memobase.rdf.RDF
import
ch.memobase.rdf.RICO
import
ch.memobase.rdf.SKOS
import
org.apache.jena.rdf.model.Literal
import
org.apache.jena.rdf.model.Model
import
org.apache.jena.rdf.model.ModelFactory
import
org.apache.jena.rdf.model.Property
import
org.apache.jena.rdf.model.Resource
import
org.apache.logging.log4j.LogManager
import
org.memobase.rdf.DC
import
org.memobase.rdf.EBUCORE
import
org.memobase.rdf.NS
import
org.memobase.rdf.RDA
import
org.memobase.rdf.RDF
import
org.memobase.rdf.RICO
import
org.memobase.rdf.SKOS
abstract
class
RecordResource
(
institutionId
:
String
)
:
IResource
{
private
val
log
=
LogManager
.
getLogger
(
"ResourceBuilder"
)
...
...
src/main/kotlin/builder/ResourceBuilder.kt
View file @
dc4fcbdb
...
...
@@ -19,10 +19,10 @@
package
ch.memobase.builder
import
ch.memobase.mapping.KEYS
import
ch.memobase.mapping.MapperConfiguration
import
ch.memobase.mapping.fields.ConstantField
import
ch.memobase.mapping.fields.MappedAnnotationField
import
java.io.StringWriter
import
mapping.MapperConfiguration
import
org.apache.jena.riot.RDFDataMgr
import
org.apache.jena.riot.RDFFormat
...
...
@@ -88,33 +88,33 @@ class ResourceBuilder(
fun
generatePhysicalObject
():
ResourceBuilder
{
physicalObject
=
if
(
config
.
physicalFieldMappers
.
isNotEmpty
())
{
val
physicalObject
=
PhysicalObject
(
recordId
,
recordSetId
,
institutionId
,
1
)
config
.
physicalFieldMappers
.
forEach
{
it
.
apply
(
source
,
physicalObject
)
if
(
config
.
physicalFieldMappers
.
isNotEmpty
())
{
val
physicalObject
=
PhysicalObject
(
recordId
,
recordSetId
,
institutionId
,
1
)
config
.
physicalFieldMappers
.
forEach
{
it
.
apply
(
source
,
physicalObject
)
}
record
?.
addInstantiation
(
physicalObject
)
physicalObject
.
addRecord
(
record
!!
)
physicalObject
}
else
{
null
}
record
?.
addInstantiation
(
physicalObject
)
physicalObject
.
addRecord
(
record
!!
)
physicalObject
}
else
{
null
}
return
this
}
fun
generateDigitalObject
():
ResourceBuilder
{
digitalObject
=
if
(
config
.
digitalFieldMappers
.
isNotEmpty
())
{
val
digitalObject
=
DigitalObject
(
recordId
,
recordSetId
,
institutionId
,
1
)
config
.
digitalFieldMappers
.
forEach
{
it
.
apply
(
source
,
digitalObject
)
if
(
config
.
digitalFieldMappers
.
isNotEmpty
())
{
val
digitalObject
=
DigitalObject
(
recordId
,
recordSetId
,
institutionId
,
1
)
config
.
digitalFieldMappers
.
forEach
{
it
.
apply
(
source
,
digitalObject
)
}
digitalObject
.
addRecord
(
record
!!
)
record
?.
addInstantiation
(
digitalObject
)
digitalObject
}
else
{
null
}
digitalObject
.
addRecord
(
record
!!
)
record
?.
addInstantiation
(
digitalObject
)
digitalObject
}
else
{
null
}
return
this
}
...
...
src/main/kotlin/helpers/StringHelpers.kt
View file @
dc4fcbdb
...
...
@@ -25,9 +25,9 @@ object StringHelpers {
fun
normalizeId
(
value
:
String
):
String
{
return
value
.
trim
()
.
replace
(
illegalCharacters
,
"_"
)
.
replace
(
whitespace
,
"_"
)
.
replace
(
multiUnderScore
,
"_"
)
.
trim
()
.
replace
(
illegalCharacters
,
"_"
)
.
replace
(
whitespace
,
"_"
)
.
replace
(
multiUnderScore
,
"_"
)
}
}
src/main/kotlin/mapping/KEYS.kt
View file @
dc4fcbdb
...
...
@@ -18,12 +18,12 @@
package
ch.memobase.mapping
import
org
.memobase.rdf.DC
import
org
.memobase.rdf.EBUCORE
import
org
.memobase.rdf.NS
import
org
.memobase.rdf.RDA
import
org
.memobase.rdf.RICO
import
org
.memobase.rdf.SCHEMA
import
ch
.memobase.rdf.DC
import
ch
.memobase.rdf.EBUCORE
import
ch
.memobase.rdf.NS
import
ch
.memobase.rdf.RDA
import
ch
.memobase.rdf.RICO
import
ch
.memobase.rdf.SCHEMA
object
KEYS
{
...
...
@@ -77,8 +77,8 @@ object KEYS {
const
val
relationName
=
"relationName"
val
relationTypeMap
=
mapOf
(
Pair
(
creators
,
creator
),
Pair
(
contributors
,
contributor
)
Pair
(
creators
,
creator
),
Pair
(
contributors
,
contributor
)
)
const
val
producer
=
"producers"
...
...
@@ -86,9 +86,9 @@ object KEYS {
const
val
publishedBy
=
"publishedBy"
val
agentPropertiesMap
=
mapOf
(
Pair
(
producer
,
RDA
.
hasProducer
),
Pair
(
relatedAgents
,
RICO
.
hasSubject
),
Pair
(
publishedBy
,
RICO
.
publishedBy
)
Pair
(
producer
,
RDA
.
hasProducer
),
Pair
(
relatedAgents
,
RICO
.
hasSubject
),
Pair
(
publishedBy
,
RICO
.
publishedBy
)
)
// Agent Types
...
...
@@ -97,9 +97,9 @@ object KEYS {
const
val
person
=
"person"
val
agentTypeMap
=
mapOf
(
Pair
(
agent
,
RICO
.
Agent
),
Pair
(
corporateBody
,
RICO
.
CorporateBody
),
Pair
(
person
,
RICO
.
Person
)
Pair
(
agent
,
RICO
.
Agent
),
Pair
(
corporateBody
,
RICO
.
CorporateBody
),
Pair
(
person
,
RICO
.
Person
)
)
val
allowedAgentProperties
=
listOf
(
"relationName"
,
"name"
)
...
...
@@ -125,9 +125,9 @@ object KEYS {
private
val
languageTypes
=
listOf
(
"content"
,
"caption"
)
val
ricoConceptCategoryToTypesMap
=
mapOf
(
Pair
(
titles
,
titleTypes
),
Pair
(
identifiers
,
identifierTypes
),
Pair
(
languages
,
languageTypes
)
Pair
(
titles
,
titleTypes
),
Pair
(
identifiers
,
identifierTypes
),
Pair
(
languages
,
languageTypes
)
)
// Physical Object Keys
...
...
@@ -153,30 +153,30 @@ object KEYS {
// datatype properties
val
keysToPropertyMap
=
mapOf
(
Pair
(
abstract
,
DC
.
abstract
),
Pair
(
relatedPlaces
,
DC
.
spatial
),
Pair
(
name
,
RICO
.
name
),
Pair
(
title
,
RICO
.
title
),
Pair
(
source
,
RICO
.
source
),
Pair
(
descriptiveNote
,
RICO
.
descriptiveNote
),
Pair
(
scopeAndContent
,
RICO
.
scopeAndContent
),
Pair
(
isSponsoredByMemoriav
,
RDA
.
hasSponsoringAgentOfResource
),
Pair
(
hasFindingAid
,
RDA
.
hasFindingAid
),
Pair
(
creationDate
,
DC
.
created
),
Pair
(
issuedDate
,
DC
.
issued
),
Pair
(
temporal
,
DC
.
temporal
),
Pair
(
medium
,
EBUCORE
.
hasMedium
),
Pair
(
colour
,
RDA
.
hasColourContent
),
Pair
(
physicalCharacteristics
,
RICO
.
physicalCharacteristics
),
Pair
(
duration
,
EBUCORE
.
duration
),
Pair
(
displayAspectRatio
,
EBUCORE
.
displayAspectRatio
),
Pair
(
audioTrackConfiguration
,
EBUCORE
.
audioTrackConfiguration
),
Pair
(
playbackSpeed
,
EBUCORE
.
playbackSpeed
),
Pair
(
hasStandard
,
EBUCORE
.
hasStandard
),
Pair
(
locator
,
EBUCORE
.
locator
),
Pair
(
sameAs
,
SCHEMA
.
sameAs
),
Pair
(
relation
,
DC
.
relation
),
Pair
(
conditionsOfUse
,
RICO
.
conditionsOfUse
),
Pair
(
conditionsOfAccess
,
RICO
.
conditionsOfAccess
)
Pair
(
abstract
,
DC
.
abstract
),
Pair
(
relatedPlaces
,
DC
.
spatial
),
Pair
(
name
,
RICO
.
name
),
Pair
(
title
,
RICO
.
title
),
Pair
(
source
,
RICO
.
source
),
Pair
(
descriptiveNote
,
RICO
.
descriptiveNote
),
Pair
(
scopeAndContent
,
RICO
.
scopeAndContent
),
Pair
(
isSponsoredByMemoriav
,
RDA
.
hasSponsoringAgentOfResource
),
Pair
(
hasFindingAid
,
RDA
.
hasFindingAid
),
Pair
(
creationDate
,
DC
.
created
),
Pair
(
issuedDate
,
DC
.
issued
),
Pair
(
temporal
,
DC
.
temporal
),
Pair
(
medium
,
EBUCORE
.
hasMedium
),
Pair
(
colour
,
RDA
.
hasColourContent
),
Pair
(
physicalCharacteristics
,
RICO
.
physicalCharacteristics
),
Pair
(
duration
,
EBUCORE
.
duration
),
Pair
(
displayAspectRatio
,
EBUCORE
.
displayAspectRatio
),
Pair
(
audioTrackConfiguration
,
EBUCORE
.
audioTrackConfiguration
),
Pair
(
playbackSpeed
,
EBUCORE
.
playbackSpeed
),
Pair
(
hasStandard
,
EBUCORE
.
hasStandard
),
Pair
(
locator
,
EBUCORE
.
locator
),
Pair
(
sameAs
,
SCHEMA
.
sameAs
),
Pair
(
relation
,
DC
.
relation
),
Pair
(
conditionsOfUse
,
RICO
.
conditionsOfUse
),
Pair
(
conditionsOfAccess
,
RICO
.
conditionsOfAccess
)
)
}
src/main/kotlin/mapping/MapperConfiguration.kt
View file @
dc4fcbdb
package
mapping
package
ch.memobase.
mapping
import
ch.memobase.mapping.fields.SimpleAnnotationField
import
mapping.mappers.AbstractFieldMapper
import
ch.memobase.
mapping.mappers.AbstractFieldMapper
data class
MapperConfiguration
(
val
uri
:
String
,
...
...
src/main/kotlin/mapping/MapperParsers.kt
View file @
dc4fcbdb
package
mapping
/*
* mapper service
* Copyright (C) 2020 Memoriav
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package
ch.memobase.mapping
import
ch.memobase.mapping.KEYS
import
ch.memobase.mapping.exceptions.InvalidMappingException
import
ch.memobase.mapping.fields.ConfigField
import
ch.memobase.mapping.fields.ConstantField
...
...
@@ -9,6 +25,7 @@ import ch.memobase.mapping.fields.FieldParsers
import
ch.memobase.mapping.fields.LanguageField
import
ch.memobase.mapping.fields.ListField
import
ch.memobase.mapping.fields.PrefixField
import
ch.memobase.mapping.mappers.AbstractFieldMapper
import
ch.memobase.mapping.mappers.AgentFieldMapper
import
ch.memobase.mapping.mappers.CarrierTypeMapper
import
ch.memobase.mapping.mappers.ConstantFieldMapper
...
...
@@ -22,9 +39,8 @@ import ch.memobase.mapping.mappers.RicoConceptMapper
import
ch.memobase.mapping.mappers.RuleFieldMapper
import
ch.memobase.mapping.mappers.SkosConceptFieldMapper
import
ch.memobase.mapping.mappers.TypeFieldMapper
import
ch.memobase.rdf.SKOS
import
kotlin.reflect.full.createInstance
import
mapping.mappers.AbstractFieldMapper
import
org.memobase.rdf.SKOS
@Suppress
(
"UNCHECKED_CAST"
)
object
MapperParsers
{
...
...
@@ -132,6 +148,7 @@ object MapperParsers {
key
,
it
as
Map
<
String
,
Any
>,
SKOS
.
acceptedPropertiesList
)
}
catch
(
ex
:
ClassCastException
)
{
throw
InvalidMappingException
(
"Expected a map inside of the list entry in the $key section."
)
...
...
src/main/kotlin/mapping/MappingConfigurationParser.kt
View file @
dc4fcbdb
...
...
@@ -22,10 +22,8 @@ import ch.memobase.mapping.exceptions.InvalidMappingException
import
ch.memobase.mapping.fields.DirectMapField
import
ch.memobase.mapping.fields.FieldParsers
import
ch.memobase.mapping.fields.SimpleAnnotationField
import
ch.memobase.mapping.mappers.AbstractFieldMapper
import
ch.memobase.mapping.mappers.DateFieldMapper
import
mapping.MapperConfiguration
import
mapping.MapperParsers
import
mapping.mappers.AbstractFieldMapper
import
org.apache.logging.log4j.LogManager
@Suppress
(
"UNCHECKED_CAST"
)
...
...
src/main/kotlin/mapping/YamlLoader.kt
View file @
dc4fcbdb
...
...
@@ -19,10 +19,10 @@
package
ch.memobase.mapping
import
ch.memobase.mapping.exceptions.InvalidMappingException
import
ch.memobase.settings.CustomEnvConfig
import
ch.memobase.settings.MissingSettingException
import
java.io.ByteArrayInputStream
import
java.util.Optional
import
org.memobase.settings.CustomEnvConfig
import
org.memobase.settings.MissingSettingException
import
org.snakeyaml.engine.v2.api.Load
import
org.snakeyaml.engine.v2.api.LoadSettings
import
org.snakeyaml.engine.v2.exceptions.DuplicateKeyException
...
...
src/main/kotlin/mapping/fields/ConfigField.kt
View file @
dc4fcbdb
...
...
@@ -75,7 +75,7 @@ sealed class ConfigField(val fieldType: String) {
}
data class
DirectMapField
(
val
directKey
:
String
,
val
directField
:
String
)
:
MappedAnnotationField
(
FieldTypes
.
direct
,
directKey
,
directField
)
{
MappedAnnotationField
(
FieldTypes
.
direct
,
directKey
,
directField
)
{
override
fun
toLiteral
(
value
:
String
):
Literal
{
return
literal
(
value
)
}
...
...
@@ -129,7 +129,7 @@ sealed class ConfigField(val fieldType: String) {
}
data class
LanguageField
(
val
key
:
String
,
val
fields
:
List
<
LanguagePair
>)
:
ComplexAnnotationField
(
FieldTypes
.
language
)
{
ComplexAnnotationField
(
FieldTypes
.
language
)
{
fun
toLangLiterals
(
source
:
Map
<
String
,
Any
>):
List
<
Literal
>
{
return
fields
.
flatMap
{
languagePair
->
languagePair
.
sources
.
mapNotNull
{
...
...
@@ -161,7 +161,7 @@ sealed class ConfigField(val fieldType: String) {
}
protected
fun
langLiteral
(
text
:
String
,
language
:
String
):
Literal
=
ResourceFactory
.
createLangLiteral
(
text
.
trim
(),
language
)
ResourceFactory
.
createLangLiteral
(
text
.
trim
(),
language
)
protected
fun
literal
(
text
:
String
):
Literal
=
ResourceFactory
.
createStringLiteral
(
text
.
trim
())
}
...
...
src/main/kotlin/mapping/fields/FieldParsers.kt
View file @
dc4fcbdb
...
...
@@ -106,8 +106,8 @@ object FieldParsers {
}
}
is
List
<
*
>
->
return
ListField
(
entry
.
key
,
parseFieldList
(
entry
.
key
,
value
as
List
<
Any
>)
entry
.
key
,
parseFieldList
(
entry
.
key
,
value
as
List
<
Any
>)
)
else
->
throw
InvalidMappingException
(
"Unknown structure for field mapping: $entry"
)
}
...
...
src/main/kotlin/mapping/mappers/AbstractFieldMapper.kt
View file @
dc4fcbdb
package
mapping.mappers
package
ch.memobase.
mapping.mappers
import
ch.memobase.mapping.mappers.IFieldMapper
import
com.beust.klaxon.TypeFor
@TypeFor
(
field
=
"type"
,
adapter
=
MapperAdapter
::
class
)
...
...
src/main/kotlin/mapping/mappers/AgentFieldMapper.kt
View file @
dc4fcbdb
...
...
@@ -20,7 +20,6 @@ package ch.memobase.mapping.mappers
import
ch.memobase.builder.IResource
import
ch.memobase.mapping.KEYS
import
mapping.mappers.MapperTypes
class
AgentFieldMapper
:
TypeFieldMapper
(
MapperTypes
.
agent
)
{
var
sourceKey
:
String
=
""
...
...
@@ -47,8 +46,8 @@ class AgentFieldMapper : TypeFieldMapper(MapperTypes.agent) {
!
is
AgentFieldMapper
->
false
else
->
this
.
sourceKey
==
other
.
sourceKey
&&
super
.
equals
(
other
)
// this.agentClassType == other.agentClassType
// && this.fields == other.fields && this.properties == this.properties
// this.agentClassType == other.agentClassType
// && this.fields == other.fields && this.properties == this.properties
}
}
}
src/main/kotlin/mapping/mappers/CarrierTypeMapper.kt
View file @
dc4fcbdb
...
...
@@ -25,8 +25,6 @@ import ch.memobase.mapping.fields.FieldParsers
import
ch.memobase.mapping.fields.LanguageField
import
ch.memobase.mapping.fields.ListField
import
ch.memobase.mapping.fields.MappedAnnotationField
import
mapping.mappers.AbstractFieldMapper
import
mapping.mappers.MapperTypes
class
CarrierTypeMapper
(
val
field
:
AnnotationField
)
:
AbstractFieldMapper
(
MapperTypes
.
carrierType
)
{
override
fun
apply
(
source
:
Map
<
String
,
Any
>,
subject
:
IResource
)
{
...
...
src/main/kotlin/mapping/mappers/ConstantFieldMapper.kt
View file @
dc4fcbdb
...
...
@@ -20,8 +20,6 @@ package ch.memobase.mapping.mappers
import
ch.memobase.builder.IResource
import
ch.memobase.mapping.fields.ConstantField
import
mapping.mappers.AbstractFieldMapper
import
mapping.mappers.MapperTypes
class
ConstantFieldMapper
(
val
constantField
:
ConstantField
)
:
AbstractFieldMapper
(
MapperTypes
.
constant
)
{
override
fun
apply
(
source
:
Map
<
String
,
Any
>,
subject
:
IResource
)
{
...
...
src/main/kotlin/mapping/mappers/DateFieldMapper.kt
View file @
dc4fcbdb
...
...
@@ -21,8 +21,6 @@ package ch.memobase.mapping.mappers
import
ch.memobase.builder.IResource
import
ch.memobase.mapping.fields.DirectMapField
import
ch.memobase.mapping.fields.FieldParsers
import
mapping.mappers.AbstractFieldMapper
import
mapping.mappers.MapperTypes
class
DateFieldMapper
(
val
directMapField
:
DirectMapField
)
:
AbstractFieldMapper
(
MapperTypes
.
date
)
{
override
fun
apply
(
source
:
Map
<
String
,
Any
>,
subject
:
IResource
)
{
...
...
Prev
1
2
Next
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