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