All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.lockss.laaws.rs.controller.ReposApiController Maven / Gradle / Ivy

There is a newer version: 2.15.1
Show newest version
/*
 * Copyright (c) 2017-2018, Board of Trustees of Leland Stanford Jr. University,
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation and/or
 * other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors
 * may be used to endorse or promote products derived from this software without
 * specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.lockss.laaws.rs.controller;

import io.swagger.annotations.ApiParam;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.*;
import org.lockss.laaws.rs.api.ReposApi;
import org.lockss.laaws.rs.core.LockssRepository;
import org.lockss.laaws.rs.util.ArtifactConstants;
import org.lockss.laaws.rs.util.ArtifactDataFactory;
import org.lockss.laaws.rs.util.ArtifactDataUtil;
import org.lockss.laaws.rs.model.Artifact;
import org.lockss.laaws.rs.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * Spring controller for the REST presentation of the LOCKSS Repository service.
 */
@Controller
public class ReposApiController implements ReposApi {
    private final static Log log = LogFactory.getLog(ReposApiController.class);
    public static final String APPLICATION_HTTP_RESPONSE_VALUE = "application/http;msgtype=response";
    public static final MediaType APPLICATION_HTTP_RESPONSE = MediaType.parseMediaType(APPLICATION_HTTP_RESPONSE_VALUE);

    @Autowired
    LockssRepository repo;

    /**
     * GET /repos: Returns a list of collection names managed by this repository.
     *
     * @return List of collection names.
     */
    public ResponseEntity> reposGet() {
        List collectionIds = new ArrayList<>();
        try {
            repo.getCollectionIds().forEach(x -> collectionIds.add(x));
        } catch (IOException e) {
            log.error("IOException was caught trying to enumerate collection IDs");
            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return new ResponseEntity<>(collectionIds, HttpStatus.OK);
    }

    /**
     * DELETE /repos/{collection}/artifacts/{artifactId}: Deletes an artifact from a collection managed by this repository.
     *
     * @param repository
     * @param artifactid
     * @return
     */
    public ResponseEntity reposArtifactsArtifactidDelete(
            @ApiParam(value = "Repository to add artifact into", required=true) @PathVariable("repository") String repository,
            @ApiParam(value = "ArtifactData ID", required=true) @PathVariable("artifactid") String artifactid
    ) {
        try {
            // Check that the artifact exists
            if (!repo.artifactExists(artifactid))
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);

            // Remove the artifact from the artifact store and index
            repo.deleteArtifact(repository, artifactid);
            return new ResponseEntity<>(HttpStatus.OK);

        } catch (IOException e) {
            log.error(String.format(
                    "IOException occurred while attempting to delete artifact from repository (artifactId: %s)",
                    artifactid
            ));

            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }


    /**
     * GET /repos/{collection}/artifacts/{artifactId}: Retrieves an artifact from the repository.
     *
     * @param repository
     * @param artifactId
     * @return
     */
    public ResponseEntity reposArtifactsArtifactidGet(
            @ApiParam(value = "Repository to add artifact into", required = true) @PathVariable("repository") String repository,
            @ApiParam(value = "ArtifactData ID", required = true) @PathVariable("artifactid") String artifactId
    ) {
        log.info(String.format("Retrieving artifact: %s from collection %s", artifactId, repository));

        try {
            // Make sure the artifact exists
            if (!repo.artifactExists(artifactId))
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);

            // Retrieve the ArtifactData from the artifact store
            ArtifactData artifactData = repo.getArtifactData(repository, artifactId);

            // Setup HTTP response headers
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType("application/http; msgtype=response"));
            headers.setContentLength(artifactData.getContentLength());

            // Include LOCKSS repository headers in the HTTP response
            ArtifactIdentifier id = artifactData.getIdentifier();
            headers.set(ArtifactConstants.ARTIFACTID_ID_KEY, id.getId());
            headers.set(ArtifactConstants.ARTIFACTID_COLLECTION_KEY, id.getCollection());
            headers.set(ArtifactConstants.ARTIFACTID_AUID_KEY, id.getAuid());
            headers.set(ArtifactConstants.ARTIFACTID_URI_KEY, id.getUri());
            headers.set(ArtifactConstants.ARTIFACTID_VERSION_KEY, String.valueOf(id.getVersion()));

            return new ResponseEntity<>(
                    outputStream -> {
                        try {
                            ArtifactDataUtil.writeHttpResponse(
                                    ArtifactDataUtil.getHttpResponseFromArtifact(
                                            artifactData.getIdentifier(),
                                            artifactData.getHttpStatus(),
                                            artifactData.getMetadata(),
                                            artifactData.getInputStream()
                                    ),
                                    outputStream
                            );
                        } catch (HttpException e) {
                            e.printStackTrace();
                        }
                    },
                    headers,
                    HttpStatus.OK
            );
        } catch (IOException e) {
            log.error(String.format(
                    "IOException occurred while attempting to retrieve artifact from repository (artifactId: %s): %s",
                    artifactId,
                    e
            ));

            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * Implementation of PUT on /repos/{repository}/artifacts/{artifactId}: Updates an artifact's properties
     *
     * Currently limited to updating an artifact's committed status.
     *
     * @param repository
     * @param artifactId
     * @param committed
     * @return
     */
    public ResponseEntity reposArtifactsArtifactidPut(
            @ApiParam(value = "Repository to add artifact into",required=true ) @PathVariable("repository") String repository,
            @ApiParam(value = "ArtifactData ID",required=true ) @PathVariable("artifactid") String artifactId,
            @ApiParam(value = "New commit status of artifact") @RequestPart(value="committed", required=false) Boolean committed
    ) {
        // Return bad request if new commit status has not been passed
        if (committed == null)
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);

        try {
            // Make sure that the artifact exists
            if (!repo.artifactExists(artifactId))
                return new ResponseEntity<>(HttpStatus.NOT_FOUND);

            log.info(String.format(
                    "Updating commit status for %s (%s -> %s)",
                    artifactId,
                    repo.isArtifactCommitted(artifactId),
                    committed
            ));

            // Record the commit status in storage
            repo.commitArtifact(repository, artifactId);
        } catch (IOException e) {
            log.error(String.format(
                    "IOException occurred while attempting to update artifact metadata (artifactId: %s): %s",
                    artifactId,
                    e
            ));

            return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
        }

        return new ResponseEntity<>(HttpStatus.OK);
    }

    /**
     * Implementation of GET on /repos/{repository}/artifacts: Queries the repository for artifacts
     *
     * @param repository
     * @param artifact
     * @param auid
     * @param uri
     * @param aspect
     * @param timestamp
     * @param acquired
     * @param hash
     * @param committed
     * @param includeAllAspects
     * @param includeAllVersions
     * @param limit
     * @param nextArtifact
     * @return
     */
    public ResponseEntity> reposArtifactsGet(
            @ApiParam(value = "Collection ID",required=true ) @PathVariable("repository") String repository,
            @ApiParam(value = "ArtifactData ID") @RequestParam(value = "artifact", required = false) String artifact,
            @ApiParam(value = "ArtifactData AUID") @RequestParam(value = "auid", required = false) String auid,
            @ApiParam(value = "ArtifactData URI") @RequestParam(value = "uri", required = false) String uri,
            @ApiParam(value = "ArtifactData aspect") @RequestParam(value = "aspect", required = false) String aspect,
            @ApiParam(value = "Date and time associated with artifact's content") @RequestParam(value = "timestamp", required = false) Integer timestamp,
            @ApiParam(value = "Date and time of artifact acquisition into repository") @RequestParam(value = "acquired", required = false) Integer acquired,
            @ApiParam(value = "ArtifactData content digest") @RequestParam(value = "hash", required = false) String hash,
            @ApiParam(value = "ArtifactData committed status", defaultValue = "true") @RequestParam(value = "committed", required = false, defaultValue="true") Boolean committed,
            @ApiParam(value = "Include artifact aspects in results (default: false)", defaultValue = "false") @RequestParam(value = "includeAllAspects", required = false, defaultValue="false") Boolean includeAllAspects,
            @ApiParam(value = "Includes all versions if set (default: false)", defaultValue = "false") @RequestParam(value = "includeAllVersions", required = false, defaultValue="false") Boolean includeAllVersions,
            @ApiParam(value = "Maximum number of results to return (used for pagination)", defaultValue = "1000") @RequestParam(value = "limit", required = false, defaultValue="1000") Integer limit,
            @ApiParam(value = "Begin listing with given artifact (used for pagination)") @RequestParam(value = "next_artifact", required = false) String nextArtifact
    ) {

//        ArtifactPredicateBuilder query = new ArtifactPredicateBuilder()
//                .filterByCommitStatus(committed)
//                .filterByCollection(repository)
//                .filterByAuid(auid)
//                .filterByURIPrefix(uri);


//        Iterator result = repo.queryArtifacts(query);

//        List artifacts = new ArrayList<>();
//        result.forEachRemaining(x -> artifacts.add(x.getId()));
//        return new ResponseEntity<>(artifacts, HttpStatus.OK);

        List artifacts = new ArrayList();
        artifacts.add("Not implemented");
        return new ResponseEntity<>(artifacts, HttpStatus.OK);

        /*
        // Begin with a default query that matches everything
        Query query = new SimpleQuery(new Criteria(Criteria.WILDCARD).expression(Criteria.WILDCARD));

        // Criteria on committed status: By default, return only artifacts that have been committed to the repository
        query.addCriteria(new Criteria("committed").is(committed));

        // Set maximum rows per page of results
        query.setRows(limit);

        // Set optional criteria
        if (artifact != null) query.addCriteria(new Criteria("id").is(artifact));
        if (auid != null) query.addCriteria(new Criteria("auid").is(auid));
        if (uri != null) query.addCriteria(new Criteria("uri").startsWith(uri));
        if (aspect != null) query.addCriteria(new Criteria("aspect").is(aspect));
        if (timestamp != null) query.addCriteria(new Criteria("timestamp").greaterThanEqual(timestamp));
        if (acquired != null) query.addCriteria(new Criteria("acquired").greaterThanEqual(acquired));
        if (hash != null) query.addCriteria(new Criteria("content_hash").is(hash));

        // TODO: Implement paging
        // query.setPageRequest(page.nextPageable());
        // query.setPageRequest();

        Page page = solrTemplate.query(query, SolrArtifactIndexData.class);

        // TODO: Iterate and page through the Solr query result pages until we have fulfilled the our page request

        // Get the first and last element of
        page.getContent().get(0); // First element
        page.getContent().get(page.getNumberOfElements()); // Last element

        // Return "no content" if the query came back with no results
        //if (page.getTotalElements() == 0) {
        //    return new ResponseEntity>(HttpStatus.NO_CONTENT);
        //}

        // Do paging and HATEOAS type stuff here:
        MultiValueMap headers = new LinkedMultiValueMap();

        if (page.hasNext()) {
            headers.add("X-LOCKSS-next", page.nextPageable().toString());
        }

        // HATEOAS stuff
        //headers.add("X-LOCKSS-next", "http://localhost:8080/repos/" + repository + "/");
        //headers.add("X-LOCKSS-prev", "http://localhost:8080/repos/" + repository + "/");
        //headers.add("X-LOCKSS-total", String.valueOf(page.getTotalElements()));
        //headers.add("X-LOCKSS-count", String.valueOf(page.getNumberOfElements()));

        // Build an ArtifactPage to return results of repository query
        ArtifactPage ap = new ArtifactPage();
        ap.setNext("http://localhost/next");
        ap.setPrev("http://localhost/next");
        ap.setResults((int)page.getTotalElements());
        ap.setPage(page.getNumberOfElements());
        //ap.setItems(page.getContent());

        return new ResponseEntity(ap, headers, HttpStatus.OK);
        */

    }

    /**
     * Implementation of POST on /repos/{repository}/artifacts: Adds artifacts to the repository
     *
     * @param repository
     * @param auid
     * @param uri
     * @return
     */
     public ResponseEntity reposArtifactsPost(
            @ApiParam(value = "",required=true ) @PathVariable("repository") String repository,
            @ApiParam(value = "Archival Unit ID (AUID) of new artifact", required=true) @RequestPart(value="auid", required=true) String auid,
            @ApiParam(value = "URI represented by this artifact", required=true) @RequestPart(value="uri", required=true) String uri,
            @ApiParam(value = "ArtifactData version", required=true) @RequestPart(value="version", required=true) Integer version,
            @ApiParam(value = "ArtifactData") @RequestPart(value = "artifact", required=true) MultipartFile artifactPart,
            @ApiParam(value = "Aspects") @RequestPart("aspects") MultipartFile... aspectParts
     ) {

        log.info(String.format("Adding artifact %s, %s, %s, %d", repository, auid, uri, version));

        try {
            log.info(String.format("MultipartFile: Type: ArtifactData, Content-type: %s", artifactPart.getContentType()));

            // Only accept artifact encoded within an HTTP response
            if (!isHttpResponseType(MediaType.parseMediaType(artifactPart.getContentType()))) {

                log.error(String.format("Failed to add artifact; expected %s but got %s",
                        APPLICATION_HTTP_RESPONSE,
                        MediaType.parseMediaType(artifactPart.getContentType())));

                return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
            }

            // Convert multipart stream to ArtifactData
            ArtifactData artifactData = ArtifactDataFactory.fromHttpResponseStream(artifactPart.getInputStream());

            // Set ArtifactData properties from the POST request
            ArtifactIdentifier id = new ArtifactIdentifier(repository, auid, uri, version);
            artifactData.setIdentifier(id);
            artifactData.setContentLength(artifactPart.getSize());

            Artifact artifact = repo.addArtifact(artifactData);

            log.info(String.format("Wrote artifact to %s", artifactData.getStorageUrl()));

            // TODO: Process artifact's aspects
            for (MultipartFile aspectPart : aspectParts) {
                log.warn(String.format("Ignoring MultipartFile: Type: Aspect, Content-type: %s", aspectPart.getContentType()));
                //log.info(IOUtils.toString(aspectPart.getInputStream()));
                //log.info(aspectPart.getName());

                /*
                // Augment custom metadata headers with headers from HTTP response
                for (Header header : response.getAllHeaders()) {
                    headers.add(header.getName(), header.getValue());
                }

                // Set content stream and its properties
                artifactMetadata.setContentType(response.getEntity().getContentType().getValue());
                artifactMetadata.setContentLength((int) response.getEntity().getContentLength());
                artifactMetadata.setContentDate(0);
                artifactMetadata.setLastModified(0);
                artifactMetadata.setContentHash(null);

                // Create an artifactIndex
                SolrArtifactIndexData info = artifactStore.addArtifact(artifactMetadata, response.getEntity().getContent());
                artifactIndexRepository.save(info);
                */
            }

            return new ResponseEntity<>(artifact, HttpStatus.OK);
        } catch (IOException e) {
            log.error("Caught IOException while attempting to add an artifact to the repository");
        }

        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }

    private static Boolean isHttpResponseType(MediaType type) {
        return (APPLICATION_HTTP_RESPONSE.isCompatibleWith(type)
                && (type.getParameters().equals(APPLICATION_HTTP_RESPONSE.getParameters())));
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy