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