blob: b21e4f53405160edf1e64dcfe56c227fd2cf78f2 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.sling.ide.osgi.impl;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
import org.apache.commons.httpclient.methods.multipart.FilePart;
import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
import org.apache.commons.httpclient.methods.multipart.Part;
import org.apache.commons.httpclient.methods.multipart.PartSource;
import org.apache.commons.httpclient.methods.multipart.StringPart;
import org.apache.commons.io.IOUtils;
import org.apache.sling.ide.osgi.OsgiClient;
import org.apache.sling.ide.osgi.OsgiClientException;
import org.apache.sling.ide.osgi.SourceReference;
import org.apache.sling.ide.transport.RepositoryInfo;
import org.osgi.framework.Version;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;
import com.google.gson.stream.JsonReader;
public class HttpOsgiClient implements OsgiClient {
private static final int DEFAULT_SOCKET_TIMEOUT_SECONDS = 30;
private static final int DEFAULT_CONNECT_TIMEOUT_SECONDS = 30;
private RepositoryInfo repositoryInfo;
public HttpOsgiClient(RepositoryInfo repositoryInfo) {
this.repositoryInfo = repositoryInfo;
}
private static final class BundleInfo {
private String symbolicName;
private String version;
private long id;
public String getSymbolicName() {
return symbolicName;
}
public Version getVersion() {
return new Version(version);
}
public long getId() {
return id;
}
}
private static BundleInfo readBundleInfo(String bundleSymbolicName, Reader reader) throws IOException {
Gson gson = new Gson();
try (JsonReader jsonReader = new JsonReader(reader)) {
// wait for 'data' attribute
jsonReader.beginObject();
while (jsonReader.hasNext()) {
String name = jsonReader.nextName();
if (name.equals("data")) {
jsonReader.beginArray();
while (jsonReader.hasNext()) {
// read json for individual bundle
BundleInfo bundleInfo = gson.fromJson(jsonReader, BundleInfo.class);
if (bundleSymbolicName.equals(bundleInfo.getSymbolicName())) {
return bundleInfo;
}
}
jsonReader.endArray();
} else {
jsonReader.skipValue();
}
}
}
return null;
}
static Version getBundleVersionFromReader(String bundleSymbolicName, Reader reader) throws IOException {
BundleInfo bundleInfo = readBundleInfo(bundleSymbolicName, reader);
if ( bundleInfo == null ) {
return null;
}
return bundleInfo.getVersion();
}
static Long getBundleIdFromReader(String bundleSymbolicName, Reader reader) throws IOException {
BundleInfo bundleInfo = readBundleInfo(bundleSymbolicName, reader);
if ( bundleInfo == null ) {
return null;
}
return bundleInfo.getId();
}
@Override
public Version getBundleVersion(String bundleSymbolicName) throws OsgiClientException {
GetMethod method = new GetMethod(repositoryInfo.appendPath("system/console/bundles.json"));
HttpClient client = getHttpClient();
try {
int result = client.executeMethod(method);
if (result != HttpStatus.SC_OK) {
throw new HttpException("Got status code " + result + " for call to " + method.getURI());
}
try ( InputStream input = method.getResponseBodyAsStream();
Reader reader = new InputStreamReader(input, StandardCharsets.US_ASCII)) {
return getBundleVersionFromReader(bundleSymbolicName, reader);
}
} catch (IOException e) {
throw new OsgiClientException(e);
} finally {
method.releaseConnection();
}
}
private HttpClient getHttpClient() {
HttpClient client = new HttpClient();
client.getHttpConnectionManager().getParams().setConnectionTimeout(DEFAULT_CONNECT_TIMEOUT_SECONDS * 1000);
client.getHttpConnectionManager().getParams().setSoTimeout(DEFAULT_SOCKET_TIMEOUT_SECONDS * 1000);
client.getParams().setAuthenticationPreemptive(true);
Credentials defaultcreds = new UsernamePasswordCredentials(repositoryInfo.getUsername(),
repositoryInfo.getPassword());
client.getState().setCredentials(
new AuthScope(repositoryInfo.getHost(), repositoryInfo.getPort(), AuthScope.ANY_REALM), defaultcreds);
return client;
}
@Override
public void installBundle(InputStream in, String fileName) throws OsgiClientException {
if (in == null) {
throw new IllegalArgumentException("in may not be null");
}
if (fileName == null) {
throw new IllegalArgumentException("fileName may not be null");
}
// append pseudo path after root URL to not get redirected for nothing
final PostMethod filePost = new PostMethod(repositoryInfo.appendPath("system/console/install"));
try {
// set referrer
filePost.setRequestHeader("referer", "about:blank");
List<Part> partList = new ArrayList<>();
partList.add(new StringPart("action", "install"));
partList.add(new StringPart("_noredir_", "_noredir_"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copy(in, baos);
PartSource partSource = new ByteArrayPartSource(fileName, baos.toByteArray());
partList.add(new FilePart("bundlefile", partSource));
partList.add(new StringPart("bundlestart", "start"));
Part[] parts = partList.toArray(new Part[partList.size()]);
filePost.setRequestEntity(new MultipartRequestEntity(parts, filePost.getParams()));
int status = getHttpClient().executeMethod(filePost);
if (status != 200) {
throw new OsgiClientException("Method execution returned status " + status);
}
} catch (IOException e) {
throw new OsgiClientException(e);
} finally {
filePost.releaseConnection();
}
}
@Override
public void uninstallBundle(String bundleSymbolicName) throws OsgiClientException {
GetMethod method = new GetMethod(repositoryInfo.appendPath("system/console/bundles.json"));
HttpClient client = getHttpClient();
try {
int result = client.executeMethod(method);
if (result != HttpStatus.SC_OK) {
throw new HttpException("Got status code " + result + " for call to " + method.getURI());
}
try ( InputStream input = method.getResponseBodyAsStream();
Reader reader = new InputStreamReader(input, StandardCharsets.US_ASCII)) {
Long bundleId = getBundleIdFromReader(bundleSymbolicName, reader);
if ( bundleId == null ) {
return;
}
PostMethod postMethod = new PostMethod(repositoryInfo.appendPath("system/console/bundles/") + bundleId);
postMethod.addParameter("action", "uninstall");
result = client.executeMethod(postMethod);
if ( result != HttpStatus.SC_OK )
throw new HttpException("Got status code " + result + " for call to " + postMethod.getURI());
}
} catch (IOException e) {
throw new OsgiClientException(e);
} finally {
method.releaseConnection();
}
}
@Override
public void installLocalBundle(final String explodedBundleLocation) throws OsgiClientException {
if (explodedBundleLocation == null) {
throw new IllegalArgumentException("explodedBundleLocation may not be null");
}
new LocalBundleInstaller(getHttpClient(), repositoryInfo) {
@Override
void configureRequest(PostMethod method) {
method.addParameter("dir", explodedBundleLocation);
}
}.installBundle();
}
@Override
public void installLocalBundle(final InputStream jarredBundle, String sourceLocation) throws OsgiClientException {
if (jarredBundle == null) {
throw new IllegalArgumentException("jarredBundle may not be null");
}
new LocalBundleInstaller(getHttpClient(), repositoryInfo) {
@Override
void configureRequest(PostMethod method) throws IOException {
Part[] parts = new Part[] { new FilePart("bundle", new ByteArrayPartSource("bundle.jar",
IOUtils.toByteArray(jarredBundle))) };
method.setRequestEntity(new MultipartRequestEntity(parts, method.getParams()));
}
}.installBundle();
}
@Override
public List<SourceReference> findSourceReferences() throws OsgiClientException {
GetMethod method = new GetMethod(repositoryInfo.appendPath("system/sling/tooling/sourceReferences.json"));
HttpClient client = getHttpClient();
try {
int result = client.executeMethod(method);
if (result != HttpStatus.SC_OK) {
throw new HttpException("Got status code " + result + " for call to " + method.getURI());
}
return parseSourceReferences(method.getResponseBodyAsStream());
} catch (IOException e) {
throw new OsgiClientException(e);
} finally {
method.releaseConnection();
}
}
// visible for testing
static List<SourceReference> parseSourceReferences(InputStream response) throws IOException {
try (JsonReader jsonReader = new JsonReader(
new InputStreamReader(response, StandardCharsets.US_ASCII))) {
SourceBundleData[] refs = new Gson().fromJson(jsonReader, SourceBundleData[].class);
List<SourceReference> res = new ArrayList<>(refs.length);
for ( SourceBundleData sourceData : refs ) {
for ( SourceReferenceFromJson ref : sourceData.sourceReferences ) {
if ( ref.isMavenType() ) {
res.add(ref.getMavenSourceReference());
}
}
}
return res;
}
}
/**
* Encapsulates the JSON response from the tooling.installer
*/
private static final class BundleInstallerResult {
private String status; // either OK or FAILURE
private String message;
public boolean hasMessage() {
if (message != null && message.length() > 0) {
return true;
}
return false;
}
public String getMessage() {
return message;
}
public boolean isSuccessful() {
return "OK".equalsIgnoreCase(status);
}
}
private static final class SourceBundleData {
@SerializedName("Bundle-SymbolicName")
private String bsn;
@SerializedName("Bundle-Version")
private String version;
private List<SourceReferenceFromJson> sourceReferences;
}
private static final class SourceReferenceFromJson {
@SerializedName("__type__")
private String type; // should be "maven"
private String groupId;
private String artifactId;
private String version;
public boolean isMavenType() {
return "maven".equals(type);
}
public MavenSourceReferenceImpl getMavenSourceReference() {
if (!isMavenType()) {
throw new IllegalStateException("The type is not a Maven source reference but a " + type);
}
return new MavenSourceReferenceImpl(groupId, artifactId, version);
}
}
static abstract class LocalBundleInstaller {
private final HttpClient httpClient;
private final RepositoryInfo repositoryInfo;
public LocalBundleInstaller(HttpClient httpClient, RepositoryInfo repositoryInfo) {
this.httpClient = httpClient;
this.repositoryInfo = repositoryInfo;
}
void installBundle() throws OsgiClientException {
PostMethod method = new PostMethod(repositoryInfo.appendPath("system/sling/tooling/install"));
try {
configureRequest(method);
Gson gson = new Gson();
int status = httpClient.executeMethod(method);
try (JsonReader jsonReader = new JsonReader(
new InputStreamReader(method.getResponseBodyAsStream(), StandardCharsets.UTF_8))) {
BundleInstallerResult result = null;
if (status != 200) {
try {
result = gson.fromJson(jsonReader, BundleInstallerResult.class);
if (result.hasMessage()) {
throw new OsgiClientException(result.getMessage());
}
} catch (JsonParseException e) {
// ignore, fallback to status code reporting
}
throw new OsgiClientException("Method execution returned status " + status);
}
result = gson.fromJson(jsonReader, BundleInstallerResult.class);
if (!result.isSuccessful()) {
String errorMessage = !result.hasMessage() ? "Bundle deployment failed, please check the Sling logs"
: result.getMessage();
throw new OsgiClientException(errorMessage);
}
}
} catch (IOException e) {
throw new OsgiClientException(e);
} finally {
method.releaseConnection();
}
}
abstract void configureRequest(PostMethod method) throws IOException;
}
}