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

com.hazelcast.internal.usercodedeployment.impl.ClassLocator Maven / Gradle / Ivy

/*
 * Copyright (c) 2008-2018, 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.internal.usercodedeployment.impl;

import com.hazelcast.config.UserCodeDeploymentConfig;
import com.hazelcast.core.Member;
import com.hazelcast.internal.cluster.ClusterService;
import com.hazelcast.internal.usercodedeployment.UserCodeDeploymentClassLoader;
import com.hazelcast.internal.usercodedeployment.UserCodeDeploymentService;
import com.hazelcast.internal.usercodedeployment.impl.operation.ClassDataFinderOperation;
import com.hazelcast.internal.util.filter.Filter;
import com.hazelcast.logging.ILogger;
import com.hazelcast.nio.IOUtil;
import com.hazelcast.spi.NodeEngine;
import com.hazelcast.spi.OperationService;

import java.io.Closeable;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.security.AccessController.doPrivileged;

/**
 * Provides classes to a local member.
 *
 * It's called by {@link UserCodeDeploymentClassLoader} when a class
 * is not found on a local classpath.
 *
 * The current implementation can consult a cache and when the class is not found then it consults
 * remote members.
 */
public final class ClassLocator {
    private static final Pattern CLASS_PATTERN = Pattern.compile("^(.*)\\$.*");
    private final ConcurrentMap classSourceMap;
    private final ConcurrentMap clientClassSourceMap;
    private final ClassLoader parent;
    private final Filter classNameFilter;
    private final Filter memberFilter;
    private final UserCodeDeploymentConfig.ClassCacheMode classCacheMode;
    private final NodeEngine nodeEngine;
    private final ClassloadingMutexProvider mutexFactory = new ClassloadingMutexProvider();
    private final ILogger logger;

    public ClassLocator(ConcurrentMap classSourceMap,
                        ConcurrentMap clientClassSourceMap,
                        ClassLoader parent, Filter classNameFilter, Filter memberFilter,
                        UserCodeDeploymentConfig.ClassCacheMode classCacheMode, NodeEngine nodeEngine) {
        this.classSourceMap = classSourceMap;
        this.clientClassSourceMap = clientClassSourceMap;
        this.parent = parent;
        this.classNameFilter = classNameFilter;
        this.memberFilter = memberFilter;
        this.classCacheMode = classCacheMode;
        this.nodeEngine = nodeEngine;
        this.logger = nodeEngine.getLogger(ClassLocator.class);
    }

    public static void onStartDeserialization() {
        ThreadLocalClassCache.onStartDeserialization();
    }

    public static void onFinishDeserialization() {
        ThreadLocalClassCache.onFinishDeserialization();
    }

    public Class handleClassNotFoundException(String name)
            throws ClassNotFoundException {
        if (!classNameFilter.accept(name)) {
            throw new ClassNotFoundException("Class " + name + " is not allowed to be loaded from other members.");
        }
        Class clazz = tryToGetClassFromLocalCache(name);
        if (clazz != null) {
            return clazz;
        }
        return tryToGetClassFromRemote(name);
    }

    public void defineClassFromClient(final String name, final byte[] classDef) {
        // we need to acquire a classloading lock before defining a class
        // Java 7+ can use locks with per-class granularity while Java 6 has to use a single lock
        // mutexFactory abstract these differences away
        String mainClassName = extractMainClassName(name);
        Closeable classMutex = mutexFactory.getMutexForClass(mainClassName);
        try {
            synchronized (classMutex) {
                ClassSource classSource = clientClassSourceMap.get(mainClassName);
                if (classSource != null) {
                    if (classSource.getClazz(name) != null) {
                        if (!Arrays.equals(classDef, classSource.getClassDefinition(name))) {
                            throw new IllegalStateException("Class " + name
                                    + " is already in a local cache and conflicting byte code representation");
                        } else if (logger.isFineEnabled()) {
                            logger.finest("Class " + name + " is already in a local cache. ");
                        }
                        return;
                    }
                } else {
                    classSource = doPrivileged(new PrivilegedAction() {
                        @Override
                        public ClassSource run() {
                            return new ClassSource(parent, ClassLocator.this);
                        }
                    });
                    clientClassSourceMap.put(mainClassName, classSource);
                }
                classSource.define(name, classDef);
            }
        } finally {
            IOUtil.closeResource(classMutex);
        }
    }

    private Class tryToGetClassFromRemote(String name) throws ClassNotFoundException {
        // we need to acquire a classloading lock before defining a class
        // Java 7+ can use locks with per-class granularity while Java 6 has to use a single lock
        // mutexFactory abstract these differences away
        String mainClassName = extractMainClassName(name);
        Closeable classMutex = mutexFactory.getMutexForClass(mainClassName);
        try {
            synchronized (classMutex) {
                ClassSource classSource = classSourceMap.get(mainClassName);
                if (classSource != null) {
                    Class clazz = classSource.getClazz(name);
                    if (clazz != null) {
                        if (logger.isFineEnabled()) {
                            logger.finest("Class " + name + " is already in a local cache. ");
                        }
                        return clazz;
                    }
                } else if (ThreadLocalClassCache.getFromCache(mainClassName) != null) {
                    classSource = ThreadLocalClassCache.getFromCache(mainClassName);
                } else {
                    classSource = new ClassSource(parent, this);
                }
                ClassData classData = fetchBytecodeFromRemote(name);
                if (classData == null) {
                    throw new ClassNotFoundException("Failed to load class " + name + " from other members.");
                }

                Map innerClassDefinitions = classData.getInnerClassDefinitions();
                classSource.define(name, classData.getMainClassDefinition());
                for (Map.Entry entry : innerClassDefinitions.entrySet()) {
                    classSource.define(entry.getKey(), entry.getValue());
                }
                cacheClass(classSource, mainClassName);
                return classSource.getClazz(name);
            }
        } finally {
            IOUtil.closeResource(classMutex);
        }
    }

    private Class tryToGetClassFromLocalCache(String name) {
        String mainClassDefinition = extractMainClassName(name);
        ClassSource classSource = classSourceMap.get(mainClassDefinition);
        if (classSource != null) {
            Class clazz = classSource.getClazz(name);
            if (clazz != null) {
                if (logger.isFineEnabled()) {
                    logger.finest("Class " + name + " is already in a local cache. ");
                }
                return clazz;
            }
        }

        classSource = clientClassSourceMap.get(mainClassDefinition);
        if (classSource != null) {
            Class clazz = classSource.getClazz(name);
            if (clazz != null) {
                if (logger.isFineEnabled()) {
                    logger.finest("Class " + name + " is already in a local cache. ");
                }
                return clazz;
            }
        }

        classSource = ThreadLocalClassCache.getFromCache(mainClassDefinition);
        if (classSource != null) {
            return classSource.getClazz(name);
        }
        return null;
    }

    static String extractMainClassName(String className) {
        Matcher matcher = CLASS_PATTERN.matcher(className);
        if (matcher.matches()) {
            return matcher.group(1);
        }
        return className;
    }

    // called while holding class lock
    private void cacheClass(ClassSource classSource, String outerClassName) {
        if (classCacheMode != UserCodeDeploymentConfig.ClassCacheMode.OFF) {
            classSourceMap.put(outerClassName, classSource);
        } else {
            ThreadLocalClassCache.store(outerClassName, classSource);
        }
    }

    // called while holding class lock
    private ClassData fetchBytecodeFromRemote(String className) {
        ClusterService cluster = nodeEngine.getClusterService();
        ClassData classData;
        boolean interrupted = false;
        for (Member member : cluster.getMembers()) {
            if (isCandidateMember(member)) {
                continue;
            }
            try {
                classData = tryToFetchClassDataFromMember(className, member);
                if (classData != null) {
                    if (logger.isFineEnabled()) {
                        logger.finest("Loaded class " + className + " from " + member);
                    }
                    return classData;
                }
            } catch (InterruptedException e) {
                // question: should we give-up on loading and this point and simply throw ClassNotFoundException?
                interrupted = true;
            } catch (Exception e) {
                if (logger.isFinestEnabled()) {
                    logger.finest("Unable to get class data for class " + className
                            + " from member " + member, e);
                }
            }
        }
        if (interrupted) {
            Thread.currentThread().interrupt();
        }
        return null;
    }

    private ClassData tryToFetchClassDataFromMember(String className, Member member) throws ExecutionException,
            InterruptedException {
        OperationService operationService = nodeEngine.getOperationService();

        ClassDataFinderOperation op = new ClassDataFinderOperation(className);
        Future classDataFuture = operationService.invokeOnTarget(UserCodeDeploymentService.SERVICE_NAME,
                op, member.getAddress());
        return classDataFuture.get();
    }

    private boolean isCandidateMember(Member member) {
        if (member.localMember()) {
            return true;
        }
        if (!memberFilter.accept(member)) {
            return true;
        }
        return false;
    }

    public Class findLoadedClass(String name) {
        ClassSource classSource = classSourceMap.get(extractMainClassName(name));
        if (classSource == null) {
            return null;
        }
        return classSource.getClazz(name);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy