Fixed and improved file export system

(cherry picked from commit 71ccd76896)
AboutPageRedesign
George Kalampokis 4 years ago committed by Diamantis Tziotzios
parent 4ee39074a3
commit c028aad615

@ -18,6 +18,7 @@ import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
import eu.eudat.logic.security.claims.ClaimedAuthorities;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.operations.DatabaseRepository;
import eu.eudat.logic.utilities.documents.helpers.FileEnvelope;
import eu.eudat.models.data.datasetprofile.DatasetProfileListingModel;
import eu.eudat.models.data.datasetwizard.DatasetsToBeFinalized;
import eu.eudat.models.data.dmp.DataManagementPlan;
@ -206,22 +207,22 @@ public class DMPs extends BaseController {
public @ResponseBody
ResponseEntity<byte[]> getPDFDocument(@PathVariable String id, @RequestHeader("Content-Type") String contentType,
@ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) throws IllegalAccessException, IOException, InstantiationException, InterruptedException {
File file = this.dataManagementPlanManager.getWordDocument(id, principal, configLoader);
String name = file.getName().substring(0, file.getName().length() - 5);
File pdffile = datasetManager.convertToPDF(file, environment, name);
FileEnvelope file = this.dataManagementPlanManager.getWordDocument(id, principal, configLoader);
String name = file.getFilename().substring(0, file.getFilename().length() - 5);
File pdffile = datasetManager.convertToPDF(file, environment);
InputStream resource = new FileInputStream(pdffile);
logger.info("Mime Type of " + file.getName() + " is " +
new MimetypesFileTypeMap().getContentType(file));
logger.info("Mime Type of " + name + " is " +
new MimetypesFileTypeMap().getContentType(file.getFile()));
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(pdffile.length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + pdffile.getName());
responseHeaders.set("Content-Disposition", "attachment;filename=" + name + ".pdf");
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
byte[] content = org.apache.poi.util.IOUtils.toByteArray(resource);
resource.close();
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
Files.deleteIfExists(pdffile.toPath());
return new ResponseEntity<>(content, responseHeaders, HttpStatus.OK);
}

@ -12,6 +12,7 @@ import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
import eu.eudat.logic.security.claims.ClaimedAuthorities;
import eu.eudat.logic.services.ApiContext;
import eu.eudat.logic.services.forms.VisibilityRuleService;
import eu.eudat.logic.utilities.documents.helpers.FileEnvelope;
import eu.eudat.models.data.datasetwizard.DataManagentPlanListingModel;
import eu.eudat.models.data.datasetwizard.DatasetWizardModel;
import eu.eudat.models.data.dmp.AssociatedProfile;
@ -20,6 +21,7 @@ import eu.eudat.models.data.listingmodels.DataManagementPlanOverviewModel;
import eu.eudat.models.data.security.Principal;
import eu.eudat.models.data.user.composite.PagedDatasetProfile;
import eu.eudat.types.ApiMessageCode;
import eu.eudat.types.Authorities;
import org.apache.poi.util.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -82,24 +84,24 @@ public class DatasetWizardController extends BaseController {
@Transactional
@RequestMapping(method = RequestMethod.GET, value = {"{id}"}, produces = "application/json")
public @ResponseBody
ResponseEntity getSingle(@PathVariable String id, @RequestHeader("Content-Type") String contentType, Principal principal) throws IllegalAccessException, IOException, InstantiationException {
ResponseEntity getSingle(@PathVariable String id, @RequestHeader("Content-Type") String contentType, @ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) throws IllegalAccessException, IOException, InstantiationException {
try {
if (contentType.equals("application/xml")) {
VisibilityRuleService visibilityRuleService = this.getApiContext().getUtilitiesService().getVisibilityRuleService();
return this.datasetManager.getDocument(id, visibilityRuleService, contentType, principal);
} else if (contentType.equals("application/msword")) {
File file = datasetManager.getWordDocumentFile(this.configLoader, id, this.getApiContext().getUtilitiesService().getVisibilityRuleService(), principal);
InputStream resource = new FileInputStream(file);
FileEnvelope file = datasetManager.getWordDocumentFile(this.configLoader, id, this.getApiContext().getUtilitiesService().getVisibilityRuleService(), principal);
InputStream resource = new FileInputStream(file.getFile());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(file.length());
responseHeaders.setContentLength(file.getFile().length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + file.getName());
responseHeaders.set("Content-Disposition", "attachment;filename=" + file.getFilename());
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
byte[] content = IOUtils.toByteArray(resource);
resource.close();
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
return new ResponseEntity<>(content,
responseHeaders,
HttpStatus.OK);
@ -151,25 +153,25 @@ public class DatasetWizardController extends BaseController {
@RequestMapping(method = RequestMethod.GET, value = {"/getPDF/{id}"})
public @ResponseBody
ResponseEntity<byte[]> getPDFDocument(@PathVariable String id, Principal principal) throws IllegalAccessException, IOException, InstantiationException, InterruptedException {
File file = datasetManager.getWordDocumentFile(this.configLoader, id, this.getApiContext().getUtilitiesService().getVisibilityRuleService(), principal);
String fileName = file.getName();
ResponseEntity<byte[]> getPDFDocument(@PathVariable String id, @ClaimedAuthorities(claims = {Authorities.ADMIN, Authorities.MANAGER, Authorities.USER, Authorities.ANONYMOUS}) Principal principal) throws IllegalAccessException, IOException, InstantiationException, InterruptedException {
FileEnvelope file = datasetManager.getWordDocumentFile(this.configLoader, id, this.getApiContext().getUtilitiesService().getVisibilityRuleService(), principal);
String fileName = file.getFilename();
if (fileName.endsWith(".docx")){
fileName = fileName.substring(0, fileName.length() - 5);
}
File pdffile = datasetManager.convertToPDF(file, environment, fileName);
File pdffile = datasetManager.convertToPDF(file, environment);
InputStream resource = new FileInputStream(pdffile);
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(pdffile.length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + pdffile.getName());
responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName + ".pdf");
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
byte[] content = IOUtils.toByteArray(resource);
resource.close();
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
Files.deleteIfExists(pdffile.toPath());
return new ResponseEntity<>(content,
responseHeaders,

@ -201,11 +201,11 @@ public class DataManagementPlanManager {
return;
}
public File getWordDocument(String id, Principal principal, ConfigLoader configLoader) throws IOException {
public FileEnvelope getWordDocument(String id, Principal principal, ConfigLoader configLoader) throws IOException {
return this.getWordDocument(id, principal, configLoader, false);
}
public File getWordDocument(String id, Principal principal, ConfigLoader configLoader, Boolean versioned) throws IOException {
public FileEnvelope getWordDocument(String id, Principal principal, ConfigLoader configLoader, Boolean versioned) throws IOException {
WordBuilder wordBuilder = new WordBuilder();
VisibilityRuleService visibilityRuleService = this.utilitiesService.getVisibilityRuleService();
DatasetWizardModel dataset = new DatasetWizardModel();
@ -358,12 +358,16 @@ public class DataManagementPlanManager {
fileName = dmpEntity.getLabel();
}
fileName = fileName.replaceAll("[^a-zA-Z0-9+ ]", "");
File exportFile = new File(this.environment.getProperty("temp.temp") + fileName + ".docx");
FileEnvelope exportEnvelope = new FileEnvelope();
exportEnvelope.setFilename(fileName + ".docx");
String uuid = UUID.randomUUID().toString();
File exportFile = new File(this.environment.getProperty("temp.temp") + uuid + ".docx");
FileOutputStream out = new FileOutputStream(exportFile);
document.write(out);
out.close();
exportEnvelope.setFile(exportFile);
return exportFile;
return exportEnvelope;
}
/*public File getPdfDocument(String id) throws InstantiationException, IllegalAccessException, InterruptedException, IOException {
@ -999,9 +1003,10 @@ public class DataManagementPlanManager {
if (!dmp.isPublic() && dmp.getUsers().stream().filter(userInfo -> userInfo.getUser().getId() == principal.getId()).collect(Collectors.toList()).size() == 0)
throw new UnauthorisedException();
List<Dataset> datasets = dmp.getDataset().stream().collect(Collectors.toList());
String fileName = dmp.getLabel();
fileName = fileName.replaceAll("[^a-zA-Z0-9+ ]", "");
File xmlFile = new File(this.environment.getProperty("temp.temp") + fileName + ".xml");
/*String fileName = dmp.getLabel();
fileName = fileName.replaceAll("[^a-zA-Z0-9+ ]", "");*/
String uuid = UUID.randomUUID().toString();
File xmlFile = new File(this.environment.getProperty("temp.temp") + uuid + ".xml");
BufferedWriter writer = new BufferedWriter(new FileWriter(xmlFile, true));
Document xmlDoc = XmlBuilder.getDocument();
Element dmpElement = xmlDoc.createElement("dmp");
@ -1122,7 +1127,7 @@ public class DataManagementPlanManager {
writer.close();
FileEnvelope fileEnvelope = new FileEnvelope();
fileEnvelope.setFile(xmlFile);
fileEnvelope.setFilename(dmp.getLabel());
fileEnvelope.setFilename(dmp.getLabel() + ".xml");
return fileEnvelope;
}
@ -1138,7 +1143,8 @@ public class DataManagementPlanManager {
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String fileName = dmp.getLabel();
fileName = fileName.replaceAll("[^a-zA-Z0-9+ ]", "");
File file = new File(this.environment.getProperty("temp.temp") + fileName + ".json");
String uuid = UUID.randomUUID().toString();
File file = new File(this.environment.getProperty("temp.temp") + uuid + ".json");
OutputStream output = new FileOutputStream(file);
try {
// mapper.writeValue(file, rdaExportModel);
@ -1153,7 +1159,7 @@ public class DataManagementPlanManager {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(file.length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + file.getName());
responseHeaders.set("Content-Disposition", "attachment;filename=" + fileName + ".json");
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
@ -1164,28 +1170,28 @@ public class DataManagementPlanManager {
}
public ResponseEntity<byte[]> getDocument(String id, String contentType, Principal principal, ConfigLoader configLoader) throws InstantiationException, IllegalAccessException, IOException {
File file;
FileEnvelope file;
switch (contentType) {
case "application/xml":
file = getXmlDocument(id, principal).getFile();
file = getXmlDocument(id, principal);
break;
case "application/msword":
file = getWordDocument(id, principal, configLoader);
break;
default:
file = getXmlDocument(id, principal).getFile();
file = getXmlDocument(id, principal);
}
InputStream resource = new FileInputStream(file);
InputStream resource = new FileInputStream(file.getFile());
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setContentLength(file.length());
responseHeaders.setContentLength(file.getFile().length());
responseHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
responseHeaders.set("Content-Disposition", "attachment;filename=" + file.getName());
responseHeaders.set("Content-Disposition", "attachment;filename=" + file.getFilename());
responseHeaders.set("Access-Control-Expose-Headers", "Content-Disposition");
responseHeaders.get("Access-Control-Expose-Headers").add("Content-Type");
byte[] content = org.apache.poi.util.IOUtils.toByteArray(resource);
resource.close();
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
return new ResponseEntity<>(content,
responseHeaders,
HttpStatus.OK);
@ -1564,15 +1570,15 @@ public class DataManagementPlanManager {
fileHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
LinkedMultiValueMap<String, Object> addFileMap = new LinkedMultiValueMap<>();
File file = getWordDocument(id.toString(), principal, configLoader);
addFileMap.add("filename", file.getName());
FileSystemResource fileSystemResource = new FileSystemResource(file);
FileEnvelope file = getWordDocument(id.toString(), principal, configLoader);
addFileMap.add("filename", file.getFilename());
FileSystemResource fileSystemResource = new FileSystemResource(file.getFile());
addFileMap.add("file", fileSystemResource);
HttpEntity<MultiValueMap<String, Object>> addFileMapRequest = new HttpEntity<>(addFileMap, fileHeaders);
String addFileUrl = links.get("files") + "?access_token=" + this.environment.getProperty("zenodo.access_token");
ResponseEntity<String> addFileResponse = restTemplate.postForEntity(addFileUrl, addFileMapRequest, String.class);
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
// Third post call to Zenodo to publish the entry and return the DOI.
String publishUrl = links.get("publish") + "?access_token=" + this.environment.getProperty("zenodo.access_token");

@ -24,6 +24,7 @@ import eu.eudat.logic.services.ApiContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
@ -49,11 +50,13 @@ public class DataManagementProfileManager {
private ApiContext apiContext;
private DatabaseRepository databaseRepository;
private Environment environment;
@Autowired
public DataManagementProfileManager(ApiContext apiContext) {
public DataManagementProfileManager(ApiContext apiContext, Environment environment) {
this.apiContext = apiContext;
this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
this.environment = environment;
}
public DataTableData<DataManagementPlanProfileListingModel> getPaged(DataManagementPlanProfileTableRequest dataManagementPlanProfileTableRequest, Principal principal) throws Exception {
@ -114,7 +117,7 @@ public class DataManagementProfileManager {
public FileEnvelope getXmlDocument(DataManagementPlanProfileListingModel dmpProfile, String label) throws InstantiationException, IllegalAccessException, IOException {
ExportXmlBuilderDmpProfile xmlBuilder = new ExportXmlBuilderDmpProfile();
File file = xmlBuilder.build(dmpProfile);
File file = xmlBuilder.build(dmpProfile, environment);
FileEnvelope fileEnvelope = new FileEnvelope();
fileEnvelope.setFile(file);
fileEnvelope.setFilename(label);

@ -404,7 +404,7 @@ public class DatasetManager {
visibilityRuleService.buildVisibilityContext(pagedDatasetProfile.getRules());
wordBuilder.build(document, pagedDatasetProfile, visibilityRuleService);
String label = datasetEntity.getLabel().replaceAll("[^a-zA-Z0-9+ ]", "");
File exportFile = new File(label + ".docx");
// File exportFile = new File(label + ".docx");
// Removes the top empty headings.
for (int i = 0; i < 6; i++) {
@ -448,19 +448,23 @@ public class DatasetManager {
// return exportFile;
}
public File getWordDocumentFile(ConfigLoader configLoader, String id, VisibilityRuleService visibilityRuleService, Principal principal) throws IOException {
public FileEnvelope getWordDocumentFile(ConfigLoader configLoader, String id, VisibilityRuleService visibilityRuleService, Principal principal) throws IOException {
eu.eudat.data.entities.Dataset datasetEntity = databaseRepository.getDatasetDao().find(UUID.fromString(id), HintedModelFactory.getHint(DatasetWizardModel.class));
if (!datasetEntity.getDmp().isPublic() && datasetEntity.getDmp().getUsers()
.stream().filter(userInfo -> userInfo.getUser().getId() == principal.getId())
.collect(Collectors.toList()).size() == 0)
throw new UnauthorisedException();
String label = datasetEntity.getLabel().replaceAll("[^a-zA-Z0-9+ ]", "");
File exportFile = new File(this.environment.getProperty("temp.temp") + label + ".docx");
FileEnvelope exportEnvelope = new FileEnvelope();
exportEnvelope.setFilename(label + ".docx");
String uuid = UUID.randomUUID().toString();
File exportFile = new File(this.environment.getProperty("temp.temp") + uuid + ".docx");
XWPFDocument document = getWordDocument(configLoader, datasetEntity, visibilityRuleService);
FileOutputStream out = new FileOutputStream(exportFile);
document.write(out);
out.close();
return exportFile;
exportEnvelope.setFile(exportFile);
return exportEnvelope;
}
public String getWordDocumentText (DatasetWizardModel datasetEntity) throws Exception {
@ -488,17 +492,19 @@ public class DatasetManager {
File file = xmlBuilder.build(pagedDatasetProfile, datasetEntity.getProfile().getId(), visibilityRuleService, environment);
FileEnvelope fileEnvelope = new FileEnvelope();
fileEnvelope.setFile(file);
fileEnvelope.setFilename(datasetEntity.getLabel());
String label = datasetEntity.getLabel().replaceAll("[^a-zA-Z0-9+ ]", "");
fileEnvelope.setFilename(label);
return fileEnvelope;
}
public File convertToPDF(File file, Environment environment, String label) throws IOException, InterruptedException {
public File convertToPDF(FileEnvelope file, Environment environment) throws IOException, InterruptedException {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("files", new FileSystemResource(file));
map.add("filename", label + ".pdf");
String uuid = UUID.randomUUID().toString();
map.add("files", new FileSystemResource(file.getFile()));
map.add("filename", uuid + ".pdf");
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.add("Content-disposition", "attachment; filename=" + label + ".pdf");
headers.add("Content-disposition", "attachment; filename=" + uuid + ".pdf");
headers.add("Content-type", "application/pdf");
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<LinkedMultiValueMap<String, Object>>(
@ -507,11 +513,11 @@ public class DatasetManager {
byte[] queueResult = new RestTemplate().postForObject(environment.getProperty("pdf.converter.url") + "convert/office"
, requestEntity, byte[].class);
File resultPdf = new File(label + ".pdf");
File resultPdf = new File(environment.getProperty("temp.temp") + uuid + ".pdf");
FileOutputStream output = new FileOutputStream(resultPdf);
IOUtils.write(queueResult, output);
output.close();
Files.deleteIfExists(file.toPath());
Files.deleteIfExists(file.getFile().toPath());
return resultPdf;
}

@ -21,10 +21,10 @@ import eu.eudat.models.data.entities.xmlmodels.datasetprofiledefinition.Field;
import eu.eudat.models.data.externaldataset.ExternalAutocompleteFieldModel;
import eu.eudat.models.data.helpers.common.DataTableData;
import eu.eudat.queryable.QueryableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.http.*;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestTemplate;
@ -34,12 +34,10 @@ import org.w3c.dom.Element;
import javax.activation.MimetypesFileTypeMap;
import javax.xml.xpath.*;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.util.*;
import java.io.*;
@Component
public class DatasetProfileManager {
@ -47,11 +45,13 @@ public class DatasetProfileManager {
private ApiContext apiContext;
private DatabaseRepository databaseRepository;
private Environment environment;
@Autowired
public DatasetProfileManager(ApiContext apiContext) {
public DatasetProfileManager(ApiContext apiContext, Environment environment) {
this.apiContext = apiContext;
this.databaseRepository = apiContext.getOperationsContext().getDatabaseRepository();
this.environment = environment;
}
public eu.eudat.models.data.admin.composite.DatasetProfile getDatasetProfile(String id) {
@ -142,7 +142,7 @@ public class DatasetProfileManager {
public FileEnvelope getXmlDocument(eu.eudat.models.data.user.composite.DatasetProfile datatasetProfile, String label) throws InstantiationException, IllegalAccessException, IOException {
ExportXmlBuilderDatasetProfile xmlBuilder = new ExportXmlBuilderDatasetProfile();
File file = xmlBuilder.build(datatasetProfile);
File file = xmlBuilder.build(datatasetProfile, environment);
FileEnvelope fileEnvelope = new FileEnvelope();
fileEnvelope.setFile(file);
fileEnvelope.setFilename(label);

@ -8,6 +8,7 @@ import eu.eudat.models.data.user.components.datasetprofile.FieldSet;
import eu.eudat.models.data.user.components.datasetprofile.Section;
import eu.eudat.logic.utilities.builders.XmlBuilder;
import org.springframework.core.env.Environment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -22,9 +23,9 @@ import java.util.UUID;
public class ExportXmlBuilderDatasetProfile {
public File build(eu.eudat.models.data.user.composite.DatasetProfile datasetProfile) throws IOException {
public File build(eu.eudat.models.data.user.composite.DatasetProfile datasetProfile, Environment environment) throws IOException {
File xmlFile = new File(UUID.randomUUID() + ".xml");
File xmlFile = new File(environment.getProperty("temp.temp") + UUID.randomUUID() + ".xml");
BufferedWriter writer = new BufferedWriter(new FileWriter(xmlFile, true));
Document xmlDoc = XmlBuilder.getDocument();
// Element root = xmlDoc.createElement("root");

@ -4,6 +4,7 @@ import eu.eudat.logic.utilities.builders.XmlBuilder;
import eu.eudat.models.data.entities.xmlmodels.dmpprofiledefinition.DataManagementPlanProfile;
import eu.eudat.models.data.listingmodels.DataManagementPlanProfileListingModel;
import org.springframework.core.env.Environment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@ -16,9 +17,9 @@ import java.util.UUID;
public class ExportXmlBuilderDmpProfile {
public File build(DataManagementPlanProfileListingModel dmpProfile) throws IOException {
public File build(DataManagementPlanProfileListingModel dmpProfile, Environment environment) throws IOException {
File xmlFile = new File(UUID.randomUUID() + ".xml");
File xmlFile = new File(environment.getProperty("temp.temp") + UUID.randomUUID() + ".xml");
BufferedWriter writer = new BufferedWriter(new FileWriter(xmlFile, true));
Document xmlDoc = XmlBuilder.getDocument();
Element root = xmlDoc.createElement("root");

Loading…
Cancel
Save