blob: 4f8e992b05662c3ddcd2a892c793d1f462ef9092 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2015 The University of York.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License, v. 2.0 are satisfied: GNU General Public License, version 3.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-3.0
*
* Contributors:
* Antonio Garcia-Dominguez - initial API and implementation
******************************************************************************/
package org.eclipse.hawk.http;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.utils.DateUtils;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.eclipse.hawk.core.IConsole;
import org.eclipse.hawk.core.ICredentialsStore;
import org.eclipse.hawk.core.IModelIndexer;
import org.eclipse.hawk.core.IVcsManager;
import org.eclipse.hawk.core.VcsChangeType;
import org.eclipse.hawk.core.VcsCommit;
import org.eclipse.hawk.core.VcsCommitItem;
import org.eclipse.hawk.core.VcsRepositoryDelta;
import org.eclipse.hawk.core.ICredentialsStore.Credentials;
public class HTTPManager implements IVcsManager {
private static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding";
private static final String HEADER_CONTENT_LENGTH = "Content-Length";
private static final String HEADER_LAST_MODIFIED = "Last-Modified";
private static final String HEADER_NONE_MATCH = "If-None-Match";
private static final String HEADER_ETAG = "ETag";
private static final String FIRST_REV = "0";
private boolean isActive;
private IConsole console;
private URI repositoryURI;
private String username;
private String password;
private String lastETag;
private String lastDelta;
private IModelIndexer indexer;
private boolean isFrozen = false;
@Override
public String getCurrentRevision() throws Exception {
try (final CloseableHttpClient cl = createClient()) {
/*
* Since HTTP servers can be quite varied, we try several methods in
* sequence:
*
* - ETag headers are preferred, since these explicitly check for
* changes. - Otherwise, Last-Modified dates are used. - Otherwise,
* Content-Length is used.
*
* We try first a HEAD request, and if that doesn't work a GET
* request.
*/
final HttpHead headRequest = new HttpHead(repositoryURI);
decorateCurrentRevisionRequest(headRequest);
try (CloseableHttpResponse response = cl.execute(headRequest)) {
String headRevision = getRevision(response);
if (headRevision != null) {
return headRevision;
}
}
final HttpGet getRequest = new HttpGet(repositoryURI);
decorateCurrentRevisionRequest(getRequest);
try (CloseableHttpResponse response = cl.execute(getRequest)) {
String getRev = getRevision(response);
if (getRev != null) {
return getRev;
}
}
}
// No way to detect changes - just fetch the file once
return "1";
}
protected void decorateCurrentRevisionRequest(final HttpRequestBase request) {
if (lastETag != null) {
request.setHeader(HEADER_ETAG, lastETag);
request.setHeader(HEADER_NONE_MATCH, "*");
}
}
protected String getRevision(CloseableHttpResponse response) {
if (response.getStatusLine().getStatusCode() == 304) {
// Not-Modified, as told by the server
return lastETag;
} else if (response.getStatusLine().getStatusCode() != 200) {
// Request failed for some reason (4xx, 5xx: we already
// handle 3xx redirects)
return FIRST_REV;
}
final Header etagHeader = response.getFirstHeader(HEADER_ETAG);
if (etagHeader != null) {
lastETag = etagHeader.getValue();
return lastETag;
}
final Header lmHeader = response.getFirstHeader(HEADER_LAST_MODIFIED);
if (lmHeader != null) {
final Date lmDate = DateUtils.parseDate(lmHeader.getValue());
return lmDate.getTime() + "";
}
final Header clHeader = response.getFirstHeader(HEADER_CONTENT_LENGTH);
if (clHeader != null) {
return clHeader.getValue();
}
final HttpEntity entity = response.getEntity();
if (entity != null) {
final long cLength = entity.getContentLength();
if (cLength >= 0) {
return cLength + "";
}
// Since there is an entity but no length, we must have a chunked entity
// chunked entities are dynamically generated and streamed
final Header teHeader = response.getFirstHeader(HEADER_TRANSFER_ENCODING);
if (teHeader.getValue().equals("chunked")) {
// TODO: how to handle? Won't know file contents until it is fully downloaded
}
}
return null;
}
private CloseableHttpClient createClient() {
final HttpClientBuilder builder = HttpClientBuilder.create();
// Provide username and password if specified
if (username != null) {
final BasicCredentialsProvider credProvider = new BasicCredentialsProvider();
credProvider.setCredentials(new AuthScope(new HttpHost(repositoryURI.getHost())),
new UsernamePasswordCredentials(username, password));
builder.setDefaultCredentialsProvider(credProvider);
}
// Follow redirects
builder.setRedirectStrategy(new LaxRedirectStrategy());
return builder.build();
}
@Override
public String getFirstRevision() throws Exception {
return FIRST_REV;
}
@Override
public Collection<VcsCommitItem> getDelta(String endRevision) throws Exception {
return getDelta(FIRST_REV, endRevision).getCompactedCommitItems();
}
@Override
public VcsRepositoryDelta getDelta(String startRevision, String endRevision) throws Exception {
VcsRepositoryDelta delta;
if (lastDelta == null || !lastDelta.equals(endRevision)) {
VcsCommit c = new VcsCommit();
c.setAuthor("Unknown");
c.setJavaDate(new Date());
c.setMessage("HTTP file changed: " + repositoryURI);
c.setRevision(endRevision);
VcsCommitItem ci = new VcsCommitItem();
ci.setChangeType(VcsChangeType.UPDATED);
ci.setPath(repositoryURI.getPath());
ci.setCommit(c);
c.getItems().add(ci);
delta = new VcsRepositoryDelta(Collections.singleton(c));
} else {
delta = new VcsRepositoryDelta(Collections.emptyList());
}
delta.setManager(this);
return delta;
}
@Override
public File importFile(String revision, String path, File temp) {
try (CloseableHttpClient cl = createClient()) {
try (CloseableHttpResponse response = cl.execute(new HttpGet(repositoryURI))) {
Files.copy(response.getEntity().getContent(), temp.toPath());
return temp;
}
} catch (IOException e) {
console.printerrln(e);
return null;
}
}
@Override
public boolean isActive() {
return isActive;
}
@Override
public void init(String vcsloc, IModelIndexer indexer) throws URISyntaxException {
console = indexer.getConsole();
this.repositoryURI = new URI(vcsloc);
this.indexer = indexer;
}
@Override
public void run() throws Exception {
try {
final ICredentialsStore credStore = indexer.getCredentialsStore();
if (username != null) {
// The credentials were provided by a previous setCredentials
// call: retry the change to the credentials store.
setCredentials(username, password, credStore);
} else {
final Credentials credentials = credStore.get(repositoryURI.toString());
if (credentials != null) {
this.username = credentials.getUsername();
this.password = credentials.getPassword();
} else {
/*
* If we use null for the default username/password, SVNKit
* will try to use the GNOME keyring in Linux, and that will
* lock up our Eclipse instance in some cases.
*/
console.printerrln("No username/password recorded for the repository " + repositoryURI);
this.username = "";
this.password = "";
}
}
isActive = true;
} catch (Exception e) {
console.printerrln("exception in svnmanager run():");
console.printerrln(e);
}
}
@Override
public void shutdown() {
repositoryURI = null;
console = null;
}
@Override
public String getLocation() {
return repositoryURI.toString();
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setCredentials(String username, String password, ICredentialsStore credStore) {
if (username != null && password != null && repositoryURI != null
&& (!username.equals(this.username) || !password.equals(this.password))) {
try {
credStore.put(repositoryURI.toString(), new Credentials(username, password));
} catch (Exception e) {
console.printerrln("Could not save new username/password");
console.printerrln(e);
}
}
this.username = username;
this.password = password;
}
@Override
public String getHumanReadableName() {
return "HTTP Monitor";
}
@Override
public boolean isAuthSupported() {
return true;
}
@Override
public boolean isPathLocationAccepted() {
return false;
}
@Override
public boolean isURLLocationAccepted() {
return true;
}
@Override
public String getRepositoryPath(String rawPath) {
final String sRepositoryURI = repositoryURI.toString();
if (rawPath.startsWith(sRepositoryURI)) {
return rawPath.substring(sRepositoryURI.length());
}
return rawPath;
}
@Override
public boolean isFrozen() {
return isFrozen;
}
@Override
public void setFrozen(boolean f) {
isFrozen = f;
}
@Override
public String getDefaultLocation() {
return "http://host:port/path";
}
}