All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hazelcast.jet.impl.deployment.MapResourceClassLoader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2008-2024, Hazelcast, Inc. All Rights Reserved.
 *
 * 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
 *
 * 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 com.hazelcast.jet.impl.deployment;

import com.hazelcast.internal.nio.IOUtil;
import com.hazelcast.jet.impl.util.ReflectionUtils;
import com.hazelcast.jet.impl.util.Util;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.Logger;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.zip.InflaterInputStream;

import static com.hazelcast.internal.util.StringUtil.isNullOrEmpty;
import static com.hazelcast.jet.impl.JobRepository.classKeyName;
import static com.hazelcast.jet.impl.JobRepository.fileKeyName;
import static com.hazelcast.jet.impl.util.ReflectionUtils.toClassResourceId;

/**
 * Class loader that can be customized with:
 * 
    *
  • A resources supplier {@code Supplier>}.
  • *
  • Choice of whether it looks up classes and resources in child-first order (so this ClassLoader's resources are * first search, then, if not found, the parent ClassLoader is queried). If not child-first, then the common parent-first * hierarchical ClassLoader model is followed.
  • *
* Resources in the {@code >} supplied by the resource supplier are expected to follow * these conventions: *
    *
  • the {@code byte[]} value is the {@link IOUtil#compress(byte[]) deflated} payload of the resource
  • *
  • key of classes is composed with a {@code "c."} prefix, followed by the classname as file path and the * {@code ".class"} suffix. Assuming {@code className} is the binary name of the class, the key is formatted * as {@code classKeyName(toClassResourceId(className))}. See * {@link com.hazelcast.jet.impl.JobRepository#classKeyName(String)} and * {@link com.hazelcast.jet.impl.util.ReflectionUtils#toClassResourceId(String)}.
  • *
  • key of other classpath resources if composed of the {@code "f."} prefix and the path of the resource. * See also {@link com.hazelcast.jet.impl.JobRepository#fileKeyName(String)}.
  • *
*/ public class MapResourceClassLoader extends JetDelegatingClassLoader { static final String PROTOCOL = "map-resource"; protected final Supplier> resourcesSupplier; /** * When {@code true}, if the requested class/resource is not found in this ClassLoader's resources, then parent * is queried. Otherwise, only resources in this ClassLoader are searched. */ protected final boolean childFirst; protected volatile boolean isShutdown; private final ILogger logger = Logger.getLogger(getClass()); private final @Nullable String userCodeNamespace; private final ConcurrentMap>> classCache = new ConcurrentHashMap<>(100); static { ClassLoader.registerAsParallelCapable(); } // Jet only constructor MapResourceClassLoader(ClassLoader parent, @Nonnull Supplier> resourcesSupplier, boolean childFirst) { super(parent); this.userCodeNamespace = null; this.resourcesSupplier = Util.memoizeConcurrent(resourcesSupplier); this.childFirst = childFirst; } // User Code Namespaces oriented constructor public MapResourceClassLoader(@Nonnull String userCodeNamespace, ClassLoader parent, @Nonnull Supplier> resourcesSupplier, boolean childFirst) { super("ucd-namespace", parent); this.userCodeNamespace = userCodeNamespace; this.resourcesSupplier = Util.memoizeConcurrent(resourcesSupplier); this.childFirst = childFirst; } @Nullable public String getUserCodeNamespace() { return userCodeNamespace; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (!childFirst) { return super.loadClass(name, resolve); } // caching for our resources because synchronized is expensive WeakReference> reference = classCache.get(name); if (reference != null) { Class clazz = reference.get(); if (clazz != null) { return clazz; } } synchronized (getClassLoadingLock(name)) { Class klass = findLoadedClass(name); // first lookup class in own resources try { if (klass == null) { klass = findClass(name); } } catch (ClassNotFoundException ignored) { if (logger.isFinestEnabled()) { logger.finest(ignored); } } if (klass == null && getParent() != null) { try { klass = getParent().loadClass(name); } catch (ClassNotFoundException ex) { throw newClassNotFoundException(name); } } if (resolve) { resolveClass(klass); } classCache.put(name, new WeakReference<>(klass)); return klass; } } /** * Loads the passed {@link Class} freshly from this {@link ClassLoader}, meaning that the * returned class will resolve {@link Class#getClassLoader()} to this instance. * * @param clazz The {@link Class} to load using this {@link ClassLoader} instance * @return A {@link Class} that has its {@link ClassLoader} as this instance */ public Class loadClassFromThisLoader(Class clazz) { try { byte[] content = ReflectionUtils.getClassContent(clazz.getName(), clazz.getClassLoader()); if (content == null) { throw new IllegalArgumentException("Unable to read bytes for extra resource class: " + clazz); } definePackage(clazz.getName()); Class result = defineClass(clazz.getName(), content, 0, content.length); resolveClass(result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to create extra resource class: " + clazz); } } @Override protected Class findClass(String name) throws ClassNotFoundException { if (isNullOrEmpty(name)) { return null; } byte[] classBytes = resourceBytes(toClassResourceId(name)); if (classBytes == null) { throw newClassNotFoundException(name); } definePackage(name); return defineClass(name, classBytes, 0, classBytes.length); } @Override public void shutdown() { isShutdown = true; } @Nullable @Override public URL getResource(@Nonnull String name) { Objects.requireNonNull(name); if (!childFirst) { return super.getResource(name); } URL res = findResource(name); if (res == null) { res = super.getResource(name); } return res; } @Override protected URL findResource(String name) { if (checkShutdown(name) || isNullOrEmpty(name)) { return null; } Map resourceMap = getResourceMap(); if (!resourceMap.containsKey(classKeyName(name)) && !resourceMap.containsKey(fileKeyName(name))) { return null; } try { return new URL(PROTOCOL, null, -1, name, new MapResourceURLStreamHandler()); } catch (MalformedURLException e) { // this should never happen with custom URLStreamHandler throw new RuntimeException(e); } } private Map getResourceMap() { return resourcesSupplier.get(); } @Override protected Enumeration findResources(String name) throws IOException { URL foundResource = findResource(name); return foundResource == null ? Collections.emptyEnumeration() : Collections.enumeration(Collections.singleton(foundResource)); } public boolean isShutdown() { return isShutdown; } // argument is used in overridden implementation @SuppressWarnings("java:S1172") boolean checkShutdown(@SuppressWarnings("unused") String resource) { return isShutdown; } @Nullable InputStream resourceStream(String name) { if (checkShutdown(name)) { return null; } byte[] classData = getBytes(name); if (classData == null) { return null; } return new InflaterInputStream(new ByteArrayInputStream(classData)); } @Nullable byte[] resourceBytes(String name) { if (checkShutdown(name)) { return null; } byte[] classData = getBytes(name); if (classData == null) { return null; } return IOUtil.decompress(classData); } @Nullable private byte[] getBytes(String name) { Map resourceMap = getResourceMap(); byte[] classData = resourceMap.get(classKeyName(name)); if (classData == null) { classData = resourceMap.get(fileKeyName(name)); if (classData == null) { return null; } } return classData; } ClassNotFoundException newClassNotFoundException(String name) { // Output more detail if we're `FINEST` logging if (logger.isFinestEnabled()) { return new ClassNotFoundException("No resource could be identified for '" + name + "'. List of resources:\n" + getResourceMap().keySet()); } return new ClassNotFoundException(name); } /** * Defines the package if it is not already defined for the given class * name. * * @param className the class name */ void definePackage(String className) { if (isNullOrEmpty(className)) { return; } int lastDotIndex = className.lastIndexOf('.'); if (lastDotIndex == -1) { return; } String packageName = className.substring(0, lastDotIndex); if (getDefinedPackage(packageName) != null) { return; } try { definePackage(packageName, null, null, null, null, null, null, null); } catch (IllegalArgumentException ignored) { // ignore } } protected final class MapResourceURLStreamHandler extends URLStreamHandler { @Override protected URLConnection openConnection(URL u) throws IOException { return new MapResourceURLConnection(u); } } private final class MapResourceURLConnection extends URLConnection { MapResourceURLConnection(URL url) { super(url); } @Override public void connect() throws IOException { } @Override public InputStream getInputStream() throws IOException { return resourceStream(url.getFile()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy