Package org.locationtech.geogig.remote

Source Code of org.locationtech.geogig.remote.HttpMappedRemoteRepo

/* Copyright (c) 2013-2014 Boundless 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
* https://www.eclipse.org/org/documents/edl-v10.html
*
* Contributors:
* Johnathan Garrett (LMN Solutions) - initial implementation
*/
package org.locationtech.geogig.remote;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.locationtech.geogig.api.ObjectId;
import org.locationtech.geogig.api.Ref;
import org.locationtech.geogig.api.RepositoryFilter.FilterDescription;
import org.locationtech.geogig.api.RevCommit;
import org.locationtech.geogig.api.RevObject;
import org.locationtech.geogig.api.RevObject.TYPE;
import org.locationtech.geogig.api.SymRef;
import org.locationtech.geogig.api.plumbing.CheckSparsePath;
import org.locationtech.geogig.api.plumbing.FindCommonAncestor;
import org.locationtech.geogig.api.plumbing.RevObjectParse;
import org.locationtech.geogig.api.plumbing.diff.DiffEntry;
import org.locationtech.geogig.api.porcelain.DiffOp;
import org.locationtech.geogig.repository.Repository;
import org.locationtech.geogig.storage.ObjectSerializingFactory;
import org.locationtech.geogig.storage.ObjectWriter;
import org.locationtech.geogig.storage.datastream.DataStreamSerializationFactoryV1;

import com.google.common.base.Optional;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;

/**
* Provides a means of communicating between a local sparse clone and a remote http full repository.
*
* @see AbstractMappedRemoteRepo
*/
class HttpMappedRemoteRepo extends AbstractMappedRemoteRepo {

    private URL repositoryURL;

    /**
     * Constructs a new {@code HttpMappedRemoteRepo}.
     *
     * @param repositoryURL the URL of the repository
     * @param localRepository the local sparse repository
     */
    public HttpMappedRemoteRepo(URL repositoryURL, Repository localRepository) {
        super(localRepository);
        String url = repositoryURL.toString();
        if (url.endsWith("/")) {
            url = url.substring(0, url.lastIndexOf('/'));
        }
        try {
            this.repositoryURL = new URL(url);
        } catch (MalformedURLException e) {
            this.repositoryURL = repositoryURL;
        }
    }

    /**
     * Currently does nothing for HTTP Remote.
     *
     * @throws IOException
     */
    @Override
    public void open() throws IOException {
    }

    /**
     * Currently does nothing for HTTP Remote.
     *
     * @throws IOException
     */
    @Override
    public void close() throws IOException {
    }

    /**
     * List the mapped versions of the remote's {@link Ref refs}. For example, if the remote ref
     * points to commit A, the returned ref will point to the commit that A is mapped to.
     *
     * @param getHeads whether to return refs in the {@code refs/heads} namespace
     * @param getTags whether to return refs in the {@code refs/tags} namespace
     * @return an immutable set of refs from the remote
     */
    @Override
    public ImmutableSet<Ref> listRefs(boolean getHeads, boolean getTags) {
        HttpURLConnection connection = null;
        ImmutableSet.Builder<Ref> builder = new ImmutableSet.Builder<Ref>();
        try {
            String expanded = repositoryURL.toString() + "/repo/manifest";

            connection = (HttpURLConnection) new URL(expanded).openConnection();
            connection.setRequestMethod("GET");

            connection.setUseCaches(false);
            connection.setDoOutput(true);

            // Get Response
            InputStream is = connection.getInputStream();
            BufferedReader rd = new BufferedReader(new InputStreamReader(is));
            String line;
            try {
                while ((line = rd.readLine()) != null) {
                    if ((getHeads && line.startsWith("refs/heads"))
                            || (getTags && line.startsWith("refs/tags"))) {
                        Ref remoteRef = HttpUtils.parseRef(line);
                        Ref newRef = remoteRef;
                        if (!(newRef instanceof SymRef)
                                && localRepository.graphDatabase().exists(remoteRef.getObjectId())) {
                            ObjectId mappedCommit = localRepository.graphDatabase().getMapping(
                                    remoteRef.getObjectId());
                            if (mappedCommit != null) {
                                newRef = new Ref(remoteRef.getName(), mappedCommit);
                            }
                        }
                        builder.add(newRef);
                    }
                }
            } finally {
                rd.close();
            }

        } catch (Exception e) {
            throw Throwables.propagate(e);
        } finally {
            HttpUtils.consumeErrStreamAndCloseConnection(connection);
        }
        return builder.build();
    }

    /**
     * @return the remote's HEAD {@link Ref}.
     */
    @Override
    public Ref headRef() {
        HttpURLConnection connection = null;
        Ref headRef = null;
        try {
            String expanded = repositoryURL.toString() + "/repo/manifest";

            connection = (HttpURLConnection) new URL(expanded).openConnection();
            connection.setRequestMethod("GET");

            connection.setUseCaches(false);
            connection.setDoOutput(true);

            // Get Response
            InputStream is = connection.getInputStream();
            try {
                BufferedReader rd = new BufferedReader(new InputStreamReader(is));
                String line;

                while ((line = rd.readLine()) != null) {
                    if (line.startsWith("HEAD")) {
                        headRef = HttpUtils.parseRef(line);
                    }
                }
                rd.close();
            } finally {
                is.close();
            }

        } catch (Exception e) {

            Throwables.propagate(e);

        } finally {
            HttpUtils.consumeErrStreamAndCloseConnection(connection);
        }
        return headRef;
    }

    /**
     * Delete a {@link Ref} from the remote repository.
     *
     * @param refspec the ref to delete
     */
    @Override
    public Optional<Ref> deleteRef(String refspec) {
        return updateRemoteRef(refspec, null, true);
    }

    /**
     * @return the {@link RepositoryWrapper} for this remote
     */
    @Override
    protected RepositoryWrapper getRemoteWrapper() {
        return new HttpRepositoryWrapper(repositoryURL);
    }

    /**
     * Gets all of the changes from the target commit that should be applied to the sparse clone.
     *
     * @param commit the commit to get changes from
     * @return an iterator for changes that match the repository filter
     */
    @Override
    protected FilteredDiffIterator getFilteredChanges(RevCommit commit) {
        // Get affected features
        ImmutableList<ObjectId> affectedFeatures = HttpUtils.getAffectedFeatures(repositoryURL,
                commit.getId());
        // Create a list of features I have
        List<ObjectId> tracked = new LinkedList<ObjectId>();
        for (ObjectId id : affectedFeatures) {
            if (localRepository.blobExists(id)) {
                tracked.add(id);
            }
        }
        // Get changes from commit, pass filter and my list of features
        final JsonObject message = createFetchMessage(commit.getId(), tracked);
        final URL resourceURL;
        try {
            resourceURL = new URL(repositoryURL.toString() + "/repo/filteredchanges");
        } catch (MalformedURLException e) {
            throw Throwables.propagate(e);
        }

        final Gson gson = new Gson();
        final HttpURLConnection connection;
        final OutputStream out;
        final Writer writer;
        try {
            connection = (HttpURLConnection) resourceURL.openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            out = connection.getOutputStream();
            writer = new OutputStreamWriter(out);
            gson.toJson(message, writer);
            writer.flush();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }

        final InputStream in;
        try {
            in = connection.getInputStream();
        } catch (IOException e) {
            throw Throwables.propagate(e);
        }

        BinaryPackedChanges unpacker = new BinaryPackedChanges(localRepository);

        return new HttpFilteredDiffIterator(in, unpacker);
    }

    private JsonObject createFetchMessage(ObjectId commitId, List<ObjectId> tracked) {
        JsonObject message = new JsonObject();
        JsonArray trackedArray = new JsonArray();
        for (ObjectId id : tracked) {
            trackedArray.add(new JsonPrimitive(id.toString()));
        }
        message.add("commitId", new JsonPrimitive(commitId.toString()));
        message.add("tracked", trackedArray);
        JsonArray filterArray = new JsonArray();
        ImmutableList<FilterDescription> repoFilters = filter.getFilterDescriptions();
        for (FilterDescription description : repoFilters) {
            JsonObject typeFilter = new JsonObject();
            typeFilter.add("featurepath", new JsonPrimitive(description.getFeaturePath()));
            typeFilter.add("type", new JsonPrimitive(description.getFilterType()));
            typeFilter.add("filter", new JsonPrimitive(description.getFilter()));
            filterArray.add(typeFilter);
        }
        message.add("filter", filterArray);
        return message;
    }

    /**
     * Gets the remote ref that matches the provided ref spec.
     *
     * @param refspec the refspec to parse
     * @return the matching {@link Ref} or {@link Optional#absent()} if the ref could not be found
     */
    @Override
    protected Optional<Ref> getRemoteRef(String refspec) {
        return HttpUtils.getRemoteRef(repositoryURL, refspec);
    }

    /**
     * Perform pre-push actions.
     */
    @Override
    protected void beginPush() {
        HttpUtils.beginPush(repositoryURL);
    }

    /**
     * Perform post-push actions, this includes verification that the remote wasn't changed while we
     * were pushing.
     *
     * @param refspec the refspec that we are pushing to
     * @param newCommitId the new commit id
     * @param originalRefValue the original value of the ref before pushing
     */
    @Override
    protected void endPush(String refspec, ObjectId newCommitId, String originalRefValue) {
        HttpUtils.endPush(repositoryURL, refspec, newCommitId, originalRefValue);
    }

    /**
     * Pushes a sparse commit to a remote repository and updates all mappings.
     *
     * @param commitId the commit to push
     */
    @Override
    protected void pushSparseCommit(ObjectId commitId) {
        Repository from = localRepository;
        Optional<RevObject> object = from.command(RevObjectParse.class).setObjectId(commitId)
                .call();
        if (object.isPresent() && object.get().getType().equals(TYPE.COMMIT)) {
            RevCommit commit = (RevCommit) object.get();
            ObjectId parent = ObjectId.NULL;
            List<ObjectId> newParents = new LinkedList<ObjectId>();
            for (int i = 0; i < commit.getParentIds().size(); i++) {
                ObjectId parentId = commit.getParentIds().get(i);
                if (i != 0) {
                    Optional<ObjectId> commonAncestor = from.command(FindCommonAncestor.class)
                            .setLeftId(commit.getParentIds().get(0)).setRightId(parentId).call();
                    if (commonAncestor.isPresent()) {
                        if (from.command(CheckSparsePath.class).setStart(parentId)
                                .setEnd(commonAncestor.get()).call()) {
                            // This should be the base commit to preserve changes that were filtered
                            // out.
                            newParents.add(0, from.graphDatabase().getMapping(parentId));
                            continue;
                        }
                    }
                }
                newParents.add(from.graphDatabase().getMapping(parentId));
            }
            if (newParents.size() > 0) {
                parent = from.graphDatabase().getMapping(newParents.get(0));
            }
            Iterator<DiffEntry> diffIter = from.command(DiffOp.class).setNewVersion(commitId)
                    .setOldVersion(parent).setReportTrees(true).call();

            // connect and send packed changes
            final URL resourceURL;
            try {
                resourceURL = new URL(repositoryURL.toString() + "/repo/applychanges");
            } catch (MalformedURLException e) {
                throw Throwables.propagate(e);
            }

            final HttpURLConnection connection;
            final OutputStream out;
            try {
                connection = (HttpURLConnection) resourceURL.openConnection();
                connection.setDoOutput(true);
                connection.setDoInput(true);
                out = connection.getOutputStream();
                // pack the commit object
                final ObjectSerializingFactory factory = DataStreamSerializationFactoryV1.INSTANCE;
                final ObjectWriter<RevCommit> commitWriter = factory
                        .createObjectWriter(TYPE.COMMIT);
                commitWriter.write(commit, out);

                // write the new parents
                out.write(newParents.size());
                for (ObjectId parentId : newParents) {
                    out.write(parentId.getRawValue());
                }

                // pack the changes
                BinaryPackedChanges changes = new BinaryPackedChanges(from);
                changes.write(out, diffIter);
            } catch (IOException e) {
                throw Throwables.propagate(e);
            }

            final InputStream in;
            try {
                in = connection.getInputStream();
                BufferedReader rd = new BufferedReader(new InputStreamReader(in));

                String line = rd.readLine();
                if (line != null) {
                    ObjectId remoteCommitId = ObjectId.valueOf(line);
                    from.graphDatabase().map(commit.getId(), remoteCommitId);
                    from.graphDatabase().map(remoteCommitId, commit.getId());
                }

            } catch (IOException e) {
                throw Throwables.propagate(e);
            }

        }
    }

    /**
     * Retrieves an object with the specified id from the remote.
     *
     * @param objectId the object to get
     * @return the fetched object
     */
    @Override
    protected Optional<RevObject> getObject(ObjectId objectId) {
        return HttpUtils.getNetworkObject(repositoryURL, null, objectId);
    }

    /**
     * Updates the remote ref that matches the given refspec.
     *
     * @param refspec the ref to update
     * @param commitId the new value of the ref
     * @param delete if true, the remote ref will be deleted
     * @return the updated ref
     */
    @Override
    protected Optional<Ref> updateRemoteRef(String refspec, ObjectId commitId, boolean delete) {
        return HttpUtils.updateRemoteRef(repositoryURL, refspec, commitId, delete);
    }

    /**
     * Gets the depth of the remote repository.
     *
     * @return the depth of the repository, or {@link Optional#absent()} if the repository is not
     *         shallow
     */
    @Override
    public Optional<Integer> getDepth() {
        return HttpUtils.getDepth(repositoryURL, null);
    }

}
TOP

Related Classes of org.locationtech.geogig.remote.HttpMappedRemoteRepo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.