/** * Copyright 2005-2014 Restlet * * The contents of this file are subject to the terms of one of the following * open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can * select the license that you prefer but you may not use this file except in * compliance with one of these Licenses. * * You can obtain a copy of the Apache 2.0 license at * http://www.opensource.org/licenses/apache-2.0 * * You can obtain a copy of the EPL 1.0 license at * http://www.opensource.org/licenses/eclipse-1.0 * * See the Licenses for the specific language governing permissions and * limitations under the Licenses. * * Alternatively, you can obtain a royalty free commercial license with less * limitations, transferable or non-transferable, directly at * http://restlet.com/products/restlet-framework * * Restlet is a registered trademark of Restlet S.A.S. */ package org.restlet.service; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import org.restlet.Application; import org.restlet.Context; import org.restlet.Request; import org.restlet.Response; import org.restlet.data.Reference; import org.restlet.data.Status; import org.restlet.engine.application.StatusInfo; import org.restlet.representation.Representation; import org.restlet.representation.Variant; import org.restlet.resource.Resource; import org.restlet.resource.ResourceException; /** * Service to handle error statuses. If an exception is thrown within your * application or Restlet code, it will be intercepted by this service if it is * enabled.<br> * <br> * When an exception or an error is caught, the * {@link #getStatus(Throwable, Request, Response)} method is first invoked to * obtain the status that you want to set on the response. If this method isn't * overridden or returns null, the {@link Status#SERVER_ERROR_INTERNAL} constant * will be set by default.<br> * <br> * Also, when the status of a response returned is an error status (see * {@link Status#isError()}, the * {@link #getRepresentation(Status, Request, Response)} method is then invoked * to give your service a chance to override the default error page.<br> * <br> * If you want to customize the default behavior, you need to create a subclass * of StatusService that overrides some or all of the methods mentioned above. * Then, just create a instance of your class and set it on your Component or * Application via the setStatusService() methods.<br> * <br> * In case the response's entity has already been set, the status service does * not generate an error representation. You can turn off this default behavior * by calling the {@link #setOverwriting(boolean)} method. * * @see <a href="http://wiki.restlet.org/docs_2.2/202-restlet.html">User * Guide</a> * @author Jerome Louvel */ public class StatusService extends Service { // [ifndef gwt] member /** The service used to select the preferred variant. */ private volatile ConnegService connegService; /** The email address to contact in case of error. */ private volatile String contactEmail; // [ifndef gwt] member /** The service used to convert between status/throwable and representation. */ private volatile ConverterService converterService; /** The home URI to propose in case of error. */ private volatile Reference homeRef; // [ifndef gwt] member /** The service used to select the preferred variant. */ private volatile MetadataService metadataService; /** True if an existing entity should be overwritten. */ private volatile boolean overwriting; /** * Constructor. By default, it creates the necessary services. */ public StatusService() { this(true); } /** * Constructor. By default, it creates the necessary services. * * @param enabled * True if the service has been enabled. * */ public StatusService(boolean enabled) { // [ifndef gwt] instruction this(enabled, new ConverterService(), new MetadataService(), new ConnegService()); // [ifdef gwt] instruction uncomment // super(enabled); // this.homeRef = new Reference("/"); } // [ifndef gwt] method /** * Constructor. * * @param enabled * True if the service has been enabled. * @param converterService * The service used to convert between status/throwable and * representation. * @param metadataService * The service used to select the preferred variant. * @param connegService * The service used to select the preferred variant. */ public StatusService(boolean enabled, ConverterService converterService, MetadataService metadataService, ConnegService connegService) { super(enabled); this.converterService = converterService; this.metadataService = metadataService; this.connegService = connegService; this.contactEmail = null; this.homeRef = new Reference("/"); this.overwriting = false; } // [ifndef gwt] method @Override public org.restlet.routing.Filter createInboundFilter(Context context) { return new org.restlet.engine.application.StatusFilter(context, this); } // [ifndef gwt] method /** * Returns the service used to select the preferred variant. * * @return The service used to select the preferred variant. */ public ConnegService getConnegService() { return connegService; } /** * Returns the email address to contact in case of error. This is typically * used when creating the status representations. * * @return The email address to contact in case of error. */ public String getContactEmail() { return this.contactEmail; } // [ifndef gwt] method /** * Returns the service used to convert between status/throwable and * representation. * * @return The service used to convert between status/throwable and * representation. */ public ConverterService getConverterService() { return converterService; } /** * Returns the home URI to propose in case of error. * * @return The home URI to propose in case of error. */ public Reference getHomeRef() { return this.homeRef; } // [ifndef gwt] method /** * Returns the service used to select the preferred variant. * * @return The service used to select the preferred variant. */ public MetadataService getMetadataService() { return metadataService; } /** * Returns a representation for the given status. In order to customize the * default representation, this method can be overridden. It returns null by * default. * * @param status * The status to represent. * @param request * The request handled. * @param response * The response updated. * @return The representation of the given status. * @deprecated Use {@link #toRepresentation(Status, Request, Response)} * instead. */ @Deprecated public Representation getRepresentation(Status status, Request request, Response response) { Representation result = null; // [ifndef gwt] // Do content negotiation for status if (converterService != null && connegService != null && metadataService != null) { Object representationObject = null; // Serialize exception if any and if {@link // org.restlet.resource.Status} annotation asks for it Throwable cause = status.getThrowable(); if (cause != null) { org.restlet.engine.resource.ThrowableAnnotationInfo tai = org.restlet.engine.resource.AnnotationUtils .getInstance().getThrowableAnnotationInfo( cause.getClass()); if (tai != null && tai.isSerializable()) { if (Application.getCurrent() != null && !Application.getCurrent().isDebugging()) { // We clear the stack trace to prevent technical // information leak cause.setStackTrace(new StackTraceElement[] {}); if (cause.getCause() != null) { Context.getCurrentLogger() .log(Level.WARNING, "The cause of the exception should be null except in debug mode"); } } representationObject = cause; } } try { // Default representation match with the status properties if (representationObject == null) { representationObject = new StatusInfo(status, getContactEmail(), getHomeRef().toString()); } List<org.restlet.engine.resource.VariantInfo> variants = org.restlet.engine.converter.ConverterUtils .getVariants(representationObject.getClass(), null); if (variants == null) { variants = new ArrayList<>(); } Variant variant = connegService.getPreferredVariant(variants, request, metadataService); result = converterService.toRepresentation( representationObject, variant); } catch (Exception e) { Context.getCurrentLogger().log( Level.WARNING, "Could not serialize throwable class " + ((cause == null) ? null : cause.getClass()), e); } } // [enddef] return result; } /** * Returns a status for a given exception or error. By default it unwraps * the status of {@link ResourceException}. For other exceptions or errors, * it returns an {@link Status#SERVER_ERROR_INTERNAL} status.<br> * <br> * In order to customize the default behavior, this method can be * overridden. * * @param throwable * The exception or error caught. * @param request * The request handled. * @param response * The response updated. * @return The representation of the given status. * @deprecated Use {@link #toStatus(Throwable, Request, Response)} instead. */ @Deprecated public Status getStatus(Throwable throwable, Request request, Response response) { Status result; Status defaultStatus = Status.SERVER_ERROR_INTERNAL; Throwable t = throwable; // If throwable is a ResourceException, use its status and the cause. if (throwable instanceof ResourceException) { defaultStatus = ((ResourceException) throwable).getStatus(); if (throwable.getCause() != null && throwable.getCause() != throwable) { t = throwable.getCause(); } } // [ifndef gwt] // look for Status annotation org.restlet.engine.resource.ThrowableAnnotationInfo tai = org.restlet.engine.resource.AnnotationUtils .getInstance().getThrowableAnnotationInfo(t.getClass()); if (tai != null) { result = new Status(tai.getStatus(), t); } else { result = new Status(defaultStatus, t); } // [enddef] // [ifdef gwt] instruction uncomment // result = new Status(defaultStatus, t); return result; } /** * Returns a status for a given exception or error. By default it returns an * {@link Status#SERVER_ERROR_INTERNAL} status and logs a severe message.<br> * In order to customize the default behavior, this method can be * overridden. * * @param throwable * The exception or error caught. * @param resource * The parent resource. * @return The representation of the given status. * @deprecated Use {@link #toStatus(Throwable, Resource)} instead. */ @Deprecated public Status getStatus(Throwable throwable, Resource resource) { return getStatus(throwable, (resource == null) ? null : resource.getRequest(), (resource == null) ? null : resource.getResponse()); } /** * Indicates if an existing entity should be overwritten. False by default. * * @return True if an existing entity should be overwritten. */ public boolean isOverwriting() { return this.overwriting; } // [ifndef gwt] method /** * Sets the service used to select the preferred variant. * * @param connegService * The service used to select the preferred variant. */ public void setConnegService(ConnegService connegService) { this.connegService = connegService; } /** * Sets the email address to contact in case of error. This is typically * used when creating the status representations. * * @param contactEmail * The email address to contact in case of error. */ public void setContactEmail(String contactEmail) { this.contactEmail = contactEmail; } // [ifndef gwt] method /** * Sets the service used to convert between status/throwable and * representation. * * @param converterService * The service used to convert between status/throwable and * representation. */ public void setConverterService(ConverterService converterService) { this.converterService = converterService; } /** * Sets the home URI to propose in case of error. * * @param homeRef * The home URI to propose in case of error. */ public void setHomeRef(Reference homeRef) { this.homeRef = homeRef; } // [ifndef gwt] method /** * Sets the service used to select the preferred variant. * * @param metadataService * The service used to select the preferred variant. */ public void setMetadataService(MetadataService metadataService) { this.metadataService = metadataService; } /** * Indicates if an existing entity should be overwritten. * * @param overwriting * True if an existing entity should be overwritten. */ public void setOverwriting(boolean overwriting) { this.overwriting = overwriting; } /** * Returns a representation for the given status. In order to customize the * default representation, this method can be overridden. It returns a * {@link org.restlet.data.Status} representation by default or a * {@link java.lang.Throwable} representation if the throwable is annotated * with {@link org.restlet.resource.Status}. * * @param status * The status to represent. * @param request * The request handled. * @param response * The response updated. * @return The representation of the given status. */ public Representation toRepresentation(Status status, Request request, Response response) { return getRepresentation(status, request, response); } /** * Returns a representation for the given status.<br> * In order to customize the default representation, this method can be * overridden. By default it invokes * {@link #toRepresentation(Status, Request, Response)} * * @param status * The status to represent. * @param resource * The parent resource. * @return The representation of the given status. */ public Representation toRepresentation(Status status, Resource resource) { return toRepresentation(status, resource.getRequest(), resource.getResponse()); } /** * Returns a status for a given exception or error. By default it unwraps * the status of {@link ResourceException}. For other exceptions or errors, * it returns an {@link Status#SERVER_ERROR_INTERNAL} status.<br> * <br> * In order to customize the default behavior, this method can be * overridden. * * @param throwable * The exception or error caught. * @param request * The request handled. * @param response * The response updated. * @return The representation of the given status. */ public Status toStatus(Throwable throwable, Request request, Response response) { return getStatus(throwable, request, response); } /** * Returns a status for a given exception or error. By default it returns an * {@link Status#SERVER_ERROR_INTERNAL} status and logs a severe message.<br> * In order to customize the default behavior, this method can be * overridden. * * @param throwable * The exception or error caught. * @param resource * The parent resource. * @return The representation of the given status. */ public Status toStatus(Throwable throwable, Resource resource) { return getStatus(throwable, resource); } }