org.fcrepo.apix.binding.impl.RuntimeExtensionBinding.java Source code

Java tutorial

Introduction

Here is the source code for org.fcrepo.apix.binding.impl.RuntimeExtensionBinding.java

Source

/*
 * Licensed to DuraSpace under one or more contributor license agreements.
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * DuraSpace licenses this file to you 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.fcrepo.apix.binding.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.fcrepo.apix.model.Extension;
import org.fcrepo.apix.model.WebResource;
import org.fcrepo.apix.model.components.ExtensionBinding;
import org.fcrepo.apix.model.components.ExtensionRegistry;
import org.fcrepo.apix.model.components.OntologyService;
import org.fcrepo.apix.model.components.Registry;
import org.fcrepo.apix.model.components.ResourceNotFoundException;
import org.fcrepo.client.FcrepoLink;

import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.impl.client.CloseableHttpClient;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Simple extension binding based on runtime lookup and reasoning.
 * <p>
 * Polls the extension registry for all known extensions at time of transaction, and performs reasoning at runtime in
 * order to bind a repository resource to the extensions that match it.
 * </p>
 *
 * @author apb@jhu.edu
 */
@Component(configurationPolicy = ConfigurationPolicy.REQUIRE)
public class RuntimeExtensionBinding implements ExtensionBinding {

    private static final Logger LOG = LoggerFactory.getLogger(RuntimeExtensionBinding.class);

    private CloseableHttpClient httpClient;

    private ExtensionRegistry extensionRegistry;

    private OntologyService ontologySvc;

    private Registry registry;

    /**
     * Set the http client
     *
     * @param client client;
     */
    public void setHttpClient(final CloseableHttpClient client) {
        this.httpClient = client;
    }

    /**
     * Set the underlying registry containing extensions that may be bound.
     *
     * @param exr Extension registry instance.
     */
    @Reference
    public void setExtensionRegistry(final ExtensionRegistry exr) {
        extensionRegistry = exr;
    }

    /**
     * Set the underlying ontology service for performing reasoning for binding.
     *
     * @param os Ontology service instance.
     */
    @Reference
    public void setOntologyService(final OntologyService os) {
        ontologySvc = os;
    }

    /**
     * Set the underlying delegate registry for retrieving arbitrary web resources from the repository.
     * <p>
     * This registry is consulted in support of {@link #getExtensionsFor(URI)}
     * </p>
     *
     * @param registry Registry impl.
     */
    @Reference(target = "(org.fcrepo.apix.registry.role=default)")
    public void setDelegateRegistry(final Registry registry) {
        this.registry = registry;
    }

    /**
     * Simple/naive binding algorithm, there may be opportunities for optimization when the time is right
     * <ol>
     * <li>Determine the set of known extensions</li>
     * <li>for each extension, get its ontology closure</li>
     * <li>for each ontology closure, infer classes of the instance</li>
     * <li>For each extension, see if its binding class is in that list of inferred classes.</li>
     * <li>Return all extensions that match</li>
     * </ol>
     */
    @Override
    public Collection<Extension> getExtensionsFor(final WebResource resource) {

        return getExtensionsFor(resource, extensionRegistry.getExtensions());
    }

    @Override
    public Collection<Extension> getExtensionsFor(final WebResource resource,
            final Collection<Extension> extensions) {

        try (final InputStream resourceContent = resource.representation()) {

            final byte[] content = IOUtils.toByteArray(resourceContent);

            final Set<URI> rdfTypes = extensions.stream().flatMap(RuntimeExtensionBinding::getExtensionResource)
                    .peek(r -> LOG.debug("Examinining the ontology closure of extension {}", r.uri()))
                    .map(ontologySvc::parseOntology)
                    .flatMap(o -> ontologySvc.inferClasses(resource.uri(), cached(resource, content), o).stream())
                    .peek(rdfType -> LOG.debug("Instance {} is of class {}", resource.uri(), rdfType))
                    .collect(Collectors.toSet());

            return extensions.stream()
                    .peek(e -> LOG.debug("Extension {} binds to instances of {}", e.uri(), e.bindingClass()))
                    .filter(e -> rdfTypes.contains(e.bindingClass()))
                    .peek(e -> LOG.debug("Extension {} bound to instance {} via {}", e.uri(), resource.uri(),
                            e.bindingClass()))
                    .collect(Collectors.toList());
        } catch (final IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static Stream<WebResource> getExtensionResource(final Extension e) {

        try {
            return Stream.of(e.getResource());
        } catch (final ResourceNotFoundException x) {
            return Stream.empty();
        }
    }

    private WebResource cached(final WebResource initial, final byte[] content) {
        return WebResource.of(new ByteArrayInputStream(content), initial.contentType(), initial.uri(),
                initial.name());
    }

    /** Just does a dumb dereference and lookup */
    @Override
    public Collection<Extension> getExtensionsFor(final URI resourceURI, final Collection<Extension> from) {

        if (from.isEmpty()) {
            return Collections.emptyList();
        }

        // Use object contents for reasoning, or if binary the binary's description
        try (final CloseableHttpResponse response = httpClient.execute(new HttpHead(resourceURI))) {

            if (response.getStatusLine().getStatusCode() != 200) {
                throw new RuntimeException(String.format("Got unexpected status code %s in HEAD to <%s>",
                        response.getStatusLine().getStatusCode(), resourceURI));
            }

            final List<FcrepoLink> describedByLinks = Arrays.asList(response.getHeaders("Link")).stream()
                    .map(Header::getValue).map(FcrepoLink::new).filter(l -> "describedby".equals(l.getRel()))
                    .collect(Collectors.toList());

            if (!describedByLinks.isEmpty()) {
                if (describedByLinks.size() > 1) {
                    throw new RuntimeException(
                            String.format("Ambiguous; more than one describes header for <%s>", resourceURI));
                }

                LOG.debug("Using <{}> for inference about binary <{}>", describedByLinks.get(0).getUri(),
                        resourceURI);

                try (WebResource resource = registry.get(describedByLinks.get(0).getUri())) {
                    return getExtensionsFor(
                            WebResource.of(resource.representation(), resource.contentType(), resourceURI, null),
                            from);
                }

            } else {
                try (WebResource resource = registry.get(resourceURI)) {
                    return getExtensionsFor(resource, from);
                }
            }

        } catch (final Exception e) {
            throw new RuntimeException("Could not get triples for reasoning over " + resourceURI, e);
        }
    }

    /** Just does a dumb dereference and lookup */
    @Override
    public Collection<Extension> getExtensionsFor(final URI resourceURI) {
        return getExtensionsFor(resourceURI, extensionRegistry.getExtensions());
    }
}