org.intermine.webservice.server.WebService.java Source code

Java tutorial

Introduction

Here is the source code for org.intermine.webservice.server.WebService.java

Source

package org.intermine.webservice.server;

/*
 * Copyright (C) 2002-2013 FlyMine
 *
 * This code may be freely distributed and modified under the
 * terms of the GNU Lesser General Public Licence.  This should
 * be distributed with the code.  See the LICENSE file for more
 * information or http://www.gnu.org/copyleft/lesser.html.
 *
 */

import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.intermine.api.InterMineAPI;
import org.intermine.api.profile.Profile;
import org.intermine.api.profile.ProfileManager;
import org.intermine.api.profile.ProfileManager.ApiPermission;
import org.intermine.api.profile.ProfileManager.AuthenticationException;
import org.intermine.util.PropertiesUtil;
import org.intermine.util.StringUtil;
import org.intermine.web.context.InterMineContext;
import org.intermine.web.logic.RequestUtil;
import org.intermine.web.logic.export.Exporter;
import org.intermine.web.logic.export.ResponseUtil;
import org.intermine.web.logic.profile.LoginHandler;
import org.intermine.webservice.server.exceptions.BadRequestException;
import org.intermine.webservice.server.exceptions.InternalErrorException;
import org.intermine.webservice.server.exceptions.MissingParameterException;
import org.intermine.webservice.server.exceptions.NotAcceptableException;
import org.intermine.webservice.server.exceptions.ServiceException;
import org.intermine.webservice.server.exceptions.ServiceForbiddenException;
import org.intermine.webservice.server.exceptions.UnauthorizedException;
import org.intermine.webservice.server.output.CSVFormatter;
import org.intermine.webservice.server.output.HTMLTableFormatter;
import org.intermine.webservice.server.output.JSONCountFormatter;
import org.intermine.webservice.server.output.JSONFormatter;
import org.intermine.webservice.server.output.JSONObjectFormatter;
import org.intermine.webservice.server.output.JSONResultFormatter;
import org.intermine.webservice.server.output.JSONRowFormatter;
import org.intermine.webservice.server.output.JSONTableFormatter;
import org.intermine.webservice.server.output.MemoryOutput;
import org.intermine.webservice.server.output.Output;
import org.intermine.webservice.server.output.PlainFormatter;
import org.intermine.webservice.server.output.StreamedOutput;
import org.intermine.webservice.server.output.TabFormatter;
import org.intermine.webservice.server.output.XMLFormatter;

/**
 *
 * Base class for web services. See methods of class to be able implement
 * subclass. <h3>Output</h3> There can be 3 types of output:
 * <ul>
 * <li>Only Error output
 * <li>Complete results - xml, tab separated, html
 * <li>Incomplete results - error messages are appended at the end
 * </ul>
 *
 * <h3>Web service design</h3>
 * <ul>
 * <li>Request is parsed with corresponding RequestProcessor class and returned
 * as a corresponding Input class.
 * <li>Web services are subclasses of WebService class.
 * <li>Web services use implementations of Output class to print results.
 * <li>Request parameter names are constants in corresponding
 * RequestProcessorBase subclass.
 * <li>Servlets are used only for forwarding to corresponding web service, that
 * is created always new. With this implementation fields of new service are
 * correctly initialized and there don't stay values from previous requests.
 * </ul>
 * For using of web services see InterMine wiki pages.
 *
 * @author Jakub Kulaviak
 * @author Alex Kalderimis
 * @version
 */
public abstract class WebService {

    /** Default jsonp callback **/
    public static final String DEFAULT_CALLBACK = "callback";

    private static final String COMPRESS = "compress";
    private static final String GZIP = "gzip";
    private static final String ZIP = "zip";

    private static final Logger LOG = Logger.getLogger(WebService.class);
    private static final String AUTHENTICATION_FIELD_NAME = "Authorization";
    private static final String AUTH_TOKEN_PARAM_KEY = "token";
    private static final Profile ANON_PROFILE = new AnonProfile();

    /**
     * Constants for property keys in global property configuration.
     */
    private static final String WS_HEADERS_PREFIX = "ws.response.header";
    private static final String BOTS = "ws.robots";
    private static final String WEB_SERVICE_DISABLED_PROPERTY = "webservice.disabled";

    /**
     * The servlet request.
     */
    protected HttpServletRequest request;

    /**
     * The servlet response.
     */
    protected HttpServletResponse response;

    /**
     * The response to the outside world.
     */
    protected Output output;

    /**
     * The configuration object.
     */
    protected final InterMineAPI im;

    /** The properties this mine was configured with **/
    protected final Properties webProperties = InterMineContext.getWebProperties();

    private ApiPermission permission = ProfileManager.getDefaultPermission(ANON_PROFILE);
    private boolean initialised = false;
    private String propertyNameSpace = null;

    /**
     * Return the permission object representing the authorisation state of the
     * request. This is guaranteed to not be null.
     *
     * @return A permission object, from which a service may inspect the level
     *         of authorisation, and retrieve details about whom the request is
     *         authorised for.
     */
    protected ApiPermission getPermission() {
        if (permission == null) {
            throw new IllegalStateException("There should always be a valid permission object");
        }
        return permission;
    }

    /**
     * Get a parameter this service deems to be required.
     *
     * @param name
     *            The name of the parameter
     * @return The value of the parameter. Never null, never blank.
     * @throws MissingParameterException
     *             If the value of the parameter is blank or null.
     */
    protected String getRequiredParameter(String name) throws MissingParameterException {
        String value = request.getParameter(name);
        if (StringUtils.isBlank(value)) {
            throw new MissingParameterException(name);
        }
        return value;
    }

    /**
     * Get a parameter this service deems to be optional, or the default value.
     *
     * @param name
     *            The name of the parameter.
     * @param defaultValue
     *            The default value.
     * @return The value provided, if there is a non-blank one, or the default
     *         value.
     */
    protected String getOptionalParameter(String name, String defaultValue) {
        String value = request.getParameter(name);
        if (StringUtils.isBlank(value)) {
            return defaultValue;
        }
        return value;
    }

    /**
     * Get a profile that is a true authenticated user that exists in the
     * database.
     *
     * @return The user's profile.
     * @throws ServiceForbiddenException
     *             if this request resolves to an unauthenticated profile.
     */
    protected Profile getAuthenticatedUser() throws ServiceForbiddenException {
        Profile profile = getPermission().getProfile();
        if (profile.isLoggedIn()) {
            return profile;
        }
        throw new ServiceForbiddenException("You must be logged in to use this service");
    }

    /**
     * Get a parameter this service deems to be optional, or <code>null</code>.
     *
     * @param name
     *            The name of the parameter.
     * @return The value of the parameter, or <code>null</code>
     */
    protected String getOptionalParameter(String name) {
        return getOptionalParameter(name, null);
    }

    /**
     * Get the value of a parameter that should be interpreted as an integer.
     *
     * @param name The name of the parameter.
     * @return An integer
     * @throws BadRequestException if The value is absent or mal-formed.
     */
    protected Integer getIntParameter(String name) {
        String value = getRequiredParameter(name);
        try {
            return Integer.valueOf(value);
        } catch (NumberFormatException e) {
            String msg = String.format("%s should be a valid number. Got %s", name, value);
            throw new BadRequestException(msg, e);
        }
    }

    /**
     * Get the value of a parameter that should be interpreted as an integer.
     *
     * @param name The name of the parameter.
     * @param defaultValue The value to return if none is provided by the user.
     * @return An integer
     * @throw BadRequestException if the user provided a mal-formed value.
     */
    protected Integer getIntParameter(String name, Integer defaultValue) {
        try {
            return getIntParameter(name);
        } catch (MissingParameterException e) {
            return defaultValue;
        }
    }

    /**
     * Set the default name-space for configuration property look-ups.
     *
     * If a value is set, it must be provided before any actions are taken. This
     * means this property must be set before the execute method is called.
     *
     * @param namespace
     *            The name space to use (eg: "some.namespace"). May not be null.
     */
    protected void setNameSpace(String namespace) {
        if (namespace == null || namespace.endsWith(".")) {
            throw new IllegalArgumentException("Namespace must be a non-null string, and may "
                    + "not terminate in a period. Value was: " + namespace);
        }
        if (initialised) {
            throw new IllegalStateException("Name space must be set prior to, or as part of, " + "initialisation.");
        }

        propertyNameSpace = namespace;
    }

    /**
     * Get a configuration property by name.
     *
     * @param name
     *            The name of the property to retrieve.
     * @return A configuration value.
     */
    protected String getProperty(String name) {
        if (StringUtils.contains(name, '.')) {
            return webProperties.getProperty(name);
        }
        return webProperties.getProperty(propertyNameSpace == null ? name : propertyNameSpace + "." + name);
    }

    /**
     * Construct the web service with the InterMine API object that gives access
     * to the core InterMine functionality.
     *
     * @param im
     *            the InterMine application
     */
    public WebService(InterMineAPI im) {
        this.im = im;
    }

    // TODO:
    // Change the API to:
    // new WebService(Req, Resp)
    // Get rid of servlets - move to single dispatcher.

    /**
     * Starting method of web service. The web service should be run like
     *
     * <pre>
     * new ListsService().service(request, response);
     * </pre>
     *
     * Ensures initialisation of web service and makes steps common for all web
     * services and after that executes the <tt>execute</tt> method, for which
     * each subclass must provide an implementation.
     *
     * @param request
     *            The request, as received by the servlet.
     * @param response
     *            The response, as handled by the servlet.
     */
    public void service(HttpServletRequest request, HttpServletResponse response) {
        this.request = request;
        this.response = response;

        try {
            if (agentIsRobot()) {
                response.sendError(Output.SC_FORBIDDEN);
            } else {
                setHeaders();
                initState();
                initOutput();
                checkEnabled();
                authenticate();
                initialised = true;
                postInit();
                validateState();
                execute();
            }
        } catch (Throwable t) {
            sendError(t, response);
        }

        try {
            if (output == null) {
                response.flushBuffer();
            } else {
                output.flush();
            }
        } catch (Throwable t) {
            logError(t, "Error flushing", 500);
        }

        try {
            cleanUp();
        } catch (Throwable t) {
            LOG.error("Error cleaning up", t);
        }

    }

    private boolean agentIsRobot() {
        String ua = request.getHeader("User-Agent");
        if (ua != null) {
            ua = ua.toLowerCase();
            String[] robots = StringUtils.split(webProperties.getProperty(BOTS, ""), ',');
            for (String bot : robots) {
                if (ua.contains(bot.trim())) {
                    return true;
                }
            }
        }
        return false;
    }

    private void setHeaders() {
        Properties headerProps = PropertiesUtil.getPropertiesStartingWith(WS_HEADERS_PREFIX, webProperties);
        for (Object o : headerProps.values()) {
            String h = o.toString();
            String[] parts = StringUtils.split(h, ":", 2);
            if (parts.length != 2) {
                LOG.warn("Ignoring invalid response header: " + h);
            } else {
                response.setHeader(parts[0].trim(), parts[1].trim());
            }
        }
    }

    private void checkEnabled() {
        if ("true".equalsIgnoreCase(webProperties.getProperty(WEB_SERVICE_DISABLED_PROPERTY))) {
            throw new ServiceForbiddenException("Web service is disabled.");
        }
    }

    /**
     * Subclasses may put clean-up code here, to be run after the request has
     * been executed.
     */
    protected void cleanUp() {
        // No-op stub.
    }

    /**
     * Subclasses can put initialisation here.
     */
    protected void initState() {
        // No-op stub
    }

    /**
     * Subclasses can put initialisation checks here. The main use case is for
     * confirming authentication.
     */
    protected void validateState() {
        // No-op stub
    }

    /**
     * Subclasses can hook in here to do common behaviour that needs to happen
     * after initialisation.
     */
    protected void postInit() {
        // No-op stub;
    }

    /**
     * If user name and password is specified in request, then it setups user
     * profile in session. User was authenticated. It uses HTTP basic access
     * authentication.
     * {@link "http://en.wikipedia.org/wiki/Basic_access_authentication"}
     */
    private void authenticate() {

        String authToken = request.getParameter(AUTH_TOKEN_PARAM_KEY);
        final String authString = request.getHeader(AUTHENTICATION_FIELD_NAME);
        final ProfileManager pm = im.getProfileManager();

        if (StringUtils.isEmpty(authToken) && StringUtils.isEmpty(authString)) {
            return; // Not Authenticated.
        }
        // Accept tokens passed in the Authorization header.
        if (StringUtils.isEmpty(authToken) && StringUtils.startsWith(authString, "Token ")) {
            authToken = StringUtils.removeStart(authString, "Token ");
        }

        try {
            // Use a token if provided.
            if (StringUtils.isNotEmpty(authToken)) {
                permission = pm.getPermission(authToken, im.getClassKeys());
            } else {
                // Try and read the authString as a basic auth header.
                // Strip off the "Basic" part - but don't require it.
                final String encoded = StringUtils.removeStart(authString, "Basic ");
                final String decoded = new String(Base64.decodeBase64(encoded.getBytes()));
                final String[] parts = decoded.split(":", 2);
                if (parts.length != 2) {
                    throw new UnauthorizedException(
                            "Invalid request authentication. " + "Authorization field contains invalid value. "
                                    + "Decoded authorization value: " + parts[0]);
                }
                final String username = StringUtils.lowerCase(parts[0]);
                final String password = parts[1];

                permission = pm.getPermission(username, password, im.getClassKeys());
            }
        } catch (AuthenticationException e) {
            throw new UnauthorizedException(e.getMessage());
        }

        LoginHandler.setUpPermission(im, permission);
    }

    private void sendError(Throwable t, HttpServletResponse response) {

        String msg = WebServiceConstants.SERVICE_FAILED_MSG;
        boolean showAllMsgs = webProperties.containsKey("i.am.a.dev");

        int code;
        if (t instanceof ServiceException) {
            code = ((ServiceException) t).getHttpErrorCode();
        } else {
            code = Output.SC_INTERNAL_SERVER_ERROR;
        }
        String realMsg = t.getMessage();
        if ((showAllMsgs || code < 500) && !StringUtils.isBlank(realMsg)) {
            msg = realMsg;
        }
        logError(t, realMsg, code);
        if (!formatIsJSONP()) {
            // Don't set errors statuses on jsonp requests, to enable
            // better error checking in the browser.
            response.setStatus(code);
        } else {
            // But do set callbacks
            String callback = getCallback();
            if (callback == null) {
                callback = "makeInterMineResultsTable";
            }
            Map<String, Object> attributes = new HashMap<String, Object>();
            attributes.put(JSONResultFormatter.KEY_CALLBACK, callback);
            output.setHeaderAttributes(attributes);
        }
        if (output != null) {
            output.setError(msg, code);
        }
        LOG.debug("Set error to : " + msg + "," + code);
    }

    private void logError(Throwable t, String msg, int code) {

        // Stack traces for all!
        ByteArrayOutputStream b = new ByteArrayOutputStream();
        PrintStream ps = new PrintStream(b);
        t.printStackTrace(ps);
        ps.flush();

        if (code == Output.SC_INTERNAL_SERVER_ERROR) {
            LOG.error("Service failed by internal error. Request parameters: \n" + requestParametersToString()
                    + b.toString());
        } else {
            LOG.debug("Service didn't succeed. It's not an internal error. " + "Reason: "
                    + getErrorDescription(msg, code) + "\n" + b.toString());
        }
    }

    private String requestParametersToString() {
        StringBuilder sb = new StringBuilder();
        Map<String, String[]> map = request.getParameterMap();
        for (String name : map.keySet()) {
            for (String value : map.get(name)) {
                sb.append(name);
                sb.append(": ");
                sb.append(value);
                sb.append("\n");
            }
        }
        return sb.toString();
    }

    private String getErrorDescription(String msg, int errorCode) {
        StringBuilder sb = new StringBuilder();
        sb.append(StatusDictionary.getDescription(errorCode));
        sb.append(" ");
        sb.append(msg);
        return sb.toString();
    }

    /**
     * @return Whether or not the requested result format is one of our JSON
     *         formats.
     */
    protected final boolean formatIsJSON() {
        return Format.JSON_FORMATS.contains(getFormat());
    }

    /**
     * @return Whether or not the format is a JSON-P format
     */
    protected final boolean formatIsJSONP() {
        if (isJsonP == null) {
            isJsonP = WebServiceRequestParser.isJsonP(request);
        }
        return isJsonP;
    }

    /**
     * @return Whether or not the format is a flat-file format
     */
    protected final boolean formatIsFlatFile() {
        return Format.FLAT_FILES.contains(getFormat());
    }

    /**
     * Returns true if the format requires the count, rather than the full or
     * paged result set.
     *
     * @return a truth value
     */
    // This should not be in the general case.
    //public boolean formatIsCount() {
    //    int format = getFormat();
    //    return (format == Formats.COUNT || format == Formats.JSON_COUNT);
    //}

    /**
     * @return Whether or not the format is XML.
     */
    public boolean formatIsXML() {
        return (getFormat() == Format.XML);
    }

    /**
     * Make the XML output given the HttpResponse's PrintWriter.
     *
     * @param out
     *            The PrintWriter from the HttpResponse.
     * @return An Output that produces good XML.
     */
    protected Output makeXMLOutput(PrintWriter out, String separator) {
        ResponseUtil.setXMLHeader(response, "result.xml");
        return new StreamedOutput(out, new XMLFormatter(), separator);
    }

    /**
     * Make the default JSON output given the HttpResponse's PrintWriter.
     *
     * @param out
     *            The PrintWriter from the HttpResponse.
     * @return An Output that produces good JSON.
     */
    protected Output makeJSONOutput(PrintWriter out, String separator) {
        return new StreamedOutput(out, new JSONFormatter(), separator);
    }

    /**
     * @return Whether or not this request wants gzipped data.
     */
    protected boolean isGzip() {
        return GZIP.equalsIgnoreCase(request.getParameter(COMPRESS));
    }

    /**
     * @return Whether or not this request wants zipped data.
     */
    protected boolean isZip() {
        return ZIP.equalsIgnoreCase(request.getParameter(COMPRESS));
    }

    /**
     * @return Whether or not this request wants uncompressed data.
     */
    protected boolean isUncompressed() {
        return StringUtils.isEmpty(request.getParameter(COMPRESS));
    }

    /**
     * @return the file-name extension for the result-set.
     */
    protected String getExtension() {
        if (isGzip()) {
            return ".gz";
        } else if (isZip()) {
            return ".zip";
        } else {
            return "";
        }
    }

    private PrintWriter out = null;

    /**
     * Get access to the underlying print-writer.
     *
     * Most services should not need this method.
     * @return The raw print-writer.
     */
    protected PrintWriter getRawOutput() {
        return out;
    }

    private void initOutput() {
        final String separator;
        if (RequestUtil.isWindowsClient(request)) {
            separator = Exporter.WINDOWS_SEPARATOR;
        } else {
            separator = Exporter.UNIX_SEPARATOR;
        }
        Format format = getFormat();

        OutputStream os;
        try {
            // set reasonable buffer size
            response.setBufferSize(8 * 1024);
            os = response.getOutputStream();
            if (isGzip()) {
                os = new GZIPOutputStream(os);
            } else if (isZip()) {
                os = new ZipOutputStream(new BufferedOutputStream(os));
            }
            out = new PrintWriter(os);
        } catch (IOException e) {
            throw new InternalErrorException(e);
        }
        // TODO: retrieve the content types from the formats.
        String filename = getDefaultFileName();
        switch (format) {
        case HTML:
            output = new StreamedOutput(out, new HTMLTableFormatter(), separator);
            ResponseUtil.setHTMLContentType(response);
            break;
        case XML:
            output = makeXMLOutput(out, separator);
            break;
        case TSV:
            output = new StreamedOutput(out,
                    new TabFormatter(StringUtils.equals(getProperty("ws.tsv.quoted"), "true")), separator);
            filename = "result.tsv";
            if (isUncompressed()) {
                ResponseUtil.setTabHeader(response, filename);
            }
            break;
        case CSV:
            output = new StreamedOutput(out, new CSVFormatter(), separator);
            filename = "result.csv";
            if (isUncompressed()) {
                ResponseUtil.setCSVHeader(response, filename);
            }
            break;
        case TEXT:
            output = new StreamedOutput(out, new PlainFormatter(), separator);
            if (filename == null) {
                filename = "result.txt";
            }
            filename += getExtension();
            if (isUncompressed()) {
                ResponseUtil.setPlainTextHeader(response, filename);
            }
            break;
        case JSON:
            output = makeJSONOutput(out, separator);
            filename = "result.json";
            if (isUncompressed()) {
                ResponseUtil.setJSONHeader(response, filename, formatIsJSONP());
            }
            break;
        case OBJECTS:
            output = new StreamedOutput(out, new JSONObjectFormatter(), separator);
            filename = "result.json";
            if (isUncompressed()) {
                ResponseUtil.setJSONHeader(response, filename, formatIsJSONP());
            }
            break;
        case TABLE:
            output = new StreamedOutput(out, new JSONTableFormatter(), separator);
            filename = "resulttable.json";
            if (isUncompressed()) {
                ResponseUtil.setJSONHeader(response, filename, formatIsJSONP());
            }
            break;
        case ROWS:
            output = new StreamedOutput(out, new JSONRowFormatter(), separator);
            if (isUncompressed()) {
                ResponseUtil.setJSONHeader(response, "result.json", formatIsJSONP());
            }
            break;
        default:
            output = getDefaultOutput(out, os, separator);
        }
        if (!isUncompressed()) {
            ResponseUtil.setGzippedHeader(response, filename + getExtension());
            if (isZip()) {
                try {
                    ((ZipOutputStream) os).putNextEntry(new ZipEntry(filename));
                } catch (IOException e) {
                    throw new InternalErrorException(e);
                }
            }
        }
    }

    /**
     * @return The default file name for this service. (default = "result.tsv")
     */
    protected String getDefaultFileName() {
        return "result";
    }

    /**
     * Make the default output for this service.
     *
     * @param out
     *            The response's PrintWriter.
     * @param os
     *            The Response's output stream.
     * @return An Output. (default = new StreamedOutput(out, new
     *         TabFormatter()))
     */
    protected Output getDefaultOutput(PrintWriter out, OutputStream os, String separator) {
        output = new StreamedOutput(out, new TabFormatter(), separator);
        ResponseUtil.setTabHeader(response, getDefaultFileName());
        return output;
    }

    /**
     * Returns true if the request wants column headers as well as result rows
     *
     * @return true if the request declares it wants column headers
     */
    public boolean wantsColumnHeaders() {
        String wantsCols = request.getParameter(WebServiceRequestParser.ADD_HEADER_PARAMETER);
        boolean no = (wantsCols == null || wantsCols.isEmpty() || "0".equals(wantsCols));
        return !no;
    }

    /**
     * Get an enum which represents the column header style (path, friendly, or
     * none)
     *
     * @return a column header style
     */
    public ColumnHeaderStyle getColumnHeaderStyle() {
        if (wantsColumnHeaders()) {
            String style = request.getParameter(WebServiceRequestParser.ADD_HEADER_PARAMETER);
            if ("path".equalsIgnoreCase(style)) {
                return ColumnHeaderStyle.PATH;
            } else {
                return ColumnHeaderStyle.FRIENDLY;
            }
        } else {
            return ColumnHeaderStyle.NONE;
        }
    }

    /**
     * @return The default format constant for this service.
     */
    protected Format getDefaultFormat() {
        return Format.EMPTY;
    }

    private Format format = null;
    private Boolean isJsonP = null;

    /**
     * Returns required output format.
     *
     * Cannot be overridden.
     *
     * @return format
     */
    public final Format getFormat() {
        if (format == null) {
            List<Format> askedFor = WebServiceRequestParser.getAcceptableFormats(request);
            if (askedFor.isEmpty()) {
                format = getDefaultFormat();
            } else {
                for (Format acceptable : askedFor) {
                    if (Format.DEFAULT == acceptable) {
                        format = getDefaultFormat();
                        break;
                    }
                    // Serve the first acceptable format.
                    if (canServe(acceptable)) {
                        format = acceptable;
                        break;
                    }
                }
                // Nothing --> NotAcceptable
                if (format == null) {
                    throw new NotAcceptableException();
                }
                // But empty --> default
                if (format == Format.EMPTY) {
                    format = getDefaultFormat();
                }
            }
        }

        return format;
    }

    /**
     * For very picky services, you can just set it yourself, and say "s****w you requester".
     *
     * Use this with caution, and fall-back to getFormat(). Please.
     *
     * @param format The format you have decided this request really wants.
     */
    protected void setFormat(Format format) {
        this.format = format;
    }

    /**
     * Get the value of the callback parameter.
     *
     * @return The value, or null if this request type does not support this.
     */
    public String getCallback() {
        if (formatIsJSONP()) {
            return getOptionalParameter(WebServiceRequestParser.CALLBACK_PARAMETER, DEFAULT_CALLBACK);
        } else {
            return null;
        }
    }

    /**
     * Determine whether a callback was supplied to this request.
     *
     * @return Whether or not a callback was supplied.
     */
    public boolean hasCallback() {
        return getOptionalParameter(WebServiceRequestParser.CALLBACK_PARAMETER) != null;
    }

    /**
     * Runs service. This is abstract method, that must be defined in subclasses
     * and so performs something useful. Standard procedure is overwrite this
     * method in subclasses and let this method to be called from
     * WebService.doGet method that encapsulates logic common for all web
     * services else you can overwrite doGet method in your web service class
     * and manage all the things alone.
     *
     * @throws Exception
     *             if some error occurs
     */
    protected abstract void execute() throws Exception;

    /**
     * @return true if this request has been authenticated to a specific
     *         existing user.
     */
    public boolean isAuthenticated() {
        return getPermission().getProfile() != ANON_PROFILE;
    }

    /**
     * Check whether the format is acceptable.
     *
     * By default returns true. Services with a particular set of accepted
     * formats should override this and check.
     * @param format The format to check.
     * @return whether or not this format is acceptable.
     */
    protected boolean canServe(Format format) {
        return format == getDefaultFormat();
    }

}