package org.gcube.datatransfer.resolver.services; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; 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.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.common.scope.impl.ScopeBean.Type; import org.gcube.datacatalogue.ckanutillibrary.server.DataCatalogueRunningCluster.ACCESS_LEVEL_TO_CATALOGUE_PORTLET; import org.gcube.datatransfer.resolver.ConstantsResolver; import org.gcube.datatransfer.resolver.caches.LoadingMapOfDetachedVRE; import org.gcube.datatransfer.resolver.caches.LoadingMapOfScopeCache; import org.gcube.datatransfer.resolver.catalogue.CatalogueRequest; import org.gcube.datatransfer.resolver.catalogue.ItemCatalogueURLs; import org.gcube.datatransfer.resolver.catalogue.ResourceCatalogueCodes; import org.gcube.datatransfer.resolver.catalogue.resource.CatalogueStaticConfigurations; import org.gcube.datatransfer.resolver.catalogue.resource.CkanCatalogueConfigurationsReader; import org.gcube.datatransfer.resolver.catalogue.resource.GatewayCKANCatalogueReference; import org.gcube.datatransfer.resolver.catalogue.resource.GetAllInfrastructureScopes; import org.gcube.datatransfer.resolver.services.error.ExceptionManager; import org.gcube.datatransfer.resolver.util.Util; import org.gcube.infrastructure.detachedres.detachedreslibrary.shared.re.VRE; import org.gcube.smartgears.utils.InnerMethodName; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.CacheLoader.InvalidCacheLoadException; import eu.trentorise.opendata.jackan.model.CkanDataset; /** * The CatalogueResolver is able to get/resolve a link to a "Catalogue Entity" * stored in one of the D4Science Catalogue's instances. A Catalogue Entity is * either a "group" or an "organization" or a "product" of D4Science Data * Catalogue. * * See more at * https://gcube.wiki.gcube-system.org/gcube/URI_Resolver#CATALOGUE_Resolver * * @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it) * Nov 16, 2018 */ @Path("{entityContext:ctlg(-(o|g|p|d))?}") public class CatalogueResolver { private static Logger logger = LoggerFactory.getLogger(CatalogueResolver.class); private static String helpURI = "https://wiki.gcube-system.org/gcube/URI_Resolver#CATALOGUE_Resolver"; /** * The Enum SCOPE_STATUS. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Mar 24, 2022 */ private static enum SCOPE_STATUS { ACTIVE, DETACHED } /** * Resolve an entity of the Catalogue * * Request format: * [URI_RESOLVER_SERVICE_ENDPOINT]/[ctlg|ctlg-d|ctlg-o|ctlg-g]/[VRE_NAME]/[entity_name] * * @param req the req * @param entityName the entity name * @param vreName the vre name * @param entityContext the entity context * @return the response * @throws WebApplicationException the web application exception */ @GET @Path("/{vreName}/{entityName}") public Response resolveCatalogue(@Context HttpServletRequest req, @PathParam("entityName") String entityName, @PathParam("vreName") String vreName, @PathParam("entityContext") String entityContext) throws WebApplicationException { logger.info(this.getClass().getSimpleName() + " GET starts..."); try { InnerMethodName.instance.set("resolveCataloguePublicLink"); ItemCatalogueURLs itemCatalogueURLs = getItemCatalogueURLs(req, vreName, entityContext, entityName); String itemCatalogueURL; if (itemCatalogueURLs.isPublicItem()) { logger.info("The dataset " + itemCatalogueURLs.getItemName() + " was detected as public item (not private to VRE)"); if (itemCatalogueURLs.getPublicVRECataloguePortletURL() != null && !itemCatalogueURLs.getPublicVRECataloguePortletURL().isEmpty()) { itemCatalogueURL = itemCatalogueURLs.getPublicVRECataloguePortletURL(); logger.info( "I found the public VRE catalogue URL, so using public access to it: " + itemCatalogueURL); } else { itemCatalogueURL = itemCatalogueURLs.getPublicGatewayCataloguePortletURL(); logger.info("No public VRE catalogue URL found, so using public access to gateway CKAN portlet: " + itemCatalogueURL); } } else { itemCatalogueURL = itemCatalogueURLs.getPrivateVRECataloguePortletURL(); logger.info("The dataset " + itemCatalogueURLs.getItemName() + " is a private item (to VRE) so using protected access to CKAN portlet: " + itemCatalogueURL); } return Response.seeOther(new URL(itemCatalogueURL).toURI()).build(); } catch (Exception e) { if (!(e instanceof WebApplicationException)) { // UNEXPECTED EXCEPTION managing it as WebApplicationException String error = "Error occurred on resolving the Catalgoue 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 logger.error("Exception:", e); throw (WebApplicationException) e; } } /** * Create a Catalogue Link * * * @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 postCatalogue(@Context HttpServletRequest req, CatalogueRequest jsonRequest) throws WebApplicationException { logger.info(this.getClass().getSimpleName() + " POST starts..."); try { InnerMethodName.instance.set("postCataloguePublicLink"); logger.info("The body contains the request: " + jsonRequest.toString()); // CHECK IF INPUT SCOPE IS VALID String scope = jsonRequest.getGcube_scope(); if (!scope.startsWith(ConstantsResolver.SCOPE_SEPARATOR)) { logger.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) { logger.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); ResourceCatalogueCodes rc = ResourceCatalogueCodes.valueOfCodeValue(jsonRequest.getEntity_context()); if (rc == null) { logger.error("Entity context is null/malformed"); throw ExceptionManager.badRequestException(req, "Entity context is null/malformed", this.getClass(), helpURI); } String linkURL = String.format("%s/%s/%s/%s", serverUrl, rc.getId(), vreName, jsonRequest.getEntity_name()); logger.info("Returining Catalogue URL: " + linkURL); return Response.ok(linkURL).header("Location", linkURL).build(); } catch (Exception e) { if (!(e instanceof WebApplicationException)) { // UNEXPECTED EXCEPTION managing it as WebApplicationException String error = "Error occurred on resolving the Analytics URL. Please, contact the support!"; throw ExceptionManager.internalErrorException(req, error, this.getClass(), helpURI); } // ALREADY MANAGED AS WebApplicationExceptiongetItemCatalogueURLs logger.error("Exception:", e); throw (WebApplicationException) e; } } /** * Gets the item catalogue UR ls. * * @param req the req * @param scopeName the scope name * @param entityContext the entity context * @param entityName the entity name * @return the item catalogue UR ls * @throws Exception the exception */ protected static ItemCatalogueURLs getItemCatalogueURLs(HttpServletRequest req, String scopeName, String entityContext, String entityName) throws Exception { try { String entityContextValue = ResourceCatalogueCodes.valueOfCodeId(entityContext).getValue(); ScopeBean scopeBean = null; SCOPE_STATUS scopeStatus = SCOPE_STATUS.ACTIVE; VRE vreDetached = null; try { scopeBean = LoadingMapOfScopeCache.get(scopeName); } catch (ExecutionException | InvalidCacheLoadException e) { logger.error( "Error on getting the fullscope from cache for scopeName {}. Tryng to load it from DetachedRE", scopeName); boolean isScopeDetached = false; try { vreDetached = LoadingMapOfDetachedVRE.get(scopeName); scopeBean = new ScopeBean(vreDetached.getScope()); scopeStatus = SCOPE_STATUS.DETACHED; logger.info("I loaded a valid VRE obj for scope name {}", scopeName); isScopeDetached = true; } catch (Exception e1) { logger.warn("I was not able to load a detached VRE for vreName {}. Going to error for wrong scope", scopeName); } // If is not a cas of scope detached, going to error for wrong scope if (!isScopeDetached) { logger.error("Error on getting the fullscope from cache for scopeName " + scopeName, e); throw ExceptionManager.wrongParameterException(req, "Error on getting full scope for the scope name '" + scopeName + "'. Is it registered as a valid Scope in the D4Science Infrastructure System?", CatalogueResolver.class, helpURI); } } String fullScope = scopeBean.toString(); logger.info("Read fullScope: " + fullScope + " for SCOPE name: " + scopeName + " from cache created by: " + GetAllInfrastructureScopes.class.getSimpleName()); if (scopeBean.is(Type.VO)) { logger.info("It is a {} scope", Type.VO); logger.warn("The Catalogue can't work at {} level, I'm overriding the scope to {} level", Type.VO, Type.INFRASTRUCTURE); String[] splitScope = fullScope.split(ConstantsResolver.SCOPE_SEPARATOR); fullScope = ConstantsResolver.SCOPE_SEPARATOR + splitScope[1]; // THIS IS THE INFRASTRUCTURE SCOPE logger.info("Overriden the input scope {} with {} as type: {}", scopeBean.toString(), Type.INFRASTRUCTURE, fullScope); } ScopeProvider.instance.set(fullScope); GatewayCKANCatalogueReference ckanCatalogueReference = null; logger.info("Managing scope status: {}", scopeStatus); switch (scopeStatus) { case DETACHED: String privatePortletURL = vreDetached.getCatalogPortletURL(); // The private portlet URL Map mapAccessURLToCatalogue = new HashMap( 3); mapAccessURLToCatalogue.put(ACCESS_LEVEL_TO_CATALOGUE_PORTLET.PRIVATE_VRE, privatePortletURL); // Building the gateway catalogue public URL from private VRE Portlet URL URI toURL = new URI(privatePortletURL); String publicURL = privatePortletURL.startsWith("https://") ? "https://" + toURL.getHost() : "http://" + toURL.getHost(); // It returns the string "catalogue" CatalogueStaticConfigurations staticConf = new CatalogueStaticConfigurations(); // Replacing for example "ckan-bb" with "[PREFIXES-TO-CATALOGUE-URL]-bb" (e.g // catalogue-bb) String relativeURLWithCatalogueName = staticConf .buildRelativeURLToPublicCatalogueGateway(vreDetached.getCatalogUrl()); String toGatewayPortletURL = String.format("%s/%s", publicURL, relativeURLWithCatalogueName); mapAccessURLToCatalogue.put(ACCESS_LEVEL_TO_CATALOGUE_PORTLET.PUBLIC_GATEWAY, toGatewayPortletURL); ckanCatalogueReference = new GatewayCKANCatalogueReference(fullScope, vreDetached.getCatalogUrl(), mapAccessURLToCatalogue); break; case ACTIVE: default: ckanCatalogueReference = CkanCatalogueConfigurationsReader.loadCatalogueEndPoints(); break; } logger.info("For scope " + fullScope + " loaded end points: " + ckanCatalogueReference); // IS THE PRODUCT PLUBLIC OR PRIVATE? String datasetName = entityName; boolean isPublicItem = false; if (ckanCatalogueReference.getCkanURL() != null) { try { CkanDataset dataset = CkanCatalogueConfigurationsReader.getDataset(datasetName, ckanCatalogueReference.getCkanURL()); if (dataset != null) { isPublicItem = true; // ckanPorltetUrl = ckanCatalogueReference.getPublicPortletURL(); logger.info("The dataset " + datasetName + " is a public item"); } } catch (Exception e) { logger.warn("Error on checking if dataset: " + datasetName + " is private or not", e); isPublicItem = true; } } String publicGatewayPorltetURL = String.format("%s?path=/%s/%s", ckanCatalogueReference.getCatalogueURL(ACCESS_LEVEL_TO_CATALOGUE_PORTLET.PUBLIC_GATEWAY), entityContextValue, entityName); String privateVREPortletURL = String.format("%s?path=/%s/%s", ckanCatalogueReference.getCatalogueURL(ACCESS_LEVEL_TO_CATALOGUE_PORTLET.PRIVATE_VRE), entityContextValue, entityName); // Checking if the public VRE portlet URL is available (so it was read from GR) String publicVREPortletURL = null; String toCheckPublicVREPortletURL = ckanCatalogueReference .getCatalogueURL(ACCESS_LEVEL_TO_CATALOGUE_PORTLET.PUBLIC_VRE); if (toCheckPublicVREPortletURL != null && !toCheckPublicVREPortletURL.isEmpty()) { // here the catalogue is available/deployed as public at VRE level publicVREPortletURL = String.format("%s?path=/%s/%s", toCheckPublicVREPortletURL, entityContextValue, entityName); } return new ItemCatalogueURLs(entityName, isPublicItem, privateVREPortletURL, publicVREPortletURL, publicGatewayPorltetURL); } catch (Exception e) { logger.error("Error when resolving CatalogueURL:", e); throw e; } } }