gcube-cms-suite/sdi-plugins/src/main/java/org/gcube/application/cms/sdi/plugins/SDIIndexerPlugin.java

453 lines
17 KiB
Java

package org.gcube.application.cms.sdi.plugins;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.implementations.IndexConstants;
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.Constants;
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.sdi.model.ApplyRegex;
import org.gcube.application.cms.sdi.model.ApplyRegex.REGEX_TYPES;
import org.gcube.application.cms.sdi.model.MappingObject;
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.gcube.portlets.user.uriresolvermanager.UriResolverManager;
import org.gcube.portlets.user.uriresolvermanager.resolvers.query.GeoportalResolverQueryStringBuilder;
import org.gcube.portlets.user.uriresolvermanager.resolvers.query.GeoportalResolverQueryStringBuilder.RESOLVE_AS;
import org.geojson.Crs;
import org.geojson.GeoJsonObject;
import org.geojson.LngLatAlt;
import org.geojson.Point;
import com.vdurmont.semver4j.Semver;
import lombok.extern.slf4j.Slf4j;
/**
* The Class SDIIndexerPlugin.
*
* @author created by Fabio Sinibaldi
* @author new architect and maintainer - Francesco Mangiacrapa at ISTI-CNR
* francesco.mangiacrapa@isti.cnr.it
*
* Apr 28, 2023
*/
@Slf4j
public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPluginInterface {
static final PluginDescriptor DESCRIPTOR = new PluginDescriptor(Constants.INDEXER_PLUGIN_ID,
IndexerPluginDescriptor.INDEXER);
static final ArrayList<BBOXEvaluator> 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());
}
/**
* Gets the descriptor.
*
* @return the descriptor
*/
@Override
public PluginDescriptor getDescriptor() {
return DESCRIPTOR;
}
/**
* Inits the in context.
*
* @return the initialization report
* @throws InitializationException the initialization exception
*/
@Override
public InitializationReport initInContext() throws InitializationException {
InitializationReport report = new InitializationReport();
report.setStatus(Report.Status.OK);
return report;
}
/**
* Expected parameters : - indexName (unique) - workspace - flagInternalIndex as
* Boolean; boolean - centroidRecord (OPT).
*
* @param request the request
* @return the index document report
* @throws InvalidPluginRequestException the invalid plugin request exception
*/
@Override
public IndexDocumentReport index(IndexDocumentRequest request) throws InvalidPluginRequestException {
log.info("Serving Indexer {} : request CallParameters {}, request Context {}: ", this.getDescriptor().getId(),
request.getCallParameters(), request.getContext());
log.debug("Indexer {} : Serving Request {} ", this.getDescriptor().getId(), request);
Project project = request.getDocument();
UseCaseDescriptor useCaseDescriptor = request.getUseCaseDescriptor();
Document requestArguments = request.getCallParameters();
log.debug("requestArguments is {} ", requestArguments);
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);
// *********** Added by Francesco. Creating Gis Viewer Link as public or private
Boolean isInternalIndex = null;
try {
isInternalIndex = requestArguments.getBoolean(IndexConstants.INDEX_PARAMETER_FLAGINTERNALINDEX);
log.debug(IndexConstants.INDEX_PARAMETER_FLAGINTERNALINDEX + " read as {} ", isInternalIndex);
} catch (Exception e) {
// TODO: handle exception
}
log.info(IndexConstants.INDEX_PARAMETER_FLAGINTERNALINDEX + " is {} ", isInternalIndex);
if (isInternalIndex != null) {
try {
log.debug("Trying to generate Geoportal Gis Link...");
// Contacting the Geoportal-Resolver via UriResolverManager
UriResolverManager uriResolverManager = new UriResolverManager("GEO");
GeoportalResolverQueryStringBuilder builder = new GeoportalResolverQueryStringBuilder(
project.getProfileID(), project.getId());
builder.scope(request.getContext().getId());
if (isInternalIndex) {
builder.resolverAs(RESOLVE_AS.PRIVATE);
} else {
builder.resolverAs(RESOLVE_AS.PUBLIC);
}
Map<String, String> params = builder.buildQueryParameters();
String shortLink = uriResolverManager.getLink(params, true);
log.info("Geoportal GisViewer link is {} ", shortLink);
centroidDoc.put(DBConstants.Defaults.GEOVIEWER_LINK_FIELD, shortLink);
} catch (Exception e) {
log.error("Error on creating the Geoportal GisViewer link for project id {}", project.getId(), e);
}
}
// ********************** Updated by Francesco, see #25056, Calculating Spatial
// Reference as Centroid Object
// **** EVALUATE POSITION
log.debug("indexing UseCaseDescriptor {} : Evaluating Centroid... ", useCaseDescriptor.getId());
SpatialReference reference = null;
List<IdentificationReference> refs = project
.getIdentificationReferenceByType(SpatialReference.SPATIAL_REFERENCE_TYPE);
GCubeSDILayer.BBOX bbox = null;
if (!refs.isEmpty()) {
// Use existing Reference
try {
reference = Serialization.convert(refs.get(0), SpatialReference.class);
log.debug("Using already defined spatial reference " + reference);
GeoJsonObject object = Serialization.convert(reference.getGeoJson(), GeoJsonObject.class);
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);
// No overriding SpatialReference in the IndexDocumentReport, using the existing
// one.
} catch (Exception e) {
log.info("The defined spatial reference is wrong or empty: " + reference);
}
}
// Calculating and overriding the SpatialReference (the Centroid JSON) in the
// IndexDocumentReport.
if (bbox == null) {
log.info("No bbox defined in the current spatial reference going to calculate it...");
// 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<Object> 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();
}
// Added by Francesco M. #26322
toSetValue = toSetValueByApplyRegex(m, toSetValue);
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<String> ids = Serialization.convert(requestArguments.get("_toHideIds"), List.class);
log.info("Requested to hide centroids {} ", ids);
indexer.updateIsVisible(false, ids);
}
if (requestArguments.containsKey("_toDisplayIds")) {
List<String> ids = Serialization.convert(requestArguments.get("_toDisplayIds"), List.class);
log.info("Requested to display 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;
}
}
// Added by Francesco M. #26322
public static Object toSetValueByApplyRegex(MappingObject m, Object toSetValue) {
ApplyRegex applyRegex = m.getApplyRegex();
if (applyRegex != null) {
try {
String valueString = toSetValue==null?"":toSetValue.toString();
String type = applyRegex.getType();
REGEX_TYPES theRegexType = ApplyRegex.REGEX_TYPES.valueOf(type);
Pattern p = Pattern.compile(applyRegex.getRegex());
Matcher matcher = p.matcher(valueString);
switch (theRegexType) {
case find:
while (matcher.find()) {
toSetValue += matcher.group();
}
break;
case replaceFirst:
toSetValue = matcher.replaceFirst(applyRegex.getReplacement());
break;
case replaceAll:
toSetValue = matcher.replaceFirst(applyRegex.getReplacement());
break;
default:
break;
}
} catch (Exception e) {
log.warn("Error on applying replaceAll by regex {} on field {} = {} in centroid doc ", applyRegex,
m.getName(), toSetValue, e);
}
}
return toSetValue;
}
/**
* Gets the mappings.
*
* @param useCaseDescriptor the use case descriptor
* @return the mappings
* @throws InvalidProfileException the invalid profile exception
*/
private List<MappingObject> getMappings(UseCaseDescriptor useCaseDescriptor) throws InvalidProfileException {
return MappingObject.getMappingsFromUCD(useCaseDescriptor, getDescriptor().getId());
}
/**
* Deindex.
*
* @param request the request
* @return the index document report
* @throws InvalidPluginRequestException the invalid plugin request exception
*/
@Override
public IndexDocumentReport deindex(IndexDocumentRequest request) throws InvalidPluginRequestException {
log.info("Serving DeIndexer {} : request CallParameters {}, request Context {}: ", this.getDescriptor().getId(),
request.getCallParameters(), request.getContext());
log.debug("DeIndexer {} : Serving Request {} ", this.getDescriptor().getId(), request);
IndexDocumentReport report = new IndexDocumentReport(request);
try {
PostgisIndexer indexer = getIndexer(request.getUseCaseDescriptor(), request.getCallParameters());
indexer.removeByFieldValue(PostgisIndexer.StandardFields.PROJECT_ID, request.getDocument().getId());
// ***** Added by Francesco, see #25056
// Replacing the "old" centroid if any. Creating an empty SpatialReference
SpatialReference reference = new SpatialReference(new Document());
log.info("DeIndexer project {}, Setting Spatial Reference empty {} ", request.getDocument().getId(),
Serialization.write(reference));
report.addIdentificationReference(reference);
} 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 the request
* @return the index
* @throws ConfigurationException the configuration exception
*/
@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
/**
* Gets the indexer.
*
* @param ucd the ucd
* @param params the params
* @return the indexer
* @throws ConfigurationException the configuration exception
* @throws SQLException the SQL exception
* @throws InvalidProfileException the invalid profile exception
* @throws SDIInteractionException the SDI interaction exception
*/
// TODO CACHE
private PostgisIndexer getIndexer(UseCaseDescriptor ucd, Document params)
throws ConfigurationException, SQLException, InvalidProfileException, SDIInteractionException {
PostgisIndexer indexer = new PostgisIndexer(sdiCache.getObject(), ucd, postgisCache.getObject());
List<MappingObject> mappingObjects = getMappings(ucd);
List<PostgisTable.Field> fields = PostgisTable.Field.fromMappings(mappingObjects);
indexer.initIndex(params.getString(IndexConstants.INDEX_PARAMETER_INDEXNAME), fields,
params.getString(IndexConstants.INDEX_PARAMETER_WORKSPACE),
params.getString(IndexConstants.INDEX_PARAMETER_INDEXNAME));
return indexer;
}
}