package org.gcube.datatransfer.resolver.services; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.gcube.common.scope.api.ScopeProvider; import org.gcube.common.scope.impl.ScopeBean; import org.gcube.datatransfer.resolver.ConstantsResolver; import org.gcube.datatransfer.resolver.caches.LoadingMapOfScopeCache; import org.gcube.datatransfer.resolver.geoportal.GeoportalCommonConstants; import org.gcube.datatransfer.resolver.geoportal.GeoportalDataViewerConfigProfile; import org.gcube.datatransfer.resolver.geoportal.GeoportalDataViewerConfigProfileReader; import org.gcube.datatransfer.resolver.geoportal.GeoportalRequest; import org.gcube.datatransfer.resolver.geoportal.TargetAppGeoportalCodes; import org.gcube.datatransfer.resolver.services.error.ExceptionManager; import org.gcube.datatransfer.resolver.util.Util; import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; /** * The GeoportalResolver is able to get/resolve a link to "Geoportal Viewer" or * "Geoportal Entry" Application. * * See more at * https://gcube.wiki.gcube-system.org/gcube/URI_Resolver#Geoportal_Resolver * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 23, 2023 */ @Path("/geo") public class GeoportalResolver { public static final String GEO_DE = "de"; // data-entry public static final String GEO = "geo"; // geoportal public static final String GEO_DV = "dv"; // data-viewer private static final String QP_RESOLVE_AS = "res"; private static final String PATH_PROJECT_ID = "project_id"; private static final String PATH_USECASE_ID = "usecase_id"; private static final String PATH_VRE_NAME = "vre_name"; private static final String PATH_TARGET_APP = "targetAppId"; private static final Logger LOG = LoggerFactory.getLogger(GeoportalResolver.class); private static String helpURI = "https://wiki.gcube-system.org/gcube/URI_Resolver#Geoportal_Resolver"; /** * The Enum RESOLVE_AS_PARAMETER. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 24, 2023 */ private static enum RESOLVE_AS { PUBLIC, PRIVATE } /** * Resolve geoportal. Resolve a Geoportal Link to "Data-Viewer" App * * @param req the req * @param vreName the vre name * @param ucdID the ucd ID * @param projectID the project ID * @param resolveAs the resolve as * @return the response * @throws WebApplicationException the web application exception */ @GET @Path("/{vre_name}/{usecase_id}/{project_id}") public Response resolveGeoportalNoAppDef(@Context HttpServletRequest req, @PathParam(PATH_VRE_NAME) String vreName, @PathParam(PATH_USECASE_ID) String ucdID, @PathParam(PATH_PROJECT_ID) String projectID, @QueryParam(QP_RESOLVE_AS) String resolveAs) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " resolveGeoportalNoAppDef - GET starts..."); TargetAppGeoportalCodes targetAppGeoportalCodes = checkTargetApplictionID(req, null); LOG.info("The target app is: " + targetAppGeoportalCodes); return genericResolveLink(req, targetAppGeoportalCodes, vreName, ucdID, projectID, resolveAs); } /** * Resolve geoportal. Resolve a Geoportal Link to "Data-Viewer" or "Data-Entry" * App * * @param req the req * @param targetAppId the target app id * @param vreName the vre name * @param ucdID the ucd ID * @param projectID the project ID * @param resolveAs the resolve as * @return the response * @throws WebApplicationException the web application exception */ @GET @Path("/{targetAppId}/{vre_name}/{usecase_id}/{project_id}") public Response resolveGeoportal(@Context HttpServletRequest req, @PathParam(PATH_TARGET_APP) String targetAppId, @PathParam(PATH_VRE_NAME) String vreName, @PathParam(PATH_USECASE_ID) String ucdID, @PathParam(PATH_PROJECT_ID) String projectID, @QueryParam(QP_RESOLVE_AS) String resolveAs) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " resolveGeoportal - GET starts..."); TargetAppGeoportalCodes targetAppGeoportalCodes = checkTargetApplictionID(req, targetAppId); LOG.info("The target app is: " + targetAppGeoportalCodes); return genericResolveLink(req, targetAppGeoportalCodes, vreName, ucdID, projectID, resolveAs); } /** * Resolve data viewer link. * * @param req the req * @param gcubeScope the gcube scope * @param itemId the item id * @param itemType the item type * @param resolveAs the resolve as * @return the response * @throws WebApplicationException the web application exception */ @GET @Path("/") public Response resolveDataViewerLink(@Context HttpServletRequest req, @QueryParam(GeoportalRequest.P_GCUBE_SCOPE) String gcubeScope, @QueryParam(GeoportalRequest.P_ITEM_ID) String itemId, @QueryParam(GeoportalRequest.P_ITEM_TYPE) String itemType, @QueryParam(QP_RESOLVE_AS) String resolveAs) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " resolveDataViewerLink - GET starts..."); TargetAppGeoportalCodes targetAppGeoportalCodes = checkTargetApplictionID(req, null); LOG.info("The target app is: " + targetAppGeoportalCodes); if (gcubeScope == null || gcubeScope.isEmpty()) { LOG.error("The query parameter '" + GeoportalRequest.P_GCUBE_SCOPE + "' not found or empty"); throw ExceptionManager.badRequestException(req, "Mandatory query parameter '" + GeoportalRequest.P_GCUBE_SCOPE + "' not found or empty", this.getClass(), helpURI); } String scope = gcubeScope.substring(gcubeScope.lastIndexOf("/") + 1, gcubeScope.length()); return genericResolveLink(req, targetAppGeoportalCodes, scope, itemType, itemId, resolveAs); } /** * Post geoportal. Create a Geoportal Link to "Data-Viewer" App * * @param req the req * @param jsonRequest the json request * @return the response * @throws WebApplicationException the web application exception */ @POST @Path("") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public Response postGeoportalNoAppDef(@Context HttpServletRequest req, GeoportalRequest jsonRequest) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " postGeoportalNoAppDef - POST starts..."); TargetAppGeoportalCodes targetAppGeoportalCodes = checkTargetApplictionID(req, null); LOG.info("The target app is: " + targetAppGeoportalCodes); String linkURL = genericCreateLink(req, jsonRequest, targetAppGeoportalCodes); return Response.ok(linkURL).header("Location", linkURL).build(); } /** * Post geoportal. Create a Geoportal Link to "Data-Viewer" or "Data-Entry" App * * @param req the req * @param targetAppId the target app id * @param jsonRequest the json request * @return the response * @throws WebApplicationException the web application exception */ @POST @Path("/{targetAppId}") @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.TEXT_PLAIN) public Response postGeoportal(@Context HttpServletRequest req, @PathParam(PATH_TARGET_APP) String targetAppId, GeoportalRequest jsonRequest) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " postGeoportal - POST starts..."); TargetAppGeoportalCodes targetAppGeoportalCodes = checkTargetApplictionID(req, targetAppId); LOG.info("The target app is: " + targetAppGeoportalCodes); String linkURL = genericCreateLink(req, jsonRequest, targetAppGeoportalCodes); return Response.ok(linkURL).header("Location", linkURL).build(); } /** * Check target appliction ID. * * @param req the req * @param targetAppId the target app id * @return the target app geoportal codes */ private TargetAppGeoportalCodes checkTargetApplictionID(@Context HttpServletRequest req, String targetAppId) { TargetAppGeoportalCodes targetAppGeoportalCodes = null; if (targetAppId == null) { targetAppGeoportalCodes = TargetAppGeoportalCodes.GEO_DV; LOG.warn("Target application parameter is null, using default: " + targetAppGeoportalCodes); } else { // IF the target application passed in the request. It must be proper. targetAppGeoportalCodes = TargetAppGeoportalCodes.valueOfId(targetAppId); if (targetAppGeoportalCodes == null) { LOG.error("Target application parameter is malformed"); List targetApps = Arrays.asList(TargetAppGeoportalCodes.values()).stream() .map(TargetAppGeoportalCodes::getTarget_app).collect(Collectors.toList()); throw ExceptionManager.badRequestException(req, "Target application is wrong. It must be one value of: " + targetApps, this.getClass(), helpURI); } } return targetAppGeoportalCodes; } /** * Generic resolve link. * * @param req the req * @param resoruceGeoportalCodes the resoruce geoportal codes * @param vreName the vre name * @param ucdID the ucd ID * @param projectID the project ID * @param resolveAs the resolve as * @return the response * @throws WebApplicationException the web application exception */ public Response genericResolveLink(@Context HttpServletRequest req, TargetAppGeoportalCodes resoruceGeoportalCodes, @PathParam(PATH_VRE_NAME) String vreName, @PathParam(PATH_USECASE_ID) String ucdID, @PathParam(PATH_PROJECT_ID) String projectID, @QueryParam(QP_RESOLVE_AS) String resolveAs) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " genericResolveLink starts..."); try { InnerMethodName.instance.set("resolveGeoportalPublicLink"); LOG.info("Found target app: " + resoruceGeoportalCodes); if (resoruceGeoportalCodes == null) { LOG.error("The path parameter '" + PATH_TARGET_APP + "' not found or empty in the path"); throw ExceptionManager.badRequestException(req, "Mandatory path parameter '" + PATH_TARGET_APP + "' not found or empty", this.getClass(), helpURI); } if (vreName == null || vreName.isEmpty()) { LOG.error("The path parameter '" + PATH_VRE_NAME + "' not found or empty in the path"); throw ExceptionManager.badRequestException(req, "Mandatory path parameter '" + PATH_VRE_NAME + "' not found or empty", this.getClass(), helpURI); } if (ucdID == null) { LOG.error("The path parameter '" + PATH_USECASE_ID + "' not found or empty in the path"); throw ExceptionManager.badRequestException(req, "Mandatory path parameter '" + PATH_USECASE_ID + "' not found or empty", this.getClass(), helpURI); } if (projectID == null) { LOG.error("The path parameter '" + PATH_PROJECT_ID + "' not found or empty in the path"); throw ExceptionManager.badRequestException(req, "Mandatory path parameter '" + PATH_PROJECT_ID + "' not found or empty", this.getClass(), helpURI); } ScopeBean fullScopeBean = null; // CHECKING IF THE INPUT VRE NAME IS REGISTRED IN THE INFRASTRUCTURE... try { fullScopeBean = LoadingMapOfScopeCache.get(vreName); } catch (ExecutionException | InvalidCacheLoadException e) { LOG.error("Error on getting the fullscope from cache for vreName " + vreName, e); throw ExceptionManager.wrongParameterException(req, "Error on getting full scope for the VRE name " + vreName + ". Is it registered as VRE in the D4Science Infrastructure System?", this.getClass(), helpURI); } RESOLVE_AS resolveTO = RESOLVE_AS.PUBLIC; if (resolveAs != null) { switch (resolveAs.toLowerCase()) { case "public": resolveTO = RESOLVE_AS.PUBLIC; break; case "private": resolveTO = RESOLVE_AS.PRIVATE; break; } } LOG.info("Found RESOLVE_AS_PARAMETER: " + resolveAs); String originalScope = ScopeProvider.instance.get(); GeoportalDataViewerConfigProfileReader reader; try { String theScope = fullScopeBean.toString(); LOG.info("Full scope is: " + theScope); ScopeProvider.instance.set(theScope); reader = new GeoportalDataViewerConfigProfileReader( org.gcube.datatransfer.resolver.geoportal.GeoportalCommonConstants.GEOPORTAL_DATA_VIEWER_APP); } catch (Exception e) { LOG.error("Error on reading the " + GeoportalDataViewerConfigProfileReader.SECONDARY_TYPE + " with generic resource name: " + GeoportalDataViewerConfigProfileReader.GENERIC_RESOURCE_NAME, e); throw ExceptionManager.internalErrorException(req, "Error on reading the " + GeoportalDataViewerConfigProfileReader.SECONDARY_TYPE + " for name " + GeoportalDataViewerConfigProfileReader.GENERIC_RESOURCE_NAME + ". Please contact the support", this.getClass(), helpURI); } finally { if (originalScope != null && !originalScope.isEmpty()) { ScopeProvider.instance.set(originalScope); LOG.info("scope provider set to orginal scope: " + originalScope); } else { ScopeProvider.instance.reset(); LOG.info("scope provider reset"); } } // Resolving towards Data-Viewer or Data-Entry Application String itemLink = null; switch (resoruceGeoportalCodes) { case GEO_DV: { GeoportalDataViewerConfigProfile geonaDataProfile = reader.getGeoportalDataViewerConfigProfile(); switch (resolveTO) { case PUBLIC: // Open Link itemLink = String.format("%s?%s=%s&%s=%s", geonaDataProfile.getOpenPortletURL(), GeoportalCommonConstants.GET_GEONA_ITEM_ID, projectID, GeoportalCommonConstants.GET_GEONA_ITEM_TYPE, ucdID); break; case PRIVATE: // Restricted Link itemLink = String.format("%s?%s=%s&%s=%s", geonaDataProfile.getRestrictedPortletURL(), GeoportalCommonConstants.GET_GEONA_ITEM_ID, projectID, GeoportalCommonConstants.GET_GEONA_ITEM_TYPE, ucdID); break; default: break; } break; } case GEO_DE: { LOG.error("The Resolver towards '" + resoruceGeoportalCodes + "' not implemented yet"); throw ExceptionManager.internalErrorException(req, "The Resolver towards '" + resoruceGeoportalCodes + "' not implemented yet", this.getClass(), helpURI); } default: break; } LOG.info("Returning link: " + itemLink); return Response.seeOther(new URL(itemLink).toURI()).build(); } catch (Exception e) { if (!(e instanceof WebApplicationException)) { // UNEXPECTED EXCEPTION managing it as WebApplicationException String error = "Error occurred on resolving the " + GeoportalResolver.class.getSimpleName() + " URL. Please, contact the support!"; if (e.getCause() != null) error += "\n\nCaused: " + e.getCause().getMessage(); throw ExceptionManager.internalErrorException(req, error, this.getClass(), helpURI); } // ALREADY MANAGED AS WebApplicationException LOG.error("Exception:", e); throw (WebApplicationException) e; } } /** * Generic create link. * * @param req the req * @param jsonRequest the json request * @param targetAppGeoportalCodes the target app geoportal codes * @return the URL */ protected String genericCreateLink(@Context HttpServletRequest req, GeoportalRequest jsonRequest, TargetAppGeoportalCodes targetAppGeoportalCodes) { LOG.info(this.getClass().getSimpleName() + " genericCreateLink starts..."); try { InnerMethodName.instance.set("postGeoportalPublicLink"); LOG.info("The body contains the request: " + jsonRequest.toString()); if (jsonRequest.getGcubeScope() == null) { throw ExceptionManager.badRequestException(req, "Missing parameter " + GeoportalRequest.P_GCUBE_SCOPE, this.getClass(), helpURI); } if (jsonRequest.getItemID() == null) { throw ExceptionManager.badRequestException(req, "Missing parameter " + GeoportalRequest.P_ITEM_ID, this.getClass(), helpURI); } if (jsonRequest.getItemType() == null) { throw ExceptionManager.badRequestException(req, "Missing parameter " + GeoportalRequest.P_ITEM_TYPE, this.getClass(), helpURI); } // CHECK IF INPUT SCOPE IS VALID String scope = jsonRequest.getGcubeScope(); if (!scope.startsWith(ConstantsResolver.SCOPE_SEPARATOR)) { LOG.info("Scope not start with char '{}' adding it", ConstantsResolver.SCOPE_SEPARATOR); scope += ConstantsResolver.SCOPE_SEPARATOR + scope; } String serverUrl = Util.getServerURL(req); final String vreName = scope.substring(scope.lastIndexOf(ConstantsResolver.SCOPE_SEPARATOR) + 1, scope.length()); ScopeBean fullScope = null; // CHECK IF THE vreName has a valid scope, so it is a valid VRE try { fullScope = LoadingMapOfScopeCache.get(vreName); } catch (ExecutionException e) { LOG.error("Error on getting the fullscope from cache for vreName " + vreName, e); throw ExceptionManager.wrongParameterException(req, "Error on getting full scope for the VRE name " + vreName + ". Is it registered as VRE in the D4Science Infrastructure System?", this.getClass(), helpURI); } if (fullScope == null) throw ExceptionManager.notFoundException(req, "The scope '" + scope + "' does not matching any scope in the infrastructure. Is it valid?", this.getClass(), helpURI); String linkURL = String.format("%s/%s/%s/%s/%s/%s", serverUrl, GEO, targetAppGeoportalCodes.getId(), vreName, jsonRequest.getItemType(), jsonRequest.getItemID()); if (jsonRequest.getQueryString() != null) { linkURL += "?" + jsonRequest.getQueryString(); } LOG.info("Returning " + GeoportalResolver.class.getSimpleName() + " URL: " + linkURL); return linkURL; } catch (Exception e) { if (!(e instanceof WebApplicationException)) { // UNEXPECTED EXCEPTION managing it as WebApplicationException String error = "Error occurred on creating the " + GeoportalResolver.class.getSimpleName() + " URL. Please, contact the support!"; throw ExceptionManager.internalErrorException(req, error, this.getClass(), helpURI); } // ALREADY MANAGED AS WebApplicationExceptiongetItemCatalogueURLs LOG.error("Exception:", e); throw (WebApplicationException) e; } } }