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