Implementing Geoportal_Exporter

This commit is contained in:
Francesco Mangiacrapa 2024-04-19 11:26:55 +02:00
parent 57dec57f72
commit 99cfe3976a
3 changed files with 302 additions and 160 deletions

View File

@ -7,7 +7,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
## [v2.10.0-SNAPSHOT]
- GeoPortal-Resolver enhancement: implemented share link towards Geoportal Data-Entry facility [#27135]
- Added Geoportal Exporter as PDF [#26026]
- Added Geoportal Exporter as PDF [#27274]
## [v2.9.0]

View File

@ -19,20 +19,25 @@ import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.ext.Provider;
import org.gcube.common.authorization.library.provider.AccessTokenProvider;
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
import org.gcube.common.authorization.utils.manager.SecretManager;
import org.gcube.common.authorization.utils.manager.SecretManagerProvider;
import org.gcube.common.authorization.utils.secret.GCubeSecret;
import org.gcube.common.authorization.utils.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.datatransfer.resolver.ConstantsResolver;
import org.gcube.datatransfer.resolver.UriResolverServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The Class RequestHandler.
*
* @author Francesco Mangiacrapa at ISTI-CNR (francesco.mangiacrapa@isti.cnr.it)
*
* Mar 15, 2019
* Mar 15, 2019
*/
@Provider
@PreMatching
@ -44,7 +49,8 @@ public class RequestHandler implements ContainerRequestFilter, ContainerResponse
private static final Logger log = LoggerFactory.getLogger(RequestHandler.class);
@Context ServletContext context;
@Context
ServletContext context;
@Context
HttpServletRequest webRequest;
@ -55,127 +61,195 @@ public class RequestHandler implements ContainerRequestFilter, ContainerResponse
@Context
ResourceContext resourceContext;
/* (non-Javadoc)
* @see javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.ContainerRequestContext)
*/
@Override
public void filter(ContainerRequestContext reqContext) throws IOException {
log.info(RequestHandler.class.getSimpleName() +" Request called");
if(SecurityTokenProvider.instance.get()==null)
SecurityTokenProvider.instance.set(context.getInitParameter(ROOT_APP_TOKEN));
/**
* Filter.
*
* @param reqContext the req context
* @throws IOException Signals that an I/O exception has occurred.
*/
/*
* (non-Javadoc)
*
* @see
* javax.ws.rs.container.ContainerRequestFilter#filter(javax.ws.rs.container.
* ContainerRequestContext)
*/
@Override
public void filter(ContainerRequestContext reqContext) throws IOException {
log.info(RequestHandler.class.getSimpleName() + " Request called");
setSecretManager(reqContext);
if(ScopeProvider.instance.get()==null)
ScopeProvider.instance.set(context.getInitParameter(ROOT_SCOPE));
log.info("Token and Scope Provider set called");
if (SecurityTokenProvider.instance.get() == null)
SecurityTokenProvider.instance.set(context.getInitParameter(ROOT_APP_TOKEN));
if (ScopeProvider.instance.get() == null)
ScopeProvider.instance.set(context.getInitParameter(ROOT_SCOPE));
log.info("Token and Scope Provider set called");
List<String> listOfPath = UriResolverServices.getInstance().getListOfResourcePath(application.getClasses());
log.debug("The resources are: {}", listOfPath);
log.debug("The resources are: {}", listOfPath);
String path = reqContext.getUriInfo().getPath();
log.debug("The path is: {}", path);
//HOW TO READ THE QUERY STRING
/*MultivaluedMap<String, String> queryParameters = reqContext.getUriInfo().getQueryParameters();
String queryString = "";
try {
queryString = Util.toQueryString(queryParameters);
}catch (Exception e) {
//silent
log.warn("Error on reading the query string, trying to continue...");
String path = reqContext.getUriInfo().getPath();
log.debug("The path is: {}", path);
// HOW TO READ THE QUERY STRING
/*
* MultivaluedMap<String, String> queryParameters =
* reqContext.getUriInfo().getQueryParameters(); String queryString = ""; try {
* queryString = Util.toQueryString(queryParameters); }catch (Exception e) {
* //silent
* log.warn("Error on reading the query string, trying to continue..."); }
* log.debug("The query string is: {}", queryString);
*/
if (path == null || path.isEmpty()) {
log.debug("The path is null or empty, redirecting to /index");
URI newRequestURI = reqContext.getUriInfo().getBaseUriBuilder().path("/index").build();
reqContext.setRequestUri(newRequestURI);
return;
}
log.debug("The query string is: {}", queryString);
*/
if(path==null || path.isEmpty()) {
log.debug("The path is null or empty, redirecting to /index");
URI newRequestURI = reqContext.getUriInfo().getBaseUriBuilder().path("/index").build();
reqContext.setRequestUri(newRequestURI);
return;
}
String[] splittePath = null;
boolean resourceToRedirectFound = false;
String candidateResource = "";
try {
splittePath = path.split("/");
if(splittePath!=null && splittePath.length>0) {
String requestedResourceName = splittePath[0];
log.debug("The resource requested is: {}",requestedResourceName);
if(requestedResourceName!=null && !requestedResourceName.isEmpty()) {
for (String resource : listOfPath) {
log.trace("Is resource '{}' starting with '{}' ?",resource,requestedResourceName);
if(resource.startsWith(requestedResourceName)) {
String[] splittePath = null;
boolean resourceToRedirectFound = false;
String candidateResource = "";
try {
splittePath = path.split("/");
if (splittePath != null && splittePath.length > 0) {
String requestedResourceName = splittePath[0];
log.debug("The resource requested is: {}", requestedResourceName);
if (requestedResourceName != null && !requestedResourceName.isEmpty()) {
for (String resource : listOfPath) {
log.trace("Is resource '{}' starting with '{}' ?", resource, requestedResourceName);
if (resource.startsWith(requestedResourceName)) {
log.trace("Yes it starts!");
candidateResource = requestedResourceName;
log.info("The candidate resource to manage the request is: {}",candidateResource);
log.info("The candidate resource to manage the request is: {}", candidateResource);
resourceToRedirectFound = true;
break;
}
}
//Try to manage as Catalogue Request ctlg, ctlg-p, etc.
if(!resourceToRedirectFound) {
log.info("Trying to manage as hard-coded case among cases: {}", Arrays.asList(ConstantsResolver.resourcesHardCoded).toString());
String[] hardCode = ConstantsResolver.resourcesHardCoded;
for (String resource : hardCode) {
log.trace("Is requested resource '{}' starting with hard-coded resource '{}'?",requestedResourceName,resource);
if(requestedResourceName.startsWith(resource)) {
// Try to manage as Catalogue Request ctlg, ctlg-p, etc.
if (!resourceToRedirectFound) {
log.info("Trying to manage as hard-coded case among cases: {}",
Arrays.asList(ConstantsResolver.resourcesHardCoded).toString());
String[] hardCode = ConstantsResolver.resourcesHardCoded;
for (String resource : hardCode) {
log.trace("Is requested resource '{}' starting with hard-coded resource '{}'?",
requestedResourceName, resource);
if (requestedResourceName.startsWith(resource)) {
log.trace("Yes it starts!");
candidateResource = resource;
log.info("The candidate resource to manage the request is the hard-coded resource: {}",candidateResource);
log.info("The candidate resource to manage the request is the hard-coded resource: {}",
candidateResource);
resourceToRedirectFound = true;
break;
}
}
}
}else
log.warn("It was not possible to get the resource name from the splitted path {}. No action performed", path);
}else {
log.warn("It was not possible to split the path {}. No action performed", path);
}
}catch (Exception e) {
}
} else
log.warn(
"It was not possible to get the resource name from the splitted path {}. No action performed",
path);
} else {
log.warn("It was not possible to split the path {}. No action performed", path);
}
} catch (Exception e) {
log.error("Error trying to retrieve the service able to manage the request. No action performed", e);
}
if(resourceToRedirectFound) {
log.debug("The input request '{}' can be managed by the service '{}'. No redirect performed", path, candidateResource);
}else {
log.info("No resource/service found to manage the input request '{}'", path);
String newPath = String.format("/%s/%s", ConstantsResolver.defaultServiceToRedirect,path);
//log.debug("The path to redirect is '{}'", newPath);
//URI newRequestURI = reqContext.getUriInfo().getBaseUriBuilder().path(newPath).build();
UriBuilder uriBuilder = reqContext.getUriInfo().getBaseUriBuilder();
//ADDING THE INPUT QUERY STRING
MultivaluedMap<String, String> queryParameters = reqContext.getUriInfo().getQueryParameters();
for (String param : queryParameters.keySet()) {
List<String> values = queryParameters.get(param);
if(values!=null && !values.isEmpty())
uriBuilder.queryParam(param,values.toArray());
}
URI newRequestURI = uriBuilder.path(newPath).build();
log.info("Redirect to URI path '{}'", newRequestURI.toString());
reqContext.setRequestUri(newRequestURI);
}
}
/* (non-Javadoc)
* @see javax.ws.rs.container.ContainerResponseFilter#filter(javax.ws.rs.container.ContainerRequestContext, javax.ws.rs.container.ContainerResponseContext)
if (resourceToRedirectFound) {
log.debug("The input request '{}' can be managed by the service '{}'. No redirect performed", path,
candidateResource);
} else {
log.info("No resource/service found to manage the input request '{}'", path);
String newPath = String.format("/%s/%s", ConstantsResolver.defaultServiceToRedirect, path);
// log.debug("The path to redirect is '{}'", newPath);
// URI newRequestURI =
// reqContext.getUriInfo().getBaseUriBuilder().path(newPath).build();
UriBuilder uriBuilder = reqContext.getUriInfo().getBaseUriBuilder();
// ADDING THE INPUT QUERY STRING
MultivaluedMap<String, String> queryParameters = reqContext.getUriInfo().getQueryParameters();
for (String param : queryParameters.keySet()) {
List<String> values = queryParameters.get(param);
if (values != null && !values.isEmpty())
uriBuilder.queryParam(param, values.toArray());
}
URI newRequestURI = uriBuilder.path(newPath).build();
log.info("Redirect to URI path '{}'", newRequestURI.toString());
reqContext.setRequestUri(newRequestURI);
}
}
/**
* Filter.
*
* @param requestContext the request context
* @param responseContext the response context
* @throws IOException Signals that an I/O exception has occurred.
*/
/*
* (non-Javadoc)
*
* @see
* javax.ws.rs.container.ContainerResponseFilter#filter(javax.ws.rs.container.
* ContainerRequestContext, javax.ws.rs.container.ContainerResponseContext)
*/
@Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
log.info(RequestHandler.class.getSimpleName() +" Response called");
log.info(RequestHandler.class.getSimpleName() + " Response called");
SecurityTokenProvider.instance.reset();
ScopeProvider.instance.reset();
log.info("Token and Scope Provider reset called");
log.debug("Token and Scope Provider reset called");
resetScretManager(requestContext, responseContext);
}
/**
* Reset scret manager.
*
* @param requestContext the request context
* @param responseContext the response context
* @throws IOException Signals that an I/O exception has occurred.
*/
private void resetScretManager(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
log.debug("SecreteManager instance remove");
SecretManagerProvider.instance.remove();
}
/**
* Sets the secret manager.
*
* @param requestContext the new secret manager
* @throws IOException Signals that an I/O exception has occurred.
*/
private void setSecretManager(ContainerRequestContext requestContext) throws IOException {
log.debug("setSecretManager called");
SecretManagerProvider.instance.remove();
SecretManager secretManager = new SecretManager();
String token = AccessTokenProvider.instance.get();
if (token != null) {
Secret secret = new JWTSecret(token);
secretManager.addSecret(secret);
}
token = SecurityTokenProvider.instance.get();
if (token != null) {
Secret secret = new GCubeSecret(token);
secretManager.addSecret(secret);
}
SecretManagerProvider.instance.set(secretManager);
}
}

View File

@ -1,5 +1,8 @@
package org.gcube.datatransfer.resolver.services;
import java.io.InputStream;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@ -9,17 +12,23 @@ 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.TargetAppGeoportalCodes;
import org.gcube.datatransfer.resolver.services.GeoportalResolver.RESOLVE_AS;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -59,11 +68,13 @@ public class GeoportalExporter {
*/
@GET
@Path("/export/{type}/{usecase_id}/{project_id}")
@Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
@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);
@ -71,7 +82,7 @@ public class GeoportalExporter {
checkPathParameterNotNull(req, PATH_USECASE_ID, ucdID);
checkPathParameterNotNull(req, PATH_PROJECT_ID, projectID);
ACCEPTED_EXPORT_TYPE exportType = toKnowExportType(req, export_type);
checkExportType(req, export_type);
try {
@ -87,43 +98,56 @@ public class GeoportalExporter {
Geoportal_PDF_Exporter pdfExporter = new Geoportal_PDF_Exporter();
boolean checked = pdfExporter.checkConfig();
if (!checked)
return Response.status(Status.NOT_FOUND).build();
ExporterProjectSource exporterSource = new ExporterProjectSource();
RESOLVE_AS resolveAs = RESOLVE_AS.PUBLIC;
// #NB SET USERNAME = null to export with PUBLIC ACCESS
if (!cm.getUser().isApplication()) {
// here the token is of an user
resolveAs = RESOLVE_AS.PRIVATE;
exporterSource.setAccountname(user.getUsername());
}
exporterSource.setScope(context);
exporterSource.setProjectID(projectID);
exporterSource.setProfileID(ucdID);
String vreName = context.substring(context.lastIndexOf("/") + 1, context.length());
LOG.info("Requesting gis link to vre {}", vreName);
Response theReponseWithLink = new GeoportalResolver().genericResolveLink(req,
TargetAppGeoportalCodes.GEO_DV, vreName, ucdID, projectID, resolveAs.name());
String theGisLink = theReponseWithLink.getEntity().toString();
LOG.info("Gis link returned {}", theGisLink);
exporterSource.setGisLink(theGisLink);
// ExporterProjectSource exporterSource = new ExporterProjectSource();
// RESOLVE_AS resolveAs = RESOLVE_AS.PUBLIC;
// // #NB SET USERNAME = null to export with PUBLIC ACCESS
// if (!cm.getUser().isApplication()) {
// // here the token is of an user
// resolveAs = RESOLVE_AS.PRIVATE;
// exporterSource.setAccountname(user.getUsername());
// }
//
// exporterSource.setScope(context);
// exporterSource.setProjectID(projectID);
// exporterSource.setProfileID(ucdID);
//
// String vreName = context.substring(context.lastIndexOf("/") + 1, context.length());
// LOG.info("Requesting gis link to vre {}", vreName);
// Response theReponseWithLink = new GeoportalResolver().genericResolveLink(req,
// TargetAppGeoportalCodes.GEO_DV, vreName, ucdID, projectID, resolveAs.name());
// String theGisLink = theReponseWithLink.getEntity().toString();
// LOG.info("Gis link returned {}", theGisLink);
// exporterSource.setGisLink(theGisLink);
if (checked) {
String entity = entityHTMLMessage("Exporting as PDF...", "The project with " + projectID, true);
return Response.ok(entity).encoding("UTF-8").header(ConstantsResolver.CONTENT_TYPE, "text/html")
.build();
String pdfURL = null;
if (userAgentName != null) {
LOG.info("Serving request as User-Agent {}", userAgentName);
String entity = entityHTMLMessage("Exporting as PDF...", "The project with " + projectID, true);
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 = new URL(pdfURL).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();
}
} else {
return Response.status(Status.NOT_FOUND).entity(GeoportalExporter.class.getSimpleName() + " Config KO")
return Response.status(Status.NOT_FOUND)
.entity(GeoportalExporter.class.getSimpleName() + " Config not found in the context " + context)
.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",
LOG.error("Error on performing export", e);
throw ExceptionManager.internalErrorException(req, "Sorry, error occurred when exporting the project",
this.getClass(), helpURI);
}
@ -131,7 +155,7 @@ public class GeoportalExporter {
@GET
@Path("/export/{type}/healthcheck")
@Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
@Produces({ MediaType.TEXT_HTML, MediaType.TEXT_PLAIN })
public Response healthcheck(@Context HttpServletRequest req, @PathParam(EXPORT_TYPE) String export_type)
throws WebApplicationException {
@ -140,7 +164,7 @@ public class GeoportalExporter {
checkPathParameterNotNull(req, EXPORT_TYPE, export_type);
ACCEPTED_EXPORT_TYPE exportType = toKnowExportType(req, export_type);
checkExportType(req, export_type);
try {
@ -183,8 +207,7 @@ public class GeoportalExporter {
}
}
public ACCEPTED_EXPORT_TYPE toKnowExportType(HttpServletRequest req, String export_type)
throws BadRequestException {
public ACCEPTED_EXPORT_TYPE checkExportType(HttpServletRequest req, String export_type) throws BadRequestException {
ACCEPTED_EXPORT_TYPE exportType;
try {
exportType = ACCEPTED_EXPORT_TYPE.valueOf(export_type);
@ -199,10 +222,71 @@ public class GeoportalExporter {
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());
}
}
/**
* Sets the HTML message.
*
* @param action the action
* @param action the action
* @param message the message
* @param waiting the waiting
* @return the string
@ -210,38 +294,22 @@ public class GeoportalExporter {
protected String entityHTMLMessage(String action, String message, boolean waiting) {
String html = "<html>" + "<head>" + "<meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">"
+ "<style>"
+ "html, body {\n"
+ " margin: 10px;\n"
+ " width: 100%;\n"
+ " height: 100%;\n"
+ " display: table\n"
+ "}\n"
+ "#content {\n"
+ " position: absolute;\n"
+ " left: 50%;\n"
+ " top: 50%;\n"
+ " -webkit-transform: translate(-50%, -50%);\n"
+ " transform: translate(-50%, -50%);\n"
+ " text-align: center;\n"
+ "}"
+ "#message {\n"
+ " color:gray;"
+ " font-size: 24px;"
+ "}"
+ "</style>"
+ "<style>" + "html, body {\n" + " margin: 10px;\n" + " width: 100%;\n" + " height: 100%;\n"
+ " display: table\n" + "}\n" + "#content {\n" + " position: absolute;\n" + " left: 50%;\n"
+ " top: 50%;\n" + " -webkit-transform: translate(-50%, -50%);\n"
+ " transform: translate(-50%, -50%);\n" + " text-align: center;\n" + "}" + "#message {\n"
+ " color:gray;" + " font-size: 24px;" + "}" + "</style>"
+ "<title>D4Science Geoportal - Action</title>" + "</head>" + "<body>";
html += "<img alt=\"D4Science Logo\" src=\"https://services.d4science.org/image/layout_set_logo?img_id=32727\"><br />";
html += "<div id=\"content\">";
html += "<p style=\"font-size: 18px;\">" + action + "</p>";
if (waiting) {
html += "<img alt=\"D4Science Geoportal Loading...\" src=\"img/loading-gears.gif\"><br />";
}
html += "<br/><p id=\"message\">" + message + "</p>";
html += "</div></body></html>";