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 }