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.execution.MavenSession;
030import org.apache.maven.model.Activation;
031import org.apache.maven.model.ActivationOS;
032import org.apache.maven.model.Profile;
033import org.apache.maven.model.profile.DefaultProfileActivationContext;
034import org.apache.maven.model.profile.ProfileActivationContext;
035import org.apache.maven.model.profile.activation.ProfileActivator;
036import org.codehaus.plexus.util.Os;
037import org.codehaus.plexus.util.StringUtils;
038
039/**
040 * This rule checks that the OS is allowed by combinations of family, name, version and cpu architecture. The behavior
041 * is exactly the same as the Maven Os profile activation so the same values are allowed here.
042 *
043 * @author <a href="mailto:brianf@apache.org">Brian Fox</a>
044 */
045@Named("requireOS")
046public final class RequireOS extends AbstractStandardEnforcerRule {
047    private final ProfileActivator activator;
048
049    private final ProfileActivationContext profileActivationContext;
050
051    /**
052     * The OS family type desired<br />
053     * Possible values:
054     * <ul>
055     * <li>dos</li>
056     * <li>mac</li>
057     * <li>netware</li>
058     * <li>os/2</li>
059     * <li>tandem</li>
060     * <li>unix</li>
061     * <li>windows</li>
062     * <li>win9x</li>
063     * <li>z/os</li>
064     * <li>os/400</li>
065     * </ul>
066     */
067    private String family = null;
068
069    /**
070     * The OS name desired.
071     */
072    private String name = null;
073
074    /**
075     * The OS version desired.
076     */
077    private String version = null;
078
079    /**
080     * The OS architecture desired.
081     */
082    private String arch = null;
083
084    /**
085     * Display detected OS information.
086     */
087    private boolean display = false;
088
089    /**
090     * Instantiates a new RequireOS.
091     */
092    @Inject
093    RequireOS(@Named("os") ProfileActivator activator, MavenSession session) {
094        this.activator = Objects.requireNonNull(activator);
095        this.profileActivationContext = createProfileActivationContext(session);
096    }
097
098    private ProfileActivationContext createProfileActivationContext(MavenSession session) {
099        DefaultProfileActivationContext context = new DefaultProfileActivationContext();
100        context.setActiveProfileIds(session.getRequest().getActiveProfiles());
101        context.setInactiveProfileIds(session.getRequest().getInactiveProfiles());
102        context.setProjectDirectory(session.getCurrentProject().getBasedir());
103        context.setProjectProperties(session.getCurrentProject().getProperties());
104        context.setSystemProperties(System.getProperties());
105        context.setUserProperties(session.getUserProperties());
106        return context;
107    }
108
109    @Override
110    public void execute() throws EnforcerRuleException {
111
112        displayOSInfo();
113
114        if (allParamsEmpty()) {
115            throw new EnforcerRuleError("All parameters can not be empty. "
116                    + "You must pick at least one of (family, name, version, arch), "
117                    + "you can use mvn --version to see the current OS information.");
118        }
119
120        if (isValidFamily(this.family)) {
121            if (!isAllowed()) {
122                String message = getMessage();
123                if (message == null || message.isEmpty()) {
124                    // @formatter:off
125                    message = "OS Arch: "
126                            + Os.OS_ARCH + " Family: "
127                            + Os.OS_FAMILY + " Name: "
128                            + Os.OS_NAME + " Version: "
129                            + Os.OS_VERSION + " is not allowed by" + (arch != null ? " Arch=" + arch : "")
130                            + (family != null ? " Family=" + family : "")
131                            + (name != null ? " Name=" + name : "")
132                            + (version != null ? " Version=" + version : "");
133                    // @formatter:on
134                }
135                throw new EnforcerRuleException(message);
136            }
137        } else {
138            String validFamilies = String.join(",", Os.getValidFamilies());
139            throw new EnforcerRuleError("Invalid Family type used. Valid family types are: " + validFamilies);
140        }
141    }
142
143    /**
144     * Log the current OS information.
145     */
146    private void displayOSInfo() {
147        String string = OSUtil.getOSInfo();
148
149        if (!display) {
150            getLog().debug(string);
151        } else {
152            getLog().info(string);
153        }
154    }
155
156    /**
157     * Helper method to determine if the current OS is allowed based on the injected values for family, name, version
158     * and arch.
159     *
160     * @return true if the version is allowed.
161     */
162    public boolean isAllowed() {
163        // empty lambda as problems collector
164        return activator.isActive(createProfile(), profileActivationContext, (req -> {}));
165    }
166
167    /**
168     * Helper method to check that at least one of family, name, version or arch is set.
169     *
170     * @return true if all parameters are empty.
171     */
172    public boolean allParamsEmpty() {
173        return (family == null || family.isEmpty())
174                && (arch == null || arch.isEmpty())
175                && (name == null || name.isEmpty())
176                && (version == null || version.isEmpty());
177    }
178
179    /**
180     * Creates a Profile object that contains the activation information.
181     *
182     * @return a properly populated profile to be used for OS validation.
183     */
184    private Profile createProfile() {
185        Profile profile = new Profile();
186        profile.setActivation(createActivation());
187        return profile;
188    }
189
190    /**
191     * Creates an Activation object that contains the ActivationOS information.
192     *
193     * @return a properly populated Activation object.
194     */
195    private Activation createActivation() {
196        Activation activation = new Activation();
197        activation.setActiveByDefault(false);
198        activation.setOs(createOsBean());
199        return activation;
200    }
201
202    /**
203     * Creates an ActivationOS object containing family, name, version and arch.
204     *
205     * @return a properly populated ActivationOS object.
206     */
207    private ActivationOS createOsBean() {
208        ActivationOS os = new ActivationOS();
209
210        os.setArch(arch);
211        os.setFamily(family);
212        os.setName(name);
213        os.setVersion(version);
214
215        return os;
216    }
217
218    /**
219     * Helper method to check if the given family is in the following list:
220     * <ul>
221     * <li>dos</li>
222     * <li>mac</li>
223     * <li>netware</li>
224     * <li>os/2</li>
225     * <li>tandem</li>
226     * <li>unix</li>
227     * <li>windows</li>
228     * <li>win9x</li>
229     * <li>z/os</li>
230     * <li>os/400</li>
231     * </ul>
232     * Note: '!' is allowed at the beginning of the string and still considered valid.
233     *
234     * @param theFamily the family to check.
235     * @return true if one of the valid families.
236     */
237    public boolean isValidFamily(String theFamily) {
238
239        // in case they are checking !family
240        theFamily = StringUtils.stripStart(theFamily, "!");
241
242        return (theFamily == null || theFamily.isEmpty())
243                || Os.getValidFamilies().contains(theFamily);
244    }
245
246    /**
247     * Sets the arch.
248     *
249     * @param theArch the arch to set
250     */
251    public void setArch(String theArch) {
252        this.arch = theArch;
253    }
254
255    /**
256     * Sets the family.
257     *
258     * @param theFamily the family to set
259     */
260    public void setFamily(String theFamily) {
261        this.family = theFamily;
262    }
263
264    /**
265     * Sets the name.
266     *
267     * @param theName the name to set
268     */
269    public void setName(String theName) {
270        this.name = theName;
271    }
272
273    /**
274     * Sets the version.
275     *
276     * @param theVersion the version to set
277     */
278    public void setVersion(String theVersion) {
279        this.version = theVersion;
280    }
281
282    /**
283     * @param display The value for the display.
284     */
285    public void setDisplay(boolean display) {
286        this.display = display;
287    }
288
289    @Override
290    public String getCacheId() {
291        // return the hashcodes of all the parameters
292        StringBuilder b = new StringBuilder();
293        if (version != null && !version.isEmpty()) {
294            b.append(version.hashCode());
295        }
296        if (name != null && !name.isEmpty()) {
297            b.append(name.hashCode());
298        }
299        if (arch != null && !arch.isEmpty()) {
300            b.append(arch.hashCode());
301        }
302        if (family != null && !family.isEmpty()) {
303            b.append(family.hashCode());
304        }
305        return b.toString();
306    }
307
308    @Override
309    public String toString() {
310        return String.format(
311                "RequireOS[message=%s, arch=%s, family=%s, name=%s, version=%s, display=%b]",
312                getMessage(), arch, family, name, version, display);
313    }
314}