001package org.apache.maven.plugins.enforcer;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.util.Iterator;
023
024import org.apache.maven.enforcer.rule.api.EnforcerRule;
025import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
026import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper;
027import org.apache.maven.model.Activation;
028import org.apache.maven.model.ActivationOS;
029import org.apache.maven.model.Profile;
030import org.apache.maven.plugin.logging.Log;
031import org.apache.maven.model.profile.activation.ProfileActivator;
032import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
033import org.codehaus.plexus.util.Os;
034import org.codehaus.plexus.util.StringUtils;
035
036/**
037 * This rule checks that the OS is allowed by combinations of family, name, version and cpu architecture. The behavior
038 * is exactly the same as the Maven Os profile activation so the same values are allowed here.
039 *
040 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
041 * @version $Id$
042 */
043public class RequireOS
044    extends AbstractStandardEnforcerRule
045{
046    private ProfileActivator activator;
047
048    /**
049     * The OS family type desired<br />
050     * Possible values:
051     * <ul>
052     * <li>dos</li>
053     * <li>mac</li>
054     * <li>netware</li>
055     * <li>os/2</li>
056     * <li>tandem</li>
057     * <li>unix</li>
058     * <li>windows</li>
059     * <li>win9x</li>
060     * <li>z/os</li>
061     * <li>os/400</li>
062     * </ul>
063     * 
064     * @see {@link #setFamily(String)}
065     * @see {@link #getFamily()}
066     */
067    private String family = null;
068
069    /**
070     * The OS name desired.
071     *
072     * @see {@link #setName(String)}
073     * @see {@link #getName()}
074     */
075    private String name = null;
076
077    /**
078     * The OS version desired.
079     * 
080     * @see {@link #setVersion(String)}
081     * @see {@link #getVersion()}
082     */
083    private String version = null;
084
085    /**
086     * The OS architecture desired.
087     * 
088     * @see {@link #setArch(String)}
089     * @see {@link #getArch()}
090     */
091    private String arch = null;
092
093    /**
094     * Display detected OS information.
095     * 
096     * @see {@link #setDisplay(boolean)}
097     * @see {@link #isDisplay()}
098     */
099    private boolean display = false;
100
101    /**
102     * Instantiates a new RequireOS.
103     */
104    public RequireOS()
105    {
106
107    }
108    
109    // For testing
110    RequireOS( ProfileActivator activator ) 
111    {
112        this.activator = activator;
113    }
114    
115
116    @Override
117    public void execute( EnforcerRuleHelper helper )
118        throws EnforcerRuleException
119    {
120
121        displayOSInfo( helper.getLog(), display );
122
123        if ( allParamsEmpty() )
124        {
125            throw new EnforcerRuleException( "All parameters can not be empty. "
126                + "You must pick at least one of (family, name, version, arch) "
127                + "or use -Denforcer.os.display=true to see the current OS information." );
128        }
129        
130        try
131        {
132            activator = helper.getComponent( ProfileActivator.class, "os" );
133        }
134        catch ( ComponentLookupException e )
135        {
136            throw new EnforcerRuleException( e.getMessage() );
137        }
138
139        if ( isValidFamily( this.family ) )
140        {
141            if ( !isAllowed() )
142            {
143                String message = getMessage();
144                if ( StringUtils.isEmpty( message ) )
145                {
146                    //@formatter:off
147                    message =
148                        ( "OS Arch: " 
149                            + Os.OS_ARCH + " Family: " 
150                            + Os.OS_FAMILY + " Name: " 
151                            + Os.OS_NAME + " Version: "
152                            + Os.OS_VERSION + " is not allowed by" + ( arch != null ? " Arch=" + arch : "" )
153                            + ( family != null ? " Family=" + family : "" ) 
154                            + ( name != null ? " Name=" + name : "" ) 
155                            + ( version != null ? " Version=" + version : "" ) );
156                    //@formatter:on
157                }
158                throw new EnforcerRuleException( message );
159            }
160        }
161        else
162        {
163            final int minimumBufferSize = 50;
164            StringBuilder buffer = new StringBuilder( minimumBufferSize );
165            Iterator<?> iter = Os.getValidFamilies().iterator();
166            while ( iter.hasNext() )
167            {
168                buffer.append( iter.next() );
169                buffer.append( ", " );
170            }
171            String help = StringUtils.stripEnd( buffer.toString().trim(), "." );
172            throw new EnforcerRuleException( "Invalid Family type used. Valid family types are: " + help );
173        }
174    }
175
176    /**
177     * Log the current OS information.
178     *
179     * @param log the log
180     * @param info the info
181     */
182    public void displayOSInfo( Log log, boolean info )
183    {
184        String string =
185            "OS Info: Arch: " + Os.OS_ARCH + " Family: " + Os.OS_FAMILY + " Name: " + Os.OS_NAME + " Version: "
186                + Os.OS_VERSION;
187
188        if ( !info )
189        {
190            log.debug( string );
191        }
192        else
193        {
194            log.info( string );
195        }
196    }
197
198    /**
199     * Helper method to determine if the current OS is allowed based on the injected values for family, name, version
200     * and arch.
201     *
202     * @return true if the version is allowed.
203     */
204    public boolean isAllowed()
205    {
206        return activator.isActive( createProfile(), null, null );
207    }
208
209    /**
210     * Helper method to check that at least one of family, name, version or arch is set.
211     *
212     * @return true if all parameters are empty.
213     */
214    public boolean allParamsEmpty()
215    {
216        // CHECKSTYLE_OFF: LineLength
217        return ( StringUtils.isEmpty( family ) && StringUtils.isEmpty( arch ) && StringUtils.isEmpty( name ) && StringUtils.isEmpty( version ) );
218        // CHECKSTYLE_ON: LineLength
219    }
220
221    /**
222     * Creates a Profile object that contains the activation information.
223     *
224     * @return a properly populated profile to be used for OS validation.
225     */
226    private Profile createProfile()
227    {
228        Profile profile = new Profile();
229        profile.setActivation( createActivation() );
230        return profile;
231    }
232
233    /**
234     * Creates an Activation object that contains the ActivationOS information.
235     *
236     * @return a properly populated Activation object.
237     */
238    private Activation createActivation()
239    {
240        Activation activation = new Activation();
241        activation.setActiveByDefault( false );
242        activation.setOs( createOsBean() );
243        return activation;
244    }
245
246    /**
247     * Creates an ActivationOS object containing family, name, version and arch.
248     *
249     * @return a properly populated ActivationOS object.
250     */
251    private ActivationOS createOsBean()
252    {
253        ActivationOS os = new ActivationOS();
254
255        os.setArch( arch );
256        os.setFamily( family );
257        os.setName( name );
258        os.setVersion( version );
259
260        return os;
261    }
262
263    /**
264     * Helper method to check if the given family is in the following list:
265     * <ul>
266     * <li>dos</li>
267     * <li>mac</li>
268     * <li>netware</li>
269     * <li>os/2</li>
270     * <li>tandem</li>
271     * <li>unix</li>
272     * <li>windows</li>
273     * <li>win9x</li>
274     * <li>z/os</li>
275     * <li>os/400</li>
276     * </ul>
277     * Note: '!' is allowed at the beginning of the string and still considered valid.
278     *
279     * @param theFamily the family to check.
280     * @return true if one of the valid families.
281     */
282    public boolean isValidFamily( String theFamily )
283    {
284
285        // in case they are checking !family
286        theFamily = StringUtils.stripStart( theFamily, "!" );
287
288        return ( StringUtils.isEmpty( theFamily ) || Os.getValidFamilies().contains( theFamily ) );
289    }
290
291    /**
292     * Gets the arch.
293     *
294     * @return the arch
295     */
296    public String getArch()
297    {
298        return this.arch;
299    }
300
301    /**
302     * Sets the arch.
303     *
304     * @param theArch the arch to set
305     */
306    public void setArch( String theArch )
307    {
308        this.arch = theArch;
309    }
310
311    /**
312     * Gets the family.
313     *
314     * @return the family
315     */
316    public String getFamily()
317    {
318        return this.family;
319    }
320
321    /**
322     * Sets the family.
323     *
324     * @param theFamily the family to set
325     */
326    public void setFamily( String theFamily )
327    {
328        this.family = theFamily;
329    }
330
331    /**
332     * Gets the name.
333     *
334     * @return the name
335     */
336    public String getName()
337    {
338        return this.name;
339    }
340
341    /**
342     * Sets the name.
343     *
344     * @param theName the name to set
345     */
346    public void setName( String theName )
347    {
348        this.name = theName;
349    }
350
351    /**
352     * Gets the version.
353     *
354     * @return the version
355     */
356    public String getVersion()
357    {
358        return this.version;
359    }
360
361    /**
362     * Sets the version.
363     *
364     * @param theVersion the version to set
365     */
366    public void setVersion( String theVersion )
367    {
368        this.version = theVersion;
369    }
370
371    /**
372     * @param display The value for the display.
373     */
374    public final void setDisplay( boolean display )
375    {
376        this.display = display;
377    }
378
379    public final boolean isDisplay()
380    {
381        return display;
382    }
383
384    @Override
385    public String getCacheId()
386    {
387        // return the hashcodes of all the parameters
388        StringBuffer b = new StringBuffer();
389        if ( StringUtils.isNotEmpty( version ) )
390        {
391            b.append( version.hashCode() );
392        }
393        if ( StringUtils.isNotEmpty( name ) )
394        {
395            b.append( name.hashCode() );
396        }
397        if ( StringUtils.isNotEmpty( arch ) )
398        {
399            b.append( arch.hashCode() );
400        }
401        if ( StringUtils.isNotEmpty( family ) )
402        {
403            b.append( family.hashCode() );
404        }
405        return b.toString();
406    }
407
408    @Override
409    public boolean isCacheable()
410    {
411        // the os is not going to change between projects in the same build.
412        return true;
413    }
414
415    @Override
416    public boolean isResultValid( EnforcerRule theCachedRule )
417    {
418        // i will always return the hash of the parameters as my id. If my parameters are the same, this
419        // rule must always have the same result.
420        return true;
421    }
422}