org.roda.wui.api.v1.IndexResource.java Source code

Java tutorial

Introduction

Here is the source code for org.roda.wui.api.v1.IndexResource.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE file at the root of the source
 * tree and available online at
 *
 * https://github.com/keeps/roda
 */
package org.roda.wui.api.v1;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.lang3.StringUtils;
import org.roda.core.RodaCoreFactory;
import org.roda.core.common.UserUtility;
import org.roda.core.data.common.RodaConstants;
import org.roda.core.data.exceptions.AuthorizationDeniedException;
import org.roda.core.data.exceptions.GenericException;
import org.roda.core.data.exceptions.RODAException;
import org.roda.core.data.exceptions.RequestNotValidException;
import org.roda.core.data.utils.JsonUtils;
import org.roda.core.data.v2.index.CountRequest;
import org.roda.core.data.v2.index.FindRequest;
import org.roda.core.data.v2.index.IndexResult;
import org.roda.core.data.v2.index.IsIndexed;
import org.roda.core.data.v2.index.facet.FacetParameter;
import org.roda.core.data.v2.index.facet.FacetParameter.SORT;
import org.roda.core.data.v2.index.facet.Facets;
import org.roda.core.data.v2.index.facet.SimpleFacetParameter;
import org.roda.core.data.v2.index.filter.Filter;
import org.roda.core.data.v2.index.filter.SimpleFilterParameter;
import org.roda.core.data.v2.index.sort.SortParameter;
import org.roda.core.data.v2.index.sort.Sorter;
import org.roda.core.data.v2.index.sublist.Sublist;
import org.roda.core.data.v2.user.User;
import org.roda.core.index.utils.IterableIndexResult;
import org.roda.wui.api.controllers.Browser;
import org.roda.wui.api.v1.utils.ApiUtils;
import org.roda.wui.api.v1.utils.ExtraMediaType;
import org.roda.wui.api.v1.utils.FacetsCSVOutputStream;
import org.roda.wui.api.v1.utils.ResultsCSVOutputStream;
import org.roda.wui.common.I18nUtility;
import org.roda.wui.common.server.RodaStreamingOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;

/**
 * REST API resource Index.
 *
 * @author Rui Castro <rui.castro@gmail.com>
 */
@Path("/v1/index")
@Api(value = "v1 index")
public class IndexResource {
    /**
     * Logger.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(IndexResource.class);
    /**
     * Default value for <i>start</i> parameter.
     */
    private static final int DEFAULT_START = 0;
    /**
     * Default value for <i>limit</i> parameter.
     */
    private static final int DEFAULT_LIMIT = 100;
    /**
     * Default value for <i>onlyActive</i> parameter.
     */
    private static final boolean DEFAULT_ONLY_ACTIVE = true;
    /**
     * Default filename for CSV files.
     */
    private static final String DEFAULT_CSV_FILENAME = "export.csv";
    /**
     * CSV type.
     */
    private static final String TYPE_CSV = "csv";
    /**
     * Default value for <i>facetLimit</i> parameter.
     */
    private static final int DEFAULT_FACET_LIMIT = 100;
    /** CSV field delimiter config key. */
    private static final String CONFIG_KEY_CSV_DELIMITER = "csv.delimiter";

    /**
     * HTTP request.
     */
    @Context
    private HttpServletRequest request;

    /**
     * Find indexed resources.
     *
     * @param returnClass
     *          {@link Class} of resources to return.
     * @param filterParameters
     *          List of filter parameters. Example: "formatPronom=fmt/19".
     * @param sortParameters
     *          List of sort parameters. Examples: "formatPronom", "uuid desc".
     * @param start
     *          Index of the first element to return (0-based index).
     * @param limit
     *          Maximum number of elements to return.
     * @param facetAttributes
     *          Facets to return.
     * @param facetLimit
     *          Maximum number of facets to return.
     * @param localeString
     *          the locale.
     * @param onlyActive
     *          Return only active resources?
     * @param exportFacets
     *          for CSV results, export only facets?
     * @param filename
     *          the filename for exported CSV.
     * @param <T>
     *          Type of the resources to return.
     * @return a {@link Response} with the resources.
     * @throws RODAException
     *           if some error occurs.
     */
    @GET
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, ExtraMediaType.TEXT_CSV })
    @ApiOperation(value = "Find indexed resources", notes = "Find indexed resources.", response = IndexResult.class, responseContainer = "List")
    public <T extends IsIndexed> Response list(
            @ApiParam(value = "Class of resources to return", required = true, example = "org.roda.core.data.v2.ip.IndexedFile") @QueryParam(RodaConstants.API_QUERY_KEY_RETURN_CLASS) final String returnClass,
            @ApiParam(value = "Filter parameters", example = "formatPronom=fmt/19") @QueryParam(RodaConstants.API_QUERY_KEY_FILTER) final List<String> filterParameters,
            @ApiParam(value = "Sort parameters", example = "\"formatPronom\", \"uuid desc\"") @QueryParam(RodaConstants.API_QUERY_KEY_SORT) final List<String> sortParameters,
            @ApiParam(value = "Index of the first element to return (0-based index)", defaultValue = "0") @QueryParam(RodaConstants.API_QUERY_KEY_START) final Integer start,
            @ApiParam(value = "Maximum number of elements to return", defaultValue = "100") @QueryParam(RodaConstants.API_QUERY_KEY_LIMIT) final Integer limit,
            @ApiParam(value = "Facets to return", example = "formatPronom") @QueryParam(RodaConstants.API_QUERY_KEY_FACET) final List<String> facetAttributes,
            @ApiParam(value = "Facet limit", example = "100", defaultValue = "100") @QueryParam(RodaConstants.API_QUERY_KEY_FACET_LIMIT) final Integer facetLimit,
            @ApiParam(value = "Language", example = "en", defaultValue = "en") @QueryParam(RodaConstants.API_QUERY_KEY_LANG) final String localeString,
            @ApiParam(value = "Return only active resources?", defaultValue = "true") @QueryParam(RodaConstants.API_QUERY_KEY_ONLY_ACTIVE) final Boolean onlyActive,
            @ApiParam(value = "Export facet data", defaultValue = "false") @QueryParam(RodaConstants.API_QUERY_KEY_EXPORT_FACETS) final boolean exportFacets,
            @ApiParam(value = "Filename", defaultValue = DEFAULT_CSV_FILENAME) @QueryParam(RodaConstants.API_QUERY_KEY_FILENAME) final String filename)
            throws RODAException {

        final String mediaType = ApiUtils.getMediaType(null, request);
        final User user = UserUtility.getApiUser(request);

        final FindRequest findRequest = new FindRequest();
        findRequest.classToReturn = returnClass;
        findRequest.exportFacets = exportFacets;
        findRequest.filename = StringUtils.isBlank(filename) ? DEFAULT_CSV_FILENAME : filename;

        findRequest.filter = new Filter();
        for (String filterParameter : filterParameters) {
            final String[] parts = filterParameter.split("=");
            if (parts.length == 2) {
                findRequest.filter.add(new SimpleFilterParameter(parts[0], parts[1]));
            } else {
                LOGGER.warn("Unable to parse filter parameter '{}'. Ignored", filterParameter);
            }
        }

        findRequest.sorter = new Sorter();
        for (String sortParameter : sortParameters) {
            final String[] parts = sortParameter.split(" ");
            final boolean descending = parts.length == 2 && "desc".equalsIgnoreCase(parts[1]);
            if (parts.length > 0) {
                findRequest.sorter.add(new SortParameter(parts[0], descending));
            } else {
                LOGGER.warn("Unable to parse sorter parameter '{}'. Ignored", sortParameter);
            }
        }

        findRequest.sublist = new Sublist(start == null ? DEFAULT_START : start,
                limit == null ? DEFAULT_LIMIT : limit);

        final int paramFacetLimit = facetLimit == null ? DEFAULT_FACET_LIMIT : facetLimit;

        final Set<FacetParameter> facetParameters = new HashSet<>();
        for (String facetAttribute : facetAttributes) {
            facetParameters.add(new SimpleFacetParameter(facetAttribute, paramFacetLimit, SORT.COUNT));
        }
        findRequest.facets = new Facets(facetParameters);

        findRequest.onlyActive = onlyActive == null ? DEFAULT_ONLY_ACTIVE : onlyActive;

        final Response response;
        if (ExtraMediaType.TEXT_CSV.equals(mediaType)) {
            response = csvResponse(findRequest, user);
        } else {
            final Class<T> classToReturn = getClass(findRequest.classToReturn);

            IndexResult<T> indexResult = Browser.find(classToReturn, findRequest.filter, findRequest.sorter,
                    findRequest.sublist, findRequest.facets, user, findRequest.onlyActive, new ArrayList<>());
            indexResult = I18nUtility.translate(indexResult, classToReturn, localeString);

            response = Response.ok(indexResult, mediaType).build();
        }

        return response;
    }

    /**
     * Find indexed resources.
     *
     * @param findRequest
     *          find parameters.
     * @param <T>
     *          Type of the resources to return.
     * @return a {@link Response} with the resources.
     * @throws RODAException
     *           if some error occurs.
     */
    @POST
    @Path("/find")
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, ExtraMediaType.TEXT_CSV })
    @ApiOperation(value = "Find indexed resources", notes = "Find indexed resources.", response = IsIndexed.class, responseContainer = "List")
    public <T extends IsIndexed> Response find(@ApiParam(value = "Find parameters") final FindRequest findRequest)
            throws RODAException {

        final String mediaType = ApiUtils.getMediaType(null, request);
        final User user = UserUtility.getApiUser(request);

        if (ExtraMediaType.TEXT_CSV.equals(mediaType)) {
            return csvResponse(findRequest, user);
        } else {
            final IndexResult<T> result = Browser.find(getClass(findRequest.classToReturn), findRequest.filter,
                    findRequest.sorter, findRequest.sublist, findRequest.facets, user, findRequest.onlyActive,
                    new ArrayList<>());
            return Response.ok(result, mediaType).build();
        }

    }

    /**
     * Find indexed resources.
     *
     * @param findRequestString
     *          find parameters.
     * @param type
     *          the type of output ("csv").
     * @return a {@link Response} with the resources.
     * @throws RODAException
     *           if some error occurs.
     */
    @POST
    @Path("/findFORM")
    @Consumes({ MediaType.APPLICATION_FORM_URLENCODED })
    public Response findFORM(@FormParam("findRequest") final String findRequestString,
            @FormParam("type") final String type) throws RODAException {

        final User user = UserUtility.getApiUser(request);
        final FindRequest findRequest = JsonUtils.getObjectFromJson(findRequestString, FindRequest.class);

        if (type.equals(IndexResource.TYPE_CSV)) {
            return csvResponse(findRequest, user);
        } else {
            // TODO support JSON type
            throw new GenericException("Type not yet supported:" + type);
        }
    }

    /**
     * Count indexed resources.
     *
     * @param countRequest
     *          count parameters.
     * @return a {@link Response} with the count.
     * @throws RODAException
     *           if some error occurs.
     */
    @POST
    @Path("/count")
    @Consumes({ MediaType.APPLICATION_JSON })
    @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
    @ApiOperation(value = "Count indexed resources", notes = "Count indexed resources.", response = Long.class)
    public Response count(@ApiParam(value = "Count parameters") final CountRequest countRequest)
            throws RODAException {
        final String mediaType = ApiUtils.getMediaType(null, request);
        final User user = UserUtility.getApiUser(request);
        final long result = Browser.count(user, getClass(countRequest.classToReturn), countRequest.filter,
                countRequest.onlyActive);
        return Response.ok(result, mediaType).build();
    }

    /**
     * Produces a CSV response with results or facets.
     * 
     * @param findRequest
     *          the request parameters.
     * @param user
     *          the current {@link User}.
     * @param <T>
     *          Type of the resources to return.
     * @return a {@link Response} with CSV.
     * @throws RequestNotValidException
     *           it the request is not valid.
     * @throws AuthorizationDeniedException
     *           if the user is not authorized to perform this operation.
     * @throws GenericException
     *           if some other error occurs.
     */
    private <T extends IsIndexed> Response csvResponse(final FindRequest findRequest, final User user)
            throws RequestNotValidException, AuthorizationDeniedException, GenericException {

        final Class<T> returnClass = getClass(findRequest.classToReturn);
        final Configuration config = RodaCoreFactory.getRodaConfiguration();
        final char delimiter;
        if (StringUtils.isBlank(config.getString(CONFIG_KEY_CSV_DELIMITER))) {
            delimiter = CSVFormat.DEFAULT.getDelimiter();
        } else {
            delimiter = config.getString(CONFIG_KEY_CSV_DELIMITER).trim().charAt(0);
        }

        if (findRequest.exportFacets) {
            final IndexResult<T> result = Browser.find(returnClass, findRequest.filter, Sorter.NONE, Sublist.NONE,
                    findRequest.facets, user, findRequest.onlyActive, new ArrayList<>());

            return ApiUtils.okResponse(new RodaStreamingOutput(
                    new FacetsCSVOutputStream(result.getFacetResults(), findRequest.filename, delimiter))
                            .toStreamResponse());
        } else {
            final IterableIndexResult<T> result = Browser.findAll(returnClass, findRequest.filter,
                    findRequest.sorter, findRequest.sublist, findRequest.facets, user, findRequest.onlyActive,
                    new ArrayList<>());

            return ApiUtils.okResponse(
                    new RodaStreamingOutput(new ResultsCSVOutputStream<>(result, findRequest.filename, delimiter))
                            .toStreamResponse());
        }
    }

    /**
     * Return the {@link Class} with the specified class name.
     * 
     * @param className
     *          the fully qualified name of the desired class.
     * @param <T>
     *          the type of {@link Class}.
     * @return the {@link Class} with the specified class name.
     * @throws RequestNotValidException
     *           if the class name is not valid.
     */
    @SuppressWarnings("unchecked")
    private <T> Class<T> getClass(final String className) throws RequestNotValidException {
        try {
            return (Class<T>) Class.forName(className);
        } catch (final ClassNotFoundException e) {
            throw new RequestNotValidException(String.format("Invalid value for classToReturn '%s'", className), e);
        }
    }

}