From 11dfbea2be0b826a879da598211d1b20679c870b Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Thu, 17 Dec 2020 18:27:45 +0100 Subject: [PATCH 01/10] Mongo --- CHANGELOG.md | 4 + pom.xml | 2 +- .../geoportal/service/GeoPortalService.java | 2 + .../engine/cache/MongoClientProvider.java | 6 + .../engine/mongo/ConcessioniMongoManager.java | 98 ++++++++++++++ .../service/engine/mongo/MongoManager.java | 83 +++++++----- .../service/rest/ConcessioniOverMongo.java | 127 ++++++++++++++++++ .../service/BasicServiceTestUnit.java | 34 ++++- .../service/ConcessioniOverMongoTest.java | 36 +++++ .../geoportal/service/TestModel.java | 111 +++++++++++++++ .../service/legacy/ConcessioniTest.java | 8 +- .../{TestModel.java => OLDTestModel.java} | 2 +- 12 files changed, 476 insertions(+), 37 deletions(-) create mode 100644 src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java create mode 100644 src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java create mode 100644 src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java create mode 100644 src/test/java/org/gcube/application/geoportal/service/TestModel.java rename src/test/java/org/gcube/application/geoportal/service/legacy/{TestModel.java => OLDTestModel.java} (99%) diff --git a/CHANGELOG.md b/CHANGELOG.md index e67f6bb..16cf4a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm # Changelog for org.gcube.application.geoportal-service +## [v1.0.4-SNAPSHOT] 2020-11-11 +Mongo integration with Concessione +Project interface + ## [v1.0.3] 2020-11-11 Fixed HTTP method diff --git a/pom.xml b/pom.xml index 619ed18..f18bc81 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.gcube.application geoportal-service - 1.0.3 + 1.0.4-SNAPSHOT Geoportal Service war diff --git a/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java b/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java index 471012b..5d89e34 100644 --- a/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java +++ b/src/main/java/org/gcube/application/geoportal/service/GeoPortalService.java @@ -4,6 +4,7 @@ import javax.ws.rs.ApplicationPath; import org.gcube.application.geoportal.common.rest.InterfaceConstants; import org.gcube.application.geoportal.service.rest.Concessioni; +import org.gcube.application.geoportal.service.rest.ConcessioniOverMongo; import org.gcube.application.geoportal.service.rest.Profiles; import org.gcube.application.geoportal.service.rest.Projects; import org.gcube.application.geoportal.service.rest.Sections; @@ -18,6 +19,7 @@ public class GeoPortalService extends ResourceConfig{ super(); //Register interrfaces registerClasses(Concessioni.class); + registerClasses(ConcessioniOverMongo.class); registerClasses(Projects.class); registerClasses(Sections.class); registerClasses(Profiles.class); diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/cache/MongoClientProvider.java b/src/main/java/org/gcube/application/geoportal/service/engine/cache/MongoClientProvider.java index 9cb751a..ae30f18 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/cache/MongoClientProvider.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/cache/MongoClientProvider.java @@ -10,6 +10,8 @@ import com.mongodb.MongoClientOptions; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class MongoClientProvider extends AbstractScopedMap{ public MongoClientProvider() { @@ -21,9 +23,13 @@ public class MongoClientProvider extends AbstractScopedMap{ @Override protected MongoClient retrieveObject() throws ConfigurationException { MongoConnection conn=ImplementationProvider.get().getMongoConnectionProvider().getObject(); + log.debug("Connecting to "+conn); + MongoCredential credential = MongoCredential.createCredential(conn.getUser(), conn.getDatabase(), conn.getPassword().toCharArray()); + + MongoClientOptions options = MongoClientOptions.builder().sslEnabled(true).build(); return new MongoClient(new ServerAddress(conn.getHosts().get(0),conn.getPort()), diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java new file mode 100644 index 0000000..6b38c5f --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -0,0 +1,98 @@ +package org.gcube.application.geoportal.service.engine.mongo; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.bson.Document; +import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.report.PublicationReport; +import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.service.utils.Serialization; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.mongodb.client.MongoDatabase; + +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class ConcessioniMongoManager extends MongoManager{ + + + + public ConcessioniMongoManager() throws ConfigurationException { + super(); + // TODO Auto-generated constructor stub + } + private static final String collectionName="legacy-concessioni"; + private static final String DB_NAME="gna_dev"; + + + private MongoDatabase db=null; + + @Override + @Synchronized + protected MongoDatabase getDatabase() { + if(db==null) { + db=client.getDatabase(DB_NAME); + } + return db; + } + + protected static Document asDocument (Concessione c) throws JsonProcessingException { + return Document.parse(Serialization.write(c)); + + } + + protected static Concessione asConcessione (Document d) throws JsonProcessingException, IOException { + return Serialization.read(d.toJson(), Concessione.class); + } + + public Concessione registerNew(Concessione toRegister) throws IOException { + ObjectId id=insert(asDocument(toRegister), collectionName); + Concessione toReturn=asConcessione(getById(id,collectionName)); + toReturn.setMongo_id(id.toHexString()); + update(asDocument(toRegister),collectionName); + return toReturn; + } + + public void update(Concessione toRegister) throws JsonProcessingException { + update(asDocument(toRegister),collectionName); + } + + public List list(){ + ArrayList toReturn=new ArrayList<>(); + iterate(null, collectionName).forEach((Document d)->{ + try { + toReturn.add(asConcessione(d)); + }catch(Throwable t) { + log.error("Unable to read Document as concessione ",t); + log.debug("Document was "+d.toJson()); + } + }); + return toReturn; + } + + public Concessione getById(String id) throws JsonProcessingException, IOException { + return asConcessione(getById(new ObjectId(id),collectionName)); + } + public void deleteById(String id) { + delete(new ObjectId(id), collectionName); + } + + public Concessione publish(String id) throws JsonProcessingException, IOException{ + Concessione toReturn=asConcessione(getById(new ObjectId(id),collectionName)); + toReturn.setDefaults(); + toReturn.validate(); + publish(toReturn); + update(asDocument(toReturn),collectionName); + return toReturn; + } + + private static PublicationReport publish(Concessione c) { + //TODO implement + return null; + } +} diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java index 68e7968..d217299 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java @@ -1,72 +1,95 @@ package org.gcube.application.geoportal.service.engine.mongo; +import static com.mongodb.client.model.Filters.eq; + import org.bson.Document; -import org.gcube.application.geoportal.common.model.profile.Profile; -import org.gcube.application.geoportal.common.model.project.Project; +import org.bson.types.ObjectId; import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; import com.mongodb.MongoClient; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import lombok.extern.slf4j.Slf4j; + +@Slf4j public abstract class MongoManager { - private MongoClient client=null; + protected MongoClient client=null; public MongoManager() throws ConfigurationException { -// client=ImplementationProvider.get().getMongoClientProvider().getObject(); + client=ImplementationProvider.get().getMongoClientProvider().getObject(); - // init profile + log.info("Got Mongo Client at "+client.getConnectPoint()); + // NOT AUTHORIZED +// log.debug("Existing databases "+client.getDatabaseNames()); } // private abstract MongoDatabase getDatabase() { // return client.getDatabase("gna-db"); // } + // TODO check if existing DB protected abstract MongoDatabase getDatabase(); //*********** PROJECTS - public void insert(Project proj, Profile profile) { - MongoDatabase database=getDatabase(); - // TODO check if existing DB - - String collectionName=profile.getName(); - - MongoCollection collection = database.getCollection(collectionName); - // TODO check if existing collection - - - collection.insertOne(Document.parse(proj.toString())); - + // NB BsonId + protected ObjectId insert(Document proj, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection collection = database.getCollection(collectionName); + collection.insertOne(Document.parse(proj.toJson())); + return proj.getObjectId("_id"); } -// public Project update(Project proj) { -// -// } - public void delete(String id) { - + public void delete(ObjectId id, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection collection = database.getCollection(collectionName); + collection.deleteOne(eq("_id",id)); + } + + + + public Document getById(ObjectId id,String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + return coll.find(new Document("_id",id)).first(); } - public Document getById(String id,Profile p) { + + + public FindIterable iterate(Document filter,String collectionName) { MongoDatabase database=getDatabase(); - MongoCollection coll=database.getCollection(p.getName()); - - return coll.find(new Document("id",id)).first(); + MongoCollection coll=database.getCollection(collectionName); + if(filter==null) + return coll.find(); + else + return coll.find(filter); } - public FindIterable iterate(Document filter,Profile p) { + + public FindIterable iterateForClass(Document filter,String collectionName,Class clazz) { MongoDatabase database=getDatabase(); - MongoCollection coll=database.getCollection(p.getName()); - - return coll.find(filter); + MongoCollection coll=database.getCollection(collectionName); + if(filter==null) + return coll.find(clazz); + else + return coll.find(filter,clazz); + } + + public void update(Document toUpdate,String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + coll.findOneAndReplace(eq("_id",toUpdate.getObjectId("_id")), toUpdate); } + //********** PROFILES diff --git a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java new file mode 100644 index 0000000..f021867 --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -0,0 +1,127 @@ +package org.gcube.application.geoportal.service.rest; + +import java.util.ArrayList; +import java.util.List; + +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.WebApplicationException; +import javax.ws.rs.core.MediaType; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.service.engine.mongo.ConcessioniMongoManager; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.json.JSONArray; +import org.json.JSONObject; + +import lombok.extern.slf4j.Slf4j; + +@Path("mongo-concessioni") +@Slf4j +public class ConcessioniOverMongo { + + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String update(String jsonString) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { + Concessione c=Serialization.read(jsonString, Concessione.class); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + manager.update(c); + + return Serialization.write(manager.getById(c.getMongo_id())); + } + }.execute().getResult(); + } + + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public String createNew(String jsonString) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { + Concessione c=Serialization.read(jsonString, Concessione.class); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return Serialization.write(manager.registerNew(c)); + } + }.execute().getResult(); + } + + + + @GET + @Produces(MediaType.APPLICATION_JSON) + public String list() { + return new GuardedMethod () { + protected String run() throws Exception ,WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + JSONArray toReturn=new JSONArray(); + manager.list().forEach((Concessione c) -> { + try{ + toReturn.put(new JSONObject(Serialization.write(c))); + }catch(Throwable t) { + log.error("Unable to serialize "+c); + } + }); + return toReturn.toString(); + + }; + }.execute().getResult(); + + + } + + + // BY ID + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public String getById(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return Serialization.write(manager.getById(id)); + } + }.execute().getResult(); + } + + @DELETE + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public void deleteById(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + new GuardedMethod () { + @Override + protected Concessione run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + manager.deleteById(id); + return null; + } + }.execute(); + } + + @PUT + @Produces(MediaType.APPLICATION_JSON) + @Path("/publish/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public String publish(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return Serialization.write(manager.publish(id)); + } + }.execute().getResult(); + } + +} diff --git a/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java b/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java index 61c9866..77fb6f5 100644 --- a/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java +++ b/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java @@ -2,17 +2,24 @@ package org.gcube.application.geoportal.service; import javax.persistence.EntityManagerFactory; import javax.ws.rs.core.Application; +import javax.ws.rs.core.Response; import org.gcube.application.geoportal.managers.AbstractRecordManager; import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.model.report.PublicationReport; import org.gcube.application.geoportal.service.engine.ImplementationProvider; import org.gcube.application.geoportal.service.engine.ScopedEMFProvider; import org.gcube.application.geoportal.service.engine.StorageClientProvider; +import org.gcube.application.geoportal.service.engine.cache.MongoClientProvider; +import org.gcube.application.geoportal.service.engine.cache.MongoConnectionProvider; import org.gcube.application.geoportal.service.legacy.TokenSetter; +import org.gcube.application.geoportal.service.utils.Serialization; import org.gcube.contentmanagement.blobstorage.service.IClient; import org.glassfish.jersey.test.JerseyTest; import org.junit.BeforeClass; +import com.mongodb.MongoClient; + public class BasicServiceTestUnit extends JerseyTest { @@ -33,7 +40,6 @@ public class BasicServiceTestUnit extends JerseyTest { @Override public EntityManagerFactory getFactory() { -// System.err.println("***********************SETTING DEBUG CONTEXT******************"); TokenSetter.set(scope); return super.getFactory(); } @@ -47,5 +53,31 @@ public class BasicServiceTestUnit extends JerseyTest { } }); + + ImplementationProvider.get().setMongoConnectionProvider(new MongoConnectionProvider() { + @Override + public org.gcube.application.geoportal.service.model.internal.db.MongoConnection getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + }); + + ImplementationProvider.get().setMongoClientProvider(new MongoClientProvider() { + @Override + public MongoClient getObject() throws ConfigurationException { + TokenSetter.set(scope); + return super.getObject(); + } + }); + + } + + + protected static T check(Response resp, Class clazz) throws Exception { + String resString=resp.readEntity(String.class); + if(resp.getStatus()<200||resp.getStatus()>=300) + throw new Exception("RESP STATUS IS "+resp.getStatus()+". Message : "+resString); + System.out.println("Resp String is "+resString); + return Serialization.read(resString, clazz); } } diff --git a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java new file mode 100644 index 0000000..8642243 --- /dev/null +++ b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -0,0 +1,36 @@ +package org.gcube.application.geoportal.service; + +import java.io.IOException; +import java.util.List; + +import javax.ws.rs.client.Entity; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.junit.Test; + +public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ + + + private static final String PATH="mongo-concessioni"; + + + @Test + public void list() { + WebTarget target=target(PATH); + System.out.println(target.request(MediaType.APPLICATION_JSON).get(List.class)); + } + + @Test + public void createNew() throws Exception { + WebTarget target=target(PATH); + Concessione conc=TestModel.prepareConcessione(); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + System.out.println("ID IS "+c.getMongo_id()); + } + +} diff --git a/src/test/java/org/gcube/application/geoportal/service/TestModel.java b/src/test/java/org/gcube/application/geoportal/service/TestModel.java new file mode 100644 index 0000000..2f478b8 --- /dev/null +++ b/src/test/java/org/gcube/application/geoportal/service/TestModel.java @@ -0,0 +1,111 @@ +package org.gcube.application.geoportal.service; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; + +import org.gcube.application.geoportal.common.model.legacy.AccessPolicy; +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; + + + +public class TestModel { + + + public static Concessione prepareEmptyConcessione() { + Concessione concessione=new Concessione(); + + // Generic fields + + // Concessione fields + + concessione.setNome("MONGO Italia, forse, ma su "); + concessione.setIntroduzione("This is my MONGO project"); + concessione.setDescrizioneContenuto("It contains this and that"); + + concessione.setAuthors(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + + concessione.setContributore("Contrib 1"); + concessione.setTitolari(Arrays.asList(new String[] {"Some one","Some, oneelse"})); + concessione.setResponsabile("Someone"); + concessione.setEditore("Editore"); + + concessione.setFontiFinanziamento(Arrays.asList(new String[] {"Big pharma","Pentagon"})); + + + concessione.setSoggetto(Arrays.asList(new String[] {"Research Excavation","Archeology"})); + + + concessione.setDataInizioProgetto(LocalDateTime.now()); + concessione.setDataFineProgetto(LocalDateTime.now()); + + concessione.setLicenzaID("CC-BY"); + + concessione.setTitolareLicenza(Arrays.asList(new String[] {"Qualcun altro"})); + concessione.setTitolareCopyright(Arrays.asList(new String[] {"Chiedilo in giro"})); + + concessione.setParoleChiaveLibere(Arrays.asList(new String[] {"Robba","Stuff"})); + concessione.setParoleChiaveICCD(Arrays.asList(new String[] {"vattelapesca","somthing something"})); + + + concessione.setCentroidLat(43.0); //N-S + concessione.setCentroidLong(9.0); //E-W + + return concessione; + } + + public static Concessione prepareConcessione() { + + Concessione concessione=prepareEmptyConcessione(); + + + + // Attachments + + // Relazione scavo + RelazioneScavo relScavo=new RelazioneScavo(); + + relScavo.setAbstractSection("simple abstract section"); + relScavo.setResponsabili(concessione.getAuthors()); + + concessione.setRelazioneScavo(relScavo); + //Immagini rappresentative + ArrayList imgs=new ArrayList<>(); + for(int i=0;i<5;i++) { + UploadedImage img=new UploadedImage(); + img.setTitolo("My image number "+i); + img.setDidascalia("You can see my image number "+i); + img.setFormat("TIFF"); + img.setCreationTime(LocalDateTime.now()); + img.setResponsabili(concessione.getAuthors()); + imgs.add(img); + } + concessione.setImmaginiRappresentative(imgs); + //Posizionamento + LayerConcessione posizionamento=new LayerConcessione(); + posizionamento.setValutazioneQualita("Secondo me si"); + posizionamento.setMetodoRaccoltaDati("Fattobbene"); + posizionamento.setScalaAcquisizione("1:10000"); + posizionamento.setAuthors(concessione.getAuthors()); + concessione.setPosizionamentoScavo(posizionamento); + + // Piante fine scavo + ArrayList piante=new ArrayList(); + for(int i=0;i<4;i++) { + LayerConcessione pianta=new LayerConcessione(); + pianta.setValutazioneQualita("Secondo me si"); + pianta.setMetodoRaccoltaDati("Fattobbene"); + pianta.setScalaAcquisizione("1:10000"); + pianta.setAuthors(concessione.getAuthors()); + pianta.setPolicy(AccessPolicy.RESTRICTED); + piante.add(pianta); + } + concessione.setPianteFineScavo(piante); + + return concessione; + } + +} diff --git a/src/test/java/org/gcube/application/geoportal/service/legacy/ConcessioniTest.java b/src/test/java/org/gcube/application/geoportal/service/legacy/ConcessioniTest.java index 1478edc..6b709c0 100644 --- a/src/test/java/org/gcube/application/geoportal/service/legacy/ConcessioniTest.java +++ b/src/test/java/org/gcube/application/geoportal/service/legacy/ConcessioniTest.java @@ -71,7 +71,7 @@ public class ConcessioniTest extends BasicServiceTestUnit { @Test public void failPublish() throws com.fasterxml.jackson.core.JsonProcessingException, IOException { - Concessione toCreate=TestModel.prepareEmptyConcessione(); + Concessione toCreate=OLDTestModel.prepareEmptyConcessione(); Concessione conc=pushConcessione(toCreate); System.out.println(publish(conc.getId()+"").prettyPrint()); @@ -81,17 +81,17 @@ public class ConcessioniTest extends BasicServiceTestUnit { @Test public void createNew() throws IOException { - Concessione toCreate=TestModel.prepareEmptyConcessione(); + Concessione toCreate=OLDTestModel.prepareEmptyConcessione(); pushConcessione(toCreate); } @Test public void publishNew() throws IOException, RemoteBackendException, ConfigurationException { - Concessione toCreate=TestModel.prepareEmptyConcessione(); + Concessione toCreate=OLDTestModel.prepareEmptyConcessione(); Concessione registered = pushConcessione(toCreate); System.out.println("Registered at "+Serialization.write(registered)); - Concessione fullTemplate=TestModel.prepareConcessione(); + Concessione fullTemplate=OLDTestModel.prepareConcessione(); //Push Relazione publishSection(registered.getId()+"",formRequest(Section.RELAZIONE,fullTemplate.getRelazioneScavo(),"concessioni/relazione.pdf")); diff --git a/src/test/java/org/gcube/application/geoportal/service/legacy/TestModel.java b/src/test/java/org/gcube/application/geoportal/service/legacy/OLDTestModel.java similarity index 99% rename from src/test/java/org/gcube/application/geoportal/service/legacy/TestModel.java rename to src/test/java/org/gcube/application/geoportal/service/legacy/OLDTestModel.java index 64685a5..26472ec 100644 --- a/src/test/java/org/gcube/application/geoportal/service/legacy/TestModel.java +++ b/src/test/java/org/gcube/application/geoportal/service/legacy/OLDTestModel.java @@ -10,7 +10,7 @@ import org.gcube.application.geoportal.model.concessioni.LayerConcessione; import org.gcube.application.geoportal.model.concessioni.RelazioneScavo; import org.gcube.application.geoportal.model.content.UploadedImage; -public class TestModel { +public class OLDTestModel { public static Concessione prepareEmptyConcessione() { Concessione concessione=new Concessione(); From ab1d97ad3b37fb7db0427ab80c4d8b4527ff71c0 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Thu, 17 Dec 2020 18:34:53 +0100 Subject: [PATCH 02/10] registerFile method --- .../service/rest/ConcessioniOverMongo.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java index f021867..c017a6b 100644 --- a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java +++ b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -1,6 +1,5 @@ package org.gcube.application.geoportal.service.rest; -import java.util.ArrayList; import java.util.List; import javax.ws.rs.Consumes; @@ -16,6 +15,7 @@ import javax.ws.rs.core.MediaType; import org.gcube.application.geoportal.common.model.legacy.Concessione; import org.gcube.application.geoportal.common.rest.InterfaceConstants; +import org.gcube.application.geoportal.common.rest.TempFile; import org.gcube.application.geoportal.service.engine.mongo.ConcessioniMongoManager; import org.gcube.application.geoportal.service.utils.Serialization; import org.json.JSONArray; @@ -124,4 +124,20 @@ public class ConcessioniOverMongo { }.execute().getResult(); } + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("/registerFiles/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public String registerFile(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,List files) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { + //TODO FILE register + + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + return Serialization.write(manager.publish(id)); + } + }.execute().getResult(); + } + } From dea72bf237d2c42d7a78ee08690c93d1f1815b26 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Fri, 18 Dec 2020 12:40:46 +0100 Subject: [PATCH 03/10] Fixes --- .../engine/mongo/ConcessioniMongoManager.java | 21 +++-- .../service/engine/mongo/MongoManager.java | 28 +++++-- .../service/BasicServiceTestUnit.java | 4 +- .../service/ConcessioniOverMongoTest.java | 82 +++++++++++++++++++ 4 files changed, 117 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java index 6b38c5f..f49fe00 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -26,7 +26,7 @@ public class ConcessioniMongoManager extends MongoManager{ super(); // TODO Auto-generated constructor stub } - private static final String collectionName="legacy-concessioni"; + private static final String collectionName="legacyConcessioni"; private static final String DB_NAME="gna_dev"; @@ -42,8 +42,10 @@ public class ConcessioniMongoManager extends MongoManager{ } protected static Document asDocument (Concessione c) throws JsonProcessingException { - return Document.parse(Serialization.write(c)); - + Document toReturn=Document.parse(Serialization.write(c)); + if(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()) + toReturn.append(ID, new ObjectId(c.getMongo_id())); + return toReturn; } protected static Concessione asConcessione (Document d) throws JsonProcessingException, IOException { @@ -52,14 +54,15 @@ public class ConcessioniMongoManager extends MongoManager{ public Concessione registerNew(Concessione toRegister) throws IOException { ObjectId id=insert(asDocument(toRegister), collectionName); + Concessione toReturn=asConcessione(getById(id,collectionName)); toReturn.setMongo_id(id.toHexString()); - update(asDocument(toRegister),collectionName); - return toReturn; + + return asConcessione(update(asDocument(toReturn),collectionName)); } - public void update(Concessione toRegister) throws JsonProcessingException { - update(asDocument(toRegister),collectionName); + public Concessione update(Concessione toRegister) throws IOException { + return asConcessione(update(asDocument(toRegister),collectionName)); } public List list(){ @@ -76,6 +79,7 @@ public class ConcessioniMongoManager extends MongoManager{ } public Concessione getById(String id) throws JsonProcessingException, IOException { + log.debug("Loading by ID "+id); return asConcessione(getById(new ObjectId(id),collectionName)); } public void deleteById(String id) { @@ -87,8 +91,7 @@ public class ConcessioniMongoManager extends MongoManager{ toReturn.setDefaults(); toReturn.validate(); publish(toReturn); - update(asDocument(toReturn),collectionName); - return toReturn; + return asConcessione(update(asDocument(toReturn),collectionName)); } private static PublicationReport publish(Concessione c) { diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java index d217299..354575e 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java @@ -12,6 +12,8 @@ import com.mongodb.MongoClient; import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; +import com.mongodb.client.model.FindOneAndReplaceOptions; +import com.mongodb.client.model.ReturnDocument; import lombok.extern.slf4j.Slf4j; @@ -20,7 +22,7 @@ public abstract class MongoManager { protected MongoClient client=null; - + protected static final String ID="_id"; public MongoManager() throws ConfigurationException { @@ -42,16 +44,24 @@ public abstract class MongoManager { // NB BsonId protected ObjectId insert(Document proj, String collectionName) { MongoDatabase database=getDatabase(); - MongoCollection collection = database.getCollection(collectionName); - collection.insertOne(Document.parse(proj.toJson())); - return proj.getObjectId("_id"); + MongoCollection collection = database.getCollection(collectionName); + + // Check if _id is present + ObjectId id=proj.getObjectId(ID); + if(id==null) { + proj.append(ID, new ObjectId()); + id=proj.getObjectId(ID); + } + + collection.insertOne(Document.parse(proj.toJson())); + return id; } public void delete(ObjectId id, String collectionName) { MongoDatabase database=getDatabase(); MongoCollection collection = database.getCollection(collectionName); - collection.deleteOne(eq("_id",id)); + collection.deleteOne(eq(ID,id)); } @@ -59,7 +69,7 @@ public abstract class MongoManager { public Document getById(ObjectId id,String collectionName) { MongoDatabase database=getDatabase(); MongoCollection coll=database.getCollection(collectionName); - return coll.find(new Document("_id",id)).first(); + return coll.find(new Document(ID,id)).first(); } @@ -82,10 +92,12 @@ public abstract class MongoManager { return coll.find(filter,clazz); } - public void update(Document toUpdate,String collectionName) { + public Document update(Document toUpdate,String collectionName) { MongoDatabase database=getDatabase(); MongoCollection coll=database.getCollection(collectionName); - coll.findOneAndReplace(eq("_id",toUpdate.getObjectId("_id")), toUpdate); + return coll.findOneAndReplace( + eq(ID,toUpdate.getObjectId(ID)), toUpdate,new FindOneAndReplaceOptions().returnDocument(ReturnDocument.AFTER)); + } diff --git a/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java b/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java index 77fb6f5..e05a8f9 100644 --- a/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java +++ b/src/test/java/org/gcube/application/geoportal/service/BasicServiceTestUnit.java @@ -78,6 +78,8 @@ public class BasicServiceTestUnit extends JerseyTest { if(resp.getStatus()<200||resp.getStatus()>=300) throw new Exception("RESP STATUS IS "+resp.getStatus()+". Message : "+resString); System.out.println("Resp String is "+resString); - return Serialization.read(resString, clazz); + if(clazz!=null) + return Serialization.read(resString, clazz); + else return null; } } diff --git a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java index 8642243..fb7840c 100644 --- a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java +++ b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -10,6 +10,7 @@ import javax.ws.rs.core.Response; import org.gcube.application.geoportal.common.model.legacy.Concessione; import org.gcube.application.geoportal.service.utils.Serialization; +import org.junit.Assert; import org.junit.Test; public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ @@ -17,6 +18,9 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ private static final String PATH="mongo-concessioni"; + private static final String PUBLISH_PATH="publish"; + private static final String FILES_PATH="registerFiles"; + @Test public void list() { @@ -26,11 +30,89 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ @Test public void createNew() throws Exception { + WebTarget target=target(PATH); + Concessione conc=TestModel.prepareConcessione(); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + System.out.println("ID IS "+c.getMongo_id()); + } + + + @Test + public void delete() throws Exception { WebTarget target=target(PATH); Concessione conc=TestModel.prepareConcessione(); Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); Concessione c=check(resp,Concessione.class); System.out.println("ID IS "+c.getMongo_id()); + + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + + resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).delete(); + check(resp,null); + } + + + @Test + public void getById() throws Exception { + WebTarget target=target(PATH); + Concessione conc=TestModel.prepareConcessione(); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + + resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); + Concessione loaded=check(resp,Concessione.class); + System.out.println("Got by ID "+loaded); + + Assert.assertTrue(loaded.getMongo_id()!=null&&!loaded.getMongo_id().isEmpty()); } + + @Test + public void update() throws Exception { + WebTarget target=target(PATH); + Concessione conc=TestModel.prepareConcessione(); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + + resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); + Concessione loaded=check(resp,Concessione.class); + + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + System.out.println("Modifying "+loaded); + String newTitle="Questo titolo l'ho modificato mo'"; + loaded.setNome(newTitle); + + resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(loaded), MediaType.APPLICATION_JSON)); + Assert.assertTrue(check(resp,Concessione.class).getNome().equals(newTitle)); + + } + + @Test + public void uploadFile() { + // TODO + } + + @Test + public void publish() throws Exception { + WebTarget target=target(PATH); + Concessione conc=TestModel.prepareConcessione(); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); +// System.out.println("ID IS "+c.getMongo_id()); + resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); + Concessione loaded=check(resp,Concessione.class); + System.out.println("Modifying "+loaded); + String newTitle="Questo titolo l'ho modificato mo'"; + loaded.setNome(newTitle); + + resp=target.path(PUBLISH_PATH).path(c.getMongo_id()).request(MediaType.APPLICATION_JSON). + put(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + + // TODO Validation +// Assert.assertTrue(check(resp,Concessione.class).getNome().equals(newTitle)); + } } From 9a500d5ae1a997e4d5eae82bd1dfadf8ef0f77b6 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Fri, 18 Dec 2020 18:24:20 +0100 Subject: [PATCH 04/10] temp files --- CHANGELOG.md | 3 + .../engine/ImplementationProvider.java | 4 + .../service/engine/StorageHubProvider.java | 25 ++++ .../service/engine/WorkspaceManager.java | 137 ++++++++++++++++++ .../engine/mongo/ConcessioniMongoManager.java | 78 +++++++++- .../service/engine/mongo/MongoManager.java | 2 +- .../service/rest/ConcessioniOverMongo.java | 41 +++++- .../service/utils/Serialization.java | 4 + .../service/ConcessioniOverMongoTest.java | 46 +++++- .../geoportal/service/TestModel.java | 15 ++ 10 files changed, 342 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/gcube/application/geoportal/service/engine/StorageHubProvider.java create mode 100644 src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 16cf4a5..81ef133 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [v1.0.4-SNAPSHOT] 2020-11-11 Mongo integration with Concessione Project interface +TempFile management +WorkspaceContent for Concessioni-over-mongo + ## [v1.0.3] 2020-11-11 Fixed HTTP method diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java b/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java index eb6f22a..b6059b5 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/ImplementationProvider.java @@ -41,6 +41,10 @@ public class ImplementationProvider { private EMFProvider emfProvider=new ScopedEMFProvider(); + @Getter + @Setter + private StorageHubProvider sHubProvider=new StorageHubProvider(); + public void shutdown() { // Stop JPA AbstractRecordManager.shutdown(); diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/StorageHubProvider.java b/src/main/java/org/gcube/application/geoportal/service/engine/StorageHubProvider.java new file mode 100644 index 0000000..7aa2c8c --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/engine/StorageHubProvider.java @@ -0,0 +1,25 @@ +package org.gcube.application.geoportal.service.engine; + +import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; + +public class StorageHubProvider implements Engine{ + + + @Override + public StorageHubClient getObject() throws ConfigurationException { + return new StorageHubClient(); + } + + @Override + public void init() { + // TODO Auto-generated method stub + + } + @Override + public void shustdown() { + // TODO Auto-generated method stub + + } + +} diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java new file mode 100644 index 0000000..e4cb836 --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java @@ -0,0 +1,137 @@ +package org.gcube.application.geoportal.service.engine; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +import javax.validation.constraints.NotNull; + +import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; +import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.common.storagehub.client.dsl.StorageHubClient; +import org.gcube.common.storagehub.model.exceptions.StorageHubException; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.Synchronized; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class WorkspaceManager { + + private static final String APP_FOLDER=".GNA_RECORDS"; + + + private StorageHubClient sgClient=null; + private FolderContainer appBase=null; + + @Getter + @Setter + @AllArgsConstructor + @RequiredArgsConstructor + public static class FolderOptions{ + @NotNull + private String folderName; + private String folderDescription; + private FolderContainer parent; + } + + @Getter + @Setter + @AllArgsConstructor + @RequiredArgsConstructor + public static class FileOptions{ + @NotNull + private String fileName; + @NonNull + private InputStream is; + + private String fileDescription; + private FolderContainer parent; + + } + + + public WorkspaceManager() throws ConfigurationException, StorageHubException { + sgClient= ImplementationProvider.get().getSHubProvider().getObject(); + appBase=getApplicationBaseFolder(sgClient); + } + + public FolderContainer createFolder(FolderOptions opts) throws StorageHubException { + if(opts.getParent()==null) + opts.setParent(appBase); + return createFolder(opts,sgClient); + } + + public FileContainer getFileById(String id) throws StorageHubException { + return sgClient.open(id).asFile(); + } + + public FolderContainer getFolderById(String id) throws StorageHubException { + return sgClient.open(id).asFolder(); + } + + public FolderContainer getSubFolder(FolderContainer parentFolder,String path) throws StorageHubException { + try{ + return parentFolder.openByRelativePath(path).asFolder(); + }catch(StorageHubException e) { + log.debug("Missing subPath "+path); + FolderContainer targetParent=parentFolder; + String targetName=path; + if(path.contains("/")) { + String parent=path.substring(0, path.lastIndexOf("/")); + log.debug("Checking intermediate "+parent); + targetParent=getSubFolder(parentFolder,parent); + targetName=path.substring(path.lastIndexOf("/")+1); + } + log.debug("Creating "+targetName); + return createFolder(new FolderOptions(targetName,"",targetParent),sgClient); + } + } + + + public WorkspaceContent storeToWS(FileOptions opts) throws FileNotFoundException, StorageHubException { + FileContainer item=createFile(opts,sgClient); + item=sgClient.open(item.getId()).asFile(); + + WorkspaceContent content=new WorkspaceContent(); + content.setLink(item.getPublicLink().toString()); + content.setMimetype(item.get().getContent().getMimeType()); + content.setStorageID(item.getId()); + return content; + + } + + public void deleteFromWS(WorkspaceContent toDelete) throws StorageHubException { + sgClient.open(toDelete.getStorageID()).asFile().forceDelete(); + } + + // STATIC SYNCH METHODS + + @Synchronized + private static FolderContainer getApplicationBaseFolder(StorageHubClient sgClient) throws StorageHubException { + FolderContainer vre=sgClient.openVREFolder(); + try { + return vre.openByRelativePath(APP_FOLDER).asFolder(); + }catch(StorageHubException e) { + log.debug("APP Fodler missing. Initializing.."); + FolderContainer toReturn= vre.newFolder(APP_FOLDER, "Base folder for GNA records"); + toReturn.setHidden(); + return toReturn; + } + } + + @Synchronized + private static FolderContainer createFolder(FolderOptions opts, StorageHubClient sgClient) throws StorageHubException { + return opts.getParent().newFolder(opts.getFolderName(),opts.getFolderDescription()); + } + + @Synchronized + private static FileContainer createFile(FileOptions opts, StorageHubClient sgClient) throws StorageHubException { + return opts.getParent().uploadFile(opts.getIs(), opts.getFileName(), opts.getFileDescription()); + } +} diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java index f49fe00..b015780 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -1,15 +1,32 @@ package org.gcube.application.geoportal.service.engine.mongo; import java.io.IOException; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import org.bson.Document; import org.bson.types.ObjectId; +import org.gcube.application.geoportal.common.model.legacy.AssociatedContent; import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; +import org.gcube.application.geoportal.common.model.legacy.OtherContent; +import org.gcube.application.geoportal.common.model.legacy.PersistedContent; +import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; +import org.gcube.application.geoportal.common.model.legacy.SDILayerDescriptor; +import org.gcube.application.geoportal.common.model.legacy.UploadedImage; +import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; import org.gcube.application.geoportal.common.model.legacy.report.PublicationReport; +import org.gcube.application.geoportal.common.rest.TempFile; import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.engine.StorageClientProvider; +import org.gcube.application.geoportal.service.engine.WorkspaceManager; +import org.gcube.application.geoportal.service.engine.WorkspaceManager.FileOptions; +import org.gcube.application.geoportal.service.engine.WorkspaceManager.FolderOptions; import org.gcube.application.geoportal.service.utils.Serialization; +import org.gcube.common.storagehub.client.dsl.FolderContainer; +import org.gcube.common.storagehub.model.exceptions.StorageHubException; import com.fasterxml.jackson.core.JsonProcessingException; import com.mongodb.client.MongoDatabase; @@ -58,11 +75,11 @@ public class ConcessioniMongoManager extends MongoManager{ Concessione toReturn=asConcessione(getById(id,collectionName)); toReturn.setMongo_id(id.toHexString()); - return asConcessione(update(asDocument(toReturn),collectionName)); + return asConcessione(replace(asDocument(toReturn),collectionName)); } public Concessione update(Concessione toRegister) throws IOException { - return asConcessione(update(asDocument(toRegister),collectionName)); + return asConcessione(replace(asDocument(toRegister),collectionName)); } public List list(){ @@ -91,11 +108,66 @@ public class ConcessioniMongoManager extends MongoManager{ toReturn.setDefaults(); toReturn.validate(); publish(toReturn); - return asConcessione(update(asDocument(toReturn),collectionName)); + return asConcessione(replace(asDocument(toReturn),collectionName)); + } + + + public Concessione persistContent(String id, String destinationPath, List files) throws Exception { + Concessione c = getById(id); + WorkspaceManager ws=new WorkspaceManager(); + c.setDefaults(); + + //Check Init Base folder + FolderContainer baseFolder=null; + if(c.getFolderId()==null) { + String folderName="mConcessione"+"_"+c.getNome()+"_"+Serialization.FULL_FORMATTER.format(LocalDateTime.now()); + log.info("Creating folder {} for Concessione ID {} ",folderName,id); + FolderContainer folder=ws.createFolder(new FolderOptions(folderName, "Base Folder for "+c.getNome(),null)); + c.setFolderId(folder.getId()); + } + + baseFolder=ws.getFolderById(c.getFolderId()); + + AssociatedContent section=c.getContentByPath(destinationPath); + + store(section,files,ws,baseFolder); + return asConcessione(replace(asDocument(c),collectionName)); } private static PublicationReport publish(Concessione c) { //TODO implement return null; } + + private static final void store(AssociatedContent content,List files, WorkspaceManager ws, FolderContainer base) throws Exception { + FolderContainer sectionParent=null; + + if(content instanceof RelazioneScavo) + sectionParent = ws .createFolder(new FolderOptions( + "relazione","Relazione di scavo : "+content.getTitolo(),base)); + + else if (content instanceof UploadedImage) + sectionParent = ws .createFolder(new FolderOptions( + "imgs","Immagini rappresentative : "+content.getTitolo(),base)); + + else if (content instanceof SDILayerDescriptor) + //SDI Section + if(content instanceof LayerConcessione) + sectionParent = ws .createFolder(new FolderOptions( + content.getTitolo(),"Layer Concessione : "+content.getTitolo(),ws.getSubFolder(base,"layers"))); + else throw new Exception("Invalid SDI Content "+content); + else if (content instanceof OtherContent ) + sectionParent = ws .createFolder(new FolderOptions( + content.getTitolo(),"Relazione di scavo : "+content.getTitolo(),ws.getSubFolder(base,"other"))); + else throw new Exception("Invalid Content "+content); + + content.setActualContent(new ArrayList()); + StorageClientProvider storage=ImplementationProvider.get().getStorageProvider(); + for(TempFile f : files) { + WorkspaceContent wsContent=ws.storeToWS(new FileOptions(f.getFilename(), storage.open(f.getId()), "Imported via GeoPortal", sectionParent)); + log.debug("Registered "+wsContent+" for "+content); + content.getActualContent().add(wsContent); + } + } + } diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java index 354575e..6ff0374 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java @@ -92,7 +92,7 @@ public abstract class MongoManager { return coll.find(filter,clazz); } - public Document update(Document toUpdate,String collectionName) { + public Document replace(Document toUpdate,String collectionName) { MongoDatabase database=getDatabase(); MongoCollection coll=database.getCollection(collectionName); return coll.findOneAndReplace( diff --git a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java index c017a6b..1883db2 100644 --- a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java +++ b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -1,6 +1,6 @@ package org.gcube.application.geoportal.service.rest; -import java.util.List; +import java.time.LocalDateTime; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; @@ -14,10 +14,14 @@ import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.rest.AddSectionToConcessioneRequest; import org.gcube.application.geoportal.common.rest.InterfaceConstants; import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.service.engine.WorkspaceManager; +import org.gcube.application.geoportal.service.engine.WorkspaceManager.FolderOptions; import org.gcube.application.geoportal.service.engine.mongo.ConcessioniMongoManager; import org.gcube.application.geoportal.service.utils.Serialization; +import org.gcube.common.storagehub.client.dsl.FolderContainer; import org.json.JSONArray; import org.json.JSONObject; @@ -31,7 +35,7 @@ public class ConcessioniOverMongo { @PUT @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - public String update(String jsonString) { + public String replace(String jsonString) { return new GuardedMethod () { @Override protected String run() throws Exception, WebApplicationException { @@ -111,6 +115,26 @@ public class ConcessioniOverMongo { }.execute(); } + + @PUT + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + public String replace(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,String jsonString) { + return new GuardedMethod () { + @Override + protected String run() throws Exception, WebApplicationException { +// Concessione c=Serialization.read(jsonString, Concessione.class); +// ConcessioniMongoManager manager=new ConcessioniMongoManager(); +// manager.update(c); +// +// return Serialization.write(manager.getById(c.getMongo_id())); + throw new RuntimeException("TO IMPLEMENT"); + } + }.execute().getResult(); + } + + @PUT @Produces(MediaType.APPLICATION_JSON) @Path("/publish/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") @@ -128,14 +152,19 @@ public class ConcessioniOverMongo { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("/registerFiles/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") - public String registerFile(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,List files) { + public String registerFile(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,String jsonRequest) { return new GuardedMethod () { @Override protected String run() throws Exception, WebApplicationException { - //TODO FILE register + AddSectionToConcessioneRequest request=Serialization.read(jsonRequest,AddSectionToConcessioneRequest.class); + log.info("Registering {} file(s) for {} Concessione ID {}", + request.getStreams().size(), + request.getDestinationPath(),id); + ConcessioniMongoManager manager=new ConcessioniMongoManager(); + Concessione toReturn= manager.persistContent(id, request.getDestinationPath(), request.getStreams()); - ConcessioniMongoManager manager=new ConcessioniMongoManager(); - return Serialization.write(manager.publish(id)); + log.debug("Returning "+toReturn); + return Serialization.write(toReturn); } }.execute().getResult(); } diff --git a/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java b/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java index 66c9013..08dbaff 100644 --- a/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java +++ b/src/main/java/org/gcube/application/geoportal/service/utils/Serialization.java @@ -1,6 +1,7 @@ package org.gcube.application.geoportal.service.utils; import java.io.IOException; +import java.time.format.DateTimeFormatter; import org.gcube.application.geoportal.model.Record; import org.gcube.application.geoportal.model.concessioni.Concessione; @@ -17,6 +18,9 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; public class Serialization { + + public static final DateTimeFormatter FULL_FORMATTER=DateTimeFormatter.ofPattern("uuuuMMdd_HH-mm-ss"); + public static ObjectMapper mapper; static { diff --git a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java index fb7840c..5184691 100644 --- a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java +++ b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -1,6 +1,10 @@ package org.gcube.application.geoportal.service; -import java.io.IOException; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.FileInputStream; +import java.util.Collections; import java.util.List; import javax.ws.rs.client.Entity; @@ -9,8 +13,15 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.common.utils.StorageUtils; +import org.gcube.application.geoportal.service.legacy.TokenSetter; import org.gcube.application.geoportal.service.utils.Serialization; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ @@ -22,6 +33,11 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ private static final String FILES_PATH="registerFiles"; + @Before + public void setContext() { + TokenSetter.set("/gcube/devsec/devVRE"); + } + @Test public void list() { WebTarget target=target(PATH); @@ -92,8 +108,32 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ } @Test - public void uploadFile() { - // TODO + public void uploadFile() throws Exception { + WebTarget target=target(PATH); + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(TestModel.prepareEmptyConcessione()), MediaType.APPLICATION_JSON)); + Concessione c=check(resp,Concessione.class); + Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); + System.out.println("ID IS "+c.getMongo_id()); + + // Insert section + c.setRelazioneScavo(TestModel.prepareConcessione().getRelazioneScavo()); +// c.getRelazioneScavo().setMongo_id(TestModel.rnd()); + + resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); + c=check(resp,Concessione.class); + + // Add file + TempFile f=new StorageUtils().putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione.pdf"); + AddSectionToConcessioneRequest request=new AddSectionToConcessioneRequest(); + request.setDestinationPath(Paths.RELAZIONE); + request.setStreams(Collections.singletonList(f)); + + resp=target.path(FILES_PATH).path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(request), MediaType.APPLICATION_JSON)); + c=check(resp,Concessione.class); + assertNotNull(c.getRelazioneScavo().getActualContent()); + assertTrue(c.getRelazioneScavo().getActualContent().size()>0); + + System.out.println("ADDED FILE TO "+c); } @Test diff --git a/src/test/java/org/gcube/application/geoportal/service/TestModel.java b/src/test/java/org/gcube/application/geoportal/service/TestModel.java index 2f478b8..25aef77 100644 --- a/src/test/java/org/gcube/application/geoportal/service/TestModel.java +++ b/src/test/java/org/gcube/application/geoportal/service/TestModel.java @@ -3,7 +3,9 @@ package org.gcube.application.geoportal.service; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Arrays; +import java.util.UUID; +import org.bson.types.ObjectId; import org.gcube.application.geoportal.common.model.legacy.AccessPolicy; import org.gcube.application.geoportal.common.model.legacy.Concessione; import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; @@ -57,6 +59,19 @@ public class TestModel { return concessione; } + public static final Concessione setIds(Concessione c) { +// c.setMongo_id(rnd()); + c.getRelazioneScavo().setMongo_id(rnd()); + c.getPosizionamentoScavo().setMongo_id(rnd()); + c.getPianteFineScavo().forEach((LayerConcessione l)->{l.setMongo_id(rnd());}); + c.getImmaginiRappresentative().forEach((UploadedImage i)->{i.setMongo_id(rnd());}); + return c; + } + + public static final String rnd() { + return new ObjectId().toHexString(); + } + public static Concessione prepareConcessione() { Concessione concessione=prepareEmptyConcessione(); From 931bd443c29a056cda586d3b6f77a75d374d4b0e Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Tue, 22 Dec 2020 18:22:56 +0100 Subject: [PATCH 05/10] Publishing mongo Centroids --- .../geoportal/service/engine/SDIManager.java | 319 ++++++++++++++++++ .../service/engine/WorkspaceManager.java | 3 + .../engine/mongo/ConcessioniMongoManager.java | 204 ++++++++--- .../service/engine/mongo/MongoManager.java | 15 +- .../service/engine/mongo/PostgisIndex.java | 169 ++++++++++ .../faults/InvalidStateException.java | 38 +++ .../faults/SDIInteractionException.java | 38 +++ .../service/rest/ConcessioniOverMongo.java | 4 +- .../service/ConcessioniOverMongoTest.java | 173 ++++++---- src/test/resources/log4j.properties | 3 +- 10 files changed, 835 insertions(+), 131 deletions(-) create mode 100644 src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java create mode 100644 src/main/java/org/gcube/application/geoportal/service/engine/mongo/PostgisIndex.java create mode 100644 src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java create mode 100644 src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java new file mode 100644 index 0000000..0dff8c5 --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java @@ -0,0 +1,319 @@ +package org.gcube.application.geoportal.service.engine; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; + +import org.gcube.application.geoportal.common.model.legacy.BBOX; +import org.gcube.application.geoportal.common.model.legacy.GeoServerContent; +import org.gcube.application.geoportal.common.model.legacy.PersistedContent; +import org.gcube.application.geoportal.common.model.legacy.SDILayerDescriptor; +import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; +import org.gcube.application.geoportal.common.utils.Files; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; +import org.gcube.common.storagehub.client.dsl.FileContainer; +import org.gcube.data.transfer.library.DataTransferClient; +import org.gcube.data.transfer.library.TransferResult; +import org.gcube.data.transfer.library.faults.RemoteServiceException; +import org.gcube.data.transfer.model.Destination; +import org.gcube.data.transfer.model.DestinationClashPolicy; +import org.gcube.data.transfer.model.RemoteFileDescriptor; +import org.gcube.spatial.data.gis.GISInterface; +import org.gcube.spatial.data.gis.is.AbstractGeoServerDescriptor; + +import it.geosolutions.geoserver.rest.GeoServerRESTPublisher; +import it.geosolutions.geoserver.rest.GeoServerRESTPublisher.UploadMethod; +import it.geosolutions.geoserver.rest.GeoServerRESTReader; +import it.geosolutions.geoserver.rest.decoder.RESTFeatureType; +import it.geosolutions.geoserver.rest.decoder.RESTLayer; +import it.geosolutions.geoserver.rest.encoder.GSLayerEncoder; +import it.geosolutions.geoserver.rest.encoder.datastore.GSPostGISDatastoreEncoder; +import it.geosolutions.geoserver.rest.encoder.feature.GSFeatureTypeEncoder; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class SDIManager { + + static private String DEFAULT_CRS="EPSG:4326"; + + + private GISInterface gis; + @Getter + private DataTransferClient dtGeoServer; + private String geoserverHostName; + + + public SDIManager() throws SDIInteractionException { + try{ + log.debug("Initializing GIS Interface.."); + gis=GISInterface.get(); + AbstractGeoServerDescriptor geoserver=gis.getCurrentGeoServer(); + if(geoserver==null) + throw new Exception("Unable to contact data transfer for geoserver "); + + log.debug("Found geoserver descriptor "+geoserver); + geoserverHostName=new URL(gis.getCurrentGeoServer().getUrl()).getHost(); + + log.debug("Contacting Data Transfer from geoserver {} ",geoserverHostName); + dtGeoServer=DataTransferClient.getInstanceByEndpoint("http://"+geoserverHostName); + if(!gis.getCurrentGeoServer().getReader().existGeoserver()) + throw new Exception("Geoserver not reachable"); + }catch(Exception e) { + throw new SDIInteractionException("Unable to initialize SDI Manager",e); + } + } + + + public RemoteFileDescriptor getGeoServerRemoteFolder() throws RemoteServiceException { + return dtGeoServer.getWebClient().getInfo("geoserver/GNA"); + } + + public String createWorkspace(String toCreate) throws SDIInteractionException { + try { + if(!gis.getCurrentGeoServer().getReader().getWorkspaceNames().contains(toCreate)) { + log.debug("Creating workspace : "+toCreate); + if(!gis.getCurrentGeoServer().getPublisher().createWorkspace(toCreate)) + throw new SDIInteractionException("Unable to create workspace "+toCreate); + }else log.debug("Workspace "+toCreate+" exists."); + return toCreate; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create workspace "+toCreate,e); + } + } + + + // GEOSERVER-PERSISTENCE-ID / GNA / PROJECT-ID/ LAYER-ID /FILENAME(no extension)/... + + public GeoServerContent pushShapeLayerFileSet(SDILayerDescriptor currentElement,String workspace, String projectId) throws SDIInteractionException{ + try { +// String remoteFolder=null; +// String fileName=null; + + log.debug("Publishing "+currentElement+" files to geoserver @ "+geoserverHostName); + + GeoServerContent content=new GeoServerContent(); + content.setGeoserverHostName(geoserverHostName); + content.setWorkspace(workspace); + WorkspaceManager wsManager=new WorkspaceManager(); + + + currentElement.getActualContent().forEach((PersistedContent c)->{ + try { + if(c instanceof WorkspaceContent) { + WorkspaceContent wc=(WorkspaceContent) c; + FileContainer fc=wsManager.getFileById(wc.getStorageID()); + + String completeFilename=Files.fixFilename(fc.get().getName()); + String filename=completeFilename.contains(".")?completeFilename.substring(0, completeFilename.lastIndexOf(".")):completeFilename; + + + Destination destination=new Destination(completeFilename); + destination.setCreateSubfolders(true); + destination.setOnExistingFileName(DestinationClashPolicy.REWRITE); + destination.setOnExistingSubFolder(DestinationClashPolicy.APPEND); + + destination.setPersistenceId("geoserver"); + destination.setSubFolder("GNA/"+projectId+"/"+ + currentElement.getMongo_id()+"/"+filename); + + log.debug("Sending "+wc+" to "+destination); + TransferResult result=dtGeoServer.httpSource(fc.getPublicLink(), destination); + log.debug("Transferred "+result); + + + content.getFileNames().add(completeFilename); + + content.setGeoserverPath(result.getRemotePath().substring(0,result.getRemotePath().lastIndexOf("/"))); + } + }catch(Throwable t) { + log.warn("Unable to transfer Persisted content"+c,t); + } + + }); + + + + + + + if(content.getFileNames().isEmpty()) + throw new SDIInteractionException("No Persisted content found in "+currentElement); + + String completeFileName=content.getFileNames().get(0); + String filename=completeFileName.contains(".")?completeFileName.substring(0, completeFileName.lastIndexOf(".")):completeFileName; + + String remoteFolder=content.getGeoserverPath(); + + String toSetLayerName=filename; + int count=0; + while(gis.getCurrentGeoServer().getReader().getLayer(workspace,toSetLayerName)!=null){ + count++; + toSetLayerName=filename+"_"+count; + log.debug("layer for "+filename+" already existing, trying "+toSetLayerName); + }; + + + String storeName=toSetLayerName+"_store"; + content.setStore(storeName); + content.setFeatureType(toSetLayerName); + + GeoServerRESTPublisher publisher=gis.getCurrentGeoServer().getPublisher(); + log.debug("Trying to create remote workspace : "+workspace); + createWorkspace(workspace); + log.debug("Publishing remote folder "+remoteFolder); + + URL directoryPath=new URL("file:"+remoteFolder+"/"+completeFileName); + + + //TODO Evaluate SRS + + boolean published=publisher.publishShp( + workspace, + storeName, + null, + toSetLayerName, + // UploadMethod.FILE, // neeeds zip + UploadMethod.EXTERNAL, // needs shp + directoryPath.toURI(), + DEFAULT_CRS, //SRS + ""); // default style + + if(!published) { + throw new SDIInteractionException("Unable to publish layer "+toSetLayerName+" under "+workspace+". Unknown Geoserver fault."); + } + + currentElement.setLayerName(toSetLayerName); + GeoServerRESTReader reader=gis.getCurrentGeoServer().getReader(); + RESTLayer l=reader.getLayer(workspace, toSetLayerName); + RESTFeatureType f= reader.getFeatureType(l); + /*http://geoserver1.dev.d4science.org/geoserver/gna_conc_18/wms? + service=WMS&version=1.1.0&request=GetMap&layers=gna_conc_18:pos& + styles=&bbox=8.62091913167495,40.62975046683799,8.621178639172953,40.630257904721645& + width=392&height=768&srs=EPSG:4326&format=application/openlayers */ + + + currentElement.setWmsLink( + String.format("https://%1$s/geoserver/%2$s/wms?" + +"service=WMS&version=1.1.0&request=GetMap&layers=%2$s:%3$s&" + + "styles=&bbox=%4$f,%5$f,%6$f,%7$f&srs=%8$s&format=application/openlayers&width=%9$d&height=%10$d", + geoserverHostName, + workspace, + toSetLayerName, + f.getMinX(), + f.getMinY(), + f.getMaxX(), + f.getMaxY(), + DEFAULT_CRS, + 400, + 400)); + + currentElement.setWorkspace(workspace); + currentElement.setBbox(new BBOX(f.getMaxY(), f.getMaxX(), f.getMinY(), f.getMinX())); + + + // TODO Metadata + return content; +// } catch (InvalidSourceException | SourceNotSetException | FailedTransferException | InitializationException +// | InvalidDestinationException | DestinationNotSetException e) { +// throw new SDIInteractionException("Unable to transfer fileSet for content "+currentElement,e); + } catch (SDIInteractionException e) { + throw e; + } catch (Throwable t) { + throw new SDIInteractionException("Unexpected internal fault while interacting with SDI.",t); + } + } + + private String createStoreFromPostgisDB(String workspace,String storeName) throws SDIInteractionException { + //SET BY PROVISIONING + GSPostGISDatastoreEncoder encoder=new GSPostGISDatastoreEncoder(storeName); + encoder.setJndiReferenceName("java:comp/env/jdbc/postgres"); + encoder.setLooseBBox(true); + encoder.setDatabaseType("postgis"); + encoder.setEnabled(true); + encoder.setFetchSize(1000); + encoder.setValidateConnections(true); + try { + log.debug("Looking for datastore "+storeName+" under "+workspace); + + if(gis.getCurrentGeoServer().getReader().getDatastore(workspace,storeName)==null) + + if(!gis.getCurrentGeoServer().getDataStoreManager().create(workspace, encoder)) + throw new SDIInteractionException("Unable to create store "+storeName+" in "+workspace); + log.debug("Store "+storeName+" exists under "+workspace); + return storeName; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create store "+storeName,e); + } + + } + + private String publishStyle(File sldFile,String name) throws SDIInteractionException { + try { + if(!gis.getCurrentGeoServer().getReader().existsStyle(name)) { + log.debug("Registering style "+name); + if(!gis.getCurrentGeoServer().getPublisher().publishStyle(sldFile, name)) + throw new SDIInteractionException("Unable to register style "+name); + }else log.debug("Style "+name+" already existing"); + return name; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create style "+name,e); + } + + } + + public String configureCentroidLayer(String name,String workspace,String storeName) throws SDIInteractionException { + + GSFeatureTypeEncoder fte=new GSFeatureTypeEncoder(); + fte.setAbstract("Centroid layer for "+name); + fte.setEnabled(true); + fte.setNativeCRS(DEFAULT_CRS); + fte.setTitle(name); + fte.setName(name); + + + String style="clustered_centroids"; + + GSLayerEncoder layerEncoder=new GSLayerEncoder(); + layerEncoder.setDefaultStyle(style); + layerEncoder.setEnabled(true); + layerEncoder.setQueryable(true); + try { + //Checking workspace + createWorkspace(workspace); + //Checking store + createStoreFromPostgisDB(workspace, storeName); + //Checkig layer + publishStyle(Files.getFileFromResources("styles/clustered_points.sld"),style); + if(gis.getCurrentGeoServer().getReader().getLayer(workspace, name)==null) + if(!gis.getCurrentGeoServer().getPublisher().publishDBLayer(workspace, storeName, fte, layerEncoder)) + throw new SDIInteractionException("Unable to create layer "+name); + log.debug("layer "+name+" already exists"); + return name; + } catch (IllegalArgumentException | MalformedURLException e) { + throw new SDIInteractionException("Unable to create layer "+name,e); + } + + + } + + + public void deleteContent(GeoServerContent toDelete) throws IllegalArgumentException, MalformedURLException, RemoteServiceException { + log.debug("Deleting geoserver layer "+toDelete); + //delete layer + GeoServerRESTPublisher publisher=gis.getCurrentGeoServer().getPublisher(); + //delete store + publisher.removeDatastore(toDelete.getWorkspace(), toDelete.getStore(), true); + //delete WS if empty + GeoServerRESTReader reader=gis.getCurrentGeoServer().getReader(); + if(reader.getDatastores(toDelete.getWorkspace()).isEmpty()) { + log.debug("Deleting emtpy workspace "+toDelete.getWorkspace()); + publisher.removeWorkspace(toDelete.getWorkspace(), true); + } + //delete file + + dtGeoServer.getWebClient().delete(toDelete.getGeoserverPath()); + } + + +} diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java index e4cb836..c265e4e 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/WorkspaceManager.java @@ -6,6 +6,7 @@ import java.io.InputStream; import javax.validation.constraints.NotNull; import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; +import org.gcube.application.geoportal.common.utils.Files; import org.gcube.application.geoportal.model.fault.ConfigurationException; import org.gcube.common.storagehub.client.dsl.FileContainer; import org.gcube.common.storagehub.client.dsl.FolderContainer; @@ -127,11 +128,13 @@ public class WorkspaceManager { @Synchronized private static FolderContainer createFolder(FolderOptions opts, StorageHubClient sgClient) throws StorageHubException { + opts.setFolderName(Files.fixFilename(opts.getFolderName())); return opts.getParent().newFolder(opts.getFolderName(),opts.getFolderDescription()); } @Synchronized private static FileContainer createFile(FileOptions opts, StorageHubClient sgClient) throws StorageHubException { + opts.setFileName(Files.fixFilename(opts.getFileName())); return opts.getParent().uploadFile(opts.getIs(), opts.getFileName(), opts.getFileDescription()); } } diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java index b015780..f8c1e4e 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -9,6 +9,7 @@ import org.bson.Document; import org.bson.types.ObjectId; import org.gcube.application.geoportal.common.model.legacy.AssociatedContent; import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.common.model.legacy.GeoServerContent; import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; import org.gcube.application.geoportal.common.model.legacy.OtherContent; import org.gcube.application.geoportal.common.model.legacy.PersistedContent; @@ -16,14 +17,20 @@ import org.gcube.application.geoportal.common.model.legacy.RelazioneScavo; import org.gcube.application.geoportal.common.model.legacy.SDILayerDescriptor; import org.gcube.application.geoportal.common.model.legacy.UploadedImage; import org.gcube.application.geoportal.common.model.legacy.WorkspaceContent; -import org.gcube.application.geoportal.common.model.legacy.report.PublicationReport; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; import org.gcube.application.geoportal.common.rest.TempFile; +import org.gcube.application.geoportal.common.utils.Files; import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.model.fault.PublishException; import org.gcube.application.geoportal.service.engine.ImplementationProvider; +import org.gcube.application.geoportal.service.engine.SDIManager; import org.gcube.application.geoportal.service.engine.StorageClientProvider; import org.gcube.application.geoportal.service.engine.WorkspaceManager; import org.gcube.application.geoportal.service.engine.WorkspaceManager.FileOptions; import org.gcube.application.geoportal.service.engine.WorkspaceManager.FolderOptions; +import org.gcube.application.geoportal.service.model.internal.faults.InvalidStateException; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; import org.gcube.application.geoportal.service.utils.Serialization; import org.gcube.common.storagehub.client.dsl.FolderContainer; import org.gcube.common.storagehub.model.exceptions.StorageHubException; @@ -37,18 +44,18 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class ConcessioniMongoManager extends MongoManager{ - - + + public ConcessioniMongoManager() throws ConfigurationException { super(); // TODO Auto-generated constructor stub } private static final String collectionName="legacyConcessioni"; private static final String DB_NAME="gna_dev"; - - + + private MongoDatabase db=null; - + @Override @Synchronized protected MongoDatabase getDatabase() { @@ -57,31 +64,47 @@ public class ConcessioniMongoManager extends MongoManager{ } return db; } - + protected static Document asDocument (Concessione c) throws JsonProcessingException { Document toReturn=Document.parse(Serialization.write(c)); if(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()) - toReturn.append(ID, new ObjectId(c.getMongo_id())); + toReturn.append(ID, asId(c.getMongo_id())); return toReturn; } - + protected static Concessione asConcessione (Document d) throws JsonProcessingException, IOException { return Serialization.read(d.toJson(), Concessione.class); } - + + + // *** PUBLIC METHODS + + public Concessione registerNew(Concessione toRegister) throws IOException { + log.trace("Registering {} ",toRegister); + toRegister.setDefaults(); ObjectId id=insert(asDocument(toRegister), collectionName); - + Concessione toReturn=asConcessione(getById(id,collectionName)); - toReturn.setMongo_id(id.toHexString()); - + toReturn.setMongo_id(asString(id)); + return asConcessione(replace(asDocument(toReturn),collectionName)); } - - public Concessione update(Concessione toRegister) throws IOException { + + public Concessione replace(Concessione toRegister) throws IOException { + log.trace("Replacing {} ",toRegister); + toRegister.setDefaults(); return asConcessione(replace(asDocument(toRegister),collectionName)); } - + + public Concessione update(String id,String json) throws IOException { + log.trace("Updating id {} with {} ",id,json); + Concessione toReturn=asConcessione(update(asId(id),asDoc(json),collectionName)); + log.debug("Refreshing defaults.."); + toReturn.setDefaults(); + return asConcessione(replace(asDocument(toReturn),collectionName)); + } + public List list(){ ArrayList toReturn=new ArrayList<>(); iterate(null, collectionName).forEach((Document d)->{ @@ -94,62 +117,135 @@ public class ConcessioniMongoManager extends MongoManager{ }); return toReturn; } - + public Concessione getById(String id) throws JsonProcessingException, IOException { log.debug("Loading by ID "+id); - return asConcessione(getById(new ObjectId(id),collectionName)); + return asConcessione(getById(asId(id),collectionName)); } public void deleteById(String id) { - delete(new ObjectId(id), collectionName); + delete(asId(id), collectionName); } - - public Concessione publish(String id) throws JsonProcessingException, IOException{ - Concessione toReturn=asConcessione(getById(new ObjectId(id),collectionName)); + + public Concessione publish(String id) throws JsonProcessingException, IOException, InvalidStateException{ + Concessione toReturn=asConcessione(getById(asId(id),collectionName)); toReturn.setDefaults(); toReturn.validate(); - publish(toReturn); + + // MATERIALIZE LAYERS + toReturn=publish(toReturn); + // replace(asDocument(toReturn),collectionName); + + // CREATE INDEXES + toReturn=index(toReturn); + // replace(asDocument(toReturn),collectionName); + return asConcessione(replace(asDocument(toReturn),collectionName)); } - - - public Concessione persistContent(String id, String destinationPath, List files) throws Exception { - Concessione c = getById(id); - WorkspaceManager ws=new WorkspaceManager(); - c.setDefaults(); - - //Check Init Base folder - FolderContainer baseFolder=null; - if(c.getFolderId()==null) { - String folderName="mConcessione"+"_"+c.getNome()+"_"+Serialization.FULL_FORMATTER.format(LocalDateTime.now()); - log.info("Creating folder {} for Concessione ID {} ",folderName,id); - FolderContainer folder=ws.createFolder(new FolderOptions(folderName, "Base Folder for "+c.getNome(),null)); - c.setFolderId(folder.getId()); + + + public Concessione persistContent(String id, String destinationPath, List files) throws Exception{ + log.info("Persisting {} files for path {} in concessione ",files.size(),destinationPath,id); + try{ + Concessione c = getById(id); + WorkspaceManager ws=new WorkspaceManager(); + //Check Init Base folder + FolderContainer baseFolder=null; + if(c.getFolderId()==null) { + String folderName=Files.fixFilename("mConcessione"+"_"+c.getNome()+"_"+Serialization.FULL_FORMATTER.format(LocalDateTime.now())); + log.info("Creating folder {} for Concessione ID {} ",folderName,id); + FolderContainer folder=ws.createFolder(new FolderOptions(folderName, "Base Folder for "+c.getNome(),null)); + c.setFolderId(folder.getId()); + } + + log.debug("Folder id is : "+c.getFolderId()); + baseFolder=ws.getFolderById(c.getFolderId()); + + AssociatedContent section=c.getContentByPath(destinationPath); + log.debug("Found section {} for path {}",section,destinationPath); + store(section,files,ws,baseFolder); + log.debug("Updating dafults for {} ",c); + c.setDefaults(); + return asConcessione(replace(asDocument(c),collectionName)); + }catch(Exception e) { + throw new Exception("Unable to save file.",e); } - - baseFolder=ws.getFolderById(c.getFolderId()); - - AssociatedContent section=c.getContentByPath(destinationPath); - - store(section,files,ws,baseFolder); - return asConcessione(replace(asDocument(c),collectionName)); } - - private static PublicationReport publish(Concessione c) { - //TODO implement - return null; + + private static Concessione index(Concessione record) { + log.info("Indexing {} ",record.getId()); + ValidationReport report= new ValidationReport("Index Report "); + PostgisIndex index; + try { + index = new PostgisIndex(record); + index.registerCentroid(); + report.addMessage(ValidationStatus.PASSED, "Registered centroid"); + } catch (SDIInteractionException | PublishException e) { + log.error("Unable to index {} ",record,e); + report.addMessage(ValidationStatus.WARNING, "Internal error while indexing."); + } + return record; + } + + + + + + private static Concessione publish(Concessione conc) { + + // CHECK CONDITION BY PROFILE + + + log.debug("Publishing "+conc.getNome()); + + ValidationReport report=new ValidationReport("Publish report"); + try { + SDIManager sdiManager=new SDIManager(); + ArrayList list=new ArrayList(); + + //Concessione + String workspace= sdiManager.createWorkspace("gna_conc_"+conc.getMongo_id()); + list.add(conc.getPosizionamentoScavo()); + list.addAll(conc.getPianteFineScavo()); + + for(AssociatedContent c:list) { + if(c instanceof LayerConcessione) { + try { + List p=c.getActualContent(); + + GeoServerContent geoserverPersisted=sdiManager.pushShapeLayerFileSet((SDILayerDescriptor)c, workspace, conc.getMongo_id()); + // geoserverPersisted.setAssociated(c); + + + p.add(geoserverPersisted); + c.setActualContent(p); + }catch(SDIInteractionException e) { + log.warn("Unable to publish layers.",e); + report.addMessage(ValidationStatus.WARNING, "Layer "+c.getTitolo()+" non pubblicato."); + } + report.addMessage(ValidationStatus.PASSED, "Pubblicato layer "+c.getTitolo()); + } + } + + + } catch (SDIInteractionException e1) { + report.addMessage(ValidationStatus.WARNING, "Unable to publish layers "+e1.getMessage()); + } + + conc.setReport(report); + return conc; } - + private static final void store(AssociatedContent content,List files, WorkspaceManager ws, FolderContainer base) throws Exception { FolderContainer sectionParent=null; - + if(content instanceof RelazioneScavo) sectionParent = ws .createFolder(new FolderOptions( "relazione","Relazione di scavo : "+content.getTitolo(),base)); - + else if (content instanceof UploadedImage) sectionParent = ws .createFolder(new FolderOptions( "imgs","Immagini rappresentative : "+content.getTitolo(),base)); - + else if (content instanceof SDILayerDescriptor) //SDI Section if(content instanceof LayerConcessione) @@ -160,7 +256,7 @@ public class ConcessioniMongoManager extends MongoManager{ sectionParent = ws .createFolder(new FolderOptions( content.getTitolo(),"Relazione di scavo : "+content.getTitolo(),ws.getSubFolder(base,"other"))); else throw new Exception("Invalid Content "+content); - + content.setActualContent(new ArrayList()); StorageClientProvider storage=ImplementationProvider.get().getStorageProvider(); for(TempFile f : files) { @@ -169,5 +265,5 @@ public class ConcessioniMongoManager extends MongoManager{ content.getActualContent().add(wsContent); } } - + } diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java index 6ff0374..f57cafb 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/MongoManager.java @@ -13,6 +13,7 @@ import com.mongodb.client.FindIterable; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import com.mongodb.client.model.FindOneAndReplaceOptions; +import com.mongodb.client.model.FindOneAndUpdateOptions; import com.mongodb.client.model.ReturnDocument; import lombok.extern.slf4j.Slf4j; @@ -24,6 +25,11 @@ public abstract class MongoManager { protected static final String ID="_id"; + protected static final ObjectId asId(String id) {return new ObjectId(id);} + protected static final String asString(ObjectId id) {return id.toHexString();} + + protected static final String asString(Document d) {return d.toJson();} + protected static final Document asDoc(String json) {return Document.parse(json);} public MongoManager() throws ConfigurationException { client=ImplementationProvider.get().getMongoClientProvider().getObject(); @@ -100,7 +106,14 @@ public abstract class MongoManager { } - + public Document update(ObjectId id, Document updateSet, String collectionName) { + MongoDatabase database=getDatabase(); + MongoCollection coll=database.getCollection(collectionName); + return coll.findOneAndUpdate( + eq(ID,id), + updateSet, + new FindOneAndUpdateOptions().returnDocument(ReturnDocument.AFTER)); + } //********** PROFILES diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/PostgisIndex.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/PostgisIndex.java new file mode 100644 index 0000000..a51c5b3 --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/PostgisIndex.java @@ -0,0 +1,169 @@ +package org.gcube.application.geoportal.service.engine.mongo; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.gcube.application.geoportal.common.model.legacy.Concessione; +import org.gcube.application.geoportal.model.db.DBConstants; +import org.gcube.application.geoportal.model.db.PostgisTable; +import org.gcube.application.geoportal.model.db.PostgisTable.Field; +import org.gcube.application.geoportal.model.db.PostgisTable.FieldType; +import org.gcube.application.geoportal.model.fault.ConfigurationException; +import org.gcube.application.geoportal.model.fault.PublishException; +import org.gcube.application.geoportal.service.engine.SDIManager; +import org.gcube.application.geoportal.service.model.internal.faults.SDIInteractionException; +import org.gcube.application.geoportal.service.utils.Serialization; +import org.gcube.application.geoportal.storage.PostgisDBManager; +import org.gcube.application.geoportal.storage.PostgisDBManagerI; + +import lombok.Getter; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class PostgisIndex { + + @NonNull + @Getter + private Concessione record; + private SDIManager sdiManager; + + + public PostgisIndex(Concessione record) throws SDIInteractionException { + super(); + this.record = record; + this.sdiManager=new SDIManager(); + } + + + protected PostgisTable getCentroidsTable() { + return DBConstants.Concessioni.CENTROIDS; + } + + + public void registerCentroid() throws PublishException{ + + try { + log.debug("Evaluating Centroid"); + Map centroidRow=evaluateCentroid(); + + log.debug("Contacting postgis DB .. "); + PostgisDBManagerI db=PostgisDBManager.get(); + + PostgisTable centroidsTable=getCentroidsTable(); + log.debug("Inserting / updated centroid Row {} ",centroidRow); + + PreparedStatement ps = db.prepareInsertStatement(centroidsTable, true, true); + + log.debug("Deleting centroid if present. ID is "+record.getId()); + db.deleteByFieldValue(centroidsTable, new Field(DBConstants.Concessioni.PRODUCT_ID,FieldType.TEXT), record.getId()+""); + + centroidsTable.fillCSVPreparedStatament(centroidRow, ps, false); + ps.executeUpdate(); + db.commit(); + + initCentroidLayer(); + + + }catch(SQLException e) { + log.warn("Unable to publish Centroid for record "+record,e); + throw new PublishException("Unable to publish centroid.",e, null); + }catch(SDIInteractionException e) { + log.warn("Unable to publish Centroid Layer for record type "+getRecord().getRecordType(),e); + throw new PublishException("Unable to publish centroid.",e, null); + } catch (ConfigurationException e) { + log.warn("Unable to contact centroids db "+getRecord().getRecordType(),e); + throw new PublishException("Unable to publish centroid.",e, null); + } + + } + + protected void initCentroidLayer() throws SDIInteractionException { + log.debug("Checking for centroid layer configuration.. "); + + + sdiManager.configureCentroidLayer("centroids_concessioni", "gna", "gna_postgis"); + } + + + public void removeCentroid() { + try { + PostgisDBManagerI db=PostgisDBManager.get(); + PostgisTable centroidsTable=getCentroidsTable(); + log.debug("Deleting centroid if present. ID is "+record.getId()); + db.deleteByFieldValue(centroidsTable, new Field(DBConstants.Concessioni.PRODUCT_ID,FieldType.TEXT), record.getId()+""); + }catch(Exception e) { + log.warn("Unable to remove centroid ",e); + } + } + + + protected Map evaluateCentroid(){ + + + // CENTROID + Map centroidsRow=new HashMap(); + centroidsRow.put(DBConstants.Concessioni.PRODUCT_ID, record.getId()+""); + centroidsRow.put(DBConstants.Concessioni.ANNO, record.getDataInizioProgetto().getYear()+""); + centroidsRow.put(DBConstants.Concessioni.NOME, record.getNome()); + centroidsRow.put(DBConstants.Concessioni.REGIONE, ""); //TODO + + + + if(record.getCentroidLat()==null||record.getCentroidLat()==0) + try { + log.debug("Evaluating Centroid latitude for record "+record); + record.setCentroidLat((record.getPosizionamentoScavo().getBbox().getMaxLat()+ + record.getPosizionamentoScavo().getBbox().getMinLat())/2); + }catch (Throwable t) { + log.warn("Unable to evaluate centroid latitude "+t); + } + + if(record.getCentroidLong()==null||record.getCentroidLong()==0) + try { + log.debug("Evaluating Centroid Longituted for record "+record); + record.setCentroidLong((record.getPosizionamentoScavo().getBbox().getMaxLong()+ + record.getPosizionamentoScavo().getBbox().getMinLong())/2); + }catch (Throwable t) { + log.warn("Unable to evaluate centroid latitude "+t); + } + + + centroidsRow.put(DBConstants.Defaults.XCOORD_FIELD, record.getCentroidLong()+""); + centroidsRow.put(DBConstants.Defaults.YCOORD_FIELD, record.getCentroidLat()+""); + + //Updated Schema + centroidsRow.put(DBConstants.Concessioni.DESCRIZIONE,record.getIntroduzione()); + centroidsRow.put(DBConstants.Concessioni.CONTENUTO,record.getDescrizioneContenuto()); + centroidsRow.put(DBConstants.Concessioni.AUTORE,asString(record.getAuthors())); + centroidsRow.put(DBConstants.Concessioni.CONTRIBUTORE,record.getContributore()); + centroidsRow.put(DBConstants.Concessioni.TITOLARE,asString(record.getTitolari())); + centroidsRow.put(DBConstants.Concessioni.RESPONSABILE,record.getResponsabile()); + centroidsRow.put(DBConstants.Concessioni.EDITORE,record.getEditore()); + centroidsRow.put(DBConstants.Concessioni.FINANZIAMENTO,asString(record.getFontiFinanziamento())); + centroidsRow.put(DBConstants.Concessioni.SOGGETTO,asString(record.getSoggetto())); + centroidsRow.put(DBConstants.Concessioni.RISORSE,asString(record.getRisorseCorrelate())); + centroidsRow.put(DBConstants.Concessioni.DATE_SCAVO,Serialization.FULL_FORMATTER.format(record.getDataFineProgetto())); + centroidsRow.put(DBConstants.Concessioni.DATA_ARCHIVIAZIONE,Serialization.FULL_FORMATTER.format(record.getLastUpdateTime())); + centroidsRow.put(DBConstants.Concessioni.VERSIONE,record.getVersion()); + centroidsRow.put(DBConstants.Concessioni.LICENZA,record.getLicenzaID()); + centroidsRow.put(DBConstants.Concessioni.TITOLARE_LICENZA,asString(record.getTitolareLicenza())); + centroidsRow.put(DBConstants.Concessioni.ACCESSO,record.getPolicy().toString()); + centroidsRow.put(DBConstants.Concessioni.PAROLE_CHIAVE,asString(record.getParoleChiaveLibere())); + + return centroidsRow; + } + + + private static String asString(Collection coll) { + if(coll==null||coll.isEmpty()) return ""; + StringBuilder builder=new StringBuilder(); + for(Object t : coll) { + builder.append(t.toString() +","); + } + return builder.substring(0, builder.lastIndexOf(",")); + } +} diff --git a/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java b/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java new file mode 100644 index 0000000..f2f39fa --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/InvalidStateException.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class InvalidStateException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 8926481061304048080L; + + public InvalidStateException() { + super(); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public InvalidStateException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java b/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java new file mode 100644 index 0000000..635b714 --- /dev/null +++ b/src/main/java/org/gcube/application/geoportal/service/model/internal/faults/SDIInteractionException.java @@ -0,0 +1,38 @@ +package org.gcube.application.geoportal.service.model.internal.faults; + +public class SDIInteractionException extends Exception { + + /** + * + */ + private static final long serialVersionUID = 1L; + + public SDIInteractionException() { + super(); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message, Throwable cause, boolean enableSuppression, + boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message, Throwable cause) { + super(message, cause); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(String message) { + super(message); + // TODO Auto-generated constructor stub + } + + public SDIInteractionException(Throwable cause) { + super(cause); + // TODO Auto-generated constructor stub + } + + + +} diff --git a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java index 1883db2..e6126d0 100644 --- a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java +++ b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -41,7 +41,7 @@ public class ConcessioniOverMongo { protected String run() throws Exception, WebApplicationException { Concessione c=Serialization.read(jsonString, Concessione.class); ConcessioniMongoManager manager=new ConcessioniMongoManager(); - manager.update(c); + manager.replace(c); return Serialization.write(manager.getById(c.getMongo_id())); } @@ -120,7 +120,7 @@ public class ConcessioniOverMongo { @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) @Path("{"+InterfaceConstants.Parameters.PROJECT_ID+"}") - public String replace(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,String jsonString) { + public String update(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,String jsonString) { return new GuardedMethod () { @Override protected String run() throws Exception, WebApplicationException { diff --git a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java index 5184691..554b3fd 100644 --- a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java +++ b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -1,10 +1,12 @@ package org.gcube.application.geoportal.service; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.FileInputStream; -import java.util.Collections; +import java.io.FileNotFoundException; +import java.util.ArrayList; import java.util.List; import javax.ws.rs.client.Entity; @@ -14,99 +16,112 @@ import javax.ws.rs.core.Response; import org.gcube.application.geoportal.common.model.legacy.Concessione; import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; import org.gcube.application.geoportal.common.rest.AddSectionToConcessioneRequest; import org.gcube.application.geoportal.common.rest.TempFile; import org.gcube.application.geoportal.common.utils.Files; import org.gcube.application.geoportal.common.utils.StorageUtils; import org.gcube.application.geoportal.service.legacy.TokenSetter; import org.gcube.application.geoportal.service.utils.Serialization; +import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import com.fasterxml.jackson.core.JsonProcessingException; + public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ - + private static final String PATH="mongo-concessioni"; private static final String PUBLISH_PATH="publish"; private static final String FILES_PATH="registerFiles"; - - + + @Before public void setContext() { TokenSetter.set("/gcube/devsec/devVRE"); } + + + private static Concessione upload(WebTarget target,String id, String path, String ...files) throws Exception { + ArrayList array=new ArrayList(); + for(String file:files) + array.add(new StorageUtils().putOntoStorage(new FileInputStream( + Files.getFileFromResources("concessioni/"+file)), file)); + + + AddSectionToConcessioneRequest request=new AddSectionToConcessioneRequest(); + request.setDestinationPath(path); + + request.setStreams(array); + + return check(target.path(FILES_PATH).path(id).request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(request), MediaType.APPLICATION_JSON)),Concessione.class); + + } + + + private static Concessione publish(WebTarget target, Concessione conc) throws Exception { + Response resp=target.path(PUBLISH_PATH).path(conc.getMongo_id()).request(MediaType.APPLICATION_JSON). + put(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + } + private static Concessione register(WebTarget target, Concessione c) throws Exception { + Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); + return check(resp,Concessione.class); + } + + private static Concessione get(WebTarget target) throws Exception { + return register(target,TestModel.prepareConcessione()); + } + + // ********** TESTS @Test public void list() { WebTarget target=target(PATH); System.out.println(target.request(MediaType.APPLICATION_JSON).get(List.class)); } - + @Test public void createNew() throws Exception { WebTarget target=target(PATH); - Concessione conc=TestModel.prepareConcessione(); - Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); - Concessione c=check(resp,Concessione.class); + Concessione c=register(target,TestModel.prepareConcessione()); Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); - System.out.println("ID IS "+c.getMongo_id()); } @Test public void delete() throws Exception { WebTarget target=target(PATH); - Concessione conc=TestModel.prepareConcessione(); - Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); - Concessione c=check(resp,Concessione.class); - System.out.println("ID IS "+c.getMongo_id()); - - Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); - - resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).delete(); - check(resp,null); + Concessione c = get(target); + check(target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).delete(),null); } - - + + @Test public void getById() throws Exception { WebTarget target=target(PATH); - Concessione conc=TestModel.prepareConcessione(); - Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); - Concessione c=check(resp,Concessione.class); - - Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); - - resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); + Concessione c = get(target); + Response resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); Concessione loaded=check(resp,Concessione.class); - System.out.println("Got by ID "+loaded); - Assert.assertTrue(loaded.getMongo_id()!=null&&!loaded.getMongo_id().isEmpty()); + System.out.println("Got by ID "+loaded); } - - + + @Test public void update() throws Exception { - WebTarget target=target(PATH); - Concessione conc=TestModel.prepareConcessione(); - Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); - Concessione c=check(resp,Concessione.class); - - resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); - Concessione loaded=check(resp,Concessione.class); - - Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); - System.out.println("Modifying "+loaded); - String newTitle="Questo titolo l'ho modificato mo'"; - loaded.setNome(newTitle); - - resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(loaded), MediaType.APPLICATION_JSON)); + WebTarget target=target(PATH); + Concessione c = get(target); + String newTitle="Questo titolo l'ho modificato mo nel test quello proprio apposta pewr questa cosa'"; + c.setNome(newTitle); + Response resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); Assert.assertTrue(check(resp,Concessione.class).getNome().equals(newTitle)); - } - + @Test public void uploadFile() throws Exception { WebTarget target=target(PATH); @@ -114,45 +129,57 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ Concessione c=check(resp,Concessione.class); Assert.assertTrue(c.getMongo_id()!=null&&!c.getMongo_id().isEmpty()); System.out.println("ID IS "+c.getMongo_id()); - + // Insert section c.setRelazioneScavo(TestModel.prepareConcessione().getRelazioneScavo()); -// c.getRelazioneScavo().setMongo_id(TestModel.rnd()); - + // c.getRelazioneScavo().setMongo_id(TestModel.rnd()); + resp=target.request(MediaType.APPLICATION_JSON).put(Entity.entity(Serialization.write(c), MediaType.APPLICATION_JSON)); - c=check(resp,Concessione.class); + - // Add file - TempFile f=new StorageUtils().putOntoStorage(new FileInputStream(Files.getFileFromResources("concessioni/relazione.pdf")), "relazione.pdf"); - AddSectionToConcessioneRequest request=new AddSectionToConcessioneRequest(); - request.setDestinationPath(Paths.RELAZIONE); - request.setStreams(Collections.singletonList(f)); - resp=target.path(FILES_PATH).path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(request), MediaType.APPLICATION_JSON)); - c=check(resp,Concessione.class); + + c=upload(target,c.getMongo_id(),Paths.RELAZIONE,"relazione.pdf"); assertNotNull(c.getRelazioneScavo().getActualContent()); assertTrue(c.getRelazioneScavo().getActualContent().size()>0); - - System.out.println("ADDED FILE TO "+c); + + System.out.println("File is "+c.getRelazioneScavo().getActualContent().get(0)); } + + @Test public void publish() throws Exception { WebTarget target=target(PATH); - Concessione conc=TestModel.prepareConcessione(); - Response resp=target.request(MediaType.APPLICATION_JSON).post(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); - Concessione c=check(resp,Concessione.class); -// System.out.println("ID IS "+c.getMongo_id()); - resp=target.path(c.getMongo_id()).request(MediaType.APPLICATION_JSON).get(); - Concessione loaded=check(resp,Concessione.class); - System.out.println("Modifying "+loaded); - String newTitle="Questo titolo l'ho modificato mo'"; - loaded.setNome(newTitle); + Concessione c=TestModel.prepareConcessione(); + + c.setNome("Concessione : publish test"); + + - resp=target.path(PUBLISH_PATH).path(c.getMongo_id()).request(MediaType.APPLICATION_JSON). - put(Entity.entity(Serialization.write(conc), MediaType.APPLICATION_JSON)); + // Register new + c=register(target,c); + + //Upload files + upload(target,c.getMongo_id(),Paths.RELAZIONE,"relazione.pdf"); + upload(target,c.getMongo_id(),Paths.POSIZIONAMENTO,"pos.shp","pos.shx"); + + + upload(target,c.getMongo_id(),Paths.piantaByIndex(0),"pos.shp","pos.shx"); + upload(target,c.getMongo_id(),Paths.imgByIndex(0),"immagine.png"); + upload(target,c.getMongo_id(),Paths.imgByIndex(1),"immagine2.png"); + + + + // Immagini + Concessione published=publish(target, c); + System.out.println("Published : "+published); + assertNotNull(published.getReport()); + assertNotEquals(published.getReport().getStatus(),ValidationStatus.ERROR); - // TODO Validation -// Assert.assertTrue(check(resp,Concessione.class).getNome().equals(newTitle)); } + + + + } diff --git a/src/test/resources/log4j.properties b/src/test/resources/log4j.properties index 8696f85..8524e96 100644 --- a/src/test/resources/log4j.properties +++ b/src/test/resources/log4j.properties @@ -1,5 +1,6 @@ -log4j.rootLogger=DEBUG, stdout +log4j.rootLogger=WARN, stdout +log4j.org.gcube.Threshold=DEBUG #CONSOLE log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.Threshold=DEBUG From b1e88ad283f27ee34239a114363eb83122083daf Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Tue, 22 Dec 2020 18:29:10 +0100 Subject: [PATCH 06/10] Explicit forEach --- .../engine/mongo/ConcessioniMongoManager.java | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java index f8c1e4e..1432aec 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.function.Consumer; import org.bson.Document; import org.bson.types.ObjectId; @@ -105,16 +106,22 @@ public class ConcessioniMongoManager extends MongoManager{ return asConcessione(replace(asDocument(toReturn),collectionName)); } + + public List list(){ ArrayList toReturn=new ArrayList<>(); - iterate(null, collectionName).forEach((Document d)->{ - try { - toReturn.add(asConcessione(d)); - }catch(Throwable t) { - log.error("Unable to read Document as concessione ",t); - log.debug("Document was "+d.toJson()); - } - }); + iterate(null, collectionName).forEach( + new Consumer() { + @Override + public void accept(Document d) { + try { + toReturn.add(asConcessione(d)); + }catch(Throwable t) { + log.error("Unable to read Document as concessione ",t); + log.debug("Document was "+d.toJson()); + } + } + }); return toReturn; } From 2d2bb132e07c95abfe5feb727b780d80bdf2c3d8 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Mon, 4 Jan 2021 16:58:40 +0100 Subject: [PATCH 07/10] Publication fixes --- CHANGELOG.md | 3 +- .../geoportal/service/engine/SDIManager.java | 5 ++- .../service/engine/StorageClientProvider.java | 2 ++ .../engine/mongo/ConcessioniMongoManager.java | 1 + .../service/rest/ConcessioniOverMongo.java | 6 ++-- .../service/ConcessioniOverMongoTest.java | 31 ++++++++++-------- .../geoportal/service/TestModel.java | 9 +++-- src/test/resources/concessioni/pianta.shp | Bin 0 -> 96444 bytes src/test/resources/concessioni/pianta.shx | Bin 0 -> 3308 bytes 9 files changed, 35 insertions(+), 22 deletions(-) create mode 100644 src/test/resources/concessioni/pianta.shp create mode 100644 src/test/resources/concessioni/pianta.shx diff --git a/CHANGELOG.md b/CHANGELOG.md index 81ef133..d5eaf74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm Mongo integration with Concessione Project interface TempFile management -WorkspaceContent for Concessioni-over-mongo - +WorkspaceContent and publication for Concessioni-over-mongo ## [v1.0.3] 2020-11-11 Fixed HTTP method diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java index 0dff8c5..fc86bdf 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java @@ -98,6 +98,8 @@ public class SDIManager { WorkspaceManager wsManager=new WorkspaceManager(); + + currentElement.getActualContent().forEach((PersistedContent c)->{ try { if(c instanceof WorkspaceContent) { @@ -160,7 +162,8 @@ public class SDIManager { GeoServerRESTPublisher publisher=gis.getCurrentGeoServer().getPublisher(); log.debug("Trying to create remote workspace : "+workspace); - createWorkspace(workspace); + createWorkspace(workspace); + log.debug("Publishing remote folder "+remoteFolder); URL directoryPath=new URL("file:"+remoteFolder+"/"+completeFileName); diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/StorageClientProvider.java b/src/main/java/org/gcube/application/geoportal/service/engine/StorageClientProvider.java index 6ad3db3..a963a8d 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/StorageClientProvider.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/StorageClientProvider.java @@ -39,6 +39,8 @@ public class StorageClientProvider extends AbstractScopedMap{ protected void dispose(IClient toDispose) { try { toDispose.close(); + }catch (NullPointerException e) { + // expected if closed without uploading }catch(Throwable t) { log.warn(" unable to dispose "+toDispose,t); } diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java index 1432aec..7f0eeb0 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/mongo/ConcessioniMongoManager.java @@ -271,6 +271,7 @@ public class ConcessioniMongoManager extends MongoManager{ log.debug("Registered "+wsContent+" for "+content); content.getActualContent().add(wsContent); } + content.setMongo_id(asString(new ObjectId())); } } diff --git a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java index e6126d0..4e502d5 100644 --- a/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java +++ b/src/main/java/org/gcube/application/geoportal/service/rest/ConcessioniOverMongo.java @@ -27,7 +27,7 @@ import org.json.JSONObject; import lombok.extern.slf4j.Slf4j; -@Path("mongo-concessioni") +@Path(InterfaceConstants.Methods.MONGO_CONCESSIONI) @Slf4j public class ConcessioniOverMongo { @@ -137,7 +137,7 @@ public class ConcessioniOverMongo { @PUT @Produces(MediaType.APPLICATION_JSON) - @Path("/publish/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + @Path("/{"+InterfaceConstants.Methods.PUBLISH_PATH+"}/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") public String publish(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id) { return new GuardedMethod () { @Override @@ -151,7 +151,7 @@ public class ConcessioniOverMongo { @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) - @Path("/registerFiles/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") + @Path("/"+InterfaceConstants.Methods.REGISTER_FILES_PATH+"/{"+InterfaceConstants.Parameters.PROJECT_ID+"}") public String registerFile(@PathParam(InterfaceConstants.Parameters.PROJECT_ID) String id,String jsonRequest) { return new GuardedMethod () { @Override diff --git a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java index 554b3fd..4b64d40 100644 --- a/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java +++ b/src/test/java/org/gcube/application/geoportal/service/ConcessioniOverMongoTest.java @@ -1,11 +1,11 @@ package org.gcube.application.geoportal.service; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; @@ -16,28 +16,26 @@ import javax.ws.rs.core.Response; import org.gcube.application.geoportal.common.model.legacy.Concessione; import org.gcube.application.geoportal.common.model.legacy.Concessione.Paths; -import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport; +import org.gcube.application.geoportal.common.model.legacy.LayerConcessione; import org.gcube.application.geoportal.common.model.legacy.report.ValidationReport.ValidationStatus; import org.gcube.application.geoportal.common.rest.AddSectionToConcessioneRequest; +import org.gcube.application.geoportal.common.rest.InterfaceConstants; import org.gcube.application.geoportal.common.rest.TempFile; import org.gcube.application.geoportal.common.utils.Files; import org.gcube.application.geoportal.common.utils.StorageUtils; import org.gcube.application.geoportal.service.legacy.TokenSetter; import org.gcube.application.geoportal.service.utils.Serialization; -import org.gcube.contentmanagement.blobstorage.transport.backend.RemoteBackendException; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import com.fasterxml.jackson.core.JsonProcessingException; - public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ - private static final String PATH="mongo-concessioni"; + private static final String PATH=InterfaceConstants.Methods.MONGO_CONCESSIONI; - private static final String PUBLISH_PATH="publish"; - private static final String FILES_PATH="registerFiles"; + private static final String PUBLISH_PATH=InterfaceConstants.Methods.PUBLISH_PATH; + private static final String FILES_PATH=InterfaceConstants.Methods.REGISTER_FILES_PATH; @Before @@ -151,7 +149,7 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ @Test public void publish() throws Exception { WebTarget target=target(PATH); - Concessione c=TestModel.prepareConcessione(); + Concessione c=TestModel.prepareConcessione(1,2); c.setNome("Concessione : publish test"); @@ -164,8 +162,8 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ upload(target,c.getMongo_id(),Paths.RELAZIONE,"relazione.pdf"); upload(target,c.getMongo_id(),Paths.POSIZIONAMENTO,"pos.shp","pos.shx"); - - upload(target,c.getMongo_id(),Paths.piantaByIndex(0),"pos.shp","pos.shx"); + // Clash on workspaces + upload(target,c.getMongo_id(),Paths.piantaByIndex(0),"pianta.shp","pianta.shx"); upload(target,c.getMongo_id(),Paths.imgByIndex(0),"immagine.png"); upload(target,c.getMongo_id(),Paths.imgByIndex(1),"immagine2.png"); @@ -175,8 +173,15 @@ public class ConcessioniOverMongoTest extends BasicServiceTestUnit{ Concessione published=publish(target, c); System.out.println("Published : "+published); assertNotNull(published.getReport()); - assertNotEquals(published.getReport().getStatus(),ValidationStatus.ERROR); - + assertEquals(published.getReport().getStatus(),ValidationStatus.PASSED); + + assertEquals(published.getImmaginiRappresentative().size(),2); + assertEquals(published.getPianteFineScavo().size(),1); + assertNotNull(published.getPosizionamentoScavo().getWmsLink()); + for(LayerConcessione l : published.getPianteFineScavo()) + assertNotNull(l.getWmsLink()); + assertNotNull(published.getCentroidLat()); + assertNotNull(published.getCentroidLong()); } diff --git a/src/test/java/org/gcube/application/geoportal/service/TestModel.java b/src/test/java/org/gcube/application/geoportal/service/TestModel.java index 25aef77..3bfc3c5 100644 --- a/src/test/java/org/gcube/application/geoportal/service/TestModel.java +++ b/src/test/java/org/gcube/application/geoportal/service/TestModel.java @@ -71,8 +71,11 @@ public class TestModel { public static final String rnd() { return new ObjectId().toHexString(); } - public static Concessione prepareConcessione() { + return prepareConcessione(4,2); + } + + public static Concessione prepareConcessione(int pianteCount ,int imgsCount) { Concessione concessione=prepareEmptyConcessione(); @@ -89,7 +92,7 @@ public class TestModel { concessione.setRelazioneScavo(relScavo); //Immagini rappresentative ArrayList imgs=new ArrayList<>(); - for(int i=0;i<5;i++) { + for(int i=0;i piante=new ArrayList(); - for(int i=0;i<4;i++) { + for(int i=0;i>ohM{=3ulbRAaeQ0m+w+#oGuU=++~fydXxY{V;ID)CH3PF)D(CFN z5ZkJ^7V#`sM!VH>*jF74T?gLgG8-Qa9j-popXt%-T1#Z3;2 zgw4AH%-6lT;lg<!L{26=3mpw=~$YSa#J5uK>IFb{zoEVkHIal}Bvb_LVpJx$G^3 zGJ&nNM|^~RuI+>~z=HWW&D_ihIiTND7T8QKX&-C@MD*pBtTf?+SQOwC}`?=YU@LgGd@Phd{NxirchC1Fj?VERpiiAwg5V&IZcpKoe2B~9#S*|lqJ5+$T z-{4Xb8ec)liX+MEa=?X6bmhf_RM|`H`l+Q>hXggW|d-@OK(G_P(Lu(p=iYT(5o5fZ?H^>HPmMbsxfpPL`KZdt2e8^$*%->4z<@YH@$@nvDm%jhg1zYC(&&9eQY}L{ZX_Rt&yF$+~z;Yq4 zUIDXMS1%s-e8AyEUlEGk^W)Jc!0m18sE;ONVrL+CQl5=8{F#>2?m0BR@If2ARi(#Q z?8bz`#-G#E;(Bv0nBP)`IjvmT@QY0@pV7wNpn2@4Oq8l|L>@$D>0)3Sg<~> zN21)=fo45#lM%ni+4hPqr@i1~)TFQQU34~))&%+_$qoF=yBquqwv0=yFtvqkhgwm8 z)b;)LnM=S+hFFOLvskHbPX2=qy@|tWz%Tp$o%W&DhXcZZdmfE?2VUaNRbgPJ#rt@h zJMvlkeR%_3ac|5RVBr(CR5#NuG-M^!{p6A4Fz`$BoO&R?r_bjA;N+Z0nnywXTuEZw z*n&P-Onu{x+*g0p??tW|yLX$Sru{rc3)# zv%1g^zSg+hPuajO6JG9v|CxFFI>ND)H7!NGiAFgFMQfPfizt`D*i?jXEuPy+# zhL3lI-R5v@i*p<>`pnkX%>t3#u&teDKy@=;W_z_$`vUkwZ!%-yo3%pbC$Recwg})w zUa6*t{l0tsuv~RQ&5_`ZbX9}Evw7&~Sz-(v)|9*hyzS22|M*(_b$DJUyl2c#@YgF7 zj#9qv^(Cm6D+PBcx-W#aevT>TVk-?yq&>puc;FS}8e22T5YH&;X7?OdTbq}N%WRpi zdDY^nu-#rdP6qq~z0caf!$af4fmtm3eo?p5e@H?`)&9?{(Y#kEla7ztI7{(2dOl|H95MIKt=NxCK-L8;>km z3R~v;Kt|RGczRx8DfqIwzjJ_(Oy9N*nB`)*ACzyN2VO8fmy%m5Jyf7`#=vD(xI4I< zc};V_Cv3tQoXKAguJ(a1bjoo9|8jrWoIp*@GEBV-~KjBmTWq>CiogIu=?LoiB z1GCs>dt20jr!Vic1%FCv-D2S9CO_hU4bz8EPd1#qA%}Pd_gdD2zi`H&0GMg{e0S{z z)X8|YPPfOv-^zBbf&Ij7?Yw!GiihUb9wIzwj4d*FBPHiUxDsn#z8*7&&96nNFo zrx$<|Vl(d`pYz9^w9ltlZ#)V-;F~l(msc%h`vSAMl&j3$fcpEL|4VyBP4~KT?7sib zpJnES{#~5);Tq!Gf6mWCzk54olmM@dUTgwvTTxRDJhb;yRbb&Bum1o${#HH=ysT3~ z4D|`F4{pie=%;(axaobwaJQ4ZY<3a8uZspx!`XWNoL(z1))~7eXk6G;fqUBdii%(0 zLWZ>UfUU?C)t`J{ardrD;JV?_4Y1EsPp9{f=&0Svz^kkdUI+hA@6uRcmV02@pkQcm z)b!5-&$RXFuG>3yhYI-Y^1T$_t6Fguu)XM(D%d-JZ9WbxI`7vP;3aO~N0Hv{S_JiT zrS)Tdn0M1I7`8=^cDcc~)7LWrX8_aA?}N6dVH#}vUX!9a>JBV;3(Rb{{=2XReYt#O z&=th8x_$8wVqNRLbEGF2?ExOpJ^%K}2f_=yNfflLTH+Ki31iTG0J^=Y^6hSla{D z3urAjSjm^cm-QHZVDBME9}fxz$83|Q^`L$a?4v~ITR)9ek(dvc)YbGJ=X3MGXyC&- zlQW=0*;niia$WW>p+2$Pd3R>eI`{wDqXB&I*iFx2FF4=#9_x%pJc%(!9)m9taZ&}& z`+5I1u*A!f0$@qIv6q39M+L}3FPj&(9vzi_7l3EAss)~-c@?aWD}4Yr=-hu--Ea@F zLMu1YeadV0Z*klycBw3(yE*#A&iyFQZ*;F?+>|~p+i~BRB65)43raKe>wtsS)z(4> zi}kKY`BUJ{I!-m<4}>}t0naF%uo(EziRI^j%`|?5V|_3!m%m25$9hmZo0|@vX{}Oz zMg9y=U-$7L_-#%TWni!Jcn#$j)XSAJkQ?;AT_nGH0KP%k=_vezuj5v!mB1su`_OwU z>SpiTg0?b+mE?OuR#6E1lGL&*6ZyVZj~1CwY_DBC!L!)k5|+}v-?{nL65{{Pr}w#n zrWeYD2ZeP)%UZ{}F~HuZy-1s2eoopTZg}E+ed=;)e?Dis;=)UM$MCe!{tmn#Yd*z7 z9k?{$pPy`JI=Hu`r8Ku=&x|YP+ul=L@Cuma9dYr_H(>kj^Mu8|t3LiWbl%mqt|oqI z8tt1~UsxprGc8P8PWL_=kTByY`0shI=zL;&;(AHbe$Oz|$4SQ3p6+Rj&-uLJ_ej$) zaW}pRy?(r`FM5nR**z_(=i_?V9)H`1-oy3h|Iy^zGT)Nw>ok9n>es!%+jaLp?p6I{ zK5lco+C{m$-@lobZ}vVkzk>NVDTBE|_q0Az*Vn=Kt(1>2bpDII{sMS--=P0=vwNE8 zyDL$A&Ps3U4PaIW^R=CHn%1aLa$W)WY3Dyu?A*Xd{=h63%PlIKMc-2d<8vB^aKjMm z$WA3B48C8_4(|<}o!ipOfj2mgq2p zSjW~xN>c1}y)u3-!FXH>X>QPS8retD2XkSVxsBG`UZ?O%;5TLAz1Y07=k)NQfM{@g zrw=2prp4s~-?p-H-Cg2Wou__PUU+d2I9si>9qXCJ&URTv>v5m2^b7DT_w4(Bl7QKI zW!l7^&if00!TekeGOP}jw{g$myT37w>X1K>G6mlT?Yh?#f4^b2Z{udtGhE?N^bx+y z_QmRje}NYq`$Ig7^?i~I?H3A4U&kVL_Cjf@OKEMNp}^wyqI4IUFtS1z_{aMVgm=JS#I#~=8eVjK00Uk9`Gx%iv*hjM2RRkA&{fqF1L z@yblT@96urw!r`GH{G%0j9SV5{^d!_z_Z-t;=1I2@AWj=`^t~>oCdz~_wgT?55fFg z4a2xW_eJM#4bNeF&-o{P&(3HwSI4*9oQT%{@4k5Hbp(CKWn8w<^?|VUIhjWH#|xrH zV}XAJ1%5|f7E5;XInu>&ww9WN1v@^_VFdaG4>;P z!FZge5!|42O*GG77;=?fx1qB()ec+kh_X;-O2L|L(&zRN& zrOz4=|H8SE<6(b4^*QxHP#-6KBsbi!@1E&s-ws=~vNNyYt37qX7s@qkU?VWef?E4os#0k({L|McSs*dCPpN_l5Ze?AjPS+%!Adht6#L2KtC^=2{{DRW~I(**&-M>FccK;suU+l;h z(siwOp&jfO=6$5^68X z@h@dAc<0OWX+Db7XO9PFxi^gaP2aN;dtaydWZKI1deeN}wVd!1^XJqQPjw0E;c6Po z4Z2s#{ptS@wsPvT$oF2B)i>aUUJ|4gb+LQpxKh6|a9fO9C~m2f+*`ga^PO!OPyO9w z{pSOCrfckE)i=N_7t39j@RsZa<8!IUaf9A370+6vz}KSWTmkw&w0o^2e)pmtK_`2^ z99w_jIpr-~LfoBTQ(^Rv*?P2p90TmPDW9G_w(iEy^1Q{5o$w{C2)Ix_hvu*Ua8bhR z^dc1Cf7Vg%2KbTZ_jU5MJ(g?A1a|g}qj*evqDvF~cI%Wdqp`5c|FceA~a{q?^TZ z($=M(UN0QB41C?(6<3k#Mw6TY*>~smuuG)pQ_lDoeTOS*9XSPjZIH-K*fXuHZ-RQb zn#Oa3&gJ~>XZ6ZwtvvW|DO)?nk=&HKkHs$JK9k^KB(QcF}L# zX1{dEH>c`(BKXT@)5xC1u5A7t+WoBSDW0G{PP#ldXx-*-?{t8#LZjw+#Ik#qb{_aqcWnN;l~^z-2HZf; zB(jw(b)-6(Z@;WX0pK-M=UzZ87TbS-Ru1sMPt}g#!zQI%1a>jd^5EyX;w9n-?9%-` zG57YZI6nSQTi_V-7gL{3TKz3YXCYqi!%ykEdV6bs$}gywt66~?^uE=1!>qlC?H@BI z8NL~#2Rs30zNnkMZ#hbwC)@U&Ptst^eCIek&HTUeiY}Q?-+vGPHK9JS*mLr`$X`@G z^AhD=ygU{DS-~?2CoO+WTEvTg(OP5L1oLy!6uCk7+7;eE5)i9@d2B9X38^jq0Q~2H zHa(B1gT2qPeld>Oh7Pf(b=GU*oDzO4rsI#5F!h_|syljs*4h6TPe@3KOGo>*y|7*; zVwG>T$$+lpp0dLDMt())0L6kXwr?|A=bd&`LuTrehwx>#ONV>6@G*;JUMNfbO;bMUDrv(JM6 z8k)ZdxGOy&0oYI?Rs)!6GY&0NB>wg6)8Nf7n2Hf^)rY=mwx5iq9uMyKnfyC6D}O<6 z!Q{w9*t1%-ruwD;3)aJxIDs3qFU0kjtB%-KdWR!mTl+!bI`Gn!t0^}6!p^gAHpk|H zOVz$fI*!H2EalraHHz&8UOTdqdaOC6IuQQV_rreqgUTeg6DPX^fHj2lzz4|$7 z0{DixD@K7&9KTozSWq7)eG)h5+&;ZRD3x-J*hyMigqmA{V^fU)T1hJ}_SvE1~ow@eH>`wv)g97tKoeU*7LTJ^eoG6ZPSY;!wgY zzEih06N>@qupfGMKWP=z!%3aYjW9gV3DV2Yz_#q#P|E%JL&aC%2aegAtSeFOx=920$KfTn__2>qEPe%S`gs}U znx8S2=2Pa?Il@fKuHhEsAM2R*1@^nH*;79Kif`1HPxY5(BEDhysS@xH5A7m-MknVL z0)Lutl<+TUi_5@r%fhMF-y8BsAFExkKCV4exIy2UzaJU)0KP4afpd{--mXR8fX9a9 z=m2B>*>~on)6ARr9J8I()0k|JhUtC=zq3G23%(16OFrkvX1RQKSrgB2kCD^0VQ&y- zOnuYU9`Zl&1oClGrg9?|^V?Np=#JPA!wjj1`Yu~*foHl1n=#$^(Fgw+mtK`V58Th| zO+~Pc8NGpYoatUGEY>3L&=Byo>Dkni6jQscz%u5Iq-)8sSi%QAC+Q*Hxu@3a!PmU& zHy@a3Ga0wl7&vu=&_?jmzjvAd|M}5EzcW|2jwakT;8qIws3qa1;D2}T7p#_FjaROK z|D`*I_;_)LYrr}mGAX`M_xkv~w+CUtdbt*;a3dal>pOgMANaP+%34g?#QXz*o0sfg z1&sN|k4E^H{lsS~-!`{BP!>4vhsRRbuAH`EG_ci6Ln~m3=-qZGeXa zM-xt)8boVCry$W5{;A!yxo1A6d`s5}Q>|A1BbS3uAF|OI{w>Aoq*uf!#Sl1JyRRkX z7pR|W$uw?IAG4bZroeVr?{J#K_S;s+fuD;OS;Lp#$Mks;PT($y{UBSL6Mpl6mnF5B zQ=aZvJLM8+P6YdYq5JPD(#k$9fxSp*umSu-dg_u+>*jQ8Ucm7Vs|$7tLe4mx9Tg&>$ z&e;L`hPJ_$aVs55UU?p@AC{>Ct%0Hu>-*0ys>X3aKCGFLNjvkGTUEIu+ak ze)dav4X~g-PWlXP(0xMj-?{~`O@3Fp6}GY=uY-X#AMV-(j5^tUqRgZ3YH*7uiu;3O zz729*Wa)a){6=JD_VEaT_-WRd_?5tLi?~>l66?L(DaKIgZ z1LD>$-3D8RJEl94j`H)0cHoaJY$hEnR-=#n7T9lXk6X&mHKEVysleBd%_DwS?o0Z+ zJJ(I(0kE$K{2&d!;fV~zXIfoV6e<7C`3iF2le@KU{~RvZJIIVXUh)&zQim$jcC0?f2*J{P74{V2ay$#3#! z+W#$R+z5WFp1uw2-K3={zo1^OW;Jfmd+QzdiE*$^aSh!JUysG&H-Xt&McwSZbwjV` z=lC46Ros#92Va?b`2z5R{NpGVd!AYBkz$FIE6}Kv=9lGul9xe!6pYWMp3QVtDXeP7 zJ558uks#z!`Y_fIx%_wJgaY$*zRA}R;d46|v`hdFcP~7^_szdFd>^n%ylo%wM=EC% zR{j0Ajh~C<4p*o?2z$Z!oJMt~bHbiA8i@6zb16NW*t9VjL&JD!wtF*@Aq~3jvAL7 zXs72|`N05P#MZRwLw|>kezN=U{u>h155+$>sP|J;&e3zoY?<#re~I0wapIjl(-4cr zuE^9I1$;fOH}z!Ss}WtuJ@@@X(#fn zQ6s1>^q-xR%=TJ*IjzrYyL?Ugwz=ci?gjQATNn)8A}5`bffqeFP4i`W=)?oy#ocqw zas_@mN&E8hdlPfOGc8%tUMGQ-B(~FjHF#3f4D$b=K>IG!6X5ig)(*o=A18e-H|RY1 zQoMN={stlIp`P>^PTT0<^hErYEWBW@8Zhc)=LxgzIp23GxX>5kZ{VBp_3RAb-ByA0 zcLB^dF#3`sVr9(Sr^=6QBi1epyrp^bKJX%UGe!U_^eFWLw!N`<81Sp<11AA9ZPjU0 z{~(`eRoigzAKZVH0^0#> z7HTCmNDG)>H?z%%^rN0^{2iJE-}E2uny|gGB!+&EXTBHz4$(!d9@n>O@M8-$vp)Bn zJV^yuP%l@rHaF<|;*mkIh3HZ4t*@Q-~BGPhD=R z1fFSO^V{2DO&7GW^_8>8PZ;)0`~H#cBETB2UOt4rchWB;fIpOsrN1k(%Re{}I8^pR zHF&01upX{d9d5K?-+I39kp;A@T)(^nxtCvU+=w}RZB{S_vCtR%@c{k}tdSl-oO5sD zE|vUH1!n8`irQI4;JxEo2gA4Tt5h}Mw<-;!%YJd9HL&RI2I?Ej#dKeY32+C``k31g zW&~{iXdv~`A#}Pn@QQiC)CZU7c=|h*6?sC`Cs)U9y}{>3+Xx~5%|2hhQEfWK|4^I9 zHqTeURi$o(y{>lM;MaanOg{~{_gQ1&w}cwg|Ne5uwn>DQl%G2gAKLXlIH<=8;NZix zJ&=#}Qv8N3&GRTL-v9n9Phk5Hl|H~vuYD)~to0VupMc60Zs0{TPf-8L z<<#hJomf8w_l>(hZsfq;=4<9?#B;Y=-3-j;PH?`sDb(dgEatte)m7pQXR9(yJ|~Xz z&FZ=y1FWMM{SX-Q&c5HQNzt|9bIx}w*8xYrT28hrS6tZ#{IYgJ34B)`G~Ww6`E~g% zer%7EH*Nxp-g^)Ye#7xUm-+T0%59|OddMm}@S-~N2;ceAJ`I>@W!i1#WRm}uCzJYt zKRWdMHN^9t<23;|^NSkg8#!dw6kw0R|47d;BWq3ILe0**urHiDWdX3XOVEL#c8a!-;UP(^q|7uhTS6QO(` zA9dd6Yh`*ksq?r&=V4KI56spm|Awttt78;;vT0>)uan(%vGedsTBkR-U(Nf7b7=J6 z&9}YyXWD+?(&KJI@MW=P^-27P*cUBV1cCQ0kN5+8RcxXsa9f@WdbyhCbA#?XxuwU0VLQk8 z&qw$sBx}|H*Oo482gZD|`_6JrjTGW)6G(T4|GqT7ZR+vyMZlLMcK?F!nUzu8(ZPKK z-xmIjAKQGj(`VpCVeJpV*Y9X<0nXa=_c$=qvLeah5ODkb=1<_6)_(R!egbFPHb;Ut z8*fN`5!A;?*XIVEe~)%f5QS}^tNA;=Z~sZNNDJy@=O3G&b_E&g3CrW+mfr+lG5J3c zh~+OoqXC%d?2ubj2Ye~U&ma7tHmPdhHKs$B^K)+;;~)pDwC`6X_=guw2Lm6`s;B{G zS_5`Eya#?T@xV~C#caAODd z!67?zXzeV$GWQv5m!7_o58t>Hb<&Q$u>Tzovz>9X{RM2LwB%`@QW|*qHE_QLzCQ5H zUbvd-%9#)v3M}?Cq6+-^kF(?Xv6m_3T>zfj6+-ss&Zk}h?i>^HhOedboY_lYOHcbe z@Z0^Lkyep?SrqU7w+DpT`e3yTxTNF;`(W<^(krN+D{TQc=s766JZL0rFN)r%K(4}s zbX(v(pPOjC@SlSR*D7fK&X<%Dx2fdMWWMd4zE5<3*L=B5Jd4%7v_Ii>vfJtT{bDaq zbZm@r52y8i2j1uw9z6=o_%EwJzmGo$URR*$xdjO163B9(k}W(w3t6$Zgto zo$q`8P0({bUY2mX26)+!D`d}deVVF6^N|)*-vFLz5zNO)G2#ZjUmiDE-~!tN(lxY} zf-SdQ1OC3zp4L2cv-eB&DkG|qaWzKvS+MPti>K$8`34`9rRS?kOpjvSS1+bDU*r4Z zJU>@t-lAaOrTL3%(KDuH`?e;+pARkf1pjnZZ;CINpOa?H4LU#j%x`Qt&Dj=yNT+B0 zN8KNL_%4}g_1`|k&QI}Yv)cH!%s1`t$$IF>nO)Ede(?Hudd_>sCJ#ic!GktZPZrhn znZb{J`omod;5FYL(muj+-;n9uh-lKddfh8+iOCoeRfq8GFHY@#<{a*N+FBaR*j3{6gp9 zr%@^X{8*v0lhc74es6At?#h@jx)(f&nn;-C9yBS4;#H1aQUv=o(WgmU?1kHYz)Y*4 z9!{z$t1HRk^Gw+KeAzCIvs6uL-38!jGJWLOy@1ta{6%gFpVPZCCIHy(Ys)y;YQ!X* z0QPd*P5AKEB3h^R;q9aNu}`j>ISg1UT>22~Pi9HW0H3lGwFG8bn6^>N9+JP=wH`EI zBbC)g!(LDiSCbj5>wQCFB4Vvj6{lP~#Z~G_TlBE8@a50f?9yB3z-2Xrj{#TLBQK3_ zTlFd}A9$Kw3-K)0yHz_!z`i0lhVE%U#4;&YRoajO*t6UR7hlc+zT4DH_J>x_rTAi! z4@bhEX|;25rn&_6a5XJtbv=G()f0Q;$=y4K!go#4VtZhHktzjXeqDY-#eKou)t4b% z%vY>xmMm=LN1dTNgV&S!odK4HPz68D_ZDb}P*0s; zKYhMw5bdjs3-HMN1K)wVu4Mbb(_I?AUiJrV;p^=tMs?U(dsy>h2QIo`2|W7#-7&Bq zv`%d)@D`bV)HkMO`_FTq5TEg{wI)0UZZO+u1$(Bw;oE}czaKl$t!OncUo zzdcn{>Zp03Grgs{#}nUDeXi5)>KmNXytoB;1!2Vuz`Q%yw?YTJ0!EIk4&q-LN&!d@>D4n z{3Dl1j<6Th$4Otz4GY{)HunssyKvRym6EW%-dSOd9@(GWJ_Hzbvfn*QPxcrB&c{!0 z5V)bee6{$t%(u6BBk^l}>&?OE&Qr|$O%x>^pEmcS^X_i>tHJQq>J>`&3+5XdE=lJC z!z{LJ$u{!Wh}0WQrJ z7KIl6I*R#~ffmnoQg5Z#5lsADQB(3wyPwi(SB9^;#T(Lk^Fj{DS(p8ZEgIhIQ1C ze7Y^3v+a1(M(<>6PdHr0UCBh!lwzSy_V-s0&9wda9J4ih`-R>sW#;wF1RpB3jP5HV zewD`pyS&*&@2x&U?T3KpsHoAsYP5;xUSR*93iN*HFYdY;m}%K_EP4*GM%Qb)XVv6f zk>T5GURm}J+OH;tQ#@A7nT-jQzo!3(!LUE%ZAAB@S81l&z^2`It*5$*Ho!HCPyeBB zg7tBwTXBQd!GchKtp5 zT4SHwSPYyW^~&f$zrWP_K|cxT%5lJyl1ATK4QO~b8rqY z%N=pAlj0eyT%-%$aAa)};@>uEB73Iw=;cfk@Pc|csn!@+=NU&&Zh~)I_2oIRHCA!G z3Vi2j8pTFkY#&Om(f22=haUCd^cuxXzAf|h3LLD>kF`MNoCa{>Op$ngE=z~Xoxt9& zbTq*;Ehn_o$X_rYCuKP|=zB~@(0~Z|9_?c`6uwVGf|j92LoOYj0u0^kd(7_zwDns4|w+*ds*POUt}i(552qaf3S(k z1mIiyg-3v|z3AB=m}#5uYW|3{-f7be(%zxNx-qKC1Zf+>OcO<&vl~gEUm-I?=KV2w8+%1q`sO; zg{8sX>d1X%__IDV|7@EKELhjKD%%p+KkNOG^c~60`3}r#5!BC>X3LGcSl3FDpDdu2 z`&)#*h`np(rKjgO{6wx}HZb~t|Is@BbqxJHp3kYbnH&Xf+PRbRzG+i025ugAcoIKW zddCNvuYC=>3&8J+4xqY&o_5{=wmuU)4E7C5SqFf>sO%pM+~Ta`1-#zZR2sPb#gi$( z8}cU;pE0jQ6jHx5Q_7_R;55>&gfrI?clKqxr>(&6X^~ZWq z)V}=5@rqui?OJ-`<~qc(S9zw*xl8*6Eg$Got6sOBx@ z5O9aT*cie#q~t>~-*&3R*Al{AlgXFGlApbW_CYoe1Ew#h^J4$0x0hl6*!_$a;vJNe zIt$FS%w5>B7uY``lJ*~__3Dl&@{ih=zLoNMpQZSM`Z($K+@SNqVnU)aY)@yeutcs# z%~`vESI&}N35+_~c@cj}P7mDJ0qd>6F<*zz>z4!n*_t#B`~mmzcEBfxy$OdrLF*97VEjQ zMPJ~ruIa?HT-c$Wo*|!O{W~<~(n8oW-$Zp=T8BEDe{X`l{OW#m5Ba@A zRhOTOYq ze-Ze4i@l`toc}!S5pn$vTgFXVK2;dD@1{*7TjpERprQ_4tl!zW{%XJ#XU?|6-_5d& zas?e${>;x^IbOPo&sSwuyas-E$e#R}R@FfvvcY2JgvH-_;5k-J&G>I?^ZT%L z>Mpv+GHy}$oc*vZ_w;Ontyz^v9B`Qb_y*wbVyp9j2X57^17@*1uJo(~{uq4i75K)| zsjq?cUkcp@cGEdPc+t#$^d7^s^-2(S0KlWkq4C=9U&Sh8F&&eLr2!4g1gfVb^x0bv; zGqj06exLaMo6RN@_LxEbO#7b~uUp_RsE@1Bg&TAZ4Ie4|2k&~$XT`t3);?myBE(WV zZPf;hI@vi?G@zmTf^SdtnoWZ$pgrz-fmj}NF%Qti8k z0kggh6%XtI%(O6Vm;aukz6s{%q^;%#eV1)2eNhhIdFMm^AotXq(<8CwO-B2P;h8}l z?7QsyXG#sQW!y}^ADx7qZA4(Jax`oRe08nldjYdpJ6pE(0IpU~{tNqzH1l@kI@GWF z3qN<*w($4Bjdw?I*hZ!2i-Kdm5@kZ-z?JuY ziNg2BAVu;G67`Yc$6~Q{F3R-g=W14IrXElB+N%Kj9WxXN?{RUVIVi2jrM|xT@PPU{ zCZU-6`p#;b1nimiswJ0&fuqHns1`xJT+MFWh{b!!UcY83*e=tZPCXmBDt#01GyS)H zSl#UJhE(P{4+S@|GL-VNI+*UK+$SLL#@}kG4}RV6YpjQ(AKS0x$1ZuhZ6olI%v01S zmYemF;T?|oRQD#y_b#xvy5vduOI1fZ0{4wer1ccAaUQ)FGrY6={c!B0y;R#2oe5)L z?_1JISg>}6XBmE_^&(g=*RD0(pyxevn$2>o0mo10>6uLEQ5O#Uz;HFKW&ZQt(<{ji zoLYIyZ>&=xU)6QMzPD!6Gxj&Sd>7xB#X6nqLVE|pW(#(ce@8$8ofm9BIJf=KdiXOf zOxwdIpI^xL|K%4FTFccz-&^8ulpKdId#2bk)?2EUdX75S_m<;|eJ{h7aXRDHKLI{a z=SJ%+!fa+G@b8$Q&j0IqHR%4n#(0){(}f(GKgN%~(vRj>Fdrwyo$1c%{`PiIeh>9< z@ldq@a?$j1WjBDdlsAJD(xB6fx2wI zOIm^NQyWD4j@A91bk0;47f~I8`8dt%xIybiHqQGU&WRfFMDiVcUb-)0jS1QEU%%No z!MI;{Yp5SX-la>x)?w@us?lS9&wq%uxzdQ%^Bu9hE&SMbjZW9{d6s)~cfY;vsyBh3 zIoyWg7iHCV0y8aw`MDZASRHP&B!)oCfs^}4ch0_c1K`|GNwlY-|EvxPwKgq2$7~Pi z+0s7Id;6*R;5YS8qW#pVeJgzjjqiFwu?N=89?s9ja-VA4NA`mTmJbF0({e4v>+iEq z9{AKjThbz!pOdzp)p2L$$d9PGGWI*|K?5dMkB0B`2>StS|6_G*u-^X@wv1c5MZOPg z#Ty6qhi(3_2_t~--kRDEn8h;M=H3^W^^@gV*Z86f@p{_^(_VXPY|IbRveu65|I;S# zPw@ovb2V(>2EE_EEsoy;-#PU%{67UMmTtd$M4j^7{izU_YR!k?wyATl?PtUMQ_d_o_RQ{k{UTT-RnB0FQWPL-WgOxfi;t8u67z zrI21h{ak5YtPit)_Opbo;?WFR^N%Vd&I4Plkf624??d&$%Z}ill%J&g1M~f+_>7*1 z+Im@Q@JW{EX`O|nuW$x_B=ncQ-|e$1*vyYzVRbQtkClV;PVwyncKM_O8+5O8rp+L| z2d(RX%2TP}OI+su&wb}s%pu?f-FM3U)jPa_S#5&)xgL0PgWkL4EKEKnakewt^Xbl? zxAeU|e1{pt{C7WP@7;`>F>x5JJLj5Brm(Ggwv*OQ!IW!iz`wfJ;V{u+Szs3XUec>x zz@z8t(|UNgSMxJ+2me(3f%#-wf@A;Eb5QDi={NYhn>*;-_WM{&TKDeROy`WRSwK7Z zpzibXo^gg4;-5EqN^|N}XEg+v)v~%xmCHZsHzSzk9U_e6`bWQ*Gm% zZqZs6wlfYy&zbpxGIm7hH~Aoif5@pSII9sO_< z@Dn|0IyYX)FAoA{xn5pO4g*$gDW?5f>0whMuu@-nx+nPk-Fz81F20)1EvB`}_A2Es zb6b8D{L|&g z$_{zp1I&6;b$##};2ECn)T`>N3s(X6s%;>9r@wob09Q`mNb#M6jLd;ApD>~Pg8k!K zx{(`npFU}`LmalbA)iV6jKS&dz<0jire_V$DZ5W=KTey)=jP0sKy&!vx;H&Hp&xS= zgCE%=kDi;EO4FQx|Gki=^}=Guc^lKbR3+V`x+0v1&^<)0I+&h~)E~cT9z(B>S_=Qc zrVanFevfo`>j5*Z5v9_az^Stv`G?cdAFQUal3JxRHYO8~7*xIi8KEakIwbtc+Z!OYg{6Z|tY<^5`4> zqt*DwxYf5u(f6}v84F?Tvx9P7>HE}zL#N(Cmz$V6eFy3iJ9GkgO^SgsV%w})ww>^c zw~D~IgYLNkuQggqxr?&D$?>tT$n&3kZSpreYJel(EmeSjxmHvou-56a3BartH`zV( zdqrSeSOs|3z|HjC>e25{wZN<2y3zN$BCFd9$oFB~WcuFMXLc{zk6C?xt9E68ufHQi zze~n%yL=y*^+vECT#Ghy!vcMsvdMZ0^=3dMedBsCI_WU5MzYO+-znMOpOn4q4CZsp zHvFP7>DjChtt0z*QUsSm}@?-y(t9u?^r5VvZTBAjS=1DLJ{ zX%9A_IZ;->M|X0~&7G7B^UvNx)IV*ebC>m-`D)pHnuPw(o++zpqlqd=+pg@ z#d;;SnZC^o>i&+%a!F}Tpzngro@o)x$4S}34Z3&Ly7#9uzR3OFP~`S1c$1FYYkT?7 z+JJ6$?;7)bY797L`?yQo4>)?X=ScX5g!-!h-wb+9zm?YHyN zESIlUOc?BSPKwL%wdiU~)9<>YGUQXhw@h{z23}APC)JM|bng;+sS=Gf_R3=%{RVhc zX89-Jbv-gD7V2X6uD^HHGWndDW)ht}g*l#NYgB&q3HaJ5H>%U%xdFYG98%gl96DI+ z-78gPfR7E1sE7Ufd3P!I&s7zVfSHyvhkfaMV7MdZO)_|=SF5GrfA05-7+~SUX;dex zrSAq~%KvZd$urv&{{zs*nOjEWvdwa z%WS{7I@A3w#(5FdDcFZrThl_smNsu99c@x)uK`E7%8WxDLO1Ja4w|3+q4{K5)^zV% z3)QPH!MXHeAOFzv<~c^h)}fE=LW5h&8vER#~!xlv>DwQ|GoMhhunGjd#T6#^}%d+Z4RXQaVxBt3EP_P z^{{Nfw_v_6i#4{Pn*M&F)9K1l@RGHK^o%X<-oImwb)-<2ZJIaTM|R7E(C^K8edp5s z_UJQVx*NORIyo2qOzU;KMRe}&IUr2$RktkEWx(IszL4JeHi{qbKy7Wa%LgI8pnk3g z0oFOE+ye3EaCY zmi9mvD}IwH&42QI2|5RA)HP{8xbQ-q-rH7y!{10|NS4Hiy%E04>_|p2j zrI|Y#_>hMy_0cyqrIoLp)$%YolK3r$W4?k9x!8}^r_aulbhn9bET;T|^>952Wb0$L z^4$IK{j$NI=3s4Dz*OYk*89kRePR2@UV}dAuMvKdjLjP-$pNE1MG7Fgfp8`|y`itfIYYN2FFEu=2w`bT8U7bW;W}(-y3-J_nt|L)&Qha|KpXo=xXc#E0oLKTmr8qjjl~ z;js*R9~X3fsKP2etu~ zg>>&{z71yosE&xi_g90TtDiu196Pdc9zWM{`p2cq?Rj2cp;(3&p8BBlsVz+9KBYQsCd%`yH z$@F&@jAOR@wl8pitm@A4krI6Z~W3Fc+Kxt31W@IBQ1 zcNQ#meRr-L#fQtm3&!J81#yGcNmJpL2;|E9an1y`(=Tb>1+MxKx0vZ-``eNC?@xep z@SaE9p%lwJzU>UDHMfB;u4y#q$67VvgefqyXStZJgTwDp?uhQc=g51zlI)o_hq4V6 zFMQI$1jG*s6SY7*hO7TIE&>+R$JMx(8}y!bDMFpTp{czIFobRVbd&2i8+*^XPIHa^ zv-dQSEHO`T%$E6v6r5NH-xUTw9l$%pzakwyCO_T{oSSu+=GG#ql)f9W*eur!g=XSA zOxGWy__wARA)a79PRc%R(0kev=|H+eCNB6+-xFdEMSp{@Z-bOEe4(4YrC7Mo&3+*TGIE_T90D}@E6p>N!`!tx;Xdf3dFkNWJkVSu@)fCJP`i|z<`%?Z{&Q|_x59(QX z_g=b+trgCYeMie{c~J!0gqJ!rU+0D%Yy(!48&B)3-Ka_m`=Z*M4Q9yO8rWkTFpKro zcDEkz=~>gse%?Ogg}{z`?@~OSY3Ce(Yn86hnrB+tI{c}Q=fjS<_m74Dqr#fmz{{M3zwxzLz0&>#%9OMR_3+tp$M`m!o**8<8(36!@9zOxLZFe{cj5^r4z-+y( zKSYBoJXRJ3+ttoRCxC}UsUHX4`&5s7UBy$<`LV7ZK7J8+#LI1Quy<;0%m-GO5Jmnh zx9dywqu~3=94-LgSkQ;^xv2Nb0cKj)F8Fs0c)|8wF|ZfZ$4NiLjd+~PFHT!ch3yX| zk4V_w&~=Lk-a1|=0T^|%{|@fW+D%l)w|^%{C$kOT6?6=?(xW?~;VU-fAmuGu<9!%7 zFfxmDt_vO-4t!neCgs{_n&=MfwyJ~heXlB4V5a5Ktt)20imfU6nm)ggG2BwH|>!GU-Xgv@4S4U z#;@daqvDc$C~uW)B5b{42KoW3WQx%o6pt+p2EJg?Mmkw++qxm7>sF-RUhpjU{Z3cv z>zno0NXuA>BM#sV+_#fG>sylNa^n3@Ts8uqw7ehjPeh;31NNLO{eSj_)ynh*DERBc zKB(6z(l1yaSNdUYY(bw~?i|d6ZI<|=r@B}!|HaW%htu(VvVZiVm~vf|-~0&nEVo!+ zyGme@7Ej{;({|dE{B1=`>cHQT^rZOq!%g1+pYYp2`33cI<%V&C?lBjC4!aNA;b!?1 z`=3d4pG*!Hmo80Vb+da+PIV;hHLvD?zx0xM=9{JbEUN2nAW3eOKI&2>fve%=LVfy zjlo|wAotXmeDXc;p?3`M?i6p*#^3L2qQnmKx#~Xp5y11->ys_>?PcObdDTx(+XH^N zo;a;5jf;8yz%191BgWpqM-VzE3BaDgdbDq{TwlvY z$v?rMO`dW&i_zLRwRQLm!rg1-o3Dv3@CNBpT8GEhjir1+p{ocB>fxkDaAOCaw_!E@ zWZRg0llI^lqyEzTtlIgP)-dW~-}4$L{9B7WqK0wA^?&<}`ps;9Qqfxt}5jouqwfR$=Lk+x6fx{Jx*v5NL{ zrhVMwFgjm~T25GjUv}D#@k{k5=9k;6W!WzFQd^-fs#6H>1 z$G|>uC3N=i=T|RcFtBSpYB=g24-5Aw%o&JWdB87cnkQ-Uzd)+UQiELQxrGo`HPGFM`xzl zp?}+8d(h?(?SXxig!TjTpFhT#PJ6r)TR|URHXjbIro;t147ohtQ=_O}q7Da5l_Awm6vp;NUgs|@ye zS4WYq>1#I9-mI4JWf%C6NB8r=Gc6kPOcH^8C2~lc_R){wzgKoDaP-5V7w&4WKm}@BD`DLO<$4=vn2@ zM`!r%DSTVzd*H9>A>=*ppdtl+#wWWlVC$;8!w83~(DTzZ!e9b0i``z8NB0k(4bvzW z%YAq3FS-XVEPg@vm6fq6^t`34GgO6t?|)MXpPjEo{>Bl(p~xqwkE`)0H|V>Rt=Lm> z_*U-HpaQX;uM`tfayt_<5C^t20hmv$Lr3fbG9CbRcMcQ7$n*RJSq1Voo&#?-Up=%%+K>VmoZ(B zfDb0@AzO*iiU#1;2bFYAvRLDUKT&MSdG4>lJ1%h{EVaG;CNRs*v`iW>j?Q?Qp3^eG zFT3VJ@dWd8(vEY3)=`;6vL|e3-*BcgqtL>Gd^eXB{&zoM&-Iw}v>mWz+~{{=`+!S# z1d*-xk?>I97d=$zp2K3rEGi-0!%lCB2XEHfh0fLce;Ozr%RScYia+?+=oHezv`Ln$ zk$u?@TQBf^^*2#n4ps?i*_le4DMS;TVH^mip+{__%O z9=;s5x}CqM4#kceD}b5pBh#YQzz^PL(fpOaGF}VJ`p;tf=3bz>8D@2rN3K~1d!{Ae zc9}i!nWh5Lro7H;2{7vu)7}y$Mfn8vaW%$qgT5mw574)QuV-U4J&U(Tnw$d8ls`bR zQ78M3_~6(t>i2x*J9J);%2o^G+cMt`E1uEwG0wRc*|XSBn|s^9{`JHD^t`a##}@V= z`|(MmX}*q)ai{x%U_MUDNp8@4N6WLT@6)(gCz4%hPk5H#xD>H(<+_tL=w|O7uPeXJ z0r%yQ8F4#q=q)6Ct&Z|AU;VZuJp+RDi{gdjZTz*FZ_9jb^wtuexhU8J{O!A6 zX&>+7p8mgbvD|NzHR&ED{ zqo&gRoIhW=P4gFlQ=Pe*&UfbPE#*pQm&ntvtHJMmnn?Y&3>Bpw==Z-!I{s{|i|5CV zP5n^;EE1MPb+X)SK6UisK7fC>v@hK+KF7zt02Y%uL%3D-?m1w?j1IbknIDsl17@`d z>g8%a%?-Mz?rhV`#BVSn&-alw@3LjTftUN-`S1B+?;+bd%dYb|dC9zT;G?&+{$~%m zG^CdB_h-~I7HfXccba?e344BkzisJ9b&h_l&;-nKKhCkf4Xj@yOZ>#AV%fkpgP+m5 zWLjTM5~k-wP!CsA0;_A(ruL8UHJ5zjPnFwC9c{_@)0$cOdq)*Q+N0cO_j61irCgu?#TN zGBhei82HAz{lqh^5pT>Wo}eC1>KSg(`<3kEB_E)5|9-VSh<#aev>IZ)xo<>wdDO+; zuhuWHr96yVW05fowvW%=qkZDqw=8Agj^cx~ZY96e&H@fhTS4oZ#h!brgnHILSz{Xb z#%cBRjIcg2EkzUj#lb5bOCfEZm-@Cs>!@KJ)Hm+|J}toC+hM_zreQg~ zU)Zp@44EQhC{m^prG!FA5oM@EDH1A4bA$#dgfc`FMX5}YAt6I4NhwmMlr%_Y$~?UL zT>pFhj{V%v`|l*fTUH5Kp$g(B3VJ?z0z7EwH);dsV!cX_^6+t#CvJ&d z?hx03c@OVHpScgtnw|x0eXyA1rT%WIzy~Dvj?n3r;lQmHo6vm1xw|Tip!kfx2d<&} zgLJ+^DH4T?)<^owd(MWhoRxox+o)3&^sb7QehdGr8?TR8rk-=2;ykx7+5+1JKeLF( zmiWv-;6cCb2y3;ON;u(=Ijy_L{`tI89BWr=OH1IjIjc!NA@%w^;8wj?(tOU`cDOE~ zJjQ=>w1%9yg|9m30o-#;3C&f~{Dd5j2^Y=hACt$^y9u%+ZLaNiRrr9g@IQ(AmuL-x z{=xU(x}$p+L$=Lu7p==!_tBbanzyz|-k;%{r%g4|NeK6ZKKq`a^2Z9;^ut_|>p!1A z7x?9=(`4V^a>^K3XH+@ePd{kqj0U#6ltl9+b4|COPxEN>d}nRQEvAknKD$T1n-9#~ zrTGe}BncO-d?ce)dtm1F>+!j7_?_H8 zc|XaS`}iSSX+HnG#QO{6kx?!bZ`M1FRk++y{Q`E-TF>b2S$ZFl`WF-OJ>_T;^<$0|HRa?~ z=qD_2BmMf?Gt{qZ9&aFj%q?|{G4115uCXAwxwje3i~3`O%wT^x?l|2qCYs97`%T6f z9{hi;i*(&S?HD!$_EXJ^$zD2Np=QU0i`GTgqaw|r8(ASwx?2~O)BUE;XgR6{>c;B_ z);(shm~^Fik6)Ew3tz{6uBW{8>XMv*hsj+dd@)&@-r>T&IacnzJtXgtu0nIy#W#Lb zhmOH5SHs>rZy&|W?6jB0F>{sXBTzjdTwxejfeU3`pfA3;GL`Zu9CKL?zP7x&O>-*p z;^&lER$1Xf?Kx=cHd*LywKkykE9#pM@aRgaM8S3l0KB)F*E%1U79m|zpZKquG>WCfjn=ruFL&Xw2oQ7 z>jUL6d}Ram!~2r=%{Ye_Q2P{TUZlL)m!C?jw(xB+ERf_JtLpP7vL96BLUpYg5Jvu0 z@4WbmJeiwxd?ANZ!bSad-3F(>(B-}{AZbYt#ENPg^ndkRZa;;+s{iW?{a1Gs>lQ3m z=tew?=8>FzDV%hteyOCXLURlE%X{k%(!AK*FO%{bRF_Wg^kFV47Z&~lx4?Hjs2?-e zZIW|4X+AUvElX-|eEdJ^ zC$a6}aF^!aq07}8AfN1PNcV@*`R>%OI2Ok?m7KRq`xio|3K#XG&fRPe!&i9M{gmUv zHj4^?XXw~a9|lkEN6Va==>0v<&i$ozqusE$b7EcD6^gfjCv|;JB(=%+P)4WL5I={47tb4lU_9wu52kxeE*uwBhIq>{uXK7u@ zzMXgOqH%qCy6Ypz17wB}=Dc4>exI;?y@~Q>Zg!W{(n-IcC(TQC67DY~^VVW@$19NA zsc2GsX}&@#XN8N#k7bYw^|R~uDkzs@uKWJLcF1aPTDu`{p7)z;%%gtN_Q7J39p7m2 zPOSUq!(SS=d!mbJ{lmV_JFKU*SyIT$-_ZA(bddHH$!F4NolxoAM6plkq`rcjxmY?s z{U5jV-K5W4=Pf=;{-yZ{RMUhj0`tDMe-Q0~@*9dBv3|-Gz6^%FTHju_Yk57z&kKyq zzd-9c6V1IVAk(PHr8Q)~^u$%bp?>FfVP2@1_0koXeajZ?TLWAnd2iq-V^dGyN5|XH z8G~lBfgkXOZI(A-&#{%_^6A_l)Vd|fnH$^Zoj%(rj_0#)^$Xz2#|cjGS8V;c2)O@H zMZyn*eqRHQ`+0OZ36bb)?PVH{s~m3+JTZ=v-O$MRIS7(lTHG?1LR zbSTg!d+!B*x{=0j2gz@x!K=e*o@1`h z)?TE0MUQm~bYIe%zlr9pCE32@Z`-JzR7ahw&1jty9%g_&>ps7|+$ z57**Rz+{?-zkcgX`AXMADAmRPzplc2aV34~|HHbb3w5Z9JPF{WeG zsulvbNc!K|A^IzymzxHfjTg&eEPSkiqgz_Ji*2n{EXmi``*Wsrdr zeU7c}xY--{_@FV}p`R68Li3MwJb}t3=3T$|#%Aa)Fjzshn+`0x2OLT$@8q~-u0^Go-OeI zR=q#XlhW}7&Kbf*^Sf@OJU!EX@qWH9VwD}3TMgUp+5P|H&GY-?cCG0hl{X~kw1EnP zY5y_6eF@cvZBLl>TL)jkcP~AJuQ3`=DX-9SA6mb0tje&|^N`0c&?33co+mU{&5Tf? zJeXU+sD*Sc{4!`Dt>2u&)oH#D*O)`|FmsjWC**ipxafXgV}AP(Vp%NkBVQZ6deT}# ztIUJuShNq{?<=->U4<^oMlS4p7ntX3^bdtp_7p* z-FG-vK&u>T4{4qw9?H|a$J{jUg;IZ*SoM;)jt=g98S(F~%_RF8J%3tLw+o#|ZN$0d z_lu->+y6Elh5p91E$Ch*eZP^;Pe}cWa8ZAl{b@a zLSWR1`$MdgO#*b)uY_1Y7W~p-uvk|)UdI~PO6lVQv9FQc-p&AC`}CVBX4v&Zr&h$=pJ*=_lNDN<|bCa$di9JnClh2n*4riKS!)9)9&nV~FE-wXZRn?sl=tVM=5nxIX#8_JY{O6Ou@?IZ z8)EAUyr@BD3go4uk`4gd9I+#R9J}&&iVo!EL05bs*B(1^G_XNL?igU^D$PfrdX4ik z8*aWRLC~F}JC6EZK-2Wjc!u7@D|G{WiSzofRsffGba@B-yRhG4V4X|POMy8ya~Yh}jO0fO=et0DROyu~^uKgf-w0fA z>69<9G#??S>zvo7(i14tF8eP4a?X?uKw#l+TOBW!cKcgqBhsaK2uMqYg1A2DNy z6J+i|=fWZLjp-Ew+}AUECvaX!&RMZ7`|9ti69D<(U){nX=U5veny&};YI}y{%*EsO zo~_WAjwevLDO_~EVbe{iA8NWGZRkAcntZ(J1#I@U|59M^=JSmdMJE%;l09#du9em` zEwSyXKLw+KOJuWXK4sskix*HIZh8CJD9FvnHMfWFq_(wlfH_ve1Q~mhpZ2v8bD5j< zhvG@c7pP?k7tQ-O)>}P64g4>Bq7%}CS*mn6y z`S-y0+lSeL2m5B8E8}jxgnYLCZ5luF>$cPZe{X171^v#cCmMk{w#ZCZgODZd zctADD8xnn9th=J#xgOYj-w4W+eQ^yr??2z))4beo_Cxy4CC6qiCH>{7y`|#`oNozN z817e-<%(M)R?!_7s#$*3r_;c9)!+CdXYl58>iVU&^j*jo#{x-KC2wLN)_ok<+Zp)9 z!yfBktI~ZieaAG;Wgh9X@2VE3{UG<6=0~w!DvYN3dwF&`1^c64%-kR^X!1S=+%Zzw z1$e|wNBTC`PWzOVz%_E$=zCktU4LQAKG65oyu2FnJpuYXfK7)^CjXr4*T|hTA4=yV zq?#jK)Skn266hPeg$1LCORt4*i{NWO{!*H|#qBB0n{B@?{Xuya&*(vYgKatQi@&s~ zPJLo!a^c%YE5HZ5IhNd>dJo`RJ_UCmXD-t51kSm_Me9+Uy?Md-hEeClB$_Acwe$MH zHvX*kTKEEQUXSiio=)GNlRGh*;z-wzZFjU@PxYT%IaUU~`2N&1L!G{dthZd>0ltrU z%g}edIQB`W5AooV*iVVZPn$)u5x|3VW;;Ru%&s4V?^Um-_G0cjtMZ9Y>eIL==okFi z;SK!-U3Ss8q`3V!9_Kb=&B|xc-!5zD34Q7Ogw$^fml^uK#gKU=xQFbT)L|}kqun13 z$N!M&eQIV0j5_f-_xv@#9z&O9pO=2@4c%q=FX_I^w&PoGwuJ2$AFr;Ev+qAT<7#0) z-eftASB@Q_Fn$sArTq(`^Ms4mlf6gQT8d-8IGDK_zO~#UsPBL$uP4*$OwK{K=57wP zeWpUzTCpwr+G$%&^L%%+AXmu4ZycocLRU54jlg5lPSAL|6R%u+|xcT+{~PUtRO z<4^rgO{q9lY&*bf=R@G_l77y z7pUb67tJfTgT3qEi@BXJRH6UDwcoa#YK}a3J(44z^9j1Txg%-5W7|s|nkbKJ*=Oi| zZ%Yj8oWaXxu%!w9pO<~L$O)(Ss8vbkSB|y4ZyxE(Ywo7^!7&%o?p5kX0oD;mA+NBHqctW7=M10Ix~h$FHoYHE`@$NUZ#y=%q<7oc^;GZz zw?5BH=)F12U7DXjy+F8pF)t(>nbn9s{=!RzxCTnzZ4 zf{b-JuaS~>?wps*^P{8R`$M07*X>NC_nKU=TtfFF9uJI}%VN7sn!ArSH2nZK+hhmI zd%wa74fvBi;2DhglO*rP7}czj*5y1NnY%P!A>BgZqV_E)Gb@0v2L-OQhIbtHw>N68 z(kXBc=gsY#|Ixk#x@HTX(i(bI|GHnmth@4HYA4wKoY}A&w%5j}bq21pRUm!#ZC)Eg z>ul%e`K=*0mmf{*{ae5NKEq$6!wFg^D@xw)$K3Ar`+gsC|CV+nXYPZWR&PfC8>#R z*_T7B!YIfYb6vO&99!DIz_CcUX#bokyNlj&lo9rmV$YVmlPmDrWLG0vMu|{YEn7`x!Kvjn;^HEzx4p{Y03M#IQBWsKm*7- zel4dOId(hO2e|sZ!$IgX*AdSS90r!=Bji*pTr|FBnavNw8N!Y$F2&IGRav$WXEl{q zmevAe9^vt|cSd3XbXoSS)zjY4z2GvX3cB}v$5;S+YnVR~`%>1fD*;whPS%Iq_iy6^ z;Go!GHQ=LtFHvmWwL_Z3T)ryo-2=QdHih(=>&&!+Ww1ZGGkzQ7Gc8tAyw77sod9k+ zwubmf^A%FLFI+V5KPoR8hSUSX5o zpp8Lfo0&s$_O;ga*lp-5&a&}@d|X5##k%i)e403RX`YD|@a3|VxzLx6CvbisT(th+ z*frliQ>^9Pw;hMAlH=s3%$wIAy$8V%qRtBUrScY zKLpOKh)#jLdeum3ANf(v3Ba89jpIHCfd`EmPyqckrDp4anVZ+JPfox)dPGxAnY-?- z+ti*ucXVwbPtQ3{?U}#7#0A(s{W7(uG=HH6CBjAHv7i4Jcjy)dDHGQ-U+=sE?mtMi z23*j7JRWoE+EDw1N5qg{w)L_*RS8|^PX7*wZLjI&9s_Qd{j3^tyK(1>fxSlcpnk=% zyh{d^L!PEv`y6r|z1EL_JG`{W1!ivDu8xQUmOIvp>MYGip!!g_XkF#p^lmqF`#!K5 z2w#aCPV~mNJ=HgK(F8_bysomFJvI-zEK3PbXaU{T-JI3MwhM!@Re|S@Es=p-v+sgI zz-=DdRET3aZp%9jeAaJ;3gpbi?Sk(R;A;ae9EM!u=>+mG9bceU%6W_(7aM|DsmrQI zz*f2K)F$A`FO|mvi}R>_XL$fJ_4Ws}APat|l?lA+!E9aNjb?v~#kK=?%X|XP{(%{U6IaWY8*{+OGqhs|hU>G(npWt>M+mo4;d<+A!g6QTR| z`&|0A_E6EeDYwV53yV^*-x(UZbUJiRzAq~xEYW=zq|ik8!jEZUUlu(N(YflU#}{8h z&aq5CKe-MZAbWQb^p914J_UT^kHQpS=2qG(ko-x<7jk&SdDu-Ua)j-C$(ii$lg-JO zzEQj>e2Lq`=AoiHbXnHwf-8OhdG`+o(w$d#GY;}0lJkA`wNbg8@}ASIBnk4Nl02=P zzSH-jIkwE)i@uOMX~|ASZ07cG=QR^x>G(npkA;iY3yVw^dc(HI#tjpo`}^rW`gZ)A zvhRkR2d@{JwLEe_tefhk90XjZa77=w?8{=B7S)&K?CZp*H+!Mq{mfJH&9TbvKcT)L z?O)*dM7Zd_YBaLo*JZ)B>an-nQ6W~l;#{Sx0!}l{QwB!8`M!E!#2?u!f-LNnO@GLO z3OlX^W?ko11Kt2XAAFo_9sG3L!B<6x=ssfK7mUWs1E+hh>;XB)?p4s|8SL#%?-LiF zAK9tE7mu#!4E^9C<9vYa_WF|j?y=p~2rqxx4RX#!d(^`oz|wq$RGtbK&0o5YO{T(j z`x&pc&{bIER0q5*B(W>7xSxe@e>fAe@T&WrAY)tB9en;x4CL(%s)(n1FZ*2J4rx}D z2m7{n=u6l|#jh6f^eNu$;V*xV(q&-g5)%E^ne1P@C%JTdA%|zeMfaDX@iONw3bsoh z7B+{kzRfGY0*5>AAzS3Z_m|>td-EZ)Px;dhy8R2U){Ax7Ha+f$EabO$|89g_F86sW z;DR2btAML}?`jF`*Sp0-V2-`vr$Z*;V>%>fZZAvEw1)nF*l!d0m*yi-Ef+58m%i6Z z%izl~{p)9pfv*V%Pp1nw<7MVw9zWbK2ZVI5hHk{;jo%^brQN(Wba(pC{RMnMe$8y) zPEVKo2DaE5vmUtS%3<=AkmM9E_RX<6$^Q5W{lvxD2O%Hl9Qzg6x?{;o;45=he*wK} z$b4fJ3xAt6+;yY6C9sEc!ZF}gYH0k3w~M&?~g#vx!dfkN&y~y_~j+Y7kw=`4P2qwjM{CQau?z! zoxf0;7s5sT;eO1dMP~$CmmM-Wux&jp<_EBTv)vDX#r>hMXL~ou45oV0`u^hN%5t$T z+s=D9zX-N%W5&ONd}zoR+Iy_HQ*#Blxx*=nb$d?6c3_U}D>L62_{99oJlHe0f3wx@ z0ZYdha;Orn0Q83y$1e3t6>Qf{ocR*E!OsqB!`I;#4m1ZM58mgn?)UraKS9^F?(+=T z#w!Q>06ul4mlH7C9$LEp9I(mO!e*F1$JRY-2H#EZ=MjE-Y4mX5_{P^XPjT$;mq&eu zzi~h9zQaDzcfc#)LozNGfd}shBd(TuX>P#Go%#Hfk0$-jcLu3J{xjXB0rt}Th16aO z7p-^hcKY}P?_N1sxsm3nlPzzqhV6!m-KDTa9eBOtxg+cabccOEbsI9a<$7nGuxJI_ zc_zDQKfa{nA3d=z_PsIcEwy)>Q=_aP=h)Kzh0xW)Mg6W*mx2+{Eqk4rkJuXycb<(J zCf(mmdn9qc`?sjyTIjMYa$Vjh;N*qIv@d1bL zm6NTqfZ6xC>CSISKH~U$$Xo3&{0Dq*@TiZ#iOG{kg3FJE6W;+dw?z9=>Q^)6Ees$p z-nhRO^50c01_2-IrB40wZ&PR=VDs!K+V^HU*j$Bw&UL+EjX7}fDfJrIOXnw~UMpO5 zKee`;^B1~BO{K4)TQ_#qPWUePWlv)cb>j6=%#;*s$XIu-jQ#-NS!s``4(#j5-fvCt zb?oYGRr1yK6s3EkM_SzZ8ovG!UuTv z+*)b}X+8qgI?ikTtMqxu>+7*oH1@d-^0%F#wVF7uwSAs0fv$;qJk3eHHUvZiv##9? zy-HxOe)n2no;ly_RvqwwyfHM-EOdU<1U#ee#~<+KSR6asYHBRxcf%TJeN`Ztue~Ct z{)0Yq<#|k+kC4+V;iB=m?{0Y!#(>Gxx5TCNaq(pMo~HPc*8Iqe_p!nQGG)+Z+4C0@ z`apNhxWq2VgKcBFg%VcJe?1a%qqiZn4j+AdW^Z86CvT}{RkG9D0dwrJhqQjdKg*e$ zd%$Me2S~>ksMQOX8^&j%-NB(~%NgAws3!Ll7kviq8#kP|AP+tluTSZ>Nv!)d(PIK| zzF}LcAN#r^Y1>#U^*NBoRu82$zW%Vyw7!WwWc%{Ydjk60{?hye>aT^1#@8D8Ra;RrM1k(1-2EZ^vj4hCF$E z^?G_T3c4(l&3{lU#y{4WYrytke5dYWUy8r2i1+MqcIuGlUF|@5o6W9O0p{59lRu@w z-*nC8B+ov-<^XWtds)<8%r!T1Jna{y`3O`SIIovomd3%i>A`TC(|Q(7*2dZ>|5Z8d zv&4A~mr*T+F3Se}%lHl4)H;sZ?^b5`NZ1}$nMSs*`^~k0n@j>}JhAWZM_+h2F> zM{#WC;?w*a?Ki@w&x?or>&_*#zX`6nd>6R?fjIIn%}2=T4d+!lWOoI0r*Gd)dobDS zD>i}4)Zw{WLvb|j!)nDwZi)0<$ z`{?JM(C1hOFIiK+DmEV3B<4~s+3RpWjF9ZmEfpS4J6` zoP(|Z-4UDz&l9ujXWfSGr0=nm`jB)LW5A%~B`MeFyMeG8jI_v835s##I~ zBWqxdvLvb*#t*OGx83>G2C_DXN=TP&pG7UCIb^g}Ck4oRhbQ$yJvW5>GZOn^-ys*9 zlYH<@16s>)Y-#^O=ug5$o-I!V_n%h zM=7qQeEoRHD<$V_ZLA)s0P{M=p-`zAaC^!5*LC%`FU7HY4rqNJIL=A+fA=d1H@44w zTl^OCMu%OrPFhxV>>F^uyT9q4fBs@&cliJEw20coHgZ=l;Mjsnx(`e97gGBoTy)N7 z6?2Ot5{1{_ zu&$l5d(l2%?@@DU4gT+msxk0g>v?ppD9u+$rBS$O-Q@E9veyMcSI(pxt^H4JseK5% zc;wIj&Nq2JWZiJT?R5XYxh&QPx-&v$XkXFYICDL)_PHsv*1R&LV=#p)P#&pwo85){t8E)ka#_P~m(i)p@7G#_dK+^=&5wWoXUvGakA zJ%VVR!@fC|uSMQC$Or6uN_Al_hb8@bSpV0ZA=j#jqxKFMdAK`pp2jBX$3vbM_6B~g zXhr?jr=ayjVCF8(SIGIRaM60-^Ficn=r*Q!kngr{QqzImmG)3B=*PU?zdh{30?0-U zpHAat^{j%ihV8C4@?8*d7oo#+1QKvd8f0@4?u1# z*+V%^T(S4yO?Rz{|*$-#kkGk!+i7_zdQ%$E! z0^ajYht4#_c>Pz+E>Vr8=B^n&bvNw{;rjF}H_G zAn?4Mp2VAD?dcdy?LB|kabL)rebT;zx}7kq^#o>a9m6+k0+;rfM{;RC0@a_wWrh3g z`fUFs{3f%yxgzzAl^QPXVC%B9^eKEJFMfWKb@w@$QhWJU_qqmK*1fr6_+Q|xu+Eeh z``SPD6P;b(yk+%E9LsZY^?l&|X5&fz;Nhr~WPf-eo%>106R7+WE?V!nFG+avy+c-uNme@u+Kf4V4Z~h$OQQgYp zGjM5}(ZrKul|?jB9*hIcQ!3!^O@|jWUYOe<$?t)WTCO7b@kdoF;O|6S9-U80^AmFX z!+F*Xh%rQs%rw*PU`Ijo?Wn+ z=4tko_|%)u-k_@_!hvnF~0WW+%k=6kmezALXLlhi~8lne7WJ!{c2iEdyA_Rl{=wEpAYt=J(Rd#vhJN$ za}=>3s9j-b4BI>J+R*;G@T403Zk(rmdLZQeq8>hgoPD`;D*6KKK7XDH z(0A8wOZ#b#{bl@MWylAqM_hzF@46f9*EJRFe1Vy(*2@V_z|*zvlKj=<#tFd2GZY9P zGO%d_%yr^iW7Wq{9h1}7l^~zo+1H7Wbbig6A^ZQ0UyqJTW1y?l*`M}KF`=zGP;A8j z+Cz)`^|z7UjnJJgk+H6a+$rMKOoy8pcz)+P~a z__!{MiOW6t>wRErG^?E22#S3Fi(K-Jbn_Dz(|HNo9{Tfy`cl(PVyb-Cr0 zr@-v1tk3X9V8zSN2BEz<)}68|WPiUhhSt(e8zzzeP5pk=LEkf{HSNiF=)JoL%v`1U z2zj;O_N#r}a2mP^jiI#GE%~C<8hOopZA0xTZok^?*Umz>@YPz{i*B{vS_rHY)|GSv z=O{J-vn~5lo^M0@-k{UQpCRvf(uvl-&(;)F0oSNspx9%4oNfa%m+<^?S-{n=OK82z zT&1y`nLeFwOY;-*Y{~6+^!M&L@by);6`co+cb=0BoNu?`zjF+3Khu;fOUUeYnNfXB zp4TiR>~NX-RrHJaAmD8iVhA@JOpg}(V&CmmT&b_THrN&ec~!(Jiv8JZhX*io`4$|y zko?`YrGCv^$GmDq=O)s8guGe_dC|K2)L+j&usv_9LStxuN8Lc+KF2KnJ3r%nRq~$M zV<5{_8bb5V(ajdK#JY!D3|K-qGoIvM(o|0Kzy;@MN0Z@->Z zw1$4$pRvT{kBzN1Fmq$BhvH9>{;A@MzL2}S9HxGLvgc-H;LtVK=w2bsSI8SvGtJK% zZ9*+@)^KWcFFK=fNnMr(d}EF=wF&wy&(DQx=dTs(`hBrq1N^utm26e#*w_R2G*Tn1 zc6y5~a8|n`bY9J|CJij6_Pk%*v>bAd<+XZPh#0TQv^@Y^RyCCRPoMn*4*@stF_zkQ zb;^??z?OG@Q-8R1d1n%EkDWz?ITx4JLnwY$3!PJtPkyFG_tadgN92Ehf)0%<>HLH` zwc&n0Xy{yb*!KDtKx4@1;ET(^F7khfi+J8*U6%RG@4bO|WiBHgw_ip_0k7KZN#p%i zy~;7*5s_-dgMAz7%%}Mw>tWn!$e)~&rSU$%cXKE(b19s8eI0O5uWK}ZrSp*HBjkmn zMw*WcKJFrJlW#qznm8nVe*#}a22G$jRh$>=He5Q_7xQV}fzJ`poj2?P^~0Ner=J(w zX67d40v~V8rhC}spLZStPpo)P?a8q?_R8we8<6*Sc!ugC-M()2qhcXHA!|(b(tL!x zu*EP#=MJ*`H4VD@gQIA!R|;>x5%|x0_5bcOeBK)P>z*-WDeXHEPqt-UwyjT4r~7(g ztDfG_|LgmS@>X{L=>z4c~{xkJy zo{;u0@WfP3^JDMv-{PPvfASdhiwlx7OY;Y_$TxWM{CIVLmptgQti$)8Rlrw&Clk;1 zHC_$Ck$PPTv#x1sr1ny-*Ij*@5{e*T)?d+1)3ZzSE{hNkM6TN5w1 zj^g&={o(0(-P?*~$B#_!2fX&nBTeYC?UgN>)V9exvIj%X?UUNUwIwjelIgksJMom% ziMbrJ@TYrH8#{x)kQbL|j)Z^d_(C3N655|1e7I#Pd_O35R)g-QX|2nEGrUgfa2~ur zXWc6c50c;UAHw#Cb;Bg3(r+!-IEL1Dn5& zruLVPC-BD6Jk|eSQ!hH}<{YcLG{wU9NV9SS=FRn=b$6l;WWK?JCqNeI_HnydmueLE6BgvHkkrDUCj&!HmR$(0B+vnTLW-E{iQa*`@6TAiTKQ&`MEnfP%c>w z^8LX-c2)Ue=(m~P*A3X?$uqM5U}#+doNxHu67t{&7fetKr7umhftA;{3B-Nq7nwn?zfJ@bPP<+T{c3|0JvAMEaf5BxM&9O+Kavh;#eO|v?c&A8J#i<@|?r<t;M-RcDWd$-jof9ZM%brn8A(45BckIqk?V?VTS zZVNgO9Qrsg7k$Wk^ekVD9n^J*aN%#qFFPabeMib1p?U7RRX{atN367_amKz%uPU3s z_LHnjYv`*Qm0JVXMVRUW54ut52<#;}7bsZ$&;z)wg7FBPZ*c5`5&e$<->vOJT*5l< z`3+B7Ef3;hB=>NX9{Z<3#Du%5FE?PO=0XWkpD+YMIOAkxnD~T(Q0GB2{B7Tm# zp>e&vD=-mInj7vQpOxrV^+^$ez?7LRL&j|HxLnHdQ@@}mWvb8xLjwJN9k8IL!u zBLiuT9(bX9CiGuknb02gk9Vb<1Fk*fNas*54k&~J@46H~=afdp!^Q%?-f)MW51cD} zUIX||N_08S>n^|RoD6>4R(%b#oq;ELB-7bqNoy-j;Jsa5(YdDLuRe~z`bqip4B(27 zl?Q-drM;ow({h$+cma>Q6;IFc6+Jhd3>^6&l79aRsox?8%zVe6=vRXJj}6*P=VXU3 z4WaYM>PtQ?k>B6gp>`yn_mQyOm-2zYy7?pM_nu72`EixzI@*WyxVYvu`a|W1#Z<3? z8>h|#hlaJK^}q;QnGL`p#b@Y#CEfpoK8#1s128|xE!B90_r^Zl-w=vgMZZ>b#Ds)vkEFj%RlX_FJZA=$6`u@`3s6KnezSU-V z?j$_bWCip~#$VeCe8e<;A~16?ldT*9{CD`9Es&?g-t7mRytJJ37wK9wfqU}I$Ze2w zF3BGqb^xbG51{Yx{PMBd2Q1BBr~{@x+BXE2Pc($DAzDkrpnEn^_Zx80)=!6lQ3pN; zD0#BR46?ards5soy08 zCpHMWONQ&y_nGEao~C|y=3V7)_+s1HW}8RD*F3qY)OXnTaUXlKx9U8TzTd^MGfpKI z!TyBCWq!XOw2B%I6YUd z7uyb>s_Y9a|Eu9LDtSZBT$<}2_XAcsDDMOLFz*c; z2!Av11P-{69|$bXN61S-$SVx<&fdBmHF&r2m`&$4Lf2n@$O71U4v3~cj=cDNP^>%j zLm-X&*J(@N!1m6AMWkCBpYRKK!y*U5V~RC9iG69Vx1{ePvG4VpAIH8> z6GrV<`tGX{><7%d6#{OLZIbDGP{XdNYyu9k^U;8uxraL+r+5lSu2A0fYVjYyr)=zZ zY9j-!yOgW;g-)AcFP)E27c7;jpVqGmdI#H+%?D9UwHqcYAeO>5xBuFk`)S>RKTXh$ zUpbuOj&=2;??bWfwsYp)VOw=MApmjNSK!YRRP$=hFKr;_Sap-rzLLKjD~iorE`FCI zd+B%r@16p0>ZdhVE>P^Ah1;laZHg?>Cf=`ND3-XNR<#+}8M2F=7Lcqwe>b&{f<%{X z_oWY{eybsOjK0^kr2Bj7cba>SsKK6NY4thW7r4-3H^tTs+u9bmh3aA&>&z|4;xUak z#y)SoDNkuWLSDUuyl9;__U~;u_?ou2As#g>x~K03oPDO^Brx*g{Tl092VYBstXh)i zX~&8aRR6V+N@Cm4K2h}jt@m4QkT3uHa!Vm!9rip~9LxGZTkW3lm zfk&Y)9bd?!w?L21J1WlvDZ_T;O;z%>RHIb|aPyRpG?yX|KJQ@Npe4g;ULKiui@qbq zx@tEpZDBjvVlU0pS#_?bf%j$mkiMg3N1At9j6RnJUL32Hk?wKe6`hvfhW>|1y^?_4 zhb`C#%-ja1*?I#{ZLf9=@)7sUrUCcv^NaAP)8{Df=tsY)Zqj^(y!#00oWVTv@Kt;U z-X;6Abyr#w)I`*_hp(RfZk~p3l!FaIBd2$fA+5It_ zfKxpm(S80>lG!BSr|V|#g8ce72YP=y$Lbe2mcDs(=5I8ucf4&2>7BhA&DN7Wb31i) z1FaJt_`O!uL)zdAWWpJR22Ua0}xu9dDAVl$WXXB=t#OUDy< zD++XI-l|J|=ztpce|CfBmYeMx=z9S<-Xp2az?;{7TcXZ}LYHNOyp$dQPi;{~eXIAM z7OlZ!&F;>$PAC{>Ujez|?Qt}Bv+q0AeP|xaHTvJX>^Zi&_NxTw-zg|2`8dUNN8rQl z^awLo_nXIPtuD<+$g3acr8T4|1h!N9zS@hLY{+t?cl^JNol1R4oR?PNkud0H&#>DM zUAEm{ecoTXRgwGguMC-dC_{-UGi<7LBC3~U_0&5$2+Kb<*Iw-z{rdDE36y$ z=0_Q1S0w$eap<(Cz)Lzjo=CPYral2q8fUUw>}&dq_BVjH743WoImas0+I|=KsQknP z$bXb~%>lMf)^`SGZjNncY7)NKcnk8Ci{ADFHtoAO54gwTD_wwR_E({2#BSdoN8cwH zqt%1rN%I%#FhEGp4f9&mFgNyBnI1!Vu`k^RwznZyGwDsf*FMv@2|T2Q2Yu(2V{ek_q6hn; zD_gEZ{vv8kA7H(r{e<7m(Pzk|`3rRzB&0|8v43&qm$8?g7P^w!q;7b{XvF&P{vzdyI`Dn$*ZWOH z&}A9xa$R0odyazbluJ1jcXGo+dGcja{SiD2r#0^a?AFJLYW{8fr5gA<^ zvoqwyzvmxbJm|exmu=O<%BVe4OasW*yX?B(;1TbUQVV@chYj>R+rA-6J>Yv%X-sqY zj?f-E26*BUmDa%84_)VqV?T}4TMcZH?M8CuX3;;E&iRHeGTRLK$tjPiF9!E7@d7q{ zt@H!&HILwx(oa!_aw=a#{Rkud}8EW z>Z{W6g*?=RJgA==Ux~298PX|9U+FsA!xpx$yY!}K2gLn+V#xLY=>GeyO3zbNZj-qT zd~J3xJ@eq>*0P(}*5hq~D)2SOd-Oa8`)=qskLs}U$dm<;JCBp0e(c=!t0}Oh>1Y}U zfg|tK!GF-a6aUa&VW&N^fn9R$(74&*nUe_2-1lj1^99cG*-rA9$(!ten;RJZuWpiD zPxO+d^KHX{r5=zoAL;yrI^m}j>gQHBnk9kTm;STq*^_rSOFE%V97lAdXZKJi?&rsp z&(J)?vbd@8uVDMBaRWW?!M0ad?xE)|Vm`S4fIQ9pI6cqvwWD$!uwra0!twD2G|$yv zct_9UBtMm zuaNgpAsw3EGkW^DLwB*QIF}YJ)Uf(HW$zD9XBuZgsk#G80oUD)U5d#7{<3Zz^$+%aezOMkuT!=LspM~a64@_k{W1ag!jEI*-!}d9PGIJCzFojp zv0R#ukk>FFuQ1H-&qLCNVNJ6l>vkpjR%hdWv__h>-+|@| z*QhL|`%<47|MmkH512ytq3hdaGl1_uj-hn~`+oJb=_O(<>pG$c^5rM|UjX+U*pvdy zT)do4hXAV@W>i7$UvISpIBDUl8sJBgbHb}vN7VvzE?11N)B}gC4Wo0DqGwBJ9t=33 zLgzP~6e0*q=Off*xR5UOb4#VkK3KEYNq*C6_%`J=@Q+D_G$)Jud9x{}1H`i5@8oD7 zGxm7IC-5+BZI%Ri!W(U>)0ZutnZUKVZZx02+Z$dE+&Mj#=5LNwryNUpyh)9%hTPm? zGS$(fefdRT=9c)fIu_XR%RpN1F`sdzc4W`F6ic4JXF2C01yeOg~dE`zP3cL1BM+fFq^p8TBD@F+Dev5a-^ zU)x6gwtSbf737)LYbnotC*sctp#LVgV)hsyhYb%6U4e-Lv;LrC;V}u{nm%2 z8PUM<`r1=SXjui5w@n*BgS_!_lY&}giGB6vK4t%R{1g&w!VqI(ebtB7X3Y(Crigu;^|W0A+$-P{&0BHrHU$8W zJoAFaTTIT{NMP$YIl`0swoL(EC?7)oj=6?jX-Dzjm0wMS{JCTwE6rEPTT{rJ)=ytX z+3bdHkz3z1aDUJ>MH78^z~K~XZ{*GEr`lawkD!~8|KS{TKRRxV2bMeC=`t|;Qg$_^ zeZSEZ`TdY9)ojg%e6M}g72x*zw&c5dLuw^3$Ku$7&aAiq{l~{0x`PXIla4RsF-o9E z_mK{Mdi1X72Tl=3V7qhGkEie*t}-$U7NI~%2leRWODB;N)dJKuyp$1-1nn87I`|PtS+nK`Ee^$+h`HH4+}5$ZBp zNSD?P0acCJc;~{}^FO_?X8qG?Nj%ID3+r++1Lwucsnq{55NqL6)BSfa2Z=?!HH?o0+b21o(T8DB{V!e*D=);rLkqyAiee9QF>id4y8uU() zOO5BKZqj^(ymf?h^04l>Dff6MeED})rDw;uO*l8?jUVap*E#gT8R*`V^m7^Wq9?!u z$1I@sYrbv$H(+jmWBtKn&>rk-uhHryz?X6rscsyL+rPm|WjW*n9X`-FdYf0a5V*$m zXaMrux3F;m@I5&z!f}z`=sj_MTZU3yXX+jp4Eg2$o2X7R26druR{? zoNL)x-O~^H%Xj@wg#PS`iuJ%<4q7GycbpzV{H5zDw2Q7#X6ldMVw5PCUlwmXVY_AV zVS1kwj~#J;lpa^CyR;yU`a^}qltaiXVp}lvr?FdogMbIO(4o8xl(igzIo6bi&CG$v zMfRjR#a-Y2f7Fq={akRC>KylOAiYCeTG4$(ny--eSRrpK^vBj! zinXXw`uK%3W|2KV0fz!`F_2H=3&lrjOv%2s5!^JJ8A6>VCi@QZ#{tyt&8<{T0F$NTXb{uC`bSN z7gG_d-K|-4&j4@Ur?Bpk&(<_g>9rqF4_l5?WfV*A-L$*%g>v3}X?zc{uhQ*H=^dfl z_LP#GW7#iiPI(WH-aZuiJpRMf-nR#4Zrf$en-FiHX##QGrC{vpfMliXnO^CM)%vhQ6uEZ=j-u$DIt(;TfcP|WMA4&*(28dP&GCY_@adi z$)~PQBV1)uL+$ad`yzUODYpm5df9yf^^G?+Ei+(m8k0@)$%k`Cj{#q5|BtvOL>SZi zGOHzAnfs1D4`^QeGq+#~^sA2KQ2QR6euUoF^Hs7Qk>)SdVZ4wYt)sj*9?!rzQ}sm? zn&Yj8Jv4@|8P>`F^;2F)-8sG{2fE4|no)bRty23f)LwbBrshHZ^_m9RX0HAD2bg_b zaNn*%zSn%Ax-7U5H5K^c3w7env5&Mpwgq)Q7k+3sbbbKKXea_?V z^o&cW;l=94z0hsfvCUxQb$H^A!@%gLoCoX1eXom#EI-2T8f>=?8bon>NPeeb+fF8X zJHVIc-pPl=zP%*t)-EGAwT7HyU*EB@3I6OgHKU-~*PjOvKR#u38OWO-P zCJJt8e%tzSM+VMholOTif^+5&-6c5Fi}ci`HH>(E8}|I$8|Wr!o}v40Kl6EX-psme zE8lIYJA64=$IgYXl)A~zz&>>odjfN;pzx+@_$##=MX|S(x6J}JXr8|k`poUG%1&3p zx?`?G{#}XUzarU2bTW#BTuyTTew{Eni16ZKT6ay3n&}42u@5dNS`3^aJC@cx-`qcs z2R1!3o%EUO%ZYNd&XeXNKpvLtDIyFg}iti_m-UZvdnRC zS}x9KJ59Y#?ZdVzMm?$i4z3FdAU`%$+YR;3U0%5hm}7Bl^WAGmzA^9049NAC2GP37 z*EeYu@T{gGl&5rjA&)5nJu{3?waiEh=;}(==&QVQ6M)07C;qp;;PXA!-EMb|?$4^D zi|Ba^Zp;1)z6XeH*;o9mP`VH1j5Xc^xw2$k?jbq1=GfBy1)fs{iq!v&v$f9R?6%Z1 z-3B$eCGiz`SeC|)xc{^6)i?Gu?hIP4u7EA;dML{fPqlm9R)~GAT$kYh++XtC`tel; zi-EVey7d5Wj{PaU-BtK|Sdy{?a_M*i?`Z<>0MvJQStETz{_(jEGogDxR_+#T@5>ZW z+krQ)OCJ1vdkN1c?Txx<4PCaqo^D_V?D_BMOUNf^*b(oHxcej|H5Q6vO>(_p0jzK; zJrVYk=OxS*`(tjSBFhIrzJG?XDdamJ@BNMVhS4E}rTGbYP8aCMp}p<9`5(YpV8zYW zTClz9RNe=+Csg)RO_3*mN8z;Jr)=mx|7@=Z-Nntm9RY4LY>WXg`)aNJdm3l*^@EQPq6v-1^i`KnQH?}=P%UJSSSyTo7&jrbgms%b%J!eg#HeM?b8WG zMz9r+8{KcW=$tdn)?ODfwq4XXMISg!t><%`TR!XHF$sA6yuz+xU+w`x(}6Ym4WT?& zP5ELEeAc9#^5ocOcV?2m%n^ruAP=2*h}!LzQm7R$$5++Zr2uS`T~BoxF>viS#OGWp zTc0cewvo-J+@$#nbuba?upj-YP+83p-x$uaGNryeZOFEH_$K!R<2AH~KpptGGS;0q z`~b}tYi`;E!*+gYxFhm*K3sVe_}}waG=E$!9(5h~Z>qTy<_F_gY{Z6TR6r1KT(HbW=}jYH+PW0t{o zzNr;RY zE15&}J8OOJ{yz7)zn-T**0bJWz3^h)-GV z_yc{34X&}al+pk4fWHhaBc3BW&I?_2?V#_*@UcPiy~rFtatUz4o)P2~>zDdCTIwU` zZFLQIb^HcbH`$!vcs0xiIOCNn=NR-+%-gA_cXgNNCKYWN3B152ly%qXsqFz={$Ug6 zo0@U|{SH|`tBLG`h;3~#fqgjD?w6iT1PTKYRTk zP3CKXYiU6?G1I?syxv-DP#&IA5D%&%gddo_F$f?hbj*-<3;BFXlWce8qU_ zTkx(Nu_i>%;rK9_bjJYw7O|G^@8u3`GS7{%MXpnGR5(NN=NzFE04`-58SctzEj|vR%~huy!w^# z5#YDcv-E(wvC{s!`m7Bn8>dBz=A*gYz6fQf1y{geyNXRq&{+eI?;cLCwlJdUZsgx zVamZ7z_0T%+5w}FVqYh0|6Iyz4bFbGUjS?mC%iHMKIm}xki2hs^0qYK?RKH$OaJt` z4!k2|MH|?Mx6~ki;r2%MP2>*t&}H8&Wxre4OxJ~ds<3~^-$fK!BRe!bjCjFEt6CzS zZPV->!2MX-UpFO@e1>r?!<>`CywT)(I>VwGk`rUUQ1Je;x7 z2XP&A)U~;5SXK9SssexWoXFob^gF241UNS4{95=w zkUc}^*lpxOV4+25J2o|e_KNwXI>t)!R-lgMW!4!O8>@1bv_R|zhDw%*^>&LM#|r8Y z_gS)ycin=ms%H)RzbHB78L+U;SU2`FaI;-|bP?;q^vSx2^{4h3+Kbr1qjGt?8u%Lz z1|Mry%(0|W-?J<5yVw5Ia!L89DzMO|E&Kh==CL~Jh`)G_Y9aECO-|AP7Pa(GbYXl& zy;9xdq;hzDUS*Jb3csOUeQsDY_-^R1c|4v?FhB5sV-IzU>vLf{qh>jK5*5ELAHKr2 zbnv!+z&R-sc|JYAu+#>zX3FLo5!>(3bLuvat=$oPr)j&IBX`AK{R?RfLqAk z-xM?I&Mja=zZi~dMLkko4wBqp9M3r24UMs8e)>LC8L^kP(XJ0)^92^1i%^%izqoJj z{PD0g&zw!&{fldFB$nB3vRaV<{5N(J&p*S5XWjs>GMz~sBKGVvjaWy=QD5JKH>wlG z=g_+C@i_;)w<@A8w1|8{+mAcZ=HQ*|D;t16GQhw9_{w8zj$2WSgToabqhk$fse^wo zx0K^U;D0gKs3&r``4I5UW#d!i6ZJ`ba+J#Fb@iQxhnk`<;#pGFPs$U4#W6vj#OF%- zmK&OZTO&LFgm3lRqNdP&vyW>ld0(wD5vIVRFCzAbWJA{Fc}8sl_!<{S&K)<}6|@5u zS`_n1x}79BfjGX-L)Y$yZTs8JxYj(^JY8(0y!dSp@P*1(T;me81J@J#AK|(tj;F}2b>dD}@H#dYmy_=aEnGX%J^TT&sg@cmz`h_C!S;lXOXSKxhiy7D@G~qL)I38kO;8Qz`zZdOXxtO*_JKT=L_Oq2UpJx@ZYL$)Pb7dx3pMt^1`5W^& zwEGL=`FqtO_M$)TufeamwwK>A5xGySoyli2^*ZZEpy z^Xp5+8mYkkTWvZbp8Ad!Nx+Ri`|L%2p?z-A+ndm*opmS_{BY-wxv1@O=z`P0Cw1+% z0IO_@Ed&;|y9Aflft~{@OCE#&^SNPHV8wc+zPU>OgVDFb{cRudv!k4gO0XT3wt5}3 z*7WY)1sHwnApPS{*g7YvXn-q_eP>W9Mpp~iaCttz@2pvns}1bCq^=t391=E52Y99I z`|6sbRrP@vYyRfv;d2kgwgFzWB9YHy4-R;30IY7IdI0`&l)^gzx7d&p4ZJ7av?H)* z;Nr``HCMh7H;yPN2JUPTX9PaUFto0GEp;3wH3!~pf1bS3#Y21_jFVMc9>-b_Pxk_U z^|TxFIYnOQdpJ7(P->5O4$ID4f#2h4(H6MM-fuYYkO2eqfREnS7z4cSa7`=V%Dghs%@s4v~}7a@64;EBju<(Dc5|fhRP0uLb{8M?N(I zZtzL_2kI?b+p!+-RDHY0z!R=#aU4wh*d+_Nj?;(Q;F~SK#OK!+&iYNPX0(N$zb43z zy{6tGKA*1rCX-`fX|s6);Gc2Sit+EdCmAwdf9nQ_cmJ-3F7fTVje+wY-v5rVR64M6 zGvIZf+Z6-Pckb5$SaJMFW6(_+R$LEjPgy(%bJomaonf$jKmEN4#_$od)=t0}hhjYx zT-xIeJbyoSzu5-1xwB@Dgze#Rvrfv#s0`?;JjLI&y z%vHx`K6u~ik@mpHhS_fe&ant}0FJg=m;o%bRXppI zRJXfS4%d~O#DNvi?qIuk5Nu~FH(ri&KXk#_HWo0<=q|*TU~pPzVf!| zWuZ>M@9reC57)we%>-UlP>1#+w%-ODEBIeCxG)X8#mdB?z^(Jc+7mx4qLvjqchmtE z+Jx4sGgoe%EQq66E!QNi?m?r}*KSi~}$+qMd^Z>ioOzrEyN32=n13GvRbJGsD@(oPLP?vS`Z ze(qc})W-riG^-w;Gv0O9Y#{Ky(aZV#byAaggMo*g>gxyp0|t}WCs9jmw-9Uc?U$_s zFKYSdbTtWhSeI1xWmoH)H-MXIs<766I-Ons?{D0K{yispG(kO2-L^7*k2D?D>%HBb z^(*#6>g!~wT~X-k$?)Ynur@W$^5C4=zi756&anocYRsSweHHgD#^=lmhV2>sF5SQ# zKd8)mTqACp_W+LV{X7-?w|jef0gur%y$ih9@(b~3PY>S560r~Vtoj1}c48iL?N*H( zj@%>ctBm09=)&~D#b;I`XkKA*%3h|d( zc+!8Afz4;&Es+Vtwz?OKfE(TG#QA#MxXb+Bf2N&IXZVZWj9eYUbx*N=sgF~n_6$cK z>u5NvM69i6|1h`h$gxj>&-7kqidg6)UT%y(VJkRc8Ke8ly&fGZu7<&)N3IzwHm(W$hfu?GB$} z1uSge%6x-uH;Novq#YnJ^QxbK8V z#0PISuOlBz#7?(+&N{z7+13nvddQWg@c(&lDc={fa^*K_86z9xLfcqlpKQd}K9oS- zap9ipzzI_tHGzNCp{o~x1H7IQE9#Z%_LRyA#PQc$QNI~%zs%66!JgEq$+NS-maCoF zbJQ)a^Ob+@iw38yRm!n)a=TRmu$Avl>JYxI?yliJCDDAt7Wvp`-IV75Z)nYy&6HQ==)_feOdCFz0JHtkD2Gkr6<0$ZLe z<@h=uw#%MaR!efdX{&(K+YIM%QLIPm%XFz+jt3U~x6Mli8xVv{fV3BKstPh9t z8*x4z)~$UtwEX*)S_pfg%_%97&oy-0@{7k%X!jZ$uZ(!(zh$u&)5(U+r>Iw|dxlgu zKiBiHzUGA3B?IsOM6TM+P96h3a-u(v3F;P~>#1Z+;PDe&y&j$U{+L9K2_1 zb&F3>cl?r`jFt0iH+w2#PdA$R7rB&^Qe$BMrGC3Vz^CqpM*w#mHRCsM9ly{$z`p8B ze*xF^$>sZKnk&s9c2f)32wrGE?XiPvMUs&L`Nr!9@;y4;yh<2PW%^8K*o*w4_M8_Z zs8_K*sZTSd@;Pr>T`f9+y}^$K>mNZ&P&;oG>PdaxA2Of7U(|M`#GUaK^-F!2 zC6&j0aIey3ZJ?#K$5LuD3NZ5l&huRR5wXw*u@4rucRG}_u4f148pGCMT0{wKpDvj? z5ZGeJlMleg+Fi%X$Ljp-z(U}N-Dlo|7rEY#*-rm8Q!~B5f6Fm{1HMr9+(5&$;q3E9 z{Y-vuQ)pGxBh@uqlFNCqaA#jz#CqN$ow4;b=41f(_;c(#VxulGFZ$m$vIE!9$Cxz> z-$2hBWz_MwcpUgem&3jQci!`3Hn850%@x3YC;P0HkKLiW+iu`zOMSnAH)uHS2>DKf zh=mrtu_=t7)w16?@cLiV*|+w}H}?QwZ`S94YJLqcM8U#544% zU$GvkFLR`FIqv%z6}Q1T^K;2bYB6@1I0@MC#<&0G9?4LQ`;BQIhs$%Dv|0`W-tB73 z^Z)7HC|lrSO+%jl{(gHV0&7^d=J_vTubscL5`B1@;$#Q@#Gy@`n>HzBGoI4Et~@3^ z^-m54KQ-_owH0UBnE?x}TVI-V10LV|2<=5JjYs$9d=RvLKo{^!j((y(Mg3AA=1S#p zec0Dn=RJB>aCIbOy;eTl9==zCU8x=8L+lrX?b_stoQHf>0iF$-v{7jhdZ#Zih8BG=Sk)8d4(|-jQCyYmz__3Megq|lOMo$?~noX zMcv|g1>2s+*X6mN^WJ0vNBPyKt?(7PqHN;sf!8!@%HtPp9Ciy>MG&(_H2r zGpXu4@MuFX+6!%>4|cV4FM>B)XURJ4v~`J>UGySW)F;(BUy^T+ebJ#sD{sKJz;`ya z9;j&W61e8H1;;At6whsbb?xv}o)flRK8)h=irA%DBp)kU)0WSfZ7u7-@gQ<#{z;>K z)Y3tBVZU~~3CEIRd`ag5DLt<{k`8$K!M1H5OCH~gne&9; zVm4N%$=eFwWy^L@*SHqJC*)%%1Qu@q7X1{te=7B+ed6H0Yr&6l$>z9K%qQtyD9PdZ z)3tusb*xFI`?X$U9JSiNQysox{?4C)<~ zA6gP%+2sPrNKKx0il z`0#Om-$L8EVQ=_3fp%h}cfg{SQ-kkw{#Dc~)$JwfE?fB00kOI{ct3-$@wAL9z@_>* z|F7;R4QHN}=Y;M2Yga$PH}GqFejcLJZasVW;{N$i*n3TGPz-+O_{+=Wa}Cbrnn80^V9!(#Isfgju0u#cfvU%LV?C?C&0n?D@S&s)lT{;_UF zy;9xYQr+BFj##>;AAG+}ocsXV0@SXb1K#hKRRoOVE%ucMIyW35&%N`lTm;;zp*L-x zDL>`++AD@V;r>K%|01y4-6@6gxq29AJObX_=HfxbyEWjzL*SPad_#cs3e^jM%}4F# z=R~(1-sJ-i`0#WI_z0i6w6|Cs=>;rm2^$&f4BT^15Nj*LJX1aNmXAh^5=7K5LafFI?`{`T|?;mQx=?XH6e<-XC!bT*H0b zAEQ<+<$Zs-TXX=fSmH(gM=JwU>b%pQ{vy|={6h92y69mS@W~l3$0q3~`}DtB8fUWaih8BG7fW?>Uy-pdcP)Ik+#7uhwZv*oS3@q* zbJQ*N6@qJMYLEk4C#81P@clm6E*m&uRrNdI;`VRu05|ODmBZLkDYt=B!@iu8&n0qi zt~quWe9P}9TfyJ?{{24i1^W@nz&+CqcwcC;p2j`kh@JMV+r43M0kLd+B-Xq38hCL@ z9gc^i)eenOOJwP)T*OnUSn}sdP~N63 z;F21?BiGAfKi{JvY`aZd#CLRb^im;TFt;`5+!=-Usl%wzq)hk^Fb-fJMXu!I-|r#! z!_4Nlz*pJ%voCAX+R|Q4hVHA>2SU&1V%PdsKOX`MM=Egqjj+&v|d=c%YA=;DB_ zh;n% zTxvs~#C1o9h(*ujxi)pralU`n-<0EH={X-_htz}AC1O>3G`NA-*=<9=!9VUthjic$ zE<5?Y3X$9B#ws48gE>d2MQCf7qMrf#&VCPX!oSmtON_6mN2+U?B= zGVc@ct=Fm^uX)t_WL*PBU3i&2{ygl=9k7lGTh%oOt$@o^Q%=G*&7rU_aJpGO@%8-S zdcd~tDv7=G$MQOI+T>v3CHHRpM(&vPi{jy*n-ft0>^yf~GVp^9-R=OV-0zzLEVMSt zjJXJ$cW_7!_}(@XE(5QSt)soP)2{&E9=M=3@}HT!iM1>+YsB|@h}veB{4fK5Hm~{; z>^+t5S_A)B6LSGL<)W?=aFp7$^S}*0#?1oGj`>S{qE{Zuo(bTS`;?H63BSbp6#FIh zeYw;|j;HU(j`x7}q@yN0M(=i6an0GF`8^x4!>p9<5bf6`zsu9nzuwAz9z=K`B{%<}J`}NAy*#_XcoqWW3Zj|c+ z_C(n3@3zMtyyjk4`dSW1nF#EyzMto-h&`j%Kjs>~!kX*2$h}PV-k#h^$>2jlbZ1r^+|QElfgDJ_C9w!6TH)~n~WDU#yuVQYGexYm27ZM0KWD*lzrU&HzE>PXt(?uyBYZ9+Ee5e z^-6X7h;jQjsCYPRUq4poSQuvhkM|)Twb;ZtTYlXBoit)3Y_H$h$Z;AYdlu37=1AIp zm)&<1z6WIYG=;s0y{+;B=h$yABIhICii8WyJ^JE2H+e0UJC4``YhCL?d!cpPtA#ud z6!l1T`HFEHF}cqY_{RKg!FAB$O3N7F(E4BKi@L;296*5<$v>zsIyLk3_W=+ zu69S>N1op@owmYvee7Y@`Om7aCG182V{JniyPf(_9 z_v!+#xLKF>LaVXCa`r8;$pkI%wOiFNpQ1jgPCu#6t610eE^NzZ04E=>O2wR+KRvT2 ze0QGu#Mn5V8qz=hUiI8uhWq6^Z>-t7y~~Gb^UM8 zB#RxfHRHea*L`7k@UuQ_=eo1@*48e-vrW9XEcpl$ zI%~jQXz%rKTQ&4G`rVIwzvg;xfm=`5Ks>Nw%{}0_r|M~_bx)MvO<<#b?YXXfIyFBX zcv41F>KQz;gjmnyEcX|pSDhSsQ~$@G>U>^uT=iP+M}p(-KZE}w**@#7W^yrbTBH{H zrPx2|IINc1%KfEspBY178!79}=j}NsfuCggaW9MGAg;s0doSj5oV&hdb3FV$`frK6 zZ~T1cp};NT8?f&p_WL8jw0E97(;9q_t8N@GC#sIv0+*Eb;e2y!Ssb5Ftv75w_t|lI z566Sw7}TBfy?v(lY+(ByF`NgZe^;*p7FrKz9S8yLpX*G1s7vt);IT(^xStiZ#hz}& ze5Iz(kAmOi`tZMbLZLpXPiv&|xgW4C&EEvu2c3Ie#W9qA42|Pg^g@1K{aU>)8n%K{ z?3>lO+(hse3lFkSrPse+0QRd}$>S|zpI(;7T%X7IUY5@twY5bsaL?FLv=`cje;LK` z@v~zP`CW~)*mp%eQeA7My12i*`sG+To_SFY9DNdX#T>|6gV-CE22fntqQat$j!P#cOL;b>N zKg~8>6F4;OO%nL&J(E>|*J++V4J@>-oiv$qM%uulI^eH#(BOI0{E1^-;NAKW+;8@+ zj!^@)D(b{Odd8LWSzYz%Q;E-B?cNEzgQW-iczU&F58$d%mv~;ew!AtBSo9*!qdlLO zJr-U`Ui9kNtsU%pjBGp(tenbyzhb|nzOR$onS=Sy%`Msju|+R6lpO9e_t6jZMc?t` zKluARjxv3>3;1W7B;qkka(#gtC+^~LSFG#A(YjN>FH4Um zuRJ$!4DB6foI||2Yns>qzvvyq0n@?NA%)M%NM+Ea6LTSl6-+V>#37CoAZQz zNfXvv`{fM>@S7`jiEB-~GZ}cqv~>1o+?(|rmw_9Pa6TO~tiK<4_q{9F+bG#QQ6=lO zVt=LMv|eg^7><*7>K#5K7Ba(%$GFbXmK?KB4Ucn9!f_JMqs}er`Wokw;4U^SAcsu30Y4thi_3$8fj1G}rFa31nf^3DWSYd4JZ zp{QN=p&j+;G|J8fFZSb#^-6v7m)gPmW$!mFtc0yN_fGHHZv<^ZEBYqxmnqh9Hb12` zY)iX#VO=nsA8dvBrzVHvbGMxFh{}|5Wx=prcQ>;ho zOMq0a3&vCHDf2F%FX37V9P5|9E%*g|-Jq1Y(HHUgys(`XyoLJ`-7)bzKDHm-X?rbg z<4bv8_4l@=!0iU9u`UsNrEP7VN3)%}ajp+n^5nd5<4tgmycWfLlI{(X9G(ZSP93&_ zZBz4a^woR3@FwuoKJ7VHQgg@ z<8;ZyM_0hFIQgA*F8i&28ra`BntizUx#}3OiSIMw2c5R<0B)+{&iTOj@T`@@GOe3z zpG*a=yynI}Y{@Th0)E}Ym-eDI7uDy?SDfYR0Dk?XD(VTCljH?FbyZ!CN7MU#i62Fb zU_FZUN`2cX^^NC2WkAj&e0TJ|>sQWw!A+uiA=X}>nT#cW9_&BuSPPs=<`8nBruzKe zk+8MM+GYg)oV7ap*8iSEci_7=^EpmM?6Sd$>`O_(sh;4Q&f3he_AV`=E3o#(gIotb zXYOTuq0RcEwG#N$*RJG8o_7BP?e}VK@R*BQUYCY*d@;AU^=KYKU7FrecNp%KF^0~hrK61r1*tSTV_XNIuR2rz^ zUTjCV4)1|cr`TVAIkPnfwt^ejvj11++28dYZ2QG)G=Z<%-D>I(vD{?OnDm|1OBMX0 z`_8PZzuqk+;Nk66*eBuNKx^qU#Cx^r37_@SzZ}iaF$UK9!hAxjRoICFdH`iiS+!aZ{{M>eFVae9nvEuO`{!dmjE* zF2%Ce=3cAaY@pBuWx z_ildA?P&ncWlKUFY=>7So6Fk@-v!z`sq^6IfZpJr7(HR^Kk;^2z#^B(UGBEy5aRvq z(_9_=f=P>5$CwefXz%Y+#CVGNr8>5XI`&0wUXJg=JiQwqgIFQQqxS-D_rA#<%GY68 z?-76Z!*t(za)ZJ@?S$=0!<(#2_^M6_JBnC)f`j=x03vqiVU10&f0E*H7<}n~+iQru z*HO37;xn*0;|VOZDCU>y2$JgHyi)1(-3GDjJ!VHUc5>YSVAZO*|EojHD^X_FuHY7a z`olc?=No$imoJMVE(`m<3V5PyT$IXUO`jP^-9wKbUk6^~8rE{}0^k|xXPEo$fwpOsk?ksm^Uuom`Lqwax1X+mVUY)S5ne|61UF z^^Yf_ZX7SMFOBVNJOEtv91n5^%l-H}9>R7+cBiS}U(fAM-g?^u{-#9S`AA}|nn{d( z`ul8Rkt=*wBCi?KeY*4b`Ba+nTn(@CW#1|?@Qn6mCZ`-AIaGt5xWR@~~ zdo{|VRuRjgd#4GN18W>ZLxDx>JYvoR4bUrrKq+V_(@${={wHK#spZzihZ@g z3|QEUTtfFz|B&wBg%-tplI|Un?iHA4GS6ji;dhQYsB`Qj{Y|O{_A~V4H5_z{&$DL* zC*F|frVc*v4!A10A#EFv`1JueZt*2vw4j7{DST$s9reDN>G0N@2H z-g15xS_@5Ya}MpT7tG(^a9$R41o56&2ZqCb-qj%H4`^e{-^ch|UYE5f>X-VkQ!0=9 z$cewyw&R+mUiruGh_!HY{$#Apqpr;1cTLa-aXn?0;Anx*C+(J0@%taXD(?IZiC<4g z@_RH+D#I*sA4T}KFFMx^vFuEB`{4Ot5&Pfdb^LyYz{hps_Rp7{>8SQPgxc48_p#j-sX*%4#dCD(YQPHFS@fj-tAvtBQFH^Qx&=$5c_T zqN=DVib_Ow&@4M>WCuIgK@WD&NDp?fgGP4H_+I%vClbq+PwwBJ>%M zBpp+WSv(ZX>%dsbJBppsv95PZ$CZToxFICx_&iKwJ$F|~`Q6m9bV6ZbFOqv=3duQf z5F_cNd~CxmB zf(w&*7c#HsS-3x}pXz*aP8|#Di%Kz)7WE>17EL4bEiS_hI-mQel_U2~8%6q@UW8e6 z&AZMhz&;$poV27S@i21l%%()YpZjN3BDrTJ`DZ(yzGwF&9tsw%MDmL~Uxaf=-Z?GE z{d4?1@LcCJ@44>xk96KF_DM^(BmI^hLC&4uirhcn`JBJNb&TF;{laE!z&#jA#VO=| zvHRQk;4=58UY5M)qDpG=FY0;O9T|ACyDOG^vrku}u%0KU{v^<$-x#uD8lGXI*Do~we`O60 z;~b`?>s-_4x*;Uz`o$RIF690V?nixt_o>UvLR~%{ys;d4*Nx7Pq??laH~Bv6s|t`l zt2|FNj)e7!rAXh3E~Ib8Oz`G9WX_upVJjpW?o_eAJ< zsBbO87zaYV*7HQt+EyfQ?IC2os+FOx@_fu$HHVRO+xoD6oBMITx)PbY+IjT7-TBnF zcO>-@q;E}Q;y#R}J8H2HXOWycz0dtS4`L+URe?G5Je;fDisaP#d`7z4`4Rd&>**?F z?sR`xPtPNH>$<}JI)BfsROgz0b%(?HJ?n8;x;H;@3>)~(Uzpf}oWE}xxnJKN>U!tJ z(*542|NRGo4gMa~4WmfT0|gjiE0Xho-^2QPzb}LPLj7P0$$xMH)6zrkA7d{@=hbOU9YIHy9#&P7{2KS@?27e#+HylCkMYR~?eq{d<_m6Ne*yQ(9H~Bqw3dwu4 z7|D5bAgpi9L+)?%dsyE%8|ugE!~SEQKPNRehPrtV#?q!LByUrHSl={{S)%+t@*a1M zPbB@HaQ`-`r82Cyj0T@9O5C34_eIiE?$7iR{6w>eMuB4vESZZxT=5BS(`DbcE z{mdlNudO`PZT_Crn@f;$n|C4KadXnI-Tl~a-xqwg0wd|!LCi|etqkkWjUe}*UlQu) zyOVkjnWLi}Ilm=8tZ#A6`7NF=&5^Q1znAkbtp~&U%T<_>Ufv(pgCAeDgq9 z@2*2~yC*R%y;T(Iw|c_Lzx0^%#_7S9i&vvBGJ9)Sl-Jd$^ehoN; z^m*6$F%BR(y-9wr^CN11ztz&tCCK@m-N^l&GsyY(Ja2^K$h`0SKKt)`o)~A5d%GGj zCw<^~V)T4{{4K^DP9o<%EKeK_>;2A;rG9^(Nczb8o#=k_|Je7l=$gKtEXEA_J)9fx z`&b|7K+X>w!btkG3j2`ynAOr}DQeDtHi(=XEW}})z?}5C=dZ>QB=3uSjM3kNdtbWF zU~j00e4n|7oEJ-9IiK9GywADerD1*8``rK9HSho0`)Qm(`ixYD{gFR{-}wFHelr#7 zZ)-4?zIDy|cTGs2?_B>Xje0)n(K#gl`xd0%_s(a1cU@TDJsJGL`F~13%p-YYo`-W| zuF3n+-{UXorylH+elEcbCf|R2aabQ84eP%+kG{XSChu3jrvV2rl77oeY(~!gb`Z&( T@Xw2L6Z^4K`n?uobe;JZkG~-u literal 0 HcmV?d00001 From db7053f282d8b8ba45853366d5673eac3347f4fd Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Mon, 11 Jan 2021 12:30:22 +0100 Subject: [PATCH 08/10] Test code adaptation --- .../java/org/gcube/application/geoportal/service/TestModel.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/org/gcube/application/geoportal/service/TestModel.java b/src/test/java/org/gcube/application/geoportal/service/TestModel.java index 3bfc3c5..205ac09 100644 --- a/src/test/java/org/gcube/application/geoportal/service/TestModel.java +++ b/src/test/java/org/gcube/application/geoportal/service/TestModel.java @@ -86,7 +86,7 @@ public class TestModel { // Relazione scavo RelazioneScavo relScavo=new RelazioneScavo(); - relScavo.setAbstractSection("simple abstract section"); + relScavo.setAbstractIta("simple abstract section"); relScavo.setResponsabili(concessione.getAuthors()); concessione.setRelazioneScavo(relScavo); From 08918c93ba0a9e69c3397c1964a9f4314c54e5fd Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Thu, 14 Jan 2021 12:58:28 +0100 Subject: [PATCH 09/10] Fixed shp extension --- .../gcube/application/geoportal/service/engine/SDIManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java index fc86bdf..54e3526 100644 --- a/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java +++ b/src/main/java/org/gcube/application/geoportal/service/engine/SDIManager.java @@ -166,7 +166,7 @@ public class SDIManager { log.debug("Publishing remote folder "+remoteFolder); - URL directoryPath=new URL("file:"+remoteFolder+"/"+completeFileName); + URL directoryPath=new URL("file:"+remoteFolder+"/"+filename+".shp"); //TODO Evaluate SRS From 1452c3f0a83e2bfe8ab64823318324cb70d81e76 Mon Sep 17 00:00:00 2001 From: Fabio Sinibaldi Date: Thu, 14 Jan 2021 17:00:36 +0100 Subject: [PATCH 10/10] Releasing 1.0.4 --- CHANGELOG.md | 2 +- pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5eaf74..f39b456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm # Changelog for org.gcube.application.geoportal-service -## [v1.0.4-SNAPSHOT] 2020-11-11 +## [v1.0.4] 2020-11-11 Mongo integration with Concessione Project interface TempFile management diff --git a/pom.xml b/pom.xml index f18bc81..29c5d3f 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.gcube.application geoportal-service - 1.0.4-SNAPSHOT + 1.0.4 Geoportal Service war