| /******************************************************************************* |
| * 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; |
| } |
| } |