Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"ruleKey": "S8913",
"hasTruePositives": true,
"falseNegatives": 0,
"falsePositives": 0
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package checks;

import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import io.quarkus.hibernate.orm.panache.PanacheRepositoryBase;
import io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource;
import io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource;
import io.quarkus.mongodb.panache.PanacheMongoEntityBase;
import io.quarkus.mongodb.panache.PanacheMongoRepositoryBase;
import io.quarkus.mongodb.rest.data.panache.PanacheMongoEntityResource;
import io.quarkus.mongodb.rest.data.panache.PanacheMongoRepositoryResource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import java.util.Collections;
import java.util.List;

class Person extends PanacheEntityBase {
public Long id;
public String name;
}

class PersonRepository implements PanacheRepositoryBase<Person, Long> {
}

class MongoPerson extends PanacheMongoEntityBase {
public Long id;
public String name;
}

class MongoPersonRepository implements PanacheMongoRepositoryBase<MongoPerson, Long> {
}

interface PeopleResource extends PanacheEntityResource<Person, Long> {
}

class PeopleResourceImpl implements PeopleResource { // Noncompliant {{Remove this implementation class; Quarkus generates the resource implementation automatically.}}
}

interface PersonRepositoryResource extends PanacheRepositoryResource<PersonRepository, Person, Long> {
}

class PersonRepositoryResourceImpl implements PersonRepositoryResource { // Noncompliant
}

interface MongoPersonResource extends PanacheMongoEntityResource<MongoPerson, Long> {
}

class MongoPersonResourceImpl implements MongoPersonResource { // Noncompliant
}

interface MongoPersonRepositoryResource extends PanacheMongoRepositoryResource<MongoPersonRepository, MongoPerson, Long> {
}

class MongoPersonRepositoryResourceImpl implements MongoPersonRepositoryResource { // Noncompliant
}

abstract class AbstractPersonResource implements PeopleResource { // Noncompliant
}

class ConcretePersonResource extends AbstractPersonResource { // Noncompliant
}

interface CompliantResource extends PanacheEntityResource<Person, Long> {
}

interface ResourceWithDefaults extends PanacheEntityResource<Person, Long> {
@GET
@Path("/name/{name}")
@Produces("application/json")
default List<Person> findByName(@PathParam("name") String name) {
return Collections.emptyList();
}
}

interface RegularRestInterface {
Person get(Long id);
}

class RegularRestImpl implements RegularRestInterface {
@Override
public Person get(Long id) {
return null;
}
}

class PersonService {
public Person findById(Long id) {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.hibernate.orm.panache;

public abstract class PanacheEntityBase {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.hibernate.orm.panache;

public interface PanacheRepositoryBase<Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.hibernate.orm.rest.data.panache;

public interface PanacheEntityResource<Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.hibernate.orm.rest.data.panache;

public interface PanacheRepositoryResource<Repository, Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.mongodb.panache;

public abstract class PanacheMongoEntityBase {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.mongodb.panache;

public interface PanacheMongoRepositoryBase<Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.mongodb.rest.data.panache;

public interface PanacheMongoEntityResource<Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package io.quarkus.mongodb.rest.data.panache;

public interface PanacheMongoRepositoryResource<Repository, Entity, Id> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import java.util.Collections;
import java.util.List;
import org.sonar.check.Rule;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.Tree;

@Rule(key = "S8913")
public class RestDataPanacheResourceImplementationCheck extends IssuableSubscriptionVisitor {

private static final String PANACHE_ENTITY_RESOURCE = "io.quarkus.hibernate.orm.rest.data.panache.PanacheEntityResource";
private static final String PANACHE_REPOSITORY_RESOURCE = "io.quarkus.hibernate.orm.rest.data.panache.PanacheRepositoryResource";
private static final String PANACHE_MONGO_ENTITY_RESOURCE = "io.quarkus.mongodb.rest.data.panache.PanacheMongoEntityResource";
private static final String PANACHE_MONGO_REPOSITORY_RESOURCE = "io.quarkus.mongodb.rest.data.panache.PanacheMongoRepositoryResource";

@Override
public List<Tree.Kind> nodesToVisit() {
return Collections.singletonList(Tree.Kind.CLASS);
}

@Override
public void visitNode(Tree tree) {
ClassTree classTree = (ClassTree) tree;
if (classTree.symbol().isUnknown()) {
return;
}
if (implementsPanacheResourceInterface(classTree)) {
reportIssue(classTree.simpleName(), "Remove this implementation class; Quarkus generates the resource implementation automatically.");
}
}

private static boolean implementsPanacheResourceInterface(ClassTree classTree) {
Type classType = classTree.symbol().type();
return isPanacheResourceType(classType);
}

private static boolean isPanacheResourceType(Type type) {
return type.isSubtypeOf(PANACHE_ENTITY_RESOURCE)
|| type.isSubtypeOf(PANACHE_REPOSITORY_RESOURCE)
|| type.isSubtypeOf(PANACHE_MONGO_ENTITY_RESOURCE)
|| type.isSubtypeOf(PANACHE_MONGO_REPOSITORY_RESOURCE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks;

import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;

import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;

class RestDataPanacheResourceImplementationCheckTest {

@Test
void test() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/RestDataPanacheResourceImplementationCheckSample.java"))
.withCheck(new RestDataPanacheResourceImplementationCheck())
.verifyIssues();
}

@Test
void testWithoutSemantic() {
CheckVerifier.newVerifier()
.onFile(mainCodeSourcesPath("checks/RestDataPanacheResourceImplementationCheckSample.java"))
.withCheck(new RestDataPanacheResourceImplementationCheck())
.withoutSemantic()
.verifyNoIssues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<p>This rule raises an issue when a class implements an interface that extends one of the framework’s provided base resource interfaces for REST
endpoints with built-in database entity operations.</p>
<p>In Java with Quarkus, these interfaces are specifically: <code>PanacheEntityResource</code>, <code>PanacheRepositoryResource</code>,
<code>PanacheMongoEntityResource</code>, and <code>PanacheMongoRepositoryResource</code>.</p>
<h2>Why is this an issue?</h2>
<p>Certain frameworks use code generation mechanisms to automatically create complete implementations from interface definitions or contract
specifications. When you define an interface that extends framework-specific base interfaces designed for resource management, the framework generates
the complete implementation at build time.</p>
<p>The framework processes these interfaces during the build phase and creates resource handlers with standard operations (create, read, update,
delete, list). This generation happens automatically without requiring any manual implementation.</p>
<p>If you create a class that implements such an interface, the framework will ignore your implementation entirely. The code generator doesn’t check
for existing implementations — it simply creates its own implementation class. This means:</p>
<ul>
<li>Your custom implementation code will never be executed</li>
<li>Any method overrides you write will have no effect</li>
<li>The application will use the auto-generated implementation instead</li>
<li>You’ll waste development time writing code that serves no purpose</li>
</ul>
<p>This silent failure can be particularly confusing because the application will compile and run without errors, but your custom logic won’t execute.
Developers may spend significant time debugging why their code changes aren’t taking effect.</p>
<p>The only supported way to add custom functionality is through extension mechanisms that are explicitly recognized by the code generation process,
such as inline method implementations within the interface definition itself. These inline implementations are respected by the code generation
mechanism and will be included in the final generated resource alongside the standard operations.</p>
<p>In Quarkus REST Data with Panache, this applies to interfaces extending <code>PanacheEntityResource</code> or
<code>PanacheRepositoryResource</code>. The framework generates Jakarta REST resources at build time. The supported extension mechanism is
<strong>default methods</strong> defined directly in the resource interface.</p>
<h3>What is the potential impact?</h3>
<p>This issue leads to wasted development effort and potential confusion when debugging. Developers may spend time writing and maintaining
implementation code that has no effect on the application’s behavior. The silent nature of this problem makes it particularly problematic, as there
are no build-time errors or runtime warnings to indicate that the custom implementation is being ignored.</p>
<p>While this is not a security or data integrity issue, it can delay development and lead to frustration when custom business logic doesn’t execute
as expected.</p>
<h2>How to fix it</h2>
<p>Remove the implementation class entirely. If you need custom methods, add them as default methods directly in the resource interface. Default
methods will be included in the generated resource alongside the standard CRUD operations.</p>
<h3>Code examples</h3>
<h4>Noncompliant code example</h4>
<pre data-diff-id="1" data-diff-type="noncompliant">
public interface PeopleResource extends PanacheEntityResource&lt;Person, Long&gt; {
}

// Noncompliant: this implementation will be completely ignored
public class PeopleResourceImpl implements PeopleResource {
@Override
public Person get(Long id) {
return Person.findById(id);
}
}
</pre>
<h4>Compliant solution</h4>
<pre data-diff-id="1" data-diff-type="compliant">
// Compliant: only the interface is needed, with custom methods as defaults
public interface PeopleResource extends PanacheEntityResource&lt;Person, Long&gt; {

@GET
@Path("/name/{name}")
@Produces("application/json")
default List&lt;Person&gt; findByName(@PathParam("name") String name) {
return Person.find("name = :name", Collections.singletonMap("name", name)).list();
}
}
</pre>
<h2>Resources</h2>
<h3>Documentation</h3>
<ul>
<li>Quarkus - Generating Jakarta REST resources with Panache - <a href="https://quarkus.io/guides/rest-data-panache">Official Quarkus documentation
on REST Data with Panache, including how to properly use resource interfaces and add custom methods</a></li>
<li>Quarkus - Adding additional methods to the generated resource - <a
href="https://quarkus.io/guides/rest-data-panache#adding-additional-methods-to-the-generated-resource">Specific section on how to correctly add
custom methods using default interface methods</a></li>
</ul>

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"title": "REST Data with Panache resource interfaces should not have implementation classes",
"type": "CODE_SMELL",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "10min"
},
"tags": [
"quarkus",
"rest",
"confusing"
],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-8913",
"sqKey": "S8913",
"scope": "All",
"quickfix": "unknown",
"code": {
"impacts": {
"MAINTAINABILITY": "MEDIUM"
},
"attribute": "CLEAR"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@
"S8786",
"S8909",
"S8911",
"S8913",
"S8924"
]
}
Loading