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