Java tutorial
/* Copyright (C) GridGain Systems. 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 org.gridgain.grid.kernal.managers.deployment; import org.gridgain.grid.*; import org.gridgain.grid.kernal.*; import org.gridgain.grid.logger.*; import org.gridgain.grid.util.*; import org.gridgain.grid.util.tostring.*; import org.gridgain.grid.util.typedef.internal.*; import org.jdk8.backport.*; import org.jetbrains.annotations.*; import java.io.*; import java.util.*; import java.util.concurrent.*; /** * Class loader that is able to resolve task subclasses and resources * by requesting remote node. Every class that could not be resolved * by system class loader will be downloaded from given remote node * by task deployment identifier. If identifier has been changed on * remote node this class will throw exception. */ @SuppressWarnings({ "CustomClassloader" }) class GridDeploymentClassLoader extends ClassLoader implements GridDeploymentInfo { /** Class loader ID. */ private final GridUuid id; /** {@code True} for single node deployment. */ private final boolean singleNode; /** Manager registry. */ @GridToStringExclude private final GridKernalContext ctx; /** */ @GridToStringExclude private final GridLogger log; /** Registered nodes. */ @GridToStringExclude private LinkedList<UUID> nodeList; /** Node ID -> Loader ID. */ @GridToStringInclude private Map<UUID, GridUuid> nodeLdrMap; /** */ @GridToStringExclude private final GridDeploymentCommunication comm; /** */ private final String[] p2pExclude; /** P2P timeout. */ private final long p2pTimeout; /** Cache of missed resources names. */ @GridToStringExclude private final GridBoundedLinkedHashSet<String> missedRsrcs; /** Map of class byte code if it's not available locally. */ @GridToStringExclude private final ConcurrentMap<String, byte[]> byteMap; /** User version. */ private final String usrVer; /** Deployment mode. */ private final GridDeploymentMode depMode; /** {@code True} to omit any output to INFO or higher. */ private boolean quiet; /** */ private final Object mux = new Object(); /** * Creates a new peer class loader. * <p> * If there is a security manager, its * {@link SecurityManager#checkCreateClassLoader()} * method is invoked. This may result in a security exception. * * @param id Class loader ID. * @param usrVer User version. * @param depMode Deployment mode. * @param singleNode {@code True} for single node. * @param ctx Kernal context. * @param parent Parent class loader. * @param clsLdrId Remote class loader identifier. * @param nodeId ID of node that have initiated task. * @param comm Communication manager loader will work through. * @param p2pTimeout Timeout for class-loading requests. * @param log Logger. * @param p2pExclude List of P2P loaded packages. * @param missedResourcesCacheSize Size of the missed resources cache. * @param clsBytesCacheEnabled Flag to enable class byte cache. * @param quiet {@code True} to omit output to log. * @throws SecurityException If a security manager exists and its * {@code checkCreateClassLoader} method doesn't allow creation * of a new class loader. */ GridDeploymentClassLoader(GridUuid id, String usrVer, GridDeploymentMode depMode, boolean singleNode, GridKernalContext ctx, ClassLoader parent, GridUuid clsLdrId, UUID nodeId, GridDeploymentCommunication comm, long p2pTimeout, GridLogger log, String[] p2pExclude, int missedResourcesCacheSize, boolean clsBytesCacheEnabled, boolean quiet) throws SecurityException { super(parent); assert id != null; assert depMode != null; assert ctx != null; assert comm != null; assert p2pTimeout > 0; assert log != null; assert clsLdrId != null; assert nodeId.equals(clsLdrId.globalId()); this.id = id; this.usrVer = usrVer; this.depMode = depMode; this.singleNode = singleNode; this.ctx = ctx; this.comm = comm; this.p2pTimeout = p2pTimeout; this.log = log; this.p2pExclude = p2pExclude; nodeList = new LinkedList<>(); nodeList.add(nodeId); Map<UUID, GridUuid> map = U.newHashMap(1); map.put(nodeId, clsLdrId); nodeLdrMap = singleNode ? Collections.unmodifiableMap(map) : map; missedRsrcs = missedResourcesCacheSize > 0 ? new GridBoundedLinkedHashSet<String>(missedResourcesCacheSize) : null; byteMap = clsBytesCacheEnabled ? new ConcurrentHashMap8<String, byte[]>() : null; this.quiet = quiet; } /** * Creates a new peer class loader. * <p> * If there is a security manager, its * {@link SecurityManager#checkCreateClassLoader()} * method is invoked. This may result in a security exception. * * @param id Class loader ID. * @param usrVer User version. * @param depMode Deployment mode. * @param singleNode {@code True} for single node. * @param ctx Kernal context. * @param parent Parent class loader. * @param participants Participating nodes class loader map. * @param comm Communication manager loader will work through. * @param p2pTimeout Timeout for class-loading requests. * @param log Logger. * @param p2pExclude List of P2P loaded packages. * @param missedResourcesCacheSize Size of the missed resources cache. * @param clsBytesCacheEnabled Flag to enable class byte cache. * @param quiet {@code True} to omit output to log. * @throws SecurityException If a security manager exists and its * {@code checkCreateClassLoader} method doesn't allow creation * of a new class loader. */ GridDeploymentClassLoader(GridUuid id, String usrVer, GridDeploymentMode depMode, boolean singleNode, GridKernalContext ctx, ClassLoader parent, Map<UUID, GridUuid> participants, GridDeploymentCommunication comm, long p2pTimeout, GridLogger log, String[] p2pExclude, int missedResourcesCacheSize, boolean clsBytesCacheEnabled, boolean quiet) throws SecurityException { super(parent); assert id != null; assert depMode != null; assert ctx != null; assert comm != null; assert p2pTimeout > 0; assert log != null; assert participants != null; this.id = id; this.usrVer = usrVer; this.depMode = depMode; this.singleNode = singleNode; this.ctx = ctx; this.comm = comm; this.p2pTimeout = p2pTimeout; this.log = log; this.p2pExclude = p2pExclude; nodeList = new LinkedList<>(participants.keySet()); nodeLdrMap = new HashMap<>(participants); missedRsrcs = missedResourcesCacheSize > 0 ? new GridBoundedLinkedHashSet<String>(missedResourcesCacheSize) : null; byteMap = clsBytesCacheEnabled ? new ConcurrentHashMap8<String, byte[]>() : null; this.quiet = quiet; } /** {@inheritDoc} */ @Override public GridUuid classLoaderId() { return id; } /** {@inheritDoc} */ @Override public GridDeploymentMode deployMode() { return depMode; } /** {@inheritDoc} */ @Override public String userVersion() { return usrVer; } /** {@inheritDoc} */ @Override public boolean localDeploymentOwner() { return false; } /** {@inheritDoc} */ @Override public long sequenceNumber() { return -1; } /** {@inheritDoc} */ @Override public Map<UUID, GridUuid> participants() { synchronized (mux) { return new HashMap<>(nodeLdrMap); } } /** * Adds new node and remote class loader id to this class loader. * Class loader will ask all associated nodes for the class/resource * until find it. * * @param nodeId Participating node ID. * @param ldrId Participating class loader id. */ void register(UUID nodeId, GridUuid ldrId) { assert nodeId != null; assert ldrId != null; assert nodeId.equals(ldrId.globalId()); assert !singleNode; synchronized (mux) { if (missedRsrcs != null) missedRsrcs.clear(); /* * We need to put passed in node into the * first position in the list. */ // 1. Remove passed in node if any. nodeList.remove(nodeId); // 2. Add passed in node to the first position. nodeList.addFirst(nodeId); // 3. Put to map. nodeLdrMap.put(nodeId, ldrId); } } /** * Remove remote node and remote class loader id associated with it from * internal map. * * @param nodeId Participating node ID. * @return Removed class loader ID. */ @Nullable GridUuid unregister(UUID nodeId) { assert nodeId != null; synchronized (mux) { nodeList.remove(nodeId); return nodeLdrMap.remove(nodeId); } } /** * @return Registered nodes. */ Collection<UUID> registeredNodeIds() { synchronized (mux) { return new ArrayList<>(nodeList); } } /** * @return Registered class loader IDs. */ Collection<GridUuid> registeredClassLoaderIds() { Collection<GridUuid> ldrIds = new LinkedList<>(); synchronized (mux) { for (GridUuid ldrId : nodeLdrMap.values()) ldrIds.add(ldrId); } return ldrIds; } /** * @param nodeId Node ID. * @return Class loader ID for node ID. */ GridUuid registeredClassLoaderId(UUID nodeId) { synchronized (mux) { return nodeLdrMap.get(nodeId); } } /** * Checks if node is participating in deployment. * * @param nodeId Node ID to check. * @param ldrId Class loader ID. * @return {@code True} if node is participating in deployment. */ boolean hasRegisteredNode(UUID nodeId, GridUuid ldrId) { assert nodeId != null; assert ldrId != null; GridUuid ldrId0; synchronized (mux) { ldrId0 = nodeLdrMap.get(nodeId); } return ldrId0 != null && ldrId0.equals(ldrId); } /** * @return {@code True} if class loader has registered nodes. */ boolean hasRegisteredNodes() { synchronized (mux) { return !nodeList.isEmpty(); } } /** * @param name Name of the class. * @return {@code True} if locally excluded. */ private boolean isLocallyExcluded(String name) { if (p2pExclude != null) { for (String path : p2pExclude) { // Remove star (*) at the end. if (path.endsWith("*")) path = path.substring(0, path.length() - 1); if (name.startsWith(path)) return true; } } return false; } /** {@inheritDoc} */ @Override public Class<?> loadClass(String name) throws ClassNotFoundException { assert !Thread.holdsLock(mux); // Check if we have package name on list of P2P loaded. // GridComputeJob must be always loaded locally to avoid // any possible class casting issues. Class<?> cls = null; try { if (!"org.gridgain.grid.GridComputeJob".equals(name)) { if (isLocallyExcluded(name)) // P2P loaded class. cls = p2pLoadClass(name, true); } if (cls == null) cls = loadClass(name, true); } catch (ClassNotFoundException e) { throw e; } // Catch Throwable to secure against any errors resulted from // corrupted class definitions or other user errors. catch (Throwable e) { throw new ClassNotFoundException("Failed to load class due to unexpected error: " + name, e); } return cls; } /** * Loads the class with the specified binary name. The * default implementation of this method searches for classes in the * following order: * <p> * <ol> * <li> Invoke {@link #findLoadedClass(String)} to check if the class * has already been loaded. </li> * <li>Invoke the {@link #findClass(String)} method to find the class.</li> * </ol> * <p> If the class was found using the above steps, and the * {@code resolve} flag is true, this method will then invoke the {@link * #resolveClass(Class)} method on the resulting {@code Class} object. * * @param name The binary name of the class. * @param resolve If {@code true} then resolve the class. * @return The resulting {@code Class} object. * @throws ClassNotFoundException If the class could not be found */ @Nullable private Class<?> p2pLoadClass(String name, boolean resolve) throws ClassNotFoundException { assert !Thread.holdsLock(mux); // First, check if the class has already been loaded. Class<?> cls = findLoadedClass(name); if (cls == null) cls = findClass(name); if (resolve) resolveClass(cls); return cls; } /** {@inheritDoc} */ @Nullable @Override protected Class<?> findClass(String name) throws ClassNotFoundException { assert !Thread.holdsLock(mux); if (!isLocallyExcluded(name)) { // This is done for URI deployment in which case the parent loader // does not have the requested resource, but it is still locally // available. GridDeployment dep = ctx.deploy().getLocalDeployment(name); if (dep != null) { if (log.isDebugEnabled()) log.debug("Found class in local deployment [cls=" + name + ", dep=" + dep + ']'); return dep.deployedClass(name); } } String path = U.classNameToResourceName(name); GridByteArrayList byteSrc = sendClassRequest(name, path); synchronized (this) { Class<?> cls = findLoadedClass(name); if (cls == null) { if (byteMap != null) byteMap.put(path, byteSrc.array()); cls = defineClass(name, byteSrc.internalArray(), 0, byteSrc.size()); /* Define package in classloader. See URLClassLoader.defineClass(). */ int i = name.lastIndexOf('.'); if (i != -1) { String pkgName = name.substring(0, i); if (getPackage(pkgName) == null) // Too much nulls is normal because we don't have package's meta info. definePackage(pkgName, null, null, null, null, null, null, null); } } if (log.isDebugEnabled()) log.debug("Loaded class [cls=" + name + ", ldr=" + this + ']'); return cls; } } /** * Computes end time based on timeout value passed in. * * @param timeout Timeout. * @return End time. */ private long computeEndTime(long timeout) { long endTime = U.currentTimeMillis() + timeout; // Account for overflow. if (endTime < 0) endTime = Long.MAX_VALUE; return endTime; } /** * Sends class-loading request to all nodes associated with this class loader. * * @param name Class name. * @param path Class path. * @return Class byte source. * @throws ClassNotFoundException If class was not found. */ private GridByteArrayList sendClassRequest(String name, String path) throws ClassNotFoundException { assert !Thread.holdsLock(mux); long endTime = computeEndTime(p2pTimeout); Collection<UUID> nodeListCp; Map<UUID, GridUuid> nodeLdrMapCp; synchronized (mux) { // Skip requests for the previously missed classes. if (missedRsrcs != null && missedRsrcs.contains(path)) throw new ClassNotFoundException("Failed to peer load class [class=" + name + ", nodeClsLdrIds=" + nodeLdrMap + ", parentClsLoader=" + getParent() + ']'); // If single-node mode, then node cannot change and we simply reuse list and map. // Otherwise, make copies that can be used outside synchronization. nodeListCp = singleNode ? nodeList : new LinkedList<>(nodeList); nodeLdrMapCp = singleNode ? nodeLdrMap : new HashMap<>(nodeLdrMap); } GridException err = null; for (UUID nodeId : nodeListCp) { if (nodeId.equals(ctx.discovery().localNode().id())) // Skip local node as it is already used as parent class loader. continue; GridUuid ldrId = nodeLdrMapCp.get(nodeId); GridNode node = ctx.discovery().node(nodeId); if (node == null) { if (log.isDebugEnabled()) log.debug("Found inactive node in class loader (will skip): " + nodeId); continue; } try { GridDeploymentResponse res = comm.sendResourceRequest(path, ldrId, node, endTime); if (res == null) { String msg = "Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ']'; if (!quiet) U.warn(log, msg); else if (log.isDebugEnabled()) log.debug(msg); err = new GridException(msg); continue; } if (res.success()) return res.byteSource(); // In case of shared resources/classes all nodes should have it. if (log.isDebugEnabled()) log.debug("Failed to find class on remote node [class=" + name + ", nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", reason=" + res.errorMessage() + ']'); synchronized (mux) { if (missedRsrcs != null) missedRsrcs.add(path); } throw new ClassNotFoundException( "Failed to peer load class [class=" + name + ", nodeClsLdrs=" + nodeLdrMapCp + ", parentClsLoader=" + getParent() + ", reason=" + res.errorMessage() + ']'); } catch (GridException e) { // This thread should be interrupted again in communication if it // got interrupted. So we assume that thread can be interrupted // by processing cancellation request. if (Thread.currentThread().isInterrupted()) { if (!quiet) U.error(log, "Failed to find class probably due to task/job cancellation: " + name, e); else if (log.isDebugEnabled()) log.debug("Failed to find class probably due to task/job cancellation [name=" + name + ", err=" + e + ']'); } else { if (!quiet) U.warn(log, "Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); else if (log.isDebugEnabled()) log.debug("Failed to send class-loading request to node (is node alive?) [node=" + node.id() + ", clsName=" + name + ", clsPath=" + path + ", clsLdrId=" + ldrId + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); err = e; } } } throw new ClassNotFoundException("Failed to peer load class [class=" + name + ", nodeClsLdrs=" + nodeLdrMapCp + ", parentClsLoader=" + getParent() + ']', err); } /** {@inheritDoc} */ @Nullable @Override public InputStream getResourceAsStream(String name) { assert !Thread.holdsLock(mux); if (byteMap != null && name.endsWith(".class")) { byte[] bytes = byteMap.get(name); if (bytes != null) { if (log.isDebugEnabled()) log.debug("Got class definition from byte code cache: " + name); return new ByteArrayInputStream(bytes); } } InputStream in = ClassLoader.getSystemResourceAsStream(name); if (in == null) in = super.getResourceAsStream(name); // Most probably, this is initiated by GridUtils.getUserVersion(). // No point to send request. if ("META-INF/services/org.apache.commons.logging.LogFactory".equalsIgnoreCase(name)) { if (log.isDebugEnabled()) log.debug( "Denied sending remote request for META-INF/services/org.apache.commons.logging.LogFactory."); return null; } if (in == null) in = sendResourceRequest(name); return in; } /** * Sends resource request to all remote nodes associated with this class loader. * * @param name Resource name. * @return InputStream for resource or {@code null} if resource could not be found. */ @Nullable private InputStream sendResourceRequest(String name) { assert !Thread.holdsLock(mux); long endTime = computeEndTime(p2pTimeout); Collection<UUID> nodeListCp; Map<UUID, GridUuid> nodeLdrMapCp; synchronized (mux) { // Skip requests for the previously missed classes. if (missedRsrcs != null && missedRsrcs.contains(name)) return null; // If single-node mode, then node cannot change and we simply reuse list and map. // Otherwise, make copies that can be used outside synchronization. nodeListCp = singleNode ? nodeList : new LinkedList<>(nodeList); nodeLdrMapCp = singleNode ? nodeLdrMap : new HashMap<>(nodeLdrMap); } for (UUID nodeId : nodeListCp) { if (nodeId.equals(ctx.discovery().localNode().id())) // Skip local node as it is already used as parent class loader. continue; GridUuid ldrId = nodeLdrMapCp.get(nodeId); GridNode node = ctx.discovery().node(nodeId); if (node == null) { if (log.isDebugEnabled()) log.debug("Found inactive node in class loader (will skip): " + nodeId); continue; } try { // Request is sent with timeout that is why we can use synchronization here. GridDeploymentResponse res = comm.sendResourceRequest(name, ldrId, node, endTime); if (res == null) { U.warn(log, "Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ']'); } else if (!res.success()) { synchronized (mux) { // Cache unsuccessfully loaded resource. if (missedRsrcs != null) missedRsrcs.add(name); } // Some frameworks like Spring often ask for the resources // just in case - none will happen if there are no such // resources. So we print out INFO level message. if (!quiet) { if (log.isInfoEnabled()) log.info("Failed to get resource from node [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", msg=" + res.errorMessage() + ']'); } else if (log.isDebugEnabled()) log.debug("Failed to get resource from node [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", msg=" + res.errorMessage() + ']'); // Do not ask other nodes in case of shared mode all of them should have the resource. return null; } else { return new ByteArrayInputStream(res.byteSource().internalArray(), 0, res.byteSource().size()); } } catch (GridException e) { // This thread should be interrupted again in communication if it // got interrupted. So we assume that thread can be interrupted // by processing cancellation request. if (Thread.currentThread().isInterrupted()) { if (!quiet) U.error(log, "Failed to get resource probably due to task/job cancellation: " + name, e); else if (log.isDebugEnabled()) log.debug("Failed to get resource probably due to task/job cancellation: " + name); } else { if (!quiet) U.warn(log, "Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); else if (log.isDebugEnabled()) log.debug("Failed to get resource from node (is node alive?) [nodeId=" + node.id() + ", clsLdrId=" + ldrId + ", resName=" + name + ", parentClsLdr=" + getParent() + ", err=" + e + ']'); } } } return null; } /** {@inheritDoc} */ @Override public String toString() { synchronized (mux) { return S.toString(GridDeploymentClassLoader.class, this); } } }