/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2014, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
package co.paralleluniverse.actors;
import co.paralleluniverse.common.reflection.ASMUtil;
import co.paralleluniverse.common.reflection.AnnotationUtil;
import co.paralleluniverse.common.reflection.ClassLoaderUtil;
import static co.paralleluniverse.common.reflection.ClassLoaderUtil.classToResource;
import static co.paralleluniverse.common.reflection.ClassLoaderUtil.isClassFile;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A module of actor code-upgrades contained in a jar file.
*
* @author pron
*/
class ActorModule extends URLClassLoader {
private static final Class<?>[] AUTOMATIC_UPGRADE_CLASSES = new Class[]{
Actor.class,
co.paralleluniverse.actors.behaviors.Initializer.class,
co.paralleluniverse.actors.behaviors.ServerHandler.class,
co.paralleluniverse.actors.behaviors.EventHandler.class,
};
static {
ClassLoader.registerAsParallelCapable();
}
private static final Logger LOG = LoggerFactory.getLogger(ActorModule.class);
private static final String UPGRADE_CLASSES_ATTR = "Upgrade-Classes";
private final URL url;
private final ClassLoader parent;
private final Set<String> upgradeClasses;
public ActorModule(URL jarUrl, ActorLoader parent) {
super(new URL[]{jarUrl}, null);
this.url = jarUrl;
this.parent = parent;
// determine upgrade classes
try {
JarFile jar = new JarFile(new File(jarUrl.toURI()));
final ImmutableSet.Builder<String> builder = ImmutableSet.builder();
Manifest manifest = jar.getManifest();
Attributes attributes = manifest.getMainAttributes();
String ucstr = attributes.getValue(UPGRADE_CLASSES_ATTR);
if (ucstr != null && !ucstr.trim().isEmpty()) {
if (ucstr.trim().equals("*")) {
ClassLoaderUtil.accept(this, new ClassLoaderUtil.Visitor() {
@Override
public void visit(String resource, URL url, ClassLoader cl) {
if (!isClassFile(resource))
return;
final String className = ClassLoaderUtil.resourceToClass(resource);
// System.out.println("className: " + className + " "
// + ASMUtil.isAssignableFrom(Actor.class, className, ActorModule.this) + " "
// + " " + url + " " + ActorModule.this.parent.getResource(resource) + " "
// + equalContent(ActorModule.this.parent.getResource(resource), url));
if (isAutomaticUpgrade(className)
&& !equalContent(ActorModule.this.parent.getResource(resource), url))
builder.add(className);
}
});
} else {
for (String className : ucstr.split("\\s")) {
LOG.debug("Upgrade of class {} (JAR manifest)", className);
builder.add(className);
}
}
}
ClassLoaderUtil.accept(this, new ClassLoaderUtil.Visitor() {
@Override
public void visit(String resource, URL url, ClassLoader cl) {
if (!isClassFile(resource))
return;
final String className = ClassLoaderUtil.resourceToClass(resource);
try (InputStream is = cl.getResourceAsStream(resource)) {
if (AnnotationUtil.hasClassAnnotation(Upgrade.class, is)) {
LOG.debug("Upgrade of class {} (annotated)", className);
builder.add(className);
}
} catch (IOException e) {
throw new RuntimeException("Exception while scanning class " + className + " for Upgrade annotation", e);
}
}
});
this.upgradeClasses = builder.build();
} catch (IOException | URISyntaxException e) {
throw new RuntimeException(e);
}
}
private boolean isAutomaticUpgrade(String className) {
for (Class<?> c : AUTOMATIC_UPGRADE_CLASSES) {
if (ASMUtil.isAssignableFrom(c, className, ActorModule.this)) {
LOG.debug("Automatic upgrade of class {} (implements/extends {})", className, c);
return true;
}
}
return false;
}
public URL getURL() {
return url;
}
public Set<String> getUpgradeClasses() {
return upgradeClasses;
}
public Class<?> loadClassInModule(String name) throws ClassNotFoundException {
Class<?> loaded = super.findLoadedClass(name);
if (loaded != null)
return loaded;
return super.loadClass(name); // first try to use the URLClassLoader findClass
}
public Class<?> findLoadedClassInModule(String name) {
return super.findLoadedClass(name);
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> loaded = super.findLoadedClass(name);
if (loaded != null)
return loaded;
LOG.debug("findClass {} in module {}", name, this);
boolean isUpgraded = upgradeClasses.contains(name);
if (!isUpgraded && parent != null
&& !name.contains("$")) { // if class is possibly an inner class, don't use parent's, which will be in a different runtime package
try {
String resourceName = classToResource(name);
URL parentUrl = parent.getResource(resourceName);
LOG.debug("findClass {} in module {} - parent URL: {}", name, this, parentUrl);
if (parentUrl != null) {
URL myUrl = super.getResource(resourceName);
if (myUrl == null || equalContent(parentUrl, myUrl)) {
// NOTE: classes may differ only in their Java source line information; considered a difference
if (myUrl != null)
LOG.debug("Class {} in module {} is identical to that in main module", name, this);
else
LOG.debug("findClass {} in module {} - not found in module", name, this);
return parent.loadClass(name);
}
}
} catch (ClassNotFoundException e) {
}
}
try {
Class<?> clazz = super.findClass(name); // first try to use the URLClassLoader findClass
LOG.info("{} loaded {} class {}", this, isUpgraded ? "upgraded" : "internal", name);
return clazz;
} catch (ClassNotFoundException e) {
if (parent != null)
return parent.loadClass(name);
throw e;
}
}
@Override
public URL getResource(String name) {
URL url = super.getResource(name);
if (url != null)
LOG.debug("{} - getResource {}: {}", this, name, url);
if (url == null && parent != null)
url = parent.getResource(name);
return url;
}
@Override
public String toString() {
return "ActorModule{" + "url=" + url + '}';
}
private static boolean equalContent(URL url1, URL url2) {
try {
return Resources.asByteSource(url1).contentEquals(Resources.asByteSource(url2));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}