diff --git a/.gitignore b/.gitignore index 41ea5492..a7ae941f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,4 @@ target .svn *.iml .checkstyle - +.DS_Store diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java new file mode 100644 index 00000000..cb6d9f1d --- /dev/null +++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/EnforcerDescriptor.java @@ -0,0 +1,42 @@ +package org.apache.maven.plugins.enforcer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.enforcer.rule.api.EnforcerRule; + +/** + * An enforcer rules descriptor used by {@link ExternalRules} + * + * @author George Gastaldi + */ +public class EnforcerDescriptor +{ + EnforcerRule[] rules; + + public EnforcerRule[] getRules() + { + return rules; + } + + public void setRules( EnforcerRule[] rules ) + { + this.rules = rules; + } +} diff --git a/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java new file mode 100644 index 00000000..ad0e7bc1 --- /dev/null +++ b/enforcer-rules/src/main/java/org/apache/maven/plugins/enforcer/ExternalRules.java @@ -0,0 +1,152 @@ +package org.apache.maven.plugins.enforcer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.enforcer.rule.api.EnforcerRule; +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; +import org.apache.maven.plugin.MojoExecution; +import org.codehaus.plexus.classworlds.realm.ClassRealm; +import org.codehaus.plexus.component.configurator.ComponentConfigurator; +import org.codehaus.plexus.component.repository.exception.ComponentLookupException; +import org.codehaus.plexus.configuration.PlexusConfiguration; +import org.codehaus.plexus.configuration.xml.XmlPlexusConfiguration; +import org.codehaus.plexus.util.xml.Xpp3DomBuilder; +import org.codehaus.plexus.util.xml.pull.XmlPullParserException; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; + +/** + * An enforcer rule that will invoke rules from an external resource + * + * @author George Gastaldi + */ +public class ExternalRules extends AbstractNonCacheableEnforcerRule +{ + private static final String LOCATION_PREFIX_CLASSPATH = "classpath:"; + + /** + * The external rules location. If it starts with "classpath:", the resource is read from the classpath. + * Otherwise, it is handled as a filesystem path, either absolute, or relative to ${project.basedir} + */ + String location; + + public ExternalRules() + { + } + + public ExternalRules( String location ) + { + this.location = location; + } + + @Override + public void execute( EnforcerRuleHelper helper ) throws EnforcerRuleException + { + // Find descriptor + EnforcerDescriptor enforcerDescriptor = getEnforcerDescriptor( helper ); + for ( EnforcerRule rule : enforcerDescriptor.getRules() ) + { + rule.execute( helper ); + } + } + + /** + * Resolve the {@link EnforcerDescriptor} based on the provided {@link #descriptor} or {@link #descriptorRef} + * + * @param helper used to build the {@link EnforcerDescriptor} + * @return an {@link EnforcerDescriptor} for this rule + * @throws EnforcerRuleException if any failure happens while reading the descriptor + */ + EnforcerDescriptor getEnforcerDescriptor( EnforcerRuleHelper helper ) + throws EnforcerRuleException + { + try ( InputStream descriptorStream = resolveDescriptor( helper ) ) + { + EnforcerDescriptor descriptor = new EnforcerDescriptor(); + // To get configuration from the enforcer-plugin mojo do: + //helper.evaluate(helper.getComponent(MojoExecution.class).getConfiguration().getChild("fail").getValue()) + // Configure EnforcerDescriptor from the XML + ComponentConfigurator configurator = helper.getComponent( ComponentConfigurator.class, "basic" ); + configurator.configureComponent( descriptor, toPlexusConfiguration( descriptorStream ), helper, + getClassRealm( helper ) ); + return descriptor; + } + catch ( EnforcerRuleException e ) + { + throw e; + } + catch ( Exception e ) + { + throw new EnforcerRuleException( "Error while enforcing rules", e ); + } + } + + private InputStream resolveDescriptor( EnforcerRuleHelper helper ) + throws ComponentLookupException, EnforcerRuleException + { + InputStream descriptorStream; + if ( location != null ) + { + if ( location.startsWith( LOCATION_PREFIX_CLASSPATH ) ) + { + String classpathLocation = location.substring( LOCATION_PREFIX_CLASSPATH.length() ); + ClassLoader classRealm = getClassRealm( helper ); + descriptorStream = classRealm.getResourceAsStream( classpathLocation ); + if ( descriptorStream == null ) + { + throw new EnforcerRuleException( "Location '" + classpathLocation + "' not found in classpath" ); + } + } + else + { + File descriptorFile = helper.alignToBaseDirectory( new File( location ) ); + try + { + descriptorStream = Files.newInputStream( descriptorFile.toPath() ); + } + catch ( IOException e ) + { + throw new EnforcerRuleException( "Could not read descriptor in " + descriptorFile, e ); + } + } + } + else + { + throw new EnforcerRuleException( "No location provided" ); + } + return descriptorStream; + } + + private static PlexusConfiguration toPlexusConfiguration( InputStream descriptorStream ) + throws XmlPullParserException, IOException + { + return new XmlPlexusConfiguration( Xpp3DomBuilder.build( descriptorStream, "UTF-8" ) ); + } + + private ClassRealm getClassRealm( EnforcerRuleHelper helper ) throws ComponentLookupException + { + return helper.getComponent( MojoExecution.class ).getMojoDescriptor().getRealm(); + } + +} \ No newline at end of file diff --git a/enforcer-rules/src/site/apt/externalRules.apt.vm b/enforcer-rules/src/site/apt/externalRules.apt.vm new file mode 100644 index 00000000..77468ae3 --- /dev/null +++ b/enforcer-rules/src/site/apt/externalRules.apt.vm @@ -0,0 +1,100 @@ +~~ Licensed to the Apache Software Foundation (ASF) under one +~~ or more contributor license agreements. See the NOTICE file +~~ distributed with this work for additional information +~~ regarding copyright ownership. The ASF licenses this file +~~ to you under the Apache License, Version 2.0 (the +~~ "License"); you may not use this file except in compliance +~~ with the License. You may obtain a copy of the License at +~~ +~~ http://www.apache.org/licenses/LICENSE-2.0 +~~ +~~ Unless required by applicable law or agreed to in writing, +~~ software distributed under the License is distributed on an +~~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +~~ KIND, either express or implied. See the License for the +~~ specific language governing permissions and limitations +~~ under the License. + + ------ + External Rules + ------ + George Gastaldi + ------ + 2022-08-17 + ------ + +External Rules + + This rule will evaluate rules from an external resource. It can be a classpath resource present in a <<>> dependency or a local file. + + + Sample Plugin Configuration: + ++---+ + + [...] + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${project.version} + + + + org.foo + foobar-rules + 1.0.0 + + + + + enforce + + enforce + + + + + + + classpath:enforcer/rules.xml + + + + + + src/build/rules.xml + + + true + + + + + + + [...] + ++---+ + + +The External Enforcer Rule Descriptor + + Here is a sample rules file. The rules element expects the same content as in the {{{https://maven.apache.org/enforcer/maven-enforcer-plugin/enforce-mojo.html#rules}Enforcer Mojo rules configuration}}: + ++---+ + + + + + + org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec + + + jakarta.xml.bind:jakarta.xml.bind-api:*:*:test + + + + ++---+ diff --git a/enforcer-rules/src/site/apt/index.apt b/enforcer-rules/src/site/apt/index.apt index f9dee969..c56d8180 100644 --- a/enforcer-rules/src/site/apt/index.apt +++ b/enforcer-rules/src/site/apt/index.apt @@ -49,6 +49,8 @@ Built-In Rules * {{{./evaluateBeanshell.html}evaluateBeanshell}} - evaluates a beanshell script. + * {{{./externalRules.html}externalRules}} - evaluate rules from an external resource. + * {{{./reactorModuleConvergence.html}reactorModuleConvergence}} - enforces that a multi module build follows best practice. * {{{./requireActiveProfile.html}requireActiveProfile}} - enforces one or more active profiles. diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java index bcbcccc2..afe287c1 100644 --- a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java +++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/EnforcerTestUtils.java @@ -38,6 +38,7 @@ import org.apache.maven.model.Plugin; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.plugin.PluginParameterExpressionEvaluator; +import org.apache.maven.plugin.descriptor.MojoDescriptor; import org.apache.maven.plugin.logging.SystemStreamLog; import org.apache.maven.plugins.enforcer.utils.MockEnforcerExpressionEvaluator; import org.apache.maven.project.MavenProject; @@ -45,6 +46,7 @@ import org.apache.maven.shared.dependency.graph.DependencyCollectorBuilder; import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode; import org.codehaus.plexus.PlexusContainer; +import org.codehaus.plexus.classworlds.ClassWorld; import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator; import org.codehaus.plexus.component.repository.exception.ComponentLookupException; import org.mockito.Mockito; @@ -121,6 +123,7 @@ public static EnforcerRuleHelper getHelper( MavenProject project ) public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockExpression ) { MavenSession session = getMavenSession(); + MojoExecution mockExecution = mock( MojoExecution.class ); ExpressionEvaluator eval; if ( mockExpression ) { @@ -128,7 +131,6 @@ public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockEx } else { - MojoExecution mockExecution = mock( MojoExecution.class ); session.setCurrentProject( project ); eval = new PluginParameterExpressionEvaluator( session, mockExecution ); } @@ -154,6 +156,18 @@ public static EnforcerRuleHelper getHelper( MavenProject project, boolean mockEx { // test will fail } + ClassWorld classWorld = new ClassWorld( "test", EnforcerTestUtils.class.getClassLoader() ); + MojoDescriptor mojoDescriptor = new MojoDescriptor(); + mojoDescriptor.setRealm( classWorld.getClassRealm( "test" ) ); + when( mockExecution.getMojoDescriptor() ).thenReturn( mojoDescriptor ); + try + { + when( container.lookup( MojoExecution.class ) ).thenReturn( mockExecution ); + } + catch ( ComponentLookupException e ) + { + // test will fail + } return new DefaultEnforcementRuleHelper( session, eval, new SystemStreamLog(), container ); } diff --git a/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java new file mode 100644 index 00000000..2a3efe21 --- /dev/null +++ b/enforcer-rules/src/test/java/org/apache/maven/plugins/enforcer/TestExternalRules.java @@ -0,0 +1,56 @@ +package org.apache.maven.plugins.enforcer; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.enforcer.rule.api.EnforcerRuleException; +import org.apache.maven.enforcer.rule.api.EnforcerRuleHelper; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +public class TestExternalRules +{ + @Test + void shouldFailIfNoLocationIsSet() + { + ExternalRules rule = new ExternalRules(); + EnforcerRuleHelper helper = EnforcerTestUtils.getHelper(); + assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) ) + .withMessage( "No location provided" ); + } + + @Test + void shouldFailIfClasspathLocationIsNotFound() + { + ExternalRules rule = new ExternalRules("classpath:foo"); + EnforcerRuleHelper helper = EnforcerTestUtils.getHelper(); + assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) ) + .withMessage( "Location 'foo' not found in classpath" ); + } + + @Test + void shouldFailIfFileLocationIsNotFound() + { + ExternalRules rule = new ExternalRules("blah.xml"); + EnforcerRuleHelper helper = EnforcerTestUtils.getHelper(); + assertThatExceptionOfType( EnforcerRuleException.class ).isThrownBy( () -> rule.execute( helper ) ) + .withMessageMatching( "Could not read descriptor in .*blah.xml" ); + } +} diff --git a/enforcer-rules/src/test/resources/enforcer-rules/pass.xml b/enforcer-rules/src/test/resources/enforcer-rules/pass.xml new file mode 100644 index 00000000..9f9de4ec --- /dev/null +++ b/enforcer-rules/src/test/resources/enforcer-rules/pass.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml new file mode 100644 index 00000000..16c0f97b --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/enforcer-rules.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties new file mode 100644 index 00000000..58b6526e --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/invoker.properties @@ -0,0 +1,18 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +invoker.buildResult = failure diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml new file mode 100644 index 00000000..79b09ff4 --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-fail/pom.xml @@ -0,0 +1,56 @@ + + + + + + 4.0.0 + + org.apache.maven.its.enforcer + test + 1.0 + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + @project.version@ + + + test + + enforce + + + + + enforcer-rules.xml + + + + + + + + + diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml new file mode 100644 index 00000000..9f9de4ec --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/enforcer-rules.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml new file mode 100644 index 00000000..79b09ff4 --- /dev/null +++ b/maven-enforcer-plugin/src/it/projects/external-rules-always-pass/pom.xml @@ -0,0 +1,56 @@ + + + + + + 4.0.0 + + org.apache.maven.its.enforcer + test + 1.0 + + + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + @project.version@ + + + test + + enforce + + + + + enforcer-rules.xml + + + + + + + + + diff --git a/pom.xml b/pom.xml index b3fa556b..5d880f7c 100644 --- a/pom.xml +++ b/pom.xml @@ -193,6 +193,10 @@ wangyf2010@gmail.com eBay Inc. + + George Gastaldi + gastaldi@apache.org +