|
|
@ -33,6 +33,9 @@ import org.gcube.application.geoportal.common.model.plugins.IndexerPluginDescrip
|
|
|
|
import org.gcube.application.geoportal.common.model.plugins.PluginDescriptor;
|
|
|
|
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.rest.ConfigurationException;
|
|
|
|
import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor;
|
|
|
|
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.Crs;
|
|
|
|
import org.geojson.GeoJsonObject;
|
|
|
|
import org.geojson.GeoJsonObject;
|
|
|
|
import org.geojson.LngLatAlt;
|
|
|
|
import org.geojson.LngLatAlt;
|
|
|
@ -42,29 +45,23 @@ import java.sql.SQLException;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.Collection;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.List;
|
|
|
|
|
|
|
|
import java.util.Map;
|
|
|
|
|
|
|
|
|
|
|
|
@Slf4j
|
|
|
|
@Slf4j
|
|
|
|
public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPluginInterface {
|
|
|
|
public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPluginInterface {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static final PluginDescriptor DESCRIPTOR = new PluginDescriptor(Constants.INDEXER_PLUGIN_ID,
|
|
|
|
static final PluginDescriptor DESCRIPTOR = new PluginDescriptor(Constants.INDEXER_PLUGIN_ID,
|
|
|
|
IndexerPluginDescriptor.INDEXER);
|
|
|
|
IndexerPluginDescriptor.INDEXER);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static final ArrayList<BBOXEvaluator> BBOX_EVALUATORS = new ArrayList<>();
|
|
|
|
static final ArrayList<BBOXEvaluator> BBOX_EVALUATORS = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
|
|
static {
|
|
|
|
static {
|
|
|
|
DESCRIPTOR.setDescription("SDI Indexer. " +
|
|
|
|
DESCRIPTOR.setDescription("SDI Indexer. " + "Manage Centroids layers.");
|
|
|
|
"Manage Centroids layers.");
|
|
|
|
|
|
|
|
DESCRIPTOR.setVersion(new Semver("1.0.0"));
|
|
|
|
DESCRIPTOR.setVersion(new Semver("1.0.0"));
|
|
|
|
|
|
|
|
|
|
|
|
BBOX_EVALUATORS.add(new BBOXPathScanner());
|
|
|
|
BBOX_EVALUATORS.add(new BBOXPathScanner());
|
|
|
|
BBOX_EVALUATORS.add(new BBOXByCoordinatePaths());
|
|
|
|
BBOX_EVALUATORS.add(new BBOXByCoordinatePaths());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
@ -72,7 +69,6 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
return DESCRIPTOR;
|
|
|
|
return DESCRIPTOR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
@Override
|
|
|
|
public InitializationReport initInContext() throws InitializationException {
|
|
|
|
public InitializationReport initInContext() throws InitializationException {
|
|
|
|
InitializationReport report = new InitializationReport();
|
|
|
|
InitializationReport report = new InitializationReport();
|
|
|
@ -81,10 +77,8 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Expected parameters :
|
|
|
|
* Expected parameters : - indexName (unique) - workspace - flagInternalIndex:
|
|
|
|
* - indexName (unique)
|
|
|
|
* boolean - centroidRecord (OPT)
|
|
|
|
* - workspace
|
|
|
|
|
|
|
|
* - centroidRecord (OPT)
|
|
|
|
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param request
|
|
|
|
* @param request
|
|
|
|
* @return
|
|
|
|
* @return
|
|
|
@ -101,8 +95,6 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
|
|
|
|
|
|
|
|
IndexDocumentReport report = new IndexDocumentReport(request);
|
|
|
|
IndexDocumentReport report = new IndexDocumentReport(request);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
// ********* INIT INDEX
|
|
|
|
// ********* INIT INDEX
|
|
|
|
// TODO CACHE
|
|
|
|
// TODO CACHE
|
|
|
@ -111,10 +103,8 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
Document profileConfiguration = getConfigurationFromProfile(useCaseDescriptor).getConfiguration();
|
|
|
|
Document profileConfiguration = getConfigurationFromProfile(useCaseDescriptor).getConfiguration();
|
|
|
|
log.debug("UseCaseDescriptor Configuration is {} ", profileConfiguration);
|
|
|
|
log.debug("UseCaseDescriptor Configuration is {} ", profileConfiguration);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ************* PREPARE RECORD
|
|
|
|
// ************* PREPARE RECORD
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JSONPathWrapper documentNavigator = new JSONPathWrapper(Serialization.write(project));
|
|
|
|
JSONPathWrapper documentNavigator = new JSONPathWrapper(Serialization.write(project));
|
|
|
|
|
|
|
|
|
|
|
|
Document centroidDoc = new Document();
|
|
|
|
Document centroidDoc = new Document();
|
|
|
@ -124,12 +114,41 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
centroidDoc.put(DBConstants.Defaults.PROJECT_ID, project.getId());
|
|
|
|
centroidDoc.put(DBConstants.Defaults.PROJECT_ID, project.getId());
|
|
|
|
centroidDoc.put(DBConstants.Defaults.DISPLAYED, true);
|
|
|
|
centroidDoc.put(DBConstants.Defaults.DISPLAYED, true);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
boolean isInternalIndex = false;
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
isInternalIndex = requestArguments.getBoolean("flagInternalIndex");
|
|
|
|
|
|
|
|
log.debug("flagInternalIndex read as {} ", isInternalIndex);
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
|
|
|
// TODO: handle exception
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
log.info("flagInternalIndex is {} ", isInternalIndex);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
|
|
|
|
|
|
|
// TODO: handle exception
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ********************** EVALAUTE POSITION
|
|
|
|
// ********************** EVALAUTE POSITION
|
|
|
|
log.debug("indexing UseCaseDescriptor {} : Evaluating Centroid... ", useCaseDescriptor.getId());
|
|
|
|
log.debug("indexing UseCaseDescriptor {} : Evaluating Centroid... ", useCaseDescriptor.getId());
|
|
|
|
SpatialReference reference = null;
|
|
|
|
SpatialReference reference = null;
|
|
|
|
List<IdentificationReference> refs=project.getIdentificationReferenceByType(SpatialReference.SPATIAL_REFERENCE_TYPE);
|
|
|
|
List<IdentificationReference> refs = project
|
|
|
|
|
|
|
|
.getIdentificationReferenceByType(SpatialReference.SPATIAL_REFERENCE_TYPE);
|
|
|
|
if (!refs.isEmpty()) {
|
|
|
|
if (!refs.isEmpty()) {
|
|
|
|
|
|
|
|
|
|
|
|
// Use existing Reference
|
|
|
|
// Use existing Reference
|
|
|
@ -138,7 +157,6 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
|
|
|
|
|
|
|
|
log.debug("Using already defined spatial reference " + reference);
|
|
|
|
log.debug("Using already defined spatial reference " + reference);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GeoJsonObject object = Serialization.convert(reference.getGeoJson(), GeoJsonObject.class);
|
|
|
|
GeoJsonObject object = Serialization.convert(reference.getGeoJson(), GeoJsonObject.class);
|
|
|
|
|
|
|
|
|
|
|
|
GCubeSDILayer.BBOX bbox = GCubeSDILayer.BBOX.fromGeoJSON(object.getBbox());
|
|
|
|
GCubeSDILayer.BBOX bbox = GCubeSDILayer.BBOX.fromGeoJSON(object.getBbox());
|
|
|
@ -146,20 +164,20 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
log.info("Found declared BBOX {} ", bbox);
|
|
|
|
log.info("Found declared BBOX {} ", bbox);
|
|
|
|
Double pointX = (bbox.getMaxX() + bbox.getMinX()) / 2;
|
|
|
|
Double pointX = (bbox.getMaxX() + bbox.getMinX()) / 2;
|
|
|
|
Double pointY = (bbox.getMaxY() + bbox.getMinY()) / 2;
|
|
|
|
Double pointY = (bbox.getMaxY() + bbox.getMinY()) / 2;
|
|
|
|
String wkt = String.format("POINT (%1$f %2$f) ",
|
|
|
|
String wkt = String.format("POINT (%1$f %2$f) ", pointX, pointY);
|
|
|
|
pointX, pointY);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
centroidDoc.put("geom", wkt);
|
|
|
|
centroidDoc.put("geom", wkt);
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
// unable to use current Spatial reference, try evaluating it
|
|
|
|
// unable to use current Spatial reference, try evaluating it
|
|
|
|
log.debug("UseCaseDescriptor {} : Getting evaluation paths from useCaseDescriptor.. ", useCaseDescriptor.getId());
|
|
|
|
log.debug("UseCaseDescriptor {} : Getting evaluation paths from useCaseDescriptor.. ",
|
|
|
|
|
|
|
|
useCaseDescriptor.getId());
|
|
|
|
|
|
|
|
|
|
|
|
// for each configuration option try until found
|
|
|
|
// for each configuration option try until found
|
|
|
|
GCubeSDILayer.BBOX toSet = null;
|
|
|
|
GCubeSDILayer.BBOX toSet = null;
|
|
|
|
for (BBOXEvaluator evaluator : BBOX_EVALUATORS) {
|
|
|
|
for (BBOXEvaluator evaluator : BBOX_EVALUATORS) {
|
|
|
|
log.trace("UCD {}, Project {}. Evaluating BBOX with {}",useCaseDescriptor.getId(),project.getId(),evaluator);
|
|
|
|
log.trace("UCD {}, Project {}. Evaluating BBOX with {}", useCaseDescriptor.getId(), project.getId(),
|
|
|
|
|
|
|
|
evaluator);
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
if (evaluator.isConfigured(profileConfiguration)) {
|
|
|
|
if (evaluator.isConfigured(profileConfiguration)) {
|
|
|
|
toSet = evaluator.evaluate(profileConfiguration, useCaseDescriptor, documentNavigator);
|
|
|
|
toSet = evaluator.evaluate(profileConfiguration, useCaseDescriptor, documentNavigator);
|
|
|
@ -170,8 +188,8 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (Throwable t) {
|
|
|
|
} catch (Throwable t) {
|
|
|
|
log.warn("UCD {}, Project {}. Exception with {}",
|
|
|
|
log.warn("UCD {}, Project {}. Exception with {}", useCaseDescriptor.getId(), project.getId(),
|
|
|
|
useCaseDescriptor.getId(),project.getId(),evaluator,t);
|
|
|
|
evaluator, t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (toSet == null)
|
|
|
|
if (toSet == null)
|
|
|
@ -180,12 +198,10 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
Double pointX = (toSet.getMaxX() + toSet.getMinX()) / 2;
|
|
|
|
Double pointX = (toSet.getMaxX() + toSet.getMinX()) / 2;
|
|
|
|
Double pointY = (toSet.getMaxY() + toSet.getMinY()) / 2;
|
|
|
|
Double pointY = (toSet.getMaxY() + toSet.getMinY()) / 2;
|
|
|
|
log.info("Evaluated BBOX {} ", toSet);
|
|
|
|
log.info("Evaluated BBOX {} ", toSet);
|
|
|
|
String wkt = String .format("POINT (%1$f %2$f) ",
|
|
|
|
String wkt = String.format("POINT (%1$f %2$f) ", pointX, pointY);
|
|
|
|
pointX, pointY);
|
|
|
|
|
|
|
|
// TODO support altitude
|
|
|
|
// TODO support altitude
|
|
|
|
Double pointZ = 0d;
|
|
|
|
Double pointZ = 0d;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
centroidDoc.put("geom", wkt);
|
|
|
|
centroidDoc.put("geom", wkt);
|
|
|
|
|
|
|
|
|
|
|
|
Point point = new Point();
|
|
|
|
Point point = new Point();
|
|
|
@ -195,12 +211,11 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
// TODO Manage CRS
|
|
|
|
// TODO Manage CRS
|
|
|
|
point.setCrs(new Crs());
|
|
|
|
point.setCrs(new Crs());
|
|
|
|
reference = new SpatialReference(Serialization.asDocument(point));
|
|
|
|
reference = new SpatialReference(Serialization.asDocument(point));
|
|
|
|
log.info("UCD {} project {}, Setting Spatial Reference {} ",useCaseDescriptor.getId(),project.getId(),Serialization.write(reference));
|
|
|
|
log.info("UCD {} project {}, Setting Spatial Reference {} ", useCaseDescriptor.getId(), project.getId(),
|
|
|
|
|
|
|
|
Serialization.write(reference));
|
|
|
|
report.addIdentificationReference(reference);
|
|
|
|
report.addIdentificationReference(reference);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// *********** Additional Values from useCaseDescriptor
|
|
|
|
// *********** Additional Values from useCaseDescriptor
|
|
|
|
|
|
|
|
|
|
|
|
log.info("Setting additional values to centroid from mappings ..");
|
|
|
|
log.info("Setting additional values to centroid from mappings ..");
|
|
|
@ -212,8 +227,10 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
StringBuilder b = new StringBuilder();
|
|
|
|
foundValues.forEach(o -> {
|
|
|
|
foundValues.forEach(o -> {
|
|
|
|
// Parser returns list of list
|
|
|
|
// Parser returns list of list
|
|
|
|
if (o instanceof Collection) ((Collection<?>) o).forEach(v ->b.append(v + ","));
|
|
|
|
if (o instanceof Collection)
|
|
|
|
else b.append(o+",");
|
|
|
|
((Collection<?>) o).forEach(v -> b.append(v + ","));
|
|
|
|
|
|
|
|
else
|
|
|
|
|
|
|
|
b.append(o + ",");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
b.deleteCharAt(b.length() - 1);
|
|
|
|
b.deleteCharAt(b.length() - 1);
|
|
|
|
toSetValue = b.toString();
|
|
|
|
toSetValue = b.toString();
|
|
|
@ -240,8 +257,6 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
indexer.updateIsVisible(true, ids);
|
|
|
|
indexer.updateIsVisible(true, ids);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
report.setStatus(Report.Status.OK);
|
|
|
|
report.setStatus(Report.Status.OK);
|
|
|
|
} catch (SDIInteractionException e) {
|
|
|
|
} catch (SDIInteractionException e) {
|
|
|
|
log.error("Unable to index " + request, e);
|
|
|
|
log.error("Unable to index " + request, e);
|
|
|
@ -281,9 +296,7 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
/**
|
|
|
|
* Expected parameters :
|
|
|
|
* Expected parameters : workspace indexName
|
|
|
|
* workspace
|
|
|
|
|
|
|
|
* indexName
|
|
|
|
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param request
|
|
|
|
* @param request
|
|
|
|
* @return
|
|
|
|
* @return
|
|
|
@ -294,31 +307,23 @@ public class SDIIndexerPlugin extends SDIAbstractPlugin implements IndexerPlugin
|
|
|
|
try {
|
|
|
|
try {
|
|
|
|
return getIndexer(request.getUseCaseDescriptor(), request.getCallParameters()).getIndexConfiguration();
|
|
|
|
return getIndexer(request.getUseCaseDescriptor(), request.getCallParameters()).getIndexConfiguration();
|
|
|
|
} catch (Throwable t) {
|
|
|
|
} catch (Throwable t) {
|
|
|
|
throw new ConfigurationException("Unable to get Postgis index for ucd "+request.getUseCaseDescriptor().getId()+" in "+ request.getContext(),t);
|
|
|
|
throw new ConfigurationException("Unable to get Postgis index for ucd "
|
|
|
|
|
|
|
|
+ request.getUseCaseDescriptor().getId() + " in " + request.getContext(), t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Inits index
|
|
|
|
// Inits index
|
|
|
|
// TODO CACHE
|
|
|
|
// TODO CACHE
|
|
|
|
private PostgisIndexer getIndexer(UseCaseDescriptor ucd,Document params) throws ConfigurationException, SQLException, InvalidProfileException, SDIInteractionException {
|
|
|
|
private PostgisIndexer getIndexer(UseCaseDescriptor ucd, Document params)
|
|
|
|
|
|
|
|
throws ConfigurationException, SQLException, InvalidProfileException, SDIInteractionException {
|
|
|
|
PostgisIndexer indexer = new PostgisIndexer(sdiCache.getObject(), ucd, postgisCache.getObject());
|
|
|
|
PostgisIndexer indexer = new PostgisIndexer(sdiCache.getObject(), ucd, postgisCache.getObject());
|
|
|
|
|
|
|
|
|
|
|
|
List<MappingObject> mappingObjects = getMappings(ucd);
|
|
|
|
List<MappingObject> mappingObjects = getMappings(ucd);
|
|
|
|
List<PostgisTable.Field> fields = PostgisTable.Field.fromMappings(mappingObjects);
|
|
|
|
List<PostgisTable.Field> fields = PostgisTable.Field.fromMappings(mappingObjects);
|
|
|
|
|
|
|
|
|
|
|
|
indexer.initIndex(params.getString("indexName"),
|
|
|
|
indexer.initIndex(params.getString("indexName"), fields, params.getString("workspace"),
|
|
|
|
fields,
|
|
|
|
|
|
|
|
params.getString("workspace"),
|
|
|
|
|
|
|
|
params.getString("indexName"));
|
|
|
|
params.getString("indexName"));
|
|
|
|
return indexer;
|
|
|
|
return indexer;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|