Skip to content
Snippets Groups Projects
Commit ee5a9e25 authored by OZGCloud's avatar OZGCloud
Browse files

Merge pull request 'OZG-6162-archive-manager-anbindung' (#820) from...

Merge pull request 'OZG-6162-archive-manager-anbindung' (#820) from OZG-6162-archive-manager-anbindung into master

Reviewed-on: https://git.ozg-sh.de/ozgcloud-app/alfa/pulls/820


Reviewed-by: default avatarOZGCloud <ozgcloud@mgm-tp.com>
parents a3aa7b84 5b597ce7
Branches
Tags
No related merge requests found
Showing
with 372 additions and 220 deletions
......@@ -144,11 +144,6 @@ pipeline {
sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS sonar:sonar'
}
}
dir('alfa-xdomea'){
withSonarQubeEnv('sonarqube-ozg-sh'){
sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS sonar:sonar'
}
}
}
catch (Exception e) {
unstable("SonarQube failed")
......
......@@ -24,11 +24,6 @@
<artifactId>alfa-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>de.ozgcloud.alfa</groupId>
<artifactId>alfa-xdomea</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
......
......@@ -16,6 +16,8 @@ grpc:
negotiationType: PLAINTEXT
zufi-manager:
negotiationType: PLAINTEXT
archive-manager:
negotiationType: PLAINTEXT
ozgcloud:
feature:
......
......@@ -66,6 +66,9 @@ grpc:
zufi-manager:
address: static://127.0.0.1:9190
negotiationType: TLS
archive-manager:
address: static://127.0.0.1:9090
negotiationType: TLS
ozgcloud:
auth:
......
......@@ -135,6 +135,10 @@
<groupId>de.ozgcloud.zufi</groupId>
<artifactId>zufi-manager-interface</artifactId>
</dependency>
<dependency>
<groupId>de.ozgcloud.archive</groupId>
<artifactId>archive-manager-interface</artifactId>
</dependency>
<!-- tools -->
<dependency>
......
......@@ -40,6 +40,8 @@ public class GrpcUtil {
public static final String ZUFI_MANAGER_GRPC_CLIENT = "zufi-manager";
public static final String ARCHIVE_MANAGER_GRPC_CLIENT = "archive-manager";
public static final String SERVICE_KEY = "GRPC_SERVICE";
public static Key<String> keyOfString(String key) {
......
package de.ozgcloud.alfa.export;
import org.springframework.stereotype.Service;
import de.ozgcloud.alfa.common.GrpcUtil;
import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceBlockingStub;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest;
import net.devh.boot.grpc.client.inject.GrpcClient;
@Service
class ExportRemoteService {
@GrpcClient(GrpcUtil.ARCHIVE_MANAGER_GRPC_CLIENT)
private ExportServiceBlockingStub exportServiceStub;
public ExportedVorgangFile exportVorgang(String vorgangId) {
var responseIterator = exportServiceStub.exportVorgang(GrpcExportVorgangRequest.newBuilder().setVorgangId(vorgangId).build());
return new StreamedExportedVorgangFile(responseIterator);
}
}
package de.ozgcloud.alfa.export;
import java.util.UUID;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
......@@ -13,27 +11,21 @@ import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBo
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@RestController
@RequestMapping(ExportVorgangController.PATH)
@RequiredArgsConstructor
public class ExportVorgangController {
static final String PATH = "/api/vorgangs"; // NOSONAR
private static final String EXPORT_FILENAME_TEMPLATE = "%s_Abgabe.Abgabe.0401.xdomea";
private final ExportService xDomeaService;
private final ExportRemoteService exportRemoteService;
@GetMapping(value = "{vorgangId}", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public ResponseEntity<StreamingResponseBody> exportToXdomea(@PathVariable String vorgangId) {
var filenameId = UUID.randomUUID().toString();
public ResponseEntity<StreamingResponseBody> exportVorgang(@PathVariable String vorgangId) {
var exportedVorgangFile = exportRemoteService.exportVorgang(vorgangId);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", buildZipFilename(filenameId)))
.header(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment; filename=%s", exportedVorgangFile.getFileName()))
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(out -> xDomeaService.writeExport(vorgangId, filenameId, out));
}
String buildZipFilename(String filenameId) {
return String.format(EXPORT_FILENAME_TEMPLATE, filenameId);
.body(exportedVorgangFile::writeToOutputStream);
}
}
......@@ -33,7 +33,7 @@ class ExportVorgangProcessor implements RepresentationModelProcessor<EntityModel
return ModelBuilder.fromModel(model)
.ifMatch(IS_VORGANG_ABGESCHLOSSEN)
.addLink(linkTo(methodOn(ExportVorgangController.class).exportToXdomea(vorgang.getId())).withRel(REL_EXPORT))
.addLink(linkTo(methodOn(ExportVorgangController.class).exportVorgang(vorgang.getId())).withRel(REL_EXPORT))
.buildModel();
}
}
package de.ozgcloud.alfa.export;
import java.io.IOException;
import java.io.OutputStream;
interface ExportedVorgangFile {
String getFileName();
void writeToOutputStream(OutputStream outputStream) throws IOException;
}
package de.ozgcloud.alfa.export;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse;
import de.ozgcloud.common.errorhandling.TechnicalException;
import lombok.Getter;
class StreamedExportedVorgangFile implements ExportedVorgangFile {
@Getter
private final String fileName;
private final Iterator<GrpcExportVorgangResponse> responseIterator;
public StreamedExportedVorgangFile(Iterator<GrpcExportVorgangResponse> responseIterator) {
this.fileName = getFileNameFrom(responseIterator);
this.responseIterator = responseIterator;
}
static String getFileNameFrom(Iterator<GrpcExportVorgangResponse> responseIterator) {
if (!responseIterator.hasNext()) {
throw new TechnicalException("Response is empty");
}
return responseIterator.next().getVorgangFile().getFileName();
}
@Override
public void writeToOutputStream(OutputStream outputStream) throws IOException {
while (responseIterator.hasNext()) {
outputStream.write(responseIterator.next().getVorgangFile().getFileContent().toByteArray());
}
}
}
package de.ozgcloud.alfa.export;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.Iterator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Spy;
import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory;
import de.ozgcloud.archive.grpc.export.ExportServiceGrpc.ExportServiceBlockingStub;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangRequest;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse;
class ExportRemoteServiceTest {
@Spy
@InjectMocks
private ExportRemoteService service;
@Mock
private ExportServiceBlockingStub exportServiceStub;
@Nested
class TestExportVorgang {
public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
private static final ArgumentMatcher<GrpcExportVorgangRequest> HAS_VORGANG_ID = request -> request.getVorgangId().equals(VorgangHeaderTestFactory.ID);
@Mock
private Iterator<GrpcExportVorgangResponse> responseIterator;
private MockedStatic<StreamedExportedVorgangFile> mockedStaticExportedFile;
@BeforeEach
void init() {
mockedStaticExportedFile = mockStatic(StreamedExportedVorgangFile.class);
mockedStaticExportedFile.when(() -> StreamedExportedVorgangFile.getFileNameFrom(responseIterator)).thenReturn(GrpcFileTestFactory.FILE_NAME);
when(exportServiceStub.exportVorgang(any(GrpcExportVorgangRequest.class))).thenReturn(responseIterator);
}
@AfterEach
void cleanup() {
mockedStaticExportedFile.close();
}
@Test
void shouldExportVorgang() {
service.exportVorgang(VORGANG_ID);
verify(exportServiceStub).exportVorgang(argThat(HAS_VORGANG_ID));
}
@Test
void shouldReturnExportedVorgangFile() {
var exportedVorgangFile = service.exportVorgang(VORGANG_ID);
assertThat(exportedVorgangFile).isInstanceOf(StreamedExportedVorgangFile.class).extracting("responseIterator").isEqualTo(responseIterator);
}
}
}
package de.ozgcloud.alfa.export;
import static org.assertj.core.api.AssertionsForClassTypes.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.io.OutputStream;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
......@@ -21,7 +19,6 @@ import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import de.ozgcloud.alfa.common.AlfaTestUtils;
import de.ozgcloud.alfa.vorgang.VorgangHeaderTestFactory;
class ExportVorgangControllerTest {
......@@ -29,9 +26,8 @@ class ExportVorgangControllerTest {
@Spy
@InjectMocks
private ExportVorgangController controller;
@Mock
private ExportService xDomeaService;
private ExportRemoteService exportRemoteService;
private MockMvc mockMvc;
......@@ -41,50 +37,42 @@ class ExportVorgangControllerTest {
}
@Nested
class TestBuildZipFilename {
class TestExportVorgang {
@Test
void shouldMatchPattern() {
var filename = controller.buildZipFilename(UUID.randomUUID().toString());
public static final String VORGANG_ID = VorgangHeaderTestFactory.ID;
public static final String EXPORTED_VORGANG_FILENAME = UUID.randomUUID().toString();
assertThat(filename).matches(AlfaTestUtils.uuidRegexWithSuffix("_Abgabe.Abgabe.0401.xdomea"));
}
}
@Nested
class TestExportToXdomea {
private static final String VORGANG_EXPORT_FILENAME = "00000000-0000-0000-0000-000000000000_Abgabe.Abgabe.0401.xml";
@Captor
private ArgumentCaptor<String> filenameIdArgumentCaptor;
@Mock
private ExportedVorgangFile exportedVorgangFile;
@BeforeEach
void init() {
doReturn(VORGANG_EXPORT_FILENAME).when(controller).buildZipFilename(anyString());
when(exportedVorgangFile.getFileName()).thenReturn(EXPORTED_VORGANG_FILENAME);
when(exportRemoteService.exportVorgang(VORGANG_ID)).thenReturn(exportedVorgangFile);
}
@Test
void shouldHaveContentDispositonHeader() throws Exception {
doRequest().andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + VORGANG_EXPORT_FILENAME));
void shouldExportVorgang() throws Exception {
doRequest();
verify(exportRemoteService).exportVorgang(VORGANG_ID);
}
@Test
void shouldCallXdomeaService() throws Exception {
doRequest();
void shouldHaveContentDispositonHeader() throws Exception {
doRequest().andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + EXPORTED_VORGANG_FILENAME));
}
verify(xDomeaService).writeExport(eq(VorgangHeaderTestFactory.ID), filenameIdArgumentCaptor.capture(), any());
assertThat(filenameIdArgumentCaptor.getValue()).matches(AlfaTestUtils.UUID_REGEX);
@Test
void shouldHaveContentTypeHeader() throws Exception {
doRequest().andExpect(header().string(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM.toString()));
}
@Test
void shouldUseUUIDAsFilenameId() throws Exception {
void shouldWriteFileToOutputStream() throws Exception {
doRequest();
verify(controller).buildZipFilename(filenameIdArgumentCaptor.capture());
assertThat(filenameIdArgumentCaptor.getValue()).matches(AlfaTestUtils.UUID_REGEX);
verify(exportedVorgangFile).writeToOutputStream(any(OutputStream.class));
}
private ResultActions doRequest() throws Exception {
......
package de.ozgcloud.alfa.export;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse;
import de.ozgcloud.archive.grpc.export.GrpcFile;
class GrpcExportVorgangResponseTestFactory {
public static final GrpcFile VORGANG_FILE_WITH_NAME = GrpcFileTestFactory.createWithName();
public static final GrpcFile VORGANG_FILE_WITH_CONTENT = GrpcFileTestFactory.createWithContent();
public static GrpcExportVorgangResponse createWithName() {
return createBuilder().setVorgangFile(VORGANG_FILE_WITH_NAME).build();
}
public static GrpcExportVorgangResponse createWithContent() {
return createBuilder().setVorgangFile(VORGANG_FILE_WITH_CONTENT).build();
}
public static GrpcExportVorgangResponse.Builder createBuilder() {
return GrpcExportVorgangResponse.newBuilder();
}
}
package de.ozgcloud.alfa.export;
import com.google.protobuf.ByteString;
import com.thedeanda.lorem.LoremIpsum;
import de.ozgcloud.archive.grpc.export.GrpcFile;
class GrpcFileTestFactory {
public static final String FILE_NAME = LoremIpsum.getInstance().getName();
public static final ByteString FILE_CONTENT = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(10));
public static GrpcFile createWithName() {
return createBuilder().setFileName(FILE_NAME).build();
}
public static GrpcFile createWithContent() {
return createBuilder().setFileContent(FILE_CONTENT).build();
}
public static GrpcFile.Builder createBuilder() {
return GrpcFile.newBuilder();
}
}
package de.ozgcloud.alfa.export;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Iterator;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import com.google.protobuf.ByteString;
import com.thedeanda.lorem.LoremIpsum;
import de.ozgcloud.archive.grpc.export.GrpcExportVorgangResponse;
import de.ozgcloud.common.errorhandling.TechnicalException;
class StreamedExportedVorgangFileTest {
@Mock
private Iterator<GrpcExportVorgangResponse> responseIterator;
@Nested
class TestContructor {
private MockedStatic<StreamedExportedVorgangFile> mockedStatic;
@BeforeEach
void init() {
mockedStatic = mockStatic(StreamedExportedVorgangFile.class);
mockedStatic.when(() -> StreamedExportedVorgangFile.getFileNameFrom(responseIterator)).thenReturn(GrpcFileTestFactory.FILE_NAME);
}
@AfterEach
void cleanup() {
mockedStatic.close();
}
@Test
void shouldGetFileNameFromResponseIterator() {
new StreamedExportedVorgangFile(responseIterator);
mockedStatic.verify(() -> StreamedExportedVorgangFile.getFileNameFrom(responseIterator));
}
@Test
void shouldSetFileName() {
var exportedVorgangFile = new StreamedExportedVorgangFile(responseIterator);
assertThat(exportedVorgangFile.getFileName()).isEqualTo(GrpcFileTestFactory.FILE_NAME);
}
}
@Nested
class TestGetFileNameFrom {
@Nested
class OnEmptyResponse {
@BeforeEach
void init() {
when(responseIterator.hasNext()).thenReturn(false);
}
@Test
void shouldThrowException() {
assertThatExceptionOfType(TechnicalException.class).isThrownBy(TestGetFileNameFrom.this::callTestedMethod);
}
}
@Nested
class OnNotEmptyResponse {
@BeforeEach
void init() {
when(responseIterator.hasNext()).thenReturn(true);
when(responseIterator.next()).thenReturn(GrpcExportVorgangResponseTestFactory.createWithName());
}
@Test
void shouldCallHasNext() {
callTestedMethod();
verify(responseIterator).hasNext();
}
@Test
void shouldCallNextOnce() {
callTestedMethod();
verify(responseIterator, times(1)).next();
}
@Test
void shouldReturnFileName() {
var fileName = callTestedMethod();
assertThat(fileName).isEqualTo(GrpcFileTestFactory.FILE_NAME);
}
}
private String callTestedMethod() {
return StreamedExportedVorgangFile.getFileNameFrom(responseIterator);
}
}
@Nested
class TestWriteToOutputStream {
public static final ByteString FILE_CONTENT_1 = GrpcFileTestFactory.FILE_CONTENT;
public static final ByteString FILE_CONTENT_2 = ByteString.copyFromUtf8(LoremIpsum.getInstance().getWords(8));
@Mock
private OutputStream outputStream;
private MockedStatic<StreamedExportedVorgangFile> mockedStatic;
@BeforeEach
void init() {
mockedStatic = mockStatic(StreamedExportedVorgangFile.class);
mockedStatic.when(() -> StreamedExportedVorgangFile.getFileNameFrom(responseIterator)).thenReturn(GrpcFileTestFactory.FILE_NAME);
when(responseIterator.hasNext())
.thenReturn(true)
.thenReturn(true)
.thenReturn(false);
when(responseIterator.next())
.thenReturn(GrpcExportVorgangResponseTestFactory.createWithContent())
.thenReturn(GrpcExportVorgangResponseTestFactory.createBuilder().setVorgangFile(
GrpcFileTestFactory.createBuilder().setFileContent(FILE_CONTENT_2).build()
).build());
}
@AfterEach
void cleanup() {
mockedStatic.close();
}
@Test
void shouldWriteFileContentInOrder() throws IOException {
var orderVerifier = inOrder(outputStream);
new StreamedExportedVorgangFile(responseIterator).writeToOutputStream(outputStream);
orderVerifier.verify(outputStream).write(FILE_CONTENT_1.toByteArray());
orderVerifier.verify(outputStream).write(FILE_CONTENT_2.toByteArray());
}
}
}
<?xml version="1.0"?>
<!--
Copyright (C) 2023 Das Land Schleswig-Holstein vertreten durch den
Ministerpräsidenten des Landes Schleswig-Holstein
Staatskanzlei
Abteilung Digitalisierung und zentrales IT-Management der Landesregierung
Lizenziert unter der EUPL, Version 1.2 oder - sobald
diese von der Europäischen Kommission genehmigt wurden -
Folgeversionen der EUPL ("Lizenz");
Sie dürfen dieses Werk ausschließlich gemäß
dieser Lizenz nutzen.
Eine Kopie der Lizenz finden Sie hier:
https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
Sofern nicht durch anwendbare Rechtsvorschriften
gefordert oder in schriftlicher Form vereinbart, wird
die unter der Lizenz verbreitete Software "so wie sie
ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN -
ausdrücklich oder stillschweigend - verbreitet.
Die sprachspezifischen Genehmigungen und Beschränkungen
unter der Lizenz sind dem Lizenztext zu entnehmen.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.ozgcloud.alfa</groupId>
<artifactId>alfa</artifactId>
<version>2.16.0-SNAPSHOT</version>
</parent>
<artifactId>alfa-xdomea</artifactId>
<name>Alfa xdomea</name>
<description>Alfa xdomea implementation</description>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
<jaxb2-maven-plugin.version>3.1.0</jaxb2-maven-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>de.ozgcloud.alfa</groupId>
<artifactId>alfa-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-oxm</artifactId>
</dependency>
<dependency>
<groupId>de.ozgcloud.alfa</groupId>
<artifactId>alfa-service</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<version>${jaxb2-maven-plugin.version}</version>
<executions>
<execution>
<id>xjc</id>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<sources>
<source>src/main/resources/ozgcloud_XML-Schemata</source>
</sources>
<packageName>de.xoev.xdomea</packageName>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
</plugins>
<resources>
<resource>
<directory>${project.build.directory}/generated-resources/jaxb</directory>
</resource>
</resources>
</build>
</project>
package de.ozgcloud.alfa.bescheid;
import java.util.List;
import de.ozgcloud.alfa.common.file.OzgFile;
import de.xoev.xdomea.DokumentType;
import lombok.Builder;
import lombok.Getter;
import lombok.Singular;
@Builder
@Getter
public class BescheidExportData {
@Singular
private List<DokumentType> dokumentTypes;
@Singular
private List<OzgFile> files;
}
package de.ozgcloud.alfa.bescheid;
import java.util.List;
import de.ozgcloud.alfa.common.file.OzgFile;
import lombok.Builder;
@Builder
record BescheidExportInput(Bescheid bescheid, String organisationseinheitenId, List<OzgFile> files) {
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment