/* * Copyright (c) 2008-2017, 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.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * 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 final ConcurrentMap<String, ClassSource> classSourceMap; private final ClassLoader parent; private final Filter<String> classNameFilter; private final Filter<Member> memberFilter; private final UserCodeDeploymentConfig.ClassCacheMode classCacheMode; private final NodeEngine nodeEngine; private final ClassloadingMutexProvider mutexFactory = new ClassloadingMutexProvider(); private final ILogger logger; public ClassLocator(ConcurrentMap<String, ClassSource> classSourceMap, ClassLoader parent, Filter<String> classNameFilter, Filter<Member> memberFilter, UserCodeDeploymentConfig.ClassCacheMode classCacheMode, NodeEngine nodeEngine) { this.classSourceMap = classSourceMap; 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); } 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 Closeable classMutex = mutexFactory.getMutexForClass(name); try { synchronized (classMutex) { ClassSource classSource = classSourceMap.get(name); if (classSource != null) { if (logger.isFineEnabled()) { logger.finest("Class " + name + " is already in a local cache. "); } return classSource.getClazz(); } byte[] classDef = fetchBytecodeFromRemote(name); if (classDef == null) { throw new ClassNotFoundException("Failed to load class " + name + " from other members."); } return defineAndCacheClass(name, classDef); } } finally { IOUtil.closeResource(classMutex); } } private Class<?> tryToGetClassFromLocalCache(String name) { ClassSource classSource = classSourceMap.get(name); if (classSource != null) { if (logger.isFineEnabled()) { logger.finest("Class " + name + " is already in a local cache. "); } return classSource.getClazz(); } classSource = ThreadLocalClassCache.getFromCache(name); if (classSource != null) { return classSource.getClazz(); } return null; } // called while holding class lock private Class<?> defineAndCacheClass(String name, byte[] classDef) { ClassSource classSource = new ClassSource(name, classDef, parent, this); classSource.define(); if (classCacheMode != UserCodeDeploymentConfig.ClassCacheMode.OFF) { classSourceMap.put(name, classSource); } else { ThreadLocalClassCache.store(name, classSource); } return classSource.getClazz(); } // called while holding class lock private byte[] 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); } byte[] classDef = classData.getClassDefinition(); if (classDef != null) { if (interrupted) { Thread.currentThread().interrupt(); } return classDef; } } } 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<ClassData> 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(name); if (classSource == null) { return null; } return classSource.getClazz(); } }