Skip to content
Merged
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
Expand Up @@ -16,11 +16,14 @@
*/
package org.apache.tika.language.translate;

import org.apache.tika.config.TikaComponent;

/**
* Dummy translator that always declines to give any text. Useful as a
* sentinel translator for when none others are available.
* for unknown document types.
*/
@TikaComponent
public class EmptyTranslator implements Translator {
public String translate(String text, String sourceLanguage, String targetLanguage) {
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ public class EmitterManager {
private final Map<String, Emitter> emitterMap = new ConcurrentHashMap<>();

public static EmitterManager load(PluginManager pluginManager, TikaConfigs tikaConfigs) throws IOException, TikaConfigException {
JsonNode fetchersNode = tikaConfigs.getRoot().get(CONFIG_KEY);
JsonNode fetchersNode = tikaConfigs.getTikaJsonConfig()
.getRootNode().get(CONFIG_KEY);
Map<String, Emitter> fetchers =
PluginComponentLoader.loadInstances(pluginManager, EmitterFactory.class, fetchersNode);
return new EmitterManager(fetchers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ public class FetcherManager {


public static FetcherManager load(PluginManager pluginManager, TikaConfigs tikaConfigs) throws TikaConfigException, IOException {
JsonNode fetchersNode = tikaConfigs.getRoot().get(CONFIG_KEY);
JsonNode fetchersNode = tikaConfigs.getTikaJsonConfig()
.getRootNode().get(CONFIG_KEY);
Map<String, Fetcher> fetchers =
PluginComponentLoader.loadInstances(pluginManager, FetcherFactory.class, fetchersNode);
return new FetcherManager(fetchers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class PipesIteratorManager {

public static Optional<PipesIterator> load(PluginManager pluginManager, TikaConfigs tikaConfigs) throws IOException, TikaConfigException {

JsonNode node = tikaConfigs.getRoot().get(CONFIG_KEY);
JsonNode node = tikaConfigs.getTikaJsonConfig()
.getRootNode().get(CONFIG_KEY);

return PluginComponentLoader.loadSingleton(pluginManager, PipesIteratorFactory.class, node);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ public class ReporterManager {

public static PipesReporter load(PluginManager pluginManager, TikaConfigs tikaConfigs) throws IOException, TikaConfigException {

JsonNode node = tikaConfigs.getRoot().get(CONFIG_KEY);
JsonNode node = tikaConfigs.getTikaJsonConfig()
.getRootNode().get(CONFIG_KEY);

List<PipesReporter> reporters = PluginComponentLoader.loadUnnamedInstances(pluginManager, PipesReporterFactory.class, node);
if (reporters.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@
import java.util.Iterator;
import java.util.Set;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.config.loader.TikaJsonConfig;
import org.apache.tika.exception.TikaConfigException;
Expand Down Expand Up @@ -66,9 +64,6 @@ public class TikaConfigs {
"server"
);

static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);

private final TikaJsonConfig tikaJsonConfig;

/**
Expand Down Expand Up @@ -113,17 +108,6 @@ public TikaJsonConfig getTikaJsonConfig() {
return tikaJsonConfig;
}

/**
* Gets the root JSON node.
* Deprecated - use {@link #getTikaJsonConfig()} instead.
*
* @return the root JSON node
*/
@Deprecated
public JsonNode getRoot() {
return tikaJsonConfig.getRootNode();
}

/**
* Deserializes a configuration value for the given key.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import java.util.List;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.pf4j.DefaultExtensionFinder;
import org.pf4j.DefaultPluginManager;
import org.pf4j.ExtensionFinder;
Expand All @@ -43,6 +45,14 @@ public class TikaPluginManager extends DefaultPluginManager {

private static final Logger LOG = LoggerFactory.getLogger(TikaPluginManager.class);

//we're only using this to convert a single path or a list of paths to a list
//we don't need all the functionality of the polymorphic objectmapper in tika-serialization
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

static {
OBJECT_MAPPER.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true);
}

/**
* Loads plugin manager from a pre-parsed TikaJsonConfig.
* This is the preferred method when sharing configuration across
Expand Down Expand Up @@ -83,12 +93,12 @@ public static TikaPluginManager load(Path configPath) throws TikaConfigException
*/
public static TikaPluginManager load(TikaConfigs tikaConfigs)
throws TikaConfigException, IOException {
JsonNode root = tikaConfigs.getRoot();
JsonNode root = tikaConfigs.getTikaJsonConfig().getRootNode();
JsonNode pluginRoots = root.get("plugin-roots");
if (pluginRoots == null) {
throw new TikaConfigException("plugin-roots must be specified");
}
List<Path> roots = TikaConfigs.OBJECT_MAPPER.convertValue(pluginRoots,
List<Path> roots = OBJECT_MAPPER.convertValue(pluginRoots,
new TypeReference<List<Path>>() {});
if (roots.isEmpty()) {
throw new TikaConfigException("plugin-roots must not be empty");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ public void testGetRootReturnsJsonNode() throws Exception {
""";

TikaConfigs configs = loadFromString(json);
assertNotNull(configs.getRoot());
assertNotNull(configs.getRoot().get("fetchers"));
assertNotNull(configs.getTikaJsonConfig().getRootNode());
assertNotNull(configs.getTikaJsonConfig().getRootNode().get("fetchers"));
}

private TikaConfigs loadFromString(String json) throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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.tika.config.loader;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.apache.tika.config.JsonConfig;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.utils.ServiceLoaderUtils;

/**
* Utility class for instantiating Tika components from JSON configuration.
* Provides common logic for all component loaders to avoid code duplication.
*/
public class ComponentInstantiator {

/**
* Instantiates a component with JsonConfig constructor or falls back to zero-arg constructor.
* <p>
* Instantiation strategy:
* <ol>
* <li>Try constructor with JsonConfig parameter</li>
* <li>If not found and JSON config has actual configuration, throw error</li>
* <li>Otherwise fall back to zero-arg constructor via ServiceLoader</li>
* </ol>
*
* @param componentClass the component class to instantiate
* @param jsonConfig the JSON configuration for the component
* @param classLoader the class loader to use
* @param componentTypeName the component type name (e.g., "Detector", "Parser") for error messages
* @param objectMapper the Jackson ObjectMapper for parsing JSON
* @param <T> the component type
* @return the instantiated component
* @throws TikaConfigException if instantiation fails
*/
@SuppressWarnings("unchecked")
public static <T> T instantiate(Class<?> componentClass,
JsonConfig jsonConfig,
ClassLoader classLoader,
String componentTypeName,
ObjectMapper objectMapper)
throws TikaConfigException {
try {
T component;

// Try constructor with JsonConfig parameter
try {
Constructor<?> constructor = componentClass.getConstructor(JsonConfig.class);
component = (T) constructor.newInstance(jsonConfig);
} catch (NoSuchMethodException e) {
// Check if JSON config has actual configuration
if (hasConfiguration(jsonConfig, objectMapper)) {
throw new TikaConfigException(
componentTypeName + " '" + componentClass.getName() + "' has configuration in JSON, " +
"but does not have a constructor that accepts JsonConfig. " +
"Please add a constructor: public " + componentClass.getSimpleName() + "(JsonConfig jsonConfig)");
}
// Fall back to zero-arg constructor if no configuration provided
component = (T) ServiceLoaderUtils.newInstance(componentClass,
new org.apache.tika.config.ServiceLoader(classLoader));
}

return component;
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new TikaConfigException("Failed to instantiate " + componentTypeName + ": " +
componentClass.getName(), e);
}
}

/**
* Checks if the JsonConfig contains actual configuration (non-empty JSON object with fields).
*
* @param jsonConfig the JSON configuration
* @param objectMapper the Jackson ObjectMapper for parsing JSON
* @return true if there's meaningful configuration, false if empty or just "{}"
*/
public static boolean hasConfiguration(JsonConfig jsonConfig, ObjectMapper objectMapper) {
if (jsonConfig == null) {
return false;
}
String json = jsonConfig.json();
if (json == null || json.trim().isEmpty()) {
return false;
}
// Parse to check if it's an empty object or has actual fields
try {
JsonNode node = objectMapper.readTree(json);
// Check if it's an object and has at least one field
if (node.isObject() && node.size() > 0) {
return true;
}
return false;
} catch (Exception e) {
// If we can't parse it, assume it has configuration to be safe
return true;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
*/
package org.apache.tika.config.loader;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
Expand All @@ -32,7 +30,6 @@

import org.apache.tika.config.JsonConfig;
import org.apache.tika.exception.TikaConfigException;
import org.apache.tika.utils.ServiceLoaderUtils;

/**
* Generic loader for Tika components (detectors, encoding detectors, filters, etc.).
Expand Down Expand Up @@ -172,58 +169,10 @@ private T loadConfiguredComponent(String name, JsonNode configNode,
}
}

@SuppressWarnings("unchecked")
private T instantiateComponent(Class<?> componentClass, JsonConfig configJson)
throws TikaConfigException {
try {
// Try constructor with JsonConfig parameter
try {
Constructor<?> constructor = componentClass.getConstructor(JsonConfig.class);
return (T) constructor.newInstance(configJson);
} catch (NoSuchMethodException e) {
// Check if JSON config has actual configuration
if (hasConfiguration(configJson)) {
throw new TikaConfigException(
"Component '" + componentClass.getName() + "' has configuration in JSON, " +
"but does not have a constructor that accepts JsonConfig. " +
"Please add a constructor: public " + componentClass.getSimpleName() + "(JsonConfig jsonConfig)");
}
// Fall back to zero-arg constructor if no configuration provided
return (T) ServiceLoaderUtils.newInstance(componentClass,
new org.apache.tika.config.ServiceLoader(classLoader));
}
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new TikaConfigException("Failed to instantiate component: " +
componentClass.getName(), e);
}
}

/**
* Checks if the JsonConfig contains actual configuration (non-empty JSON object with fields).
*
* @param jsonConfig the JSON configuration
* @return true if there's meaningful configuration, false if empty or just "{}"
*/
private boolean hasConfiguration(JsonConfig jsonConfig) {
if (jsonConfig == null) {
return false;
}
String json = jsonConfig.json();
if (json == null || json.trim().isEmpty()) {
return false;
}
// Parse to check if it's an empty object or has actual fields
try {
JsonNode node = objectMapper.readTree(json);
// Check if it's an object and has at least one field
if (node.isObject() && node.size() > 0) {
return true;
}
return false;
} catch (Exception e) {
// If we can't parse it, assume it has configuration to be safe
return true;
}
return ComponentInstantiator.instantiate(componentClass, configJson, classLoader,
componentTypeName, objectMapper);
}

private List<T> loadSpiComponents() {
Expand Down
Loading