This is an automated email from the ASF dual-hosted git repository. kwin pushed a commit to branch feature-analyzer-for-classes in repository https://gitbox.apache.org/repos/asf/sling-whiteboard.git
commit fa8423eb3b5034400d57ac54ddc09f712c40c20e Author: Konrad Windszus <[email protected]> AuthorDate: Mon Oct 9 11:29:46 2023 +0200 SLING-12026 Check for implementations/extension of provider types New feature analyser module for class-related analysers, leveraging ASM --- .../.gitignore | 18 +++ org.apache.sling.feature.analyser.classes/pom.xml | 143 ++++++++++++++++++++ .../readme.md | 18 +++ .../impl/ArtifactContextAwareClassVisitor.java | 52 +++++++ .../impl/CheckProviderTypeImplementations.java | 149 +++++++++++++++++++++ .../impl/CheckProviderTypeImplementationsTest.java | 91 +++++++++++++ .../src/test/resources/metadata-feature.json | 74 ++++++++++ .../src/test/resources/simplelogger.properties | 1 + 8 files changed, 546 insertions(+) diff --git a/org.apache.sling.feature.analyser.classes/.gitignore b/org.apache.sling.feature.analyser.classes/.gitignore new file mode 100644 index 00000000..c7f991ea --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/.gitignore @@ -0,0 +1,18 @@ +/target +.idea +.classpath +.metadata +.project +.settings +.externalToolBuilders +maven-eclipse.xml +*.swp +*.iml +*.ipr +*.iws +*.bak +.vlt +.vscode +.DS_Store +jcr.log +atlassian-ide-plugin.xml diff --git a/org.apache.sling.feature.analyser.classes/pom.xml b/org.apache.sling.feature.analyser.classes/pom.xml new file mode 100644 index 00000000..bc05302f --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/pom.xml @@ -0,0 +1,143 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling-bundle-parent</artifactId> + <version>52</version> + <relativePath /> + </parent> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature.analyser.classes</artifactId> + <version>0.0.1-SNAPSHOT</version> + + <name>Apache Sling Feature Model Analyser For Classes</name> + <description>Provides analysers which act on class level of bundles embedded + in features</description> + + <properties> + <sling.java.version>11</sling.java.version> + <project.build.outputTimestamp>1675867676</project.build.outputTimestamp> + </properties> + <dependencyManagement> + <dependencies> + <dependency> + <groupId>org.junit</groupId> + <artifactId>junit-bom</artifactId> + <version>5.9.3</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature.analyser</artifactId> + <version>2.0.0</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.jetbrains</groupId> + <artifactId>annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.feature</artifactId> + <version>2.0.0</version> + <scope>compile</scope> + </dependency> + <!--transitive + but provided deps of sling.feature --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.annotation.versioning</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.framework</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.resource</artifactId> + <version>1.0.1</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>jakarta.json</groupId> + <artifactId>jakarta.json-api</artifactId> + <version>2.0.2</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.util.converter</artifactId> + <version>1.0.9</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.service.feature</artifactId> + <version>1.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.utils</artifactId> + <version>1.11.8</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.cm.json</artifactId> + <version>2.0.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.ow2.asm</groupId> + <artifactId>asm</artifactId> + <version>9.6</version> + <scope>compile</scope> + </dependency> + <!-- testing dependencies --> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest-library</artifactId> + <version>2.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>jakarta.json</groupId> + <artifactId>jakarta.json-api</artifactId> + <version>2.0.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.johnzon</groupId> + <artifactId>johnzon-core</artifactId> + <classifier>jakarta</classifier> + <version>1.2.14</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <scope>compile</scope> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/org.apache.sling.feature.analyser.classes/readme.md b/org.apache.sling.feature.analyser.classes/readme.md new file mode 100644 index 00000000..9f65b60a --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/readme.md @@ -0,0 +1,18 @@ +[](https://sling.apache.org) + + [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-apache-sling-feature-analyser/job/master/) [](https://ci-builds.apache.org/job/Sling/job/modules/job/sling-org-a [...] + +# Feature Model Analyser For Classes + +This module contain analyser tasks which analyse classes contained in features' OSGi bundles. +For further information about refer to [Sling Feature Model Analyser](https://github.com/apache/sling-org-apache-sling-feature-analyser) + + +# Analyser Tasks + +Below is a list of built-in analysers. + +## `prevent-provider-type-impls` + +This analyser makes sure that no class implements or extends any type marked as provider with annotation [`org.osgi.annotation.versioning.ProviderType`](https://docs.osgi.org/javadoc/osgi.annotation/8.0.0/org/osgi/annotation/versioning/ProviderType.html). + diff --git a/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/ArtifactContextAwareClassVisitor.java b/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/ArtifactContextAwareClassVisitor.java new file mode 100644 index 00000000..96f8f20c --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/ArtifactContextAwareClassVisitor.java @@ -0,0 +1,52 @@ +/* + * 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. + */ +package org.apache.sling.feature.analyser.task.classes.impl; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; + +public abstract class ArtifactContextAwareClassVisitor extends ClassVisitor { + + private final AnalyserTaskContext ctx; + private ArtifactId currentArtifactId; + + ArtifactContextAwareClassVisitor(AnalyserTaskContext ctx) { + super(Opcodes.ASM9); + this.ctx = ctx; + } + + /** + * Called for each visited artifact prior to all other method calls from {@link ClassVisitors} + * @param artifactId the artifact id of the currently visited class + */ + public void visitArtifact(ArtifactId artifactId) { + this.currentArtifactId = artifactId; + } + + /** + * Report an error in the context of the of the given {@link AnalyserTaskContext} + * and the artifact given to {@link #visitArtifact(ArtifactId)} + * @param message the error message to emit + */ + public void reportError(String message) { + ctx.reportArtifactError(currentArtifactId, message); + } +} diff --git a/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementations.java b/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementations.java new file mode 100644 index 00000000..05dcff0b --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/src/main/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementations.java @@ -0,0 +1,149 @@ +/* + * 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. + */ +package org.apache.sling.feature.analyser.task.classes.impl; + +import java.io.IOException; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; + +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.scanner.BundleDescriptor; +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CheckProviderTypeImplementations implements AnalyserTask { + + private static final String PROVIDER_TYPE_ANNOTATION = "Lorg/osgi/annotation/versioning/ProviderType;"; + private static final String MESSAGE = "Type %s %s provider type %s. This is not allowed!"; + + static final Logger LOG = LoggerFactory.getLogger(CheckProviderTypeImplementations.class); + + @Override + public String getId() { + return "prevent-provider-type-impls"; + } + + @Override + public String getName() { + return "Prevents implementation or extension of types marked as Provider"; + } + + @Override + public void execute(AnalyserTaskContext ctx) throws Exception { + Set<String> providerTypes = collectProviderTypes(ctx); + LOG.debug("Provider types found: {}", providerTypes); + forEachClass(ctx, + new ProhibitingSuperTypeAndImplementedInterfacesClassVisitor(ctx, providerTypes), + ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + } + + private Set<String> collectProviderTypes(AnalyserTaskContext ctx) throws IOException { + AnnotatedTypeCollectorClassVisitor providerTypeCollector = new AnnotatedTypeCollectorClassVisitor(PROVIDER_TYPE_ANNOTATION); + forEachClass(ctx, providerTypeCollector, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + return providerTypeCollector.getProviderTypes(); + } + + private static final class AnnotatedTypeCollectorClassVisitor extends ClassVisitor { + + private final String annotationType; + private final Set<String> providerTypes; + private String currentClassName; + + protected AnnotatedTypeCollectorClassVisitor(String annotationType) { + super(Opcodes.ASM9); + this.annotationType = annotationType; + this.providerTypes = new HashSet<>(); + } + + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + currentClassName = name; + } + + @Override + public AnnotationVisitor visitAnnotation(String descriptor, boolean visible) { + if (descriptor.equals(annotationType)) { + providerTypes.add(currentClassName); + } + return super.visitAnnotation(descriptor, visible); + } + + public Set<String> getProviderTypes() { + return providerTypes; + } + } + + static String internalToClassName(String internalName) { + return Type.getObjectType(internalName).getClassName(); + } + + private void forEachClass(AnalyserTaskContext ctx, ClassVisitor visitor, int parsingOptions) throws IOException { + for (BundleDescriptor bundleDescriptor : ctx.getFeatureDescriptor().getBundleDescriptors()) { + URL url = bundleDescriptor.getArtifactFile(); + LOG.debug("Checking classes in bundle {}", url); + try (final JarInputStream jis = new JarInputStream(url.openStream())) { + if (visitor instanceof ArtifactContextAwareClassVisitor) { + ArtifactContextAwareClassVisitor acacv = (ArtifactContextAwareClassVisitor)visitor; + acacv.visitArtifact(bundleDescriptor.getArtifact().getId()); + } + forEachClass(jis, visitor, parsingOptions); + } + } + } + + private void forEachClass(JarInputStream jarInputStream, ClassVisitor visitor, int parsingOptions) throws IOException { + JarEntry jarEntry; + while ((jarEntry = jarInputStream.getNextJarEntry()) != null) { + if (jarEntry.getName().endsWith(".class")) { + ClassReader classReader = new ClassReader(jarInputStream); + classReader.accept(visitor, parsingOptions); + } + jarInputStream.closeEntry(); + } + } + + private static final class ProhibitingSuperTypeAndImplementedInterfacesClassVisitor extends ArtifactContextAwareClassVisitor { + + private final Set<String> prohibitedTypes; + + protected ProhibitingSuperTypeAndImplementedInterfacesClassVisitor(AnalyserTaskContext ctx, Set<String> prohibitedTypes) { + super(ctx); + this.prohibitedTypes = prohibitedTypes; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + if (prohibitedTypes.contains(superName)) { + reportError(String.format(MESSAGE, internalToClassName(name), "implements", internalToClassName(superName))); + } + Arrays.stream(interfaces).filter(prohibitedTypes::contains).forEach(s -> reportError(String.format(MESSAGE, internalToClassName(name), "extends", internalToClassName(s)))); + } + } +} diff --git a/org.apache.sling.feature.analyser.classes/src/test/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementationsTest.java b/org.apache.sling.feature.analyser.classes/src/test/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementationsTest.java new file mode 100644 index 00000000..2e881250 --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/src/test/java/org/apache/sling/feature/analyser/task/classes/impl/CheckProviderTypeImplementationsTest.java @@ -0,0 +1,91 @@ +/* + * 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. + */ +package org.apache.sling.feature.analyser.task.classes.impl; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; + +import org.apache.sling.feature.ArtifactId; +import org.apache.sling.feature.Feature; +import org.apache.sling.feature.analyser.Analyser; +import org.apache.sling.feature.analyser.AnalyserResult; +import org.apache.sling.feature.analyser.AnalyserResult.ArtifactReport; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.io.artifacts.ArtifactManager; +import org.apache.sling.feature.io.artifacts.ArtifactManagerConfig; +import org.apache.sling.feature.io.json.FeatureJSONReader; +import org.apache.sling.feature.scanner.Scanner; +import org.hamcrest.Description; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; +import org.junit.jupiter.api.Test; + + +class CheckProviderTypeImplementationsTest { + + @Test + void test() throws Exception { + AnalyserResult result = analyse("/metadata-feature.json", new CheckProviderTypeImplementations()); + assertEquals(7, result.getArtifactErrors().size()); + MatcherAssert.assertThat(result.getArtifactErrors(), Matchers.hasItem( + new ArtifactReportMatcher("org.apache.jackrabbit:oak-jackrabbit-api:1.56.0", + "Type org.apache.jackrabbit.api.security.JackrabbitAccessControlList extends provider type org.apache.jackrabbit.api.security.JackrabbitAccessControlPolicy. This is not allowed!"))); + + } + + private static final class ArtifactReportMatcher extends TypeSafeMatcher<ArtifactReport> { + + private final ArtifactId id; + private final String value; + private final String taskId; + + ArtifactReportMatcher(String mvnCoordinates, String value) { + this.id = ArtifactId.fromMvnId(mvnCoordinates); + this.value = value; + this.taskId = "prevent-provider-type-impls"; + } + + @Override + protected boolean matchesSafely(ArtifactReport item) { + return (item.getKey().equals(id) + && item.getValue().equals(value) + && item.getTaskId().equals(taskId)); + } + + @Override + public void describeTo(Description description) { + description.appendText( "ArtifactReport [").appendText(taskId).appendText("] ").appendText(id.toString()).appendText(": ").appendText(value); + } + } + + static AnalyserResult analyse(String featureResourceName, AnalyserTask... tasks) throws Exception { + final Feature feature; + try (Reader reader = new InputStreamReader(CheckProviderTypeImplementationsTest.class.getResourceAsStream(featureResourceName), StandardCharsets.UTF_8)) { + feature = FeatureJSONReader.read(reader, ""); + } + ArtifactManagerConfig config = new ArtifactManagerConfig(); + final Scanner scanner = new Scanner(ArtifactManager.getArtifactManager(config)); + Analyser analyser = new Analyser(scanner, tasks); + return analyser.analyse(feature); + } +} diff --git a/org.apache.sling.feature.analyser.classes/src/test/resources/metadata-feature.json b/org.apache.sling.feature.analyser.classes/src/test/resources/metadata-feature.json new file mode 100644 index 00000000..782109da --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/src/test/resources/metadata-feature.json @@ -0,0 +1,74 @@ +{ + "id": "org.acme:acmefeature:slingosgifeature:metadata-feature:0.0.1", + "bundles": [ + "org.apache.jackrabbit:oak-jackrabbit-api:1.56.0" + ], + "analyser-metadata:JSON|false": { + "org.slf4j:jcl-over-slf4j:1.7.25": { + "manifest" : { + "Manifest-Version": "1.0", + "Archiver-Version": "Plexus Archiver", + "Created-By": "Apache Maven", + "Built-By": "ceki", + "Build-Jdk": "1.7.0_17", + "Bundle-Description": "JCL 1.2 implemented over SLF4J", + "Bundle-Version": "1.7.25", + "Implementation-Version": "1.7.25", + "X-Compile-Source-JDK": "1.5", + "X-Compile-Target-JDK": "1.5", + "Implementation-Title": "jcl-over-slf4j", + "Bundle-ManifestVersion": "2", + "Bundle-SymbolicName": "jcl.over.slf4j", + "Bundle-Name": "jcl-over-slf4j", + "Bundle-Vendor": "SLF4J.ORG", + "Bundle-RequiredExecutionEnvironment": "J2SE-1.5", + "Export-Package": "org.apache.commons.logging;version=1.2, org.apache.commons.logging.impl;version=1.2", + "Import-Package": "org.slf4j;version=1.7.25, org.slf4j.spi;version=1.7.25" + } + }, + "org.slf4j:log4j-over-slf4j:1.7.25": { + "manifest" : { + "Manifest-Version": "2.0", + "Archiver-Version": "Plexus Archiver", + "Created-By": "Apache Maven", + "Built-By": "ceki", + "Build-Jdk": "1.7.0_17", + "Bundle-Description": "Log4j implemented over SLF4J", + "Bundle-Version": "1.7.25", + "Implementation-Version": "1.7.25", + "X-Compile-Source-JDK": "1.5", + "X-Compile-Target-JDK": "1.5", + "Implementation-Title": "log4j-over-slf4j", + "Bundle-ManifestVersion": "2", + "Bundle-SymbolicName": "log4j.over.slf4j", + "Bundle-Name": "log4j-over-slf4j", + "Bundle-Vendor": "SLF4J.ORG", + "Bundle-RequiredExecutionEnvironment": "J2SE-1.5", + "Export-Package": "org.apache.log4j;version=1.2.17,org.apache.log4j.helpers;version=1.2.17,org.apache.log4j.spi;version=1.2.17,org.apache.log4j.xml;version=1.2.17", + "Import-Package": "org.slf4j;version=1.6.0,org.slf4j.helpers;version=1.6.0,org.slf4j.spi;version=1.6.0" + } + }, + "org.slf4j:slf4j-api:1.7.25": { + "manifest" : { + "Manifest-Version": "1.0", + "Archiver-Version": "Plexus Archiver", + "Created-By": "Apache Maven", + "Built-By": "ceki", + "Build-Jdk": "1.7.0_17", + "Bundle-Description": "The slf4j API", + "Bundle-Version": "1.7.25", + "Implementation-Version": "1.7.25", + "X-Compile-Source-JDK": "1.5", + "X-Compile-Target-JDK": "1.5", + "Implementation-Title": "slf4j-api", + "Bundle-ManifestVersion": "2", + "Bundle-SymbolicName": "slf4j.api", + "Bundle-Name": "slf4j-api", + "Bundle-Vendor": "SLF4J.ORG", + "Bundle-RequiredExecutionEnvironment": "J2SE-1.5", + "Export-Package": "org.slf4j;version=1.7.25, org.slf4j.spi;version=1.7.25, org.slf4j.helpers;version=1.7.25, org.slf4j.event;version=1.7.25", + "Import-Package": "org.slf4j.impl;version=1.6.0" + } + } + } +} \ No newline at end of file diff --git a/org.apache.sling.feature.analyser.classes/src/test/resources/simplelogger.properties b/org.apache.sling.feature.analyser.classes/src/test/resources/simplelogger.properties new file mode 100644 index 00000000..92ba7a58 --- /dev/null +++ b/org.apache.sling.feature.analyser.classes/src/test/resources/simplelogger.properties @@ -0,0 +1 @@ +org.slf4j.simpleLogger.log.org.apache.sling.feature.analyser.task.classes.impl.CheckProviderTypeImplementations=debug \ No newline at end of file
