Skip to content

Commit

Permalink
Revisit apache#1017 Be even less intrusive when registering our Trans…
Browse files Browse the repository at this point in the history
…formerFactory
  • Loading branch information
ppalaga committed Apr 3, 2020
1 parent f90ab2d commit 249e4b2
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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.camel.quarkus.core.graal;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;

/**
* Helper methods invoked from generated bytecode during image processing
*/
public class ResourceUtils {

/**
* Write a string resource into the native image.
*
* @param resourcePath the path under which the given {@code content} should be stored
* @param content the content that will be serialized using {@code UTF-8}
*/
public static void registerResources(String resourcePath, String content) {
try {
final Class<?> resourcesClass = Class.forName("com.oracle.svm.core.jdk.Resources");
final Method register = resourcesClass.getDeclaredMethod("registerResource", String.class, InputStream.class);
try (InputStream in = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
register.invoke(null, resourcePath, in);
}
} catch (Exception e) {
throw new RuntimeException("Failed to load resource " + resourcePath, e);
}
}

}
4 changes: 4 additions & 0 deletions extensions-support/xalan/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-support-xalan</artifactId>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,31 @@
*/
package org.apache.camel.quarkus.support.xalan.deployment;

import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;

import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.SystemPropertyBuildItem;
import io.quarkus.deployment.builditem.GeneratedNativeImageClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBundleBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
import io.quarkus.gizmo.CatchBlockCreator;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.ClassOutput;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.TryBlock;
import org.apache.camel.quarkus.core.graal.ResourceUtils;
import org.apache.camel.quarkus.support.xalan.XalanTransformerFactory;
import org.graalvm.nativeimage.hosted.Feature;

import static io.quarkus.gizmo.MethodDescriptor.ofMethod;

class XalanNativeImageProcessor {
private static final String TRANSFORMER_FACTORY_SERVICE_FILE_PATH = "META-INF/services/javax.xml.transform.TransformerFactory";

@BuildStep
ReflectiveClassBuildItem reflectiveClasses() {
return new ReflectiveClassBuildItem(
Expand Down Expand Up @@ -58,26 +71,46 @@ List<NativeImageResourceBuildItem> resources() {

@BuildStep
void installTransformerFactory(
CamelXalanBuildTimeConfig config,
BuildProducer<SystemPropertyBuildItem> properties,
BuildProducer<ServiceProviderBuildItem> serviceProviders,
BuildProducer<NativeImageResourceBuildItem> nativeResources) {
BuildProducer<GeneratedNativeImageClassBuildItem> nativeImageClass,
BuildProducer<GeneratedResourceBuildItem> generatedResources) {

final String serviceProviderFileContent = XalanTransformerFactory.class.getName() + "\n";

/* This is primarily for the JVM mode */
generatedResources.produce(
new GeneratedResourceBuildItem(TRANSFORMER_FACTORY_SERVICE_FILE_PATH,
serviceProviderFileContent.getBytes(StandardCharsets.UTF_8)));

/* A low level way to embed only our service file in the native image.
* There are at least two META-INF/services/javax.xml.transform.TransformerFactory files
* in the class path: ours and the one from xalan.jar. As of GraalVM 19.3.1-java8, 19.3.1-java11,
* 20.0.0-java8 and 20.0.0-java11, there is no way to ensure that ServiceProviderBuildItem
* or NativeImageResourceBuildItem will pick the service file preferred by us.
* We are thus forced to use this low level mechanism to ensure that.
*/
final ClassCreator file = new ClassCreator(new ClassOutput() {
@Override
public void write(String s, byte[] bytes) {
nativeImageClass.produce(new GeneratedNativeImageClassBuildItem(s, bytes));
}
}, getClass().getName() + "AutoFeature", null,
Object.class.getName(), Feature.class.getName());
file.addAnnotation("com.oracle.svm.core.annotate.AutomaticFeature");
final MethodCreator beforeAn = file.getMethodCreator("beforeAnalysis", "V",
Feature.BeforeAnalysisAccess.class.getName());
final TryBlock overallCatch = beforeAn.tryBlock();
overallCatch.invokeStaticMethod(
ofMethod(ResourceUtils.class, "registerResources", void.class,
String.class, String.class),
overallCatch.load(TRANSFORMER_FACTORY_SERVICE_FILE_PATH),
overallCatch.load(serviceProviderFileContent));

config.transformerFactory
.ifPresent(val -> properties.produce(
/*
* If we do not do this, the service provider defined in xalan.jar's
* META-INF/services/javax.xml.transform.TransformerFactory
* wins over our factory on Java 11+ native
* I any case, the user has an option to pass his preferred factory instead of ours
*/
new SystemPropertyBuildItem("javax.xml.transform.TransformerFactory", val)));
final CatchBlockCreator print = overallCatch.addCatch(Throwable.class);
print.invokeVirtualMethod(ofMethod(Throwable.class, "printStackTrace", void.class), print.getCaughtException());

serviceProviders.produce(
new ServiceProviderBuildItem("javax.xml.transform.TransformerFactory",
"org.apache.camel.quarkus.support.xalan.XalanTransformerFactory"));
beforeAn.returnValue(null);
file.close();

nativeResources.produce(new NativeImageResourceBuildItem("META-INF/services/javax.xml.transform.TransformerFactory"));
}

}
4 changes: 4 additions & 0 deletions extensions-support/xalan/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.camel.quarkus</groupId>
<artifactId>camel-quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
Expand Down

This file was deleted.

0 comments on commit 249e4b2

Please sign in to comment.