diff --git a/native-image-support/pom.xml b/native-image-support/pom.xml deleted file mode 100644 index 02ec08878d..0000000000 --- a/native-image-support/pom.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - 4.0.0 - Google Cloud Native Image Support - com.google.cloud - native-image-support - 0.13.2-SNAPSHOT - jar - - - google-cloud-core-parent - com.google.cloud - 2.6.2-SNAPSHOT - - - - Core gRPC module for the google-cloud. - - - - - - org.graalvm.nativeimage - svm - provided - - - - org.graalvm.sdk - graal-sdk - provided - - - \ No newline at end of file diff --git a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/NativeImageUtils.java b/native-image-support/src/main/java/com/google/cloud/nativeimage/features/NativeImageUtils.java deleted file mode 100644 index 8b19f4b84f..0000000000 --- a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/NativeImageUtils.java +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed 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 - * - * https://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 com.google.cloud.nativeimage.features; - -import java.io.IOException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Enumeration; -import java.util.List; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.logging.Logger; -import org.graalvm.nativeimage.hosted.Feature.FeatureAccess; -import org.graalvm.nativeimage.hosted.RuntimeReflection; - -/** Internal class offering helper methods for registering methods/classes for reflection. */ -public class NativeImageUtils { - - private static final Logger LOGGER = Logger.getLogger(NativeImageUtils.class.getName()); - - /** Returns the method of a class or fails if it is not present. */ - public static Method getMethodOrFail(Class clazz, String methodName, Class... params) { - try { - return clazz.getDeclaredMethod(methodName, params); - } catch (NoSuchMethodException e) { - throw new RuntimeException( - "Failed to find method " + methodName + " for class " + clazz.getName(), e); - } - } - - /** Registers a class for reflective construction via its default constructor. */ - public static void registerForReflectiveInstantiation(FeatureAccess access, String className) { - Class clazz = access.findClassByName(className); - if (clazz != null) { - RuntimeReflection.register(clazz); - RuntimeReflection.registerForReflectiveInstantiation(clazz); - } else { - LOGGER.warning( - "Failed to find " + className + " on the classpath for reflective instantiation."); - } - } - - /** Registers all constructors of a class for reflection. */ - public static void registerConstructorsForReflection(FeatureAccess access, String name) { - Class clazz = access.findClassByName(name); - if (clazz != null) { - RuntimeReflection.register(clazz); - RuntimeReflection.register(clazz.getDeclaredConstructors()); - } else { - LOGGER.warning("Failed to find " + name + " on the classpath for reflection."); - } - } - - /** Registers an entire class for reflection use. */ - public static void registerClassForReflection(FeatureAccess access, String name) { - Class clazz = access.findClassByName(name); - if (clazz != null) { - RuntimeReflection.register(clazz); - RuntimeReflection.register(clazz.getDeclaredConstructors()); - RuntimeReflection.register(clazz.getDeclaredFields()); - RuntimeReflection.register(clazz.getDeclaredMethods()); - } else { - LOGGER.warning("Failed to find " + name + " on the classpath for reflection."); - } - } - - /** - * Registers the transitive class hierarchy of the provided {@code className} for reflection. - * - *

The transitive class hierarchy contains the class itself and its transitive set of - * *non-private* nested subclasses. - */ - public static void registerClassHierarchyForReflection(FeatureAccess access, String className) { - Class clazz = access.findClassByName(className); - if (clazz != null) { - registerClassForReflection(access, className); - for (Class nestedClass : clazz.getDeclaredClasses()) { - if (!Modifier.isPrivate(nestedClass.getModifiers())) { - registerClassHierarchyForReflection(access, nestedClass.getName()); - } - } - } else { - LOGGER.warning("Failed to find " + className + " on the classpath for reflection."); - } - } - - /** Registers a class for unsafe reflective field access. */ - public static void registerForUnsafeFieldAccess( - FeatureAccess access, String className, String... fields) { - Class clazz = access.findClassByName(className); - if (clazz != null) { - RuntimeReflection.register(clazz); - for (String fieldName : fields) { - try { - RuntimeReflection.register(clazz.getDeclaredField(fieldName)); - } catch (NoSuchFieldException ex) { - LOGGER.warning("Failed to register field " + fieldName + " for class " + className); - LOGGER.warning(ex.getMessage()); - } - } - } else { - LOGGER.warning( - "Failed to find " - + className - + " on the classpath for unsafe fields access registration."); - } - } - - /** Registers all the classes under the specified package for reflection. */ - public static void registerPackageForReflection(FeatureAccess access, String packageName) { - ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); - - try { - String path = packageName.replace('.', '/'); - - Enumeration resources = classLoader.getResources(path); - while (resources.hasMoreElements()) { - URL url = resources.nextElement(); - - URLConnection connection = url.openConnection(); - if (connection instanceof JarURLConnection) { - List classes = findClassesInJar((JarURLConnection) connection, packageName); - for (String className : classes) { - registerClassHierarchyForReflection(access, className); - } - } - } - } catch (IOException e) { - throw new RuntimeException("Failed to load classes under package name.", e); - } - } - - private static List findClassesInJar(JarURLConnection urlConnection, String packageName) - throws IOException { - - List result = new ArrayList<>(); - - final JarFile jarFile = urlConnection.getJarFile(); - final Enumeration entries = jarFile.entries(); - - while (entries.hasMoreElements()) { - JarEntry entry = entries.nextElement(); - String entryName = entry.getName(); - - if (entryName.endsWith(".class")) { - String javaClassName = entryName.replace(".class", "").replace('/', '.'); - - if (javaClassName.startsWith(packageName)) { - result.add(javaClassName); - } - } - } - - return result; - } -} diff --git a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudFunctionsFeature.java b/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudFunctionsFeature.java deleted file mode 100644 index 7b7eaaebaf..0000000000 --- a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudFunctionsFeature.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed 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 - * - * https://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 com.google.cloud.nativeimage.features.clients; - -import static com.google.cloud.nativeimage.features.NativeImageUtils.registerClassForReflection; -import static com.google.cloud.nativeimage.features.NativeImageUtils.registerClassHierarchyForReflection; - -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.configure.ResourcesRegistry; -import java.io.IOException; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Enumeration; -import java.util.List; -import java.util.function.Consumer; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.stream.Collectors; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeReflection; - -/** A feature which registers reflective usages of the Cloud Functions library. */ -@AutomaticFeature -final class CloudFunctionsFeature implements Feature { - - private static final String FUNCTION_INVOKER_CLASS = - "com.google.cloud.functions.invoker.runner.Invoker"; - - private static final List FUNCTIONS_CLASSES = - Arrays.asList( - "com.google.cloud.functions.HttpFunction", - "com.google.cloud.functions.RawBackgroundFunction", - "com.google.cloud.functions.BackgroundFunction"); - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - Class invokerClass = access.findClassByName(FUNCTION_INVOKER_CLASS); - if (invokerClass != null) { - // JCommander libraries - registerClassForReflection(access, "com.beust.jcommander.converters.StringConverter"); - registerClassForReflection(access, "com.beust.jcommander.validators.NoValidator"); - registerClassForReflection(access, "com.beust.jcommander.validators.NoValueValidator"); - - // Jetty libraries - registerClassForReflection(access, "org.eclipse.jetty.http.HttpTokens"); - registerClassForReflection(access, "org.eclipse.jetty.util.TypeUtil"); - - // Cloud Functions core - registerClassForReflection( - access, "com.google.cloud.functions.invoker.runner.Invoker$Options"); - - // Register Jetty Resources. - ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); - resourcesRegistry.addResources( - "\\QMETA-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder\\E"); - resourcesRegistry.addResources("\\Qorg/eclipse/jetty/http/encoding.properties\\E"); - resourcesRegistry.addResources("\\Qorg/eclipse/jetty/http/mime.properties\\E"); - resourcesRegistry.addResources("\\Qorg/eclipse/jetty/version/build.properties\\E"); - resourcesRegistry.addResourceBundles("javax.servlet.LocalStrings"); - resourcesRegistry.addResourceBundles("javax.servlet.http.LocalStrings"); - - // Register user-implemented Function classes - List> functionClasses = - FUNCTIONS_CLASSES.stream() - .map(name -> access.findClassByName(name)) - .collect(Collectors.toList()); - - scanJarClasspath( - access, - clazz -> { - boolean isFunctionSubtype = - functionClasses.stream() - .anyMatch( - function -> - function.isAssignableFrom(clazz) - && !Modifier.isAbstract(clazz.getModifiers())); - - if (isFunctionSubtype) { - RuntimeReflection.register(clazz); - RuntimeReflection.register(clazz.getDeclaredConstructors()); - RuntimeReflection.register(clazz.getDeclaredMethods()); - - // This part is to register the parameterized class of BackgroundFunctions - // for reflection; i.e. register type T in BackgroundFunction - for (Type type : clazz.getGenericInterfaces()) { - if (type instanceof ParameterizedType) { - ParameterizedType paramType = (ParameterizedType) type; - for (Type argument : paramType.getActualTypeArguments()) { - registerClassHierarchyForReflection(access, argument.getTypeName()); - } - } - } - } - }); - } - } - - /** - * Scan the JAR classpath for classes. The {@code classProcessorFunction} is run once for each - * class in the classpath. - */ - private static void scanJarClasspath( - FeatureAccess access, Consumer> classProcessorCallback) { - - List classPath = access.getApplicationClassPath(); - try { - for (Path path : classPath) { - JarFile jarFile = new JarFile(path.toFile()); - Enumeration entries = jarFile.entries(); - - while (entries.hasMoreElements()) { - JarEntry jarEntry = entries.nextElement(); - String fileName = jarEntry.getName(); - if (fileName.endsWith(".class")) { - String className = fileName.substring(0, fileName.length() - 6).replaceAll("/", "."); - - Class clazz = access.findClassByName(className); - if (clazz != null) { - classProcessorCallback.accept(clazz); - } - } - } - } - } catch (IOException e) { - throw new RuntimeException("Failed to read classpath: ", e); - } - } -} diff --git a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudSqlFeature.java b/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudSqlFeature.java deleted file mode 100644 index 48b4c1e4ef..0000000000 --- a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/CloudSqlFeature.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed 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 - * - * https://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 com.google.cloud.nativeimage.features.clients; - -import com.google.cloud.nativeimage.features.NativeImageUtils; -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.configure.ResourcesRegistry; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; -import org.graalvm.nativeimage.hosted.RuntimeReflection; - -/** Registers GraalVM configuration for the Cloud SQL libraries for MySQL and Postgres. */ -@AutomaticFeature -final class CloudSqlFeature implements Feature { - - private static final String CLOUD_SQL_SOCKET_CLASS = - "com.google.cloud.sql.core.CoreSocketFactory"; - - private static final String POSTGRES_SOCKET_CLASS = "com.google.cloud.sql.postgres.SocketFactory"; - - private static final String MYSQL_SOCKET_CLASS = "com.google.cloud.sql.mysql.SocketFactory"; - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - if (access.findClassByName(CLOUD_SQL_SOCKET_CLASS) == null) { - return; - } - - // The Core Cloud SQL Socket - NativeImageUtils.registerClassForReflection(access, CLOUD_SQL_SOCKET_CLASS); - - // Resources for Cloud SQL - ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); - resourcesRegistry.addResources("\\Qcom.google.cloud.sql/project.properties\\E"); - resourcesRegistry.addResources("\\QMETA-INF/services/java.sql.Driver\\E"); - - // Register Hikari configs if used with Cloud SQL. - if (access.findClassByName("com.zaxxer.hikari.HikariConfig") != null) { - NativeImageUtils.registerClassForReflection(access, "com.zaxxer.hikari.HikariConfig"); - - RuntimeReflection.register( - access.findClassByName("[Lcom.zaxxer.hikari.util.ConcurrentBag$IConcurrentBagEntry;")); - - RuntimeReflection.register(access.findClassByName("[Ljava.sql.Statement;")); - } - - // Register PostgreSQL driver config. - if (access.findClassByName(POSTGRES_SOCKET_CLASS) != null) { - NativeImageUtils.registerClassForReflection( - access, "com.google.cloud.sql.postgres.SocketFactory"); - NativeImageUtils.registerClassForReflection(access, "org.postgresql.PGProperty"); - } - - // Register MySQL driver config. - if (access.findClassByName(MYSQL_SOCKET_CLASS) != null) { - NativeImageUtils.registerClassForReflection(access, MYSQL_SOCKET_CLASS); - - NativeImageUtils.registerConstructorsForReflection( - access, "com.mysql.cj.conf.url.SingleConnectionUrl"); - - NativeImageUtils.registerConstructorsForReflection(access, "com.mysql.cj.log.StandardLogger"); - - access.registerSubtypeReachabilityHandler( - (duringAccess, exceptionClass) -> - NativeImageUtils.registerClassForReflection(duringAccess, exceptionClass.getName()), - access.findClassByName("com.mysql.cj.exceptions.CJException")); - - // JDBC classes create socket connections which must be initialized at run time. - RuntimeClassInitialization.initializeAtRunTime("com.mysql.cj.jdbc"); - - // Additional MySQL resources. - resourcesRegistry.addResourceBundles("com.mysql.cj.LocalizedErrorMessages"); - } - } -} diff --git a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/SpannerFeature.java b/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/SpannerFeature.java deleted file mode 100644 index 956f368dac..0000000000 --- a/native-image-support/src/main/java/com/google/cloud/nativeimage/features/clients/SpannerFeature.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright 2022 Google LLC - * - * Licensed 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 - * - * https://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 com.google.cloud.nativeimage.features.clients; - -import com.google.cloud.nativeimage.features.NativeImageUtils; -import com.oracle.svm.core.annotate.AutomaticFeature; -import com.oracle.svm.core.configure.ResourcesRegistry; -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.nativeimage.hosted.Feature; -import org.graalvm.nativeimage.impl.ConfigurationCondition; - -/** Registers Spanner library classes for reflection. */ -@AutomaticFeature -final class SpannerFeature implements Feature { - - private static final String SPANNER_CLASS = "com.google.spanner.v1.SpannerGrpc"; - private static final String SPANNER_TEST_CLASS = "com.google.cloud.spanner.GceTestEnvConfig"; - private static final String MOCK_CLASS = "com.google.cloud.spanner.MockDatabaseAdminServiceImpl"; - private static final String CLIENT_SIDE_IMPL_CLASS = - "com.google.cloud.spanner.connection.ClientSideStatementImpl"; - private static final String CLIENT_SIDE_VALUE_CONVERTER = - "com.google.cloud.spanner.connection.ClientSideStatementValueConverters"; - private static final String CONNECTION_IMPL = - "com.google.cloud.spanner.connection.ConnectionImpl"; - private static final String CLIENT_SIDE_STATEMENTS = - "com.google.cloud.spanner.connection.ClientSideStatements"; - private static final String CONNECTION_STATEMENT_EXECUTOR = - "com.google.cloud.spanner.connection.ConnectionStatementExecutor"; - private static final String CLIENT_SIDE_STATEMENT_NO_PARAM_EXECUTOR = - "com.google.cloud.spanner.connection.ClientSideStatementNoParamExecutor"; - private static final String CLIENT_SIDE_STATEMENT_SET_EXECUTOR = - "com.google.cloud.spanner.connection.ClientSideStatementSetExecutor"; - private static final String ABSTRACT_STATEMENT_PARSER = - "com.google.cloud.spanner.connection.AbstractStatementParser"; - private static final String STATEMENT_PARSER = - "com.google.cloud.spanner.connection.SpannerStatementParser"; - - @Override - public void beforeAnalysis(BeforeAnalysisAccess access) { - registerSpannerTestClasses(access); - if (access.findClassByName(CLIENT_SIDE_IMPL_CLASS) != null) { - NativeImageUtils.registerClassHierarchyForReflection(access, CLIENT_SIDE_IMPL_CLASS); - } - if (access.findClassByName(CLIENT_SIDE_STATEMENT_NO_PARAM_EXECUTOR) != null) { - NativeImageUtils.registerClassForReflection(access, CLIENT_SIDE_STATEMENT_NO_PARAM_EXECUTOR); - } - if (access.findClassByName(CLIENT_SIDE_STATEMENT_SET_EXECUTOR) != null) { - NativeImageUtils.registerClassForReflection(access, CLIENT_SIDE_STATEMENT_SET_EXECUTOR); - } - if (access.findClassByName(CLIENT_SIDE_VALUE_CONVERTER) != null) { - NativeImageUtils.registerClassHierarchyForReflection(access, CLIENT_SIDE_VALUE_CONVERTER); - } - if (access.findClassByName(CLIENT_SIDE_STATEMENTS) != null) { - NativeImageUtils.registerClassForReflection(access, CLIENT_SIDE_STATEMENTS); - } - if (access.findClassByName(CONNECTION_STATEMENT_EXECUTOR) != null) { - NativeImageUtils.registerClassForReflection(access, CONNECTION_STATEMENT_EXECUTOR); - } - if (access.findClassByName(CONNECTION_IMPL) != null) { - NativeImageUtils.registerClassForReflection(access, CONNECTION_IMPL); - } - if (access.findClassByName(ABSTRACT_STATEMENT_PARSER) != null) { - NativeImageUtils.registerClassHierarchyForReflection(access, ABSTRACT_STATEMENT_PARSER); - } - if (access.findClassByName(STATEMENT_PARSER) != null) { - NativeImageUtils.registerConstructorsForReflection(access, STATEMENT_PARSER); - } - - Class spannerClass = access.findClassByName(SPANNER_CLASS); - if (spannerClass != null) { - NativeImageUtils.registerClassHierarchyForReflection( - access, "com.google.spanner.admin.database.v1.Database"); - NativeImageUtils.registerClassHierarchyForReflection( - access, "com.google.spanner.admin.instance.v1.Instance"); - NativeImageUtils.registerClassForReflection( - access, "com.google.spanner.admin.database.v1.RestoreInfo"); - - // Resources - ResourcesRegistry resourcesRegistry = ImageSingletons.lookup(ResourcesRegistry.class); - resourcesRegistry.addResources( - ConfigurationCondition.alwaysTrue(), - "\\Qcom/google/cloud/spanner/connection/ClientSideStatements.json\\E"); - resourcesRegistry.addResources( - "\\Qcom/google/cloud/spanner/spi/v1/grpc-gcp-apiconfig.json\\E"); - resourcesRegistry.addResources( - ConfigurationCondition.alwaysTrue(), - "\\Qcom/google/cloud/spanner/connection/ITSqlScriptTest_TestQueryOptions.sql\\E"); - } - } - - private void registerSpannerTestClasses(BeforeAnalysisAccess access) { - Class spannerTestClass = access.findClassByName(SPANNER_TEST_CLASS); - if (spannerTestClass != null) { - NativeImageUtils.registerConstructorsForReflection(access, SPANNER_TEST_CLASS); - } - Class mockClass = access.findClassByName(MOCK_CLASS); - if (mockClass != null) { - NativeImageUtils.registerClassForReflection( - access, "com.google.cloud.spanner.MockDatabaseAdminServiceImpl$MockBackup"); - } - } -} diff --git a/native-image-support/src/main/resources/META-INF/native-image/com.google.cloud/google-cloud-core/native-image.properties b/native-image-support/src/main/resources/META-INF/native-image/com.google.cloud/google-cloud-core/native-image.properties deleted file mode 100644 index 5ce9338cff..0000000000 --- a/native-image-support/src/main/resources/META-INF/native-image/com.google.cloud/google-cloud-core/native-image.properties +++ /dev/null @@ -1,2 +0,0 @@ -Args = --initialize-at-build-time=com.google.cloud.spanner.IntegrationTestEnv \ ---initialize-at-run-time=com.google.cloud.firestore.FirestoreImpl diff --git a/pom.xml b/pom.xml index f3d8050d59..6636e317a0 100644 --- a/pom.xml +++ b/pom.xml @@ -158,7 +158,6 @@ 1.6.0 1.34.0 1.41.7 - 22.0.0.2 1.45.1 3.20.1 0.31.0 @@ -284,21 +283,6 @@ ${gson.version} - - - org.graalvm.nativeimage - svm - ${graalvm.version} - provided - - - - org.graalvm.sdk - graal-sdk - ${graalvm.version} - provided - - com.google.truth @@ -338,7 +322,6 @@ google-cloud-core-http google-cloud-core-grpc google-cloud-core-bom - native-image-support