edu.lternet.pasta.auditmanager.AuditManagerResource.java Source code

Java tutorial

Introduction

Here is the source code for edu.lternet.pasta.auditmanager.AuditManagerResource.java

Source

/*
 *
 * $Date$
 * $Author$
 * $Revision$
 *
 * Copyright 2010 the University of New Mexico.
 *
 * This work was supported by National Science Foundation Cooperative
 * Agreements #DEB-0832652 and #DEB-0936498.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0.
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 */

package edu.lternet.pasta.auditmanager;

import java.io.File;
import java.net.URI;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;

import com.sun.jersey.api.client.ClientResponse.Status;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import edu.lternet.pasta.common.MethodNameUtility;
import edu.lternet.pasta.common.PastaWebService;
import edu.lternet.pasta.common.QueryString;
import edu.lternet.pasta.common.ResourceNotFoundException;
import edu.lternet.pasta.common.WebExceptionFactory;
import edu.lternet.pasta.common.security.access.AccessControllerFactory;
import edu.lternet.pasta.common.security.access.JaxRsHttpAccessController;
import edu.lternet.pasta.common.security.access.UnauthorizedException;

/**
 * <p>
 * The Audit Manager web service allows services to <b>create</b>, and users to
 * <b>read</b> PASTA audit records (also known as "audit log entries").
 * </p>
 *
 * <p>
 * An <code>auditRecord</code> XML element will always consist of these required sub-elements:
 * <ul>
 *   <li><b>category</b>: the status level of severity, one of 'error', 'warn', 'info', or 'debug'</li>
 *   <li><b>service</b>: name of the originating service, e.g. 'DataPackageManager-1.0'</li>
 * </ul>
 * </p>
 *
 * <p>
 * Other optional elements are of the following:
 * <ul>
 *   <li><b>oid</b>: an object identifier value for the audit log entry, as generated and assigned by the Audit Manager when the audit log entry is created, e.g. '477'.</li>
 *   <li><b>entryTime</b>: the date and time of the audit log entry, in ISO8601 format, e.g. '2015-01-26 15:06:02.515'. An entryTime timestamp value is automatically generated by the Audit Manager when a new audit log entry is created.</li>
 *   <li><b>serviceMethod</b>: the originating service method pertaining to the creation of the audit log entry, e.g. 'readDataEntity'</li>
 *   <li><b>responseStatus</b>: the HTTP response status code from the service method, e.g. '200'</li>
 *   <li><b>resourceId</b>: the resource ID related to audit log entry, if applicable, e.g. 'https://pasta.lternet.edu/package/metadata/eml/knb-lter-nwk/1416/1'</li>
 *   <li><b>user</b>: the user distinguished name on whose behalf the service method was executed, e.g. 'uid=ucarroll,o=LTER,dc=ecoinformatics,dc=org'</li>
 *   <li><b>groups</b>: a comma-separated list of groups that the user is a member of, e.g. 'authenticated'</li>
 *   <li><b>authSystem</b>: the authentication system, e.g. 'https://pasta.lternet.edu/authentication'</li>
 *   <li><b>entryText</b>: auxiliary information to accompany the audit log entry</li>
 * </ul>
 * </p>
 * 
 * <p>
 * Note that 'oid' and 'entryTime' values are automatically generated by the Audit Manager when new audit entries are created.
 * Thus, although they are technically optional elements, they will always be present in audit records that are read from PASTA. 
 * </p>
 *
 * @webservicename Audit Manager
 * @baseurl https://audit.lternet.edu/audit
 */
@Path("")
public class AuditManagerResource extends PastaWebService {
    /*
     * Class variables
     */

    // Query parameter for range in time beginning at time (ISO 8601 format)
    public static final String FROM_TIME = "fromTime";
    // Query parameter for range in time ending at time (ISO 8601 format)
    public static final String TO_TIME = "toTime";
    // Query parameter for category
    public static final String CATEGORY = "category";
    // Query parameter for service
    public static final String SERVICE = "service";
    // Query parameter for service
    public static final String SERVICEMETHOD = "serviceMethod";
    // Query parameter for user
    public static final String USER = "user";
    // Query parameter for groups
    public static final String GROUP = "group";
    // Query parameter for authSystem
    public static final String AUTHSYSTEM = "authSystem";
    // Query parameter for status
    public static final String STATUS_CODE = "status";
    // Query parameter for user
    public static final String RESOURCE_ID = "resourceId";

    //Query parameter for record limit
    public static final String LIMIT = "limit";

    private static Logger logger = Logger.getLogger(AuditManagerResource.class);
    // Set of valid query parameters
    public static final Set<String> VALID_QUERY_KEYS;
    public static final Set<String> VALID_RECENT_UPLOADS_KEYS;
    private static final String SERVICE_OWNER = "pasta";

    static {
        Set<String> set = new TreeSet<String>();
        set.add(TO_TIME);
        set.add(FROM_TIME);
        set.add(CATEGORY);
        set.add(SERVICE);
        set.add(SERVICEMETHOD);
        set.add(USER);
        set.add(GROUP);
        set.add(AUTHSYSTEM);
        set.add(STATUS_CODE);
        set.add(RESOURCE_ID);
        set.add(LIMIT);
        VALID_QUERY_KEYS = Collections.unmodifiableSet(set);
    }

    static {
        Set<String> set = new TreeSet<String>();
        set.add(FROM_TIME);
        set.add(SERVICEMETHOD);
        set.add(LIMIT);
        VALID_RECENT_UPLOADS_KEYS = Collections.unmodifiableSet(set);
    }

    /*
     * Instance variables
     */

    /*
     * Constructors
     */

    /*
     * Class methods
     */

    /*
     * Instance methods
     */

    private void assertAuthorizedToRead(HttpHeaders headers, String serviceMethod) {

        String acr = AccessControlRuleFactory.getServiceAcr(serviceMethod);
        JaxRsHttpAccessController controller = AccessControllerFactory.getDefaultHttpAccessController();

        if (controller.canRead(headers, acr, SERVICE_OWNER)) {
            return;
        }
        String s = "This request is not authorized to retreive entries from "
                + "the audit manager. Please check your authorization " + "credentials.";

        throw WebExceptionFactory.makeUnauthorized(s);
    }

    private void assertAuthorizedToWrite(HttpHeaders headers, String serviceMethod) {

        String acr = AccessControlRuleFactory.getServiceAcr(serviceMethod);
        JaxRsHttpAccessController controller = AccessControllerFactory.getDefaultHttpAccessController();

        if (controller.canWrite(headers, acr, SERVICE_OWNER)) {
            return;
        }

        String s = "This request is not authorized to submit entries to the "
                + "audit manager. Please check your authorization " + "credentials.";

        throw WebExceptionFactory.makeUnauthorized(s);
    }

    /**
    * <strong>Create Audit Record</strong> operation, 
    * creates a new logged entry in the Audit Manager's logging database.
    * 
     * <h4>Request entity:</h4>
     *
     * <p>
     * The request entity should be an XML document (MIME type
     * <code>application/xml</code>) that is described below.
     * </p>
     *
     * <pre>
     *   &lt;auditRecord&gt;
     *       &lt;category&gt;<code>error</code>|<code>warn</code>|<code>info</code>|<code>debug</code>&lt;/category&gt;
     *       &lt;service&gt;<em>service</em>&lt;/service&gt;
     *       &lt;serviceMethod&gt;<em>service method</em>&lt;/serviceMethod&gt;
     *       &lt;responseStatus&gt;<em>HTTP response status code</em>&lt;/responseStatus&gt;
     *       &lt;resourceId&gt;<em>resource identifier</em>&lt;/resourceId&gt;
     *       &lt;user&gt;<em>user distinguished name</em>&lt;/user&gt;
     *       &lt;groups&gt;<em>groups list</em>&lt;/groups&gt;
     *       &lt;authSystem&gt;<em>authentication system</em>&lt;/authSystem&gt;
     *       &lt;entryTextt&gt;<em>auxiliary info text</em>&lt;/entryText&gt;
     *   &lt;/auditRecord&gt;
     * </pre>
     *
     * <p>The <code>category</code> and <code>service</code> elements are mandatory.</p>
     * 
     * Example auditRecord XML document:
     * 
     * <pre>
       &lt;auditRecord&gt;
       &lt;category&gt;warn&lt;/category&gt;
       &lt;service&gt;DataPackageManager-1.0&lt;/service&gt;
       &lt;serviceMethod&gt;listDataEntities&lt;/serviceMethod&gt;
       &lt;responseStatus&gt;404&lt;/responseStatus&gt;
       &lt;resourceId&gt;&lt;/resourceId&gt;
       &lt;user&gt;uid=ucarroll,o=LTER,dc=ecoinformatics,dc=org&lt;/user&gt;
       &lt;groups&gt;authenticated&lt;/groups&gt;
       &lt;authSystem&gt;https://pasta.lternet.edu/authentication&lt;/authSystem&gt;
       &lt;entryText&gt;No entity resources found for scope abc&lt;/entryText&gt;
       &lt;/auditRecord&gt;
     * </pre>
     *
     * <h4>Responses:</h4>
     *
     * <table border="1" cellspacing="0" cellpadding="3">
     *   <tr>
     *     <td><b>Status</b></td>
     *     <td><b>Reason</b></td>
     *     <td><b>Entity</b></td>
     *     <td><b>MIME type</b></td>
     *   </tr>
     *   <tr>
     *     <td>201 Created</td>
     *     <td>If the request to create an audit entry was successful.</td>
     *     <td>None, but the <code>Location</code> header will contain a URL that
     *         references the new subscription.</td>
     *     <td>N/A</td>
     *   </tr>
     *   <tr>
     *     <td>400 Bad Request</td>
     *     <td>If the request entity contains an error, such as improperly formatted XML.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     *   <tr>
     *     <td>401 Unauthorized</td>
     *     <td>If the requesting user is not authorized to create log entries.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     *   <tr>
     *     <td>500 Internal Server Error</td>
     *     <td>The server encountered an unexpected condition which prevented 
     *     it from fulfilling the request. For example, a SQL error occurred, 
     *     or an unexpected condition was encountered.</td>
     *     <td>An error message</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     * </table>
     *
     * @param headers
     *            the HTTP request headers containing the authorization token.
     * @param auditEntry
     *            the POST request's body, of XML representing a log entry.
     * @return an appropriate HTTP response.
     */
    @POST
    public Response create(@Context HttpHeaders headers, String auditEntry) {
        try {
            assertAuthorizedToWrite(headers, MethodNameUtility.methodName());
            Properties properties = ConfigurationListener.getProperties();
            AuditManager auditManager = new AuditManager(properties);
            int auditId = auditManager.create(auditEntry);
            String uriString = String.format("http://audit.lternet.edu/audit/%d", auditId);
            URI uri = URI.create(uriString);
            return Response.created(uri).build();
        } catch (UnauthorizedException e) {
            return WebExceptionFactory.makeUnauthorized(e).getResponse();
        } catch (WebApplicationException e) {
            return e.getResponse();
        } catch (Exception e) {
            WebApplicationException webApplicationException = WebExceptionFactory
                    .make(Response.Status.INTERNAL_SERVER_ERROR, e, e.getMessage());
            return webApplicationException.getResponse();
        } finally {
        }
    }

    /**
     * Returns the API documentation for the Audit Manager.
     */
    @Override
    public File getApiDocument() {
        return ConfigurationListener.getApiDocument();
    }

    /**
    * <strong>Get Audit Record</strong> operation, retrieves a single audit record 
    * based on the audit identifier value specified in the path.
     *
     * <h4>Responses:</h4>
     *
     * <p>
     * If the request is successful, the response will contain plain text.
     * </p>
     *
     * <table border="1" cellspacing="0" cellpadding="3">
     * <tr>
     * <td><b>Status</b></td>
     * <td><b>Reason</b></td>
     * <td><b>Entity</b></td>
     * <td><b>MIME type</b></td>
     * </tr>
     * <tr>
     * <td>200 OK</td>
     * <td>If the request was successful.</td>
     * <td>The specified subscription's attributes.</td>
     * <td><code>text/plain</code></td>
     * </tr>
     * <tr>
     * <td>400 Bad Request</td>
     * <td>If the specified identification number cannot be parsed as an
     * integer.</td>
     * <td>An error message.</td>
     * <td><code>text/plain</code></td>
     * </tr>
     * <tr>
     * <td>401 Unauthorized</td>
     * <td>If the requesting user is not authorized to read the specified
     * subscription.</td>
     * <td>An error message.</td>
     * <td><code>text/plain</code></td>
     * </tr>
     * <tr>
     * <td>404 Not Found</td>
     * <td>If the requested log entry does not exist.</td>
     * <td>An error message.</td>
     * <td><code>text/plain</code></td>
     * </tr>
     * </table>
     *
     * @param headers
     *             the HTTP request headers containing the authorization token.
     * @param oid
     *             the POST request's body, of XML representing a log entry.
     * @return an appropriate HTTP response.
     */
    @GET
    @Path("report/{oid}")
    public Response getAuditRecord(@Context HttpHeaders headers, @PathParam(value = "oid") int oid) {
        try {
            Properties properties = ConfigurationListener.getProperties();
            assertAuthorizedToRead(headers, MethodNameUtility.methodName());
            AuditManager auditManager = new AuditManager(properties);
            Integer oidInteger = new Integer(oid);
            String oidString = oidInteger.toString();
            List<String> oidList = new ArrayList<String>();
            oidList.add(oidString);
            Map<String, List<String>> queryParams = new HashMap<String, List<String>>();
            queryParams.put("oid", oidList);
            String xmlString = auditManager.getAuditRecords(queryParams);
            if (xmlString.length() == (AuditManager.AUDIT_OPENING_TAG.length()
                    + AuditManager.AUDIT_CLOSING_TAG.length())) {
                throw new ResourceNotFoundException(String.format("Oid %d does not exist.", oid));
            }
            return Response.ok(xmlString).build();
        } catch (ClassNotFoundException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (SQLException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (UnauthorizedException e) {
            return WebExceptionFactory.makeUnauthorized(e).getResponse();
        } catch (ResourceNotFoundException e) {
            return WebExceptionFactory.makeNotFound(e).getResponse();
        } catch (WebApplicationException e) {
            return e.getResponse();
        } catch (IllegalStateException e) {
            return WebExceptionFactory.makeBadRequest(e).getResponse();
        }
    }

    /**
     * <strong>Get Audit Report</strong> operation, gets a list of zero or more 
     * audit records matching the query parameters as specified in the request.
     *
     * <h4>Query Parameters:</h4>
     * <table border="1" cellspacing="0" celpadding="3">
     *   <tr>
     *     <td><b>Parameter</b></td>
     *     <td><b>Value Constraints</b></td>
     *   </tr>
     *   <tr>
     *     <td>category</td>
     *     <td>debug, info, error, warn</td>
     *   </tr>
     *   <tr>
     *     <td>service</td>
     *     <td>Any of the PASTA services.</td>
     *   </tr>
     *   <tr>
     *     <td>serviceMethod</td>
     *     <td>Any of the PASTA service Resource class JAX-RS methods.</td>
     *   </tr>
     *   <tr>
     *     <td>user</td>
     *     <td>Any user.</td>
     *   </tr>
     *   <tr>
     *     <td>group</td>
     *     <td>Any group.</td>
     *   </tr>
     *   <tr>
     *     <td>authSystem</td>
     *     <td>A valid auth system identifier.</td>
     *   </tr>
     *   <tr>
     *     <td>status</td>
     *     <td>A valid HTTP Response Code.</td>
     *   </tr>
     *   <tr>
     *     <td>resourceId</td>
     *     <td>A PASTA resource identifier, e.g. https://pasta.lternet.edu/package/eml/knb-lter-and/2719/6, or a substring thereof (see below)</td>
     *   <tr>
     *     <td>fromTime</td>
     *     <td>An ISO8601 timestamp</td>
     *   </tr>
     *   <tr>
     *     <td>toTime</td>
     *     <td>An ISO8601 timestamp</td>
     *   </tr>
     *   <tr>
     *     <td>limit</td>
     *     <td>A positive whole number</td>
     *   </tr>
     * </table>
     * <br/>
     * The query parameters <code>fromTime</code> and optionally
     * <code>toTime</code> should be used to indicate a time span. When
     * <code>toTime</code> is absent, the report will consist of all matching
     * records up to the current time. Either of these parameters may only be
     * used once.
     * <br/>
     * The query parameter <code>limit</code> sets an upper limit on the number
     * of audit records returned. For example, "limit=1000".
     * <br/>
     * The query parameter <code>resourceId</code> will match any audit log entry whose resourceId 
     * value contains the specified string value. Thus, a query parameter of "resourceId=knb-lter-and" 
     * will match any audit log entry whose resourceId value contains the substring "knb-lter-and",
     * while a query parameter of "resourceId=knb-lter-and/2719/6" will match any audit log entry
     * whose resourceId value contains the substring "knb-lter-and/2719/6". 
     *
     * <h4>Responses:</h4>
     *
     * <p>If the request is successful, the response will contain XML text.</p>
     *
     * <table border="1" cellspacing="0" cellpadding="3">
     *   <tr>
     *     <td><b>Status</b></td>
     *     <td><b>Reason</b></td>
     *     <td><b>Entity</b></td>
     *     <td><b>MIME type</b></td>
     *   </tr>
     *   <tr>
     *     <td>200 OK</td>
     *     <td>If the request was successful.</td>
     *     <td>The specified subscription's attributes.</td>
     *     <td><code>application/xml</code></td>
     *   </tr>
     *   <tr>
     *     <td>400 Bad Request</td>
     *     <td>If the specified identification number cannot be parsed as an integer.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     *   <tr>
     *     <td>401 Unauthorized</td>
     *     <td>If the requesting user is not authorized to read the specified subscription.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     * </table>
     *
     * @param headers  the HTTP request headers containing the authorization token.
     * @param uriInfo  a UriInfo object containing the GET's query parameters
     * @return an appropriate HTTP response.
     */
    @GET
    @Path("report")
    public Response getAuditReport(@Context HttpHeaders headers, @Context UriInfo uriInfo) {
        ResponseBuilder responseBuilder = null;
        Response response = null;

        try {
            Properties properties = ConfigurationListener.getProperties();
            assertAuthorizedToRead(headers, MethodNameUtility.methodName());
            AuditManager auditManager = new AuditManager(properties);
            QueryString queryString = new QueryString(uriInfo);
            queryString.checkForIllegalKeys(VALID_QUERY_KEYS);
            Map<String, List<String>> queryParams = queryString.getParams();
            File xmlFile = auditManager.getAuditRecordsFile(queryParams);
            if (xmlFile != null && xmlFile.exists()) {
                Long size = FileUtils.sizeOf(xmlFile);
                responseBuilder = Response.ok(xmlFile, MediaType.APPLICATION_XML);
                responseBuilder.header("Content-Length", size.toString());
                response = responseBuilder.build();
                String logMessage = String
                        .format("getAuditRecords service method finished processing: returning %d bytes", size);
                logger.warn(logMessage);
            } else {
                ResourceNotFoundException e = new ResourceNotFoundException(
                        String.format("Unable to process audit query with query parameters: %s", queryParams));
                throw (e);
            }
            return response;
        } catch (ClassNotFoundException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (ResourceNotFoundException e) {
            return WebExceptionFactory.makeNotFound(e).getResponse();
        } catch (SQLException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (UnauthorizedException e) {
            return WebExceptionFactory.makeUnauthorized(e).getResponse();
        } catch (WebApplicationException e) {
            return e.getResponse();
        } catch (IllegalStateException e) {
            return WebExceptionFactory.makeBadRequest(e).getResponse();
        }
    }

    /**
     * <strong>Get Audit Count</strong> operation, returns a count of the number 
     * audit log records from the audit table (named "eventlog") matching the provided 
     * criteria.
     *
     * <h4>Query Parameters:</h4>
     * <table border="1" cellspacing="0" celpadding="3">
     *   <tr>
     *     <td><b>Parameter</b></td>
     *     <td><b>Value Constraints</b></td>
     *   </tr>
     *   <tr>
     *     <td>category</td>
     *     <td>debug, info, error, warn</td>
     *   </tr>
     *   <tr>
     *     <td>service</td>
     *     <td>Any of the PASTA services.</td>
     *   </tr>
     *   <tr>
     *     <td>serviceMethod</td>
     *     <td>Any of the PASTA service Resource class JAX-RS methods.</td>
     *   </tr>
     *   <tr>
     *     <td>user</td>
     *     <td>Any user.</td>
     *   </tr>
     *   <tr>
     *     <td>group</td>
     *     <td>Any group.</td>
     *   </tr>
     *   <tr>
     *     <td>authSystem</td>
     *     <td>A valid auth system identifier.</td>
     *   </tr>
     *   <tr>
     *     <td>status</td>
     *     <td>A valid HTTP Response Code.</td>
     *   </tr>
     *   <tr>
     *     <td>resourceId</td>
     *     <td>A PASTA resource identifier, e.g. https://pasta.lternet.edu/package/eml/knb-lter-and/2719/6, or a substring thereof (see below)</td>
     *   </tr>
     *   <tr>
     *     <td>fromTime</td>
     *     <td>An ISO8601 timestamp</td>
     *   </tr>
     *   <tr>
     *     <td>toTime</td>
     *     <td>An ISO8601 timestamp</td>
     *   </tr>
     *   <tr>
     *     <td>limit</td>
     *     <td>A positive whole number</td>
     *   </tr>
     * </table>
     * <br/>
     * The query parameters <code>fromTime</code> and optionally
     * <code>toTime</code> should be used to indicate a time span. When
     * <code>toTime</code> is absent, the count will include of all matching
     * records up to the current time. Either of these parameters may only be
     * used once.
     * <br/>
     * The query parameter <code>limit</code> sets an upper limit on the number
     * of audit records returned. For example, "limit=1000".
     * <br/>
     * The query parameter <code>resourceId</code> will match any audit log entry whose resourceId 
     * value contains the specified string value. Thus, a query parameter of "resourceId=knb-lter-and" 
     * will match any audit log entry whose resourceId value contains the substring "knb-lter-and",
     * while a query parameter of "resourceId=knb-lter-and/2719/6" will match any audit log entry
     * whose resourceId value contains the substring "knb-lter-and/2719/6". 
     *
     * <h4>Responses:</h4>
     *
     * <p>If the request is successful, the response will contain XML text.</p>
     *
     * <table border="1" cellspacing="0" cellpadding="3">
     *   <tr>
     *     <td><b>Status</b></td>
     *     <td><b>Reason</b></td>
     *     <td><b>Entity</b></td>
     *     <td><b>MIME type</b></td>
     *   </tr>
     *   <tr>
     *     <td>200 OK</td>
     *     <td>If the request was successful.</td>
     *     <td>The specified subscription's attributes.</td>
     *     <td><code>application/xml</code></td>
     *   </tr>
     *   <tr>
     *     <td>400 Bad Request</td>
     *     <td>If the specified identification number cannot be parsed as an integer.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     *   <tr>
     *     <td>401 Unauthorized</td>
     *     <td>If the requesting user is not authorized to read the specified subscription.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     * </table>
     *
     * @param headers  the HTTP request headers containing the authorization token.
     * @param uriInfo  a UriInfo object containing the GET's query parameters
     * @return an appropriate HTTP response.
     */
    @GET
    @Path("count")
    public Response getAuditCount(@Context HttpHeaders headers, @Context UriInfo uriInfo) {
        ResponseBuilder responseBuilder = null;
        Response response = null;

        try {
            Properties properties = ConfigurationListener.getProperties();
            assertAuthorizedToRead(headers, MethodNameUtility.methodName());
            AuditManager auditManager = new AuditManager(properties);
            QueryString queryString = new QueryString(uriInfo);
            queryString.checkForIllegalKeys(VALID_QUERY_KEYS);
            Map<String, List<String>> queryParams = queryString.getParams();
            Integer matchCount = auditManager.getAuditRecordsCount(queryParams);
            if (matchCount != null) {
                String matchCountStr = matchCount.toString();
                responseBuilder = Response.ok(matchCountStr, MediaType.TEXT_PLAIN);
                responseBuilder.header("Content-Length", String.format("%d", matchCountStr.length()));
                response = responseBuilder.build();
                String logMessage = String.format(
                        "getAuditRecordsCount service method finished processing: %s records matched",
                        matchCountStr);
                logger.warn(logMessage);
            } else {
                ResourceNotFoundException e = new ResourceNotFoundException(
                        String.format("Unable to process audit query with query parameters: %s", queryParams));
                throw (e);
            }
            return response;
        } catch (ClassNotFoundException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (ResourceNotFoundException e) {
            return WebExceptionFactory.makeNotFound(e).getResponse();
        } catch (SQLException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (UnauthorizedException e) {
            return WebExceptionFactory.makeUnauthorized(e).getResponse();
        } catch (WebApplicationException e) {
            return e.getResponse();
        } catch (IllegalStateException e) {
            return WebExceptionFactory.makeBadRequest(e).getResponse();
        }
    }

    /**
     * <strong>Get Recent Uploads</strong> operation, gets a list of zero or more audit 
     * records of either recently inserted or recently updated data packages, as specified
     * in the request.
     *
     * <h4>Query Parameters:</h4>
     * <table border="1" cellspacing="0" celpadding="3">
     *   <tr>
     *     <td><b>Parameter</b></td>
     *     <td><b>Value Constraints</b></td>
     *   </tr>
     *   <tr>
     *     <td>serviceMethod</td>
     *     <td>Either of &quot;createDataPackage&quot; or &quot;updateDataPackage&quot;
     *     </td>
     *   </tr>
     *   <tr>
     *     <td>fromTime</td>
     *     <td>An ISO8601 timestamp</td>
     *   </tr>
     *   <tr>
     *     <td>limit</td>
     *     <td>A positive whole number</td>
     *   </tr>
     * </table>
     * <br/>
     * The query parameter <code>serviceMethod</code> should have the value
     * &quot;createDataPackage&quot; (to retrieve recent inserts) or &quot;updateDataPackage&quot;
     * (to retrieve recent updates)
     * <br/>
     * The query parameter <code>fromTime</code> is used to specify the
     * date/time in the past that represents the oldest audit records that should be
     * returned. Data packages uploaded prior to that time are not considered
     * recent uploads and are thus filtered from the query results.
     * <br/>
     * The query parameter <code>limit</code> sets an upper limit on the number
     * of audit records returned. For example, "limit=3".
     *
     * <h4>Responses:</h4>
     *
     * <p>If the request is successful, the response will contain XML text.</p>
     *
     * <table border="1" cellspacing="0" cellpadding="3">
     *   <tr>
     *     <td><b>Status</b></td>
     *     <td><b>Reason</b></td>
     *     <td><b>Entity</b></td>
     *     <td><b>MIME type</b></td>
     *   </tr>
     *   <tr>
     *     <td>200 OK</td>
     *     <td>If the request was successful.</td>
     *     <td>The specified subscription's attributes.</td>
     *     <td><code>application/xml</code></td>
     *   </tr>
     *   <tr>
     *     <td>400 Bad Request</td>
     *     <td>If the specified identification number cannot be parsed as an integer.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     *   <tr>
     *     <td>401 Unauthorized</td>
     *     <td>If the requesting user is not authorized to read the specified subscription.</td>
     *     <td>An error message.</td>
     *     <td><code>text/plain</code></td>
     *   </tr>
     * </table>
     *
     * @param headers  the HTTP request headers containing the authorization token.
     * @param uriInfo  the POST request's body, of XML representing a log entry.
     * @return an appropriate HTTP response.
     */
    @GET
    @Path("recent-uploads")
    public Response getRecentUploads(@Context HttpHeaders headers, @Context UriInfo uriInfo) {
        try {
            Properties properties = ConfigurationListener.getProperties();
            assertAuthorizedToRead(headers, MethodNameUtility.methodName());
            AuditManager auditManager = new AuditManager(properties);
            QueryString queryString = new QueryString(uriInfo);
            queryString.checkForIllegalKeys(VALID_RECENT_UPLOADS_KEYS);
            Map<String, List<String>> queryParams = queryString.getParams();
            String xmlString = auditManager.getRecentUploads(queryParams);
            return Response.ok(xmlString).build();
        } catch (ClassNotFoundException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (ResourceNotFoundException e) {
            return WebExceptionFactory.makeNotFound(e).getResponse();
        } catch (SQLException e) {
            return WebExceptionFactory.make(Status.INTERNAL_SERVER_ERROR, e, e.getMessage()).getResponse();
        } catch (UnauthorizedException e) {
            return WebExceptionFactory.makeUnauthorized(e).getResponse();
        } catch (WebApplicationException e) {
            return e.getResponse();
        } catch (IllegalStateException e) {
            return WebExceptionFactory.makeBadRequest(e).getResponse();
        }
    }

    /**
     * Returns the tutorial document for the Audit Manager.
     */
    @Override
    public File getTutorialDocument() {
        return ConfigurationListener.getTutorialDocument();
    }

    /**
     * Returns the Audit Manager's version, such as {@code auditmanager-0.1}.
     */
    @Override
    public String getVersionString() {
        return ConfigurationListener.getWebServiceVersion();
    }

}