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