org.trellisldp.http.impl.MementoResource.java Source code

Java tutorial

Introduction

Here is the source code for org.trellisldp.http.impl.MementoResource.java

Source

/*
 * 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 org.trellisldp.http.impl;

import static java.lang.String.join;
import static java.time.ZoneOffset.UTC;
import static java.time.ZonedDateTime.ofInstant;
import static java.time.ZonedDateTime.parse;
import static java.time.format.DateTimeFormatter.RFC_1123_DATE_TIME;
import static java.util.Objects.nonNull;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.concat;
import static javax.ws.rs.HttpMethod.GET;
import static javax.ws.rs.HttpMethod.HEAD;
import static javax.ws.rs.HttpMethod.OPTIONS;
import static javax.ws.rs.core.HttpHeaders.ALLOW;
import static javax.ws.rs.core.HttpHeaders.VARY;
import static javax.ws.rs.core.Response.Status.FOUND;
import static javax.ws.rs.core.UriBuilder.fromUri;
import static org.trellisldp.http.domain.HttpConstants.ACCEPT_DATETIME;
import static org.trellisldp.http.domain.HttpConstants.APPLICATION_LINK_FORMAT;
import static org.trellisldp.http.impl.RdfUtils.getProfile;
import static org.trellisldp.http.impl.RdfUtils.getSyntax;
import static org.trellisldp.vocabulary.JSONLD.expanded;
import static org.trellisldp.vocabulary.LDP.RDFSource;
import static org.trellisldp.vocabulary.LDP.Resource;
import static org.trellisldp.vocabulary.Memento.mementoDatetime;
import static org.trellisldp.vocabulary.Memento.timegate;
import static org.trellisldp.vocabulary.Memento.timemap;
import static org.trellisldp.vocabulary.RDF.type;
import static org.trellisldp.vocabulary.XSD.dateTime;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.function.Function;
import java.util.stream.Stream;

import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;

import org.apache.commons.rdf.api.IRI;
import org.apache.commons.rdf.api.RDF;
import org.apache.commons.rdf.api.RDFSyntax;
import org.apache.commons.rdf.api.Triple;

import org.trellisldp.api.IOService;
import org.trellisldp.api.Resource;
import org.trellisldp.api.VersionRange;
import org.trellisldp.http.domain.LdpRequest;
import org.trellisldp.vocabulary.Memento;
import org.trellisldp.vocabulary.Time;

/**
 * @author acoburn
 */
public final class MementoResource {

    private static final RDF rdf = ServiceLoader.load(RDF.class).iterator().next();

    private static final String ORIGINAL = "original";

    private static final String TIMEGATE = "timegate";

    private static final String TIMEMAP = "timemap";

    private static final String MEMENTO = "memento";

    private static final String FROM = "from";

    private static final String UNTIL = "until";

    private static final String DATETIME = "datetime";

    private static final String TIMEMAP_PARAM = "?ext=timemap";

    private final Resource resource;

    /**
     * Wrap a resource in some Memento-specific response builders
     * @param resource the resource
     */
    public MementoResource(final Resource resource) {
        this.resource = resource;
    }

    /**
     * Create a response builder for a TimeMap response
     * @param baseUrl the base URL
     * @param req the LDP request
     * @param serializer the serializer to use
     * @return a response builder object
     */
    public Response.ResponseBuilder getTimeMapBuilder(final LdpRequest req, final IOService serializer,
            final String baseUrl) {

        final List<MediaType> acceptableTypes = req.getHeaders().getAcceptableMediaTypes();
        final String identifier = getBaseUrl(baseUrl, req) + req.getPartition() + req.getPath();
        final List<Link> links = getMementoLinks(identifier, resource.getMementos()).collect(toList());

        final Response.ResponseBuilder builder = Response.ok().link(identifier, ORIGINAL + " " + TIMEGATE);
        builder.links(links.toArray(new Link[0])).link(Resource.getIRIString(), "type")
                .link(RDFSource.getIRIString(), "type").header(ALLOW, join(",", GET, HEAD, OPTIONS));

        final RDFSyntax syntax = getSyntax(acceptableTypes, of(APPLICATION_LINK_FORMAT)).orElse(null);
        if (nonNull(syntax)) {
            final IRI profile = ofNullable(getProfile(acceptableTypes, syntax)).orElse(expanded);

            final List<Triple> extraData = getExtraTriples(identifier);
            for (final Link l : links) {
                if (l.getRels().contains(MEMENTO)) {
                    extraData.add(rdf.createTriple(rdf.createIRI(identifier), Memento.memento,
                            rdf.createIRI(l.getUri().toString())));
                }
            }

            final StreamingOutput stream = new StreamingOutput() {
                @Override
                public void write(final OutputStream out) throws IOException {
                    serializer.write(concat(links.stream().flatMap(linkToTriples), extraData.stream()), out, syntax,
                            profile);
                }
            };

            return builder.type(syntax.mediaType).entity(stream);
        }

        return builder.type(APPLICATION_LINK_FORMAT)
                .entity(links.stream().map(Link::toString).collect(joining(",\n")) + "\n");
    }

    private static List<Triple> getExtraTriples(final String identifier) {
        final IRI originalResource = rdf.createIRI(identifier);
        final List<Triple> extraData = new ArrayList<>();

        extraData.add(rdf.createTriple(originalResource, type, Memento.OriginalResource));
        extraData.add(rdf.createTriple(originalResource, type, Memento.TimeGate));
        extraData.add(rdf.createTriple(originalResource, timegate, originalResource));
        extraData.add(rdf.createTriple(originalResource, timemap, rdf.createIRI(identifier + TIMEMAP_PARAM)));

        return extraData;
    }

    /**
     * Create a response builder for a TimeGate response
     * @param req the LDP request
     * @param baseUrl the base URL
     * @return a response builder object
     */
    public Response.ResponseBuilder getTimeGateBuilder(final LdpRequest req, final String baseUrl) {
        final String identifier = getBaseUrl(baseUrl, req) + req.getPartition() + req.getPath();
        return Response.status(FOUND)
                .location(fromUri(identifier + "?version=" + req.getDatetime().getInstant().toEpochMilli()).build())
                .link(identifier, ORIGINAL + " " + TIMEGATE)
                .links(getMementoLinks(identifier, resource.getMementos()).toArray(Link[]::new))
                .header(VARY, ACCEPT_DATETIME);
    }

    /**
     * Retrieve all of the Memento-related link headers given a stream of VersionRange objects
     * @param identifier the public identifier for the resource
     * @param mementos a stream of memento values
     * @return a stream of link headers
     */
    public static Stream<Link> getMementoLinks(final String identifier, final List<VersionRange> mementos) {
        return concat(getTimeMap(identifier, mementos.stream()), mementos.stream().map(mementoToLink(identifier)));
    }

    private String getBaseUrl(final String baseUrl, final LdpRequest req) {
        return ofNullable(baseUrl).orElseGet(req::getBaseUrl);
    }

    private static final Function<Link, Stream<Triple>> linkToTriples = link -> {
        final String linkUri = link.getUri().toString();
        final IRI iri = rdf.createIRI(linkUri);
        final List<Triple> buffer = new ArrayList<>();
        final String timeIriPrefix = "http://reference.data.gov.uk/id/gregorian-instant/";

        // TimeMap quads
        if (link.getParams().containsKey(FROM)) {
            buffer.add(rdf.createTriple(iri, type, Memento.TimeMap));
            buffer.add(rdf.createTriple(iri, Time.hasBeginning, rdf
                    .createIRI(timeIriPrefix + parse(link.getParams().get(FROM), RFC_1123_DATE_TIME).toString())));
        }
        if (link.getParams().containsKey(UNTIL)) {
            buffer.add(rdf.createTriple(iri, Time.hasEnd, rdf
                    .createIRI(timeIriPrefix + parse(link.getParams().get(UNTIL), RFC_1123_DATE_TIME).toString())));
        }

        // Quads for Mementos
        if (MEMENTO.equals(link.getRel()) && link.getParams().containsKey(DATETIME)) {
            final IRI original = rdf.createIRI(linkUri.split("\\?")[0]);
            final IRI timemapUrl = rdf.createIRI(linkUri.split("\\?")[0] + TIMEMAP_PARAM);
            buffer.add(rdf.createTriple(iri, type, Memento.Memento));
            buffer.add(rdf.createTriple(iri, Memento.original, original));
            buffer.add(rdf.createTriple(iri, timegate, original));
            buffer.add(rdf.createTriple(iri, timemap, timemapUrl));
            buffer.add(rdf.createTriple(iri, Time.hasTime, rdf.createIRI(
                    timeIriPrefix + parse(link.getParams().get(DATETIME), RFC_1123_DATE_TIME).toString())));
            buffer.add(rdf.createTriple(iri, mementoDatetime, rdf.createLiteral(
                    parse(link.getParams().get(DATETIME), RFC_1123_DATE_TIME).toString(), dateTime)));
        }
        return buffer.stream();
    };

    private static Stream<Link> getTimeMap(final String identifier, final Stream<VersionRange> mementos) {
        return mementos.reduce((acc, x) -> new VersionRange(acc.getFrom(), x.getUntil())).map(x -> Link
                .fromUri(identifier + TIMEMAP_PARAM).rel(TIMEMAP).type(APPLICATION_LINK_FORMAT)
                .param(FROM, ofInstant(x.getFrom().minusNanos(1L).plusSeconds(1L), UTC).format(RFC_1123_DATE_TIME))
                .param(UNTIL, ofInstant(x.getUntil(), UTC).format(RFC_1123_DATE_TIME)).build())
                // TODO use Optional::stream with JDK9
                .map(Stream::of).orElseGet(Stream::empty);
    }

    private static Function<VersionRange, Link> mementoToLink(final String identifier) {
        return range -> Link.fromUri(identifier + "?version=" + range.getFrom().toEpochMilli()).rel(MEMENTO)
                .param(DATETIME,
                        ofInstant(range.getFrom().minusNanos(1L).plusSeconds(1L), UTC).format(RFC_1123_DATE_TIME))
                .build();
    }
}