504 lines
18 KiB
Java
504 lines
18 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.
|
|
*
|
|
* The Geoportal Exporter provides the facilities to export GIS projects stored in
|
|
* the Geoportal system provided via D4Science VREs.
|
|
*
|
|
* @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. Must be a value of the enum
|
|
* {#ACCEPTED_EXPORT_TYPE}
|
|
* @param ucdID the ucd ID
|
|
* @param projectID the project ID
|
|
* @param asURL the as URL. If true returns the project exported as an URL
|
|
* in the response
|
|
* @return the response. Can be an URL or a Stream of the project exported.
|
|
* @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 secreteManager = SecretManagerProvider.instance.get();
|
|
final String context = secreteManager.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 = secreteManager.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 the configuration exists in the context
|
|
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. Checks if the export facility is configured in the context (i.e.
|
|
* the scope read from input token).
|
|
*
|
|
* @param req the req
|
|
* @param export_type the export type
|
|
* @return an object of type {#GeoportalJsonResponse) that contains 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. Check the job export status.
|
|
*
|
|
* @param req the req
|
|
* @param jobCode the job code. The job ID assigned by export facility that
|
|
* returns a waiting HTML page
|
|
* @return the response. It is a {#GeoportalJsonResponse} with the status of the
|
|
* job. An export job completed contains the URL of the project
|
|
* exported.
|
|
* @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());
|
|
}
|
|
}
|
|
|
|
}
|