You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by am...@apache.org on 2006/05/04 06:09:23 UTC
svn commit: r399534 [3/4] - in /geronimo/branches/1.1:
applications/console-standard/src/java/org/apache/geronimo/console/car/
applications/console-standard/src/webapp/WEB-INF/view/car/
configs/console-jetty/src/conf/ configs/console-tomcat/src/conf/ c...
Copied: geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java (from r399072, geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java)
URL: http://svn.apache.org/viewcvs/geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java?p2=geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java&p1=geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java&r1=399072&r2=399534&rev=399534&view=diff
==============================================================================
--- geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/configuration/ConfigInstallerGBean.java (original)
+++ geronimo/branches/1.1/modules/system/src/java/org/apache/geronimo/system/plugin/PluginInstallerGBean.java Wed May 3 21:09:17 2006
@@ -14,10 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.geronimo.system.configuration;
+package org.apache.geronimo.system.plugin;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
@@ -36,9 +37,9 @@
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
-import java.util.zip.ZipEntry;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
+import java.util.zip.ZipEntry;
import javax.security.auth.login.FailedLoginException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -67,8 +68,9 @@
import org.apache.geronimo.kernel.repository.ImportType;
import org.apache.geronimo.kernel.repository.MissingDependencyException;
import org.apache.geronimo.kernel.repository.Repository;
-import org.apache.geronimo.kernel.repository.WritableListableRepository;
import org.apache.geronimo.kernel.repository.Version;
+import org.apache.geronimo.kernel.repository.WritableListableRepository;
+import org.apache.geronimo.system.configuration.ConfigurationStoreUtil;
import org.apache.geronimo.system.serverinfo.ServerInfo;
import org.apache.geronimo.system.threads.ThreadPool;
import org.apache.geronimo.util.encoders.Base64;
@@ -77,7 +79,9 @@
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
+import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
/**
@@ -85,8 +89,8 @@
*
* @version $Rev: 46019 $ $Date: 2004-09-14 05:56:06 -0400 (Tue, 14 Sep 2004) $
*/
-public class ConfigInstallerGBean implements ConfigurationInstaller {
- private final static Log log = LogFactory.getLog(ConfigInstallerGBean.class);
+public class PluginInstallerGBean implements PluginInstaller {
+ private final static Log log = LogFactory.getLog(PluginInstallerGBean.class);
private static int counter;
private ConfigurationManager configManager;
private WritableListableRepository writeableRepo;
@@ -96,7 +100,7 @@
private Map asyncKeys;
private ThreadPool threadPool;
- public ConfigInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool) {
+ public PluginInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool) {
this.configManager = configManager;
this.writeableRepo = repository;
this.configStore = configStore;
@@ -106,14 +110,13 @@
asyncKeys = Collections.synchronizedMap(new HashMap());
}
- private static Object getNextKey() {
- int value;
- synchronized(ConfigInstallerGBean.class) {
- value = ++counter;
- }
- return new Integer(value);
- }
-
+ /**
+ * Lists the plugins installed in the local Geronimo server, by name and
+ * ID.
+ *
+ * @return A Map with key type String (plugin name) and value type Artifact
+ * (config ID of the plugin).
+ */
public Map getInstalledPlugins() {
SortedSet artifacts = writeableRepo.list();
@@ -156,8 +159,17 @@
return plugins;
}
- public ConfigurationArchiveData getPluginMetadata(Artifact configId) {
- File dir = writeableRepo.getLocation(configId);
+ /**
+ * Gets a CofigurationMetadata for a configuration installed in the local
+ * server. Should load a saved one if available, or else create a new
+ * default one to the best of its abilities.
+ *
+ * @param moduleId Identifies the configuration. This must match a
+ * configuration currently installed in the local server.
+ * The configId must be fully resolved (isResolved() == true)
+ */
+ public PluginMetadata getPluginMetadata(Artifact moduleId) {
+ File dir = writeableRepo.getLocation(moduleId);
Document doc;
ConfigurationData configData;
String source = dir.getAbsolutePath();
@@ -168,73 +180,92 @@
return null;
}
File xml = new File(meta, "geronimo-plugin.xml");
- configData = configStore.loadConfiguration(configId);
+ configData = configStore.loadConfiguration(moduleId);
if(!xml.isFile() || !xml.canRead() || xml.length() == 0) {
- return new ConfigurationArchiveData(null, new URL[0], createDefaultMetadata(configData));
+ return createDefaultMetadata(configData);
}
source = xml.getAbsolutePath();
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
+ DocumentBuilder builder = createDocumentBuilder();
doc = builder.parse(xml);
} else {
if(!dir.isFile() || !dir.canRead()) {
throw new IllegalStateException("Cannot read configuration "+dir.getAbsolutePath());
}
- configData = configStore.loadConfiguration(configId);
+ configData = configStore.loadConfiguration(moduleId);
JarFile jar = new JarFile(dir);
try {
ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml");
if(entry == null) {
- return new ConfigurationArchiveData(null, new URL[0], createDefaultMetadata(configData));
+ return createDefaultMetadata(configData);
}
source = dir.getAbsolutePath()+"#META-INF/geronimo-plugin.xml";
InputStream in = jar.getInputStream(entry);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
+ DocumentBuilder builder = createDocumentBuilder();
doc = builder.parse(in);
in.close();
} finally {
jar.close();
}
}
- ConfigurationArchiveData result = loadConfigurationMetadata(doc, source);
- overrideDependencies(configData, result.getConfiguration());
+ PluginMetadata result = loadPluginMetadata(doc, source);
+ overrideDependencies(configData, result);
return result;
} catch (InvalidConfigException e) {
- log.warn("Unable to generate metadata for "+configId, e);
+ log.warn("Unable to generate metadata for "+moduleId, e);
} catch (Exception e) {
log.warn("Invalid XML at "+source, e);
}
return null;
}
- public void updatePluginMetadata(ConfigurationArchiveData metadata) {
- File dir = writeableRepo.getLocation(metadata.getConfiguration().getConfigId());
+ /**
+ * Saves a ConfigurationMetadata for a particular plugin, if the server is
+ * able to record it. This can be used if you later re-export the plugin,
+ * or just want to review the information for a particular installed
+ * plugin.
+ *
+ * @param metadata The data to save. The contained configId (which must
+ * be fully resolved) identifies the configuration to save
+ * this for.
+ */
+ public void updatePluginMetadata(PluginMetadata metadata) {
+ File dir = writeableRepo.getLocation(metadata.getModuleId());
if(dir == null) {
- throw new IllegalArgumentException(metadata.getConfiguration().getConfigId()+" is not installed!");
+ throw new IllegalArgumentException(metadata.getModuleId()+" is not installed!");
}
File meta = new File(dir, "META-INF");
if(!meta.isDirectory() || !meta.canRead()) {
- throw new IllegalArgumentException(metadata.getConfiguration().getConfigId()+" is not a plugin!");
+ throw new IllegalArgumentException(metadata.getModuleId()+" is not a plugin!");
}
File xml = new File(meta, "geronimo-plugin.xml");
try {
if(!xml.isFile()) {
if(!xml.createNewFile()) {
- throw new RuntimeException("Cannot create plugin metadata file for "+metadata.getConfiguration().getConfigId());
+ throw new RuntimeException("Cannot create plugin metadata file for "+metadata.getModuleId());
}
}
- Document doc = writeConfigurationMetadata(metadata);
+ Document doc = writePluginMetadata(metadata);
TransformerFactory xfactory = TransformerFactory.newInstance();
Transformer xform = xfactory.newTransformer();
xform.setOutputProperty(OutputKeys.INDENT, "yes");
+ xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
xform.transform(new DOMSource(doc), new StreamResult(xml));
} catch (Exception e) {
- log.error("Unable to save plugin metadata for "+metadata.getConfiguration().getConfigId(), e);
+ log.error("Unable to save plugin metadata for "+metadata.getModuleId(), e);
}
}
- public ConfigurationList listConfigurations(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
+ /**
+ * Lists the plugins available for download in a particular Geronimo repository.
+ *
+ * @param mavenRepository The base URL to the maven repository. This must
+ * contain the file geronimo-plugins.xml
+ * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ */
+ public PluginList listPlugins(URL mavenRepository, String username, String password) throws IOException, FailedLoginException {
String repository = mavenRepository.toString();
if(!repository.endsWith("/")) {
repository = repository+"/";
@@ -243,8 +274,8 @@
URL url = new URL(repository+"geronimo-plugins.xml");
try {
//todo: use a progress monitor
- InputStream in = openStream(null, url, new URL[0], username, password, null).getStream();
- return loadConfiguration(mavenRepository, in);
+ InputStream in = openStream(null, new URL[]{url}, username, password, null).getStream();
+ return loadPluginList(mavenRepository, in);
} catch (MissingDependencyException e) {
log.error("Cannot find plugin index at site "+url);
return null;
@@ -254,243 +285,54 @@
}
}
- private ConfigurationArchiveData loadConfigurationArchive(File file) throws IOException, ParserConfigurationException, SAXException {
- if(!file.canRead()) {
- log.error("Cannot read from downloaded CAR file "+file.getAbsolutePath());
- return null;
- }
- JarFile jar = new JarFile(file);
- Document doc;
- try {
- JarEntry entry = jar.getJarEntry("META-INF/geronimo-plugin.xml");
- if(entry == null) {
- log.error("Downloaded CAR file does not contain META-INF/geronimo-plugin.xml file");
- jar.close();
- return null;
- }
- InputStream in = jar.getInputStream(entry);
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- doc = builder.parse(in);
- in.close();
- } finally {
- jar.close();
- }
- return loadConfigurationMetadata(doc, file.getAbsolutePath());
- }
-
- private ConfigurationArchiveData loadConfigurationMetadata(Document doc, String file) throws SAXException, MalformedURLException {
- Element root = doc.getDocumentElement();
- NodeList configs = root.getElementsByTagName("configuration");
- if(configs.getLength() != 1) {
- log.error("Configuration archive "+file+" does not have exactly one configuration in META-INF/geronimo-plugin.xml");
- return null;
- }
- ConfigurationMetadata data = processConfiguration((Element) configs.item(0));
- String repo = getChildText(root, "source-repository");
- URL repoURL;
- if(repo == null || repo.equals("")) {
- log.warn("Configuration archive "+file+" does not list a repository for downloading dependencies.");
- repoURL = null;
- } else {
- repoURL = new URL(repo);
- }
- String[] others = getChildrenText(root, "backup-repository");
- URL[] backups = new URL[others.length];
- for (int i = 0; i < backups.length; i++) {
- backups[i] = new URL(others[i]);
- }
- return new ConfigurationArchiveData(repoURL, backups, data);
- }
-
- private ConfigurationList loadConfiguration(URL repo, InputStream in) throws ParserConfigurationException, IOException, SAXException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document doc = builder.parse(in);
- in.close();
- Element root = doc.getDocumentElement();
- NodeList configs = root.getElementsByTagName("configuration");
- List results = new ArrayList();
- for (int i = 0; i < configs.getLength(); i++) {
- Element config = (Element) configs.item(i);
- ConfigurationMetadata data = processConfiguration(config);
- results.add(data);
- }
- String[] backups = getChildrenText(root, "backup-repository");
- URL[] backupURLs = new URL[backups.length];
- for(int i = 0; i < backups.length; i++) {
- if(backups[i].endsWith("/")) {
- backupURLs[i] = new URL(backups[i]);
- } else {
- backupURLs[i] = new URL(backups[i]+"/");
- }
- }
-
- ConfigurationMetadata[] data = (ConfigurationMetadata[]) results.toArray(new ConfigurationMetadata[results.size()]);
- return new ConfigurationList(repo, backupURLs, data);
- }
-
- private ConfigurationMetadata processConfiguration(Element config) throws SAXException {
- String configId = getChildText(config, "config-id");
- NodeList licenseNodes = config.getElementsByTagName("license");
- ConfigurationMetadata.License[] licenses = new ConfigurationMetadata.License[licenseNodes.getLength()];
- for(int j=0; j<licenseNodes.getLength(); j++) {
- Element node = (Element) licenseNodes.item(j);
- String licenseName = getText(node);
- String openSource = node.getAttribute("osi-approved");
- if(licenseName == null || licenseName.equals("") || openSource == null || openSource.equals("")) {
- throw new SAXException("Invalid config file: license name and osi-approved flag required");
- }
- licenses[j] = new ConfigurationMetadata.License(licenseName, Boolean.valueOf(openSource).booleanValue());
- }
- boolean eligible = true;
- NodeList preNodes = config.getElementsByTagName("prerequisite");
- ConfigurationMetadata.Prerequisite[] prereqs = new ConfigurationMetadata.Prerequisite[preNodes.getLength()];
- for(int j=0; j<preNodes.getLength(); j++) {
- Element node = (Element) preNodes.item(j);
- String originalConfigId = getChildText(node, "id");
- if(originalConfigId == null) {
- throw new SAXException("Prerequisite requires <id>");
- }
- Artifact artifact = Artifact.create(originalConfigId.replaceAll("\\*", ""));
- boolean present = resolver.queryArtifacts(artifact).length > 0;
- prereqs[j] = new ConfigurationMetadata.Prerequisite(artifact, present,
- getChildText(node, "resource-type"), getChildText(node, "description"));
- if(!present) {
- log.debug(configId+" is not eligible due to missing "+prereqs[j].getConfigId());
- eligible = false;
- }
- }
- String[] gerVersions = getChildrenText(config, "geronimo-version");
- if(gerVersions.length > 0) {
- String version = serverInfo.getVersion();
- boolean match = false;
- for (int j = 0; j < gerVersions.length; j++) {
- String gerVersion = gerVersions[j];
- if(gerVersion == null || gerVersion.equals("")) {
- throw new SAXException("geronimo-version should not be empty!");
- }
- if(gerVersion.equals(version)) {
- match = true;
- break;
- }
- }
- if(!match) eligible = false;
- }
- String[] jvmVersions = getChildrenText(config, "jvm-version");
- if(jvmVersions.length > 0) {
- String version = System.getProperty("java.version");
- boolean match = false;
- for (int j = 0; j < jvmVersions.length; j++) {
- String jvmVersion = jvmVersions[j];
- if(jvmVersion == null || jvmVersion.equals("")) {
- throw new SAXException("jvm-version should not be empty!");
- }
- if(version.startsWith(jvmVersion)) {
- match = true;
- break;
- }
- }
- if(!match) eligible = false;
- }
- Artifact artifact = null;
- boolean installed = false;
- if (configId != null) {
- artifact = Artifact.create(configId);
- // Tests, etc. don't need to have a ConfigurationManager
- installed = configManager != null && configManager.isLoaded(artifact);
- }
- log.trace("Checking "+configId+": installed="+installed+", eligible="+eligible);
- ConfigurationMetadata data = new ConfigurationMetadata(artifact, getChildText(config, "name"),
- getChildText(config, "description"), getChildText(config, "category"), installed, eligible);
- data.setGeronimoVersions(gerVersions);
- data.setJvmVersions(jvmVersions);
- data.setLicenses(licenses);
- data.setPrerequisites(prereqs);
- NodeList list = config.getElementsByTagName("dependency");
- List start = new ArrayList();
- String deps[] = new String[list.getLength()];
- for(int i=0; i<list.getLength(); i++) {
- Element node = (Element) list.item(i);
- deps[i] = getText(node);
- if(node.hasAttribute("start") && node.getAttribute("start").equalsIgnoreCase("true")) {
- start.add(deps[i]);
- }
- }
- data.setDependencies(deps);
- data.setForceStart((String[]) start.toArray(new String[start.size()]));
- data.setObsoletes(getChildrenText(config, "obsoletes"));
- return data;
- }
-
- private static String getChildText(Element root, String property) {
- NodeList children = root.getChildNodes();
- for(int i=0; i<children.getLength(); i++) {
- Node check = children.item(i);
- if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
- return getText(check);
- }
- }
- return null;
- }
-
- private static String getText(Node target) {
- NodeList nodes = target.getChildNodes();
- StringBuffer buf = null;
- for(int j=0; j<nodes.getLength(); j++) {
- Node node = nodes.item(j);
- if(node.getNodeType() == Node.TEXT_NODE) {
- if(buf == null) {
- buf = new StringBuffer();
- }
- buf.append(node.getNodeValue());
- }
- }
- return buf == null ? null : buf.toString();
- }
-
- private static String[] getChildrenText(Element root, String property) {
- NodeList children = root.getChildNodes();
- List results = new ArrayList();
- for(int i=0; i<children.getLength(); i++) {
- Node check = children.item(i);
- if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
- NodeList nodes = check.getChildNodes();
- StringBuffer buf = null;
- for(int j=0; j<nodes.getLength(); j++) {
- Node node = nodes.item(j);
- if(node.getNodeType() == Node.TEXT_NODE) {
- if(buf == null) {
- buf = new StringBuffer();
- }
- buf.append(node.getNodeValue());
- }
- }
- results.add(buf == null ? null : buf.toString());
- }
- }
- return (String[]) results.toArray(new String[results.size()]);
- }
-
-
- public DownloadResults install(ConfigurationList list, String username, String password) {
+ /**
+ * Installs a configuration from a remote repository into the local Geronimo server,
+ * including all its dependencies. The caller will get the results when the
+ * operation completes. Note that this method does not throw exceptions on failure,
+ * but instead sets the failure property of the DownloadResults.
+ *
+ * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param pluginsToInstall The list of configurations to install
+ */
+ public DownloadResults install(PluginList pluginsToInstall, String username, String password) {
DownloadResults results = new DownloadResults();
- install(list, username, password, results);
+ install(pluginsToInstall, username, password, results);
return results;
}
- public void install(ConfigurationList list, String username, String password, DownloadPoller poller) {
+ /**
+ * Installs a configuration from a remote repository into the local Geronimo server,
+ * including all its dependencies. The method blocks until the operation completes,
+ * but the caller will be notified of progress frequently along the way (using the
+ * supplied DownloadPoller). Therefore the caller is meant to create the poller and
+ * then call this method in a background thread. Note that this method does not
+ * throw exceptions on failure, but instead sets the failure property of the
+ * DownloadPoller.
+ *
+ * @param pluginsToInstall The list of configurations to install
+ * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param poller Will be notified with status updates as the download proceeds
+ */
+ public void install(PluginList pluginsToInstall, String username, String password, DownloadPoller poller) {
try {
+ Map metaMap = new HashMap();
// Step 1: validate everything
- for (int i = 0; i < list.getConfigurations().length; i++) {
- ConfigurationMetadata metadata = list.getConfigurations()[i];
- validateConfiguration(metadata);
+ for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
+ PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
+ validatePlugin(metadata);
+ metaMap.put(metadata.getModuleId(), metadata);
}
// Step 2: everything is valid, do the installation
- for (int i = 0; i < list.getConfigurations().length; i++) {
+ for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
// 1. Identify the configuration
- ConfigurationMetadata metadata = list.getConfigurations()[i];
+ PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
// 2. Unload obsoleted configurations
List obsoletes = new ArrayList();
for (int j = 0; j < metadata.getObsoletes().length; j++) {
@@ -506,8 +348,12 @@
}
// 3. Download the artifact if necessary, and its dependencies
Set working = new HashSet();
- if(metadata.getConfigId() != null) {
- downloadArtifact(metadata.getConfigId(), list.getMainRepository(), list.getBackupRepositories(),
+ if(metadata.getModuleId() != null) {
+ URL[] repos = pluginsToInstall.getRepositories();
+ if(metadata.getRepositories().length > 0) {
+ repos = metadata.getRepositories();
+ }
+ downloadArtifact(metadata.getModuleId(), metaMap, repos,
username, password, new ResultsFileWriteMonitor(poller), working);
} else {
String[] deps = metadata.getDependencies();
@@ -517,9 +363,13 @@
if(configManager.isRunning(entry)) {
continue;
}
- downloadArtifact(entry, list.getMainRepository(), list.getBackupRepositories(),
+ URL[] repos = pluginsToInstall.getRepositories();
+ if(metadata.getRepositories().length > 0) {
+ repos = metadata.getRepositories();
+ }
+ downloadArtifact(entry, metaMap, repos,
username, password, new ResultsFileWriteMonitor(poller), working);
- poller.addInstalledConfigID(metadata.getConfigId());
+ poller.addInstalledConfigID(metadata.getModuleId());
}
}
// 4. Uninstall obsolete configurations
@@ -528,14 +378,14 @@
configManager.uninstallConfiguration(artifact);
}
// 5. Installation of this configuration finished successfully
- if(metadata.getConfigId() != null) {
- poller.addInstalledConfigID(metadata.getConfigId());
+ if(metadata.getModuleId() != null) {
+ poller.addInstalledConfigID(metadata.getModuleId());
}
}
// Step 3: Start anything that's marked accordingly
- for (int i = 0; i < list.getConfigurations().length; i++) {
- ConfigurationMetadata metadata = list.getConfigurations()[i];
+ for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) {
+ PluginMetadata metadata = pluginsToInstall.getPlugins()[i];
for (int j = 0; j < metadata.getForceStart().length; j++) {
String id = metadata.getForceStart()[j];
Artifact artifact = Artifact.create(id);
@@ -555,67 +405,26 @@
}
/**
- * Installs from a pre-downloaded CAR file
+ * Installs a configuration from a remote repository into the local Geronimo server,
+ * including all its dependencies. The method returns immediately, providing a key
+ * that can be used to poll the status of the download operation. Note that the
+ * installation does not throw exceptions on failure, but instead sets the failure
+ * property of the DownloadResults that the caller can poll for.
+ *
+ * @param pluginsToInstall The list of configurations to install
+ * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ *
+ * @return A key that can be passed to checkOnInstall
*/
- public void install(File carFile, String username, String password, DownloadPoller poller) {
- try {
- // 1. Extract the configuration metadata
- ConfigurationArchiveData data = loadConfigurationArchive(carFile);
- if(data == null) {
- throw new IllegalArgumentException("Invalid Configuration Archive "+carFile.getAbsolutePath()+" see server log for details");
- }
-
- // 2. Validate that we can install this
- validateConfiguration(data.getConfiguration());
-
- // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
- if(data.getConfiguration().getConfigId() != null) {
- ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
- writeableRepo.copyToRepository(carFile, data.getConfiguration().getConfigId(), monitor);
- }
-
- // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
- // This will validate all over again (oh, well)
- install(new ConfigurationList(data.getRepository(), data.getBackups(), new ConfigurationMetadata[]{data.getConfiguration()}),
- username, password, poller);
- } catch (Exception e) {
- poller.setFailure(e);
- } finally {
- poller.setFinished();
- }
- }
-
- private void validateConfiguration(ConfigurationMetadata metadata) throws MissingDependencyException {
- // 1. Check that it's not already running
- if(metadata.getConfigId() != null) { // that is, it's a real configuration not a plugin list
- if(configManager.isRunning(metadata.getConfigId())) {
- throw new IllegalArgumentException("Configuration "+metadata.getConfigId()+" is already running!");
- }
- } else { // Different validation for plugin lists
- for (int i = 0; i < metadata.getDependencies().length; i++) {
- String dep = metadata.getDependencies()[i];
- Artifact artifact = Artifact.create(dep);
- if(!artifact.isResolved()) {
- throw new MissingDependencyException("Configuration list "+metadata.getName()+" may not use partal artifact names for dependencies ("+dep+")");
- }
- }
- }
- // 2. Check that we meet the prerequisites
- ConfigurationMetadata.Prerequisite[] prereqs = metadata.getPrerequisites();
- for (int i = 0; i < prereqs.length; i++) {
- ConfigurationMetadata.Prerequisite prereq = prereqs[i];
- if(resolver.queryArtifacts(prereq.getConfigId()).length == 0) {
- throw new MissingDependencyException("Required configuration '"+prereq.getConfigId()+"' is not installed.");
- }
- }
- }
-
- public Object startInstall(final ConfigurationList configsToInstall, final String username, final String password) {
+ public Object startInstall(final PluginList pluginsToInstall, final String username, final String password) {
Object key = getNextKey();
final DownloadResults results = new DownloadResults();
Runnable work = new Runnable() {
public void run() {
- install(configsToInstall, username, password, results);
+ install(pluginsToInstall, username, password, results);
}
};
asyncKeys.put(key, results);
@@ -627,6 +436,26 @@
return key;
}
+ /**
+ * Installs a configuration downloaded from a remote repository into the local Geronimo
+ * server, including all its dependencies. The method returns immediately, providing a
+ * key that can be used to poll the status of the download operation. Note that the
+ * installation does not throw exceptions on failure, but instead sets the failure
+ * property of the DownloadResults that the caller can poll for.
+ *
+ * @param carFile A CAR file downloaded from a remote repository. This is a packaged
+ * configuration with included configuration information, but it may
+ * still have external dependencies that need to be downloaded
+ * separately. The metadata in the CAR file includes a repository URL
+ * for these downloads, and the username and password arguments are
+ * used in conjunction with that.
+ * @param username Optional username, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ * @param password Optional password, if the maven repo uses HTTP Basic authentication.
+ * Set this to null if no authentication is required.
+ *
+ * @return A key that can be passed to checkOnInstall
+ */
public Object startInstall(final File carFile, final String username, final String password) {
Object key = getNextKey();
final DownloadResults results = new DownloadResults();
@@ -644,6 +473,14 @@
return key;
}
+ /**
+ * Gets the current progress of a download operation. Note that once the
+ * DownloadResults is returned for this operation shows isFinished = true,
+ * the operation will be forgotten, so the caller should be careful not to
+ * call this again after the download has finished.
+ *
+ * @param key Identifies the operation to check on
+ */
public DownloadResults checkOnInstall(Object key) {
DownloadResults results = (DownloadResults) asyncKeys.get(key);
results = results.duplicate();
@@ -654,38 +491,128 @@
}
/**
- * Download (if necessary) and install something, which may be a Configuration or may
- * be just a JAR. For each artifact processed, all its dependencies will be
- * processed as well.
- *
- * @param configID Identifies the artifact to install
- * @param repoURL The main URL to contact the repository
- * @param backups Any additional repositories to search
- * @param username The username used for repositories secured with HTTP Basic authentication
- * @param password The password used for repositories secured with HTTP Basic authentication
- * @param monitor The ongoing results of the download operations, with some monitoring logic
- *
- * @throws IOException When there's a problem reading or writing data
- * @throws FailedLoginException When a repository requires authentication and either no username
- * and password are supplied or the username and password supplied
- * are not accepted
- * @throws MissingDependencyException When a dependency cannot be located in any of the listed repositories
+ * Installs from a pre-downloaded CAR file
*/
- private void downloadArtifact(Artifact configID, URL repoURL, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor, Set soFar) throws IOException, FailedLoginException, MissingDependencyException {
- if(soFar.contains(configID)) {
- return; // Avoid enless work due to circular dependencies
- } else {
- soFar.add(configID);
- }
- //todo: check all repositories?
- Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
- if(matches.length == 0) {
- OpenResult result = openStream(configID, repoURL, backups, username, password, monitor);
- try {
- writeableRepo.copyToRepository(result.getStream(), result.getFileSize(), result.getConfigID(), monitor); //todo: download SNAPSHOTS if previously available?
- monitor.getResults().addDependencyInstalled(configID);
- configID = result.getConfigID();
- } finally {
+ public void install(File carFile, String username, String password, DownloadPoller poller) {
+ try {
+ // 1. Extract the configuration metadata
+ PluginMetadata data = loadCARFile(carFile, true);
+ if(data == null) {
+ throw new IllegalArgumentException("Invalid Configuration Archive "+carFile.getAbsolutePath()+" see server log for details");
+ }
+
+ // 2. Validate that we can install this
+ validatePlugin(data);
+
+ // 3. Install the CAR into the repository (it shouldn't be re-downloaded)
+ if(data.getModuleId() != null) {
+ ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller);
+ writeableRepo.copyToRepository(carFile, data.getModuleId(), monitor);
+ }
+
+ // 4. Use the standard logic to remove obsoletes, install dependencies, etc.
+ // This will validate all over again (oh, well)
+ install(new PluginList(data.getRepositories(), new PluginMetadata[]{data}),
+ username, password, poller);
+ } catch (Exception e) {
+ poller.setFailure(e);
+ } finally {
+ poller.setFinished();
+ }
+ }
+
+ /**
+ * Ensures that a plugin is installable.
+ */
+ private void validatePlugin(PluginMetadata metadata) throws MissingDependencyException {
+ // 1. Check that it's not already running
+ if(metadata.getModuleId() != null) { // that is, it's a real configuration not a plugin list
+ if(configManager.isRunning(metadata.getModuleId())) {
+ throw new IllegalArgumentException("Configuration "+metadata.getModuleId()+" is already running!");
+ }
+ } else { // Different validation for plugin lists
+ for (int i = 0; i < metadata.getDependencies().length; i++) {
+ String dep = metadata.getDependencies()[i];
+ Artifact artifact = Artifact.create(dep);
+ if(!artifact.isResolved()) {
+ throw new MissingDependencyException("Configuration list "+metadata.getName()+" may not use partal artifact names for dependencies ("+dep+")");
+ }
+ }
+ }
+ // 2. Check that we meet the prerequisites
+ PluginMetadata.Prerequisite[] prereqs = metadata.getPrerequisites();
+ for (int i = 0; i < prereqs.length; i++) {
+ PluginMetadata.Prerequisite prereq = prereqs[i];
+ if(resolver.queryArtifacts(prereq.getModuleId()).length == 0) {
+ throw new MissingDependencyException("Required configuration '"+prereq.getModuleId()+"' is not installed.");
+ }
+ }
+ // 3. Check that we meet the Geronimo, JVM versions
+ if(metadata.getGeronimoVersions().length > 0 && !checkGeronimoVersions(metadata.getGeronimoVersions())) {
+ throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on Geronimo "+serverInfo.getVersion());
+ }
+ if(metadata.getJvmVersions().length > 0 && !checkJVMVersions(metadata.getJvmVersions())) {
+ throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on JVM "+System.getProperty("java.version"));
+ }
+ }
+
+ /**
+ * Download (if necessary) and install something, which may be a Configuration or may
+ * be just a JAR. For each artifact processed, all its dependencies will be
+ * processed as well.
+ *
+ * @param configID Identifies the artifact to install
+ * @param repos The URLs to contact the repositories (in order of preference)
+ * @param username The username used for repositories secured with HTTP Basic authentication
+ * @param password The password used for repositories secured with HTTP Basic authentication
+ * @param monitor The ongoing results of the download operations, with some monitoring logic
+ *
+ * @throws IOException When there's a problem reading or writing data
+ * @throws FailedLoginException When a repository requires authentication and either no username
+ * and password are supplied or the username and password supplied
+ * are not accepted
+ * @throws MissingDependencyException When a dependency cannot be located in any of the listed repositories
+ */
+ private void downloadArtifact(Artifact configID, Map metadata, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor, Set soFar) throws IOException, FailedLoginException, MissingDependencyException {
+ if(soFar.contains(configID)) {
+ return; // Avoid enless work due to circular dependencies
+ } else {
+ soFar.add(configID);
+ }
+ //todo: check all repositories?
+ Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID);
+ if(matches.length == 0) { // not present, needs to be downloaded
+ OpenResult result = openStream(configID, repos, username, password, monitor);
+ try {
+ File tempFile = downloadFile(result, monitor);
+ PluginMetadata pluginData = ((PluginMetadata) metadata.get(configID));
+ // Only bother with the hash if we got it from a source other than the download file itself
+ PluginMetadata.Hash hash = pluginData == null ? null : pluginData.getHash();
+ if(hash != null) {
+ String actual = ConfigurationStoreUtil.getActualChecksum(tempFile, hash.getType());
+ if(!actual.equals(hash.getValue())) {
+ throw new IOException("File download incorrect (expected "+hash.getType()+" hash "+hash.getValue()+" but got "+actual+")");
+ }
+ }
+ // See if the download file has plugin metadata
+ if(pluginData == null) {
+ try {
+ pluginData = loadCARFile(tempFile, false);
+ } catch (Exception e) {
+ throw new IOException("Unable to read plugin metadata: "+e.getMessage());
+ }
+ }
+ if(pluginData != null) { // it's a plugin, not a plain JAR
+ validatePlugin(pluginData);
+ }
+ writeableRepo.copyToRepository(tempFile, result.getConfigID(), monitor); //todo: download SNAPSHOTS if previously available?
+ if(!tempFile.delete()) {
+ log.warn("Unable to delete temporary download file "+tempFile.getAbsolutePath());
+ tempFile.deleteOnExit();
+ }
+ monitor.getResults().addDependencyInstalled(configID);
+ configID = result.getConfigID();
+ } finally {
result.getStream().close();
}
} else {
@@ -716,7 +643,7 @@
for (int i = 0; i < dependencies.length; i++) {
Dependency dep = dependencies[i];
Artifact artifact = dep.getArtifact();
- downloadArtifact(artifact, repoURL, backups, username, password, monitor, soFar);
+ downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar);
}
} catch (NoSuchConfigException e) {
throw new IllegalStateException("Installed configuration into repository but ConfigStore does not see it: "+e.getMessage());
@@ -726,6 +653,30 @@
}
/**
+ * Downloads to a temporary file so we can validate the download before
+ * installing into the repository.
+ */
+ private File downloadFile(OpenResult result, ResultsFileWriteMonitor monitor) throws IOException {
+ InputStream in = result.getStream();
+ if(in == null) {
+ throw new IllegalStateException();
+ }
+ monitor.writeStarted(result.getConfigID().toString(), result.fileSize);
+ File file = File.createTempFile("geronimo-plugin-download-", ".tmp");
+ FileOutputStream out = new FileOutputStream(file);
+ byte[] buf = new byte[4096];
+ int count, total = 0;
+ while((count = in.read(buf)) > -1) {
+ out.write(buf, 0, count);
+ monitor.writeProgress(total += count);
+ }
+ monitor.writeComplete(total);
+ in.close();
+ out.close();
+ return file;
+ }
+
+ /**
* Used to get dependencies for a JAR
*/
private static Dependency[] getDependencies(Repository repo, Artifact artifact) {
@@ -752,23 +703,43 @@
return (Dependency[]) dependencies.toArray(new Dependency[dependencies.size()]);
}
+ /**
+ * Constructs a URL to a particular artifact in a particular repository
+ */
private static URL getURL(Artifact configId, URL repository) throws MalformedURLException {
return new URL(repository, configId.getGroupId().replace('.','/')+"/"+configId.getArtifactId()+"/"+configId.getVersion()+"/"+configId.getArtifactId()+"-"+configId.getVersion()+"."+configId.getType());
}
- private static OpenResult openStream(Artifact artifact, URL repo, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException {
+ /**
+ * Attemps to open a stream to an artifact in one of the listed repositories.
+ * The username and password provided are only used if one of the repositories
+ * returns an HTTP authentication failure on the first try.
+ *
+ * @param artifact The artifact we're looking for, or null to just connect to the base repo URL
+ * @param repos The base URLs to the repositories to search for the artifact
+ * @param username A username if one of the repositories might require authentication
+ * @param password A password if one of the repositories might require authentication
+ * @param monitor Callback for progress on the connection operation
+ *
+ * @throws IOException Occurs when the IO with the repository failed
+ * @throws FailedLoginException Occurs when a repository requires authentication and either
+ * no username and password were provided or they weren't
+ * accepted
+ * @throws MissingDependencyException Occurs when none of the repositories has the artifact
+ * in question
+ */
+ private static OpenResult openStream(Artifact artifact, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException {
if(monitor != null) {
monitor.getResults().setCurrentFilePercent(-1);
monitor.getResults().setCurrentMessage("Attempting to download "+artifact);
monitor.setTotalBytes(-1); // In case the server doesn't say
}
if(artifact != null && !artifact.isResolved()) {
- artifact = findArtifact(artifact, repo, backups, username, password, monitor);
+ artifact = findArtifact(artifact, repos, username, password, monitor);
}
InputStream in;
LinkedList list = new LinkedList();
- list.add(repo);
- list.addAll(Arrays.asList(backups));
+ list.addAll(Arrays.asList(repos));
while (true) {
if(list.isEmpty()) {
throw new MissingDependencyException("Unable to download dependency "+artifact);
@@ -786,9 +757,17 @@
}
}
+ /**
+ * Does the meat of connecting to a URL
+ */
private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException {
return connect(url, username, password, monitor, null);
}
+
+ /**
+ * Does the meat of connecting to a URL. Can be used to just test the existance of
+ * something at the specified URL by passing the method 'HEAD'.
+ */
private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor, String method) throws IOException, FailedLoginException {
URLConnection con = url.openConnection();
if(con instanceof HttpURLConnection) {
@@ -850,14 +829,17 @@
}
}
- private static Artifact findArtifact(Artifact query, URL repo, URL[] backups, String username, String password, ResultsFileWriteMonitor monitor) throws MissingDependencyException {
+ /**
+ * Searches for an artifact in the listed repositories, where the artifact
+ * may have wildcards in the ID.
+ */
+ private static Artifact findArtifact(Artifact query, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor) throws MissingDependencyException {
if(query.getGroupId() == null || query.getArtifactId() == null || query.getType() == null) {
throw new MissingDependencyException("No support yet for dependencies missing more than a version: "+query);
}
List list = new ArrayList();
- list.add(repo);
- for (int i = 0; i < backups.length; i++) {
- list.add(backups[i]);
+ for (int i = 0; i < repos.length; i++) {
+ list.add(repos[i]);
}
Artifact result = null;
for (int i = 0; i < list.size(); i++) {
@@ -874,6 +856,10 @@
throw new MissingDependencyException("No repository has a valid artifact for "+query);
}
+ /**
+ * Checks for an artifact in a specific repository, where the artifact may
+ * have wildcards in the ID.
+ */
private static Artifact findArtifact(Artifact query, URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, ParserConfigurationException, SAXException {
monitor.getResults().setCurrentMessage("Searching for "+query+" at "+url);
String base = query.getGroupId().replace('.', '/') + "/" + query.getArtifactId();
@@ -883,8 +869,8 @@
if(in == null) {
return null;
}
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
+ // Don't use the validating parser that we normally do
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = builder.parse(in);
Element root = doc.getDocumentElement();
NodeList list = root.getElementsByTagName("versions");
@@ -911,50 +897,13 @@
return null;
}
- private static class ResultsFileWriteMonitor implements FileWriteMonitor {
- private final DownloadPoller results;
- private int totalBytes;
- private String file;
-
- public ResultsFileWriteMonitor(DownloadPoller results) {
- this.results = results;
- }
-
- public void setTotalBytes(int totalBytes) {
- this.totalBytes = totalBytes;
- }
-
- public int getTotalBytes() {
- return totalBytes;
- }
-
- public void writeStarted(String fileDescription, int fileSize) {
- totalBytes = fileSize;
- file = fileDescription;
- results.setCurrentFile(fileDescription);
- results.setCurrentMessage("Downloading "+fileDescription+"...");
- results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
- }
-
- public void writeProgress(int bytes) {
- if(totalBytes > 0) {
- results.setCurrentFilePercent((bytes*100)/totalBytes);
- } else {
- results.setCurrentMessage((bytes/1024)+" kB of "+file);
- }
- }
-
- public void writeComplete(int bytes) {
- results.setCurrentFilePercent(100);
- results.setCurrentMessage("Downloaded "+file+" ("+(bytes/1024)+" kB)");
- results.addDownloadBytes(bytes);
- }
-
- public DownloadPoller getResults() {
- return results;
- }
- }
-
+ /**
+ * Puts the name and ID of a plugin into the argument map of plugins,
+ * by reading the values out of the provided plugin descriptor file.
+ *
+ * @param xml The geronimo-plugin.xml for this plugin
+ * @param plugins The result map to populate
+ */
private void readNameAndID(File xml, Map plugins) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -969,6 +918,13 @@
}
}
+ /**
+ * Puts the name and ID of a plugin into the argument map of plugins,
+ * by reading the values out of the provided plugin descriptor stream.
+ *
+ * @param xml The geronimo-plugin.xml for this plugin
+ * @param plugins The result map to populate
+ */
private void readNameAndID(InputStream xml, Map plugins) {
try {
SAXParserFactory factory = SAXParserFactory.newInstance();
@@ -983,87 +939,377 @@
}
}
- private static class PluginNameIDHandler extends DefaultHandler {
- private String id = "";
- private String name = "";
- private String element = null;
+ /**
+ * Replaces all the dependency elements in the argument configuration data
+ * with the dependencies from the actual data for that module.
+ */
+ private void overrideDependencies(ConfigurationData data, PluginMetadata metadata) {
+ //todo: this ends up doing a little more work than necessary
+ PluginMetadata temp = createDefaultMetadata(data);
+ metadata.setDependencies(temp.getDependencies());
+ }
- public void characters(char ch[], int start, int length) throws SAXException {
- if(element != null) {
- if(element.equals("config-id")) {
- id += new String(ch, start, length);
- } else if(element.equals("name")) {
- name += new String(ch, start, length);
+ /**
+ * Generates a default plugin metadata based on the data for this module
+ * in the server.
+ */
+ private PluginMetadata createDefaultMetadata(ConfigurationData data) {
+ PluginMetadata meta = new PluginMetadata(data.getId().toString(), // name
+ data.getId(), // module ID
+ "Unknown", // category
+ "Please provide a description",
+ null, // URL
+ null, // author
+ null, // hash
+ true, // installed
+ false);
+ meta.setGeronimoVersions(new String[]{serverInfo.getVersion()});
+ meta.setJvmVersions(new String[0]);
+ meta.setLicenses(new PluginMetadata.License[0]);
+ meta.setObsoletes(new String[0]);
+ List deps = new ArrayList();
+ PluginMetadata.Prerequisite prereq = null;
+ prereq = processDependencyList(data.getEnvironment().getDependencies(), prereq, deps);
+ Map children = data.getChildConfigurations();
+ for (Iterator it = children.values().iterator(); it.hasNext();) {
+ ConfigurationData child = (ConfigurationData) it.next();
+ prereq = processDependencyList(child.getEnvironment().getDependencies(), prereq, deps);
+ }
+ meta.setDependencies((String[]) deps.toArray(new String[deps.size()]));
+ meta.setPrerequisites(prereq == null ? new PluginMetadata.Prerequisite[0] : new PluginMetadata.Prerequisite[]{prereq});
+ return meta;
+ }
+
+ /**
+ * Read the plugin metadata out of a plugin CAR file on disk.
+ */
+ private PluginMetadata loadCARFile(File file, boolean definitelyCAR) throws IOException, ParserConfigurationException, SAXException {
+ if(!file.canRead()) {
+ log.error("Cannot read from downloaded CAR file "+file.getAbsolutePath());
+ return null;
+ }
+ JarFile jar = new JarFile(file);
+ Document doc;
+ try {
+ JarEntry entry = jar.getJarEntry("META-INF/geronimo-plugin.xml");
+ if(entry == null) {
+ if(definitelyCAR) {
+ log.error("Downloaded CAR file does not contain META-INF/geronimo-plugin.xml file");
}
+ jar.close();
+ return null;
}
+ InputStream in = jar.getInputStream(entry);
+ DocumentBuilder builder = createDocumentBuilder();
+ doc = builder.parse(in);
+ in.close();
+ } finally {
+ jar.close();
}
+ return loadPluginMetadata(doc, file.getAbsolutePath());
+ }
- public void endElement(String uri, String localName, String qName) throws SAXException {
- element = null;
+ /**
+ * Read a set of plugin metadata from a DOM document.
+ */
+ private PluginMetadata loadPluginMetadata(Document doc, String file) throws SAXException, MalformedURLException {
+ Element root = doc.getDocumentElement();
+ if(!root.getNodeName().equals("geronimo-plugin")) {
+ log.error("Configuration archive "+file+" does not have a geronimo-plugin in META-INF/geronimo-plugin.xml");
+ return null;
}
+ return processPlugin(root);
+ }
- public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
- if(qName.equals("config-id") || qName.equals("name")) {
- element = qName;
+ /**
+ * Loads the list of all available plugins from the specified stream
+ * (representing geronimo-plugins.xml at the specified repository).
+ */
+ private PluginList loadPluginList(URL repo, InputStream in) throws ParserConfigurationException, IOException, SAXException {
+ DocumentBuilder builder = createDocumentBuilder();
+ Document doc = builder.parse(in);
+ in.close();
+ Element root = doc.getDocumentElement(); // geronimo-plugin-list
+ NodeList configs = root.getElementsByTagName("plugin");
+ List results = new ArrayList();
+ for (int i = 0; i < configs.getLength(); i++) {
+ Element config = (Element) configs.item(i);
+ PluginMetadata data = processPlugin(config);
+ results.add(data);
+ }
+ String[] repos = getChildrenText(root, "default-repository");
+ URL[] repoURLs = new URL[repos.length];
+ for(int i = 0; i < repos.length; i++) {
+ if(repos[i].endsWith("/")) {
+ repoURLs[i] = new URL(repos[i]);
+ } else {
+ repoURLs[i] = new URL(repos[i]+"/");
}
}
- public void endDocument() throws SAXException {
- id = id.trim();
- name = name.trim();
+ PluginMetadata[] data = (PluginMetadata[]) results.toArray(new PluginMetadata[results.size()]);
+ return new PluginList(repoURLs, data);
+ }
+
+ /**
+ * Common logic for setting up a document builder to deal with plugin files.
+ * @return
+ * @throws ParserConfigurationException
+ */
+ private static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ factory.setValidating(true);
+ factory.setNamespaceAware(true);
+ factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage",
+ "http://www.w3.org/2001/XMLSchema");
+ factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource",
+ PluginInstallerGBean.class.getResourceAsStream("/META-INF/schema/plugins-1.1.xsd"));
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ builder.setErrorHandler(new ErrorHandler() {
+ public void error(SAXParseException exception) throws SAXException {
+ throw new SAXException("Unable to read plugin file", exception);
+ }
+
+ public void fatalError(SAXParseException exception) throws SAXException {
+ throw new SAXException("Unable to read plugin file", exception);
+ }
+
+ public void warning(SAXParseException exception) {
+ log.warn("Warning reading XML document", exception);
+ }
+ });
+ return builder;
+ }
+
+ /**
+ * Given a DOM element representing a plugin, load it into a PluginMetadata
+ * object.
+ */
+ private PluginMetadata processPlugin(Element plugin) throws SAXException, MalformedURLException {
+ String moduleId = getChildText(plugin, "module-id");
+ NodeList licenseNodes = plugin.getElementsByTagName("license");
+ PluginMetadata.License[] licenses = new PluginMetadata.License[licenseNodes.getLength()];
+ for(int j=0; j<licenseNodes.getLength(); j++) {
+ Element node = (Element) licenseNodes.item(j);
+ String licenseName = getText(node);
+ String openSource = node.getAttribute("osi-approved");
+ if(licenseName == null || licenseName.equals("") || openSource == null || openSource.equals("")) {
+ throw new SAXException("Invalid config file: license name and osi-approved flag required");
+ }
+ licenses[j] = new PluginMetadata.License(licenseName, Boolean.valueOf(openSource).booleanValue());
+ }
+ PluginMetadata.Hash hash = null;
+ NodeList hashList = plugin.getElementsByTagName("hash");
+ if(hashList.getLength() > 0) {
+ Element elem = (Element) hashList.item(0);
+ hash = new PluginMetadata.Hash(elem.getAttribute("type"), getText(elem));
}
+ boolean eligible = true;
+ NodeList preNodes = plugin.getElementsByTagName("prerequisite");
+ PluginMetadata.Prerequisite[] prereqs = new PluginMetadata.Prerequisite[preNodes.getLength()];
+ for(int j=0; j<preNodes.getLength(); j++) {
+ Element node = (Element) preNodes.item(j);
+ String originalConfigId = getChildText(node, "id");
+ if(originalConfigId == null) {
+ throw new SAXException("Prerequisite requires <id>");
+ }
+ Artifact artifact = Artifact.create(originalConfigId.replaceAll("\\*", ""));
+ boolean present = resolver.queryArtifacts(artifact).length > 0;
+ prereqs[j] = new PluginMetadata.Prerequisite(artifact, present,
+ getChildText(node, "resource-type"), getChildText(node, "description"));
+ if(!present) {
+ log.debug(moduleId+" is not eligible due to missing "+prereqs[j].getModuleId());
+ eligible = false;
+ }
+ }
+ String[] gerVersions = getChildrenText(plugin, "geronimo-version");
+ if(gerVersions.length > 0) {
+ boolean match = checkGeronimoVersions(gerVersions);
+ if(!match) eligible = false;
+ }
+ String[] jvmVersions = getChildrenText(plugin, "jvm-version");
+ if(jvmVersions.length > 0) {
+ boolean match = checkJVMVersions(jvmVersions);
+ if(!match) eligible = false;
+ }
+ String[] repoNames = getChildrenText(plugin, "source-repository");
+ URL[] repos = new URL[repoNames.length];
+ for (int i = 0; i < repos.length; i++) {
+ repos[i] = new URL(repoNames[i]);
+ }
+ Artifact artifact = null;
+ boolean installed = false;
+ if (moduleId != null) {
+ artifact = Artifact.create(moduleId);
+ // Tests, etc. don't need to have a ConfigurationManager
+ installed = configManager != null && configManager.isLoaded(artifact);
+ }
+ log.trace("Checking "+moduleId+": installed="+installed+", eligible="+eligible);
+ PluginMetadata data = new PluginMetadata(getChildText(plugin, "name"),
+ artifact,
+ getChildText(plugin, "category"),
+ getChildText(plugin, "description"),
+ getChildText(plugin, "url"),
+ getChildText(plugin, "author"),
+ hash,
+ installed, eligible);
+ data.setGeronimoVersions(gerVersions);
+ data.setJvmVersions(jvmVersions);
+ data.setLicenses(licenses);
+ data.setPrerequisites(prereqs);
+ data.setRepositories(repos);
+ NodeList list = plugin.getElementsByTagName("dependency");
+ List start = new ArrayList();
+ String deps[] = new String[list.getLength()];
+ for(int i=0; i<list.getLength(); i++) {
+ Element node = (Element) list.item(i);
+ deps[i] = getText(node);
+ if(node.hasAttribute("start") && node.getAttribute("start").equalsIgnoreCase("true")) {
+ start.add(deps[i]);
+ }
+ }
+ data.setDependencies(deps);
+ data.setForceStart((String[]) start.toArray(new String[start.size()]));
+ data.setObsoletes(getChildrenText(plugin, "obsoletes"));
+ return data;
+ }
- public String getID() {
- return id;
+ /**
+ * Check whether the specified JVM versions match the current runtime
+ * environment.
+ *
+ * @return true if the specified versions match the current
+ * execution environment as defined by plugins-1.1.xsd
+ */
+ private boolean checkJVMVersions(String[] jvmVersions) {
+ if(jvmVersions.length == 0) return true;
+ String version = System.getProperty("java.version");
+ boolean match = false;
+ for (int j = 0; j < jvmVersions.length; j++) {
+ String jvmVersion = jvmVersions[j];
+ if(jvmVersion == null || jvmVersion.equals("")) {
+ throw new IllegalStateException("jvm-version should not be empty!");
+ }
+ if(version.startsWith(jvmVersion)) {
+ match = true;
+ break;
+ }
}
+ return match;
+ }
- public String getName() {
- return name;
+ /**
+ * Check whether the specified Geronimo versions match the current runtime
+ * environment.
+ *
+ * @return true if the specified versions match the current
+ * execution environment as defined by plugins-1.1.xsd
+ */
+ private boolean checkGeronimoVersions(String[] gerVersions) {
+ if(gerVersions.length == 0) return true;
+ String version = serverInfo.getVersion();
+ boolean match = false;
+ for (int j = 0; j < gerVersions.length; j++) {
+ String gerVersion = gerVersions[j];
+ if(gerVersion == null || gerVersion.equals("")) {
+ throw new IllegalStateException("geronimo-version should not be empty!");
+ }
+ if(gerVersion.equals(version)) {
+ match = true;
+ break;
+ }
}
+ return match;
+ }
- public boolean isComplete() {
- return !id.equals("") && !name.equals("");
+ /**
+ * Gets the text out of a child of the specified DOM element.
+ *
+ * @param root The parent DOM element
+ * @param property The name of the child element that holds the text
+ */
+ private static String getChildText(Element root, String property) {
+ NodeList children = root.getChildNodes();
+ for(int i=0; i<children.getLength(); i++) {
+ Node check = children.item(i);
+ if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
+ return getText(check);
+ }
}
+ return null;
}
- private void overrideDependencies(ConfigurationData data, ConfigurationMetadata metadata) {
- //todo: this ends up doing a little more work than necessary
- ConfigurationMetadata temp = createDefaultMetadata(data);
- metadata.setDependencies(temp.getDependencies());
+ /**
+ * Gets all the text contents of the specified DOM node.
+ */
+ private static String getText(Node target) {
+ NodeList nodes = target.getChildNodes();
+ StringBuffer buf = null;
+ for(int j=0; j<nodes.getLength(); j++) {
+ Node node = nodes.item(j);
+ if(node.getNodeType() == Node.TEXT_NODE) {
+ if(buf == null) {
+ buf = new StringBuffer();
+ }
+ buf.append(node.getNodeValue());
+ }
+ }
+ return buf == null ? null : buf.toString();
}
- private ConfigurationMetadata createDefaultMetadata(ConfigurationData data) {
- ConfigurationMetadata meta = new ConfigurationMetadata(data.getId(), data.getId().toString(),
- "Please provide a description", "Unknown", true, false);
- meta.setGeronimoVersions(new String[]{serverInfo.getVersion()});
- meta.setJvmVersions(new String[0]);
- meta.setLicenses(new ConfigurationMetadata.License[0]);
- meta.setObsoletes(new String[0]);
- List deps = new ArrayList();
- ConfigurationMetadata.Prerequisite prereq = null;
- prereq = processDependencyList(data.getEnvironment().getDependencies(), prereq, deps);
- Map children = data.getChildConfigurations();
- for (Iterator it = children.values().iterator(); it.hasNext();) {
- ConfigurationData child = (ConfigurationData) it.next();
- prereq = processDependencyList(child.getEnvironment().getDependencies(), prereq, deps);
+ /**
+ * Gets the text out of all the child nodes of a certain type. The result
+ * array has one element for each child of the specified DOM element that
+ * has the specified name.
+ *
+ * @param root The parent DOM element
+ * @param property The name of the child elements that hold the text
+ */
+ private static String[] getChildrenText(Element root, String property) {
+ NodeList children = root.getChildNodes();
+ List results = new ArrayList();
+ for(int i=0; i<children.getLength(); i++) {
+ Node check = children.item(i);
+ if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) {
+ NodeList nodes = check.getChildNodes();
+ StringBuffer buf = null;
+ for(int j=0; j<nodes.getLength(); j++) {
+ Node node = nodes.item(j);
+ if(node.getNodeType() == Node.TEXT_NODE) {
+ if(buf == null) {
+ buf = new StringBuffer();
+ }
+ buf.append(node.getNodeValue());
+ }
+ }
+ results.add(buf == null ? null : buf.toString());
+ }
}
- meta.setDependencies((String[]) deps.toArray(new String[deps.size()]));
- meta.setPrerequisites(prereq == null ? new ConfigurationMetadata.Prerequisite[0] : new ConfigurationMetadata.Prerequisite[]{prereq});
- return meta;
+ return (String[]) results.toArray(new String[results.size()]);
}
- private ConfigurationMetadata.Prerequisite processDependencyList(List real, ConfigurationMetadata.Prerequisite prereq, List deps) {
+ /**
+ * Generates dependencies and an optional prerequisite based on a list of
+ * dependencies for a Gernonimo module.
+ *
+ * @param real A list with elements of type Dependency
+ * @param prereq The incoming prerequisite (if any), which may be replaced
+ * @param deps A list with elements of type String (holding a module ID / Artifact name)
+ *
+ * @return The resulting prerequisite, if any.
+ */
+ private PluginMetadata.Prerequisite processDependencyList(List real, PluginMetadata.Prerequisite prereq, List deps) {
for (int i = 0; i < real.size(); i++) {
Dependency dep = (Dependency) real.get(i);
if(dep.getArtifact().getGroupId().equals("geronimo")) {
if(dep.getArtifact().getArtifactId().indexOf("jetty") > -1) {
if(prereq == null) {
- prereq = new ConfigurationMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Jetty distribution. It is not intended to run in the Geronimo/Tomcat distribution. There is a separate version of this plugin that works with Tomcat.");
+ prereq = new PluginMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Jetty distribution. It is not intended to run in the Geronimo/Tomcat distribution. There is a separate version of this plugin that works with Tomcat.");
}
continue;
} else if(dep.getArtifact().getArtifactId().indexOf("tomcat") > -1) {
if(prereq == null) {
- prereq = new ConfigurationMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Tomcat distribution. It is not intended to run in the Geronimo/Jetty distribution. There is a separate version of this plugin that works with Jetty.");
+ prereq = new PluginMetadata.Prerequisite(dep.getArtifact(), true, "Web Container", "This plugin works with the Geronimo/Tomcat distribution. It is not intended to run in the Geronimo/Jetty distribution. There is a separate version of this plugin that works with Jetty.");
}
continue;
}
@@ -1075,27 +1321,39 @@
return prereq;
}
- private static Document writeConfigurationMetadata(ConfigurationArchiveData metadata) throws ParserConfigurationException {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder builder = factory.newDocumentBuilder();
+ /**
+ * Writes plugin metadata to a DOM tree.
+ */
+ private static Document writePluginMetadata(PluginMetadata data) throws ParserConfigurationException {
+ DocumentBuilder builder = createDocumentBuilder();
Document doc = builder.newDocument();
- Element root = doc.createElement("geronimo-plugin");
- doc.appendChild(root);
- Element config = doc.createElement("configuration");
- root.appendChild(config);
+ Element config = doc.createElementNS("http://geronimo.apache.org/xml/ns/plugins-1.1", "geronimo-plugin");
+ config.setAttribute("xmlns", "http://geronimo.apache.org/xml/ns/plugins-1.1");
+ doc.appendChild(config);
- ConfigurationMetadata data = metadata.getConfiguration();
addTextChild(doc, config, "name", data.getName());
- addTextChild(doc, config, "config-id", data.getConfigId().toString());
+ addTextChild(doc, config, "config-id", data.getModuleId().toString());
addTextChild(doc, config, "category", data.getCategory());
addTextChild(doc, config, "description", data.getDescription());
+ if(data.getPluginURL() != null) {
+ addTextChild(doc, config, "url", data.getPluginURL());
+ }
+ if(data.getAuthor() != null) {
+ addTextChild(doc, config, "author", data.getAuthor());
+ }
for (int i = 0; i < data.getLicenses().length; i++) {
- ConfigurationMetadata.License license = data.getLicenses()[i];
+ PluginMetadata.License license = data.getLicenses()[i];
Element lic = doc.createElement("license");
lic.appendChild(doc.createTextNode(license.getName()));
lic.setAttribute("osi-approved", Boolean.toString(license.isOsiApproved()));
config.appendChild(lic);
}
+ if(data.getHash() != null) {
+ Element hash = doc.createElement("hash");
+ hash.setAttribute("type", data.getHash().getType());
+ hash.appendChild(doc.createTextNode(data.getHash().getValue()));
+ config.appendChild(hash);
+ }
for (int i = 0; i < data.getGeronimoVersions().length; i++) {
addTextChild(doc, config, "geronimo-version", data.getGeronimoVersions()[i]);
}
@@ -1103,9 +1361,9 @@
addTextChild(doc, config, "jvm-version", data.getJvmVersions()[i]);
}
for (int i = 0; i < data.getPrerequisites().length; i++) {
- ConfigurationMetadata.Prerequisite prereq = data.getPrerequisites()[i];
+ PluginMetadata.Prerequisite prereq = data.getPrerequisites()[i];
Element pre = doc.createElement("prerequisite");
- addTextChild(doc, pre, "id", prereq.getConfigId().toString());
+ addTextChild(doc, pre, "id", prereq.getModuleId().toString());
if(prereq.getResourceType() != null) {
addTextChild(doc, pre, "resource-type", prereq.getResourceType());
}
@@ -1120,25 +1378,134 @@
for (int i = 0; i < data.getObsoletes().length; i++) {
addTextChild(doc, config, "obsoletes", data.getObsoletes()[i]);
}
-
- Element repo = doc.createElement("source-repository");
- repo.appendChild(doc.createTextNode(metadata.getRepository() == null ? "" : metadata.getRepository().toString()));
- root.appendChild(repo);
- for (int i = 0; i < metadata.getBackups().length; i++) {
- URL url = metadata.getBackups()[i];
- Element backup = doc.createElement("backup-repository");
- backup.appendChild(doc.createTextNode(url.toString()));
- root.appendChild(backup);
+ for (int i = 0; i < data.getRepositories().length; i++) {
+ URL url = data.getRepositories()[i];
+ addTextChild(doc, config, "source-repository", url.toString());
}
return doc;
}
+ /**
+ * Adds a child of the specified Element that just has the specified text content
+ * @param doc The document
+ * @param parent The parent element
+ * @param name The name of the child element to add
+ * @param text The contents of the child element to add
+ */
private static void addTextChild(Document doc, Element parent, String name, String text) {
Element child = doc.createElement(name);
child.appendChild(doc.createTextNode(text));
parent.appendChild(child);
}
+ /**
+ * Gets a token unique to this run of the server, used to track asynchronous
+ * downloads.
+ */
+ private static Object getNextKey() {
+ int value;
+ synchronized(PluginInstallerGBean.class) {
+ value = ++counter;
+ }
+ return new Integer(value);
+ }
+
+ /**
+ * Helper clas to extract a name and module ID from a plugin metadata file.
+ */
+ private static class PluginNameIDHandler extends DefaultHandler {
+ private String id = "";
+ private String name = "";
+ private String element = null;
+
+ public void characters(char ch[], int start, int length) throws SAXException {
+ if(element != null) {
+ if(element.equals("module-id")) {
+ id += new String(ch, start, length);
+ } else if(element.equals("name")) {
+ name += new String(ch, start, length);
+ }
+ }
+ }
+
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ element = null;
+ }
+
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if(qName.equals("module-id") || qName.equals("name")) {
+ element = qName;
+ }
+ }
+
+ public void endDocument() throws SAXException {
+ id = id.trim();
+ name = name.trim();
+ }
+
+ public String getID() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isComplete() {
+ return !id.equals("") && !name.equals("");
+ }
+ }
+
+ /**
+ * Helper class to bridge a FileWriteMonitor to a DownloadPoller.
+ */
+ private static class ResultsFileWriteMonitor implements FileWriteMonitor {
+ private final DownloadPoller results;
+ private int totalBytes;
+ private String file;
+
+ public ResultsFileWriteMonitor(DownloadPoller results) {
+ this.results = results;
+ }
+
+ public void setTotalBytes(int totalBytes) {
+ this.totalBytes = totalBytes;
+ }
+
+ public int getTotalBytes() {
+ return totalBytes;
+ }
+
+ public void writeStarted(String fileDescription, int fileSize) {
+ totalBytes = fileSize;
+ file = fileDescription;
+ results.setCurrentFile(fileDescription);
+ results.setCurrentMessage("Downloading "+fileDescription+"...");
+ results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1);
+ }
+
+ public void writeProgress(int bytes) {
+ if(totalBytes > 0) {
+ results.setCurrentFilePercent((bytes*100)/totalBytes);
+ } else {
+ results.setCurrentMessage((bytes/1024)+" kB of "+file);
+ }
+ }
+
+ public void writeComplete(int bytes) {
+ results.setCurrentFilePercent(100);
+ results.setCurrentMessage("Downloaded "+file+" ("+(bytes/1024)+" kB)");
+ results.addDownloadBytes(bytes);
+ }
+
+ public DownloadPoller getResults() {
+ return results;
+ }
+ }
+
+ /**
+ * Interesting data resulting from opening a connection to a remote file.
+ */
private static class OpenResult {
private final InputStream stream;
private final Artifact configID;
@@ -1166,13 +1533,13 @@
public static final GBeanInfo GBEAN_INFO;
static {
- GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(ConfigInstallerGBean.class);
+ GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(PluginInstallerGBean.class);
infoFactory.addReference("ConfigManager", ConfigurationManager.class, "ConfigurationManager");
infoFactory.addReference("Repository", WritableListableRepository.class, "Repository");
infoFactory.addReference("ConfigStore", ConfigurationStore.class, "ConfigurationStore");
infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean");
infoFactory.addReference("ThreadPool", ThreadPool.class, "GBean");
- infoFactory.addInterface(ConfigurationInstaller.class);
+ infoFactory.addInterface(PluginInstaller.class);
infoFactory.setConstructor(new String[]{"ConfigManager", "Repository", "ConfigStore", "ServerInfo","ThreadPool"});