Skip to content

Commit ffd7129

Browse files
authored
TIKA-4750 - improve error msg when component not on classpath (#2868)
1 parent 127665a commit ffd7129

2 files changed

Lines changed: 90 additions & 4 deletions

File tree

tika-serialization/src/main/java/org/apache/tika/serialization/ComponentNameResolver.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222
import java.util.Optional;
2323
import java.util.Set;
24+
import java.util.TreeSet;
2425
import java.util.concurrent.ConcurrentHashMap;
2526

2627
import org.apache.tika.config.loader.ComponentInfo;
@@ -113,10 +114,50 @@ public static Class<?> resolveClass(String name, ClassLoader classLoader)
113114
}
114115
}
115116
}
116-
throw new ClassNotFoundException(
117-
"Component '" + name + "' is not registered. " +
118-
"Components must be registered via @TikaComponent annotation or .idx file. " +
119-
"Arbitrary class names are not allowed for security reasons.");
117+
throw new ClassNotFoundException(unregisteredMessage(name));
118+
}
119+
120+
/**
121+
* Builds a diagnostic message for an unregistered component name. It calls out the
122+
* two usual causes -- the name is misspelled, or the module that provides it is not
123+
* on the classpath (optional components such as the Tess4J OCR parser ship in
124+
* separate jars that must be added explicitly) -- and lists the names that
125+
* <em>are</em> registered so the caller can find the right one (or notice that
126+
* nothing registered, which means no {@code .idx} files were on the classpath).
127+
*/
128+
private static String unregisteredMessage(String name) {
129+
TreeSet<String> known = new TreeSet<>();
130+
for (ComponentRegistry registry : REGISTRIES.values()) {
131+
known.addAll(registry.getAllComponents().keySet());
132+
}
133+
StringBuilder sb = new StringBuilder()
134+
.append("Component '").append(name).append("' is not registered. ")
135+
.append("Either the name is misspelled, or the module that provides it is ")
136+
.append("not on the classpath -- optional components (for example the Tess4J ")
137+
.append("OCR parser in tika-parser-tess4j-module) ship as separate jars that ")
138+
.append("must be added explicitly. ");
139+
if (known.isEmpty()) {
140+
sb.append("No components are currently registered "
141+
+ "(no META-INF/tika/*.idx files were found on the classpath). ");
142+
} else {
143+
sb.append(known.size()).append(" registered component(s): ");
144+
int shown = 0;
145+
int cap = 50;
146+
for (String registered : known) {
147+
if (shown == cap) {
148+
sb.append(", ...");
149+
break;
150+
}
151+
if (shown > 0) {
152+
sb.append(", ");
153+
}
154+
sb.append(registered);
155+
shown++;
156+
}
157+
sb.append(". ");
158+
}
159+
sb.append("Arbitrary class names are not allowed for security reasons.");
160+
return sb.toString();
120161
}
121162

122163
/**
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.tika.serialization;
18+
19+
import static org.junit.jupiter.api.Assertions.assertThrows;
20+
import static org.junit.jupiter.api.Assertions.assertTrue;
21+
22+
import org.junit.jupiter.api.Test;
23+
24+
public class ComponentNameResolverTest {
25+
26+
/**
27+
* Resolving an unknown component name must produce an actionable message: it should
28+
* call out the two usual causes (a typo, or the providing module not being on the
29+
* classpath -- the TIKA-4750 scenario for tess4j-parser) rather than just stating
30+
* the registration rule.
31+
*/
32+
@Test
33+
public void unregisteredComponentGivesActionableMessage() {
34+
ClassNotFoundException e = assertThrows(ClassNotFoundException.class,
35+
() -> ComponentNameResolver.resolveClass(
36+
"definitely-not-a-real-parser-xyz", getClass().getClassLoader()));
37+
String msg = e.getMessage();
38+
assertTrue(msg.contains("definitely-not-a-real-parser-xyz"), msg);
39+
assertTrue(msg.contains("misspelled"), msg);
40+
assertTrue(msg.contains("not on the classpath"), msg);
41+
// names the opt-in-module cause concretely so users know what to add
42+
assertTrue(msg.contains("tika-parser-tess4j-module"), msg);
43+
assertTrue(msg.contains("Arbitrary class names are not allowed"), msg);
44+
}
45+
}

0 commit comments

Comments
 (0)