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.plugin.car;
021    
022    import java.io.File;
023    import java.net.URI;
024    
025    import java.util.ArrayList;
026    import java.util.List;
027    import java.util.Iterator;
028    import java.util.Set;
029    import java.util.HashSet;
030    
031    import org.apache.geronimo.deployment.PluginBootstrap2;
032    import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
033    import org.apache.geronimo.system.repository.Maven2Repository;
034    import org.apache.geronimo.system.resolver.ExplicitDefaultArtifactResolver;
035    import org.apache.geronimo.kernel.Kernel;
036    import org.apache.geronimo.kernel.KernelRegistry;
037    import org.apache.geronimo.kernel.KernelFactory;
038    import org.apache.geronimo.kernel.Naming;
039    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
040    import org.apache.geronimo.kernel.log.GeronimoLogging;
041    import org.apache.geronimo.kernel.management.State;
042    import org.apache.geronimo.kernel.config.ConfigurationManager;
043    import org.apache.geronimo.kernel.config.ConfigurationUtil;
044    import org.apache.geronimo.kernel.config.ConfigurationData;
045    import org.apache.geronimo.kernel.config.KernelConfigurationManager;
046    import org.apache.geronimo.gbean.AbstractName;
047    import org.apache.geronimo.gbean.GBeanData;
048    import org.apache.geronimo.gbean.GBeanInfo;
049    import org.apache.geronimo.gbean.ReferencePatterns;
050    import org.apache.geronimo.gbean.AbstractNameQuery;
051    
052    import org.apache.geronimo.genesis.ArtifactItem;
053    
054    import org.apache.maven.archiver.MavenArchiveConfiguration;
055    import org.apache.maven.archiver.MavenArchiver;
056    import org.apache.maven.plugin.MojoExecutionException;
057    import org.apache.maven.artifact.Artifact;
058    
059    import org.codehaus.plexus.util.FileUtils;
060    import org.codehaus.plexus.archiver.jar.JarArchiver;
061    
062    /**
063     * Build a Geronimo Configuration using the local Maven infrastructure.
064     *
065     * <p>
066     * <b>NOTE:</b> Calling pom.xml must have defined a ${geronimoVersion} property.
067     * </p>
068     *
069     * @goal package
070     * @requiresDependencyResolution runtime
071     *
072     * @version $Rev: 451661 $ $Date: 2006-09-30 13:45:53 -0700 (Sat, 30 Sep 2006) $
073     */
074    public class PackageMojo
075        extends AbstractCarMojo
076    {
077        /**
078         * The maven archive configuration to use.
079         *
080         * See <a href="http://maven.apache.org/ref/current/maven-archiver/apidocs/org/apache/maven/archiver/MavenArchiveConfiguration.html">the Javadocs for MavenArchiveConfiguration</a>.
081         *
082         * @parameter
083         */
084        private MavenArchiveConfiguration archive = new MavenArchiveConfiguration();
085    
086        /**
087         * The Jar archiver.
088         *
089         * @parameter expression="${component.org.codehaus.plexus.archiver.Archiver#jar}"
090         * @required
091         * @readonly
092         */
093        private JarArchiver jarArchiver = null;
094    
095        /**
096         * Directory containing the generated archive.
097         *
098         * @parameter expression="${project.build.directory}"
099         * @required
100         */
101        private File outputDirectory = null;
102    
103        /**
104         * Directory containing the classes/resources.
105         *
106         * @parameter expression="${project.build.outputDirectory}"
107         * @required
108         */
109        private File classesDirectory = null;
110    
111        /**
112         * Name of the generated archive.
113         *
114         * @parameter expression="${project.build.finalName}"
115         * @required
116         */
117        private String finalName = null;
118    
119        /**
120         * ???
121         *
122         * @parameter expression="${settings.localRepository}"
123         * @required
124         * @readonly
125         */
126        private File repository = null;
127    
128        /**
129         * ???
130         *
131         * @parameter expression="${project.build.directory}/repository"
132         * @required
133         */
134        private File targetRepository = null;
135    
136        /**
137         * ???
138         *
139         * @parameter expression="org.apache.geronimo.configs/geronimo-gbean-deployer/${geronimoVersion}/car"
140         * @required
141         * @readonly
142         */
143        private String deafultDeploymentConfig = null;
144    
145        /**
146         * ???
147         *
148         * @parameter
149         */
150        private List deploymentConfigs;
151    
152        /**
153         * The name of the deployer which will be used to deploy the CAR.
154         *
155         * @parameter expression="org.apache.geronimo.configs/geronimo-gbean-deployer/${geronimoVersion}/car?j2eeType=Deployer,name=Deployer"
156         * @required
157         */
158        private String deployerName = null;
159    
160        /**
161         * The plan file for the CAR.
162         *
163         * @parameter expression="${project.build.directory}/plan/plan.xml"
164         * @required
165         */
166        private File planFile = null;
167    
168        /**
169         * The file to include as a module of the CAR.
170         *
171         * @parameter
172         */
173        private File moduleFile = null;
174    
175        /**
176         * An {@link ArtifactItem} to include as a module of the CAR.
177         *
178         * @parameter
179         */
180        private ArtifactItem module = null;
181    
182        /**
183         * The location where the properties mapping will be generated.
184         *
185         * <p>
186         * Probably don't wanto to change this.
187         *
188         * @parameter expression="${project.build.directory}/explicit-versions.properties"
189         */
190        private File explicitResolutionProperties = null;
191    
192        /**
193         * An array of {@link ClasspathElement} objects which will be used to construct the
194         * Class-Path entry of the manifest.
195         *
196         * This is needed to allow per-element prefixes to be added, which the standard Maven archiver
197         * does not provide.
198         *
199         * @parameter
200         */
201        private ClasspathElement[] classpath = null;
202    
203        /**
204         * The default prefix to be applied to all elements of the <tt>classpath</tt> which
205         * do not provide a prefix.
206         *
207         * @parameter
208         */
209        private String classpathPrefix = null;
210    
211        /**
212         * True to enable the bootshell when packaging.
213         *
214         * @parameter
215         */
216        private boolean bootstrap = false;
217    
218        //
219        // Mojo
220        //
221    
222        protected void doExecute() throws Exception {
223            // We need to make sure to clean up any previous work first or this operation will fail
224            FileUtils.forceDelete(targetRepository);
225            FileUtils.forceMkdir(targetRepository);
226    
227            // Use the default configs if none specified
228            if (deploymentConfigs == null) {
229                deploymentConfigs = new ArrayList();
230                deploymentConfigs.add(deafultDeploymentConfig);
231            }
232            log.debug("Deployment configs: " + deploymentConfigs);
233    
234            // If module is set, then resolve the artifact and set moduleFile
235            if (module != null) {
236                Artifact artifact = getArtifact(module);
237                moduleFile = artifact.getFile();
238                log.debug("Using module file: " + moduleFile);
239            }
240    
241            generateExplicitVersionProperties(explicitResolutionProperties);
242    
243            if (bootstrap) {
244                executeBootShell();
245            }
246            else {
247                buildPackage();
248            }
249    
250            // Build the archive
251            File archive = createArchive();
252    
253            // Attach the generated archive for install/deploy
254            project.getArtifact().setFile(archive);
255        }
256    
257        private File getArtifactInRepositoryDir() {
258            //
259            // HACK: Generate the filename in the repo... really should delegate this to the repo impl
260            //
261    
262            File dir = new File(targetRepository, project.getGroupId().replace('.', '/'));
263            dir = new File(dir, project.getArtifactId());
264            dir = new File(dir, project.getVersion());
265            dir = new File(dir, project.getArtifactId() + "-" + project.getVersion() + ".car");
266    
267            return dir;
268        }
269    
270        public void executeBootShell() throws Exception {
271            log.debug("Starting bootstrap shell...");
272    
273            PluginBootstrap2 boot = new PluginBootstrap2();
274    
275            boot.setBuildDir(outputDirectory);
276            boot.setCarFile(getArtifactInRepositoryDir());
277            boot.setLocalRepo(repository);
278            boot.setPlan(planFile);
279    
280            // Generate expanded so we can use Maven to generate the archive
281            boot.setExpanded(true);
282    
283            boot.bootstrap();
284        }
285    
286        /**
287         * Generates the configuration archive.
288         */
289        private File createArchive() throws MojoExecutionException {
290            File archiveFile = getArchiveFile(outputDirectory, finalName, null);
291    
292            MavenArchiver archiver = new MavenArchiver();
293            archiver.setArchiver(jarArchiver);
294            archiver.setOutputFile(archiveFile);
295    
296            try {
297                // Incldue the generated artifact contents
298                archiver.getArchiver().addDirectory(getArtifactInRepositoryDir());
299    
300                // Include the optional classes.resources
301                if (classesDirectory.isDirectory()) {
302                    archiver.getArchiver().addDirectory(classesDirectory);
303                }
304    
305                if (classpath != null) {
306                    archive.addManifestEntry("Class-Path", getClassPath());
307                }
308    
309                archiver.createArchive(project, archive);
310    
311                return archiveFile;
312            }
313            catch (Exception e) {
314                throw new MojoExecutionException("Failed to create archive", e);
315            }
316        }
317    
318        private String getClassPath() throws MojoExecutionException {
319            StringBuffer buff = new StringBuffer();
320    
321            for (int i=0; i < classpath.length; i++) {
322                Artifact artifact = getArtifact(classpath[i]);
323    
324                //
325                // TODO: Need to optionally get all transitive dependencies... but dunno how to get that intel from m2
326                //
327    
328                String prefix = classpath[i].getClasspathPrefix();
329                if (prefix == null) {
330                    prefix = classpathPrefix;
331                }
332    
333                if (prefix != null) {
334                    buff.append(prefix);
335    
336                    if (!prefix.endsWith("/")) {
337                        buff.append("/");
338                    }
339                }
340    
341                File file = artifact.getFile();
342                buff.append(file.getName());
343    
344                if (i + 1< classpath.length) {
345                    buff.append(" ");
346                }
347            }
348    
349            log.debug("Using classpath: " + buff);
350    
351            return buff.toString();
352        }
353    
354        //
355        // Deployment
356        //
357    
358        private static final String KERNEL_NAME = "geronimo.maven";
359    
360        /**
361         * Reference to the kernel that will last the lifetime of this classloader.
362         * The KernelRegistry keeps soft references that may be garbage collected.
363         */
364        private static Kernel kernel;
365    
366        private static AbstractName targetConfigStoreAName;
367    
368        private static AbstractName targetRepositoryAName;
369    
370        private boolean targetSet;
371    
372        public void buildPackage() throws Exception {
373            log.info("Packaging module configuration: " + planFile);
374    
375    
376            Kernel kernel = createKernel();
377            if (!targetSet) {
378                kernel.stopGBean(targetRepositoryAName);
379                kernel.setAttribute(targetRepositoryAName, "root", targetRepository.toURI());
380                kernel.startGBean(targetRepositoryAName);
381    
382                if (kernel.getGBeanState(targetConfigStoreAName) != State.RUNNING_INDEX) {
383                    throw new IllegalStateException("After restarted repository then config store is not running");
384                }
385    
386                targetSet = true;
387            }
388    
389            log.debug("Starting configuration...");
390    
391            // start the Configuration we're going to use for this deployment
392            ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
393            try {
394                for (Iterator iterator = deploymentConfigs.iterator(); iterator.hasNext();) {
395                    String artifactName = (String) iterator.next();
396                    org.apache.geronimo.kernel.repository.Artifact configName =
397                            org.apache.geronimo.kernel.repository.Artifact.create(artifactName);
398                    if (!configurationManager.isLoaded(configName)) {
399                        configurationManager.loadConfiguration(configName);
400                        configurationManager.startConfiguration(configName);
401                    }
402                }
403            } finally {
404                ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
405            }
406    
407            log.debug("Deploying...");
408    
409            AbstractName deployer = locateDeployer(kernel);
410            invokeDeployer(kernel, deployer, targetConfigStoreAName.toString());
411        }
412    
413        /**
414         * Create a Geronimo Kernel to contain the deployment configurations.
415         */
416        private synchronized Kernel createKernel() throws Exception {
417            // first return our cached version
418            if (kernel != null) {
419                return kernel;
420            }
421    
422            log.debug("Creating kernel...");
423    
424            // check the registry in case someone else created one
425            kernel = KernelRegistry.getKernel(KERNEL_NAME);
426            if (kernel != null) {
427                return kernel;
428            }
429    
430            GeronimoLogging geronimoLogging = GeronimoLogging.getGeronimoLogging("WARN");
431            if (geronimoLogging == null) {
432                geronimoLogging = GeronimoLogging.DEBUG;
433            }
434            GeronimoLogging.initialize(geronimoLogging);
435    
436            // boot one ourselves
437            kernel = KernelFactory.newInstance().createKernel(KERNEL_NAME);
438            kernel.boot();
439    
440            bootDeployerSystem();
441    
442            return kernel;
443        }
444    
445        /**
446         * Boot the in-Maven deployment system.
447         *
448         * <p>
449         * This contains Repository and ConfigurationStore GBeans that map to
450         * the local maven installation.
451         */
452        private void bootDeployerSystem() throws Exception {
453            log.debug("Booting deployer system...");
454    
455            org.apache.geronimo.kernel.repository.Artifact baseId =
456                    new org.apache.geronimo.kernel.repository.Artifact("geronimo", "packaging", "fixed", "car");
457            Naming naming = kernel.getNaming();
458            ConfigurationData bootstrap = new ConfigurationData(baseId, naming);
459            ClassLoader cl = getClass().getClassLoader();
460            Set repoNames = new HashSet();
461    
462            // Source repo
463            GBeanData repoGBean = bootstrap.addGBean("SourceRepository", GBeanInfo.getGBeanInfo(Maven2Repository.class.getName(), cl));
464            URI repositoryURI = repository.toURI();
465            repoGBean.setAttribute("root", repositoryURI);
466            repoNames.add(repoGBean.getAbstractName());
467    
468            // Target repo
469            GBeanData targetRepoGBean = bootstrap.addGBean("TargetRepository", GBeanInfo.getGBeanInfo(Maven2Repository.class.getName(), cl));
470            URI targetRepositoryURI = targetRepository.toURI();
471            targetRepoGBean.setAttribute("root", targetRepositoryURI);
472            repoNames.add(targetRepoGBean.getAbstractName());
473            targetRepositoryAName = targetRepoGBean.getAbstractName();
474    
475            GBeanData artifactManagerGBean = bootstrap.addGBean("ArtifactManager", DefaultArtifactManager.GBEAN_INFO);
476            GBeanData artifactResolverGBean = bootstrap.addGBean("ArtifactResolver", ExplicitDefaultArtifactResolver.GBEAN_INFO);
477            artifactResolverGBean.setAttribute("versionMapLocation", explicitResolutionProperties.getAbsolutePath());
478            ReferencePatterns repoPatterns = new ReferencePatterns(repoNames);
479            artifactResolverGBean.setReferencePatterns("Repositories", repoPatterns);
480            artifactResolverGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
481    
482            Set storeNames = new HashSet();
483    
484            // Source config store
485            GBeanInfo configStoreInfo = GBeanInfo.getGBeanInfo(MavenConfigStore.class.getName(), cl);
486            GBeanData storeGBean = bootstrap.addGBean("ConfigStore", configStoreInfo);
487            if (configStoreInfo.getReference("Repository") != null) {
488                storeGBean.setReferencePattern("Repository", repoGBean.getAbstractName());
489            }
490            storeNames.add(storeGBean.getAbstractName());
491    
492            // Target config store
493            GBeanInfo targetConfigStoreInfo = GBeanInfo.getGBeanInfo(RepositoryConfigurationStore.class.getName(), cl);
494            GBeanData targetStoreGBean = bootstrap.addGBean("TargetConfigStore", targetConfigStoreInfo);
495            if (targetConfigStoreInfo.getReference("Repository") != null) {
496                targetStoreGBean.setReferencePattern("Repository", targetRepoGBean.getAbstractName());
497            }
498            storeNames.add(targetStoreGBean.getAbstractName());
499    
500            targetConfigStoreAName = targetStoreGBean.getAbstractName();
501            targetSet = true;
502    
503            GBeanData attrManagerGBean = bootstrap.addGBean("AttributeStore", MavenAttributeStore.GBEAN_INFO);
504            GBeanData configManagerGBean = bootstrap.addGBean("ConfigManager", KernelConfigurationManager.GBEAN_INFO);
505            configManagerGBean.setReferencePatterns("Stores", new ReferencePatterns(storeNames));
506            configManagerGBean.setReferencePattern("AttributeStore", attrManagerGBean.getAbstractName());
507            configManagerGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
508            configManagerGBean.setReferencePattern("ArtifactResolver", artifactResolverGBean.getAbstractName());
509            configManagerGBean.setReferencePatterns("Repositories", repoPatterns);
510    
511            ConfigurationUtil.loadBootstrapConfiguration(kernel, bootstrap, cl);
512        }
513    
514        /**
515         * Locate a Deployer GBean matching the deployerName pattern.
516         *
517         * @param kernel the kernel to search.
518         * @return the ObjectName of the Deployer GBean
519         *
520         * @throws IllegalStateException if there is not exactly one GBean matching the deployerName pattern
521         */
522        private AbstractName locateDeployer(final Kernel kernel) {
523            AbstractName name = new AbstractName(URI.create(deployerName));
524    
525            Iterator i = kernel.listGBeans(new AbstractNameQuery(name)).iterator();
526            if (!i.hasNext()) {
527                throw new IllegalStateException("No deployer found matching deployerName: " + name);
528            }
529    
530            AbstractName deployer = (AbstractName)i.next();
531            if (i.hasNext()) {
532                throw new IllegalStateException("Multiple deployers found matching deployerName: " + name);
533            }
534    
535            return deployer;
536        }
537    
538        private static final String[] DEPLOY_SIGNATURE = {
539            boolean.class.getName(),
540            File.class.getName(),
541            File.class.getName(),
542            File.class.getName(),
543            Boolean.TYPE.getName(),
544            String.class.getName(),
545            String.class.getName(),
546            String.class.getName(),
547            String.class.getName(),
548            String.class.getName(),
549            String.class.getName(),
550            String.class.getName(),
551            String.class.getName(),
552        };
553    
554        private List invokeDeployer(final Kernel kernel, final AbstractName deployer, final String targetConfigStore) throws Exception {
555            Object[] args = {
556                Boolean.FALSE, // Not in-place
557                planFile,
558                moduleFile,
559                null, // Target file
560                Boolean.TRUE, // Install
561                null, // main-class
562                null, // main-gbean
563                null, // main-method
564                null, // Manifest configurations
565                null, // class-path
566                null, // endorsed-dirs
567                null, // extension-dirs
568                targetConfigStore
569            };
570    
571            return (List) kernel.invoke(deployer, "deploy", args, DEPLOY_SIGNATURE);
572        }
573    }