uri-resolver/src/main/java/org/gcube/datatransfer/resolver/services/GeoportalExporter.java

495 lines
17 KiB
Java

package org.gcube.datatransfer.resolver.services;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.LinkedHashMap;
import javax.annotation.Nullable;
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.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 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.com.fasterxml.jackson.core.JsonProcessingException;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
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.GeoportalJsonResponse;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 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 JOB_CODE = "jobCode";
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;
public static final String QUERY_PARAMETER_AS_URL = "as-url";
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<String, FetchPDF> map = new LinkedHashMap<String, FetchPDF>();
/**
* The Enum ACCEPTED_EXPORT_TYPE.
*
* @author Francesco Mangiacrapa at ISTI-CNR francesco.mangiacrapa@isti.cnr.it
*
* Apr 22, 2024
*/
public enum ACCEPTED_EXPORT_TYPE {
pdf
}
/**
* Export.
*
* @param req the req
* @param export_type the export type
* @param ucdID the ucd ID
* @param projectID the project ID
* @param asURL the as URL
* @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,
@QueryParam(QUERY_PARAMETER_AS_URL) @Nullable String asURL) throws WebApplicationException {
String userAgentName = req.getHeader("User-Agent");
LOG.info(this.getClass().getSimpleName() + " export - GET starts...");
LOG.info("Params: [" + EXPORT_TYPE + ": {}, " + PATH_USECASE_ID + ": {}, " + PATH_PROJECT_ID + ": {}, "
+ QUERY_PARAMETER_AS_URL + ": {}]", export_type, ucdID, projectID, asURL);
checkPathParameterNotNull(req, EXPORT_TYPE, export_type);
checkPathParameterNotNull(req, PATH_USECASE_ID, ucdID);
checkPathParameterNotNull(req, PATH_PROJECT_ID, projectID);
boolean getAsURL = false;
try {
getAsURL = Boolean.parseBoolean(asURL);
} catch (Exception e) {
}
LOG.info("getAsURL is: {}", getAsURL);
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) {
if (getAsURL) {
try {
LOG.info("Serving request as getAsURL...");
FileReference pdfRef = exportAsPDF(req, ucdID, projectID, null, context, user);
String theURL = pdfRef.getStorageVolatileURL().toString();
LOG.info("returning URL {}", theURL);
return Response.ok(theURL).build();
} catch (Exception e) {
LOG.error("Error on performing export by url", e);
throw ExceptionManager.internalErrorException(req,
"Sorry, error occurred when generating the project URL. Error is: " + e.getMessage(),
this.getClass(), helpURI);
}
}
try {
if (userAgentName != null) {
LOG.info("Serving request as User-Agent {}", userAgentName);
final String jobCode = ucdID + "_" + projectID + "_" + System.currentTimeMillis();
final FetchPDF fetchPDF = new FetchPDF(null, jobCode, 0);
map.put(jobCode, 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: " + jobCode);
fetchPDF.setFileRef(pdfRef);
map.put(jobCode, 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",
jobCode);
String entity = HTML_Page.entityHTMLMessage("Exporting as PDF...",
"The project with id: " + projectID, true, serviceViewPDF_URL);
LOG.info("returning waiting HTML page");
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);
// returning as stream
InputStream input = pdfRef.getStorageVolatileURL().openStream();
StreamingOutput so = new SingleFileStreamingOutput(input);
LOG.info("returning project streaming...");
return Response.ok(so)
.header(ConstantsResolver.CONTENT_DISPOSITION,
"inline; filename=\"" + pdfRef.getFileName() + "\"")
.header("Content-Type", pdfRef.getContentType()).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();
}
}
/**
* Healthcheck.
*
* @param req the req
* @param export_type the export type
* @return the response
* @throws WebApplicationException the web application exception
*/
@GET
@Path("/export/{type}/healthcheck")
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN, MediaType.TEXT_HTML })
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();
GeoportalJsonResponse theJson = new GeoportalJsonResponse();
if (checked) {
theJson.setState("OK");
theJson.setMessage(GeoportalExporter.class.getSimpleName() + " Config OK in the context: " + context);
String jsonResponse = responseToString(theJson);
return Response.ok(jsonResponse).build();
} else {
theJson.setState("KO");
theJson.setMessage(GeoportalExporter.class.getSimpleName() + " Config "
+ Status.NOT_FOUND.getReasonPhrase() + " in the context: " + context);
String jsonResponse = responseToString(theJson);
return Response.status(Status.NOT_FOUND).entity(jsonResponse).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);
}
}
/**
* View job.
*
* @param req the req
* @param jobCode the job code
* @return the response
* @throws WebApplicationException the web application exception
*/
@GET
@Path("/view/{jobCode}")
@Produces({ MediaType.APPLICATION_JSON, MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
public Response viewJob(@Context HttpServletRequest req, @PathParam(JOB_CODE) String jobCode)
throws WebApplicationException {
LOG.info(this.getClass().getSimpleName() + " viewPDF - GET starts...");
LOG.info("viewJob param " + JOB_CODE + ": {}", jobCode);
FetchPDF fetchedPDF = map.get(jobCode);
LOG.info("viewJob FileReference at code {} is {}", jobCode, fetchedPDF);
String theURL = null;
String messagge = null;
String state = null;
GeoportalJsonResponse theJson = new GeoportalJsonResponse();
theJson.setState(state);
theJson.setUrl(theURL);
theJson.setMessage(messagge);
String jsonReponse = null;
if (fetchedPDF == null) {
theJson.setState(Status.NOT_FOUND.getReasonPhrase());
theJson.setMessage("No job found with id: "+jobCode);
try {
jsonReponse = responseToString(theJson);
LOG.info("viewJob returning not found: " + jsonReponse);
return Response.status(Status.NOT_FOUND).entity(jsonReponse).build();
} catch (JsonProcessingException e) {
throw ExceptionManager.internalErrorException(req, "Error when returning "
+ GeoportalExporter.class.getSimpleName() + " not found job for " + jobCode, this.getClass(),
helpURI);
}
}
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(jobCode, fetchedPDF);
} else {
// File PDF is available
state = "OK";
theURL = fetchedPDF.getFileRef().getStorageVolatileURL().toString();
messagge = "PDF created correclty";
// removing from map
map.put(jobCode, null);
}
theJson.setState(state);
theJson.setUrl(theURL);
theJson.setMessage(messagge);
try {
jsonReponse = responseToString(theJson);
LOG.info("viewJob returning OK: " + jsonReponse);
return Response.ok(jsonReponse).build();
} catch (JsonProcessingException e) {
throw ExceptionManager.internalErrorException(req,
"Error when returning " + GeoportalExporter.class.getSimpleName() + " response for " + jobCode,
this.getClass(), helpURI);
}
} catch (Exception e) {
LOG.error("Error on checking job", e);
throw ExceptionManager.internalErrorException(req,
"Error when checking " + GeoportalExporter.class.getSimpleName() + " job view for " + jobCode,
this.getClass(), helpURI);
}
}
public String responseToString(GeoportalJsonResponse jsonResponse) throws JsonProcessingException {
ObjectMapper mapper = new ObjectMapper();
return mapper.writeValueAsString(jsonResponse);
}
/**
* Check path parameter not null.
*
* @param req the req
* @param parameter the parameter
* @param value the value
* @throws BadRequestException the bad request exception
*/
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);
}
}
/**
* Check export type.
*
* @param req the req
* @param export_type the export type
* @return the accepted export type
* @throws BadRequestException the bad request exception
*/
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;
}
/**
* Export as PDF.
*
* @param req the req
* @param profileID the profile ID
* @param projectID the project ID
* @param profileTitle the profile title
* @param context the context
* @param user the user
* @return the file reference
* @throws WebApplicationException the web application exception
*/
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;
}
/**
* Gets the public links for.
*
* @param item the item
* @param context the context
* @return the public links for
* @throws Exception the exception
*/
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());
}
}
}