package org.gcube.datatransfer.resolver.services; import java.io.InputStream; import java.util.LinkedHashMap; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; 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 javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.StreamingOutput; import org.gcube.application.geoportalcommon.GeoportalCommon; import org.gcube.application.geoportalcommon.shared.GeoportalItemReferences; import org.gcube.application.geoportalcommon.shared.GeoportalItemReferences.SHARE_LINK_TO; import org.gcube.application.geoportaldatamapper.exporter.Geoportal_PDF_Exporter; import org.gcube.application.geoportaldatamapper.shared.ExporterProjectSource; import org.gcube.application.geoportaldatamapper.shared.FileReference; import org.gcube.common.authorization.utils.manager.SecretManager; import org.gcube.common.authorization.utils.manager.SecretManagerProvider; import org.gcube.common.authorization.utils.user.User; import org.gcube.datatransfer.resolver.ConstantsResolver; import org.gcube.datatransfer.resolver.geoportal.exporter.FetchPDF; import org.gcube.datatransfer.resolver.geoportal.exporter.GeoportalViewJsonResponse; import org.gcube.datatransfer.resolver.geoportal.exporter.HTML_Page; import org.gcube.datatransfer.resolver.services.error.ExceptionManager; import org.gcube.datatransfer.resolver.services.exceptions.BadRequestException; import org.gcube.datatransfer.resolver.util.SingleFileStreamingOutput; import org.gcube.datatransfer.resolver.util.Util; import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.gson.JsonObject; /** * The Class GeoportalExporter. * * @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it * * Apr 16, 2024 */ @Path("geoportal") public class GeoportalExporter { private static final String PDF_CODE = "pdfCode"; public static final String EXPORT_TYPE = "type"; public static final String PATH_PROJECT_ID = GeoportalResolver.PATH_PROJECT_ID; public static final String PATH_USECASE_ID = GeoportalResolver.PATH_USECASE_ID; public static final String PATH_VRE_NAME = GeoportalResolver.PATH_VRE_NAME; private static final Logger LOG = LoggerFactory.getLogger(GeoportalExporter.class); private static String helpURI = "https://wiki.gcube-system.org/gcube/URI_Resolver#Geoportal_Resolver"; private static LinkedHashMap map = new LinkedHashMap(); public enum ACCEPTED_EXPORT_TYPE { pdf } /** * Resolve geoportal no app def. * * @param req the req * @param vreName the vre name * @param exportType the export type * @param ucdID the ucd ID * @param projectID the project ID * @return the response * @throws WebApplicationException the web application exception */ @GET @Path("/export/{type}/{usecase_id}/{project_id}") @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN, MediaType.APPLICATION_OCTET_STREAM }) public Response export(@Context HttpServletRequest req, @PathParam(EXPORT_TYPE) String export_type, @PathParam(PATH_USECASE_ID) String ucdID, @PathParam(PATH_PROJECT_ID) String projectID) throws WebApplicationException { String userAgentName = req.getHeader("User-Agent"); LOG.info(this.getClass().getSimpleName() + " export - GET starts..."); LOG.debug("params are: exportType: {}, ucdID: {}, projectID: {}", export_type, ucdID, projectID); checkPathParameterNotNull(req, EXPORT_TYPE, export_type); checkPathParameterNotNull(req, PATH_USECASE_ID, ucdID); checkPathParameterNotNull(req, PATH_PROJECT_ID, projectID); checkExportType(req, export_type); boolean checked = false; SecretManager cm = SecretManagerProvider.instance.get(); final String context = cm.getContext(); if (context == null) { throw ExceptionManager.forbiddenException(req, "Cannot determine context (the scope)", this.getClass(), helpURI); } final org.gcube.common.authorization.utils.user.User user = cm.getUser(); if (user == null) { throw ExceptionManager.forbiddenException(req, "Cannot determine user", this.getClass(), helpURI); } LOG.info("Identified caller {} in context {}", user.getUsername(), context); try { Geoportal_PDF_Exporter pdfExporter = new Geoportal_PDF_Exporter(); checked = pdfExporter.checkConfig(); } catch (Exception e) { LOG.error("Error on performing export", e); throw ExceptionManager .internalErrorException(req, "Error the " + Geoportal_PDF_Exporter.class.getSimpleName() + " seems to be not configured in the context: " + context, this.getClass(), helpURI); } if (checked) { try { if (userAgentName != null) { LOG.info("Serving request as User-Agent {}", userAgentName); final String pollingCode = ucdID + "_" + projectID + "_" + System.currentTimeMillis(); final FetchPDF fetchPDF = new FetchPDF(null, pollingCode, 0); map.put(pollingCode, fetchPDF); try { Thread t = new Thread() { @Override public void run() { LOG.info("exportAsPDF called in thread..."); FileReference pdfRef = exportAsPDF(req, ucdID, projectID, null, context, user); LOG.info("exportAsPDF setFileRef in thread for code: " + pollingCode); fetchPDF.setFileRef(pdfRef); map.put(pollingCode, fetchPDF); } }; t.start(); } catch (Exception e) { LOG.error("Error on performing export in thread", e); throw e; } String serviceViewPDF_URL = String.format("%s/%s/view/%s", Util.getServerURL(req), "geoportal", pollingCode); String entity = HTML_Page.entityHTMLMessage("Exporting as PDF...", "The project with id: " + projectID, true, serviceViewPDF_URL); return Response.ok(entity).encoding("UTF-8").header(ConstantsResolver.CONTENT_TYPE, "text/html") .build(); } else { LOG.info("Serving request as client..."); FileReference pdfRef = exportAsPDF(req, ucdID, projectID, null, context, user); InputStream input = pdfRef.getStorageVolatileURL().openStream(); StreamingOutput so = new SingleFileStreamingOutput(input); ResponseBuilder response = Response.ok(so) .header(ConstantsResolver.CONTENT_DISPOSITION, "inline; filename=\"" + pdfRef.getFileName() + "\"") .header("Content-Type", pdfRef.getContentType()); return response.build(); } } catch (Exception e) { LOG.error("Error on performing export", e); throw ExceptionManager.internalErrorException(req, "Sorry, error occurred when exporting the project", this.getClass(), helpURI); } } else { return Response .status(Status.NOT_FOUND).entity(GeoportalExporter.class.getSimpleName() + "seems to be not configured in the context: " + context) .type(MediaType.TEXT_PLAIN).build(); } } @GET @Path("/export/{type}/healthcheck") @Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN }) public Response healthcheck(@Context HttpServletRequest req, @PathParam(EXPORT_TYPE) String export_type) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " healthcheck - GET starts..."); LOG.debug("param is: exportType: {}", export_type); checkPathParameterNotNull(req, EXPORT_TYPE, export_type); checkExportType(req, export_type); try { SecretManager cm = SecretManagerProvider.instance.get(); String context = cm.getContext(); if (context == null) { throw ExceptionManager.forbiddenException(req, "Cannot determine context (the scope)", this.getClass(), helpURI); } org.gcube.common.authorization.utils.user.User user = cm.getUser(); LOG.info("Identified caller {} in context {}", user.getUsername(), context); Geoportal_PDF_Exporter pdfExporter = new Geoportal_PDF_Exporter(); boolean checked = pdfExporter.checkConfig(); if (checked) { return Response.ok(GeoportalExporter.class.getSimpleName() + " Config OK").build(); } else { return Response.status(Status.NOT_FOUND).entity(GeoportalExporter.class.getSimpleName() + " Config KO") .type(MediaType.TEXT_PLAIN).build(); } } catch (Exception e) { LOG.error("Error on performing healthcheck", e); throw ExceptionManager.internalErrorException(req, "Error when performing " + GeoportalExporter.class.getSimpleName() + " healthcheck", this.getClass(), helpURI); } } @GET @Path("/view/{pdfCode}") @Produces({ MediaType.TEXT_PLAIN, MediaType.TEXT_HTML }) public Response viewPDF(@Context HttpServletRequest req, @PathParam(PDF_CODE) String pdfCode) throws WebApplicationException { LOG.info(this.getClass().getSimpleName() + " viewPDF - GET starts..."); LOG.info("viewPDF param " + PDF_CODE + ": {}", pdfCode); FetchPDF fetchedPDF = map.get(pdfCode); LOG.info("viewPDF FileReference at code {} is {}", pdfCode, fetchedPDF); String theURL = null; String messagge = null; String state = null; GeoportalViewJsonResponse theJson = new GeoportalViewJsonResponse(); theJson.setState(state); theJson.setUrl(theURL); theJson.setMessage(messagge); if (fetchedPDF == null) { theJson.setState(Status.NOT_FOUND.getReasonPhrase()); theJson.setMessage("No job found"); String json = theJson.toString(); LOG.info("viewPDF returning not found: " + json); return Response.status(Status.NOT_FOUND).entity(json).build(); } try { // File PDF is not available if (fetchedPDF.getFileRef() == null) { int attempt = fetchedPDF.incrementAttempt(); if (fetchedPDF.getAttempt() < fetchedPDF.getMAX_RETRY()) { state = "OK"; messagge = "Attempt #" + attempt; } else { state = "ERROR"; messagge = "Sorry, an error occurred tryng to create the PDF. Max retries reached"; theJson.setState(state); } // updating map status map.put(pdfCode, fetchedPDF); } else { // File PDF is available state = "OK"; theURL = fetchedPDF.getFileRef().getStorageVolatileURL().toString(); messagge = "PDF created correclty"; // removing from map map.put(pdfCode, null); } theJson.setState(state); theJson.setUrl(theURL); theJson.setMessage(messagge); String json = theJson.toString(); LOG.info("viewPDF returning OK: " + json); return Response.ok(json).build(); } catch (Exception e) { LOG.error("Error on checking job", e); throw ExceptionManager.internalErrorException(req, "Error when checking " + GeoportalExporter.class.getSimpleName() + " job view for " + pdfCode, this.getClass(), helpURI); } } public void checkPathParameterNotNull(HttpServletRequest req, String parameter, String value) throws BadRequestException { if (value == null || value.isEmpty()) { LOG.error("The path parameter {} not found or empty in the path", parameter); throw ExceptionManager.badRequestException(req, "Mandatory path parameter " + parameter + " not found or empty", this.getClass(), helpURI); } } public ACCEPTED_EXPORT_TYPE checkExportType(HttpServletRequest req, String export_type) throws BadRequestException { ACCEPTED_EXPORT_TYPE exportType; try { exportType = ACCEPTED_EXPORT_TYPE.valueOf(export_type); } catch (Exception e) { throw ExceptionManager .wrongParameterException(req, "The path parameter " + EXPORT_TYPE + " has a bad value: " + export_type + ". It must be value of " + ACCEPTED_EXPORT_TYPE.values(), this.getClass(), helpURI); } return exportType; } public FileReference exportAsPDF(HttpServletRequest req, String profileID, String projectID, String profileTitle, String context, User user) throws WebApplicationException { LOG.info("exportAsPDF for profileID: " + profileID + ", projectID: " + projectID + "called"); LOG.info("exportAsPDF context is {}, user is {}", context, user); FileReference pdfRef = null; try { Geoportal_PDF_Exporter gpdfe = new Geoportal_PDF_Exporter(); ExporterProjectSource exportSource = new ExporterProjectSource(); exportSource.setProfileID(profileID); exportSource.setProfileTitle(profileTitle); exportSource.setProjectID(projectID); exportSource.setScope(context); GeoportalItemReferences geoportalItemReferences = new GeoportalItemReferences(projectID, profileID, SHARE_LINK_TO.DATA_VIEWER); GeoportalItemReferences gir = getPublicLinksFor(geoportalItemReferences, context); if (user.isApplication()) { exportSource.setGisLink(gir.getOpenLink().getShortURL()); exportSource.setAccountname(null); } else { exportSource.setGisLink(gir.getRestrictedLink().getShortURL()); exportSource.setAccountname(user.getUsername()); } pdfRef = gpdfe.createPDFFile(exportSource); } catch (Exception e1) { LOG.error("Error occurred when exporting the project", e1); throw ExceptionManager.internalErrorException(req, "Sorry, an error occurred when exporting the project", this.getClass(), helpURI); } return pdfRef; } public GeoportalItemReferences getPublicLinksFor(GeoportalItemReferences item, String context) throws Exception { LOG.info("getPublicLinksFor called for: " + item); try { if (item == null) throw new Exception("Bad request, the item is null"); if (item.getProjectID() == null) throw new Exception("Bad request, the projectID is null"); if (item.getProfileID() == null) throw new Exception("Bad request, the profileID is null"); GeoportalCommon gc = new GeoportalCommon(); return gc.getPublicLinksFor(context, item, true); } catch (Exception e) { LOG.error("Error on getPublicLinksFor for: " + item, e); throw new Exception("Share link not available for this item. Try later or contact the support. Error: " + e.getMessage()); } } }