001 /** 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one or more 004 * contributor license agreements. See the NOTICE file distributed with 005 * this work for additional information regarding copyright ownership. 006 * The ASF licenses this file to You under the Apache License, Version 2.0 007 * (the "License"); you may not use this file except in compliance with 008 * the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 package org.apache.geronimo.system.plugin; 019 020 import java.io.File; 021 import java.io.FileNotFoundException; 022 import java.io.FileOutputStream; 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.BufferedOutputStream; 026 import java.net.HttpURLConnection; 027 import java.net.MalformedURLException; 028 import java.net.URL; 029 import java.net.URLConnection; 030 import java.util.ArrayList; 031 import java.util.Arrays; 032 import java.util.Collection; 033 import java.util.Collections; 034 import java.util.HashMap; 035 import java.util.HashSet; 036 import java.util.Iterator; 037 import java.util.LinkedList; 038 import java.util.List; 039 import java.util.Map; 040 import java.util.Set; 041 import java.util.SortedSet; 042 import java.util.Enumeration; 043 import java.util.jar.JarEntry; 044 import java.util.jar.JarFile; 045 import java.util.jar.JarOutputStream; 046 import java.util.jar.Manifest; 047 import java.util.zip.ZipEntry; 048 import javax.security.auth.login.FailedLoginException; 049 import javax.xml.parsers.DocumentBuilder; 050 import javax.xml.parsers.DocumentBuilderFactory; 051 import javax.xml.parsers.ParserConfigurationException; 052 import javax.xml.parsers.SAXParser; 053 import javax.xml.parsers.SAXParserFactory; 054 import javax.xml.transform.OutputKeys; 055 import javax.xml.transform.Transformer; 056 import javax.xml.transform.TransformerFactory; 057 import javax.xml.transform.dom.DOMSource; 058 import javax.xml.transform.stream.StreamResult; 059 import org.apache.commons.logging.Log; 060 import org.apache.commons.logging.LogFactory; 061 import org.apache.geronimo.gbean.GBeanInfo; 062 import org.apache.geronimo.gbean.GBeanInfoBuilder; 063 import org.apache.geronimo.kernel.config.ConfigurationData; 064 import org.apache.geronimo.kernel.config.ConfigurationManager; 065 import org.apache.geronimo.kernel.config.ConfigurationStore; 066 import org.apache.geronimo.kernel.config.InvalidConfigException; 067 import org.apache.geronimo.kernel.config.NoSuchConfigException; 068 import org.apache.geronimo.kernel.repository.Artifact; 069 import org.apache.geronimo.kernel.repository.ArtifactResolver; 070 import org.apache.geronimo.kernel.repository.DefaultArtifactResolver; 071 import org.apache.geronimo.kernel.repository.Dependency; 072 import org.apache.geronimo.kernel.repository.FileWriteMonitor; 073 import org.apache.geronimo.kernel.repository.ImportType; 074 import org.apache.geronimo.kernel.repository.MissingDependencyException; 075 import org.apache.geronimo.kernel.repository.Repository; 076 import org.apache.geronimo.kernel.repository.Version; 077 import org.apache.geronimo.kernel.repository.WritableListableRepository; 078 import org.apache.geronimo.kernel.InvalidGBeanException; 079 import org.apache.geronimo.kernel.util.XmlUtil; 080 import org.apache.geronimo.system.configuration.ConfigurationStoreUtil; 081 import org.apache.geronimo.system.configuration.GBeanOverride; 082 import org.apache.geronimo.system.configuration.PluginAttributeStore; 083 import org.apache.geronimo.system.serverinfo.ServerInfo; 084 import org.apache.geronimo.system.threads.ThreadPool; 085 import org.apache.geronimo.util.encoders.Base64; 086 import org.w3c.dom.Document; 087 import org.w3c.dom.Element; 088 import org.w3c.dom.Node; 089 import org.w3c.dom.NodeList; 090 import org.xml.sax.Attributes; 091 import org.xml.sax.ErrorHandler; 092 import org.xml.sax.SAXException; 093 import org.xml.sax.SAXParseException; 094 import org.xml.sax.helpers.DefaultHandler; 095 096 /** 097 * A GBean that knows how to download configurations from a Maven repository. 098 * 099 * @version $Rev: 470597 $ $Date: 2006-11-02 15:30:55 -0800 (Thu, 02 Nov 2006) $ 100 */ 101 public class PluginInstallerGBean implements PluginInstaller { 102 private final static Log log = LogFactory.getLog(PluginInstallerGBean.class); 103 private static int counter; 104 private ConfigurationManager configManager; 105 private WritableListableRepository writeableRepo; 106 private ConfigurationStore configStore; 107 private ArtifactResolver resolver; 108 private ServerInfo serverInfo; 109 private Map asyncKeys; 110 private ThreadPool threadPool; 111 private PluginAttributeStore attributeStore; 112 113 public PluginInstallerGBean(ConfigurationManager configManager, WritableListableRepository repository, ConfigurationStore configStore, ServerInfo serverInfo, ThreadPool threadPool, PluginAttributeStore store) { 114 this.configManager = configManager; 115 this.writeableRepo = repository; 116 this.configStore = configStore; 117 this.serverInfo = serverInfo; 118 this.threadPool = threadPool; 119 resolver = new DefaultArtifactResolver(null, writeableRepo); 120 asyncKeys = Collections.synchronizedMap(new HashMap()); 121 attributeStore = store; 122 } 123 124 /** 125 * Lists the plugins installed in the local Geronimo server, by name and 126 * ID. 127 * 128 * @return A Map with key type String (plugin name) and value type Artifact 129 * (config ID of the plugin). 130 */ 131 public Map getInstalledPlugins() { 132 SortedSet artifacts = writeableRepo.list(); 133 134 Map plugins = new HashMap(); 135 for (Iterator i = artifacts.iterator(); i.hasNext();) { 136 Artifact configId = (Artifact) i.next(); 137 File dir = writeableRepo.getLocation(configId); 138 if(dir.isDirectory()) { 139 File meta = new File(dir, "META-INF"); 140 if(!meta.isDirectory() || !meta.canRead()) { 141 continue; 142 } 143 File xml = new File(meta, "geronimo-plugin.xml"); 144 if(!xml.isFile() || !xml.canRead() || xml.length() == 0) { 145 continue; 146 } 147 readNameAndID(xml, plugins); 148 } else { 149 if(!dir.isFile() || !dir.canRead()) { 150 throw new IllegalStateException("Cannot read artifact dir "+dir.getAbsolutePath()); 151 } 152 try { 153 JarFile jar = new JarFile(dir); 154 try { 155 ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml"); 156 if(entry == null) { 157 continue; 158 } 159 InputStream in = jar.getInputStream(entry); 160 readNameAndID(in, plugins); 161 in.close(); 162 } finally { 163 jar.close(); 164 } 165 } catch (IOException e) { 166 log.error("Unable to read JAR file "+dir.getAbsolutePath(), e); 167 } 168 } 169 } 170 return plugins; 171 } 172 173 /** 174 * Gets a CofigurationMetadata for a configuration installed in the local 175 * server. Should load a saved one if available, or else create a new 176 * default one to the best of its abilities. 177 * 178 * @param moduleId Identifies the configuration. This must match a 179 * configuration currently installed in the local server. 180 * The configId must be fully resolved (isResolved() == true) 181 */ 182 public PluginMetadata getPluginMetadata(Artifact moduleId) { 183 if(configManager != null) { 184 if(!configManager.isConfiguration(moduleId)) { 185 return null; 186 } 187 } else { 188 if(!configStore.containsConfiguration(moduleId)) { 189 return null; 190 } 191 } 192 File dir = writeableRepo.getLocation(moduleId); 193 Document doc; 194 ConfigurationData configData; 195 String source = dir.getAbsolutePath(); 196 try { 197 if(dir.isDirectory()) { 198 File meta = new File(dir, "META-INF"); 199 if(!meta.isDirectory() || !meta.canRead()) { 200 return null; 201 } 202 File xml = new File(meta, "geronimo-plugin.xml"); 203 configData = configStore.loadConfiguration(moduleId); 204 if(!xml.isFile() || !xml.canRead() || xml.length() == 0) { 205 return createDefaultMetadata(configData); 206 } 207 source = xml.getAbsolutePath(); 208 DocumentBuilder builder = createDocumentBuilder(); 209 doc = builder.parse(xml); 210 } else { 211 if(!dir.isFile() || !dir.canRead()) { 212 throw new IllegalStateException("Cannot read configuration "+dir.getAbsolutePath()); 213 } 214 configData = configStore.loadConfiguration(moduleId); 215 JarFile jar = new JarFile(dir); 216 try { 217 ZipEntry entry = jar.getEntry("META-INF/geronimo-plugin.xml"); 218 if(entry == null) { 219 return createDefaultMetadata(configData); 220 } 221 source = dir.getAbsolutePath()+"#META-INF/geronimo-plugin.xml"; 222 InputStream in = jar.getInputStream(entry); 223 DocumentBuilder builder = createDocumentBuilder(); 224 doc = builder.parse(in); 225 in.close(); 226 } finally { 227 jar.close(); 228 } 229 } 230 PluginMetadata result = loadPluginMetadata(doc, source); 231 overrideDependencies(configData, result); 232 return result; 233 } catch (InvalidConfigException e) { 234 e.printStackTrace(); 235 log.warn("Unable to generate metadata for "+moduleId, e); 236 } catch (Exception e) { 237 e.printStackTrace(); 238 log.warn("Invalid XML at "+source, e); 239 } 240 return null; 241 } 242 243 /** 244 * Saves a ConfigurationMetadata for a particular plugin, if the server is 245 * able to record it. This can be used if you later re-export the plugin, 246 * or just want to review the information for a particular installed 247 * plugin. 248 * 249 * @param metadata The data to save. The contained configId (which must 250 * be fully resolved) identifies the configuration to save 251 * this for. 252 */ 253 public void updatePluginMetadata(PluginMetadata metadata) { 254 File dir = writeableRepo.getLocation(metadata.getModuleId()); 255 if(dir == null) { 256 throw new IllegalArgumentException(metadata.getModuleId()+" is not installed!"); 257 } 258 if(!dir.isDirectory()) { // must be a packed (JAR-formatted) plugin 259 try { 260 File temp = new File(dir.getParentFile(), dir.getName()+".temp"); 261 JarFile input = new JarFile(dir); 262 Manifest manifest = input.getManifest(); 263 JarOutputStream out = manifest == null ? new JarOutputStream(new BufferedOutputStream(new FileOutputStream(temp))) 264 : new JarOutputStream(new BufferedOutputStream(new FileOutputStream(temp)), manifest); 265 Enumeration en = input.entries(); 266 byte[] buf = new byte[4096]; 267 int count; 268 while (en.hasMoreElements()) { 269 JarEntry entry = (JarEntry) en.nextElement(); 270 if(entry.getName().equals("META-INF/geronimo-plugin.xml")) { 271 entry = new JarEntry(entry.getName()); 272 out.putNextEntry(entry); 273 Document doc = writePluginMetadata(metadata); 274 TransformerFactory xfactory = XmlUtil.newTransformerFactory(); 275 Transformer xform = xfactory.newTransformer(); 276 xform.setOutputProperty(OutputKeys.INDENT, "yes"); 277 xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 278 xform.transform(new DOMSource(doc), new StreamResult(out)); 279 } else if(entry.getName().equals("META-INF/MANIFEST.MF")) { 280 // do nothing, already passed in a manifest 281 } else { 282 out.putNextEntry(entry); 283 InputStream in = input.getInputStream(entry); 284 while((count = in.read(buf)) > -1) { 285 out.write(buf, 0, count); 286 } 287 in.close(); 288 out.closeEntry(); 289 } 290 } 291 out.flush(); 292 out.close(); 293 input.close(); 294 if(!dir.delete()) { 295 throw new IOException("Unable to delete old plugin at "+dir.getAbsolutePath()); 296 } 297 if(!temp.renameTo(dir)) { 298 throw new IOException("Unable to move new plugin "+temp.getAbsolutePath()+" to "+dir.getAbsolutePath()); 299 } 300 } catch (Exception e) { 301 log.error("Unable to update plugin metadata", e); 302 throw new RuntimeException("Unable to update plugin metadata", e); 303 } // TODO this really should have a finally block to ensure streams are closed 304 } else { 305 File meta = new File(dir, "META-INF"); 306 if(!meta.isDirectory() || !meta.canRead()) { 307 throw new IllegalArgumentException(metadata.getModuleId()+" is not a plugin!"); 308 } 309 File xml = new File(meta, "geronimo-plugin.xml"); 310 FileOutputStream fos = null; 311 try { 312 if(!xml.isFile()) { 313 if(!xml.createNewFile()) { 314 throw new RuntimeException("Cannot create plugin metadata file for "+metadata.getModuleId()); 315 } 316 } 317 Document doc = writePluginMetadata(metadata); 318 TransformerFactory xfactory = XmlUtil.newTransformerFactory(); 319 Transformer xform = xfactory.newTransformer(); 320 xform.setOutputProperty(OutputKeys.INDENT, "yes"); 321 xform.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); 322 fos = new FileOutputStream(xml); 323 // use a FileOutputStream instead of a File on the StreamResult 324 // constructor as problems were encountered with the file not being closed. 325 StreamResult sr = new StreamResult(fos); 326 xform.transform(new DOMSource(doc), sr); 327 } catch (Exception e) { 328 log.error("Unable to save plugin metadata for "+metadata.getModuleId(), e); 329 } finally { 330 if (fos != null) { 331 try { 332 fos.close(); 333 } catch (IOException ignored) { 334 // ignored 335 } 336 } 337 } 338 } 339 } 340 341 /** 342 * Lists the plugins available for download in a particular Geronimo repository. 343 * 344 * @param mavenRepository The base URL to the maven repository. This must 345 * contain the file geronimo-plugins.xml 346 * @param username Optional username, if the maven repo uses HTTP Basic authentication. 347 * Set this to null if no authentication is required. 348 * @param password Optional password, if the maven repo uses HTTP Basic authentication. 349 * Set this to null if no authentication is required. 350 */ 351 public PluginList listPlugins(URL mavenRepository, String username, String password) throws IOException, FailedLoginException { 352 String repository = mavenRepository.toString(); 353 if(!repository.endsWith("/")) { 354 repository = repository+"/"; 355 } 356 //todo: Try downloading a .gz first 357 URL url = new URL(repository+"geronimo-plugins.xml"); 358 try { 359 //todo: use a progress monitor 360 InputStream in = openStream(null, new URL[]{url}, username, password, null).getStream(); 361 return loadPluginList(mavenRepository, in); 362 } catch (MissingDependencyException e) { 363 log.error("Cannot find plugin index at site "+url); 364 return null; 365 } catch (Exception e) { 366 log.error("Unable to load repository configuration data", e); 367 return null; 368 } 369 } 370 371 /** 372 * Installs a configuration from a remote repository into the local Geronimo server, 373 * including all its dependencies. The caller will get the results when the 374 * operation completes. Note that this method does not throw exceptions on failure, 375 * but instead sets the failure property of the DownloadResults. 376 * 377 * @param username Optional username, if the maven repo uses HTTP Basic authentication. 378 * Set this to null if no authentication is required. 379 * @param password Optional password, if the maven repo uses HTTP Basic authentication. 380 * Set this to null if no authentication is required. 381 * @param pluginsToInstall The list of configurations to install 382 */ 383 public DownloadResults install(PluginList pluginsToInstall, String username, String password) { 384 DownloadResults results = new DownloadResults(); 385 install(pluginsToInstall, username, password, results); 386 return results; 387 } 388 389 /** 390 * Installs a configuration from a remote repository into the local Geronimo server, 391 * including all its dependencies. The method blocks until the operation completes, 392 * but the caller will be notified of progress frequently along the way (using the 393 * supplied DownloadPoller). Therefore the caller is meant to create the poller and 394 * then call this method in a background thread. Note that this method does not 395 * throw exceptions on failure, but instead sets the failure property of the 396 * DownloadPoller. 397 * 398 * @param pluginsToInstall The list of configurations to install 399 * @param username Optional username, if the maven repo uses HTTP Basic authentication. 400 * Set this to null if no authentication is required. 401 * @param password Optional password, if the maven repo uses HTTP Basic authentication. 402 * Set this to null if no authentication is required. 403 * @param poller Will be notified with status updates as the download proceeds 404 */ 405 public void install(PluginList pluginsToInstall, String username, String password, DownloadPoller poller) { 406 try { 407 Map metaMap = new HashMap(); 408 // Step 1: validate everything 409 for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) { 410 PluginMetadata metadata = pluginsToInstall.getPlugins()[i]; 411 validatePlugin(metadata); 412 if(metadata.getModuleId() != null) { 413 metaMap.put(metadata.getModuleId(), metadata); 414 } 415 } 416 417 // Step 2: everything is valid, do the installation 418 for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) { 419 // 1. Identify the configuration 420 PluginMetadata metadata = pluginsToInstall.getPlugins()[i]; 421 // 2. Unload obsoleted configurations 422 List obsoletes = new ArrayList(); 423 for (int j = 0; j < metadata.getObsoletes().length; j++) { 424 String name = metadata.getObsoletes()[j]; 425 Artifact obsolete = Artifact.create(name); 426 Artifact[] list = configManager.getArtifactResolver().queryArtifacts(obsolete); 427 for (int k = 0; k < list.length; k++) { 428 Artifact artifact = list[k]; 429 if(configManager.isLoaded(artifact)) { 430 if(configManager.isRunning(artifact)) { 431 configManager.stopConfiguration(artifact); 432 } 433 configManager.unloadConfiguration(artifact); 434 obsoletes.add(artifact); 435 } 436 } 437 } 438 // 3. Download the artifact if necessary, and its dependencies 439 Set working = new HashSet(); 440 if(metadata.getModuleId() != null) { 441 URL[] repos = pluginsToInstall.getRepositories(); 442 if(metadata.getRepositories().length > 0) { 443 repos = metadata.getRepositories(); 444 } 445 downloadArtifact(metadata.getModuleId(), metaMap, repos, 446 username, password, new ResultsFileWriteMonitor(poller), working, false); 447 } else { 448 String[] deps = metadata.getDependencies(); 449 for (int j = 0; j < deps.length; j++) { 450 String dep = deps[j]; 451 Artifact entry = Artifact.create(dep); 452 URL[] repos = pluginsToInstall.getRepositories(); 453 if(metadata.getRepositories().length > 0) { 454 repos = metadata.getRepositories(); 455 } 456 downloadArtifact(entry, metaMap, repos, 457 username, password, new ResultsFileWriteMonitor(poller), working, false); 458 } 459 } 460 // 4. Uninstall obsolete configurations 461 for (int j = 0; j < obsoletes.size(); j++) { 462 Artifact artifact = (Artifact) obsoletes.get(j); 463 configManager.uninstallConfiguration(artifact); 464 } 465 // 5. Installation of this configuration finished successfully 466 } 467 468 // Step 3: Start anything that's marked accordingly 469 for (int i = 0; i < pluginsToInstall.getPlugins().length; i++) { 470 PluginMetadata metadata = pluginsToInstall.getPlugins()[i]; 471 for (int j = 0; j < metadata.getForceStart().length; j++) { 472 String id = metadata.getForceStart()[j]; 473 Artifact artifact = Artifact.create(id); 474 if(configManager.isConfiguration(artifact)) { 475 poller.setCurrentFilePercent(-1); 476 poller.setCurrentMessage("Starting "+artifact); 477 configManager.loadConfiguration(artifact); 478 configManager.startConfiguration(artifact); 479 } 480 } 481 } 482 } catch (Exception e) { 483 poller.setFailure(e); 484 } finally { 485 poller.setFinished(); 486 } 487 } 488 489 /** 490 * Installs a configuration from a remote repository into the local Geronimo server, 491 * including all its dependencies. The method returns immediately, providing a key 492 * that can be used to poll the status of the download operation. Note that the 493 * installation does not throw exceptions on failure, but instead sets the failure 494 * property of the DownloadResults that the caller can poll for. 495 * 496 * @param pluginsToInstall The list of configurations to install 497 * @param username Optional username, if the maven repo uses HTTP Basic authentication. 498 * Set this to null if no authentication is required. 499 * @param password Optional password, if the maven repo uses HTTP Basic authentication. 500 * Set this to null if no authentication is required. 501 * 502 * @return A key that can be passed to checkOnInstall 503 */ 504 public Object startInstall(final PluginList pluginsToInstall, final String username, final String password) { 505 Object key = getNextKey(); 506 final DownloadResults results = new DownloadResults(); 507 Runnable work = new Runnable() { 508 public void run() { 509 install(pluginsToInstall, username, password, results); 510 } 511 }; 512 asyncKeys.put(key, results); 513 try { 514 threadPool.execute("Configuration Installer", work); 515 } catch (InterruptedException e) { 516 throw new RuntimeException("Unable to start work", e); 517 } 518 return key; 519 } 520 521 /** 522 * Installs a configuration downloaded from a remote repository into the local Geronimo 523 * server, including all its dependencies. The method returns immediately, providing a 524 * key that can be used to poll the status of the download operation. Note that the 525 * installation does not throw exceptions on failure, but instead sets the failure 526 * property of the DownloadResults that the caller can poll for. 527 * 528 * @param carFile A CAR file downloaded from a remote repository. This is a packaged 529 * configuration with included configuration information, but it may 530 * still have external dependencies that need to be downloaded 531 * separately. The metadata in the CAR file includes a repository URL 532 * for these downloads, and the username and password arguments are 533 * used in conjunction with that. 534 * @param username Optional username, if the maven repo uses HTTP Basic authentication. 535 * Set this to null if no authentication is required. 536 * @param password Optional password, if the maven repo uses HTTP Basic authentication. 537 * Set this to null if no authentication is required. 538 * 539 * @return A key that can be passed to checkOnInstall 540 */ 541 public Object startInstall(final File carFile, final String username, final String password) { 542 Object key = getNextKey(); 543 final DownloadResults results = new DownloadResults(); 544 Runnable work = new Runnable() { 545 public void run() { 546 install(carFile, username, password, results); 547 } 548 }; 549 asyncKeys.put(key, results); 550 try { 551 threadPool.execute("Configuration Installer", work); 552 } catch (InterruptedException e) { 553 throw new RuntimeException("Unable to start work", e); 554 } 555 return key; 556 } 557 558 /** 559 * Gets the current progress of a download operation. Note that once the 560 * DownloadResults is returned for this operation shows isFinished = true, 561 * the operation will be forgotten, so the caller should be careful not to 562 * call this again after the download has finished. 563 * 564 * @param key Identifies the operation to check on 565 */ 566 public DownloadResults checkOnInstall(Object key) { 567 DownloadResults results = (DownloadResults) asyncKeys.get(key); 568 results = results.duplicate(); 569 if(results.isFinished()) { 570 asyncKeys.remove(key); 571 } 572 return results; 573 } 574 575 /** 576 * Installs from a pre-downloaded CAR file 577 */ 578 public void install(File carFile, String username, String password, DownloadPoller poller) { 579 try { 580 // 1. Extract the configuration metadata 581 PluginMetadata data = loadCARFile(carFile, true); 582 if(data == null) { 583 throw new IllegalArgumentException("Invalid Configuration Archive "+carFile.getAbsolutePath()+" see server log for details"); 584 } 585 586 // 2. Validate that we can install this 587 validatePlugin(data); 588 589 // 3. Install the CAR into the repository (it shouldn't be re-downloaded) 590 if(data.getModuleId() != null) { 591 ResultsFileWriteMonitor monitor = new ResultsFileWriteMonitor(poller); 592 writeableRepo.copyToRepository(carFile, data.getModuleId(), monitor); 593 installConfigXMLData(data.getModuleId(), data); 594 if(data.getFilesToCopy() != null) { 595 extractPluginFiles(data.getModuleId(), data, monitor); 596 } 597 } 598 599 // 4. Use the standard logic to remove obsoletes, install dependencies, etc. 600 // This will validate all over again (oh, well) 601 install(new PluginList(data.getRepositories(), new PluginMetadata[]{data}), 602 username, password, poller); 603 } catch (Exception e) { 604 poller.setFailure(e); 605 } finally { 606 poller.setFinished(); 607 } 608 } 609 610 /** 611 * Ensures that a plugin is installable. 612 */ 613 private void validatePlugin(PluginMetadata metadata) throws MissingDependencyException { 614 // 1. Check that it's not already running 615 if(metadata.getModuleId() != null) { // that is, it's a real configuration not a plugin list 616 if(configManager.isRunning(metadata.getModuleId())) { 617 boolean upgrade = false; 618 for (int i = 0; i < metadata.getObsoletes().length; i++) { 619 String obsolete = metadata.getObsoletes()[i]; 620 Artifact test = Artifact.create(obsolete); 621 if(test.matches(metadata.getModuleId())) { 622 upgrade = true; 623 break; 624 } 625 } 626 if(!upgrade) { 627 throw new IllegalArgumentException("Configuration "+metadata.getModuleId()+" is already running!"); 628 } 629 } 630 } 631 // 2. Check that we meet the prerequisites 632 PluginMetadata.Prerequisite[] prereqs = metadata.getPrerequisites(); 633 for (int i = 0; i < prereqs.length; i++) { 634 PluginMetadata.Prerequisite prereq = prereqs[i]; 635 if(resolver.queryArtifacts(prereq.getModuleId()).length == 0) { 636 throw new MissingDependencyException("Required configuration '"+prereq.getModuleId()+"' is not installed."); 637 } 638 } 639 // 3. Check that we meet the Geronimo, JVM versions 640 if(metadata.getGeronimoVersions().length > 0 && !checkGeronimoVersions(metadata.getGeronimoVersions())) { 641 throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on Geronimo "+serverInfo.getVersion()); 642 } 643 if(metadata.getJvmVersions().length > 0 && !checkJVMVersions(metadata.getJvmVersions())) { 644 throw new MissingDependencyException("Cannot install plugin "+metadata.getModuleId()+" on JVM "+System.getProperty("java.version")); 645 } 646 } 647 648 /** 649 * Download (if necessary) and install something, which may be a Configuration or may 650 * be just a JAR. For each artifact processed, all its dependencies will be 651 * processed as well. 652 * 653 * @param configID Identifies the artifact to install 654 * @param repos The URLs to contact the repositories (in order of preference) 655 * @param username The username used for repositories secured with HTTP Basic authentication 656 * @param password The password used for repositories secured with HTTP Basic authentication 657 * @param monitor The ongoing results of the download operations, with some monitoring logic 658 * 659 * @throws IOException When there's a problem reading or writing data 660 * @throws FailedLoginException When a repository requires authentication and either no username 661 * and password are supplied or the username and password supplied 662 * are not accepted 663 * @throws MissingDependencyException When a dependency cannot be located in any of the listed repositories 664 */ 665 private void downloadArtifact(Artifact configID, Map metadata, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor, Set soFar, boolean dependency) throws IOException, FailedLoginException, MissingDependencyException { 666 if(soFar.contains(configID)) { 667 return; // Avoid enless work due to circular dependencies 668 } else { 669 soFar.add(configID); 670 } 671 // Download and install the main artifact 672 boolean pluginWasInstalled = false; 673 Artifact[] matches = configManager.getArtifactResolver().queryArtifacts(configID); 674 if(matches.length == 0) { // not present, needs to be downloaded 675 monitor.getResults().setCurrentMessage("Downloading " + configID); 676 monitor.getResults().setCurrentFilePercent(-1); 677 OpenResult result = openStream(configID, repos, username, password, monitor); 678 try { 679 File tempFile = downloadFile(result, monitor); 680 PluginMetadata pluginData = ((PluginMetadata) metadata.get(configID)); 681 // Only bother with the hash if we got it from a source other than the download file itself 682 PluginMetadata.Hash hash = pluginData == null ? null : pluginData.getHash(); 683 if(hash != null) { 684 String actual = ConfigurationStoreUtil.getActualChecksum(tempFile, hash.getType()); 685 if(!actual.equals(hash.getValue())) { 686 throw new IOException("File download incorrect (expected "+hash.getType()+" hash "+hash.getValue()+" but got "+actual+")"); 687 } 688 } 689 // See if the download file has plugin metadata 690 if(pluginData == null) { 691 try { 692 pluginData = loadCARFile(tempFile, false); 693 } catch (Exception e) { 694 throw new IOException("Unable to read plugin metadata: "+e.getMessage()); 695 } 696 } 697 if(pluginData != null) { // it's a plugin, not a plain JAR 698 validatePlugin(pluginData); 699 } 700 monitor.getResults().setCurrentMessage("Copying " + result.getConfigID() + " to the repository"); 701 writeableRepo.copyToRepository(tempFile, result.getConfigID(), monitor); //todo: download SNAPSHOTS if previously available? 702 if(!tempFile.delete()) { 703 log.warn("Unable to delete temporary download file "+tempFile.getAbsolutePath()); 704 tempFile.deleteOnExit(); 705 } 706 installConfigXMLData(result.getConfigID(), pluginData); 707 if(dependency) { 708 monitor.getResults().addDependencyInstalled(configID); 709 configID = result.getConfigID(); 710 } else { 711 configID = result.getConfigID(); 712 monitor.getResults().addInstalledConfigID(configID); 713 } 714 pluginWasInstalled = true; 715 } finally { 716 result.getStream().close(); 717 } 718 } else { 719 if(dependency) { 720 monitor.getResults().addDependencyPresent(configID); 721 } else { 722 monitor.getResults().addInstalledConfigID(configID); 723 } 724 } 725 // Download and install the dependencies 726 try { 727 ConfigurationData data = null; 728 if(!configID.isResolved()) { 729 // See if something's running 730 for (int i = matches.length-1; i >= 0; i--) { 731 Artifact match = matches[i]; 732 if(configStore.containsConfiguration(match) && configManager.isRunning(match)) { 733 return; // its dependencies must be OK 734 } 735 } 736 // Go with something that's installed 737 configID = matches[matches.length-1]; 738 } 739 if(configStore.containsConfiguration(configID)) { 740 if(configManager.isRunning(configID)) { 741 return; // its dependencies must be OK 742 } 743 data = configStore.loadConfiguration(configID); 744 } 745 Dependency[] dependencies = data == null ? getDependencies(writeableRepo, configID) : getDependencies(data); 746 // Download the dependencies 747 for (int i = 0; i < dependencies.length; i++) { 748 Dependency dep = dependencies[i]; 749 Artifact artifact = dep.getArtifact(); 750 downloadArtifact(artifact, metadata, repos, username, password, monitor, soFar, true); 751 } 752 } catch (NoSuchConfigException e) { 753 throw new IllegalStateException("Installed configuration into repository but ConfigStore does not see it: "+e.getMessage()); 754 } catch (InvalidConfigException e) { 755 throw new IllegalStateException("Installed configuration into repository but ConfigStore cannot load it: "+e.getMessage()); 756 } 757 // Copy any files out of the artifact 758 PluginMetadata currentPlugin = configManager.isConfiguration(configID) ? getPluginMetadata(configID) : null; 759 if(pluginWasInstalled && currentPlugin != null && currentPlugin.getFilesToCopy() != null) { 760 extractPluginFiles(configID, currentPlugin, monitor); 761 } 762 } 763 764 private void extractPluginFiles(Artifact configID, PluginMetadata currentPlugin, ResultsFileWriteMonitor monitor) throws IOException { 765 for (int i = 0; i < currentPlugin.getFilesToCopy().length; i++) { 766 PluginMetadata.CopyFile data = currentPlugin.getFilesToCopy()[i]; 767 monitor.getResults().setCurrentFilePercent(-1); 768 monitor.getResults().setCurrentFile(data.getSourceFile()); 769 monitor.getResults().setCurrentMessage("Copying "+data.getSourceFile()+" from plugin to Geronimo installation"); 770 Set set; 771 try { 772 set = configStore.resolve(configID, null, data.getSourceFile()); 773 } catch (NoSuchConfigException e) { 774 throw new IllegalStateException("Unable to identify module "+configID+" to copy files from"); 775 } 776 if(set.size() == 0) { 777 log.error("Installed configuration into repository but cannot locate file to copy "+data.getSourceFile()); 778 continue; 779 } 780 File targetDir = data.isRelativeToVar() ? serverInfo.resolveServer("var/"+data.getDestDir()) : serverInfo.resolve(data.getDestDir()); 781 if(!targetDir.isDirectory()) { 782 log.error("Plugin install cannot write file "+data.getSourceFile()+" to "+data.getDestDir()+" because "+targetDir.getAbsolutePath()+" is not a directory"); 783 continue; 784 } 785 if(!targetDir.canWrite()) { 786 log.error("Plugin install cannot write file "+data.getSourceFile()+" to "+data.getDestDir()+" because "+targetDir.getAbsolutePath()+" is not writable"); 787 continue; 788 } 789 for (Iterator it = set.iterator(); it.hasNext();) { 790 URL url = (URL) it.next(); 791 String path = url.getPath(); 792 if(path.lastIndexOf('/') > -1) { 793 path = path.substring(path.lastIndexOf('/')); 794 } 795 File target = new File(targetDir, path); 796 if(!target.exists()) { 797 if(!target.createNewFile()) { 798 log.error("Plugin install cannot create new file "+target.getAbsolutePath()); 799 continue; 800 } 801 } 802 if(!target.canWrite()) { 803 log.error("Plugin install cannot write to file "+target.getAbsolutePath()); 804 continue; 805 } 806 copyFile(url.openStream(), new FileOutputStream(target)); 807 } 808 } 809 } 810 811 private void copyFile(InputStream in, FileOutputStream out) throws IOException { 812 byte[] buf = new byte[4096]; 813 int count; 814 while((count = in.read(buf)) > -1) { 815 out.write(buf, 0, count); 816 } 817 in.close(); 818 out.flush(); 819 out.close(); 820 } 821 822 /** 823 * Downloads to a temporary file so we can validate the download before 824 * installing into the repository. 825 */ 826 private File downloadFile(OpenResult result, ResultsFileWriteMonitor monitor) throws IOException { 827 InputStream in = result.getStream(); 828 if(in == null) { 829 throw new IllegalStateException(); 830 } 831 FileOutputStream out = null; 832 try { 833 monitor.writeStarted(result.getConfigID().toString(), result.fileSize); 834 File file = File.createTempFile("geronimo-plugin-download-", ".tmp"); 835 out = new FileOutputStream(file); 836 byte[] buf = new byte[4096]; 837 int count, total = 0; 838 while((count = in.read(buf)) > -1) { 839 out.write(buf, 0, count); 840 monitor.writeProgress(total += count); 841 } 842 monitor.writeComplete(total); 843 in.close(); 844 in = null; 845 out.close(); 846 out = null; 847 return file; 848 } finally { 849 if (in != null) { 850 try { 851 in.close(); 852 } catch (IOException ignored) { } 853 } 854 if (out != null) { 855 try { 856 out.close(); 857 } catch (IOException ignored) { } 858 } 859 } 860 } 861 862 /** 863 * Used to get dependencies for a JAR 864 */ 865 private static Dependency[] getDependencies(Repository repo, Artifact artifact) { 866 Set set = repo.getDependencies(artifact); 867 Dependency[] results = new Dependency[set.size()]; 868 int index=0; 869 for (Iterator it = set.iterator(); it.hasNext(); ++index) { 870 Artifact dep = (Artifact) it.next(); 871 results[index] = new Dependency(dep, ImportType.CLASSES); 872 } 873 return results; 874 } 875 876 /** 877 * Used to get dependencies for a Configuration 878 */ 879 private static Dependency[] getDependencies(ConfigurationData data) { 880 List dependencies = new ArrayList(data.getEnvironment().getDependencies()); 881 Collection children = data.getChildConfigurations().values(); 882 for (Iterator it = children.iterator(); it.hasNext();) { 883 ConfigurationData child = (ConfigurationData) it.next(); 884 dependencies.addAll(child.getEnvironment().getDependencies()); 885 } 886 return (Dependency[]) dependencies.toArray(new Dependency[dependencies.size()]); 887 } 888 889 /** 890 * Constructs a URL to a particular artifact in a particular repository 891 */ 892 private static URL getURL(Artifact configId, URL repository) throws MalformedURLException { 893 URL context; 894 if(repository.toString().endsWith("/")) { 895 context = repository; 896 } else { 897 context = new URL(repository.toString()+"/"); 898 } 899 900 String qualifiedVersion = configId.getVersion().toString(); 901 if (configId.getVersion() instanceof SnapshotVersion) { 902 SnapshotVersion ssVersion = (SnapshotVersion)configId.getVersion(); 903 String timestamp = ssVersion.getTimestamp(); 904 int buildNumber = ssVersion.getBuildNumber(); 905 if (timestamp!=null && buildNumber!=0) { 906 qualifiedVersion = qualifiedVersion.replaceAll("SNAPSHOT", timestamp + "-" + buildNumber); 907 } 908 } 909 return new URL(context, configId.getGroupId().replace('.','/') + "/" 910 + configId.getArtifactId() + "/" + configId.getVersion() 911 + "/" +configId.getArtifactId() + "-" 912 + qualifiedVersion + "." +configId.getType()); 913 } 914 915 /** 916 * Attemps to open a stream to an artifact in one of the listed repositories. 917 * The username and password provided are only used if one of the repositories 918 * returns an HTTP authentication failure on the first try. 919 * 920 * @param artifact The artifact we're looking for, or null to just connect to the base repo URL 921 * @param repos The base URLs to the repositories to search for the artifact 922 * @param username A username if one of the repositories might require authentication 923 * @param password A password if one of the repositories might require authentication 924 * @param monitor Callback for progress on the connection operation 925 * 926 * @throws IOException Occurs when the IO with the repository failed 927 * @throws FailedLoginException Occurs when a repository requires authentication and either 928 * no username and password were provided or they weren't 929 * accepted 930 * @throws MissingDependencyException Occurs when none of the repositories has the artifact 931 * in question 932 */ 933 private static OpenResult openStream(Artifact artifact, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, MissingDependencyException { 934 if(artifact != null) { 935 if (!artifact.isResolved() || artifact.getVersion().toString().indexOf("SNAPSHOT") >= 0) { 936 artifact = findArtifact(artifact, repos, username, password, monitor); 937 } 938 } 939 if(monitor != null) { 940 monitor.getResults().setCurrentFilePercent(-1); 941 monitor.getResults().setCurrentMessage("Downloading "+artifact+"..."); 942 monitor.setTotalBytes(-1); // In case the server doesn't say 943 } 944 InputStream in; 945 LinkedList list = new LinkedList(); 946 list.addAll(Arrays.asList(repos)); 947 while (true) { 948 if(list.isEmpty()) { 949 throw new MissingDependencyException("Unable to download dependency "+artifact); 950 } 951 if(monitor != null) { 952 monitor.setTotalBytes(-1); // Just to be sure 953 } 954 URL repository = (URL) list.removeFirst(); 955 URL url = artifact == null ? repository : getURL(artifact, repository); 956 log.debug("Attempting to download "+artifact+" from "+url); 957 in = connect(url, username, password, monitor); 958 if(in != null) { 959 return new OpenResult(artifact, in, monitor == null ? -1 : monitor.getTotalBytes()); 960 } 961 } 962 } 963 964 /** 965 * Does the meat of connecting to a URL 966 */ 967 private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException { 968 return connect(url, username, password, monitor, null); 969 } 970 971 /** 972 * Does the meat of connecting to a URL. Can be used to just test the existance of 973 * something at the specified URL by passing the method 'HEAD'. 974 */ 975 private static InputStream connect(URL url, String username, String password, ResultsFileWriteMonitor monitor, String method) throws IOException, FailedLoginException { 976 URLConnection con = url.openConnection(); 977 if(con instanceof HttpURLConnection) { 978 HttpURLConnection http = (HttpURLConnection) url.openConnection(); 979 if(method != null) { 980 http.setRequestMethod(method); 981 } 982 http.connect(); 983 if(http.getResponseCode() == 401) { // need to authenticate 984 if(username == null || username.equals("")) { 985 throw new FailedLoginException("Server returned 401 "+http.getResponseMessage()); 986 } 987 http = (HttpURLConnection) url.openConnection(); 988 http.setRequestProperty("Authorization", "Basic " + new String(Base64.encode((username + ":" + password).getBytes()))); 989 if(method != null) { 990 http.setRequestMethod(method); 991 } 992 http.connect(); 993 if(http.getResponseCode() == 401) { 994 throw new FailedLoginException("Server returned 401 "+http.getResponseMessage()); 995 } else if(http.getResponseCode() == 404) { 996 return null; // Not found at this repository 997 } 998 if(monitor != null && http.getContentLength() > 0) { 999 monitor.setTotalBytes(http.getContentLength()); 1000 } 1001 return http.getInputStream(); 1002 } else if(http.getResponseCode() == 404) { 1003 return null; // Not found at this repository 1004 } else { 1005 if(monitor != null && http.getContentLength() > 0) { 1006 monitor.setTotalBytes(http.getContentLength()); 1007 } 1008 return http.getInputStream(); 1009 } 1010 } else { 1011 if(username != null && !username.equals("")) { 1012 con.setRequestProperty("Authorization", "Basic " + new String(Base64.encode((username + ":" + password).getBytes()))); 1013 try { 1014 con.connect(); 1015 if(monitor != null && con.getContentLength() > 0) { 1016 monitor.setTotalBytes(con.getContentLength()); 1017 } 1018 return con.getInputStream(); 1019 } catch (FileNotFoundException e) { 1020 return null; 1021 } 1022 } else { 1023 try { 1024 con.connect(); 1025 if(monitor != null && con.getContentLength() > 0) { 1026 monitor.setTotalBytes(con.getContentLength()); 1027 } 1028 return con.getInputStream(); 1029 } catch (FileNotFoundException e) { 1030 return null; 1031 } 1032 } 1033 } 1034 } 1035 1036 /** 1037 * Searches for an artifact in the listed repositories, where the artifact 1038 * may have wildcards in the ID. 1039 */ 1040 private static Artifact findArtifact(Artifact query, URL[] repos, String username, String password, ResultsFileWriteMonitor monitor) throws MissingDependencyException { 1041 if(query.getGroupId() == null || query.getArtifactId() == null || query.getType() == null) { 1042 throw new MissingDependencyException("No support yet for dependencies missing more than a version: "+query); 1043 } 1044 List list = new ArrayList(); 1045 for (int i = 0; i < repos.length; i++) { 1046 list.add(repos[i]); 1047 } 1048 Artifact result = null; 1049 for (int i = 0; i < list.size(); i++) { 1050 URL url = (URL) list.get(i); 1051 try { 1052 result = findArtifact(query, url, username, password, monitor); 1053 } catch (Exception e) { 1054 log.warn("Unable to read from "+url, e); 1055 } 1056 if(result != null) { 1057 return result; 1058 } 1059 } 1060 throw new MissingDependencyException("No repository has a valid artifact for "+query); 1061 } 1062 1063 /** 1064 * Checks for an artifact in a specific repository, where the artifact may 1065 * have wildcards in the ID. 1066 */ 1067 private static Artifact findArtifact(Artifact query, URL url, String username, String password, ResultsFileWriteMonitor monitor) throws IOException, FailedLoginException, ParserConfigurationException, SAXException { 1068 monitor.getResults().setCurrentMessage("Searching for "+query+" at "+url); 1069 String base = query.getGroupId().replace('.', '/') + "/" + query.getArtifactId(); 1070 String path = base +"/maven-metadata.xml"; 1071 URL metaURL = new URL(url.toString().endsWith("/") ? url : new URL(url.toString()+"/"), path); 1072 InputStream in = connect(metaURL, username, password, monitor); 1073 if(in == null) { 1074 return null; 1075 } 1076 // Don't use the validating parser that we normally do 1077 DocumentBuilder builder = XmlUtil.newDocumentBuilderFactory().newDocumentBuilder(); 1078 Document doc = builder.parse(in); 1079 Element root = doc.getDocumentElement(); 1080 NodeList list = root.getElementsByTagName("versions"); 1081 if(list.getLength() == 0) { 1082 return null; 1083 } 1084 list = ((Element)list.item(0)).getElementsByTagName("version"); 1085 Version[] available = new Version[list.getLength()]; 1086 for (int i = 0; i < available.length; i++) { 1087 available[i] = new Version(getText(list.item(i))); 1088 } 1089 Arrays.sort(available); 1090 for(int i=available.length-1; i>=0; i--) { 1091 Version version = available[i]; 1092 URL metadataURL = new URL(url.toString()+base+"/"+version+"/maven-metadata.xml"); 1093 InputStream metadataStream = connect(metadataURL, username, password, monitor); 1094 1095 // check for a snapshot qualifier 1096 if (metadataStream != null) { 1097 DocumentBuilder metadatabuilder = XmlUtil.newDocumentBuilderFactory().newDocumentBuilder(); 1098 Document metadatadoc = metadatabuilder.parse(metadataStream); 1099 NodeList snapshots = metadatadoc.getDocumentElement().getElementsByTagName("snapshot"); 1100 if (snapshots.getLength() >= 1) { 1101 Element snapshot = (Element)snapshots.item(0); 1102 String[] timestamp = getChildrenText(snapshot, "timestamp"); 1103 String[] buildNumber = getChildrenText(snapshot, "buildNumber"); 1104 if (timestamp.length>=1 && buildNumber.length>=1) { 1105 try { 1106 SnapshotVersion snapshotVersion = new SnapshotVersion(version); 1107 snapshotVersion.setBuildNumber(Integer.parseInt(buildNumber[0])); 1108 snapshotVersion.setTimestamp(timestamp[0]); 1109 version = snapshotVersion; 1110 } catch (NumberFormatException nfe) { 1111 log.warn("Could not create snapshot version for " + query); 1112 } 1113 } 1114 } 1115 metadataStream.close(); 1116 } 1117 1118 // look for the artifact in the maven repo 1119 Artifact verifiedArtifact = new Artifact(query.getGroupId(), query.getArtifactId(), version, query.getType()); 1120 URL test = getURL(verifiedArtifact, url); 1121 InputStream testStream = connect(test, username, password, monitor, "HEAD"); 1122 if(testStream == null) { 1123 log.warn("Maven repository "+url+" listed artifact "+query+" version "+version+" but I couldn't find it at "+test); 1124 continue; 1125 } 1126 testStream.close(); 1127 return verifiedArtifact; 1128 } 1129 return null; 1130 } 1131 1132 /** 1133 * Puts the name and ID of a plugin into the argument map of plugins, 1134 * by reading the values out of the provided plugin descriptor file. 1135 * 1136 * @param xml The geronimo-plugin.xml for this plugin 1137 * @param plugins The result map to populate 1138 */ 1139 private void readNameAndID(File xml, Map plugins) { 1140 try { 1141 SAXParserFactory factory = XmlUtil.newSAXParserFactory(); 1142 SAXParser parser = factory.newSAXParser(); 1143 PluginNameIDHandler handler = new PluginNameIDHandler(); 1144 parser.parse(xml, handler); 1145 if(handler.isComplete()) { 1146 plugins.put(handler.getName(), Artifact.create(handler.getID())); 1147 } 1148 } catch (Exception e) { 1149 log.warn("Invalid XML at "+xml.getAbsolutePath(), e); 1150 } 1151 } 1152 1153 /** 1154 * Puts the name and ID of a plugin into the argument map of plugins, 1155 * by reading the values out of the provided plugin descriptor stream. 1156 * 1157 * @param xml The geronimo-plugin.xml for this plugin 1158 * @param plugins The result map to populate 1159 */ 1160 private void readNameAndID(InputStream xml, Map plugins) { 1161 try { 1162 SAXParserFactory factory = XmlUtil.newSAXParserFactory(); 1163 SAXParser parser = factory.newSAXParser(); 1164 PluginNameIDHandler handler = new PluginNameIDHandler(); 1165 parser.parse(xml, handler); 1166 if(handler.isComplete()) { 1167 plugins.put(handler.getName(), Artifact.create(handler.getID())); 1168 } 1169 } catch (Exception e) { 1170 log.warn("Invalid XML", e); 1171 } 1172 } 1173 1174 /** 1175 * Replaces all the dependency elements in the argument configuration data 1176 * with the dependencies from the actual data for that module. 1177 */ 1178 private void overrideDependencies(ConfigurationData data, PluginMetadata metadata) { 1179 //todo: this ends up doing a little more work than necessary 1180 PluginMetadata temp = createDefaultMetadata(data); 1181 metadata.setDependencies(temp.getDependencies()); 1182 } 1183 1184 /** 1185 * Generates a default plugin metadata based on the data for this module 1186 * in the server. 1187 */ 1188 private PluginMetadata createDefaultMetadata(ConfigurationData data) { 1189 PluginMetadata meta = new PluginMetadata(data.getId().toString(), // name 1190 data.getId(), // module ID 1191 "Unknown", // category 1192 "Please provide a description", 1193 null, // URL 1194 null, // author 1195 null, // hash 1196 true, // installed 1197 false); 1198 meta.setGeronimoVersions(new String[]{serverInfo.getVersion()}); 1199 meta.setJvmVersions(new String[0]); 1200 meta.setLicenses(new PluginMetadata.License[0]); 1201 meta.setObsoletes(new String[]{new Artifact(data.getId().getGroupId(), data.getId().getArtifactId(), (Version)null, data.getId().getType()).toString()}); 1202 meta.setFilesToCopy(new PluginMetadata.CopyFile[0]); 1203 List deps = new ArrayList(); 1204 PluginMetadata.Prerequisite prereq = null; 1205 prereq = processDependencyList(data.getEnvironment().getDependencies(), prereq, deps); 1206 Map children = data.getChildConfigurations(); 1207 for (Iterator it = children.values().iterator(); it.hasNext();) { 1208 ConfigurationData child = (ConfigurationData) it.next(); 1209 prereq = processDependencyList(child.getEnvironment().getDependencies(), prereq, deps); 1210 } 1211 meta.setDependencies((String[]) deps.toArray(new String[deps.size()])); 1212 meta.setPrerequisites(prereq == null ? new PluginMetadata.Prerequisite[0] : new PluginMetadata.Prerequisite[]{prereq}); 1213 return meta; 1214 } 1215 1216 /** 1217 * Read the plugin metadata out of a plugin CAR file on disk. 1218 */ 1219 private PluginMetadata loadCARFile(File file, boolean definitelyCAR) throws IOException, ParserConfigurationException, SAXException { 1220 if(!file.canRead()) { 1221 log.error("Cannot read from downloaded CAR file "+file.getAbsolutePath()); 1222 return null; 1223 } 1224 JarFile jar = new JarFile(file); 1225 Document doc; 1226 try { 1227 JarEntry entry = jar.getJarEntry("META-INF/geronimo-plugin.xml"); 1228 if(entry == null) { 1229 if(definitelyCAR) { 1230 log.error("Downloaded CAR file does not contain META-INF/geronimo-plugin.xml file"); 1231 } 1232 jar.close(); 1233 return null; 1234 } 1235 InputStream in = jar.getInputStream(entry); 1236 DocumentBuilder builder = createDocumentBuilder(); 1237 doc = builder.parse(in); 1238 in.close(); 1239 } finally { 1240 jar.close(); 1241 } 1242 return loadPluginMetadata(doc, file.getAbsolutePath()); 1243 } 1244 1245 /** 1246 * Read a set of plugin metadata from a DOM document. 1247 */ 1248 private PluginMetadata loadPluginMetadata(Document doc, String file) throws SAXException, MalformedURLException { 1249 Element root = doc.getDocumentElement(); 1250 if(!root.getNodeName().equals("geronimo-plugin")) { 1251 log.error("Configuration archive "+file+" does not have a geronimo-plugin in META-INF/geronimo-plugin.xml"); 1252 return null; 1253 } 1254 return processPlugin(root); 1255 } 1256 1257 /** 1258 * Loads the list of all available plugins from the specified stream 1259 * (representing geronimo-plugins.xml at the specified repository). 1260 */ 1261 private PluginList loadPluginList(URL repo, InputStream in) throws ParserConfigurationException, IOException, SAXException { 1262 DocumentBuilder builder = createDocumentBuilder(); 1263 Document doc = builder.parse(in); 1264 in.close(); 1265 Element root = doc.getDocumentElement(); // geronimo-plugin-list 1266 NodeList configs = root.getElementsByTagName("plugin"); 1267 List results = new ArrayList(); 1268 for (int i = 0; i < configs.getLength(); i++) { 1269 Element config = (Element) configs.item(i); 1270 PluginMetadata data = processPlugin(config); 1271 results.add(data); 1272 } 1273 String[] repos = getChildrenText(root, "default-repository"); 1274 URL[] repoURLs = new URL[repos.length]; 1275 for(int i = 0; i < repos.length; i++) { 1276 if(repos[i].endsWith("/")) { 1277 repoURLs[i] = new URL(repos[i]); 1278 } else { 1279 repoURLs[i] = new URL(repos[i]+"/"); 1280 } 1281 } 1282 1283 PluginMetadata[] data = (PluginMetadata[]) results.toArray(new PluginMetadata[results.size()]); 1284 return new PluginList(repoURLs, data); 1285 } 1286 1287 /** 1288 * Common logic for setting up a document builder to deal with plugin files. 1289 * @return 1290 * @throws ParserConfigurationException 1291 */ 1292 private static DocumentBuilder createDocumentBuilder() throws ParserConfigurationException { 1293 DocumentBuilderFactory factory = XmlUtil.newDocumentBuilderFactory(); 1294 factory.setValidating(true); 1295 factory.setNamespaceAware(true); 1296 factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaLanguage", 1297 "http://www.w3.org/2001/XMLSchema"); 1298 factory.setAttribute("http://java.sun.com/xml/jaxp/properties/schemaSource", 1299 new InputStream[]{ 1300 PluginInstallerGBean.class.getResourceAsStream("/META-INF/schema/attributes-1.1.xsd"), 1301 PluginInstallerGBean.class.getResourceAsStream("/META-INF/schema/plugins-1.1.xsd"), 1302 } 1303 ); 1304 DocumentBuilder builder = factory.newDocumentBuilder(); 1305 builder.setErrorHandler(new ErrorHandler() { 1306 public void error(SAXParseException exception) throws SAXException { 1307 throw new SAXException("Unable to read plugin file", exception); 1308 } 1309 1310 public void fatalError(SAXParseException exception) throws SAXException { 1311 throw new SAXException("Unable to read plugin file", exception); 1312 } 1313 1314 public void warning(SAXParseException exception) { 1315 log.warn("Warning reading XML document", exception); 1316 } 1317 }); 1318 return builder; 1319 } 1320 1321 /** 1322 * Given a DOM element representing a plugin, load it into a PluginMetadata 1323 * object. 1324 */ 1325 private PluginMetadata processPlugin(Element plugin) throws SAXException, MalformedURLException { 1326 String moduleId = getChildText(plugin, "module-id"); 1327 NodeList licenseNodes = plugin.getElementsByTagName("license"); 1328 PluginMetadata.License[] licenses = new PluginMetadata.License[licenseNodes.getLength()]; 1329 for(int j=0; j<licenseNodes.getLength(); j++) { 1330 Element node = (Element) licenseNodes.item(j); 1331 String licenseName = getText(node); 1332 String openSource = node.getAttribute("osi-approved"); 1333 if(licenseName == null || licenseName.equals("") || openSource == null || openSource.equals("")) { 1334 throw new SAXException("Invalid config file: license name and osi-approved flag required"); 1335 } 1336 licenses[j] = new PluginMetadata.License(licenseName, Boolean.valueOf(openSource).booleanValue()); 1337 } 1338 PluginMetadata.Hash hash = null; 1339 NodeList hashList = plugin.getElementsByTagName("hash"); 1340 if(hashList.getLength() > 0) { 1341 Element elem = (Element) hashList.item(0); 1342 hash = new PluginMetadata.Hash(elem.getAttribute("type"), getText(elem)); 1343 } 1344 NodeList fileList = plugin.getElementsByTagName("copy-file"); 1345 PluginMetadata.CopyFile[] files = new PluginMetadata.CopyFile[fileList.getLength()]; 1346 for (int i = 0; i < files.length; i++) { 1347 Element node = (Element) fileList.item(i); 1348 String relative = node.getAttribute("relative-to"); 1349 String destDir = node.getAttribute("dest-dir"); 1350 String fileName = getText(node); 1351 files[i] = new PluginMetadata.CopyFile(relative.equals("server"), fileName, destDir); 1352 } 1353 NodeList gbeans = plugin.getElementsByTagName("gbean"); 1354 GBeanOverride[] overrides = new GBeanOverride[gbeans.getLength()]; 1355 for (int i = 0; i < overrides.length; i++) { 1356 Element node = (Element) gbeans.item(i); 1357 try { 1358 overrides[i] = new GBeanOverride(node); 1359 } catch (InvalidGBeanException e) { 1360 log.error("Unable to process config.xml entry "+node.getAttribute("name")+" ("+node+")", e); 1361 } 1362 } 1363 boolean eligible = true; 1364 NodeList preNodes = plugin.getElementsByTagName("prerequisite"); 1365 PluginMetadata.Prerequisite[] prereqs = new PluginMetadata.Prerequisite[preNodes.getLength()]; 1366 for(int j=0; j<preNodes.getLength(); j++) { 1367 Element node = (Element) preNodes.item(j); 1368 String originalConfigId = getChildText(node, "id"); 1369 if(originalConfigId == null) { 1370 throw new SAXException("Prerequisite requires <id>"); 1371 } 1372 Artifact artifact = Artifact.create(originalConfigId.replaceAll("\\*", "")); 1373 boolean present = resolver.queryArtifacts(artifact).length > 0; 1374 prereqs[j] = new PluginMetadata.Prerequisite(artifact, present, 1375 getChildText(node, "resource-type"), getChildText(node, "description")); 1376 if(!present) { 1377 log.debug(moduleId+" is not eligible due to missing "+prereqs[j].getModuleId()); 1378 eligible = false; 1379 } 1380 } 1381 String[] gerVersions = getChildrenText(plugin, "geronimo-version"); 1382 if(gerVersions.length > 0) { 1383 boolean match = checkGeronimoVersions(gerVersions); 1384 if(!match) eligible = false; 1385 } 1386 String[] jvmVersions = getChildrenText(plugin, "jvm-version"); 1387 if(jvmVersions.length > 0) { 1388 boolean match = checkJVMVersions(jvmVersions); 1389 if(!match) eligible = false; 1390 } 1391 String[] repoNames = getChildrenText(plugin, "source-repository"); 1392 URL[] repos = new URL[repoNames.length]; 1393 for (int i = 0; i < repos.length; i++) { 1394 repos[i] = new URL(repoNames[i]); 1395 } 1396 Artifact artifact = null; 1397 boolean installed = false; 1398 if (moduleId != null) { 1399 artifact = Artifact.create(moduleId); 1400 // Tests, etc. don't need to have a ConfigurationManager 1401 installed = configManager != null && configManager.isInstalled(artifact); 1402 } 1403 log.trace("Checking "+moduleId+": installed="+installed+", eligible="+eligible); 1404 PluginMetadata data = new PluginMetadata(getChildText(plugin, "name"), 1405 artifact, 1406 getChildText(plugin, "category"), 1407 getChildText(plugin, "description"), 1408 getChildText(plugin, "url"), 1409 getChildText(plugin, "author"), 1410 hash, 1411 installed, eligible); 1412 data.setGeronimoVersions(gerVersions); 1413 data.setJvmVersions(jvmVersions); 1414 data.setLicenses(licenses); 1415 data.setPrerequisites(prereqs); 1416 data.setRepositories(repos); 1417 data.setFilesToCopy(files); 1418 data.setConfigXmls(overrides); 1419 NodeList list = plugin.getElementsByTagName("dependency"); 1420 List start = new ArrayList(); 1421 String deps[] = new String[list.getLength()]; 1422 for(int i=0; i<list.getLength(); i++) { 1423 Element node = (Element) list.item(i); 1424 deps[i] = getText(node); 1425 if(node.hasAttribute("start") && node.getAttribute("start").equalsIgnoreCase("true")) { 1426 start.add(deps[i]); 1427 } 1428 } 1429 data.setDependencies(deps); 1430 data.setForceStart((String[]) start.toArray(new String[start.size()])); 1431 data.setObsoletes(getChildrenText(plugin, "obsoletes")); 1432 return data; 1433 } 1434 1435 /** 1436 * Check whether the specified JVM versions match the current runtime 1437 * environment. 1438 * 1439 * @return true if the specified versions match the current 1440 * execution environment as defined by plugins-1.1.xsd 1441 */ 1442 private boolean checkJVMVersions(String[] jvmVersions) { 1443 if(jvmVersions.length == 0) return true; 1444 String version = System.getProperty("java.version"); 1445 boolean match = false; 1446 for (int j = 0; j < jvmVersions.length; j++) { 1447 String jvmVersion = jvmVersions[j]; 1448 if(jvmVersion == null || jvmVersion.equals("")) { 1449 throw new IllegalStateException("jvm-version should not be empty!"); 1450 } 1451 if(version.startsWith(jvmVersion)) { 1452 match = true; 1453 break; 1454 } 1455 } 1456 return match; 1457 } 1458 1459 /** 1460 * Check whether the specified Geronimo versions match the current runtime 1461 * environment. 1462 * 1463 * @return true if the specified versions match the current 1464 * execution environment as defined by plugins-1.1.xsd 1465 */ 1466 private boolean checkGeronimoVersions(String[] gerVersions) { 1467 if(gerVersions.length == 0) return true; 1468 String version = serverInfo.getVersion(); 1469 boolean match = false; 1470 for (int j = 0; j < gerVersions.length; j++) { 1471 String gerVersion = gerVersions[j]; 1472 if(gerVersion == null || gerVersion.equals("")) { 1473 throw new IllegalStateException("geronimo-version should not be empty!"); 1474 } 1475 if(gerVersion.equals(version)) { 1476 match = true; 1477 break; 1478 } 1479 } 1480 return match; 1481 } 1482 1483 /** 1484 * Gets the text out of a child of the specified DOM element. 1485 * 1486 * @param root The parent DOM element 1487 * @param property The name of the child element that holds the text 1488 */ 1489 private static String getChildText(Element root, String property) { 1490 NodeList children = root.getChildNodes(); 1491 for(int i=0; i<children.getLength(); i++) { 1492 Node check = children.item(i); 1493 if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) { 1494 return getText(check); 1495 } 1496 } 1497 return null; 1498 } 1499 1500 /** 1501 * Gets all the text contents of the specified DOM node. 1502 */ 1503 private static String getText(Node target) { 1504 NodeList nodes = target.getChildNodes(); 1505 StringBuffer buf = null; 1506 for(int j=0; j<nodes.getLength(); j++) { 1507 Node node = nodes.item(j); 1508 if(node.getNodeType() == Node.TEXT_NODE) { 1509 if(buf == null) { 1510 buf = new StringBuffer(); 1511 } 1512 buf.append(node.getNodeValue()); 1513 } 1514 } 1515 return buf == null ? null : buf.toString(); 1516 } 1517 1518 /** 1519 * Gets the text out of all the child nodes of a certain type. The result 1520 * array has one element for each child of the specified DOM element that 1521 * has the specified name. 1522 * 1523 * @param root The parent DOM element 1524 * @param property The name of the child elements that hold the text 1525 */ 1526 private static String[] getChildrenText(Element root, String property) { 1527 NodeList children = root.getChildNodes(); 1528 List results = new ArrayList(); 1529 for(int i=0; i<children.getLength(); i++) { 1530 Node check = children.item(i); 1531 if(check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) { 1532 NodeList nodes = check.getChildNodes(); 1533 StringBuffer buf = null; 1534 for(int j=0; j<nodes.getLength(); j++) { 1535 Node node = nodes.item(j); 1536 if(node.getNodeType() == Node.TEXT_NODE) { 1537 if(buf == null) { 1538 buf = new StringBuffer(); 1539 } 1540 buf.append(node.getNodeValue()); 1541 } 1542 } 1543 results.add(buf == null ? null : buf.toString()); 1544 } 1545 } 1546 return (String[]) results.toArray(new String[results.size()]); 1547 } 1548 1549 /** 1550 * Generates dependencies and an optional prerequisite based on a list of 1551 * dependencies for a Gernonimo module. 1552 * 1553 * @param real A list with elements of type Dependency 1554 * @param prereq The incoming prerequisite (if any), which may be replaced 1555 * @param deps A list with elements of type String (holding a module ID / Artifact name) 1556 * 1557 * @return The resulting prerequisite, if any. 1558 */ 1559 private PluginMetadata.Prerequisite processDependencyList(List real, PluginMetadata.Prerequisite prereq, List deps) { 1560 for (int i = 0; i < real.size(); i++) { 1561 Dependency dep = (Dependency) real.get(i); 1562 if(dep.getArtifact().getGroupId().equals("geronimo")) { 1563 if(dep.getArtifact().getArtifactId().indexOf("jetty") > -1) { 1564 if(prereq == null) { 1565 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."); 1566 } 1567 continue; 1568 } else if(dep.getArtifact().getArtifactId().indexOf("tomcat") > -1) { 1569 if(prereq == null) { 1570 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."); 1571 } 1572 continue; 1573 } 1574 } 1575 if(!deps.contains(dep.getArtifact().toString())) { 1576 deps.add(dep.getArtifact().toString()); 1577 } 1578 } 1579 return prereq; 1580 } 1581 1582 /** 1583 * Writes plugin metadata to a DOM tree. 1584 */ 1585 private static Document writePluginMetadata(PluginMetadata data) throws ParserConfigurationException { 1586 DocumentBuilder builder = createDocumentBuilder(); 1587 Document doc = builder.newDocument(); 1588 Element config = doc.createElementNS("http://geronimo.apache.org/xml/ns/plugins-1.1", "geronimo-plugin"); 1589 config.setAttribute("xmlns", "http://geronimo.apache.org/xml/ns/plugins-1.1"); 1590 doc.appendChild(config); 1591 1592 addTextChild(doc, config, "name", data.getName()); 1593 addTextChild(doc, config, "module-id", data.getModuleId().toString()); 1594 addTextChild(doc, config, "category", data.getCategory()); 1595 addTextChild(doc, config, "description", data.getDescription()); 1596 if(data.getPluginURL() != null) { 1597 addTextChild(doc, config, "url", data.getPluginURL()); 1598 } 1599 if(data.getAuthor() != null) { 1600 addTextChild(doc, config, "author", data.getAuthor()); 1601 } 1602 for (int i = 0; i < data.getLicenses().length; i++) { 1603 PluginMetadata.License license = data.getLicenses()[i]; 1604 Element lic = doc.createElement("license"); 1605 lic.appendChild(doc.createTextNode(license.getName())); 1606 lic.setAttribute("osi-approved", Boolean.toString(license.isOsiApproved())); 1607 config.appendChild(lic); 1608 } 1609 if(data.getHash() != null) { 1610 Element hash = doc.createElement("hash"); 1611 hash.setAttribute("type", data.getHash().getType()); 1612 hash.appendChild(doc.createTextNode(data.getHash().getValue())); 1613 config.appendChild(hash); 1614 } 1615 for (int i = 0; i < data.getGeronimoVersions().length; i++) { 1616 addTextChild(doc, config, "geronimo-version", data.getGeronimoVersions()[i]); 1617 } 1618 for (int i = 0; i < data.getJvmVersions().length; i++) { 1619 addTextChild(doc, config, "jvm-version", data.getJvmVersions()[i]); 1620 } 1621 for (int i = 0; i < data.getPrerequisites().length; i++) { 1622 PluginMetadata.Prerequisite prereq = data.getPrerequisites()[i]; 1623 Element pre = doc.createElement("prerequisite"); 1624 addTextChild(doc, pre, "id", prereq.getModuleId().toString()); 1625 if(prereq.getResourceType() != null) { 1626 addTextChild(doc, pre, "resource-type", prereq.getResourceType()); 1627 } 1628 if(prereq.getDescription() != null) { 1629 addTextChild(doc, pre, "description", prereq.getDescription()); 1630 } 1631 config.appendChild(pre); 1632 } 1633 for (int i = 0; i < data.getDependencies().length; i++) { 1634 addTextChild(doc, config, "dependency", data.getDependencies()[i]); 1635 } 1636 for (int i = 0; i < data.getObsoletes().length; i++) { 1637 addTextChild(doc, config, "obsoletes", data.getObsoletes()[i]); 1638 } 1639 for (int i = 0; i < data.getRepositories().length; i++) { 1640 URL url = data.getRepositories()[i]; 1641 addTextChild(doc, config, "source-repository", url.toString()); 1642 } 1643 for (int i = 0; i < data.getFilesToCopy().length; i++) { 1644 PluginMetadata.CopyFile file = data.getFilesToCopy()[i]; 1645 Element copy = doc.createElement("copy-file"); 1646 copy.setAttribute("relative-to", file.isRelativeToVar() ? "server" : "geronimo"); 1647 copy.setAttribute("dest-dir", file.getDestDir()); 1648 copy.appendChild(doc.createTextNode(file.getSourceFile())); 1649 config.appendChild(copy); 1650 } 1651 if(data.getConfigXmls().length > 0) { 1652 Element content = doc.createElement("config-xml-content"); 1653 for (int i = 0; i < data.getConfigXmls().length; i++) { 1654 GBeanOverride override = data.getConfigXmls()[i]; 1655 Element gbean = override.writeXml(doc, content); 1656 gbean.setAttribute("xmlns", "http://geronimo.apache.org/xml/ns/attributes-1.1"); 1657 } 1658 config.appendChild(content); 1659 } 1660 return doc; 1661 } 1662 1663 /** 1664 * Adds a child of the specified Element that just has the specified text content 1665 * @param doc The document 1666 * @param parent The parent element 1667 * @param name The name of the child element to add 1668 * @param text The contents of the child element to add 1669 */ 1670 private static void addTextChild(Document doc, Element parent, String name, String text) { 1671 Element child = doc.createElement(name); 1672 child.appendChild(doc.createTextNode(text)); 1673 parent.appendChild(child); 1674 } 1675 1676 /** 1677 * If a plugin includes config.xml content, copy it into the attribute 1678 * store. 1679 */ 1680 private void installConfigXMLData(Artifact configID, PluginMetadata pluginData) { 1681 if(configManager.isConfiguration(configID) && attributeStore != null 1682 && pluginData != null && pluginData.getConfigXmls().length > 0) { 1683 attributeStore.setModuleGBeans(configID, pluginData.getConfigXmls()); 1684 } 1685 } 1686 1687 /** 1688 * Gets a token unique to this run of the server, used to track asynchronous 1689 * downloads. 1690 */ 1691 private static Object getNextKey() { 1692 int value; 1693 synchronized(PluginInstallerGBean.class) { 1694 value = ++counter; 1695 } 1696 return new Integer(value); 1697 } 1698 1699 /** 1700 * Helper clas to extract a name and module ID from a plugin metadata file. 1701 */ 1702 private static class PluginNameIDHandler extends DefaultHandler { 1703 private String id = ""; 1704 private String name = ""; 1705 private String element = null; 1706 1707 public void characters(char ch[], int start, int length) throws SAXException { 1708 if(element != null) { 1709 if(element.equals("module-id")) { 1710 id += new String(ch, start, length); 1711 } else if(element.equals("name")) { 1712 name += new String(ch, start, length); 1713 } 1714 } 1715 } 1716 1717 public void endElement(String uri, String localName, String qName) throws SAXException { 1718 element = null; 1719 } 1720 1721 public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 1722 if(qName.equals("module-id") || qName.equals("name")) { 1723 element = qName; 1724 } 1725 } 1726 1727 public void endDocument() throws SAXException { 1728 id = id.trim(); 1729 name = name.trim(); 1730 } 1731 1732 public String getID() { 1733 return id; 1734 } 1735 1736 public String getName() { 1737 return name; 1738 } 1739 1740 public boolean isComplete() { 1741 return !id.equals("") && !name.equals(""); 1742 } 1743 } 1744 1745 /** 1746 * Helper class to bridge a FileWriteMonitor to a DownloadPoller. 1747 */ 1748 private static class ResultsFileWriteMonitor implements FileWriteMonitor { 1749 private final DownloadPoller results; 1750 private int totalBytes; 1751 private String file; 1752 1753 public ResultsFileWriteMonitor(DownloadPoller results) { 1754 this.results = results; 1755 } 1756 1757 public void setTotalBytes(int totalBytes) { 1758 this.totalBytes = totalBytes; 1759 } 1760 1761 public int getTotalBytes() { 1762 return totalBytes; 1763 } 1764 1765 public void writeStarted(String fileDescription, int fileSize) { 1766 totalBytes = fileSize; 1767 file = fileDescription; 1768 results.setCurrentFile(fileDescription); 1769 results.setCurrentFilePercent(totalBytes > 0 ? 0 : -1); 1770 } 1771 1772 public void writeProgress(int bytes) { 1773 if(totalBytes > 0) { 1774 double percent = (double)bytes/(double)totalBytes; 1775 results.setCurrentFilePercent((int)(percent*100)); 1776 } else { 1777 results.setCurrentMessage((bytes/1024)+" kB of "+file); 1778 } 1779 } 1780 1781 public void writeComplete(int bytes) { 1782 results.setCurrentFilePercent(100); 1783 results.setCurrentMessage("Finished installing "+file+" ("+(bytes/1024)+" kB)"); 1784 results.addDownloadBytes(bytes); 1785 } 1786 1787 public DownloadPoller getResults() { 1788 return results; 1789 } 1790 } 1791 1792 /** 1793 * Interesting data resulting from opening a connection to a remote file. 1794 */ 1795 private static class OpenResult { 1796 private final InputStream stream; 1797 private final Artifact configID; 1798 private final int fileSize; 1799 1800 public OpenResult(Artifact configID, InputStream stream, int fileSize) { 1801 this.configID = configID; 1802 this.stream = stream; 1803 this.fileSize = fileSize; 1804 } 1805 1806 public Artifact getConfigID() { 1807 return configID; 1808 } 1809 1810 public InputStream getStream() { 1811 return stream; 1812 } 1813 1814 public int getFileSize() { 1815 return fileSize; 1816 } 1817 } 1818 1819 public static final GBeanInfo GBEAN_INFO; 1820 1821 static { 1822 GBeanInfoBuilder infoFactory = GBeanInfoBuilder.createStatic(PluginInstallerGBean.class); 1823 infoFactory.addReference("ConfigManager", ConfigurationManager.class, "ConfigurationManager"); 1824 infoFactory.addReference("Repository", WritableListableRepository.class, "Repository"); 1825 infoFactory.addReference("ConfigStore", ConfigurationStore.class, "ConfigurationStore"); 1826 infoFactory.addReference("ServerInfo", ServerInfo.class, "GBean"); 1827 infoFactory.addReference("ThreadPool", ThreadPool.class, "GBean"); 1828 infoFactory.addReference("PluginAttributeStore", PluginAttributeStore.class, "AttributeStore"); 1829 infoFactory.addInterface(PluginInstaller.class); 1830 1831 infoFactory.setConstructor(new String[]{"ConfigManager", "Repository", "ConfigStore", 1832 "ServerInfo", "ThreadPool", "PluginAttributeStore"}); 1833 1834 GBEAN_INFO = infoFactory.getBeanInfo(); 1835 } 1836 1837 public static GBeanInfo getGBeanInfo() { 1838 return GBEAN_INFO; 1839 } 1840 }