blob: db349fd4f6106fc425bacd911758557b01b7536d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2010-2014 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
*******************************************************************************/
package org.eclipse.skalli.services.extension.rest;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Map;
import java.util.SortedSet;
import org.apache.commons.lang.StringUtils;
import org.eclipse.skalli.model.Issue;
import org.eclipse.skalli.model.ValidationException;
import org.eclipse.skalli.services.BundleProperties;
import org.eclipse.skalli.services.permit.Permit;
import org.eclipse.skalli.services.permit.Permit.Level;
import org.eclipse.skalli.services.permit.Permits;
import org.eclipse.skalli.services.rest.RequestContext;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.representation.Representation;
import org.restlet.resource.ServerResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for the implementation of {@link RestExtensions REST extensions} providing
* convenience methods for creating certain common {@link ErrorRepresentation error representations}
* and accessing request and query attributes.
*/
public abstract class ResourceBase extends ServerResource {
private static final Logger LOG = LoggerFactory.getLogger(ResourceBase.class);
private static final String ERROR_ID_MISSING_AUTHORIZATION = "rest:permit({0}):00"; //$NON-NLS-1$
private RequestContext request;
@Override
protected void doInit() {
super.doInit();
request = new RequestContext(this);
}
/**
* Returns the resource context providing information
* about the request like query attributes and request path.
*/
protected RequestContext getResourceContext() {
return request;
}
/**
* Returns the request path including the context path
* prefix <code>/api</code>.
*/
protected String getPath() {
return request.getPath();
}
/**
* Returns the action of this request.
*/
protected String getAction() {
return request.getAction();
}
/**
* Returns the reference of the requested resource, or <code>null</code>
* if the requested resource could not be determined.
*
* @see org.restlet.Request#getResourceRef().
*/
protected Reference getResourceRef() {
return request.getResourceRef();
}
/**
* Returns the host part of the resource's URL including scheme,
* host name and port number, or <code>null</code> if the host
* could not be determined.
*/
protected String getHost() {
return request.getHost();
}
/**
* Returns the query as form, which may be an {@link Form#Form() empty form}
* if there is no query.
*/
protected Form getQueryAsForm() {
return request.getQueryAsForm();
}
/**
* Returns the query as string, which may be an empty string
* if there is no query.
*/
protected String getQueryString() {
return request.getQueryString();
}
/**
* Returns the value of a given query attribute.
*
* @param name the name of the attribute.
*
* @return the value of the attribute, which may be
* <code>null</code> either if there is no attribute
* with the given name, or the attribute is a boolean
* attribute.
*/
protected String getQueryAttribute(String name) {
return request.getQueryAttribute(name);
}
/**
* Returns <code>true</code> if there is a query attribute
* with the given name.
*
* @param name the name of the attribute.
*/
protected boolean hasQueryAttribute(String name) {
return request.hasQueryAttribute(name);
}
/**
* Returns the query attributes as map.
* @return a mapping of attribute names to their respective values,
* or an empty map if there is no query.
*/
protected Map<String,String> getQueryAttributes() {
return request.getQueryAttributes();
}
/**
* Returns the value of the header matching the given name
* (ignoring the case). If there are multiple headers with
* the same name, only the first one is matched. If there
* is no matching header, the given default value is returned.
*
* @param name the header name.
* @param defaultValue the default value to return in case
* there is no matching header.
*/
protected String getHeader(String name, String defaultValue) {
return request.getHeader(name, defaultValue);
}
/**
* Returns the requested media type.
*/
protected MediaType getMediaType() {
return request.getMediaType();
}
/**
* Returns <code>true</code>, if the requested media type is either
* <tt>text/xml</tt> or <tt>application/json</tt>.
*/
protected boolean isSupportedMediaType() {
return request.isXML() || request.isJSON();
}
/**
* Checks whether the XStream-based REST converters should be used for
* rendering the response of this REST request.
* <p>
* This method checks whether the request has a query attribute named
* <tt>"rest"</tt>, or whether there is a system-wide
* {@link BundleProperties#getProperty(String) bundle/system property}
* named <tt>"skalli.rest"</tt> with a value <i>different</i> from <tt>"v1"</tt>.
* In that case, e.g. the request has a query attribute <tt>"rest=v2"</tt>,
* the method returns <code>false</code> to indicate that the new
* RestWriter-based converters should be employed. Otherwise the
* method returns <code>true</code>.
* <p>
* If the requested media type is different from <tt>"text/xml"</tt>,
* always <code>false</code> will be returned.
*
* @return <code>true</code>, if XStream-based converters should be used
* for rendering the response of this REST request.
*/
@SuppressWarnings("nls")
protected boolean enforceOldStyleConverters() {
if (!request.isXML()) {
return false;
}
String restVersion = getQueryAttribute("rest");
if (StringUtils.isBlank(restVersion)) {
restVersion = BundleProperties.getProperty("skalli.rest");
}
if (StringUtils.isNotBlank(restVersion)) {
return "v1".equalsIgnoreCase(restVersion);
}
return true;
}
/**
* Creates an {@link ErrorRepresentation error representation} for authorization errors
* and writes a corresponding audit log entry (severity WARN). Sets the response status to
* <tt>403 Forbidden</tt>.
*
* @param action the action to perform.
* @param path path of the resource on which the action is to be performed.
*/
protected Representation createUnauthorizedRepresentation() {
String loggedInUser = Permits.getLoggedInUser();
String action = request.getAction();
String path = request.getPath();
String message = StringUtils.isBlank(loggedInUser)?
MessageFormat.format("{0} {1}: Forbidden for anonymous users", action, path) :
MessageFormat.format("{0} {1}: Forbidden for user ''{2}''", action, path, loggedInUser);
String messageId = MessageFormat.format(ERROR_ID_MISSING_AUTHORIZATION,
Permit.toString(action, path, Level.ALLOW));
return createErrorRepresentation(Status.CLIENT_ERROR_FORBIDDEN, messageId, message);
}
/**
* Creates an {@link ErrorRepresentation error representation} for i/o errors
* and writes a corresponding log entry (severity ERROR). Sets the response status to
* <tt>500 Internal Server Error</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param e the exception describing the i/o error.
*/
protected Representation createIOErrorRepresentation(String errorId, IOException e) {
String message = "I/O error when reading the request entity. Please report this error " +
"response to the server administrators.";
LOG.error(MessageFormat.format("{0} ({1})", message, errorId), e);
return createErrorRepresentation(Status.SERVER_ERROR_INTERNAL, errorId, message);
}
/**
* Creates an {@link ErrorRepresentation error representation} for unexpected exceptions
* and writes a corresponding log entry (severity ERROR). Sets the response status to
* <tt>500 Internal Server Error</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param e the exception to wrap as error representation.
*/
protected Representation createUnexpectedErrorRepresentation(String errorId, Exception e) {
String message = "An unexpected exception happend. Please report this error " +
"response to the server administrators.";
LOG.error(MessageFormat.format("{0} ({1})", message, errorId), e);
return createErrorRepresentation(Status.SERVER_ERROR_INTERNAL, errorId, message);
}
/**
* Creates an {@link ErrorRepresentation error representation} for parsing errors
* and writes a corresponding log entry (severity WARN). Sets the response status to
* <tt>400 Bad Request</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param e the exception to wrap as error representation.
*/
protected Representation createParseErrorRepresentation(String errorId, Exception e) {
String message = MessageFormat.format("Failed to parse request entity: {0}", e.getMessage());
LOG.warn(MessageFormat.format("{0} ({1})", message, errorId), e);
return createErrorRepresentation(Status.CLIENT_ERROR_BAD_REQUEST, errorId, message);
}
/**
* Creates an {@link ErrorRepresentation error representation} for validation exceptions
* and writes a corresponding log entry (severity WARN). Sets the response status to
* <tt>400 Bad Request</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param entityId the entity, e.g. a project, that has validation problems.
* @param e the exception to wrap as error representation.
*/
protected Representation createValidationFailedRepresentation(String errorId, String entityId, ValidationException e) {
return createValidationFailedRepresentation(errorId, entityId, e.getIssues());
}
/**
* Creates an {@link ErrorRepresentation error representation} for given set of issues
* and writes a corresponding log entry (severity WARN). Sets the response status to
* <tt>400 Bad Request</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param entityId the entity, e.g. a project, that has validation problems.
* @param issues the issues to report.
*/
protected Representation createValidationFailedRepresentation(String errorId, String entityId, SortedSet<Issue> issues) {
String message = Issue.getMessage(MessageFormat.format(
"Validation of {0} failed", entityId), issues);
LOG.warn(MessageFormat.format("{0} ({1})", message, errorId));
return createErrorRepresentation(Status.CLIENT_ERROR_BAD_REQUEST, errorId, message);
}
/**
* Creates an {@link ErrorRepresentation error representation} for an unavailable service
* and writes a corresponding log entry (severity WARN). Sets the response status to
* <tt>503 Service Unavailable</tt>.
*
* @param errorId a unique identifier for the error that has happened.
* @param serviceName the name of the service that is not available.
*/
protected Representation createServiceUnavailableRepresentation(String errorId, String serviceName) {
String message = MessageFormat.format("Services is not available: {0}", serviceName);
LOG.warn(MessageFormat.format("{0} ({1})", message, errorId));
return createErrorRepresentation(Status.SERVER_ERROR_SERVICE_UNAVAILABLE, errorId, message);
}
/**
* Creates a generic {@link ErrorRepresentation error representation} from a status, an error identifier
* and a message that is composed from a pattern and arguments. Sets the status of the REST response to
* that given as argument, but writes no log entries.
*
* @param status the status assigned to the error.
* @param errorId a unique identifier for the error that has happened.
* @param pattern the error message with wildcards (see {@link MessageFormat#format(String, Object...)}).
* @param arguments arguments to replace the wildcards in the message with.
*/
protected Representation createErrorRepresentation(Status status, String errorId, String pattern, Object... arguments) {
String message = MessageFormat.format(pattern, arguments);
return createErrorRepresentation(status, errorId, message);
}
/**
* Creates a generic {@link ErrorRepresentation error representation} with from a status, an error identifier
* and a message. Sets the status of the REST response to that given as argument, but writes no log entries.
*
* @param status the status assigned to the error.
* @param errorId a unique identifier for the error that has happened.
* @param message the error message.
*/
protected Representation createErrorRepresentation(Status status, String errorId, String message) {
ErrorRepresentation representation = new ErrorRepresentation(request, status, errorId, message);
setStatus(status);
return representation;
}
}