001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.enforcer.rules;
020
021import javax.inject.Inject;
022import javax.inject.Named;
023
024import java.util.Objects;
025
026import org.apache.maven.enforcer.rule.api.EnforcerRuleError;
027import org.apache.maven.enforcer.rule.api.EnforcerRuleException;
028import org.apache.maven.enforcer.rules.utils.OSUtil;
029import org.apache.maven.model.Activation;
030import org.apache.maven.model.ActivationOS;
031import org.apache.maven.model.Profile;
032import org.apache.maven.model.profile.activation.ProfileActivator;
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 */
042@Named("requireOS")
043public final class RequireOS extends AbstractStandardEnforcerRule {
044    private final ProfileActivator activator;
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    private String family = null;
063
064    /**
065     * The OS name desired.
066     */
067    private String name = null;
068
069    /**
070     * The OS version desired.
071     */
072    private String version = null;
073
074    /**
075     * The OS architecture desired.
076     */
077    private String arch = null;
078
079    /**
080     * Display detected OS information.
081     */
082    private boolean display = false;
083
084    /**
085     * Instantiates a new RequireOS.
086     */
087    @Inject
088    RequireOS(@Named("os") ProfileActivator activator) {
089        this.activator = Objects.requireNonNull(activator);
090    }
091
092    @Override
093    public void execute() throws EnforcerRuleException {
094
095        displayOSInfo();
096
097        if (allParamsEmpty()) {
098            throw new EnforcerRuleError("All parameters can not be empty. "
099                    + "You must pick at least one of (family, name, version, arch), "
100                    + "you can use mvn --version to see the current OS information.");
101        }
102
103        if (isValidFamily(this.family)) {
104            if (!isAllowed()) {
105                String message = getMessage();
106                if (StringUtils.isEmpty(message)) {
107                    // @formatter:off
108                    message = ("OS Arch: "
109                            + Os.OS_ARCH + " Family: "
110                            + Os.OS_FAMILY + " Name: "
111                            + Os.OS_NAME + " Version: "
112                            + Os.OS_VERSION + " is not allowed by" + (arch != null ? " Arch=" + arch : "")
113                            + (family != null ? " Family=" + family : "")
114                            + (name != null ? " Name=" + name : "")
115                            + (version != null ? " Version=" + version : ""));
116                    // @formatter:on
117                }
118                throw new EnforcerRuleException(message);
119            }
120        } else {
121            String validFamilies = String.join(",", Os.getValidFamilies());
122            throw new EnforcerRuleError("Invalid Family type used. Valid family types are: " + validFamilies);
123        }
124    }
125
126    /**
127     * Log the current OS information.
128     */
129    private void displayOSInfo() {
130        String string = OSUtil.getOSInfo();
131
132        if (!display) {
133            getLog().debug(string);
134        } else {
135            getLog().info(string);
136        }
137    }
138
139    /**
140     * Helper method to determine if the current OS is allowed based on the injected values for family, name, version
141     * and arch.
142     *
143     * @return true if the version is allowed.
144     */
145    public boolean isAllowed() {
146        return activator.isActive(createProfile(), null, null);
147    }
148
149    /**
150     * Helper method to check that at least one of family, name, version or arch is set.
151     *
152     * @return true if all parameters are empty.
153     */
154    public boolean allParamsEmpty() {
155        return (StringUtils.isEmpty(family)
156                && StringUtils.isEmpty(arch)
157                && StringUtils.isEmpty(name)
158                && StringUtils.isEmpty(version));
159    }
160
161    /**
162     * Creates a Profile object that contains the activation information.
163     *
164     * @return a properly populated profile to be used for OS validation.
165     */
166    private Profile createProfile() {
167        Profile profile = new Profile();
168        profile.setActivation(createActivation());
169        return profile;
170    }
171
172    /**
173     * Creates an Activation object that contains the ActivationOS information.
174     *
175     * @return a properly populated Activation object.
176     */
177    private Activation createActivation() {
178        Activation activation = new Activation();
179        activation.setActiveByDefault(false);
180        activation.setOs(createOsBean());
181        return activation;
182    }
183
184    /**
185     * Creates an ActivationOS object containing family, name, version and arch.
186     *
187     * @return a properly populated ActivationOS object.
188     */
189    private ActivationOS createOsBean() {
190        ActivationOS os = new ActivationOS();
191
192        os.setArch(arch);
193        os.setFamily(family);
194        os.setName(name);
195        os.setVersion(version);
196
197        return os;
198    }
199
200    /**
201     * Helper method to check if the given family is in the following list:
202     * <ul>
203     * <li>dos</li>
204     * <li>mac</li>
205     * <li>netware</li>
206     * <li>os/2</li>
207     * <li>tandem</li>
208     * <li>unix</li>
209     * <li>windows</li>
210     * <li>win9x</li>
211     * <li>z/os</li>
212     * <li>os/400</li>
213     * </ul>
214     * Note: '!' is allowed at the beginning of the string and still considered valid.
215     *
216     * @param theFamily the family to check.
217     * @return true if one of the valid families.
218     */
219    public boolean isValidFamily(String theFamily) {
220
221        // in case they are checking !family
222        theFamily = StringUtils.stripStart(theFamily, "!");
223
224        return (StringUtils.isEmpty(theFamily) || Os.getValidFamilies().contains(theFamily));
225    }
226
227    /**
228     * Sets the arch.
229     *
230     * @param theArch the arch to set
231     */
232    public void setArch(String theArch) {
233        this.arch = theArch;
234    }
235
236    /**
237     * Sets the family.
238     *
239     * @param theFamily the family to set
240     */
241    public void setFamily(String theFamily) {
242        this.family = theFamily;
243    }
244
245    /**
246     * Sets the name.
247     *
248     * @param theName the name to set
249     */
250    public void setName(String theName) {
251        this.name = theName;
252    }
253
254    /**
255     * Sets the version.
256     *
257     * @param theVersion the version to set
258     */
259    public void setVersion(String theVersion) {
260        this.version = theVersion;
261    }
262
263    /**
264     * @param display The value for the display.
265     */
266    public void setDisplay(boolean display) {
267        this.display = display;
268    }
269
270    @Override
271    public String getCacheId() {
272        // return the hashcodes of all the parameters
273        StringBuilder b = new StringBuilder();
274        if (StringUtils.isNotEmpty(version)) {
275            b.append(version.hashCode());
276        }
277        if (StringUtils.isNotEmpty(name)) {
278            b.append(name.hashCode());
279        }
280        if (StringUtils.isNotEmpty(arch)) {
281            b.append(arch.hashCode());
282        }
283        if (StringUtils.isNotEmpty(family)) {
284            b.append(family.hashCode());
285        }
286        return b.toString();
287    }
288
289    @Override
290    public String toString() {
291        return String.format(
292                "RequireOS[message=%s, arch=%s, family=%s, name=%s, version=%s, display=%b]",
293                getMessage(), arch, family, name, version, display);
294    }
295}