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.Set;
030    
031    import org.apache.geronimo.deployment.PluginBootstrap2;
032    import org.apache.geronimo.gbean.AbstractName;
033    import org.apache.geronimo.gbean.AbstractNameQuery;
034    import org.apache.geronimo.gbean.GBeanData;
035    import org.apache.geronimo.gbean.GBeanInfo;
036    import org.apache.geronimo.gbean.ReferencePatterns;
037    import org.apache.geronimo.kernel.Kernel;
038    import org.apache.geronimo.kernel.KernelFactory;
039    import org.apache.geronimo.kernel.KernelRegistry;
040    import org.apache.geronimo.kernel.Naming;
041    import org.apache.geronimo.kernel.config.ConfigurationData;
042    import org.apache.geronimo.kernel.config.ConfigurationManager;
043    import org.apache.geronimo.kernel.config.ConfigurationUtil;
044    import org.apache.geronimo.kernel.config.KernelConfigurationManager;
045    import org.apache.geronimo.kernel.config.LifecycleException;
046    import org.apache.geronimo.kernel.config.RecordingLifecycleMonitor;
047    import org.apache.geronimo.kernel.log.GeronimoLogging;
048    import org.apache.geronimo.kernel.management.State;
049    import org.apache.geronimo.kernel.repository.DefaultArtifactManager;
050    import org.apache.geronimo.system.configuration.RepositoryConfigurationStore;
051    import org.apache.geronimo.system.repository.Maven2Repository;
052    import org.apache.geronimo.system.resolver.ExplicitDefaultArtifactResolver;
053    import org.apache.maven.artifact.Artifact;
054    import org.codehaus.mojo.pluginsupport.util.ArtifactItem;
055    import org.codehaus.plexus.util.FileUtils;
056    
057    /**
058     * Build a Geronimo Configuration using the local Maven infrastructure.
059     *
060     * @goal package
061     * @requiresDependencyResolution runtime
062     *
063     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
064     */
065    public class PackageMojo
066        extends AbstractCarMojo
067    {
068    
069    
070        /**
071         * Directory containing the generated archive.
072         *
073         * @parameter expression="${project.build.directory}"
074         * @required
075         */
076        private File outputDirectory = null;
077    
078        /**
079         * The local Maven repository which will be used to pull artifacts into the Geronimo repository when packaging.
080         *
081         * @parameter expression="${settings.localRepository}"
082         * @required
083         * @readonly
084         */
085        private File repository = null;
086    
087        /**
088         * The Geronimo repository where modules will be packaged up from.
089         *
090         * @parameter expression="${project.build.directory}/repository"
091         * @required
092         */
093        private File targetRepository = null;
094    
095        /**
096         * The default deployer module to be used when no other deployer modules are configured.
097         *
098         * @parameter expression="org.apache.geronimo.framework/geronimo-gbean-deployer/${geronimoVersion}/car"
099         * @required
100         * @readonly
101         */
102        private String defaultDeploymentConfig = null;
103    
104        /**
105         * Ther deployer modules to be used when packaging.
106         *
107         * @parameter
108         */
109        private String[] deploymentConfigs;
110    
111        /**
112         * The name of the deployer which will be used to deploy the CAR.
113         *
114         * @parameter expression="org.apache.geronimo.framework/geronimo-gbean-deployer/${geronimoVersion}/car?j2eeType=Deployer,name=Deployer"
115         * @required
116         */
117        private String deployerName = null;
118    
119        /**
120         * The plan file for the CAR.
121         *
122         * @parameter expression="${project.build.directory}/resources/META-INF/plan.xml"
123         * @required
124         */
125        private File planFile = null;
126    
127        /**
128         * The file to include as a module of the CAR.
129         *
130         * @parameter
131         */
132        private File moduleFile = null;
133    
134        /**
135         * An {@link ArtifactItem} to include as a module of the CAR.
136         *
137         * @parameter
138         */
139        private ArtifactItem module = null;
140    
141        /**
142         * The location where the properties mapping will be generated.
143         *
144         * <p>
145         * Probably don't want to change this.
146         * </p>
147         *
148         * @parameter expression="${project.build.directory}/explicit-versions.properties"
149         */
150        private File explicitResolutionProperties = null;
151    
152        /**
153         * True to enable the bootshell when packaging.
154         *
155         * @parameter
156         */
157        private boolean bootstrap = false;
158    
159        /**
160         * Holds a local repo lookup instance so that we can use the current project to resolve.
161         * This is required since the Kernel used to deploy is cached.
162         */
163        private static ThreadLocal<Maven2RepositoryAdapter.ArtifactLookup> lookupHolder = new ThreadLocal<Maven2RepositoryAdapter.ArtifactLookup>();
164    
165        //
166        // Mojo
167        //
168    
169        protected void doExecute() throws Exception {
170            // We need to make sure to clean up any previous work first or this operation will fail
171            FileUtils.forceDelete(targetRepository);
172            FileUtils.forceMkdir(targetRepository);
173    
174            // Use the default configs if none specified
175            if (deploymentConfigs == null) {
176                if (!bootstrap) {
177                    deploymentConfigs = new String[] {defaultDeploymentConfig};
178                } else {
179                    deploymentConfigs = new String[] {};
180                }
181            }
182            log.debug("Deployment configs: " + Arrays.asList(deploymentConfigs));
183    
184            //
185            // NOTE: Resolve deployment modules, this is needed to ensure that the proper artifacts are in the
186            //       local repository to perform deployment.  If the deployer modules (or their dependencies)
187            //       are missing from the source respository, then strange packaging failures will occur.
188            //
189            Set<Artifact> additionalArtifacts = new HashSet<Artifact>();
190            for (String deploymentConfig : deploymentConfigs) {
191                Artifact artifact = geronimoToMavenArtifact(org.apache.geronimo.kernel.repository.Artifact.create(deploymentConfig));
192                log.debug("Resolving deployer module: " + artifact);
193                Artifact resolved = resolveArtifact(artifact, true);
194                additionalArtifacts.add(resolved);
195            }
196            //Ensure that these dependencies are available to geronimo
197            if (project.getDependencyArtifacts() == null) {
198                Set<Artifact> oldArtifacts = project.createArtifacts(dependencyHelper.getArtifactFactory(), null, null);
199                additionalArtifacts.addAll(oldArtifacts);
200            } else {
201                Set<Artifact> oldArtifacts = project.getDependencyArtifacts();
202                additionalArtifacts.addAll(oldArtifacts);
203            }
204            project.setDependencyArtifacts(additionalArtifacts);
205    
206    
207            // If module is set, then resolve the artifact and set moduleFile
208            if (module != null) {
209                Artifact artifact = getArtifact(module);
210                moduleFile = artifact.getFile();
211                log.debug("Using module file: " + moduleFile);
212            }
213    
214            getDependencies(project);
215    
216            generateExplicitVersionProperties(explicitResolutionProperties, dependencies);
217    
218            //
219            // NOTE: Install a local lookup, so that the cached kernel can resolve based on the current project
220            //       and not the project where the kernel was first initialized.
221            //
222            lookupHolder.set(new ArtifactLookupImpl(new HashMap()));
223    
224            if (bootstrap) {
225                executeBootShell();
226            }
227            else {
228                buildPackage();
229            }
230        }
231    
232        private File getArtifactInRepositoryDir() {
233            //
234            // HACK: Generate the filename in the repo... really should delegate this to the repo impl
235            //
236    
237            File dir = new File(targetRepository, project.getGroupId().replace('.', '/'));
238            dir = new File(dir, project.getArtifactId());
239            dir = new File(dir, project.getVersion());
240            dir = new File(dir, project.getArtifactId() + "-" + project.getVersion() + ".car");
241    
242            return dir;
243        }
244    
245        public void executeBootShell() throws Exception {
246            log.debug("Starting bootstrap shell...");
247    
248            PluginBootstrap2 boot = new PluginBootstrap2();
249    
250            boot.setBuildDir(outputDirectory);
251            boot.setCarFile(getArtifactInRepositoryDir());
252            boot.setLocalRepo(repository);
253            boot.setPlan(planFile);
254    
255            // Generate expanded so we can use Maven to generate the archive
256            boot.setExpanded(true);
257    
258            boot.bootstrap();
259        }
260    
261    
262        //
263        // Deployment
264        //
265    
266        private static final String KERNEL_NAME = "geronimo.maven";
267    
268        /**
269         * Reference to the kernel that will last the lifetime of this classloader.
270         * The KernelRegistry keeps soft references that may be garbage collected.
271         */
272        private Kernel kernel;
273    
274        private AbstractName targetConfigStoreAName;
275    
276        private AbstractName targetRepositoryAName;
277    
278        private boolean targetSet;
279    
280        public void buildPackage() throws Exception {
281            log.info("Packaging module configuration: " + planFile);
282    
283            Kernel kernel = createKernel();
284            if (!targetSet) {
285                kernel.stopGBean(targetRepositoryAName);
286                kernel.setAttribute(targetRepositoryAName, "root", targetRepository.toURI());
287                kernel.startGBean(targetRepositoryAName);
288    
289                if (kernel.getGBeanState(targetConfigStoreAName) != State.RUNNING_INDEX) {
290                    throw new IllegalStateException("After restarted repository then config store is not running");
291                }
292    
293                targetSet = true;
294            }
295    
296            log.debug("Starting configurations..." + Arrays.asList(deploymentConfigs));
297    
298            // start the Configuration we're going to use for this deployment
299            ConfigurationManager configurationManager = ConfigurationUtil.getConfigurationManager(kernel);
300            try {
301                for (String artifactName : deploymentConfigs) {
302                    org.apache.geronimo.kernel.repository.Artifact configName = org.apache.geronimo.kernel.repository.Artifact.create(artifactName);
303                    if (!configurationManager.isLoaded(configName)) {
304                        RecordingLifecycleMonitor monitor = new RecordingLifecycleMonitor();
305                        try {
306                            configurationManager.loadConfiguration(configName, monitor);
307                        } catch (LifecycleException e) {
308                            log.error("Could not load deployer configuration: " + configName + "\n" + monitor.toString(), e);
309                        }
310                        monitor = new RecordingLifecycleMonitor();
311                        try {
312                            configurationManager.startConfiguration(configName, monitor);
313                            log.info("Started deployer: " + configName);
314                        } catch (LifecycleException e) {
315                            log.error("Could not start deployer configuration: " + configName + "\n" + monitor.toString(), e);
316                        }
317                    }
318                }
319            } finally {
320                ConfigurationUtil.releaseConfigurationManager(kernel, configurationManager);
321            }
322    
323            log.debug("Deploying...");
324    
325            AbstractName deployer = locateDeployer(kernel);
326            invokeDeployer(kernel, deployer, targetConfigStoreAName.toString());
327            //use a fresh kernel for each module
328            kernel.shutdown();
329            kernel = null;
330        }
331    
332        /**
333         * Create a Geronimo Kernel to contain the deployment configurations.
334         */
335        private synchronized Kernel createKernel() throws Exception {
336            // first return our cached version
337            if (kernel != null) {
338                return kernel;
339            }
340    
341            log.debug("Creating kernel...");
342    
343            // check the registry in case someone else created one
344            kernel = KernelRegistry.getKernel(KERNEL_NAME);
345            if (kernel != null) {
346                return kernel;
347            }
348    
349            GeronimoLogging geronimoLogging = GeronimoLogging.getGeronimoLogging("WARN");
350            if (geronimoLogging == null) {
351                geronimoLogging = GeronimoLogging.DEBUG;
352            }
353            GeronimoLogging.initialize(geronimoLogging);
354    
355            // boot one ourselves
356            kernel = KernelFactory.newInstance().createKernel(KERNEL_NAME);
357            kernel.boot();
358    
359            bootDeployerSystem();
360    
361            return kernel;
362        }
363    
364        /**
365         * Boot the in-Maven deployment system.
366         *
367         * <p>
368         * This contains Repository and ConfigurationStore GBeans that map to
369         * the local maven installation.
370         * </p>
371         */
372        private void bootDeployerSystem() throws Exception {
373            log.debug("Booting deployer system...");
374    
375            org.apache.geronimo.kernel.repository.Artifact baseId =
376                    new org.apache.geronimo.kernel.repository.Artifact("geronimo", "packaging", "fixed", "car");
377            Naming naming = kernel.getNaming();
378            ConfigurationData bootstrap = new ConfigurationData(baseId, naming);
379            ClassLoader cl = getClass().getClassLoader();
380            Set<AbstractName> repoNames = new HashSet<AbstractName>();
381    
382            //
383            // NOTE: Install an adapter for the source repository that will leverage the Maven2 repository subsystem
384            //       to allow for better handling of SNAPSHOT values.
385            //
386            GBeanData repoGBean = bootstrap.addGBean("SourceRepository", GBeanInfo.getGBeanInfo(Maven2RepositoryAdapter.class.getName(), cl));
387            Maven2RepositoryAdapter.ArtifactLookup lookup = new Maven2RepositoryAdapter.ArtifactLookup() {
388                private Maven2RepositoryAdapter.ArtifactLookup getDelegate() {
389                    return lookupHolder.get();
390                }
391                
392                public File getBasedir() {
393                    return getDelegate().getBasedir();
394                }
395    
396                public File getLocation(final org.apache.geronimo.kernel.repository.Artifact artifact) {
397                    return getDelegate().getLocation(artifact);
398                }
399            };
400            repoGBean.setAttribute("lookup", lookup);
401            repoGBean.setAttribute("dependencies", dependencies);
402            repoNames.add(repoGBean.getAbstractName());
403    
404            // Target repo
405            GBeanData targetRepoGBean = bootstrap.addGBean("TargetRepository", GBeanInfo.getGBeanInfo(Maven2Repository.class.getName(), cl));
406            URI targetRepositoryURI = targetRepository.toURI();
407            targetRepoGBean.setAttribute("root", targetRepositoryURI);
408            repoNames.add(targetRepoGBean.getAbstractName());
409            targetRepositoryAName = targetRepoGBean.getAbstractName();
410    
411            GBeanData artifactManagerGBean = bootstrap.addGBean("ArtifactManager", DefaultArtifactManager.GBEAN_INFO);
412            GBeanData artifactResolverGBean = bootstrap.addGBean("ArtifactResolver", ExplicitDefaultArtifactResolver.GBEAN_INFO);
413            artifactResolverGBean.setAttribute("versionMapLocation", explicitResolutionProperties.getAbsolutePath());
414            ReferencePatterns repoPatterns = new ReferencePatterns(repoNames);
415            artifactResolverGBean.setReferencePatterns("Repositories", repoPatterns);
416            artifactResolverGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
417    
418            Set storeNames = new HashSet();
419    
420            // Source config store
421            GBeanInfo configStoreInfo = GBeanInfo.getGBeanInfo(MavenConfigStore.class.getName(), cl);
422            GBeanData storeGBean = bootstrap.addGBean("ConfigStore", configStoreInfo);
423            if (configStoreInfo.getReference("Repository") != null) {
424                storeGBean.setReferencePattern("Repository", repoGBean.getAbstractName());
425            }
426            storeNames.add(storeGBean.getAbstractName());
427    
428            // Target config store
429            GBeanInfo targetConfigStoreInfo = GBeanInfo.getGBeanInfo(RepositoryConfigurationStore.class.getName(), cl);
430            GBeanData targetStoreGBean = bootstrap.addGBean("TargetConfigStore", targetConfigStoreInfo);
431            if (targetConfigStoreInfo.getReference("Repository") != null) {
432                targetStoreGBean.setReferencePattern("Repository", targetRepoGBean.getAbstractName());
433            }
434            storeNames.add(targetStoreGBean.getAbstractName());
435    
436            targetConfigStoreAName = targetStoreGBean.getAbstractName();
437            targetSet = true;
438    
439            GBeanData attrManagerGBean = bootstrap.addGBean("AttributeStore", MavenAttributeStore.GBEAN_INFO);
440            GBeanData configManagerGBean = bootstrap.addGBean("ConfigManager", KernelConfigurationManager.GBEAN_INFO);
441            configManagerGBean.setReferencePatterns("Stores", new ReferencePatterns(storeNames));
442            configManagerGBean.setReferencePattern("AttributeStore", attrManagerGBean.getAbstractName());
443            configManagerGBean.setReferencePattern("ArtifactManager", artifactManagerGBean.getAbstractName());
444            configManagerGBean.setReferencePattern("ArtifactResolver", artifactResolverGBean.getAbstractName());
445            configManagerGBean.setReferencePatterns("Repositories", repoPatterns);
446    
447            ConfigurationUtil.loadBootstrapConfiguration(kernel, bootstrap, cl);
448        }
449    
450        /**
451         * Locate a Deployer GBean matching the deployerName pattern.
452         *
453         * @param kernel the kernel to search.
454         * @return the ObjectName of the Deployer GBean
455         *
456         * @throws IllegalStateException if there is not exactly one GBean matching the deployerName pattern
457         */
458        private AbstractName locateDeployer(final Kernel kernel) {
459            AbstractName name = new AbstractName(URI.create(deployerName));
460    
461            Iterator i = kernel.listGBeans(new AbstractNameQuery(name)).iterator();
462            if (!i.hasNext()) {
463                throw new IllegalStateException("No deployer found matching deployerName: " + name);
464            }
465    
466            AbstractName deployer = (AbstractName)i.next();
467            if (i.hasNext()) {
468                throw new IllegalStateException("Multiple deployers found matching deployerName: " + name);
469            }
470    
471            return deployer;
472        }
473    
474        private static final String[] DEPLOY_SIGNATURE = {
475            boolean.class.getName(),
476            File.class.getName(),
477            File.class.getName(),
478            File.class.getName(),
479            Boolean.TYPE.getName(),
480            String.class.getName(),
481            String.class.getName(),
482            String.class.getName(),
483            String.class.getName(),
484            String.class.getName(),
485            String.class.getName(),
486            String.class.getName(),
487            String.class.getName(),
488        };
489    
490        private List invokeDeployer(final Kernel kernel, final AbstractName deployer, final String targetConfigStore) throws Exception {
491            Object[] args = {
492                Boolean.FALSE, // Not in-place
493                moduleFile,
494                planFile,
495                null, // Target file
496                Boolean.TRUE, // Install
497                null, // main-class
498                null, // main-gbean
499                null, // main-method
500                null, // Manifest configurations
501                null, // class-path
502                null, // endorsed-dirs
503                null, // extension-dirs
504                targetConfigStore
505            };
506    
507            return (List) kernel.invoke(deployer, "deploy", args, DEPLOY_SIGNATURE);
508        }
509    
510    }