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