package org.gcube.application.cms.sdi.plugins; import com.vdurmont.semver4j.Semver; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.gcube.application.cms.plugins.IndexerPluginInterface; import org.gcube.application.cms.plugins.faults.IndexingException; import org.gcube.application.cms.plugins.faults.InitializationException; import org.gcube.application.cms.plugins.faults.InvalidPluginRequestException; import org.gcube.application.cms.plugins.faults.InvalidProfileException; import org.gcube.application.cms.plugins.reports.IndexDocumentReport; import org.gcube.application.cms.plugins.reports.InitializationReport; import org.gcube.application.cms.plugins.reports.Report; import org.gcube.application.cms.plugins.requests.BaseRequest; import org.gcube.application.cms.plugins.requests.IndexDocumentRequest; import org.gcube.application.cms.sdi.engine.DBConstants; import org.gcube.application.cms.sdi.engine.PostgisIndexer; import org.gcube.application.cms.sdi.engine.PostgisTable; import org.gcube.application.cms.sdi.engine.bboxes.BBOXByCoordinatePaths; import org.gcube.application.cms.sdi.engine.bboxes.BBOXEvaluator; import org.gcube.application.cms.sdi.engine.bboxes.BBOXPathScanner; import org.gcube.application.cms.sdi.faults.SDIInteractionException; import org.gcube.application.cms.serialization.Serialization; import org.gcube.application.geoportal.common.model.JSONPathWrapper; import org.gcube.application.geoportal.common.model.configuration.Index; import org.gcube.application.geoportal.common.model.document.Project; import org.gcube.application.geoportal.common.model.document.filesets.sdi.GCubeSDILayer; import org.gcube.application.geoportal.common.model.document.identification.IdentificationReference; import org.gcube.application.geoportal.common.model.document.identification.SpatialReference; import org.gcube.application.geoportal.common.model.plugins.IndexerPluginDescriptor; import org.gcube.application.geoportal.common.model.plugins.PluginDescriptor; import org.gcube.application.geoportal.common.model.rest.ConfigurationException; import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor; import org.geojson.Crs; import org.geojson.GeoJsonObject; import org.geojson.LngLatAlt; import org.geojson.Point; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @Slf4j public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPluginInterface { @Data private static class MappingObject{ private String name; private String type; private String path; public void validate () throws InvalidProfileException { if(name==null) throw new InvalidProfileException("Invalid mapping "+this+" : name is null"); if(type==null) throw new InvalidProfileException("Invalid mapping "+this+" : type is null"); if(path==null) throw new InvalidProfileException("Invalid mapping "+this+" : path is null"); } } static final PluginDescriptor DESCRIPTOR=new PluginDescriptor("SDI-Indexer-Plugin", IndexerPluginDescriptor.INDEXER); static final ArrayList BBOX_EVALUATORS=new ArrayList<>(); static { DESCRIPTOR.setDescription("SDI Indexer. " + "Manage Centroids layers."); DESCRIPTOR.setVersion(new Semver("1.0.0")); BBOX_EVALUATORS.add(new BBOXPathScanner()); BBOX_EVALUATORS.add(new BBOXByCoordinatePaths()); } @Override public PluginDescriptor getDescriptor() { return DESCRIPTOR; } @Override public InitializationReport initInContext() throws InitializationException { InitializationReport report = new InitializationReport(); report.setStatus(Report.Status.OK); return report; } /** * Expected parameters : * - indexName (unique) * - workspace * - centroidRecord (OPT) * * @param request * @return */ @Override public IndexDocumentReport index(IndexDocumentRequest request) throws InvalidPluginRequestException { log.info("Indexer {} : Serving Index Request {} ",this.getDescriptor().getId(),request); Project project =request.getDocument(); UseCaseDescriptor useCaseDescriptor = request.getUseCaseDescriptor(); Document requestArguments=request.getCallParameters(); IndexDocumentReport report= new IndexDocumentReport(request); try{ // ********* INIT INDEX // TODO CACHE PostgisIndexer indexer = getIndexer(useCaseDescriptor,requestArguments); Document profileConfiguration =getConfigurationFromProfile(useCaseDescriptor).getConfiguration(); log.debug("UseCaseDescriptor Configuration is {} ",profileConfiguration); // ************* PREPARE RECORD JSONPathWrapper documentNavigator=new JSONPathWrapper(Serialization.write(project)); Document centroidDoc = new Document(); if(requestArguments.containsKey("centroidRecord")) centroidDoc.putAll(requestArguments.get("centroidRecords",Document.class)); // DEFAULT VALUES centroidDoc.put(DBConstants.Defaults.PROJECT_ID, project.getId()); centroidDoc.put(DBConstants.Defaults.DISPLAYED,true); // ********************** EVALAUTE POSITION log.debug("indexing UseCaseDescriptor {} : Evaluating Centroid... ", useCaseDescriptor.getId()); SpatialReference reference =null; List refs=project.getIdentificationReferenceByType(SpatialReference.SPATIAL_REFERENCE_TYPE); if(!refs.isEmpty()){ // Use existing Reference reference = Serialization.convert(refs.get(0), SpatialReference.class); log.debug("Using already defined spatial reference " + reference); GeoJsonObject object = Serialization.convert(reference.getGeoJson(), GeoJsonObject.class); GCubeSDILayer.BBOX bbox = GCubeSDILayer.BBOX.fromGeoJSON(object.getBbox()); log.info("Found declared BBOX {} ", bbox); Double pointX = (bbox.getMaxX() + bbox.getMinX())/2; Double pointY = (bbox.getMaxY() + bbox.getMinY())/2; String wkt = String.format("POINT (%1$f %2$f) ", pointX, pointY); centroidDoc.put("geom", wkt); } else{ // unable to use current Spatial reference, try evaluating it log.debug("UseCaseDescriptor {} : Getting evaluation paths from useCaseDescriptor.. ", useCaseDescriptor.getId()); // for each configuration option try until found GCubeSDILayer.BBOX toSet = null; for(BBOXEvaluator evaluator : BBOX_EVALUATORS){ log.trace("UCD {}, Project {}. Evaluating BBOX with {}",useCaseDescriptor.getId(),project.getId(),evaluator); try{ if(evaluator.isConfigured(profileConfiguration)){ toSet=evaluator.evaluate(profileConfiguration,useCaseDescriptor,documentNavigator); if(toSet!=null) { log.info("UCD {}, Project {}. Evaluated BBOX {} with method {}", useCaseDescriptor.getId(),project.getId(),toSet,evaluator); break; } } }catch (Throwable t){ log.warn("UCD {}, Project {}. Exception with {}", useCaseDescriptor.getId(),project.getId(),evaluator,t); } } if(toSet== null) throw new IndexingException("No BBOX has been evaluated from project"); Double pointX=(toSet.getMaxX()+toSet.getMinX())/2; Double pointY = (toSet.getMaxY()+toSet.getMinY())/2; log.info("Evaluated BBOX {} ",toSet); String wkt = String .format("POINT (%1$f %2$f) ", pointX, pointY); //TODO support altitude Double pointZ= 0d; centroidDoc.put("geom",wkt); Point point = new Point(); point.setCoordinates(new LngLatAlt(pointX,pointY,pointZ)); point.setBbox(toSet.asGeoJSONArray()); //TODO Manage CRS point.setCrs(new Crs()); reference = new SpatialReference(Serialization.asDocument(point)); log.info("UCD {} project {}, Setting Spatial Reference {} ",useCaseDescriptor.getId(),project.getId(),Serialization.write(reference)); report.addIdentificationReference(reference); } //*********** Additional Values from useCaseDescriptor log.info("Setting additional values to centroid from mappings .."); for(MappingObject m : getMappings(useCaseDescriptor)){ List foundValues = documentNavigator.getByPath(m.getPath()); Object toSetValue=null; if(!foundValues.isEmpty()) { // NB CSV for multiple values StringBuilder b=new StringBuilder(); foundValues.forEach(o-> { // Parser returns list of list if (o instanceof Collection) ((Collection) o).forEach(v ->b.append(v + ",")); else b.append(o+","); }); b.deleteCharAt(b.length()-1); toSetValue = b.toString(); } log.trace("Setting {} = {} in centroid doc ",m.getName(),toSetValue); centroidDoc.put(m.getName(),toSetValue); } log.info("Inserting Centroid {} into {} ",Serialization.write(centroidDoc.toJson()),indexer); indexer.insert(centroidDoc); // Support to HIDE AND DISPLAY as requested by invoker if(requestArguments.containsKey("_toHideIds")){ List ids = Serialization.convert(requestArguments.get("_toHideIds"),List.class); log.info("Requested to hide centroids {} ",ids); indexer.updateIsVisible(false,ids); } if(requestArguments.containsKey("_toDisplayIds")){ List ids = Serialization.convert(requestArguments.get("_toDisplayIds"),List.class); log.info("Requested to hide centroids {} ",ids); indexer.updateIsVisible(true,ids); } report.setStatus(Report.Status.OK); }catch (SDIInteractionException e){ log.error("Unable to index "+request,e); report.setStatus(Report.Status.ERROR); report.putMessage(e.getMessage()); }catch (Throwable t){ log.error("Unable to index "+request,t); report.setStatus(Report.Status.ERROR); report.putMessage(t.getMessage()); }finally{ return report; } } @Override public IndexDocumentReport deindex(IndexDocumentRequest request) throws InvalidPluginRequestException { log.info("Indexer {} : Serving Index Request {} ",this.getDescriptor().getId(),request); IndexDocumentReport report= new IndexDocumentReport(request); try{ PostgisIndexer indexer = getIndexer(request.getUseCaseDescriptor(),request.getCallParameters()); indexer.removeByFieldValue(Fields.PROJECT_ID,request.getDocument().getId()); }catch (SDIInteractionException e){ log.error("Unable to index "+request,e); report.setStatus(Report.Status.ERROR); report.putMessage(e.getMessage()); }catch (Throwable t){ log.error("Unable to index "+request,t); report.setStatus(Report.Status.ERROR); report.putMessage(t.getMessage()); }finally{ return report; } } /** * Expected parameters : * workspace * indexName * * @param request * @return * @throws ConfigurationException */ @Override public Index getIndex(BaseRequest request) throws ConfigurationException { try { return getIndexer(request.getUseCaseDescriptor(), request.getCallParameters()).getIndexConfiguration(); }catch(Throwable t ){ throw new ConfigurationException("Unable to get Postgis index for ucd "+request.getUseCaseDescriptor().getId()+" in "+ request.getContext(),t); } } // Inits index // TODO CACHE private PostgisIndexer getIndexer(UseCaseDescriptor ucd,Document params) throws ConfigurationException, SQLException, InvalidProfileException, SDIInteractionException { PostgisIndexer indexer = new PostgisIndexer(sdiCache.getObject(), ucd, postgisCache.getObject()); List mappingObjects = getMappings(ucd); List fields = getFields(mappingObjects); indexer.initIndex(params.getString("indexName"), fields, params.getString("workspace"), params.getString("indexName")); return indexer; } private static class Fields{ public static final PostgisTable.Field PROJECT_ID= new PostgisTable.Field(DBConstants.Defaults.PROJECT_ID, PostgisTable.FieldType.TEXT); public static final PostgisTable.Field GEOM= new PostgisTable.Field(DBConstants.Defaults.DEFAULT_GEOMETRY_COLUMN_NAME, PostgisTable.FieldType.GEOMETRY); public static final PostgisTable.Field DISPLAY=new PostgisTable.Field(DBConstants.Defaults.DISPLAYED,PostgisTable.FieldType.BOOLEAN); } private List getFields(List mappings){ List fields = new ArrayList<>(); // TODO From UseCaseDescriptor fields.add(Fields.GEOM); fields.add(Fields.PROJECT_ID); fields.add(Fields.DISPLAY); mappings.forEach(m -> { fields.add(new PostgisTable.Field(m.getName(), PostgisTable.FieldType.valueOf(m.getType()))); }); return fields; } private List getMappings(UseCaseDescriptor ucd) throws InvalidProfileException { log.debug("UseCaseDescriptor {} : Evaluating Index schema.. ", ucd.getId()); Document profileConfiguration = getConfigurationFromProfile(ucd).getConfiguration(); List mappingObjs= profileConfiguration.get("explicitFieldMapping",List.class); log.trace("Loading mappings from useCaseDescriptor.. "); List mappingObjects= new ArrayList<>(); if(mappingObjs!=null){ for (Object mappingObj : mappingObjs) { log.trace("Mapping is {} ",mappingObj); MappingObject m = Serialization.convert(mappingObj,MappingObject.class); m.validate(); mappingObjects.add(m); } } return mappingObjects; } }