View Javadoc

1   /*
2    * Copyright (2007) Schibsted Søk AS
3    * This file is part of SESAT.
4    *
5    *   SESAT is free software: you can redistribute it and/or modify
6    *   it under the terms of the GNU Affero General Public License as published by
7    *   the Free Software Foundation, either version 3 of the License, or
8    *   (at your option) any later version.
9    *
10   *   SESAT is distributed in the hope that it will be useful,
11   *   but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *   GNU Affero General Public License for more details.
14   *
15   *   You should have received a copy of the GNU Affero General Public License
16   *   along with SESAT.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package no.sesat.mojo;
20  
21  import java.io.BufferedReader;
22  import java.io.BufferedWriter;
23  import java.io.File;
24  import java.io.FileReader;
25  import java.io.FileWriter;
26  import java.io.IOException;
27  import java.io.StringReader;
28  import java.io.StringWriter;
29  import java.text.SimpleDateFormat;
30  import java.util.ArrayList;
31  import java.util.Calendar;
32  import java.util.Date;
33  import java.util.List;
34  import org.apache.maven.artifact.Artifact;
35  import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
36  import org.apache.maven.artifact.resolver.ArtifactCollector;
37  import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
38  import org.apache.maven.artifact.resolver.ArtifactResolutionException;
39  import org.apache.maven.model.Profile;
40  import org.apache.maven.plugin.MojoExecutionException;
41  import org.apache.maven.plugin.dependency.fromConfiguration.CopyMojo;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.scm.ScmException;
44  import org.apache.maven.scm.ScmFileSet;
45  import org.apache.maven.scm.command.status.StatusScmResult;
46  import org.apache.maven.scm.command.tag.TagScmResult;
47  import org.apache.maven.scm.manager.ScmManager;
48  import org.apache.maven.wagon.ConnectionException;
49  import org.apache.maven.wagon.ResourceDoesNotExistException;
50  import org.apache.maven.wagon.TransferFailedException;
51  import org.apache.maven.wagon.Wagon;
52  import org.apache.maven.wagon.authentication.AuthenticationException;
53  import org.apache.maven.wagon.authorization.AuthorizationException;
54  import org.apache.maven.wagon.repository.Repository;
55  import org.apache.maven.wagon.repository.RepositoryPermissions;
56  import org.codehaus.plexus.PlexusConstants;
57  import org.codehaus.plexus.PlexusContainer;
58  import org.codehaus.plexus.archiver.manager.ArchiverManager;
59  import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
60  import org.codehaus.plexus.context.Context;
61  import org.codehaus.plexus.context.ContextException;
62  import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;
63  
64  /** Handles deployment of sesat and skins builds.
65   * Deployment differs greatly between the development and the other profiles.
66   * In development profile the behaviour just uses the superclass CopyMojo,
67   *  or the dependency:copy goal, with a precondition that the outputDirectory exists.
68   *  (avoids a "null" directory created).
69   * In the other profiles, the profile's classifier is used, the artifact downloaded from a remote serverDeployLocation
70   *  is neccessary, and then uploaded to the configured 'profile'DeployRepository which corresponds to the
71   *  the environments webapp directory.
72   *  Skins are expected to override these deployRepository settings.
73   *
74   * TODO separate mojos into local-deploy and server-deploy.
75   * TODO abstract override of parameters from CopyMojo.
76   *
77   * @goal deploy
78   * @author <a href="mailto:mick@semb.wever.org">Mick</a>
79   * @version $Id: DeploySesatWarfilesMojo.java 6380 2008-04-07 11:03:05Z sshafroi $
80   */
81  public final class DeploySesatWarfilesMojo extends CopyMojo implements Contextualizable{
82  
83      // Constants -----------------------------------------------------
84  
85      private static final String[] ENVIRONMENTS = new String[]{"alpha","nuclei","beta","electron","gamma","production"};
86  
87      private static final String TAG_ON_DEPLOY = "tag.on.deploy";
88  
89      private static final String DRY_RUN = "sesat.mojo.dryRun";
90  
91      // Attributes ----------------------------------------------------
92  
93      private PlexusContainer container;
94  
95      private String profile = null;
96      private Date now = Calendar.getInstance().getTime();
97      private MavenProject pomProject;
98  
99      // All of these attributes are just explicit overrides to get them into the mojo.
100     //  read http://www.mail-archive.com/dev@maven.apache.org/msg60770.html
101 
102     /**
103      * Strip artifact version during copy
104      *
105      * @parameter expression="${mdep.stripVersion}" default-value="false"
106      * @parameter
107      */
108     private boolean stripVersion = false;
109 
110     /**
111      * Default location used for mojo unless overridden in ArtifactItem
112      *
113      * @parameter expression="${outputDirectory}"
114      *            default-value="${project.build.directory}/dependency"
115      * @optional
116      * @since 1.0
117      */
118     private File outputDirectory;
119 
120     /**
121      * Overwrite release artifacts
122      *
123      * @optional
124      * @since 1.0
125      * @parameter expression="${mdep.overWriteReleases}" default-value="false"
126      */
127     private boolean overWriteReleases;
128 
129     /**
130      * Overwrite snapshot artifacts
131      *
132      * @optional
133      * @since 1.0
134      * @parameter expression="${mdep.overWriteSnapshots}" default-value="false"
135      */
136     private boolean overWriteSnapshots;
137 
138     /**
139      * Overwrite if newer
140      *
141      * @optional
142      * @since 2.0
143      * @parameter expression="${mdep.overIfNewer}" default-value="true"
144      */
145     private boolean overWriteIfNewer;
146 
147     /**
148      * Collection of ArtifactItems to work on. (ArtifactItem contains groupId,
149      * artifactId, version, type, classifier, location, destFile, markerFile and overwrite.)
150      * See "Usage" and "Javadoc" for details.
151      *
152      * @parameter
153      * @required
154      * @since 1.0
155      */
156     private ArrayList artifactItems;
157 
158     /**
159      * Used to look up Artifacts in the remote serverDeployLocation.
160      *
161      * @parameter expression="${component.org.apache.maven.artifact.factory.ArtifactFactory}"
162      * @required
163      * @readonly
164      */
165     private org.apache.maven.artifact.factory.ArtifactFactory factory;
166 
167     /**
168      * Used to look up Artifacts in the remote serverDeployLocation.
169      *
170      * @parameter expression="${component.org.apache.maven.artifact.resolver.ArtifactResolver}"
171      * @required
172      * @readonly
173      */
174     private org.apache.maven.artifact.resolver.ArtifactResolver resolver;
175 
176     /**
177      * Artifact collector, needed to resolve dependencies.
178      *
179      * @component role="org.apache.maven.artifact.resolver.ArtifactCollector"
180      * @required
181      * @readonly
182      */
183     private ArtifactCollector artifactCollector;
184 
185     /**
186      * @component role="org.apache.maven.artifact.metadata.ArtifactMetadataSource"
187      *            hint="maven"
188      * @required
189      * @readonly
190      */
191     private ArtifactMetadataSource artifactMetadataSource;
192 
193     /**
194      * Location of the local serverDeployLocation.
195      *
196      * @parameter expression="${localRepository}"
197      * @readonly
198      * @required
199      */
200     private org.apache.maven.artifact.repository.ArtifactRepository local;
201 
202     /**
203      * List of Remote Repositories used by the resolver
204      *
205      * @parameter expression="${project.remoteArtifactRepositories}"
206      * @readonly
207      * @required
208      */
209     private java.util.List remoteRepos;
210 
211     /**
212      * To look up Archiver/UnArchiver implementations
213      *
214      * @parameter expression="${component.org.codehaus.plexus.archiver.manager.ArchiverManager}"
215      * @required
216      * @readonly
217      */
218     private ArchiverManager archiverManager;
219 
220     /**
221      * POM
222      *
223      * @parameter expression="${project}"
224      * @readonly
225      * @required
226      */
227     private MavenProject project;
228 
229     /**
230      * Contains the full list of projects in the reactor.
231      *
232      * @parameter expression="${reactorProjects}"
233      * @required
234      * @readonly
235      */
236     private List reactorProjects;
237 
238     /**
239      * If the plugin should be silent.
240      *
241      * @optional
242      * @since 2.0
243      * @parameter expression="${silent}" default-value="false"
244      */
245     private boolean silent;
246 
247     /**
248      * Output absolute filename for resolved artifacts
249      *
250      * @optional
251      * @since 2.0
252      * @parameter expression="${outputAbsoluteArtifactFilename}"
253      *            default-value="false"
254      */
255     private boolean outputAbsoluteArtifactFilename;
256 
257     // Static --------------------------------------------------------
258 
259     // Constructors --------------------------------------------------
260 
261     /**
262      *
263      */
264     public DeploySesatWarfilesMojo() {
265     }
266 
267     // Public --------------------------------------------------------
268 
269     // Contextualizable implementation ----------------------------------------------
270 
271     /** {@inheritDoc}
272      */
273     public void contextualize(final Context context) throws ContextException {
274 
275         container = (PlexusContainer) context.get(PlexusConstants.PLEXUS_KEY);
276     }
277 
278     // CopyMojo overrides ---------------------------------------------------
279 
280     /** {@inheritDoc}
281      */
282     public void execute() throws MojoExecutionException{
283 
284         // only ever interested in war projects. silently ignore other projects.
285         if("war".equals(project.getPackaging())){
286 
287             pushFields();
288 
289             final Wagon wagon = getWagon();
290             if(null != wagon){
291 
292                 try{
293                     executeServerDeploy(wagon);
294 
295                 }finally{
296                     try{
297                        wagon.disconnect();
298 
299                     }catch(ConnectionException ex){
300                         getLog().error(ex);
301                         throw new MojoExecutionException("repository wagon not disconnected", ex);
302                     }
303                 }
304 
305             }else{
306 
307                 executeLocalDeploy();
308             }
309         }
310 
311     }
312 
313     // Package protected ---------------------------------------------
314 
315     // Protected -----------------------------------------------------
316 
317     // Private -------------------------------------------------------
318 
319     private void executeServerDeploy(final Wagon wagon) throws MojoExecutionException{
320 
321         // alpha|nuclei|beta|electron|gamma|production deployment goes through scpexe
322         try{
323             if(ensureNoLocalModifications()){
324 
325                 @SuppressWarnings("unchecked")
326                 final List<ArtifactItem> theArtifactItems = getProcessedArtifactItems(stripVersion);
327 
328                 for(ArtifactItem item : theArtifactItems){
329 
330                     final Artifact artifact = factory.createArtifactWithClassifier(
331                             item.getGroupId(),
332                             item.getArtifactId(),
333                             item.getVersion(),
334                             item.getType(),
335                             profile);
336 
337                     resolver.resolve(artifact, getRemoteRepos(), getLocal());
338 
339                     final String sesamSite = project.getProperties().getProperty("sesam.site");
340                     final String destName = null != sesamSite
341                             ? sesamSite
342                             : project.getBuild().getFinalName();
343 
344                     // tag the code
345                     if(Boolean.parseBoolean(project.getProperties().getProperty(TAG_ON_DEPLOY))
346                             && !Boolean.getBoolean(DRY_RUN)){
347 
348                         tagDeploy();
349                     }
350 
351                     // do the upload
352                     getLog().info("Uploading " + artifact.getFile().getAbsolutePath()
353                             + " to " + wagon.getRepository().getUrl() + '/' + destName + ".war");
354 
355                     if(!Boolean.getBoolean(DRY_RUN)){
356                         wagon.put(artifact.getFile(), destName + ".war");
357                     }
358 
359                     // update the version.txt
360                     getLog().info("Updating " + wagon.getRepository().getUrl() + "/version.txt");
361 
362                     if(Boolean.getBoolean(DRY_RUN)){
363 
364                         final StringWriter sb = new StringWriter();
365                         final BufferedWriter w = new BufferedWriter(sb);
366                         updateArtifactEntry(new BufferedReader(new StringReader("")), w);
367                         w.flush();
368                         getLog().info("version.txt entry will be \n" + sb.toString());
369 
370                     }else{
371 
372                         updateVersionFile(wagon);
373                     }
374                 }
375             }
376 
377         }catch(TransferFailedException ex){
378             getLog().error(ex);
379             throw new MojoExecutionException("transfer failed", ex);
380         }catch(ResourceDoesNotExistException ex){
381             getLog().error(ex);
382             throw new MojoExecutionException("resource does not exist", ex);
383         }catch(AuthorizationException ex){
384             getLog().error(ex);
385             throw new MojoExecutionException("authorisation exception", ex);
386         }catch(ArtifactNotFoundException ex){
387             getLog().error(ex);
388             throw new MojoExecutionException("artifact not found", ex);
389         }catch(ArtifactResolutionException ex){
390             getLog().error(ex);
391             throw new MojoExecutionException("artifact resolution exception", ex);
392         }catch(ScmException ex){
393             getLog().error(ex);
394             throw new MojoExecutionException("scm exception", ex);
395         }catch(ComponentLookupException ex){
396             getLog().error(ex);
397             throw new MojoExecutionException("failed to lookup ScmManager", ex);
398         }catch(IOException ex){
399             getLog().error(ex);
400             throw new MojoExecutionException("IOException", ex);
401         }
402 
403     }
404 
405     private void executeLocalDeploy() throws MojoExecutionException{
406 
407         // local-deploy behaviour comes from super implementation
408         // some pre-condition checks first
409 
410         // 1. the output directory must exist
411         if(getOutputDirectory().exists()){
412 
413             // 2. output directory is writable
414             if(getOutputDirectory().canWrite()){
415 
416                 super.execute();
417 
418             }else{
419 
420                 // 2.failure output directory isn't writable
421                 getLog().error(getOutputDirectory().getAbsolutePath() + " can not be written to.");
422             }
423         }else{
424 
425             // 1.failure: the output directory doesn't exist
426             getLog().error(getOutputDirectory().getAbsolutePath() + " does not exist.");
427             final String catalinaBase = System.getProperty("env.CATALINA_BASE");
428             if(null == catalinaBase || 0 == catalinaBase.length()){
429                 getLog().info("Define system variable CATALINA_BASE to enable automatic deployment.");
430             }
431         }
432 
433     }
434 
435     private void pushFields(){
436         setArchiverManager(archiverManager);
437         setArtifactCollector(artifactCollector);
438         setArtifactMetadataSource(artifactMetadataSource);
439         setFactory(factory);
440         setResolver(resolver);
441     }
442 
443     private void loadPomProject(){
444 
445         if(null == pomProject){
446             pomProject = project;
447             do{
448                 pomProject = pomProject.getParent();
449             }while(!"pom".equals(pomProject.getPackaging()));
450         }
451     }
452     /**
453      *
454      * @return the wagon (already connected) to use against the profile's serverDeployLocation
455      *              or null if in development profile
456      * @throws org.apache.maven.plugin.MojoExecutionException
457      */
458     private Wagon getWagon() throws MojoExecutionException {
459 
460         loadProfile();
461 
462         try {
463 
464             Wagon wagon = null;
465 
466             if(null != profile){
467 
468                 final String serverDeployLocation = project.getProperties().getProperty("serverDeployLocation");
469 
470                 final String protocol = serverDeployLocation.substring(0, serverDeployLocation.indexOf(':'));
471 
472                 final Repository wagonRepository = new Repository();
473 
474                 wagonRepository.setUrl(serverDeployLocation);
475 
476                 final RepositoryPermissions permissions = new RepositoryPermissions();
477                 permissions.setFileMode("g+w");
478                 wagonRepository.setPermissions(permissions);
479 
480                 wagon = (Wagon) container.lookup(Wagon.ROLE, protocol);
481                 wagon.connect(wagonRepository);
482 
483             }
484             return wagon;
485 
486         }catch (ConnectionException ex) {
487             getLog().error(ex);
488             throw new MojoExecutionException("repository wagon not connected", ex);
489         }catch (AuthenticationException ex) {
490             getLog().error(ex);
491             throw new MojoExecutionException("repository wagon not authenticated", ex);
492         }catch (ComponentLookupException ex) {
493             getLog().error(ex);
494             throw new MojoExecutionException("repository wagon not found", ex);
495         }
496 
497     }
498 
499     private void loadProfile(){
500 
501         if(null == profile){
502 
503             @SuppressWarnings("unchecked")
504             final List<Profile> profiles = project.getActiveProfiles();
505 
506             for(String entry : ENVIRONMENTS){
507                 for(Profile p : profiles){
508                     if(null != p && null != p.getId() && p.getId().equals(entry)){
509                         profile = p.getId();
510                         return;
511                     }
512                 }
513             }
514         }
515     }
516 
517     private void tagDeploy() throws ComponentLookupException, ScmException{
518 
519         final ScmManager scmManager = (ScmManager) container.lookup(ScmManager.ROLE);
520         final String date = new SimpleDateFormat("yyyyMMddHHmm").format(now);
521 
522         loadPomProject();
523 
524         final TagScmResult result = scmManager.tag(
525                 scmManager.makeScmRepository(project.getScm().getDeveloperConnection()),
526                 new ScmFileSet(pomProject.getBasedir()), // TODO server-side copy
527                 profile + "-deployments/" + date + "-" + project.getArtifactId(),
528                 "sesat " + profile + " deployment");
529 
530         if(!result.isSuccess()){
531             throw new ScmException(result.getCommandOutput());
532         }
533     }
534 
535     private boolean ensureNoLocalModifications() throws ComponentLookupException, ScmException, MojoExecutionException{
536 
537         if(!Boolean.getBoolean("sesat.mojo.localModifications.ignore")){
538 
539             final ScmManager scmManager = (ScmManager) container.lookup(ScmManager.ROLE);
540 
541             loadPomProject();
542 
543             final StatusScmResult result = scmManager.status(
544                     scmManager.makeScmRepository(project.getScm().getDeveloperConnection()),
545                     new ScmFileSet(pomProject.getBasedir()));
546 
547 
548             if(!result.isSuccess()){
549 
550                 getLog().error(result.getCommandOutput());
551                 throw new MojoExecutionException("Failed to ensure checkout has no modifications");
552             }
553 
554             if(0 < result.getChangedFiles().size()){
555 
556                 throw new MojoExecutionException("Your checkout has local modifications. "
557                         + "Server deploy can *only* be done with a clean workbench.");
558             }
559 
560             return result.isSuccess() && 0 == result.getChangedFiles().size();
561         }
562         return true; // sesat.mojo.localModifications.ignore
563     }
564 
565     private void updateVersionFile(final Wagon wagon)
566             throws IOException, TransferFailedException, ResourceDoesNotExistException, AuthorizationException{
567 
568         // update the version.txt accordingly
569         final File versionOldFile = File.createTempFile("version-old", "txt");
570         final File versionNewFile = File.createTempFile("version-new", "txt");
571         wagon.get("version.txt", versionOldFile);
572 
573         boolean updated = false;
574 
575         // look for our artifactId entry
576         final BufferedReader reader = new BufferedReader(new FileReader(versionOldFile));
577         final BufferedWriter writer = new  BufferedWriter(new FileWriter(versionNewFile));
578         for(String line = reader.readLine(); null != line; line = reader.readLine()){
579             if(line.equals(project.getArtifactId())){
580                 updateArtifactEntry(reader, writer);
581                 updated = true;
582             }else{
583                 // line remains the same
584                 writer.write(line);
585                 writer.newLine();
586             }
587         }
588         if(!updated){
589             writer.newLine();
590             updateArtifactEntry(reader, writer);
591         }
592         reader.close();
593         writer.close();
594         wagon.put(versionNewFile, "version.txt");
595     }
596 
597     private void updateArtifactEntry(final BufferedReader reader, final BufferedWriter writer) throws IOException{
598 
599         // this line remains the same
600         writer.write(project.getArtifactId());
601         writer.newLine();
602         // next line is version, author (or "deployer"), date & time,
603         reader.readLine();
604         writer.write(project.getVersion()
605                 + " was last deployed by " + System.getProperty("user.name")
606                 + " at " + SimpleDateFormat.getDateTimeInstance().format(now));
607         writer.newLine();
608         // next line is the quote
609         for(String line = reader.readLine(); null != line && 0 < line.length(); line = reader.readLine()){};
610         final String quote = project.getProperties().getProperty("version.quote");
611         if(null != quote){
612             writer.write(quote);
613             writer.newLine();
614             writer.newLine();
615         }
616 
617     }
618 
619     // Inner classes -------------------------------------------------
620 }