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 (message == null || message.isEmpty()) {
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 (family == null || family.isEmpty())
156                && (arch == null || arch.isEmpty())
157                && (name == null || name.isEmpty())
158                && (version == null || version.isEmpty());
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 (theFamily == null || theFamily.isEmpty())
225                || Os.getValidFamilies().contains(theFamily);
226    }
227
228    /**
229     * Sets the arch.
230     *
231     * @param theArch the arch to set
232     */
233    public void setArch(String theArch) {
234        this.arch = theArch;
235    }
236
237    /**
238     * Sets the family.
239     *
240     * @param theFamily the family to set
241     */
242    public void setFamily(String theFamily) {
243        this.family = theFamily;
244    }
245
246    /**
247     * Sets the name.
248     *
249     * @param theName the name to set
250     */
251    public void setName(String theName) {
252        this.name = theName;
253    }
254
255    /**
256     * Sets the version.
257     *
258     * @param theVersion the version to set
259     */
260    public void setVersion(String theVersion) {
261        this.version = theVersion;
262    }
263
264    /**
265     * @param display The value for the display.
266     */
267    public void setDisplay(boolean display) {
268        this.display = display;
269    }
270
271    @Override
272    public String getCacheId() {
273        // return the hashcodes of all the parameters
274        StringBuilder b = new StringBuilder();
275        if (version != null && !version.isEmpty()) {
276            b.append(version.hashCode());
277        }
278        if (name != null && !name.isEmpty()) {
279            b.append(name.hashCode());
280        }
281        if (arch != null && !arch.isEmpty()) {
282            b.append(arch.hashCode());
283        }
284        if (family != null && !family.isEmpty()) {
285            b.append(family.hashCode());
286        }
287        return b.toString();
288    }
289
290    @Override
291    public String toString() {
292        return String.format(
293                "RequireOS[message=%s, arch=%s, family=%s, name=%s, version=%s, display=%b]",
294                getMessage(), arch, family, name, version, display);
295    }
296}