001/*
002 * Copyright 2007 The Kuali Foundation
003 * 
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * 
008 * http://www.opensource.org/licenses/ecl2.php
009 * 
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kualigan.maven.plugins.api;
017
018import org.apache.commons.cli.CommandLine;
019import org.apache.commons.cli.OptionBuilder;
020import org.apache.commons.cli.Options;
021import org.apache.commons.cli.PosixParser;
022
023import org.apache.maven.archetype.Archetype;
024import org.apache.maven.artifact.repository.ArtifactRepository;
025import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
026import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
027import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
028
029import org.apache.maven.plugin.AbstractMojo;
030import org.apache.maven.plugin.MojoExecutionException;
031
032import org.apache.maven.shared.invoker.DefaultInvocationRequest;
033import org.apache.maven.shared.invoker.DefaultInvoker;
034import org.apache.maven.shared.invoker.InvocationOutputHandler;
035import org.apache.maven.shared.invoker.InvocationRequest;
036import org.apache.maven.shared.invoker.InvocationResult;
037import org.apache.maven.shared.invoker.Invoker;
038import org.apache.maven.shared.invoker.InvokerLogger;
039import org.apache.maven.shared.invoker.MavenInvocationException;
040
041import org.apache.maven.project.MavenProject;
042import org.codehaus.plexus.component.annotations.Component;
043import org.codehaus.plexus.component.annotations.Requirement;
044import org.codehaus.plexus.components.interactivity.Prompter;
045import org.codehaus.plexus.util.FileUtils;
046import org.codehaus.plexus.util.IOUtil;
047import org.codehaus.plexus.util.StringUtils;
048import org.codehaus.plexus.util.cli.CommandLineUtils;
049
050import java.io.File;
051import java.io.FileWriter;
052import java.io.InputStream;
053import java.util.ArrayList;
054import java.util.HashMap;
055import java.util.List;
056import java.util.Map;
057import java.util.Properties;
058import java.util.StringTokenizer;
059
060/**
061 * Creates a maven overlay for the given KFS prototype
062 * 
063 * @author Leo Przybylski (przybyls [at] arizona.edu)
064 */
065@Component(role = org.kualigan.maven.plugins.api.OverlayHelper.class, hint="default")
066public class DefaultOverlayHelper implements OverlayHelper {
067    public static final String ROLE_HINT = "default";
068
069    private static final Options OPTIONS = new Options();
070
071    private static final char SET_SYSTEM_PROPERTY = 'D';
072
073    private static final char OFFLINE = 'o';
074
075    private static final char REACTOR = 'r';
076
077    private static final char QUIET = 'q';
078
079    private static final char DEBUG = 'X';
080
081    private static final char ERRORS = 'e';
082
083    private static final char NON_RECURSIVE = 'N';
084
085    private static final char UPDATE_SNAPSHOTS = 'U';
086
087    private static final char ACTIVATE_PROFILES = 'P';
088
089    private static final String FORCE_PLUGIN_UPDATES = "cpu";
090
091    private static final String FORCE_PLUGIN_UPDATES2 = "up";
092
093    private static final String SUPPRESS_PLUGIN_UPDATES = "npu";
094
095    private static final String SUPPRESS_PLUGIN_REGISTRY = "npr";
096
097    private static final char CHECKSUM_FAILURE_POLICY = 'C';
098
099    private static final char CHECKSUM_WARNING_POLICY = 'c';
100
101    private static final char ALTERNATE_USER_SETTINGS = 's';
102
103    private static final String FAIL_FAST = "ff";
104
105    private static final String FAIL_AT_END = "fae";
106
107    private static final String FAIL_NEVER = "fn";
108    
109    private static final String ALTERNATE_POM_FILE = "f";
110
111    /**
112     */
113    @Requirement
114    private Archetype archetype;
115
116    /**
117     */
118    @Requirement
119    private Prompter prompter;
120
121    /**
122     */
123    @Requirement
124    private ArtifactRepositoryFactory artifactRepositoryFactory;
125
126    /**
127     */
128    @Requirement(role = org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout.class,
129                hint="default")
130    private ArtifactRepositoryLayout defaultArtifactRepositoryLayout;
131    
132    private AbstractMojo caller;
133    
134    /**
135     * Invokes the maven goal {@code archetype:generate} with the appropriate properties.
136     * 
137     */
138    public void generateArchetype(final File mavenHome,
139                                  final String archetypeGroupId,
140                                  final String archetypeArtifactId,
141                                  final String archetypeVersion,
142                                  final String groupId,
143                                  final String artifactId,
144                                  final String version,
145                                  final String prototypeGroupId,
146                                  final String prototypeArtifactId,
147                                  final String prototypeVersion) throws MojoExecutionException {
148        final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
149        
150        final String additionalArguments = "";
151
152        final InvocationRequest req = new DefaultInvocationRequest()
153                .setInteractive(false)
154                .setProperties(new Properties() {{
155                        setProperty("archetypeGroupId", archetypeGroupId);
156                        setProperty("archetypeArtifactId", archetypeArtifactId);
157                        setProperty("archetypeVersion", archetypeVersion);
158                        setProperty("groupId", groupId);
159                        setProperty("artifactId", artifactId);
160                        setProperty("version", version);
161                        setProperty("kfsPrototypeGroupId", prototypeGroupId);
162                        setProperty("kfsPrototypeArtifactId", prototypeArtifactId);
163                        setProperty("kfsPrototypeVersion", prototypeVersion);
164                    }});
165                    
166        try {
167            setupRequest(req, additionalArguments);
168
169            req.setGoals(new ArrayList<String>() {{ add("archetype:generate"); }});
170
171            try {
172                final InvocationResult invocationResult = invoker.execute(req);
173
174                if ( invocationResult.getExecutionException() != null ) {
175                    throw new MojoExecutionException("Error executing Maven.",
176                                                     invocationResult.getExecutionException());
177                }
178                    
179                if (invocationResult.getExitCode() != 0) {
180                    throw new MojoExecutionException(
181                        "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
182                }
183            }
184            catch (MavenInvocationException e) {
185                throw new MojoExecutionException( "Failed to invoke Maven build.", e );
186            }
187        }
188        finally {
189            /*
190            if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
191            {
192                settingsFile.deleteOnExit();
193            }
194            */
195        }
196    }
197 
198     /**
199     * 
200     */
201    private void setupRequest(final InvocationRequest req,
202                              final String additionalArguments) throws MojoExecutionException {
203        try {
204            final String[] args = CommandLineUtils.translateCommandline(additionalArguments);
205            CommandLine cli = new PosixParser().parse(OPTIONS, args);
206
207            if (cli.hasOption( SET_SYSTEM_PROPERTY))
208            {
209                String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY );
210                Properties props = new Properties();
211                for ( int i = 0; i < properties.length; i++ )
212                {
213                    String property = properties[i];
214                    String name, value;
215                    int sep = property.indexOf( "=" );
216                    if ( sep <= 0 )
217                    {
218                        name = property.trim();
219                        value = "true";
220                    }
221                    else
222                    {
223                        name = property.substring( 0, sep ).trim();
224                        value = property.substring( sep + 1 ).trim();
225                    }
226                    props.setProperty( name, value );
227                }
228
229                req.setProperties( props );
230            }
231
232            if ( cli.hasOption( OFFLINE ) )
233            {
234                req.setOffline( true );
235            }
236
237            if ( cli.hasOption( QUIET ) )
238            {
239                // TODO: setQuiet() currently not supported by InvocationRequest
240                req.setDebug( false );
241            }
242            else if ( cli.hasOption( DEBUG ) )
243            {
244                req.setDebug( true );
245            }
246            else if ( cli.hasOption( ERRORS ) )
247            {
248                req.setShowErrors( true );
249            }
250
251            if ( cli.hasOption( REACTOR ) )
252            {
253                req.setRecursive( true );
254            }
255            else if ( cli.hasOption( NON_RECURSIVE ) )
256            {
257                req.setRecursive( false );
258            }
259
260            if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
261            {
262                req.setUpdateSnapshots( true );
263            }
264
265            if ( cli.hasOption( ACTIVATE_PROFILES ) )
266            {
267                String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
268                List<String> activatedProfiles = new ArrayList<String>();
269                List<String> deactivatedProfiles = new ArrayList<String>();
270
271                if ( profiles != null )
272                {
273                    for ( int i = 0; i < profiles.length; ++i )
274                    {
275                        StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," );
276
277                        while ( profileTokens.hasMoreTokens() )
278                        {
279                            String profileAction = profileTokens.nextToken().trim();
280
281                            if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
282                            {
283                                deactivatedProfiles.add( profileAction.substring( 1 ) );
284                            }
285                            else if ( profileAction.startsWith( "+" ) )
286                            {
287                                activatedProfiles.add( profileAction.substring( 1 ) );
288                            }
289                            else
290                            {
291                                activatedProfiles.add( profileAction );
292                            }
293                        }
294                    }
295                }
296
297                if ( !deactivatedProfiles.isEmpty() )
298                {
299                    getCaller().getLog().warn( "Explicit profile deactivation is not yet supported. "
300                                          + "The following profiles will NOT be deactivated: " + StringUtils.join(
301                        deactivatedProfiles.iterator(), ", " ) );
302                }
303
304                if ( !activatedProfiles.isEmpty() )
305                {
306                    req.setProfiles( activatedProfiles );
307                }
308            }
309
310            if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
311            {
312                getCaller().getLog().warn( "Forcing plugin updates is not supported currently." );
313            }
314            else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
315            {
316                req.setNonPluginUpdates( true );
317            }
318
319            if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
320            {
321                getCaller().getLog().warn( "Explicit suppression of the plugin registry is not supported currently." );
322            }
323
324            if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
325            {
326                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
327            }
328            else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
329            {
330                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
331            }
332
333            if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
334            {
335                req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
336            }
337
338            if ( cli.hasOption( FAIL_AT_END ) )
339            {
340                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
341            }
342            else if ( cli.hasOption( FAIL_FAST ) )
343            {
344                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
345            }
346            if ( cli.hasOption( FAIL_NEVER ) )
347            {
348                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
349            }
350            if ( cli.hasOption( ALTERNATE_POM_FILE ) )
351            {
352                if ( req.getPomFileName() != null )
353                {
354                    getCaller().getLog().info( "pomFileName is already set, ignoring the -f argument" );
355                }
356                else
357                {
358                    req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) );
359                }
360            }
361        }
362        catch ( Exception e )
363        {
364            throw new MojoExecutionException("Failed to re-parse additional arguments for Maven invocation.", e );
365        }
366    }
367    
368    public void setCaller(final AbstractMojo caller) {
369        this.caller = caller;
370    }
371    
372    public AbstractMojo getCaller() {
373        return this.caller;
374    }
375}