Fixes #22139
parent
5ea5f1adfd
commit
52f537a6c4
@ -0,0 +1,62 @@
|
||||
package org.gcube.application.geoportal.service.engine.caches;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.gcube.application.cms.tests.TokenSetter;
|
||||
import org.gcube.application.geoportal.service.engine.providers.AbstractScopedMap;
|
||||
import org.gcube.application.geoportal.service.engine.providers.TTLObject;
|
||||
import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@Slf4j
|
||||
public class Caches {
|
||||
|
||||
@Test
|
||||
public void testCache() throws ConfigurationException {
|
||||
|
||||
TokenSetter.set("/gcube/devsec/devVRE");
|
||||
|
||||
Duration ttl=Duration.of(10, ChronoUnit.SECONDS);
|
||||
|
||||
Duration monitorWindow=Duration.of(100, ChronoUnit.SECONDS);
|
||||
|
||||
DummyCache cache= new DummyCache();
|
||||
cache.setTTL(ttl);
|
||||
|
||||
|
||||
Instant startMonitoring=Instant.now();
|
||||
|
||||
LocalDateTime previous=cache.getObject();
|
||||
while(Duration.between(startMonitoring,Instant.now()).compareTo(monitorWindow)<0){
|
||||
LocalDateTime obj= cache.getObject();
|
||||
if(obj.equals(previous)){
|
||||
//objects are equals, TTL should be valid
|
||||
// Assert : now-creationTime < TTL
|
||||
assertTrue(Duration.between(obj,LocalDateTime.now()).compareTo(ttl)<0);
|
||||
}else {
|
||||
// different object only after TTL
|
||||
// Assert : now-creationTime < TTL
|
||||
assertTrue(Duration.between(obj,LocalDateTime.now()).compareTo(ttl)<0);
|
||||
// Assert : now-previous.creationTime > TTL
|
||||
assertTrue(Duration.between(previous,LocalDateTime.now()).compareTo(ttl)>0);
|
||||
}
|
||||
previous=obj;
|
||||
try {
|
||||
Thread.sleep(ttl.abs().dividedBy(2).toMillis());
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package org.gcube.application.geoportal.service.engine.caches;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.gcube.application.geoportal.service.engine.providers.AbstractScopedMap;
|
||||
import org.gcube.application.geoportal.service.model.internal.faults.ConfigurationException;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Slf4j
|
||||
public class DummyCache extends AbstractScopedMap<LocalDateTime> {
|
||||
|
||||
|
||||
public DummyCache() {
|
||||
super("Dummy cache");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocalDateTime retrieveObject() throws ConfigurationException {
|
||||
return LocalDateTime.now();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispose(LocalDateTime toDispose) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
"GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite"
|
||||
"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo"
|
||||
"GNA_Monte Postale_2019_rev_CdL_FPDC","GNA_Monte Postale_2019_rev_CdL_FPDC/GNA_topografia_Monte Postale","GNA_Monte Postale_2019_rev_CdL_FPDC/GNA_topografia_Monte Postale"
|
||||
"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo"
|
||||
"GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite"
|
||||
"GNA_Appia Antica V miglio_2019_rev_CdL","GNA_Appia Antica V miglio_2019_rev_CdL/GNA_topografia_Appia Antica V miglio","GNA_Appia Antica V miglio_2019_rev_CdL/GNA_topografia_Appia Antica V miglio"
|
||||
"GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL","GNA_Aquileia Casa Bestie Ferite_2019_rev_CdL/GNA_topografia_Aquileia Casa Bestie Ferite/posizionamento saggi.shp"
|
||||
"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo/posizionamento saggi.shp"
|
||||
"GNA_Monte Postale_2019_rev_CdL_FPDC","GNA_Monte Postale_2019_rev_CdL_FPDC/GNA_topografia_Monte Postale/pianta di fine scavo.shp"
|
||||
"GNA_Castelseprio castrum-borgo_2019_rev_CdL","GNA_Castelseprio castrum-borgo_2019_rev_CdL/GNA_topografia_Castelseprio castrum-borgo/posizionamento saggi.shp"
|
||||
"GNA_Appia Antica V miglio_2019_rev_CdL","GNA_Appia Antica V miglio_2019_rev_CdL/GNA_topografia_Appia Antica V miglio/posizionamento saggi.shp"
|
|
@ -1,5 +1,4 @@
|
||||
"GNA_Bostel di Rotzo_Rev_FPDC","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo"
|
||||
"GNA_Braida Murada_Rev_FPDC","GNA_Braida Murada_Rev_FPDC/GNA_topografia_Braida Murada","GNA_Braida Murada_Rev_FPDC/GNA_topografia_Braida Murada"
|
||||
"GNA_Egnazia_Rev_FPDC","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo"
|
||||
"GNA_Ferrandina_Rev_FPDC","GNA_Ferrandina_Rev_FPDC/GNA_topografia_Ferrandina","GNA_Ferrandina_Rev_FPDC/GNA_topografia_Ferrandina"
|
||||
"GNA_Timpone della Motta_Rev_FPDC","GNA_Timpone della Motta_Rev_FPDC/GNA_topografia_Timpone della Motta","GNA_Timpone della Motta_Rev_FPDC/GNA_topografia_Timpone della Motta"
|
||||
"GNA_Bostel di Rotzo_Rev_FPDC","GNA_Bostel di Rotzo_Rev_FPDC/GNA_topografia_Bostel di Rotzo/posizionamento saggi.shp"
|
||||
"GNA_Braida Murada_Rev_FPDC","GNA_Braida Murada_Rev_FPDC/GNA_topografia_Braida Murada/Posizionamento saggi.shp"
|
||||
"GNA_Ferrandina_Rev_FPDC","GNA_Ferrandina_Rev_FPDC/GNA_topografia_Ferrandina/posizionamento saggi.shp"
|
||||
"GNA_Timpone della Motta_Rev_FPDC","GNA_Timpone della Motta_Rev_FPDC/GNA_topografia_Timpone della Motta/posizionamento saggi.shp"
|
|
@ -1,4 +1,4 @@
|
||||
"GNA_Incoronata_2019_Rennes","/GNA_Incoronata_2019_Rennes/GNA_topografia_Incoronata/D","/GNA_Incoronata_2019_Rennes/GNA_topografia_Incoronata/E/SHP"
|
||||
"MONTE MANNU_2019","/MONTE MANNU_2019/GNA_topografia_Monte_Mannu/D_Limiti dei saggi","/MONTE MANNU_2019/GNA_topografia_Monte_Mannu/E_Pianta di fine scavo"
|
||||
"Santa Rosa Poviglio_2019","/Santa Rosa Poviglio_2019/GNA_Topografia_Poviglio/Posizionamento","/Santa Rosa Poviglio_2019/GNA_Topografia_Poviglio/Planimetrie"
|
||||
"Usini Tomestighes_integrata","/Usini Tomestighes_integrata/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/GNA_Tomestighes_2019/D_GNA_Posizionamento_limiti_aree_indagate","/Usini Tomestighes_integrata/GNA_TOMESTIGHES_2019/GNA_Topografia_Tomestighes/GNA_Tomestighes_2019/E_GNA_Piante_fine_scavo_Tomestighes/Area A est"
|
||||
"GNA_Incoronata_2019_Rennes","GNA_Incoronata_2019_Rennes/GNA_topografia_Incoronata/D/limites.shp"
|
||||
"MONTE MANNU_2019","MONTE MANNU_2019/GNA_topografia_Monte_Mannu/D_Limiti dei saggi/Limiti dei saggi_polyg.shp"
|
||||
"Santa Rosa Poviglio_2019","Santa Rosa Poviglio_2019/GNA_Topografia_Poviglio/Posizionamento/PosizionamentoSettori15-19.shp"
|
||||
"Usini Tomestighes_integrata","Usini Tomestighes_integrata/integrazioni/GNA_Tomestighes_2019/D_GNA_Posizionamento_limiti_aree_ricognizione/area di concessione.shp"
|
|
@ -1,7 +1,7 @@
|
||||
"Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019","Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019/Gis Sorgenti della Nova","Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019/Gis Sorgenti della Nova/Piante di fase"
|
||||
"GNA_Aquileia_ExPasqualis_ok","GNA_Aquileia_ExPasqualis_ok/GNA_topografia_Aquileia ex Pasqualis_2019","GNA_Aquileia_ExPasqualis_ok/GNA_topografia_Aquileia ex Pasqualis_2019"
|
||||
"Jesolo (Ve) - Loc. San Mauro e Torre del Caligo","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/topografia_modificata","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/topografia_modificata"
|
||||
"Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va","Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va/CONSEGNA_WGS84","Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va/CONSEGNA_WGS84"
|
||||
"Montecompatri_Tenuta_Castiglione_doc_IVa_2019","Montecompatri_Tenuta_Castiglione_doc_IVa_2019/GNA_topografia_Gabii_Louvre/POSIZIONAMENTO","Montecompatri_Tenuta_Castiglione_doc_IVa_2019/GNA_topografia_Gabii_Louvre/PIANTA FINE SCAVO"
|
||||
"SAN_CASCIANO_BAGNI","SAN_CASCIANO_BAGNI/POSIZIONAMENTO_EPSG_4326","SAN_CASCIANO_BAGNI"
|
||||
"Tarquinia_Civita_GNA_2019","Tarquinia_Civita_GNA_2019/Va_D__Topografia_Tarquinia/Posizionamento","Tarquinia_Civita_GNA_2019/Va_D__Topografia_Tarquinia/Posizionamento"
|
||||
"Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019","Farnese (VT)_Sorgenti della Nova_documentazione fine scavo 2019/Gis Sorgenti della Nova/FN19_generalplan WSG84 EPSG4326 sector XIII polilinea.shx"
|
||||
"GNA_Aquileia_ExPasqualis_ok","GNA_Aquileia_ExPasqualis_ok/GNA_topografia_Aquileia ex Pasqualis_2019/AQU19PSQ_limiti_saggi_poligoni.shp"
|
||||
"Jesolo (Ve) - Loc. San Mauro e Torre del Caligo","Jesolo (Ve) - Loc. San Mauro e Torre del Caligo/topografia_modificata/posizionamento_vb.shp"
|
||||
"Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va","Montalto di Castro (VT)_Vulci_Indagini non invasive_Doc. paragr._Va/CONSEGNA_WGS84/GPR_area_WGS84.shp"
|
||||
"Montecompatri_Tenuta_Castiglione_doc_IVa_2019","Montecompatri_Tenuta_Castiglione_doc_IVa_2019/GNA_topografia_Gabii_Louvre/POSIZIONAMENTO/Gabii_Louvre_posizionamento_EPSG4326.shp"
|
||||
"SAN_CASCIANO_BAGNI","SAN_CASCIANO_BAGNI/POSIZIONAMENTO_EPSG_4326/Bagno_Grande_posizionamento_4326_prova.shp"
|
||||
"Tarquinia_Civita_GNA_2019","Tarquinia_Civita_GNA_2019/Va_D__Topografia_Tarquinia/Posizionamento/Posizionamento dei limiti.shp"
|
|
@ -0,0 +1,54 @@
|
||||
package org.gcube.application.cms.usecases;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.gcube.application.cms.tests.TokenSetter;
|
||||
import org.gcube.application.cms.tests.model.TestModel;
|
||||
import org.gcube.application.geoportal.common.model.legacy.Concessione;
|
||||
import org.gcube.application.geoportal.common.rest.MongoConcessioni;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.mongoConcessioni;
|
||||
|
||||
@Slf4j
|
||||
public class StressTest {
|
||||
|
||||
|
||||
private static ExecutorService service = Executors.newFixedThreadPool(100);
|
||||
|
||||
public static void main(String[] args) {
|
||||
TokenSetter.set("/gcube/devsec/devVRE");
|
||||
AtomicLong executed=new AtomicLong(0);
|
||||
|
||||
int numRequests=10000;
|
||||
for(int i=0;i<numRequests;i++){
|
||||
service.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try{
|
||||
MongoConcessioni client=mongoConcessioni().build();
|
||||
Concessione c =TestModel.prepareEmptyConcessione();
|
||||
c.setNome("Stress test");
|
||||
client.createNew(c);
|
||||
}catch(Throwable t){
|
||||
System.err.println(t);
|
||||
}finally {
|
||||
log.info("Executed "+executed.incrementAndGet());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try{
|
||||
while(!service.awaitTermination(1, TimeUnit.MINUTES)){
|
||||
log.info("Waiting termination, executed {} out of {}",executed.get(),numRequests);
|
||||
}
|
||||
}catch (InterruptedException e){
|
||||
|
||||
}
|
||||
System.out.println("DONE");
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
package org.gcube.application.cms.usecases.mocks;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.gcube.application.cms.tests.model.TestModel;
|
||||
import org.gcube.application.geoportal.client.utils.Serialization;
|
||||
import org.gcube.application.geoportal.common.model.legacy.Concessione;
|
||||
import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport;
|
||||
import org.gcube.application.geoportal.common.rest.MongoConcessioni;
|
||||
import org.gcube.application.geoportal.common.utils.FileSets;
|
||||
import org.gcube.application.geoportal.common.utils.Files;
|
||||
import org.gcube.application.geoportal.common.utils.StorageUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.NavigableSet;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.gcube.application.geoportal.client.GeoportalAbstractPlugin.mongoConcessioni;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@ToString
|
||||
public class ConcessionPublisherThread implements Runnable{
|
||||
|
||||
|
||||
|
||||
//Threads
|
||||
private static AtomicLong queued=new AtomicLong(0);
|
||||
private static AtomicLong completed = new AtomicLong(0);
|
||||
|
||||
//Concessioni
|
||||
@Data
|
||||
static class Report{
|
||||
private ConcurrentLinkedQueue<Concessione> error=new ConcurrentLinkedQueue<>();
|
||||
private ConcurrentLinkedQueue<Concessione> success=new ConcurrentLinkedQueue<>();
|
||||
private ConcurrentLinkedQueue<Concessione> warning=new ConcurrentLinkedQueue<>();
|
||||
private ConcurrentLinkedQueue<Concessione> noReport=new ConcurrentLinkedQueue<>();
|
||||
}
|
||||
|
||||
private static ConcessionPublisherThread.Report report=new ConcessionPublisherThread.Report();
|
||||
|
||||
private static ExecutorService exec=Executors.newFixedThreadPool(5);
|
||||
|
||||
public static void submit(ConcessionPublisherThread c){
|
||||
log.info("Submitting {}",c);
|
||||
exec.submit(c);
|
||||
log.info("Submitted {} ",queued.incrementAndGet());
|
||||
}
|
||||
|
||||
public static ConcessionPublisherThread.Report waitCompletion(){
|
||||
try {
|
||||
while (!exec.awaitTermination(2, TimeUnit.MINUTES)) {
|
||||
log.info("Waiting .. completed {}, out of {} ",completed.get(),queued.get());
|
||||
if(completed.get()==queued.get()) exec.shutdown();
|
||||
}
|
||||
}catch(InterruptedException e){
|
||||
log.info("Process finished");
|
||||
}
|
||||
return report;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private File baseDir;
|
||||
@NonNull
|
||||
private String positionPath;
|
||||
@NonNull
|
||||
private String projectName;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Concessione c=null;
|
||||
try {
|
||||
MongoConcessioni client=mongoConcessioni().build();
|
||||
StorageUtils storage=new StorageUtils();
|
||||
|
||||
//NB raggruppa per file
|
||||
Map.Entry<String, List<File>> posSets = Files.getAllShapeSet(new File(baseDir,positionPath),true).
|
||||
entrySet().stream().findFirst().get();
|
||||
Map<String, List<File>> pianteSets = Files.getAllShapeSet(new File(baseDir,projectName),true);
|
||||
|
||||
if(pianteSets.size()>1)
|
||||
pianteSets.remove(posSets.getKey());
|
||||
|
||||
|
||||
|
||||
log.debug("Entry {} pos Size {} piante {} ",projectName,posSets.getValue().size(),pianteSets.size());
|
||||
|
||||
|
||||
c = createMock(projectName,baseDir.getName(), pianteSets, posSets.getValue(), client, storage);
|
||||
|
||||
switch(c.getReport().getStatus()){
|
||||
case PASSED: report.getSuccess().add(c);
|
||||
break;
|
||||
case ERROR: report.getError().add(c);
|
||||
break;
|
||||
case WARNING:report.getWarning().add(c);
|
||||
break;
|
||||
}
|
||||
|
||||
}catch(Throwable t){
|
||||
log.error("Problematic entry "+this,t);
|
||||
report.getNoReport().add(c);
|
||||
}finally{
|
||||
log.info("Completed N {}", completed.incrementAndGet());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Concessione createMock(String baseName, String packageName, Map<String,List<File>> piante, List<File> pos,
|
||||
MongoConcessioni client, StorageUtils storage) throws Exception {
|
||||
|
||||
Concessione c= TestModel.prepareConcessione(piante.size(), 1);
|
||||
c.setNome("Mock for "+baseName+" ("+packageName+")");
|
||||
c= client.createNew(c);
|
||||
String mongoId=c.getMongo_id();
|
||||
|
||||
// TEST DATA, DO NOT CARE
|
||||
client.registerFileSet(mongoId, FileSets.prepareRequest(storage,
|
||||
Concessione.Paths.RELAZIONE,new File (TestModel.getBaseFolder(),"relazione.pdf")));
|
||||
|
||||
client.registerFileSet(mongoId, FileSets.prepareRequest(storage,
|
||||
Concessione.Paths.imgByIndex(0),Files.getSiblings(TestModel.getBaseFolder(),"immagine").get(0)));
|
||||
|
||||
// POSIZIONAMENTO
|
||||
|
||||
client.registerFileSet(mongoId, FileSets.prepareRequest(storage,
|
||||
Concessione.Paths.POSIZIONAMENTO,pos.toArray(new File[pos.size()])));
|
||||
|
||||
// PIANTE
|
||||
Map.Entry<String,List<File>>[] entries= piante.entrySet().toArray(new Map.Entry[0]);
|
||||
for( int i= 0; i< piante.size();i++) {
|
||||
// Set layer name
|
||||
c=client.getById(mongoId);
|
||||
String path=Concessione.Paths.piantaByIndex(i);
|
||||
c.getContentByPath(path).setTitolo(" Pianta from "+entries[i].getKey());
|
||||
client.update(mongoId, Serialization.write(c));
|
||||
|
||||
//Set fileset
|
||||
client.registerFileSet(mongoId, FileSets.prepareRequest(storage,path, entries[i].getValue().toArray(new File[0])));
|
||||
}
|
||||
|
||||
c=client.publish(mongoId);
|
||||
|
||||
System.out.println("@@@ Concessione "+c.getNome()+"\t STATUS : "+ c.getReport().getStatus());
|
||||
|
||||
return c;
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{HH:mm:ss} | %logger{0}:%L - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<logger name="org.gcube.application" level="DEBUG"/>
|
||||
<root level="ERROR">
|
||||
<appender-ref ref="STDOUT" />
|
||||
</root>
|
||||
|
||||
|
||||
|
||||
</configuration>
|
Loading…
Reference in New Issue