This is an automated email from the ASF dual-hosted git repository. tballison pushed a commit to branch TIKA-4809-stage-9 in repository https://gitbox.apache.org/repos/asf/tika.git
commit e8bbbd2c4599c839041e56949125c7eaeb5c885e Author: tallison <[email protected]> AuthorDate: Mon Aug 10 15:56:30 2026 -0400 TIKA-4809: Restore rdf+xml output on /meta, and gate SPI resource loading --- .../apache/tika/server/core/TikaServerProcess.java | 9 ++- .../server/core/resource/MetadataResource.java | 10 ++- .../server/core/resource/TikaResourceAware.java | 26 ++++++ .../standard/resource/XMPMetadataResource.java | 19 ++++- .../resource/ResourceServiceLoadingTest.java | 93 ++++++++++++++++++++++ 5 files changed, 152 insertions(+), 5 deletions(-) diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java index eb5f5fe58b..8db6425753 100644 --- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/TikaServerProcess.java @@ -82,6 +82,7 @@ import org.apache.tika.server.core.resource.TikaDetectors; import org.apache.tika.server.core.resource.TikaMimeTypes; import org.apache.tika.server.core.resource.TikaParsers; import org.apache.tika.server.core.resource.TikaResource; +import org.apache.tika.server.core.resource.TikaResourceAware; import org.apache.tika.server.core.resource.TikaServerResource; import org.apache.tika.server.core.resource.TikaServerStatus; import org.apache.tika.server.core.resource.TikaVersion; @@ -433,11 +434,12 @@ public class TikaServerProcess { resourceProviders.add(new SingletonResourceProvider( new PipesResource(helper.getPipesParser(), helper.getPipesConfig()))); } - resourceProviders.addAll(loadResourceServices(serverStatus)); + resourceProviders.addAll(loadResourceServices(serverStatus, tikaResource)); return resourceProviders; } - private static Collection<? extends ResourceProvider> loadResourceServices(ServerStatus serverStatus) { + private static Collection<? extends ResourceProvider> loadResourceServices(ServerStatus serverStatus, + TikaResource tikaResource) { List<TikaServerResource> resources = new ServiceLoader(TikaServerProcess.class.getClassLoader()).loadServiceProviders(TikaServerResource.class); List<ResourceProvider> providers = new ArrayList<>(); for (TikaServerResource r : resources) { @@ -445,6 +447,9 @@ public class TikaServerProcess { if (r instanceof ServerStatusResource) { ((ServerStatusResource) r).setServerStatus(serverStatus); } + if (r instanceof TikaResourceAware) { + ((TikaResourceAware) r).setTikaResource(tikaResource); + } providers.add(new SingletonResourceProvider(r)); } return providers; diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java index 1fbc0bffdb..d9e740d324 100644 --- a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/MetadataResource.java @@ -52,12 +52,20 @@ import org.apache.tika.server.core.TikaServerParseException; public class MetadataResource { private static final Logger LOG = LoggerFactory.getLogger(MetadataResource.class); - private final TikaResource tikaResource; + private TikaResource tikaResource; public MetadataResource(TikaResource tikaResource) { this.tikaResource = tikaResource; } + /** For subclasses the service loader constructs; see {@link TikaResourceAware}. */ + protected MetadataResource() { + } + + protected void setTikaResource(TikaResource tikaResource) { + this.tikaResource = tikaResource; + } + @POST @Consumes("multipart/form-data") @Produces({"text/csv", "application/json"}) diff --git a/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResourceAware.java b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResourceAware.java new file mode 100644 index 0000000000..409d5acc24 --- /dev/null +++ b/tika-server/tika-server-core/src/main/java/org/apache/tika/server/core/resource/TikaResourceAware.java @@ -0,0 +1,26 @@ +/* + * 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.server.core.resource; + +/** + * For SPI-loaded resources that need the shared {@link TikaResource}. The service loader can + * only call a no-arg (or ServiceLoader-arg) constructor, so the dependency is injected after + * construction, the same way {@code ServerStatusResource} receives its ServerStatus. + */ +public interface TikaResourceAware extends TikaServerResource { + void setTikaResource(TikaResource tikaResource); +} diff --git a/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java b/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java index 6bf07562ef..d8360d3d4d 100644 --- a/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java +++ b/tika-server/tika-server-standard/src/main/java/org/apache/tika/server/standard/resource/XMPMetadataResource.java @@ -35,14 +35,29 @@ import org.apache.tika.metadata.Metadata; import org.apache.tika.parser.ParseContext; import org.apache.tika.server.core.resource.MetadataResource; import org.apache.tika.server.core.resource.TikaResource; -import org.apache.tika.server.core.resource.TikaServerResource; +import org.apache.tika.server.core.resource.TikaResourceAware; -public class XMPMetadataResource extends MetadataResource implements TikaServerResource { +/** + * XMP is content negotiation on {@code /meta}, not a path of its own: this class inherits + * {@code @Path("/meta")} from {@link MetadataResource} and differs only in producing + * {@code application/rdf+xml}. CXF picks between the two by the request's Accept header. + */ +public class XMPMetadataResource extends MetadataResource implements TikaResourceAware { public XMPMetadataResource(TikaResource tikaResource) { super(tikaResource); } + /** Required by the service loader, which cannot supply the TikaResource. */ + public XMPMetadataResource() { + super(); + } + + @Override + public void setTikaResource(TikaResource tikaResource) { + super.setTikaResource(tikaResource); + } + @PUT @Path("{field}") @Produces({"application/rdf+xml"}) diff --git a/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/resource/ResourceServiceLoadingTest.java b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/resource/ResourceServiceLoadingTest.java new file mode 100644 index 0000000000..231facf191 --- /dev/null +++ b/tika-server/tika-server-standard/src/test/java/org/apache/tika/server/standard/resource/ResourceServiceLoadingTest.java @@ -0,0 +1,93 @@ +/* + * 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.server.standard.resource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import org.apache.tika.config.ServiceLoader; +import org.apache.tika.server.core.resource.TikaServerResource; +import org.apache.tika.utils.ServiceLoaderUtils; + +/** + * Every resource this module contributes has to survive the service loader, which can only call + * a no-arg (or ServiceLoader-arg) constructor. Adding a constructor argument to one of them + * silently drops its endpoints at startup -- the failure is a WARN in the log and a 406 to the + * caller, with nothing in the test suite to notice, because the resource tests construct their + * resources directly and register them by hand. + * <p> + * That is how {@link XMPMetadataResource} lost {@code Accept: application/rdf+xml} on + * {@code /meta} after gaining a {@code (TikaResource)} constructor. + */ +public class ResourceServiceLoadingTest { + + private static final String SPI_RESOURCE = + "META-INF/services/" + TikaServerResource.class.getName(); + + @Test + public void testDeclaredResourcesAreInstantiable() throws Exception { + List<String> declared = readDeclaredResources(); + assertFalse(declared.isEmpty(), "expected this module to declare resources at " + SPI_RESOURCE); + + ServiceLoader loader = new ServiceLoader(getClass().getClassLoader()); + for (String className : declared) { + Class<?> klass = Class.forName(className); + // Throws if no usable constructor exists -- exactly what the server swallows. + Object instance = ServiceLoaderUtils.newInstance(klass, loader); + assertTrue(instance instanceof TikaServerResource, className + " must be a TikaServerResource"); + } + } + + /** The XMP resource in particular, since it is the one that regressed. */ + @Test + public void testXmpResourceIsDeclaredAndLoadable() throws Exception { + assertTrue(readDeclaredResources().contains(XMPMetadataResource.class.getName()), + "XMPMetadataResource must stay declared -- it is what serves rdf+xml from /meta"); + ServiceLoaderUtils.newInstance(XMPMetadataResource.class, + new ServiceLoader(getClass().getClassLoader())); + } + + private List<String> readDeclaredResources() throws IOException { + List<String> names = new ArrayList<>(); + try (InputStream is = getClass().getClassLoader().getResourceAsStream(SPI_RESOURCE)) { + if (is == null) { + return names; + } + try (BufferedReader reader = + new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (!line.isEmpty() && !line.startsWith("#")) { + names.add(line); + } + } + } + } + return names; + } +}
