/**
* PODD is an OWL ontology database used for scientific project management
*
* Copyright (C) 2009-2013 The University Of Queensland
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero 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
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*/
package com.github.podd.restlet;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import java.util.Map;
import org.openrdf.model.BNode;
import org.openrdf.model.Model;
import org.openrdf.model.impl.LinkedHashModel;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFHandlerException;
import org.openrdf.rio.Rio;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.CharacterSet;
import org.restlet.data.Language;
import org.restlet.data.MediaType;
import org.restlet.data.Preference;
import org.restlet.data.Status;
import org.restlet.representation.AppendableRepresentation;
import org.restlet.representation.Representation;
import org.restlet.service.StatusService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.ansell.restletutils.RestletUtilMediaType;
import com.github.podd.exception.PoddException;
import com.github.podd.utils.PODD;
import freemarker.template.Configuration;
/**
* This status service is based on the standard Restlet guide for returning custom error pages.
*
* Set this to Application via application.setStatusService().
*
* TODO: create the templates used (i.e. PropertyUtils.PROPERTY_TEMPLATE...)
*
* @author Peter Ansell p_ansell@yahoo.com copied from the OAS project
* (https://github.com/ansell/oas)
*/
public class PoddStatusService extends StatusService
{
private final Logger log = LoggerFactory.getLogger(this.getClass());
private Configuration freemarkerConfiguration;
/**
*
*/
public PoddStatusService(final Configuration freemarkerConfiguration)
{
super();
this.freemarkerConfiguration = freemarkerConfiguration;
}
/**
* @param enabled
*/
public PoddStatusService(final Configuration freemarkerConfiguration, final boolean enabled)
{
super(enabled);
this.freemarkerConfiguration = freemarkerConfiguration;
}
private String convertModelToString(final Model model, final MediaType preferredMediaType)
{
final StringWriter out = new StringWriter();
try
{
Rio.write(model, out, Rio.getWriterFormatForMIMEType(preferredMediaType.getName(), RDFFormat.RDFJSON));
}
catch(final RDFHandlerException e)
{
this.log.error("Error writing RDF content in status service", e);
// We're already trying to send back an error. So ignore this?
// FIXME: The error may mean that the error message is not
// syntactically valid at this
// point, so may need to overwrite it with a hardcoded string
}
return out.toString();
}
private Model getErrorAsModel(final Status status)
{
final Model model = new LinkedHashModel();
final BNode topNode = PODD.VF.createBNode("error");
model.add(topNode, RDF.TYPE, PODD.ERR_TYPE_TOP_ERROR);
model.add(topNode, PODD.HTTP_STATUS_CODE_VALUE, PODD.VF.createLiteral(status.getCode()));
model.add(topNode, PODD.HTTP_REASON_PHRASE, PODD.VF.createLiteral(status.getReasonPhrase()));
final String errorDescription = status.getDescription();
model.add(topNode, RDFS.COMMENT, PODD.VF.createLiteral(errorDescription));
final BNode errorNode = PODD.VF.createBNode("cause");
model.add(topNode, PODD.ERR_CONTAINS, errorNode);
final Throwable throwable = status.getThrowable();
if(throwable != null && throwable instanceof PoddException)
{
model.addAll(((PoddException)throwable).getDetailsAsModel(errorNode));
}
else
{
model.add(errorNode, RDF.TYPE, PODD.ERR_TYPE_ERROR);
if(throwable != null)
{
model.add(errorNode, PODD.ERR_EXCEPTION_CLASS, PODD.VF.createLiteral(throwable.getClass().getName()));
if(throwable.getMessage() != null)
{
model.add(errorNode, RDFS.LABEL, PODD.VF.createLiteral(throwable.getMessage()));
}
final StringWriter sw = new StringWriter();
throwable.printStackTrace(new PrintWriter(sw));
model.add(errorNode, RDFS.COMMENT, PODD.VF.createLiteral(sw.toString()));
}
else
{
model.add(errorNode, RDFS.COMMENT, PODD.VF.createLiteral("Error details unavailable"));
}
}
return model;
}
/*
* (non-Javadoc)
*
* @see org.restlet.service.StatusService#getRepresentation(org.restlet.data. Status,
* org.restlet.Request, org.restlet.Response)
*/
@Override
public Representation getRepresentation(final Status status, final Request request, final Response response)
{
// identify Media Type to send response in
MediaType preferredMediaType = MediaType.TEXT_PLAIN;
float maxQuality = 0;
final List<Preference<MediaType>> acceptedMediaTypes = request.getClientInfo().getAcceptedMediaTypes();
for(final Preference<MediaType> pref : acceptedMediaTypes)
{
final float quality = pref.getQuality();
if(quality > maxQuality)
{
preferredMediaType = pref.getMetadata();
maxQuality = quality;
}
}
Representation representation = null;
if(MediaType.APPLICATION_RDF_XML.equals(preferredMediaType)
|| RestletUtilMediaType.APPLICATION_RDF_JSON.equals(preferredMediaType)
|| MediaType.APPLICATION_JSON.equals(preferredMediaType))
{
representation = this.getRepresentationRdf(status, request, response, preferredMediaType);
}
else
// if (MediaType.TEXT_HTML.equals(preferredMediaType))
{
representation = this.getRepresentationHtml(status, request, response);
}
return representation;
}
/**
* Returns an Error page representation in text/html.
*
*/
private Representation getRepresentationHtml(final Status status, final Request request, final Response response)
{
final Map<String, Object> dataModel = RestletUtils.getBaseDataModel(request);
dataModel.put("contentTemplate", "error.html.ftl");
dataModel.put("pageTitle", "An error occurred : HTTP " + status.getCode());
dataModel.put("error_code", Integer.toString(status.getCode()));
final StringBuilder message = new StringBuilder();
if(status.getDescription() != null)
{
message.append(status.getDescription());
}
if(status.getThrowable() != null && status.getThrowable().getMessage() != null)
{
message.append(" (");
message.append(status.getThrowable().getMessage());
message.append(")");
}
dataModel.put("message", message.toString());
final Model model = this.getErrorAsModel(status);
final String errorModelAsString = this.convertModelToString(model, RestletUtilMediaType.APPLICATION_RDF_JSON);
dataModel.put("message_details", errorModelAsString);
return RestletUtils.getHtmlRepresentation("poddBase.html.ftl", dataModel, MediaType.TEXT_HTML,
this.freemarkerConfiguration);
}
/**
* Returns an Error page representation in RDF/XML.
*
* @param preferredMediaType
*
*/
private Representation getRepresentationRdf(final Status status, final Request request, final Response response,
final MediaType preferredMediaType)
{
final Model model = this.getErrorAsModel(status);
// get a String representation of the statements in the Model
final String modelAsString = this.convertModelToString(model, preferredMediaType);
return new AppendableRepresentation(modelAsString, MediaType.APPLICATION_RDF_XML, Language.DEFAULT,
CharacterSet.UTF_8);
}
}