Skip to content

Commit

Permalink
[MENFORCER-422] Added externalRules rule
Browse files Browse the repository at this point in the history
Co-authored-by: Peter Palaga <ppalaga@redhat.com>
  • Loading branch information
gastaldi and ppalaga committed Aug 19, 2022
1 parent ae93fa8 commit b66d4f2
Show file tree
Hide file tree
Showing 14 changed files with 580 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ target
.svn
*.iml
.checkstyle

.DS_Store
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
*/
public class EnforcerDescriptor
{
EnforcerRule[] rules;

public EnforcerRule[] getRules()
{
return rules;
}

public void setRules( EnforcerRule[] rules )
{
this.rules = rules;
}
}
Original file line number Diff line number Diff line change
@@ -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 <a href="mailto:gastaldi@apache.org">George Gastaldi</a>
*/
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 <code>${project.basedir}</code>
*/
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();
}

}
100 changes: 100 additions & 0 deletions enforcer-rules/src/site/apt/externalRules.apt.vm
Original file line number Diff line number Diff line change
@@ -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 <<<maven-enforcer-plugin>>> dependency or a local file.


Sample Plugin Configuration:

+---+
<project>
[...]
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>${project.version}</version>
<dependencies>
<!-- Dependency containing the enforcer/rules.xml file -->
<dependency>
<groupId>org.foo</groupId>
<artifactId>foobar-rules</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<ExternalRules>
<!-- enforcer/rules.xml is supposed to be a classpath resource present -->
<!-- in org.foo:foobar-rules dependency of maven-enforcer-plugin defined above -->
<location>classpath:enforcer/rules.xml</location>
</ExternalRules>
<ExternalRules>
<!-- You can add multiple <ExternalRules> elements if you need to enforce -->
<!-- rules from multiple resources. -->
<!-- src/build/rules.xml is a local file path relative to ${project.basedir} -->
<location>src/build/rules.xml</location>
</ExternalRules>
</rules>
<fail>true</fail>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
[...]
</project>
+---+


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}}:

+---+
<enforcer>
<rules>
<dependencyConvergence/>
<bannedDependencies>
<excludes>
<exclude>org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec</exclude>
</excludes>
<includes>
<include>jakarta.xml.bind:jakarta.xml.bind-api:*:*:test</include>
</includes>
</bannedDependencies>
</rules>
</enforcer>
+---+
2 changes: 2 additions & 0 deletions enforcer-rules/src/site/apt/index.apt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@
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;
import org.apache.maven.project.ProjectBuildingRequest;
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;
Expand Down Expand Up @@ -121,14 +123,14 @@ 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 )
{
eval = new MockEnforcerExpressionEvaluator( session );
}
else
{
MojoExecution mockExecution = mock( MojoExecution.class );
session.setCurrentProject( project );
eval = new PluginParameterExpressionEvaluator( session, mockExecution );
}
Expand All @@ -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 );
}

Expand Down

0 comments on commit b66d4f2

Please sign in to comment.