Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
386ee24
PluginManager: add realm-aware loadServices(Class<T>) helper
gortiz Apr 30, 2026
0c4d3e6
PluginManager.loadServices: skip per-entry ServiceConfigurationError
gortiz Apr 30, 2026
9b6a78b
Migrate ServiceLoader.load(X) call sites to PluginManager.loadService…
gortiz Apr 30, 2026
4d81dec
AccessControlFactory.loadFactory: resolve impl via PluginManager.load…
gortiz Apr 30, 2026
46db4e5
PinotMetricUtils: discover MetricsFactory via PluginManager.loadServices
gortiz Apr 30, 2026
289cc16
Tests: add @AutoService to FakeMetricsFactory so loadServices finds it
gortiz Apr 30, 2026
b7d5a56
Address review: thread-safe loadServices snapshot + accurate docs
gortiz Apr 30, 2026
61ff2cb
pinot-tools: depend on pinot-avro-base library instead of pinot-avro …
gortiz Apr 30, 2026
d43d0cb
PluginManager: realm-walk fallback + jar-resident properties + strong…
gortiz May 4, 2026
bc7d2e0
Phase 2: ship pinot-plugin.properties from every plugin module
gortiz May 4, 2026
09fa7c9
Add pinot-plugin-verifier: standalone tool for end-to-end plugin veri…
gortiz May 5, 2026
406e9c5
verifier README: note that pinot-fastdev must be off when building th…
gortiz May 6, 2026
b1ce24c
Phase 1 (task 11): hoist plugin utility classes into -base library mo…
gortiz May 11, 2026
477b391
Phase 3: remove org.apache.pinot.shaded.* relocations from global sha…
gortiz May 11, 2026
d1f785f
Phase 3 (plugin-zip): switch distribution from shaded plugin fat-jars…
gortiz May 11, 2026
14ab5fc
Simplify plugin-zip layout: drop plugin.type directory level
gortiz May 11, 2026
9e07a66
Fix 5 plugin-verifier failures in the plugin-zip distribution layout
gortiz May 11, 2026
276998f
Add pinot-kafka-4.0 to the plugin-zip distribution layout
gortiz May 12, 2026
8df36f0
Fix CI failures: checkstyle violations and apache-rat unapproved license
gortiz May 13, 2026
ea2edc8
pinot-plugin-verifier: add Apache license header to README.md
gortiz May 13, 2026
9bf55a9
Merge remote-tracking branch 'origin/master' into pinot-no-shading
gortiz May 20, 2026
cdc6a7a
pinot-thrift: add explicit commons-lang3 compile dep
gortiz May 20, 2026
4a2a06b
Fix 3 CI failures: checkstyle, minion compile, integration-test compile
gortiz May 20, 2026
918b221
Fix 2 more CI failures: broker test method names and smoke test field…
gortiz May 21, 2026
4346894
Fix enforcer violation: add pinot-distribution to skipModules
gortiz May 21, 2026
c717b6c
Fix compat-test failure: add pinot-json back to pinot-tools as runtim…
gortiz May 21, 2026
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
6 changes: 4 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,10 @@ Apache Pinot is a real-time distributed OLAP datastore for low-latency analytics
- `pinot-confluent-json`: Confluent Schema Registry JSON input support.
- `pinot-confluent-protobuf`: Confluent Schema Registry Protobuf input support.
- `pinot-orc`: ORC input format support.
- `pinot-json`: JSON input format support.
- `pinot-parquet`: Parquet input format support.
- `pinot-json-base`: shared JSON utilities and base classes (JSONRecordReader, JSONRecordExtractor).
- `pinot-json`: JSON plugin (JSONMessageDecoder); depends on pinot-json-base.
- `pinot-parquet-base`: shared Parquet utilities (ParquetUtils); depends on parquet-avro and hadoop.
- `pinot-parquet`: Parquet plugin (ParquetRecordReader and variants); depends on pinot-parquet-base.
- `pinot-csv`: CSV input format support.
- `pinot-thrift`: Thrift input format support.
- `pinot-protobuf`: Protobuf input format support.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.apache.pinot.broker.grpc.GrpcRequesterIdentity;
import org.apache.pinot.spi.auth.broker.RequesterIdentity;
import org.apache.pinot.spi.env.PinotConfiguration;
import org.apache.pinot.spi.plugin.PluginManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -62,8 +63,16 @@ public static AccessControlFactory loadFactory(PinotConfiguration configuration,
}
try {
LOGGER.info("Instantiating Access control factory class {}", accessControlFactoryClassName);
accessControlFactory =
(AccessControlFactory) Class.forName(accessControlFactoryClassName).getDeclaredConstructor().newInstance();
// PluginManager.loadClass is realm-aware (consults plugin realms in addition to the
// system classloader). We deliberately keep the explicit getDeclaredConstructor() +
// newInstance() pattern instead of going through PluginManager.createInstance, because
// the former preserves the historical contract that factories with package-private
// no-arg constructors load successfully (PluginManager.createInstance uses
// getConstructor() which only finds public ones). Truly-private constructors still
// require setAccessible(true), which we do not do — match the prior Class.forName
// behaviour exactly.
Class<?> clazz = PluginManager.get().loadClass(accessControlFactoryClassName);
accessControlFactory = (AccessControlFactory) clazz.getDeclaredConstructor().newInstance();
LOGGER.info("Initializing Access control factory class {}", accessControlFactoryClassName);
accessControlFactory.init(configuration, propertyStore);
return accessControlFactory;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/**
* 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.pinot.broker.broker;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.pinot.spi.env.PinotConfiguration;
import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;


/**
* Regression tests for {@link AccessControlFactory#loadFactory(org.apache.pinot.spi.env.PinotConfiguration,
* org.apache.helix.store.zk.ZkHelixPropertyStore)}. The loadFactory resolution is realm-aware via
* PluginManager.loadClass, so plugin-realm-resident factories work the same as in-tree ones — but the
* non-public-constructor contract from the historical Class.forName(...).getDeclaredConstructor()
* pattern must be preserved.
*/
public class AccessControlFactoryLoaderTest {

@Test
public void loadFactoryWithNullClassConfigReturnsAllowAllAccessControlFactory() {
PinotConfiguration emptyConfig = new PinotConfiguration(Collections.emptyMap());
AccessControlFactory factory = AccessControlFactory.loadFactory(emptyConfig, null);
assertTrue(factory instanceof AllowAllAccessControlFactory,
"Expected AllowAllAccessControlFactory when no `class` config is set, got: " + factory.getClass());
}

@Test
public void loadFactoryWithExplicitInTreeFqcnResolvesViaSystemClassloader() {
Map<String, Object> props = new HashMap<>();
props.put(AccessControlFactory.ACCESS_CONTROL_CLASS_CONFIG, AllowAllAccessControlFactory.class.getName());
AccessControlFactory factory = AccessControlFactory.loadFactory(new PinotConfiguration(props), null);
assertEquals(factory.getClass(), AllowAllAccessControlFactory.class);
}

@Test
public void loadFactoryWithUnknownClassThrowsRuntimeExceptionWrappingClassNotFound() {
Map<String, Object> props = new HashMap<>();
props.put(AccessControlFactory.ACCESS_CONTROL_CLASS_CONFIG, "org.apache.pinot.does.not.Exist");
try {
AccessControlFactory.loadFactory(new PinotConfiguration(props), null);
fail("Expected RuntimeException for unknown class");
} catch (RuntimeException thrown) {
Throwable cause = thrown.getCause();
assertTrue(cause instanceof ClassNotFoundException || cause instanceof NoClassDefFoundError
|| (cause != null && cause.getCause() instanceof ClassNotFoundException),
"Expected ClassNotFoundException as the root cause, got: " + cause);
}
}

/**
* Regression test: the historical loader used getDeclaredConstructor() which finds non-public no-arg
* constructors. The realm-aware migration must preserve that contract or factories with
* package-private / protected constructors would silently break on upgrade.
*/
@Test
public void loadFactoryWithPackagePrivateConstructorSucceeds() {
Map<String, Object> props = new HashMap<>();
props.put(AccessControlFactory.ACCESS_CONTROL_CLASS_CONFIG, PackagePrivateConstructorFactory.class.getName());
AccessControlFactory factory = AccessControlFactory.loadFactory(new PinotConfiguration(props), null);
assertEquals(factory.getClass(), PackagePrivateConstructorFactory.class);
}

/** Test fixture: factory whose no-arg constructor is package-private. */
public static class PackagePrivateConstructorFactory extends AccessControlFactory {
PackagePrivateConstructorFactory() {
}

@Override
public org.apache.pinot.broker.api.AccessControl create() {
throw new UnsupportedOperationException("test fixture");
}
}
}
22 changes: 22 additions & 0 deletions pinot-clients/pinot-java-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,28 @@
<target>11</target>
</configuration>
</plugin>
<!-- Driver jar: relocate bundled jackson/guava/scala so they don't leak
onto the embedding application's classpath. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>${shade.prefix}.com.fasterxml.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>${shade.prefix}.com.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>org.scala-lang</pattern>
<shadedPattern>${shade.prefix}.org.scala-lang</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
Expand Down
35 changes: 23 additions & 12 deletions pinot-clients/pinot-jdbc-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<url>https://pinot.apache.org/</url>
<properties>
<pinot.root>${basedir}/../..</pinot.root>
<shade.phase.prop>package</shade.phase.prop>
</properties>
<build>
<resources>
Expand Down Expand Up @@ -59,6 +60,28 @@
<target>11</target>
</configuration>
</plugin>
<!-- Driver jar: relocate bundled jackson/guava/scala so they don't leak
onto the embedding application's classpath. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<configuration>
<relocations>
<relocation>
<pattern>com.fasterxml.jackson</pattern>
<shadedPattern>${shade.prefix}.com.fasterxml.jackson</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>${shade.prefix}.com.google.common</shadedPattern>
</relocation>
<relocation>
<pattern>org.scala-lang</pattern>
<shadedPattern>${shade.prefix}.org.scala-lang</shadedPattern>
</relocation>
</relocations>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
Expand All @@ -76,18 +99,6 @@
</dependency>
</dependencies>
<profiles>
<profile>
<id>build-shaded-jar</id>
<activation>
<property>
<name>skipShade</name>
<value>!true</value>
</property>
</activation>
<properties>
<shade.phase.prop>package</shade.phase.prop>
</properties>
</profile>
<profile>
<id>pinot-fastdev</id>
<properties>
Expand Down
9 changes: 0 additions & 9 deletions pinot-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,6 @@
</build>
</profile>

<profile>
<id>build-shaded-jar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<shade.phase.prop>package</shade.phase.prop>
</properties>
</profile>
<profile>
<id>pinot-fastdev</id>
<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
package org.apache.pinot.common.utils;

import com.google.common.annotations.VisibleForTesting;
import java.util.ServiceLoader;
import org.apache.pinot.spi.plugin.PluginManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
* Singleton holder for {@link LogicalTableConfigSerDe}, loaded via {@link ServiceLoader}.
* Singleton holder for {@link LogicalTableConfigSerDe}, loaded via {@link PluginManager#loadServices(Class)}.
*
* <p>When multiple implementations are discovered, the one with the highest
* {@link LogicalTableConfigSerDe#getPriority()} is selected</p>
Expand Down Expand Up @@ -54,7 +54,7 @@ private static LogicalTableConfigSerDe fromServiceLoader() {
LogicalTableConfigSerDe best = null;
int bestPriority = Integer.MIN_VALUE;

for (LogicalTableConfigSerDe serDe : ServiceLoader.load(LogicalTableConfigSerDe.class)) {
for (LogicalTableConfigSerDe serDe : PluginManager.get().loadServices(LogicalTableConfigSerDe.class)) {
LOGGER.info("Discovered LogicalTableConfigSerDe: {} with priority {}",
serDe.getClass().getName(), serDe.getPriority());
if (serDe.getPriority() > bestPriority) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.pinot.plugin.metrics.fake;

import com.google.auto.service.AutoService;
import java.util.function.Function;
import org.apache.pinot.spi.annotations.metrics.MetricsFactory;
import org.apache.pinot.spi.annotations.metrics.PinotMetricsFactory;
Expand All @@ -31,7 +32,13 @@
/**
* A test-only {@link PinotMetricsFactory} backed by the in-memory {@link FakePinotMetricsRegistry}. Used by
* pinot-common tests so they can exercise {@code AbstractMetrics} without depending on a plugin module.
*
* <p>Carries both {@code @AutoService(PinotMetricsFactory.class)} (generates the
* {@code META-INF/services} entry consumed by {@code PinotMetricUtils} via
* {@code PluginManager.loadServices}) and the legacy {@code @MetricsFactory} annotation which the
* factory loader still inspects to honour {@code enabled = false}.</p>
*/
@AutoService(PinotMetricsFactory.class)
@MetricsFactory
public class FakeMetricsFactory implements PinotMetricsFactory {
private PinotMetricsRegistry _registry;
Expand Down
9 changes: 0 additions & 9 deletions pinot-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,6 @@
</dependencies>

<profiles>
<profile>
<id>build-shaded-jar</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<properties>
<shade.phase.prop>package</shade.phase.prop>
</properties>
</profile>
<profile>
<id>pinot-fastdev</id>
<properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
package org.apache.pinot.core.routing.timeboundary;

import java.util.Map;
import java.util.ServiceLoader;
import org.apache.pinot.spi.plugin.PluginManager;


public class TimeBoundaryStrategyService {
Expand All @@ -33,7 +33,7 @@ private TimeBoundaryStrategyService(Map<String, TimeBoundaryStrategy> strategyMa

public static TimeBoundaryStrategyService fromServiceLoader() {
Map<String, TimeBoundaryStrategy> strategyMap = new java.util.HashMap<>();
for (TimeBoundaryStrategy strategy : ServiceLoader.load(TimeBoundaryStrategy.class)) {
for (TimeBoundaryStrategy strategy : PluginManager.get().loadServices(TimeBoundaryStrategy.class)) {
String strategyName = strategy.getName();
if (strategyMap.containsKey(strategyName)) {
throw new IllegalStateException("Duplicate TimeBoundaryStrategy found: " + strategyName);
Expand Down
Loading
Loading