/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.plugins.jboss.software;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.Collection;
import churchillobjects.rss4j.RssDocument;
import churchillobjects.rss4j.parser.RssParser;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.common.composite.SystemSetting;
import org.rhq.core.domain.common.composite.SystemSettings;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.enterprise.server.plugin.pc.content.ContentProvider;
import org.rhq.enterprise.server.plugin.pc.content.ContentProviderPackageDetails;
import org.rhq.enterprise.server.plugin.pc.content.PackageSource;
import org.rhq.enterprise.server.plugin.pc.content.PackageSyncReport;
import org.rhq.enterprise.server.plugin.pc.content.RepoDetails;
import org.rhq.enterprise.server.plugin.pc.content.RepoImportReport;
import org.rhq.enterprise.server.plugin.pc.content.RepoSource;
import org.rhq.enterprise.server.plugin.pc.content.SyncException;
import org.rhq.enterprise.server.plugin.pc.content.SyncProgressWeight;
import org.rhq.enterprise.server.system.SystemManagerLocal;
import org.rhq.enterprise.server.util.LookupUtil;
/**
* Hook into the server to field requests on JBoss software related packages.
*
* @author Jason Dobies
*/
public class JBossSoftwareContentSourceAdapter implements ContentProvider, PackageSource, RepoSource {
// Connection properties, read from the user specified values specified at content source creation time
private String url;
private String username;
private String password;
private String proxyUrl;
private int proxyPort;
private String proxyUsername;
private String proxyPassword;
/**
* Indicates if the feed parsing should be performed. This check is only done at the call to
* {@link #synchronizePackages(String, PackageSyncReport, Collection)}. This flag is not used in the
* call to {@link #getInputStream(String)}; it only refers to the feed retrieval and parsing. If you managed to
* get the patches into the system prior to deactivating this content source, you will still be able to
* retrieve the bits for them.
*
* This value is defined as a connection property in the same way as the above attributes. It will be set
* by the user at content source creation time.
*/
private boolean active;
private RssFeedParser parser = new RssFeedParser();
private final Log log = LogFactory.getLog(this.getClass());
public void initialize(Configuration configuration) throws Exception {
SystemManagerLocal systemManager = LookupUtil.getSystemManager();
Subject overlord = LookupUtil.getSubjectManager().getOverlord();
SystemSettings settings = systemManager.getSystemSettings(overlord);
url = safeGetConfigurationProperty("url", configuration);
if (url == null) {
throw new IllegalArgumentException("url cannot be null");
}
PropertySimple activeProperty = configuration.getSimple("active");
if (activeProperty != null)
active = activeProperty.getBooleanValue();
username = safeGetConfigurationProperty("username", configuration);
password = safeGetConfigurationProperty("password", configuration);
proxyUrl = safeGetConfigurationProperty("proxyUrl", configuration);
proxyUsername = safeGetConfigurationProperty("proxyUsername", configuration);
proxyPassword = safeGetConfigurationProperty("proxyPassword", configuration);
String sProxyPort = safeGetConfigurationProperty("proxyPort", configuration);
if (sProxyPort != null) {
proxyPort = Integer.parseInt(sProxyPort);
}
// default to global HTTP Proxy settings
if (proxyUrl == null) {
proxyUrl = settings.get(SystemSetting.HTTP_PROXY_SERVER_HOST);
}
if (proxyUsername == null) {
proxyUsername = settings.get(SystemSetting.HTTP_PROXY_SERVER_USERNAME);
}
if (proxyPassword == null) {
proxyPassword = settings.get(SystemSetting.HTTP_PROXY_SERVER_PASSWORD);
}
if (sProxyPort == null) {
sProxyPort = settings.get(SystemSetting.HTTP_PROXY_SERVER_PORT);
if (sProxyPort != null) {
proxyPort = Integer.parseInt(sProxyPort);
}
}
}
public void shutdown() {
// No-op
}
public void testConnection() throws Exception {
if (!active)
throw new Exception("Content source is NOT set to active - connection cannot be established.");
// If there is an error in the connection, this call will throw an exception describing it.
retrieveRssDocument();
}
public void synchronizePackages(String repoName, PackageSyncReport report,
Collection<ContentProviderPackageDetails> existingPackages) throws SyncException, InterruptedException {
if (!active)
return;
RssDocument rssDocument;
try {
rssDocument = retrieveRssDocument();
} catch (Exception e) {
throw new SyncException("Error retrieving RSS document.", e);
}
if (rssDocument == null) {
throw new SyncException("Null RSS document received from adapter: " + this);
}
try {
parser.parseResults(rssDocument, report, existingPackages);
} catch (Exception e) {
throw new SyncException("Error parsing RSS document.", e);
}
}
public InputStream getInputStream(String location) throws Exception {
HttpClient client = new HttpClient();
configureProxy(client);
// Authentication
GetMethod method = new GetMethod(location);
method.setDoAuthentication(true);
method.setFollowRedirects(true);
if (username != null && password != null) {
method.addRequestHeader("username", username);
method.addRequestHeader("password", password);
}
int status = client.executeMethod(method);
if (status != HttpStatus.SC_OK) {
throw new Exception("Call to retrieve stream returned status code: " + status);
}
InputStream stream = method.getResponseBodyAsStream();
return stream;
}
public RepoImportReport importRepos() throws Exception {
RepoDetails repo = new RepoDetails("JBoss Patches");
RepoImportReport report = new RepoImportReport();
report.addRepo(repo);
return report;
}
public String toString() {
return "JBossSoftwareContentSourceAdapter[url=" + this.url + ", username=" + this.username + "]";
}
/**
* Returns the string value of a property in a configuration. This method will check the property for null before
* extracting the string value.
*
* @param propertyName property being retrieved
* @param configuration contains property values
* @return string value of the property if it's in the configuration; <code>null</code> if the property was missing
*/
private String safeGetConfigurationProperty(String propertyName, Configuration configuration) {
PropertySimple property = configuration.getSimple(propertyName);
return (property != null) ? property.getStringValue() : null;
}
/**
* Uses the connection properties set on this object to conncet to the RSS feed source and parse the feed contents
* into churchill domain objects.
*
* @return churchill domain representation of the RSS feed contents
* @throws Exception if there are any errors in connecting to the feed
*/
private RssDocument retrieveRssDocument() throws Exception {
HttpClient client = new HttpClient();
configureProxy(client);
// Authentication
URI feedURI = new URI(url);
URL feedURL = feedURI.toURL(); // proper RFC2396 decode happens here
GetMethod method = new GetMethod(feedURL.toString());
method.setDoAuthentication(true);
if (username != null && password != null) {
UsernamePasswordCredentials upc = new UsernamePasswordCredentials(username, password);
client.getState().setCredentials("users", method.getHostConfiguration().getHost(), upc);
}
// Perform the connection and capture its response XML
String rawFeed = null;
try {
//store off original url to detect forwarding as 401 results otherwise
//url forwarding is true by default.
String originalUrl = method.getURI().getURI();
int status = client.executeMethod(method);
//Check to see if redirection has occurred
String currentUrl = method.getURI().getURI();
//if redirection has occurred, reconnect with correct address.
if (!originalUrl.trim().equalsIgnoreCase(currentUrl.trim())) {
method = new GetMethod(currentUrl);
method.setDoAuthentication(true);
if (username != null && password != null) {
UsernamePasswordCredentials upc = new UsernamePasswordCredentials(username, password);
client.getState().setCredentials("users", method.getHostConfiguration().getHost(), upc);
}
log.warn("Following redirect to [" + currentUrl
+ "]. You may need to update your feed URL if redirect is permanent.");
status = client.executeMethod(method);
}
if (status == 404) {
throw new SyncException("Could not find the feed at URL [" + url
+ "]. Make sure the URL field correctly refers to the CSP feed location.");
}
if (status == 401 || status == 403) {
throw new SyncException("Invalid login credentials specified for user [" + username + "]. Make sure "
+ "this user has an active account at the CSP and that the password is correct.");
}
if (status != 200) {
throw new SyncException("The call to retrieve the RSS feed failed with status code: " + status);
}
rawFeed = method.getResponseBodyAsString();
} finally {
method.releaseConnection();
}
// Parse the raw feed into churchill domain objects
RssDocument parsedFeed = null;
if (rawFeed != null) {
parsedFeed = RssParser.parseRss(rawFeed);
}
return parsedFeed;
}
/**
* If proxy information was specified, configures the client to use it.
*
* @param client client being used in the invocation
*/
private void configureProxy(HttpClient client) {
// If a proxy URL was specified, configure the client for proxy support
if (proxyUrl != null) {
log.debug("Configuring feed for proxy. URL: " + proxyUrl + ", Port: " + proxyPort);
HostConfiguration hostConfiguration = client.getHostConfiguration();
hostConfiguration.setProxy(proxyUrl, proxyPort);
// If a proxy username was specified, indicate it as the proxy credentials
if (proxyUsername != null) {
log.debug("Configuring feed for authenticating proxy. User: " + proxyUsername);
AuthScope proxyAuthScope = new AuthScope(proxyUrl, proxyPort, AuthScope.ANY_REALM);
Credentials proxyCredentials = new UsernamePasswordCredentials(proxyUsername, proxyPassword);
client.getState().setProxyCredentials(proxyAuthScope, proxyCredentials);
}
}
}
public SyncProgressWeight getSyncProgressWeight() {
return SyncProgressWeight.DEFAULT_WEIGHTS;
}
}