org.eclipse.rdf4j.repository.http.HTTPRepository.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.rdf4j.repository.http.HTTPRepository.java

Source

/*******************************************************************************
 * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Distribution License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *******************************************************************************/
package org.eclipse.rdf4j.repository.http;

import java.io.File;
import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.client.HttpClient;
import org.eclipse.rdf4j.http.client.HttpClientDependent;
import org.eclipse.rdf4j.http.client.HttpClientSessionManager;
import org.eclipse.rdf4j.http.client.SessionManagerDependent;
import org.eclipse.rdf4j.http.client.SharedHttpClientSessionManager;
import org.eclipse.rdf4j.http.client.RDF4JProtocolSession;
import org.eclipse.rdf4j.http.client.SPARQLProtocolSession;
import org.eclipse.rdf4j.http.protocol.Protocol;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.util.Literals;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.query.TupleQueryResult;
import org.eclipse.rdf4j.query.resultio.TupleQueryResultFormat;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.base.AbstractRepository;
import org.eclipse.rdf4j.rio.RDFFormat;

/**
 * A repository that serves as a proxy for a remote repository on a Sesame Server. Methods in this class may
 * throw the specific RepositoryException subclass UnautorizedException, the semantics of which is defined by
 * the HTTP protocol.
 * <p>
 * This repository proxy uses a <a href="http://docs.rdf4j.org/rest-api/">Sesame-specific extension of the SPARQL
 * 1.1 Protocol</a> to communicate with the server. For communicating with a non-Sesame-based SPARQL endpoint,
 * it is recommend to use {@link org.eclipse.rdf4j.repository.sparql.SPARQLRepository SPARQLRepository}
 * instead.
 *
 * @see org.eclipse.rdf4j.http.protocol.UnauthorizedException
 * @author Arjohn Kampman
 * @author Jeen Broekstra
 * @author Herko ter Horst
 */
public class HTTPRepository extends AbstractRepository implements HttpClientDependent, SessionManagerDependent {

    /*-----------*
     * Variables *
     *-----------*/

    /**
     * The HTTP client that takes care of the client-server communication.
     */
    private volatile HttpClientSessionManager client;

    /** dependent life cycle */
    private volatile SharedHttpClientSessionManager dependentClient;

    private String username;

    private String password;

    private String serverURL;

    private String repositoryURL;

    private RDFFormat rdfFormat;

    private TupleQueryResultFormat tupleFormat;

    private File dataDir;

    private volatile Boolean compatibleMode = null;

    /*--------------*
     * Constructors *
     *--------------*/

    private HTTPRepository() {
        super();
    }

    public HTTPRepository(final String serverURL, final String repositoryID) {
        this();
        this.serverURL = serverURL;
        this.repositoryURL = Protocol.getRepositoryLocation(serverURL, repositoryID);
    }

    public HTTPRepository(final String repositoryURL) {
        this();
        // Try to parse the server URL from the repository URL
        Pattern urlPattern = Pattern.compile("(.*)/" + Protocol.REPOSITORIES + "/[^/]*/?");
        Matcher matcher = urlPattern.matcher(repositoryURL);

        if (matcher.matches() && matcher.groupCount() == 1) {
            this.serverURL = matcher.group(1);
        } else {
            throw new IllegalArgumentException("URL must be to a Sesame Repository (not just the server)");
        }
        this.repositoryURL = repositoryURL;
    }

    /*
     * ---------------* public methods * ---------------
     */

    @Override
    public void setDataDir(final File dataDir) {
        this.dataDir = dataDir;
    }

    @Override
    public File getDataDir() {
        return dataDir;
    }

    @Override
    public HttpClientSessionManager getHttpClientSessionManager() {
        HttpClientSessionManager result = client;
        if (result == null) {
            synchronized (this) {
                result = client;
                if (result == null) {
                    result = client = dependentClient = new SharedHttpClientSessionManager();
                }
            }
        }
        return result;
    }

    @Override
    public void setHttpClientSessionManager(HttpClientSessionManager client) {
        synchronized (this) {
            this.client = client;
            // If they set a client, we need to check whether we need to
            // shutdown any existing dependentClient
            SharedHttpClientSessionManager toCloseDependentClient = dependentClient;
            dependentClient = null;
            if (toCloseDependentClient != null) {
                toCloseDependentClient.shutDown();
            }
        }
    }

    @Override
    public final HttpClient getHttpClient() {
        return getHttpClientSessionManager().getHttpClient();
    }

    @Override
    public void setHttpClient(HttpClient httpClient) {
        SharedHttpClientSessionManager toSetDependentClient = dependentClient;
        if (toSetDependentClient == null) {
            getHttpClientSessionManager();
            toSetDependentClient = dependentClient;
        }
        // The strange lifecycle results in the possibility that the
        // dependentClient will be null due to a call to setSesameClient, so add
        // a null guard here for that possibility
        if (toSetDependentClient != null) {
            toSetDependentClient.setHttpClient(httpClient);
        }
    }

    @Override
    public ValueFactory getValueFactory() {
        return SimpleValueFactory.getInstance();
    }

    @Override
    public RepositoryConnection getConnection() throws RepositoryException {
        return new HTTPRepositoryConnection(this, createHTTPClient());
    }

    @Override
    public boolean isWritable() throws RepositoryException {
        if (!isInitialized()) {
            throw new IllegalStateException("HTTPRepository not initialized.");
        }

        boolean isWritable = false;
        final String repositoryURL = createHTTPClient().getRepositoryURL();

        try {
            final TupleQueryResult repositoryList = createHTTPClient().getRepositoryList();
            try {
                while (repositoryList.hasNext()) {
                    final BindingSet bindingSet = repositoryList.next();
                    final Value uri = bindingSet.getValue("uri");

                    if (uri != null && uri.stringValue().equals(repositoryURL)) {
                        isWritable = Literals.getBooleanValue(bindingSet.getValue("writable"), false);
                        break;
                    }
                }
            } finally {
                repositoryList.close();
            }
        } catch (QueryEvaluationException e) {
            throw new RepositoryException(e);
        } catch (IOException e) {
            throw new RepositoryException(e);
        }

        return isWritable;
    }

    /**
     * Sets the preferred serialization format for tuple query results to the supplied
     * {@link TupleQueryResultFormat}, overriding the {@link SPARQLProtocolSession} 's default preference. Setting
     * this parameter is not necessary in most cases as the {@link SPARQLProtocolSession} by default indicates a
     * preference for the most compact and efficient format available.
     * 
     * @param format
     *        the preferred {@link TupleQueryResultFormat}. If set to 'null' no explicit preference will be
     *        stated.
     */
    public void setPreferredTupleQueryResultFormat(final TupleQueryResultFormat format) {
        this.tupleFormat = format;
    }

    /**
     * Indicates the current preferred {@link TupleQueryResultFormat}.
     * 
     * @return The preferred format, of 'null' if no explicit preference is defined.
     */
    public TupleQueryResultFormat getPreferredTupleQueryResultFormat() {
        return tupleFormat;
    }

    /**
     * Sets the preferred serialization format for RDF to the supplied {@link RDFFormat}, overriding the
     * {@link SPARQLProtocolSession}'s default preference. Setting this parameter is not necessary in most cases as
     * the {@link SPARQLProtocolSession} by default indicates a preference for the most compact and efficient format
     * available.
     * <p>
     * Use with caution: if set to a format that does not support context serialization any context info
     * contained in the query result will be lost.
     * 
     * @param format
     *        the preferred {@link RDFFormat}. If set to 'null' no explicit preference will be stated.
     */
    public void setPreferredRDFFormat(final RDFFormat format) {
        this.rdfFormat = format;
    }

    /**
     * Indicates the current preferred {@link RDFFormat}.
     * 
     * @return The preferred format, of 'null' if no explicit preference is defined.
     */
    public RDFFormat getPreferredRDFFormat() {
        return rdfFormat;
    }

    /**
     * Set the username and password to use for authenticating with the remote repository.
     * 
     * @param username
     *        the username. Setting this to null will disable authentication.
     * @param password
     *        the password. Setting this to null will disable authentication.
     */
    public void setUsernameAndPassword(final String username, final String password) {
        this.username = username;
        this.password = password;
    }

    public String getRepositoryURL() {
        return repositoryURL;
    }

    /*
     * -------------------* non-public methods * -------------------
     */

    @Override
    protected void initializeInternal() throws RepositoryException {
        // no-op
    }

    @Override
    protected void shutDownInternal() throws RepositoryException {
        try {
            SharedHttpClientSessionManager toCloseDependentClient = dependentClient;
            dependentClient = null;
            if (toCloseDependentClient != null) {
                toCloseDependentClient.shutDown();
            }
        } finally {
            // remove reference but do not shut down, client may be shared by
            // other repos.
            client = null;
        }
    }

    /**
     * Creates a new HTTPClient object. Subclasses may override to return a more specific HTTPClient subtype.
     * 
     * @return a HTTPClient object.
     */
    protected RDF4JProtocolSession createHTTPClient() {
        // initialize HTTP client
        RDF4JProtocolSession httpClient = getHttpClientSessionManager().createRDF4JProtocolSession(serverURL);
        httpClient.setValueFactory(SimpleValueFactory.getInstance());
        if (repositoryURL != null) {
            httpClient.setRepository(repositoryURL);
        }
        if (tupleFormat != null) {
            httpClient.setPreferredTupleQueryResultFormat(tupleFormat);
        }
        if (rdfFormat != null) {
            httpClient.setPreferredRDFFormat(rdfFormat);
        }
        if (username != null) {
            httpClient.setUsernameAndPassword(username, password);
        }
        return httpClient;
    }

    /**
     * Verify if transaction handling should be done in backward-compatible mode (this is the case when
     * communicating with an older Sesame Server).
     * 
     * @return <code>true</code> if the Server does not support the extended transaction protocol,
     *         <code>false</code> otherwise.
     * @throws RepositoryException
     *         if something went wrong while querying the server for the protocol version.
     */
    boolean useCompatibleMode() throws RepositoryException {
        Boolean result = compatibleMode;
        if (result == null) {
            synchronized (this) {
                result = compatibleMode;
                if (result == null) {
                    try {
                        final String serverProtocolVersion = createHTTPClient().getServerProtocol();

                        // protocol version 7 supports the new transaction
                        // handling. If the server is older, we need to run in
                        // backward-compatible mode.
                        result = compatibleMode = (Integer.parseInt(serverProtocolVersion) < 7);
                    } catch (NumberFormatException e) {
                        throw new RepositoryException("could not read protocol version from server: ", e);
                    } catch (IOException e) {
                        throw new RepositoryException("could not read protocol version from server: ", e);
                    }
                }
            }
        }
        return result;
    }
}