001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one
003     *  or more contributor license agreements.  See the NOTICE file
004     *  distributed with this work for additional information
005     *  regarding copyright ownership.  The ASF licenses this file
006     *  to you under the Apache License, Version 2.0 (the
007     *  "License"); you may not use this file except in compliance
008     *  with the License.  You may obtain a copy of the License at
009     *
010     *    http://www.apache.org/licenses/LICENSE-2.0
011     *
012     *  Unless required by applicable law or agreed to in writing,
013     *  software distributed under the License is distributed on an
014     *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     *  KIND, either express or implied.  See the License for the
016     *  specific language governing permissions and limitations
017     *  under the License.
018     */
019    
020    package org.apache.geronimo.mavenplugins.car;
021    
022    import java.io.File;
023    import java.net.URI;
024    import java.util.Arrays;
025    import java.util.HashMap;
026    import java.util.HashSet;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    import java.util.Set;
031    
032    import org.codehaus.mojo.pluginsupport.util.ArtifactItem;
033    import org.codehaus.mojo.pluginsupport.dependency.DependencyTree;
034    
035    import org.apache.geronimo.deployment.PluginBootstrap2;
036    import org.apache.geronimo.gbean.AbstractName;
037    import org.apache.geronimo.gbean.AbstractNameQuery;
038    import org.apache.geronimo.gbean.GBeanData;
039    import org.apache.geronimo.gbean.GBeanInfo;
040    import org.apache.geronimo.gbean.ReferencePatterns;
041    import org.apache.geronimo.kernel.Kernel;
042    import org.apache.geronimo.kernel.KernelFactory;
043    import org.apache.geronimo.kernel.KernelRegistry;
044    import org.apache.geronimo.kernel.Naming;
045    import org.apache.geronimo.kernel.config.ConfigurationData;
046    import org.apache.geronimo.kernel.config.ConfigurationManager;
047    import org.apache.geronimo.kernel.config.ConfigurationUtil;
048    import org.apache.geronimo.kernel.config.KernelConfigurationManager;
049    import org.apache.geronimo.kernel.log.GeronimoLogging;
050    import org.apache.geronimo.kernel.management.State;
051    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
052    import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
053    import org.apache.geronimo.system.repository.Maven2Repository;
054    import org.apache.geronimo.system.resolver.ExplicitDefaultArtifactResolver;
055    import org.apache.maven.archiver.MavenArchiveConfiguration;
056    import org.apache.maven.archiver.MavenArchiver;
057    import org.apache.maven.artifact.Artifact;
058    import org.apache.maven.plugin.MojoExecutionException;
059    import org.apache.maven.project.MavenProject;
060    import org.codehaus.plexus.archiver.jar.JarArchiver;
061    import org.codehaus.plexus.util.FileUtils;
062    
063    /**
064     * Build a Geronimo Configuration using the local Maven infrastructure.
065     *
066     * @goal package
067     * @requiresDependencyResolution runtime
068     *
069     * @version $Rev: 527673 $ $Date: 2007-04-11 17:09:33 -0400 (Wed, 11 Apr 2007) $
070     */
071    public class PackageMojo
072        extends AbstractCarMojo
073    {
074        /**
075         * The maven archive configuration to use.
076         *
077         * See <a href="http://maven.apache.org/ref/current/maven-archiver/apidocs/org/apache/maven/archiver/MavenArchiveConfiguration.html">the Javadocs for MavenArchiveConfiguration</a>.
078         *
079         * @parameter
080         */
081        private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
082        
083        /**
084         * Require <tt>geronimoVersion</tt> property to be set.
085         *
086         * @parameter expression="${geronimoVersion}"
087         * @required
088         * @readonly
089         */
090        private String geronimoVersion = null;
091        
092        /**
093         * The Jar archiver.
094         *
095         * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
096         * @required
097         * @readonly
098         */
099        private JarArchiver jarArchiver = null;
100        
101        /**
102         * The module base directory.
103         *
104         * @parameter expression="${project.basedir}"
105         * @required
106         * @readonly
107         */
108        private File baseDirectory = null;
109        
110        /**
111         * Directory containing the generated archive.
112         *
113         * @parameter expression="${project.build.directory}"
114         * @required
115         */
116        private File outputDirectory = null;
117    
118        /**
119         * Directory containing the classes/resources.
120         *
121         * @parameter expression="${project.build.outputDirectory}"
122         * @required
123         */
124        private File classesDirectory = null;
125    
126        /**
127         * Name of the generated archive.
128         *
129         * @parameter expression="${project.build.finalName}"
130         * @required
131         */
132        private String finalName = null;
133    
134        /**
135         * The local Maven repository which will be used to pull artifacts into the Geronimo repository when packaging.
136         *
137         * @parameter expression="${settings.localRepository}"
138         * @required
139         * @readonly
140         */
141        private File repository = null;
142    
143        /**
144         * The Geronimo repository where modules will be packaged up from.
145         *
146         * @parameter expression="${project.build.directory}/repository"
147         * @required
148         */
149        private File targetRepository = null;
150    
151        /**
152         * The default deployer module to be used when no other deployer modules are configured.
153         *
154         * @parameter expression="org.apache.geronimo.configs/geronimo-gbean-deployer/${geronimoVersion}/car"
155         * @required
156         * @readonly
157         */
158        private String defaultDeploymentConfig = null;
159    
160        /**
161         * Ther deployer modules to be used when packaging.
162         *
163         * @parameter
164         */
165        private String[] deploymentConfigs;
166    
167        /**
168         * The name of the deployer which will be used to deploy the CAR.
169         *
170         * @parameter expression="org.apache.geronimo.configs/geronimo-gbean-deployer/${geronimoVersion}/car?j2eeType=Deployer,name=Deployer"
171         * @required
172         */
173        private String deployerName = null;
174    
175        /**
176         * The plan file for the CAR.
177         *
178         * @parameter expression="${project.build.directory}/plan/plan.xml"
179         * @required
180         */
181        private File planFile = null;
182    
183        /**
184         * The file to include as a module of the CAR.
185         *
186         * @parameter
187         */
188        private File moduleFile = null;
189    
190        /**
191         * An {@link ArtifactItem} to include as a module of the CAR.
192         *
193         * @parameter
194         */
195        private ArtifactItem module = null;
196    
197        /**
198         * The location where the properties mapping will be generated.
199         *
200         * <p>
201         * Probably don't want to change this.
202         * </p>
203         *
204         * @parameter expression="${project.build.directory}/explicit-versions.properties"
205         */
206        private File explicitResolutionProperties = null;
207    
208        /**
209         * An array of {@link ClasspathElement} objects which will be used to construct the
210         * Class-Path entry of the manifest.
211         *
212         * This is needed to allow per-element prefixes to be added, which the standard Maven archiver
213         * does not provide.
214         *
215         * @parameter
216         */
217        private ClasspathElement[] classpath = null;
218    
219        /**
220         * The default prefix to be applied to all elements of the <tt>classpath</tt> which
221         * do not provide a prefix.
222         *
223         * @parameter
224         */
225        private String classpathPrefix = null;
226    
227        /**
228         * True to enable the bootshell when packaging.
229         *
230         * @parameter
231         */
232        private boolean bootstrap = false;
233    
234        /**
235         * Holds a local repo lookup instance so that we can use the current project to resolve.
236         * This is required since the Kernel used to deploy is cached.
237         */
238        private static ThreadLocal lookupHolder = new ThreadLocal();
239    
240        //
241        // Mojo
242        //
243    
244        protected void doExecute() throws Exception {
245            // We need to make sure to clean up any previous work first or this operation will fail
246            FileUtils.forceDelete(targetRepository);
247            FileUtils.forceMkdir(targetRepository);
248    
249            // Use the default configs if none specified
250            if (deploymentConfigs == null) {
251                if (!bootstrap) {
252                    deploymentConfigs = new String[] {defaultDeploymentConfig};
253                } else {
254                    deploymentConfigs = new String[] {};
255                }
256            }
257            log.debug("Deployment configs: " + Arrays.asList(deploymentConfigs));
258    
259            //
260            // NOTE: Resolve deployment modules, this is needed to ensure that the proper artifacts are in the
261            //       local repository to perform deployment.  If the deployer modules (or their dependencies)
262            //       are missing from the source respository, then strange packaging failures will occur.
263            //
264            Set additionalArtifacts = new HashSet();
265            for (int i=0; i<deploymentConfigs.length; i++) {
266                Artifact artifact = geronimoToMavenArtifact(org.apache.geronimo.kernel.repository.Artifact.create(deploymentConfigs[i]));
267                log.debug("Resolving deployer module: " + artifact);
268                Artifact resolved = resolveArtifact(artifact, true);
269                additionalArtifacts.add(resolved);
270            }
271            //Ensure that these dependencies are available to geronimo
272            if (project.getDependencyArtifacts() == null) {
273                Set oldArtifacts = project.createArtifacts(dependencyHelper.getArtifactFactory(), null, null);
274                additionalArtifacts.addAll(oldArtifacts);
275            } else {
276                Set oldArtifacts = project.getDependencyArtifacts();
277                additionalArtifacts.addAll(oldArtifacts);
278            }
279            project.setDependencyArtifacts(additionalArtifacts);
280    
281    
282            // If module is set, then resolve the artifact and set moduleFile
283            if (module != null) {
284                Artifact artifact = getArtifact(module);
285                moduleFile = artifact.getFile();
286                log.debug("Using module file: " + moduleFile);
287            }
288            dependencies.setRootNode(dependencyHelper.getDependencies(project).getRootNode());
289    
290            generateExplicitVersionProperties(explicitResolutionProperties, dependencies);
291    
292            //
293            // NOTE: Install a local lookup, so that the cached kernel can resolve based on the current project
294            //       and not the project where the kernel was first initialized.
295            //
296            lookupHolder.set(new ArtifactLookupImpl(new HashMap()));
297    
298            if (bootstrap) {
299                executeBootShell();
300            }
301            else {
302                buildPackage();
303            }
304    
305            // Build the archive
306            File archive = createArchive();
307    
308            // Attach the generated archive for install/deploy
309            project.getArtifact().setFile(archive);
310        }
311    
312        private File getArtifactInRepositoryDir() {
313            //
314            // HACK: Generate the filename in the repo... really should delegate this to the repo impl
315            //
316    
317            File dir = new File(targetRepository, project.getGroupId().replace('.', '/'));
318            dir = new File(dir, project.getArtifactId());
319            dir = new File(dir, project.getVersion());
320            dir = new File(dir, project.getArtifactId() + "-" + project.getVersion() + ".car");
321    
322            return dir;
323        }
324    
325        public void executeBootShell() throws Exception {
326            log.debug("Starting bootstrap shell...");
327    
328            PluginBootstrap2 boot = new PluginBootstrap2();
329    
330            boot.setBuildDir(outputDirectory);
331            boot.setCarFile(getArtifactInRepositoryDir());
332            boot.setLocalRepo(repository);
333            boot.setPlan(planFile);
334    
335            // Generate expanded so we can use Maven to generate the archive
336            boot.setExpanded(true);
337    
338            boot.bootstrap();
339        }
340    
341        /**
342         * Generates the configuration archive.
343         */
344        private File createArchive() throws MojoExecutionException {
345            File archiveFile = getArchiveFile(outputDirectory, finalName, null);
346    
347            MavenArchiver archiver = new MavenArchiver();
348            archiver.setArchiver(jarArchiver);
349            archiver.setOutputFile(archiveFile);
350    
351            try {
352                // Incldue the generated artifact contents
353                archiver.getArchiver().addDirectory(getArtifactInRepositoryDir());
354    
355                // Include the optional classes.resources
356                if (classesDirectory.isDirectory()) {
357                    archiver.getArchiver().addDirectory(classesDirectory);
358                }
359                
360                //
361                // HACK: Include legal files here for sanity
362                //
363    
364                //
365                // NOTE: Would be nice to share this with the copy-legal-files mojo
366                //
367                String[] includes = {
368                    "LICENSE.txt",
369                    "LICENSE",
370                    "NOTICE.txt",
371                    "NOTICE",
372                    "DISCLAIMER.txt",
373                    "DISCLAIMER"
374                };
375    
376                archiver.getArchiver().addDirectory(baseDirectory, "META-INF/", includes, new String[0]);
377                
378                if (classpath != null) {
379                    archive.addManifestEntry("Class-Path", getClassPath());
380                }
381    
382                archiver.createArchive(project, archive);
383    
384                return archiveFile;
385            }
386            catch (Exception e) {
387                throw new MojoExecutionException("Failed to create archive", e);
388            }
389        }
390    
391        private String getClassPath() throws MojoExecutionException {
392            StringBuffer buff = new StringBuffer();
393    
394            for (int i=0; i < classpath.length; i++) {
395                String entry = classpath[i].getEntry();
396                if (entry != null) {
397                    buff.append(entry);
398                } else {
399                    Artifact artifact = getArtifact(classpath[i]);
400    
401                    //
402                    // TODO: Need to optionally get all transitive dependencies... but dunno how to get that intel from m2
403                    //
404    
405                    String prefix = classpath[i].getClasspathPrefix();
406                    if (prefix == null) {
407                        prefix = classpathPrefix;
408                    }
409    
410                    if (prefix != null) {
411                        buff.append(prefix);
412    
413                        if (!prefix.endsWith("/")) {
414                            buff.append("/");
415                        }
416                    }
417    
418                    File file = artifact.getFile();
419                    buff.append(file.getName());
420                }
421    
422                if (i + 1< classpath.length) {
423                    buff.append(" ");
424                }
425            }
426    
427            log.debug("Using classpath: " + buff);
428    
429            return buff.toString();
430        }
431    
432        //
433        // Deployment
434        //
435    
436        private static final String KERNEL_NAME = "geronimo.maven";
437    
438        /**
439         * Reference to the kernel that will last the lifetime of this classloader.
440         * The KernelRegistry keeps soft references that may be garbage collected.
441         */
442        private Kernel kernel;
443    
444        private AbstractName targetConfigStoreAName;
445    
446        private AbstractName targetRepositoryAName;
447    
448        private boolean targetSet;
449    
450        private DependencyTree dependencies = new DependencyTree();
451    
452        public void buildPackage() throws Exception {
453            log.info("Packaging module configuration: " + planFile);
454    
455            Kernel kernel = createKernel();
456            if (!targetSet) {
457                kernel.stopGBean(targetRepositoryAName);
458                kernel.setAttribute(targetRepositoryAName, "root", targetRepository.toURI());
459                kernel.startGBean(targetRepositoryAName);
460    
461                if (kernel.getGBeanState(targetConfigStoreAName) != State.RUNNING_INDEX) {
462                    throw new IllegalStateException("After restarted repository then config store is not running");
463                }
464    
465                targetSet = true;
466            }
467    
468            log.debug("Starting configuration...");
469    
470            // start the Configuration we're going to use for this deployment
471            ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
472            try {
473                for (int i=0; i<deploymentConfigs.length; i++) {
474                    String artifactName = deploymentConfigs[i];
475                    org.apache.geronimo.kernel.repository.Artifact configName =
476                            org.apache.geronimo.kernel.repository.Artifact.create(artifactName);
477                    if (!configurationManager.isLoaded(configName)) {
478                        configurationManager.loadConfiguration(configName);
479                        configurationManager.startConfiguration(configName);
480                    }
481                }
482            } finally {
483                ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
484            }
485    
486            log.debug("Deploying...");
487    
488            AbstractName deployer = locateDeployer(kernel);
489            invokeDeployer(kernel, deployer, targetConfigStoreAName.toString());
490            //use a fresh kernel for each module
491            kernel.shutdown();
492            kernel = null;
493        }
494    
495        /**
496         * Create a Geronimo Kernel to contain the deployment configurations.
497         */
498        private synchronized Kernel createKernel() throws Exception {
499            // first return our cached version
500            if (kernel != null) {
501                return kernel;
502            }
503    
504            log.debug("Creating kernel...");
505    
506            // check the registry in case someone else created one
507            kernel = KernelRegistry.getKernel(KERNEL_NAME);
508            if (kernel != null) {
509                return kernel;
510            }
511    
512            GeronimoLogging geronimoLogging = GeronimoLogging.getGeronimoLogging("WARN");
513            if (geronimoLogging == null) {
514                geronimoLogging = GeronimoLogging.DEBUG;
515            }
516            GeronimoLogging.initialize(geronimoLogging);
517    
518            // boot one ourselves
519            kernel = KernelFactory.newInstance().createKernel(KERNEL_NAME);
520            kernel.boot();
521    
522            bootDeployerSystem();
523    
524            return kernel;
525        }
526    
527        /**
528         * Boot the in-Maven deployment system.
529         *
530         * <p>
531         * This contains Repository and ConfigurationStore GBeans that map to
532         * the local maven installation.
533         * </p>
534         */
535        private void bootDeployerSystem() throws Exception {
536            log.debug("Booting deployer system...");
537    
538            org.apache.geronimo.kernel.repository.Artifact baseId =
539                    new org.apache.geronimo.kernel.repository.Artifact("geronimo", "packaging", "fixed", "car");
540            Naming naming = kernel.getNaming();
541            ConfigurationData bootstrap = new ConfigurationData(baseId, naming);
542            ClassLoader cl = getClass().getClassLoader();
543            Set repoNames = new HashSet();
544    
545            //
546            // NOTE: Install an adapter for the source repository that will leverage the Maven2 repository subsystem
547            //       to allow for better handling of SNAPSHOT values.
548            //
549            GBeanData repoGBean = bootstrap.addGBean("SourceRepository", GBeanInfo.getGBeanInfo(Maven2RepositoryAdapter.class.getName(), cl));
550            Maven2RepositoryAdapter.ArtifactLookup lookup = new Maven2RepositoryAdapter.ArtifactLookup() {
551                private Maven2RepositoryAdapter.ArtifactLookup getDelegate() {
552                    return (Maven2RepositoryAdapter.ArtifactLookup) lookupHolder.get();
553                }
554                
555                public File getBasedir() {
556                    return getDelegate().getBasedir();
557                }
558    
559                public File getLocation(final org.apache.geronimo.kernel.repository.Artifact artifact) {
560                    return getDelegate().getLocation(artifact);
561                }
562            };
563            repoGBean.setAttribute("lookup", lookup);
564            repoGBean.setAttribute("dependencies", dependencies);
565            repoNames.add(repoGBean.getAbstractName());
566    
567            // Target repo
568            GBeanData targetRepoGBean = bootstrap.addGBean("TargetRepository", GBeanInfo.getGBeanInfo(Maven2Repository.class.getName(), cl));
569            URI targetRepositoryURI = targetRepository.toURI();
570            targetRepoGBean.setAttribute("root", targetRepositoryURI);
571            repoNames.add(targetRepoGBean.getAbstractName());
572            targetRepositoryAName = targetRepoGBean.getAbstractName();
573    
574            GBeanData artifactManagerGBean = bootstrap.addGBean("ArtifactManager", DefaultArtifactManager.GBEAN_INFO);
575            GBeanData artifactResolverGBean = bootstrap.addGBean("ArtifactResolver", ExplicitDefaultArtifactResolver.GBEAN_INFO);
576            artifactResolverGBean.setAttribute("versionMapLocation", explicitResolutionProperties.getAbsolutePath());
577            ReferencePatterns repoPatterns = new ReferencePatterns(repoNames);
578            artifactResolverGBean.setReferencePatterns("Repositories", repoPatterns);
579            artifactResolverGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
580    
581            Set storeNames = new HashSet();
582    
583            // Source config store
584            GBeanInfo configStoreInfo = GBeanInfo.getGBeanInfo(MavenConfigStore.class.getName(), cl);
585            GBeanData storeGBean = bootstrap.addGBean("ConfigStore", configStoreInfo);
586            if (configStoreInfo.getReference("Repository") != null) {
587                storeGBean.setReferencePattern("Repository", repoGBean.getAbstractName());
588            }
589            storeNames.add(storeGBean.getAbstractName());
590    
591            // Target config store
592            GBeanInfo targetConfigStoreInfo = GBeanInfo.getGBeanInfo(RepositoryConfigurationStore.class.getName(), cl);
593            GBeanData targetStoreGBean = bootstrap.addGBean("TargetConfigStore", targetConfigStoreInfo);
594            if (targetConfigStoreInfo.getReference("Repository") != null) {
595                targetStoreGBean.setReferencePattern("Repository", targetRepoGBean.getAbstractName());
596            }
597            storeNames.add(targetStoreGBean.getAbstractName());
598    
599            targetConfigStoreAName = targetStoreGBean.getAbstractName();
600            targetSet = true;
601    
602            GBeanData attrManagerGBean = bootstrap.addGBean("AttributeStore", MavenAttributeStore.GBEAN_INFO);
603            GBeanData configManagerGBean = bootstrap.addGBean("ConfigManager", KernelConfigurationManager.GBEAN_INFO);
604            configManagerGBean.setReferencePatterns("Stores", new ReferencePatterns(storeNames));
605            configManagerGBean.setReferencePattern("AttributeStore", attrManagerGBean.getAbstractName());
606            configManagerGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
607            configManagerGBean.setReferencePattern("ArtifactResolver", artifactResolverGBean.getAbstractName());
608            configManagerGBean.setReferencePatterns("Repositories", repoPatterns);
609    
610            ConfigurationUtil.loadBootstrapConfiguration(kernel, bootstrap, cl);
611        }
612    
613        /**
614         * Locate a Deployer GBean matching the deployerName pattern.
615         *
616         * @param kernel the kernel to search.
617         * @return the ObjectName of the Deployer GBean
618         *
619         * @throws IllegalStateException if there is not exactly one GBean matching the deployerName pattern
620         */
621        private AbstractName locateDeployer(final Kernel kernel) {
622            AbstractName name = new AbstractName(URI.create(deployerName));
623    
624            Iterator i = kernel.listGBeans(new AbstractNameQuery(name)).iterator();
625            if (!i.hasNext()) {
626                throw new IllegalStateException("No deployer found matching deployerName: " + name);
627            }
628    
629            AbstractName deployer = (AbstractName)i.next();
630            if (i.hasNext()) {
631                throw new IllegalStateException("Multiple deployers found matching deployerName: " + name);
632            }
633    
634            return deployer;
635        }
636    
637        private static final String[] DEPLOY_SIGNATURE = {
638            boolean.class.getName(),
639            File.class.getName(),
640            File.class.getName(),
641            File.class.getName(),
642            Boolean.TYPE.getName(),
643            String.class.getName(),
644            String.class.getName(),
645            String.class.getName(),
646            String.class.getName(),
647            String.class.getName(),
648            String.class.getName(),
649            String.class.getName(),
650            String.class.getName(),
651        };
652    
653        private List invokeDeployer(final Kernel kernel, final AbstractName deployer, final String targetConfigStore) throws Exception {
654            Object[] args = {
655                Boolean.FALSE, // Not in-place
656                moduleFile,
657                planFile,
658                null, // Target file
659                Boolean.TRUE, // Install
660                null, // main-class
661                null, // main-gbean
662                null, // main-method
663                null, // Manifest configurations
664                null, // class-path
665                null, // endorsed-dirs
666                null, // extension-dirs
667                targetConfigStore
668            };
669    
670            return (List) kernel.invoke(deployer, "deploy", args, DEPLOY_SIGNATURE);
671        }
672    
673        //
674        // ArtifactLookupImpl
675        //
676    
677        /**
678         * Map of G artifact to M artifact which have already been resolved.
679         */
680        // private static Map presolvedArtifacts = new HashMap();
681    }