Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
T
trismegisto-services
Overview
Overview
Details
Activity
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
Mauro Paolo Josue Zuñiga Mallqui
trismegisto-services
Commits
0bc53c57
Commit
0bc53c57
authored
Feb 26, 2024
by
Mauro Paolo Josue Zuñiga Mallqui
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[ADD] PRUEBAS CON APACHE KAFKA
parent
14feaee5
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
173 additions
and
115 deletions
+173
-115
pom.xml
pom.xml
+23
-18
DriveApi.java
.../java/web/multitask/trismegistoservices/api/DriveApi.java
+18
-14
ExcelApi.java
.../java/web/multitask/trismegistoservices/api/ExcelApi.java
+25
-19
KafkaConsumerConfig.java
...itask/trismegistoservices/config/KafkaConsumerConfig.java
+27
-0
KafkaConsumer.java
...multitask/trismegistoservices/consumer/KafkaConsumer.java
+25
-0
ExcelService.java
.../multitask/trismegistoservices/services/ExcelService.java
+55
-64
No files found.
pom.xml
View file @
0bc53c57
...
@@ -28,33 +28,33 @@
...
@@ -28,33 +28,33 @@
<dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-jdbc
</artifactId>
<artifactId>
spring-boot-starter-jdbc
</artifactId>
<exclusions
>
<!-- <exclusions>--
>
<exclusion
>
<!-- <exclusion>--
>
<groupId>
org.springframework.boot
</groupId
>
<!-- <groupId>org.springframework.boot</groupId>--
>
<artifactId>
spring-boot-starter-logging
</artifactId
>
<!-- <artifactId>spring-boot-starter-logging</artifactId>--
>
</exclusion
>
<!-- </exclusion>--
>
</exclusions
>
<!-- </exclusions>--
>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-web
</artifactId>
<artifactId>
spring-boot-starter-web
</artifactId>
<exclusions
>
<!-- <exclusions>--
>
<exclusion
>
<!-- <exclusion>--
>
<groupId>
org.springframework.boot
</groupId
>
<!-- <groupId>org.springframework.boot</groupId>--
>
<artifactId>
spring-boot-starter-logging
</artifactId
>
<!-- <artifactId>spring-boot-starter-logging</artifactId>--
>
</exclusion
>
<!-- </exclusion>--
>
</exclusions
>
<!-- </exclusions>--
>
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<groupId>
org.springframework.boot
</groupId>
<artifactId>
spring-boot-starter-security
</artifactId>
<artifactId>
spring-boot-starter-security
</artifactId>
<version>
3.2.0
</version>
<version>
3.2.0
</version>
<exclusions
>
<!-- <exclusions>--
>
<exclusion
>
<!-- <exclusion>--
>
<groupId>
org.springframework.boot
</groupId
>
<!-- <groupId>org.springframework.boot</groupId>--
>
<artifactId>
spring-boot-starter-logging
</artifactId
>
<!-- <artifactId>spring-boot-starter-logging</artifactId>--
>
</exclusion
>
<!-- </exclusion>--
>
</exclusions>
<!-- </exclusions> -->
</dependency>
</dependency>
<dependency>
<dependency>
<groupId>
org.springframework.boot
</groupId>
<groupId>
org.springframework.boot
</groupId>
...
@@ -190,6 +190,11 @@
...
@@ -190,6 +190,11 @@
<artifactId>
log4j-to-slf4j
</artifactId>
<artifactId>
log4j-to-slf4j
</artifactId>
<version>
2.20.0
</version>
<version>
2.20.0
</version>
</dependency>
</dependency>
<dependency>
<groupId>
org.springframework.kafka
</groupId>
<artifactId>
spring-kafka
</artifactId>
<version>
2.9.0
</version>
</dependency>
</dependencies>
</dependencies>
<build>
<build>
<finalName>
trismegisto-services
</finalName>
<finalName>
trismegisto-services
</finalName>
...
...
src/main/java/web/multitask/trismegistoservices/api/DriveApi.java
View file @
0bc53c57
...
@@ -21,20 +21,24 @@ public class DriveApi {
...
@@ -21,20 +21,24 @@ public class DriveApi {
@PostMapping
(
path
=
"/public/upload"
,
consumes
=
{
MediaType
.
MULTIPART_FORM_DATA_VALUE
})
@PostMapping
(
path
=
"/public/upload"
,
consumes
=
{
MediaType
.
MULTIPART_FORM_DATA_VALUE
})
public
ResponseEntity
<?>
uploadFile
(
@ModelAttribute
DriveRequest
request
)
{
public
ResponseEntity
<?>
uploadFile
(
@ModelAttribute
DriveRequest
request
)
{
String
folder_id
=
request
.
getFolder_id
();
try
{
String
file_name
=
request
.
getFile_name
();
String
folder_id
=
request
.
getFolder_id
();
MultipartFile
file
=
request
.
getFile
();
String
file_name
=
request
.
getFile_name
();
String
responseDrive
=
driveService
.
uploadFile
(
folder_id
,
file_name
,
file
,
new
JSONObject
());
MultipartFile
file
=
request
.
getFile
();
JSONObject
response
=
new
JSONObject
();
String
responseDrive
=
driveService
.
uploadFile
(
folder_id
,
file_name
,
file
,
new
JSONObject
());
if
(
responseDrive
==
null
)
{
JSONObject
response
=
new
JSONObject
();
response
.
put
(
"status"
,
false
);
if
(
responseDrive
==
null
)
{
response
.
put
(
"message"
,
"Error al subir el archivo"
);
response
.
put
(
"status"
,
false
);
return
ResponseEntity
.
badRequest
().
body
(
response
.
toMap
());
response
.
put
(
"message"
,
"Error al subir el archivo"
);
}
else
{
return
ResponseEntity
.
badRequest
().
body
(
response
.
toMap
());
response
.
put
(
"status"
,
true
);
}
else
{
response
.
put
(
"message"
,
"Archivo subido correctamente"
);
response
.
put
(
"status"
,
true
);
response
.
put
(
"file_id"
,
responseDrive
);
response
.
put
(
"message"
,
"Archivo subido correctamente"
);
return
ResponseEntity
.
ok
(
response
.
toMap
());
response
.
put
(
"file_id"
,
responseDrive
);
return
ResponseEntity
.
ok
(
response
.
toMap
());
}
}
catch
(
Exception
e
)
{
return
ResponseEntity
.
internalServerError
().
body
(
e
.
getMessage
());
}
}
}
}
...
...
src/main/java/web/multitask/trismegistoservices/api/ExcelApi.java
View file @
0bc53c57
...
@@ -26,25 +26,30 @@ public class ExcelApi {
...
@@ -26,25 +26,30 @@ public class ExcelApi {
@PostMapping
(
"/public/generate"
)
@PostMapping
(
"/public/generate"
)
public
ResponseEntity
<?>
generateExcel
(
@RequestBody
String
json
)
throws
IOException
{
public
ResponseEntity
<?>
generateExcel
(
@RequestBody
String
json
)
throws
IOException
{
JSONObject
jsonBody
=
new
JSONObject
(
json
);
try
{
int
size
=
json
.
getBytes
().
length
;
JSONObject
jsonBody
=
new
JSONObject
(
json
);
System
.
out
.
println
(
"Size: "
+
size
);
int
size
=
json
.
getBytes
().
length
;
byte
[]
excelByte
=
excelService
.
generateExcel
(
jsonBody
,
size
);
System
.
out
.
println
(
"Size: "
+
size
);
if
(
jsonBody
.
optBoolean
(
"base64"
,
false
))
{
byte
[]
excelByte
=
excelService
.
generateExcel
(
jsonBody
,
size
);
JSONObject
response
=
new
JSONObject
();
if
(
jsonBody
.
optBoolean
(
"base64"
,
false
))
{
response
.
put
(
"file_name"
,
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
JSONObject
response
=
new
JSONObject
();
response
.
put
(
"base64"
,
commonUtils
.
byteToBase64
(
excelByte
));
response
.
put
(
"file_name"
,
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
response
.
put
(
"message"
,
"OK"
);
response
.
put
(
"base64"
,
commonUtils
.
byteToBase64
(
excelByte
));
response
.
put
(
"status"
,
true
);
response
.
put
(
"message"
,
"OK"
);
return
ResponseEntity
.
ok
().
body
(
response
.
toMap
());
response
.
put
(
"status"
,
true
);
}
else
{
return
ResponseEntity
.
ok
().
body
(
response
.
toMap
());
Resource
resource
=
excelService
.
convertByteToResource
(
excelByte
,
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
}
else
{
HttpHeaders
headers
=
new
HttpHeaders
();
Resource
resource
=
excelService
.
convertByteToResource
(
excelByte
,
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
headers
.
add
(
HttpHeaders
.
CONTENT_DISPOSITION
,
"attachment; filename="
+
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
HttpHeaders
headers
=
new
HttpHeaders
();
return
ResponseEntity
.
ok
()
headers
.
add
(
HttpHeaders
.
CONTENT_DISPOSITION
,
"attachment; filename="
+
jsonBody
.
optString
(
"file_name"
,
"no_name.xlsx"
));
.
headers
(
headers
)
headers
.
add
(
HttpHeaders
.
CONTENT_TYPE
,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
.
contentLength
(
excelByte
.
length
)
return
ResponseEntity
.
ok
()
.
body
(
resource
);
.
headers
(
headers
)
.
contentLength
(
excelByte
.
length
)
.
body
(
resource
);
}
}
catch
(
Exception
e
)
{
return
ResponseEntity
.
internalServerError
().
body
(
e
.
getMessage
());
}
}
}
}
}
}
\ No newline at end of file
src/main/java/web/multitask/trismegistoservices/config/KafkaConsumerConfig.java
0 → 100644
View file @
0bc53c57
package
web
.
multitask
.
trismegistoservices
.
config
;
import
org.apache.kafka.clients.consumer.ConsumerConfig
;
import
org.apache.kafka.common.serialization.StringDeserializer
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.kafka.annotation.EnableKafka
;
import
org.springframework.kafka.core.ConsumerFactory
;
import
org.springframework.kafka.core.DefaultKafkaConsumerFactory
;
import
java.util.HashMap
;
import
java.util.Map
;
@EnableKafka
@Configuration
public
class
KafkaConsumerConfig
{
@Bean
public
ConsumerFactory
<
String
,
String
>
consumerFactory
()
{
Map
<
String
,
Object
>
config
=
new
HashMap
<>();
config
.
put
(
ConsumerConfig
.
BOOTSTRAP_SERVERS_CONFIG
,
"localhost:9092"
);
config
.
put
(
ConsumerConfig
.
GROUP_ID_CONFIG
,
"1"
);
config
.
put
(
ConsumerConfig
.
KEY_DESERIALIZER_CLASS_CONFIG
,
StringDeserializer
.
class
);
config
.
put
(
ConsumerConfig
.
VALUE_DESERIALIZER_CLASS_CONFIG
,
StringDeserializer
.
class
);
return
new
DefaultKafkaConsumerFactory
<>(
config
);
}
}
\ No newline at end of file
src/main/java/web/multitask/trismegistoservices/consumer/KafkaConsumer.java
0 → 100644
View file @
0bc53c57
package
web
.
multitask
.
trismegistoservices
.
consumer
;
import
org.json.JSONObject
;
import
org.springframework.kafka.annotation.KafkaListener
;
import
org.springframework.kafka.support.KafkaHeaders
;
import
org.springframework.messaging.handler.annotation.Header
;
import
org.springframework.messaging.handler.annotation.Payload
;
import
org.springframework.stereotype.Component
;
import
java.util.logging.Logger
;
@Component
public
class
KafkaConsumer
{
Logger
logger
=
Logger
.
getLogger
(
KafkaConsumer
.
class
.
getName
());
@KafkaListener
(
topics
=
{
"SQLSERVER.EJBPLANILLA"
,
"schemahistory.topic"
,
"SQLSERVER.EJBPLANILLA.EJBPLANILLA.dbo.CONCODIANE1"
,
"SQLSERVER.EJBPLANILLA.EJBPLANILLA.dbo.GEN0001DOC2"
},
groupId
=
"1"
)
void
listener
(
@Payload
String
data
,
@Header
(
KafkaHeaders
.
RECEIVED_PARTITION
)
int
partition
,
@Header
(
KafkaHeaders
.
OFFSET
)
int
offset
)
{
JSONObject
json
=
new
JSONObject
(
data
);
System
.
out
.
println
(
json
);
}
}
\ No newline at end of file
src/main/java/web/multitask/trismegistoservices/services/ExcelService.java
View file @
0bc53c57
...
@@ -2,6 +2,7 @@ package web.multitask.trismegistoservices.services;
...
@@ -2,6 +2,7 @@ package web.multitask.trismegistoservices.services;
import
java.awt.Color
;
import
java.awt.Color
;
import
java.io.ByteArrayOutputStream
;
import
java.io.ByteArrayOutputStream
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.HashMap
;
import
java.util.HashMap
;
...
@@ -31,13 +32,18 @@ public class ExcelService {
...
@@ -31,13 +32,18 @@ public class ExcelService {
public
byte
[]
generateExcel
(
JSONObject
json
,
int
size
)
throws
IOException
{
public
byte
[]
generateExcel
(
JSONObject
json
,
int
size
)
throws
IOException
{
ByteArrayOutputStream
outByteStream
=
new
ByteArrayOutputStream
();
ByteArrayOutputStream
outByteStream
=
new
ByteArrayOutputStream
();
XSSFWorkbook
workbook
=
new
XSSFWorkbook
();
XSSFWorkbook
workbook
=
new
XSSFWorkbook
();
// org.apache.poi.ss.usermodel.Workbook workbook s= WorkbookFactory.create(new File(System.getProperty("tmpdir") + "/file.xlsx"));
createSheet
(
workbook
,
json
,
size
);
createSheet
(
workbook
,
json
,
size
);
try
{
try
{
workbook
.
write
(
outByteStream
);
workbook
.
write
(
outByteStream
);
workbook
.
close
();
workbook
.
close
();
outByteStream
.
close
();
outByteStream
.
flush
();
return
outByteStream
.
toByteArray
();
return
outByteStream
.
toByteArray
();
}
catch
(
Exception
e
)
{
}
catch
(
Exception
e
)
{
workbook
.
close
();
workbook
.
close
();
outByteStream
.
close
();
outByteStream
.
flush
();
System
.
out
.
println
(
e
.
getMessage
());
System
.
out
.
println
(
e
.
getMessage
());
return
null
;
return
null
;
}
}
...
@@ -53,7 +59,7 @@ public class ExcelService {
...
@@ -53,7 +59,7 @@ public class ExcelService {
}
}
}
else
{
}
else
{
JSONArray
sheetArray
=
json
.
optJSONArray
(
"sheetArray"
,
new
JSONArray
());
JSONArray
sheetArray
=
json
.
optJSONArray
(
"sheetArray"
,
new
JSONArray
());
if
(
sizeOfBytes
<
100000
)
{
//
if (sizeOfBytes < 100000) {
for
(
int
i
=
0
;
i
<
sheetArray
.
length
();
i
++)
{
for
(
int
i
=
0
;
i
<
sheetArray
.
length
();
i
++)
{
JSONObject
jsonObject
=
sheetArray
.
getJSONObject
(
i
);
JSONObject
jsonObject
=
sheetArray
.
getJSONObject
(
i
);
XSSFSheet
sheet
=
workbook
.
createSheet
(
jsonObject
.
optString
(
"sheet_name"
,
"no_name"
));
XSSFSheet
sheet
=
workbook
.
createSheet
(
jsonObject
.
optString
(
"sheet_name"
,
"no_name"
));
...
@@ -63,35 +69,20 @@ public class ExcelService {
...
@@ -63,35 +69,20 @@ public class ExcelService {
}
}
buildSheet
(
workbook
,
sheet
,
jsonObject
);
buildSheet
(
workbook
,
sheet
,
jsonObject
);
}
}
}
else
{
// } else {
XSSFSheet
[]
sheets
=
new
XSSFSheet
[
sheetArray
.
length
()];
for
(
int
i
=
0
;
i
<
sheetArray
.
length
();
i
++)
{
JSONObject
jsonObject
=
sheetArray
.
getJSONObject
(
i
);
sheets
[
i
]
=
workbook
.
createSheet
(
jsonObject
.
optString
(
"sheet_name"
,
"no_name"
));
}
IntStream
.
range
(
0
,
sheetArray
.
length
()).
parallel
().
forEach
(
i
->
{
JSONObject
jsonObject
=
sheetArray
.
getJSONObject
(
i
);
JSONArray
identifiers
=
jsonObject
.
getJSONArray
(
"identifiers"
);
for
(
int
j
=
0
;
j
<
identifiers
.
length
();
j
++)
{
sheets
[
i
].
setColumnWidth
(
j
,
16
*
512
);
}
CompletableFuture
<
Boolean
>
result
=
buildSheetAsync
(
workbook
,
sheets
[
i
],
jsonObject
);
results
.
get
().
add
(
result
);
});
// sheetArray.toList().stream().parallel().forEach(sheetObject -> {
// sheetArray.toList().stream().parallel().forEach(sheetObject -> {
// HashMap<String, Object> sheetMap = (HashMap<String, Object>) sheetObject;
// HashMap<String, Object> sheetMap = (HashMap<String, Object>) sheetObject;
// JSONObject jsonObject = new JSONObject(sheetMap);
// JSONObject jsonObject = new JSONObject(sheetMap);
// XSSFSheet sheet = workbook.createSheet(jsonObject.optString("sheet_name", "no_name"));
// JSONArray identifiers = jsonObject.getJSONArray("identifiers");
// JSONArray identifiers = jsonObject.getJSONArray("identifiers");
// for (int j = 0; j < identifiers.length(); j++) {
// for (int j = 0; j < identifiers.length(); j++) {
// sheet
s[i]
.setColumnWidth(j, 16 * 512);
// sheet.setColumnWidth(j, 16 * 512);
// }
// }
// CompletableFuture<Boolean> result =
buildSheetAsync(workbook, sheets[i]
, jsonObject);
// CompletableFuture<Boolean> result =
buildSheetAsync(workbook, sheet
, jsonObject);
// results.get().add(result);
// results.get().add(result);
// });
// });
// CompletableFuture.allOf(results.get().toArray(new CompletableFuture[0])).join();
CompletableFuture
.
allOf
(
results
.
get
().
toArray
(
new
CompletableFuture
[
0
])).
join
();
// }
}
}
}
}
}
...
@@ -136,48 +127,48 @@ public class ExcelService {
...
@@ -136,48 +127,48 @@ public class ExcelService {
}
}
@Async
//
@Async
public
CompletableFuture
<
Boolean
>
buildSheetAsync
(
XSSFWorkbook
workbook
,
XSSFSheet
sheet
,
JSONObject
json
)
{
//
public CompletableFuture<Boolean> buildSheetAsync(XSSFWorkbook workbook, XSSFSheet sheet, JSONObject json) {
//
final
XSSFCellStyle
defaultStyle
=
createCellStyle
(
workbook
,
new
JSONObject
());
//
final XSSFCellStyle defaultStyle = createCellStyle(workbook, new JSONObject());
List
<
String
>
stylesSaved
=
new
ArrayList
<>();
//
List<String> stylesSaved = new ArrayList<>();
List
<
XSSFCellStyle
>
styleObjectsSaved
=
new
ArrayList
<>();
//
List<XSSFCellStyle> styleObjectsSaved = new ArrayList<>();
//
XSSFRow
row
;
//
XSSFRow row;
XSSFCell
cell
;
//
XSSFCell cell;
XSSFCellStyle
cellStyle
;
//
XSSFCellStyle cellStyle;
//
JSONArray
identifiers
=
json
.
optJSONArray
(
"identifiers"
,
new
JSONArray
());
//
JSONArray identifiers = json.optJSONArray("identifiers", new JSONArray());
JSONArray
data
=
json
.
optJSONArray
(
"data"
,
new
JSONArray
());
//
JSONArray data = json.optJSONArray("data", new JSONArray());
JSONArray
styles
=
json
.
optJSONArray
(
"styles"
,
new
JSONArray
());
//
JSONArray styles = json.optJSONArray("styles", new JSONArray());
//
fillStylesArray
(
styles
,
workbook
,
stylesSaved
,
styleObjectsSaved
);
//
fillStylesArray(styles,workbook,stylesSaved,styleObjectsSaved);
//
for
(
int
i
=
0
;
i
<
data
.
length
();
i
++)
{
//
for (int i = 0; i < data.length(); i++) {
row
=
sheet
.
createRow
(
i
);
//
row = sheet.createRow(i);
JSONObject
row_data
=
data
.
optJSONObject
(
i
);
//
JSONObject row_data = data.optJSONObject(i);
for
(
int
j
=
0
;
j
<
identifiers
.
length
();
j
++)
{
//
for (int j = 0; j < identifiers.length(); j++) {
cell
=
row
.
createCell
(
j
);
//
cell = row.createCell(j);
JSONArray
styleArray
=
styles
.
optJSONArray
(
i
,
new
JSONArray
());
//
JSONArray styleArray = styles.optJSONArray(i, new JSONArray());
JSONObject
style
=
styleArray
.
optJSONObject
(
j
,
new
JSONObject
());
//
JSONObject style = styleArray.optJSONObject(j, new JSONObject());
if
(
style
.
isEmpty
()){
//
if(style.isEmpty()){
cell
.
setCellStyle
(
defaultStyle
);
//
cell.setCellStyle(defaultStyle);
}
else
{
//
}else{
if
(!
stylesSaved
.
contains
(
style
.
toString
())){
//
if(!stylesSaved.contains(style.toString())){
stylesSaved
.
add
(
style
.
toString
());
//
stylesSaved.add(style.toString());
cellStyle
=
createCellStyle
(
workbook
,
style
);
//
cellStyle = createCellStyle(workbook,style);
cell
.
setCellStyle
(
cellStyle
);
//
cell.setCellStyle(cellStyle);
styleObjectsSaved
.
add
(
cellStyle
);
//
styleObjectsSaved.add(cellStyle);
}
else
{
//
}else{
cell
.
setCellStyle
(
styleObjectsSaved
.
get
(
stylesSaved
.
indexOf
(
style
.
toString
())));
//
cell.setCellStyle(styleObjectsSaved.get(stylesSaved.indexOf(style.toString())));
}
//
}
}
//
}
cell
.
setCellValue
(
row_data
.
optString
(
identifiers
.
optString
(
j
,
""
)));
//
cell.setCellValue(row_data.optString(identifiers.optString(j, "")));
}
//
}
}
//
}
//
return
CompletableFuture
.
completedFuture
(
true
);
//
return CompletableFuture.completedFuture(true);
}
//
}
private
XSSFCellStyle
createCellStyle
(
XSSFWorkbook
workbook
,
JSONObject
styleJson
)
{
private
XSSFCellStyle
createCellStyle
(
XSSFWorkbook
workbook
,
JSONObject
styleJson
)
{
...
...
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