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, final Properties archetypeProperties) throws MojoExecutionException {
139        final Invoker invoker = new DefaultInvoker().setMavenHome(mavenHome);
140        
141        final String additionalArguments = "";
142
143        final InvocationRequest req = new DefaultInvocationRequest()
144                .setInteractive(false)
145                .setProperties(archetypeProperties);
146                    
147        try {
148            setupRequest(req, additionalArguments);
149
150            req.setGoals(new ArrayList<String>() {{ add("archetype:generate"); }});
151
152            try {
153                final InvocationResult invocationResult = invoker.execute(req);
154
155                if ( invocationResult.getExecutionException() != null ) {
156                    throw new MojoExecutionException("Error executing Maven.",
157                                                     invocationResult.getExecutionException());
158                }
159                    
160                if (invocationResult.getExitCode() != 0) {
161                    throw new MojoExecutionException(
162                        "Maven execution failed, exit code: \'" + invocationResult.getExitCode() + "\'");
163                }
164            }
165            catch (MavenInvocationException e) {
166                throw new MojoExecutionException( "Failed to invoke Maven build.", e );
167            }
168        }
169        finally {
170            /*
171            if ( settingsFile != null && settingsFile.exists() && !settingsFile.delete() )
172            {
173                settingsFile.deleteOnExit();
174            }
175            */
176        }
177    }
178 
179    /**
180     * 
181     */
182    private void setupRequest(final InvocationRequest req,
183                              final String additionalArguments) throws MojoExecutionException {
184        try {
185            final String[] args = CommandLineUtils.translateCommandline(additionalArguments);
186            CommandLine cli = new PosixParser().parse(OPTIONS, args);
187
188            if (cli.hasOption( SET_SYSTEM_PROPERTY))
189            {
190                String[] properties = cli.getOptionValues( SET_SYSTEM_PROPERTY );
191                Properties props = new Properties();
192                for ( int i = 0; i < properties.length; i++ )
193                {
194                    String property = properties[i];
195                    String name, value;
196                    int sep = property.indexOf( "=" );
197                    if ( sep <= 0 )
198                    {
199                        name = property.trim();
200                        value = "true";
201                    }
202                    else
203                    {
204                        name = property.substring( 0, sep ).trim();
205                        value = property.substring( sep + 1 ).trim();
206                    }
207                    props.setProperty( name, value );
208                }
209
210                req.setProperties( props );
211            }
212
213            if ( cli.hasOption( OFFLINE ) )
214            {
215                req.setOffline( true );
216            }
217
218            if ( cli.hasOption( QUIET ) )
219            {
220                // TODO: setQuiet() currently not supported by InvocationRequest
221                req.setDebug( false );
222            }
223            else if ( cli.hasOption( DEBUG ) )
224            {
225                req.setDebug( true );
226            }
227            else if ( cli.hasOption( ERRORS ) )
228            {
229                req.setShowErrors( true );
230            }
231
232            if ( cli.hasOption( REACTOR ) )
233            {
234                req.setRecursive( true );
235            }
236            else if ( cli.hasOption( NON_RECURSIVE ) )
237            {
238                req.setRecursive( false );
239            }
240
241            if ( cli.hasOption( UPDATE_SNAPSHOTS ) )
242            {
243                req.setUpdateSnapshots( true );
244            }
245
246            if ( cli.hasOption( ACTIVATE_PROFILES ) )
247            {
248                String[] profiles = cli.getOptionValues( ACTIVATE_PROFILES );
249                List<String> activatedProfiles = new ArrayList<String>();
250                List<String> deactivatedProfiles = new ArrayList<String>();
251
252                if ( profiles != null )
253                {
254                    for ( int i = 0; i < profiles.length; ++i )
255                    {
256                        StringTokenizer profileTokens = new StringTokenizer( profiles[i], "," );
257
258                        while ( profileTokens.hasMoreTokens() )
259                        {
260                            String profileAction = profileTokens.nextToken().trim();
261
262                            if ( profileAction.startsWith( "-" ) || profileAction.startsWith( "!" ) )
263                            {
264                                deactivatedProfiles.add( profileAction.substring( 1 ) );
265                            }
266                            else if ( profileAction.startsWith( "+" ) )
267                            {
268                                activatedProfiles.add( profileAction.substring( 1 ) );
269                            }
270                            else
271                            {
272                                activatedProfiles.add( profileAction );
273                            }
274                        }
275                    }
276                }
277
278                if ( !deactivatedProfiles.isEmpty() )
279                {
280                    getCaller().getLog().warn( "Explicit profile deactivation is not yet supported. "
281                                          + "The following profiles will NOT be deactivated: " + StringUtils.join(
282                        deactivatedProfiles.iterator(), ", " ) );
283                }
284
285                if ( !activatedProfiles.isEmpty() )
286                {
287                    req.setProfiles( activatedProfiles );
288                }
289            }
290
291            if ( cli.hasOption( FORCE_PLUGIN_UPDATES ) || cli.hasOption( FORCE_PLUGIN_UPDATES2 ) )
292            {
293                getCaller().getLog().warn( "Forcing plugin updates is not supported currently." );
294            }
295            else if ( cli.hasOption( SUPPRESS_PLUGIN_UPDATES ) )
296            {
297                req.setNonPluginUpdates( true );
298            }
299
300            if ( cli.hasOption( SUPPRESS_PLUGIN_REGISTRY ) )
301            {
302                getCaller().getLog().warn( "Explicit suppression of the plugin registry is not supported currently." );
303            }
304
305            if ( cli.hasOption( CHECKSUM_FAILURE_POLICY ) )
306            {
307                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_FAIL );
308            }
309            else if ( cli.hasOption( CHECKSUM_WARNING_POLICY ) )
310            {
311                req.setGlobalChecksumPolicy( InvocationRequest.CHECKSUM_POLICY_WARN );
312            }
313
314            if ( cli.hasOption( ALTERNATE_USER_SETTINGS ) )
315            {
316                req.setUserSettingsFile( new File( cli.getOptionValue( ALTERNATE_USER_SETTINGS ) ) );
317            }
318
319            if ( cli.hasOption( FAIL_AT_END ) )
320            {
321                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_AT_END );
322            }
323            else if ( cli.hasOption( FAIL_FAST ) )
324            {
325                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_FAST );
326            }
327            if ( cli.hasOption( FAIL_NEVER ) )
328            {
329                req.setFailureBehavior( InvocationRequest.REACTOR_FAIL_NEVER );
330            }
331            if ( cli.hasOption( ALTERNATE_POM_FILE ) )
332            {
333                if ( req.getPomFileName() != null )
334                {
335                    getCaller().getLog().info( "pomFileName is already set, ignoring the -f argument" );
336                }
337                else
338                {
339                    req.setPomFileName( cli.getOptionValue( ALTERNATE_POM_FILE ) );
340                }
341            }
342        }
343        catch ( Exception e )
344        {
345            throw new MojoExecutionException("Failed to re-parse additional arguments for Maven invocation.", e );
346        }
347    }
348    
349    public void setCaller(final AbstractMojo caller) {
350        this.caller = caller;
351    }
352    
353    public AbstractMojo getCaller() {
354        return this.caller;
355    }
356}