This is an automated email from the ASF dual-hosted git repository.

ndipiazza pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tika.git


The following commit(s) were added to refs/heads/main by this push:
     new 70da6e034 TIKA-4581: Fix packaging issues and allow plugin-roots 
override (#2486)
70da6e034 is described below

commit 70da6e03435812dc85c85c2948e8550595fc0794
Author: Nicholas DiPiazza <[email protected]>
AuthorDate: Sat Dec 27 11:53:24 2025 -0600

    TIKA-4581: Fix packaging issues and allow plugin-roots override (#2486)
    
    * TIKA-4581: Add --plugin-roots CLI parameter for tika-grpc
    
    Adds --plugin-roots command-line parameter to override plugin-roots from 
config file.
    
    Problem:
    Users had to include 'plugin-roots' in tika-config.json file, which made
    Docker deployments less flexible. Different environments might need 
different
    plugin locations.
    
    Solution:
    - Added --plugin-roots CLI parameter to TikaGrpcServer
    - Parameter accepts comma-separated list of plugin directories
    - CLI parameter overrides config file if specified
    - Falls back to config file if not specified
    
    Changes:
    - TikaGrpcServer: Added --plugin-roots parameter
    - TikaGrpcServerImpl: Updated constructor to accept pluginRootsOverride
    - TikaPluginManager: Added loadFromPaths() method for string-based paths
    
    Usage:
    java -jar tika-grpc.jar -c config.json --plugin-roots /tmp/tika-plugins
    
    Or Docker:
    docker run apache/tika-grpc:latest -c /config/config.json --plugin-roots 
/tmp/tika-plugins
    
    Benefits:
    - No need to modify config files for different environments
    - Simplifies Docker/Kubernetes deployments
    - Backward compatible - config file still works if CLI not specified
    
    * TIKA-4581: Copy all plugin ZIPs to tika-grpc/target/plugins
    
    Adds all 13 plugin modules to the copy-plugins execution:
    - tika-pipes-az-blob
    - tika-pipes-csv
    - tika-pipes-gcs
    - tika-pipes-ignite
    - tika-pipes-jdbc
    - tika-pipes-json
    - tika-pipes-kafka
    - tika-pipes-microsoft-graph
    - tika-pipes-opensearch
    - tika-pipes-s3
    - tika-pipes-solr
    
    This ensures all plugins are available for Docker builds.
    
    * TIKA-4581: Fix MANIFEST.MF extraction for az-blob, gcs, jdbc plugins
    
    The assembly.xml was trying to include MANIFEST.MF from classes/ directory
    but it only exists in the JAR file. Changed to use dependencySet with
    unpack to properly extract MANIFEST.MF and extensions.idx from the
    project artifact JAR into the plugin ZIP.
    
    This fixes 'Cannot find the manifest path' errors for these 3 plugins.
    
    * TIKA-4581: Fix GCS plugin class name in plugin.properties
    
    The plugin.properties referenced the wrong class name:
      Wrong: org.apache.tika.pipes.emitter.gcs.GCSEmitterPlugin
      Correct: org.apache.tika.pipes.plugin.gcs.GCSPipesPlugin
    
    This caused ClassNotFoundException when loading the GCS plugin.
---
 tika-grpc/pom.xml                                  | 77 +++++++++++++++++
 .../org/apache/tika/pipes/grpc/TikaGrpcServer.java |  5 +-
 .../apache/tika/pipes/grpc/TikaGrpcServerImpl.java | 12 ++-
 .../src/main/assembly/assembly.xml                 | 97 +++++++++++++---------
 .../tika-pipes-gcs/src/main/assembly/assembly.xml  | 97 +++++++++++++---------
 .../src/main/resources/plugin.properties           |  2 +-
 .../tika-pipes-jdbc/src/main/assembly/assembly.xml | 97 +++++++++++++---------
 .../org/apache/tika/plugins/TikaPluginManager.java | 31 +++++++
 8 files changed, 298 insertions(+), 120 deletions(-)

diff --git a/tika-grpc/pom.xml b/tika-grpc/pom.xml
index a30fb0c3f..88d919936 100644
--- a/tika-grpc/pom.xml
+++ b/tika-grpc/pom.xml
@@ -299,6 +299,83 @@
                   <type>zip</type>
                   <overWrite>true</overWrite>
                 </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-az-blob</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-csv</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-gcs</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-ignite</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-jdbc</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-json</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-kafka</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-microsoft-graph</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-opensearch</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-s3</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
+                <artifactItem>
+                  <groupId>org.apache.tika</groupId>
+                  <artifactId>tika-pipes-solr</artifactId>
+                  <version>${project.version}</version>
+                  <type>zip</type>
+                  <overWrite>true</overWrite>
+                </artifactItem>
               </artifactItems>
             </configuration>
           </execution>
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
index 2b350e382..810f58961 100644
--- a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServer.java
@@ -49,6 +49,9 @@ public class TikaGrpcServer {
     @Parameter(names = {"-l", "--plugins"}, description = "The tika pipes 
plugins config file", help = true)
     private File tikaPlugins;
 
+    @Parameter(names = {"--plugin-roots"}, description = "Comma-separated list 
of plugin root directories (overrides config file)", help = true)
+    private String pluginRoots;
+
     @Parameter(names = {"-s", "--secure"}, description = "Enable credentials 
required to access this grpc server")
     private boolean secure;
 
@@ -93,7 +96,7 @@ public class TikaGrpcServer {
         healthStatusManager.setStatus(TikaGrpcServer.class.getSimpleName(), 
ServingStatus.SERVING);
         server = Grpc
                 .newServerBuilderForPort(port, creds)
-                .addService(new 
TikaGrpcServerImpl(tikaConfigFile.getAbsolutePath()))
+                .addService(new 
TikaGrpcServerImpl(tikaConfigFile.getAbsolutePath(), pluginRoots))
                 .addService(healthStatusManager.getHealthService())
                 .addService(ProtoReflectionServiceV1.newInstance())
                 .build()
diff --git 
a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java 
b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
index de318e3fb..aa102a42a 100644
--- a/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
+++ b/tika-grpc/src/main/java/org/apache/tika/pipes/grpc/TikaGrpcServerImpl.java
@@ -83,6 +83,10 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
     PluginManager pluginManager;
 
     TikaGrpcServerImpl(String tikaConfigPath) throws TikaConfigException, 
IOException {
+        this(tikaConfigPath, null);
+    }
+
+    TikaGrpcServerImpl(String tikaConfigPath, String pluginRootsOverride) 
throws TikaConfigException, IOException {
         File tikaConfigFile = new File(tikaConfigPath);
         if (!tikaConfigFile.exists()) {
             throw new TikaConfigException("Tika config file does not exist: " 
+ tikaConfigPath);
@@ -102,7 +106,13 @@ class TikaGrpcServerImpl extends TikaGrpc.TikaImplBase {
         pipesClient = new PipesClient(pipesConfig, configPath);
         
         try {
-            pluginManager = TikaPluginManager.load(tikaJsonConfig);
+            if (pluginRootsOverride != null && 
!pluginRootsOverride.trim().isEmpty()) {
+                // Use command-line plugin roots
+                pluginManager = 
TikaPluginManager.loadFromPaths(pluginRootsOverride);
+            } else {
+                // Use plugin roots from config file
+                pluginManager = TikaPluginManager.load(tikaJsonConfig);
+            }
             pluginManager.loadPlugins();
             pluginManager.startPlugins();
         } catch (TikaConfigException e) {
diff --git 
a/tika-pipes/tika-pipes-plugins/tika-pipes-az-blob/src/main/assembly/assembly.xml
 
b/tika-pipes/tika-pipes-plugins/tika-pipes-az-blob/src/main/assembly/assembly.xml
index 0b8fe6e79..35755f0d1 100644
--- 
a/tika-pipes/tika-pipes-plugins/tika-pipes-az-blob/src/main/assembly/assembly.xml
+++ 
b/tika-pipes/tika-pipes-plugins/tika-pipes-az-blob/src/main/assembly/assembly.xml
@@ -1,45 +1,64 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  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
+ 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
+      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.
+ 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.
 -->
-<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0";
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 
http://maven.apache.org/xsd/assembly-2.2.0.xsd";>
-    <id>plugin</id>
-    <formats>
-        <format>zip</format>
-    </formats>
-    <includeBaseDirectory>true</includeBaseDirectory>
-    <baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
-    <fileSets>
-        <fileSet>
-            <directory>${project.build.directory}</directory>
-            <outputDirectory>/</outputDirectory>
-            <includes>
-                <include>${project.artifactId}-${project.version}.jar</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>${project.build.directory}/lib</directory>
-            <outputDirectory>/lib</outputDirectory>
-            <includes>
-                <include>*.jar</include>
-            </includes>
-        </fileSet>
-    </fileSets>
+<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xmlns="http://maven.apache.org/ASSEMBLY/2.0.0";
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
+                              http://maven.apache.org/xsd/assembly-2.0.0.xsd";>
+  <id>dependencies-zip</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <useProjectArtifact>true</useProjectArtifact>
+      <unpack>true</unpack>
+      <scope>runtime</scope>
+      <includes>
+        <include>${project.groupId}:${project.artifactId}</include>
+      </includes>
+      <unpackOptions>
+        <includes>
+          <include>META-INF/MANIFEST.MF</include>
+          <include>META-INF/extensions.idx</include>
+        </includes>
+      </unpackOptions>
+    </dependencySet>
+  </dependencySets>
+  <fileSets>
+    <fileSet>
+      <directory>${project.build.directory}/lib</directory>
+      <outputDirectory>/lib</outputDirectory>
+    </fileSet>
+    <fileSet>
+      <directory>${project.build.directory}</directory>
+      <outputDirectory>/lib</outputDirectory>
+      <includes>
+        <include>${project.artifactId}-${project.version}.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/src/main/resources</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>plugin.properties</include>
+      </includes>
+    </fileSet>
+  </fileSets>
 </assembly>
diff --git 
a/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/assembly/assembly.xml 
b/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/assembly/assembly.xml
index 0b8fe6e79..35755f0d1 100644
--- 
a/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/assembly/assembly.xml
+++ 
b/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/assembly/assembly.xml
@@ -1,45 +1,64 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  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
+ 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
+      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.
+ 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.
 -->
-<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0";
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 
http://maven.apache.org/xsd/assembly-2.2.0.xsd";>
-    <id>plugin</id>
-    <formats>
-        <format>zip</format>
-    </formats>
-    <includeBaseDirectory>true</includeBaseDirectory>
-    <baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
-    <fileSets>
-        <fileSet>
-            <directory>${project.build.directory}</directory>
-            <outputDirectory>/</outputDirectory>
-            <includes>
-                <include>${project.artifactId}-${project.version}.jar</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>${project.build.directory}/lib</directory>
-            <outputDirectory>/lib</outputDirectory>
-            <includes>
-                <include>*.jar</include>
-            </includes>
-        </fileSet>
-    </fileSets>
+<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xmlns="http://maven.apache.org/ASSEMBLY/2.0.0";
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
+                              http://maven.apache.org/xsd/assembly-2.0.0.xsd";>
+  <id>dependencies-zip</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <useProjectArtifact>true</useProjectArtifact>
+      <unpack>true</unpack>
+      <scope>runtime</scope>
+      <includes>
+        <include>${project.groupId}:${project.artifactId}</include>
+      </includes>
+      <unpackOptions>
+        <includes>
+          <include>META-INF/MANIFEST.MF</include>
+          <include>META-INF/extensions.idx</include>
+        </includes>
+      </unpackOptions>
+    </dependencySet>
+  </dependencySets>
+  <fileSets>
+    <fileSet>
+      <directory>${project.build.directory}/lib</directory>
+      <outputDirectory>/lib</outputDirectory>
+    </fileSet>
+    <fileSet>
+      <directory>${project.build.directory}</directory>
+      <outputDirectory>/lib</outputDirectory>
+      <includes>
+        <include>${project.artifactId}-${project.version}.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/src/main/resources</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>plugin.properties</include>
+      </includes>
+    </fileSet>
+  </fileSets>
 </assembly>
diff --git 
a/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/resources/plugin.properties
 
b/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/resources/plugin.properties
index fa83efcfb..0102ccbe2 100644
--- 
a/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/resources/plugin.properties
+++ 
b/tika-pipes/tika-pipes-plugins/tika-pipes-gcs/src/main/resources/plugin.properties
@@ -15,7 +15,7 @@
 # limitations under the License.
 
 plugin.id=tika-pipes-gcs-plugin
-plugin.class=org.apache.tika.pipes.emitter.gcs.GCSEmitterPlugin
+plugin.class=org.apache.tika.pipes.plugin.gcs.GCSPipesPlugin
 plugin.version=4.0.0-SNAPSHOT
 plugin.provider=Apache Tika
 plugin.description=Pipes for the Google Cloud Storage
diff --git 
a/tika-pipes/tika-pipes-plugins/tika-pipes-jdbc/src/main/assembly/assembly.xml 
b/tika-pipes/tika-pipes-plugins/tika-pipes-jdbc/src/main/assembly/assembly.xml
index 0b8fe6e79..35755f0d1 100644
--- 
a/tika-pipes/tika-pipes-plugins/tika-pipes-jdbc/src/main/assembly/assembly.xml
+++ 
b/tika-pipes/tika-pipes-plugins/tika-pipes-jdbc/src/main/assembly/assembly.xml
@@ -1,45 +1,64 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!--
-  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
+ 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
+      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.
+ 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.
 -->
-<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0";
-          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
-          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 
http://maven.apache.org/xsd/assembly-2.2.0.xsd";>
-    <id>plugin</id>
-    <formats>
-        <format>zip</format>
-    </formats>
-    <includeBaseDirectory>true</includeBaseDirectory>
-    <baseDirectory>${project.artifactId}-${project.version}</baseDirectory>
-    <fileSets>
-        <fileSet>
-            <directory>${project.build.directory}</directory>
-            <outputDirectory>/</outputDirectory>
-            <includes>
-                <include>${project.artifactId}-${project.version}.jar</include>
-            </includes>
-        </fileSet>
-        <fileSet>
-            <directory>${project.build.directory}/lib</directory>
-            <outputDirectory>/lib</outputDirectory>
-            <includes>
-                <include>*.jar</include>
-            </includes>
-        </fileSet>
-    </fileSets>
+<assembly xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+          xmlns="http://maven.apache.org/ASSEMBLY/2.0.0";
+          xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0
+                              http://maven.apache.org/xsd/assembly-2.0.0.xsd";>
+  <id>dependencies-zip</id>
+  <formats>
+    <format>zip</format>
+  </formats>
+  <includeBaseDirectory>false</includeBaseDirectory>
+  <dependencySets>
+    <dependencySet>
+      <outputDirectory>/</outputDirectory>
+      <useProjectArtifact>true</useProjectArtifact>
+      <unpack>true</unpack>
+      <scope>runtime</scope>
+      <includes>
+        <include>${project.groupId}:${project.artifactId}</include>
+      </includes>
+      <unpackOptions>
+        <includes>
+          <include>META-INF/MANIFEST.MF</include>
+          <include>META-INF/extensions.idx</include>
+        </includes>
+      </unpackOptions>
+    </dependencySet>
+  </dependencySets>
+  <fileSets>
+    <fileSet>
+      <directory>${project.build.directory}/lib</directory>
+      <outputDirectory>/lib</outputDirectory>
+    </fileSet>
+    <fileSet>
+      <directory>${project.build.directory}</directory>
+      <outputDirectory>/lib</outputDirectory>
+      <includes>
+        <include>${project.artifactId}-${project.version}.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>${project.basedir}/src/main/resources</directory>
+      <outputDirectory>/</outputDirectory>
+      <includes>
+        <include>plugin.properties</include>
+      </includes>
+    </fileSet>
+  </fileSets>
 </assembly>
diff --git 
a/tika-plugins-core/src/main/java/org/apache/tika/plugins/TikaPluginManager.java
 
b/tika-plugins-core/src/main/java/org/apache/tika/plugins/TikaPluginManager.java
index 638ed5b59..423b84b97 100644
--- 
a/tika-plugins-core/src/main/java/org/apache/tika/plugins/TikaPluginManager.java
+++ 
b/tika-plugins-core/src/main/java/org/apache/tika/plugins/TikaPluginManager.java
@@ -134,6 +134,37 @@ public class TikaPluginManager extends 
DefaultPluginManager {
         return new TikaPluginManager(roots);
     }
 
+    /**
+     * Loads plugin manager from a comma-separated string of paths.
+     *
+     * @param pathsString comma-separated list of plugin root directories
+     * @return the plugin manager
+     * @throws TikaConfigException if configuration is invalid
+     * @throws IOException if reading or plugin initialization fails
+     */
+    public static TikaPluginManager loadFromPaths(String pathsString) 
+            throws TikaConfigException, IOException {
+        if (pathsString == null || pathsString.trim().isEmpty()) {
+            throw new TikaConfigException("plugin-roots must not be empty");
+        }
+        
+        configurePf4jRuntimeMode();
+        
+        List<Path> roots = new java.util.ArrayList<>();
+        for (String path : pathsString.split(",")) {
+            String trimmed = path.trim();
+            if (!trimmed.isEmpty()) {
+                roots.add(java.nio.file.Paths.get(trimmed));
+            }
+        }
+        
+        if (roots.isEmpty()) {
+            throw new TikaConfigException("plugin-roots must not be empty");
+        }
+        
+        return new TikaPluginManager(roots);
+    }
+
     /**
      * Loads plugin manager from a configuration file.
      *

Reply via email to