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 }