/*******************************************************************************
* Australian National University Data Commons
* Copyright (C) 2013 The Australian National University
*
* This file is part of Australian National University Data Commons.
*
* Australian National University Data Commons is free software: you
* can redistribute it and/or modify it under the terms of the GNU
* General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later
* version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package au.edu.anu.datacommons.gateway;
import static java.text.MessageFormat.format;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import au.edu.anu.datacommons.config.Config;
import au.edu.anu.datacommons.config.PropertiesFile;
import au.edu.anu.datacommons.db.DaoException;
import au.edu.anu.datacommons.gateway.logging.LogDao;
import au.edu.anu.datacommons.gateway.logging.WebSvcLog;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.WebResource.Builder;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.api.client.filter.LoggingFilter;
@Path("/")
public class GatewayResource
{
private static final Logger LOGGER = LoggerFactory.getLogger(GatewayResource.class);
private static final Client client = Client.create();
private static final ClientFilter loggingFilter = new LoggingFilter();
private static Properties redirProps;
static
{
try
{
redirProps = new PropertiesFile(new File(Config.DIR, "ws-gateway/redir.properties"));
}
catch (IOException e)
{
LOGGER.error(e.getMessage(), e);
}
}
@Context
private UriInfo uriInfo;
@Context
private HttpServletRequest request;
@Context
private HttpHeaders httpHeaders;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Response doGetRequest()
{
updateLogging();
LogDao logDao = null;
Response resp = null;
try
{
logDao = new LogDao();
WebSvcLog reqLog = generateWebSvcLog();
try
{
logDao.create(reqLog);
}
catch (DaoException e)
{
LOGGER.warn("Unable to create a log entry for GET request.", e);
}
resp = Response.ok("Test", MediaType.TEXT_PLAIN_TYPE).build();
updateLog(reqLog, resp, null);
try
{
logDao.update(reqLog);
}
catch (DaoException e)
{
LOGGER.warn("Unable to update log entry for GET request.", e);
}
}
finally
{
if (logDao != null)
logDao.close();
}
return resp;
}
/**
* Accepts HTTP POST requests with XML data as body, looks up the function name in the lookup properties file, recreates the inbound request as an outbound
* request, sends it to the looked up URL. Then accepts the in bound response, creates an outbound response from the inbound response and forwards the
* outbound response to the client.
*
* @param xmlDoc
* XML document as HTTP request body.
* @return Recreated Response object from ClientResponse.
*/
@POST
@Produces(MediaType.APPLICATION_XML)
public Response doPostRequest(Document xmlDoc)
{
updateLogging();
Response resp = null;
String function = xmlDoc.getDocumentElement().getAttribute("function");
LogDao logDao = null;
try
{
logDao = new LogDao();
WebSvcLog reqLog = generateWebSvcLog(function, xmlDoc);
logDao.create(reqLog);
long rid = reqLog.getId();
WebResource redirRes = client.resource(UriBuilder.fromPath(redirProps.getProperty(function)).queryParam("rid", rid).build());
// Add HTTP headers to the generic service resource object.
Builder reqBuilder = redirRes.accept(MediaType.APPLICATION_XML_TYPE);
reqBuilder = addHttpHeaders(reqBuilder);
ClientResponse respFromRedirRes = reqBuilder.post(ClientResponse.class, xmlDoc);
// Generate an outbound response object from the inbound response object received from the redirected URL.
try
{
Document respDoc = respFromRedirRes.getEntity(Document.class);
respDoc.getDocumentElement().setAttribute("rid", Long.toString(rid));
resp = Response.status(respFromRedirRes.getStatus()).type(respFromRedirRes.getType()).entity(respDoc).build();
updateLog(reqLog, resp, getXmlAsString(respDoc));
}
catch (Exception e)
{
String respBodyFromRedirRes = respFromRedirRes.getEntity(String.class);
resp = Response.status(respFromRedirRes.getStatus()).type(respFromRedirRes.getType()).entity(respBodyFromRedirRes).build();
updateLog(reqLog, resp, respBodyFromRedirRes);
}
try
{
logDao.update(reqLog);
}
catch (DaoException e)
{
LOGGER.warn("Unable to update log entry for POST request.", e);
}
}
catch (Exception e)
{
resp = Response.status(Status.INTERNAL_SERVER_ERROR).entity(e.getMessage()).build();
}
finally
{
if (logDao != null)
logDao.close();
}
return resp;
}
private Builder addHttpHeaders(Builder reqBuilder)
{
boolean userAgentExists = false;
MultivaluedMap<String, String> headersMap = httpHeaders.getRequestHeaders();
for (String key : headersMap.keySet())
{
for (String value : headersMap.get(key))
{
if (!key.equalsIgnoreCase("user-agent"))
reqBuilder = reqBuilder.header(key, value);
else
{
// Append 'DataCommons' in the user agent so the service can recognize that it's not a request from a web browser.
reqBuilder = reqBuilder.header(key, value + " DataCommons");
userAgentExists = true;
}
}
}
// If header doesn't have a user agent key at all, add it.
if (!userAgentExists)
reqBuilder = reqBuilder.header("user-agent", "DataCommons");
return reqBuilder;
}
private void updateLogging()
{
if (redirProps.getProperty("http.logging", "false").equalsIgnoreCase("true"))
{
if (!client.isFilterPreset(loggingFilter))
client.addFilter(loggingFilter);
}
else
{
if (client.isFilterPreset(loggingFilter))
client.removeFilter(loggingFilter);
}
}
private UriBuilder addQueryParams(UriBuilder uriBuilder)
{
Set<Entry<String, List<String>>> queryParamsSet = uriInfo.getQueryParameters().entrySet();
for (Entry<String, List<String>> queryParam : queryParamsSet)
for (String queryParamValue : queryParam.getValue())
uriBuilder = uriBuilder.queryParam(queryParam.getKey(), queryParamValue);
return uriBuilder;
}
private WebSvcLog generateWebSvcLog()
{
StringBuilder requestStr = new StringBuilder();
UriBuilder uriBuilder = uriInfo.getRequestUriBuilder();
uriBuilder = addQueryParams(uriBuilder);
requestStr.append(format("{0} {1}", request.getMethod().toUpperCase(), uriBuilder.build()));
requestStr.append(Config.NEWLINE);
for (Entry<String, List<String>> iHeader : httpHeaders.getRequestHeaders().entrySet())
{
for (String value : iHeader.getValue())
requestStr.append(format("{0}: {1}", iHeader.getKey(), iHeader.getKey().equalsIgnoreCase("authorization") ? "" : value));
requestStr.append(Config.NEWLINE);
}
WebSvcLog webSvcLog = new WebSvcLog(requestStr.toString(), request.getRemoteAddr(), null);
return webSvcLog;
}
private WebSvcLog generateWebSvcLog(String function, Document xmlDoc)
{
WebSvcLog webSvcLog = generateWebSvcLog();
StringBuilder requestStr = new StringBuilder(webSvcLog.getRequest());
requestStr.append(Config.NEWLINE);
requestStr.append(getXmlAsString(xmlDoc));
webSvcLog.setRequest(requestStr.toString());
webSvcLog.setFunction(function);
return webSvcLog;
}
private void updateLog(WebSvcLog reqLog, Response resp, String body)
{
StringBuilder respStr = new StringBuilder();
respStr.append(resp.getStatus());
respStr.append(Config.NEWLINE);
for (Entry<String, List<Object>> iMd : resp.getMetadata().entrySet())
{
for (Object val : iMd.getValue())
respStr.append(format("{0}: {1}", iMd.getKey(), val.toString()));
}
if (body != null)
{
respStr.append(Config.NEWLINE);
respStr.append(Config.NEWLINE);
respStr.append(body);
}
reqLog.addResponse(respStr.toString());
}
public void writeXmlToWriter(Document inDoc, Writer xmlWriter) throws TransformerFactoryConfigurationError, TransformerException
{
Transformer transformer;
transformer = TransformerFactory.newInstance().newTransformer();
DOMSource source = new DOMSource(inDoc);
transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty(OutputKeys.METHOD, "xml");
transformer.transform(source, new StreamResult(xmlWriter));
return;
}
public String getXmlAsString(Document inDoc)
{
StringWriter stringWriter = new StringWriter();
try
{
writeXmlToWriter(inDoc, stringWriter);
}
catch (Exception e)
{
LOGGER.warn("Unable to convert XML to String.", e);
}
return stringWriter.toString();
}
}