/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.zeppelin.helium;
import org.apache.zeppelin.dep.DependencyResolver;
import org.apache.zeppelin.resource.DistributedResourcePool;
import org.apache.zeppelin.resource.Resource;
import org.apache.zeppelin.resource.ResourcePool;
import org.apache.zeppelin.resource.ResourceSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.*;
/**
* Load application
*/
public class ApplicationLoader {
Logger logger = LoggerFactory.getLogger(ApplicationLoader.class);
private final DependencyResolver depResolver;
private final ResourcePool resourcePool;
private final Map<HeliumPackage, Class<Application>> cached;
public ApplicationLoader(ResourcePool resourcePool, DependencyResolver depResolver) {
this.depResolver = depResolver;
this.resourcePool = resourcePool;
cached = Collections.synchronizedMap(
new HashMap<HeliumPackage, Class<Application>>());
}
/**
* Information of loaded application
*/
private static class RunningApplication {
HeliumPackage packageInfo;
String noteId;
String paragraphId;
public RunningApplication(HeliumPackage packageInfo, String noteId, String paragraphId) {
this.packageInfo = packageInfo;
this.noteId = noteId;
this.paragraphId = paragraphId;
}
public HeliumPackage getPackageInfo() {
return packageInfo;
}
public String getNoteId() {
return noteId;
}
public String getParagraphId() {
return paragraphId;
}
@Override
public int hashCode() {
return (paragraphId + noteId + packageInfo.getArtifact() + packageInfo.getClassName())
.hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof RunningApplication)) {
return false;
}
RunningApplication r = (RunningApplication) o;
return packageInfo.equals(r.getPackageInfo()) && paragraphId.equals(r.getParagraphId()) &&
noteId.equals(r.getNoteId());
}
}
/**
*
* Instantiate application
*
* @param packageInfo
* @param context
* @return
* @throws Exception
*/
public Application load(HeliumPackage packageInfo, ApplicationContext context)
throws Exception {
if (packageInfo.getType() != HeliumType.APPLICATION) {
throw new ApplicationException(
"Can't instantiate " + packageInfo.getType() + " package using ApplicationLoader");
}
// check if already loaded
RunningApplication key =
new RunningApplication(packageInfo, context.getNoteId(), context.getParagraphId());
// get resource required by this package
ResourceSet resources = findRequiredResourceSet(packageInfo.getResources(),
context.getNoteId(), context.getParagraphId());
// load class
Class<Application> appClass = loadClass(packageInfo);
// instantiate
ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
ClassLoader cl = appClass.getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
try {
Constructor<Application> constructor = appClass.getConstructor(ApplicationContext.class);
Application app = new ClassLoaderApplication(constructor.newInstance(context), cl);
return app;
} catch (Exception e) {
throw new ApplicationException(e);
} finally {
Thread.currentThread().setContextClassLoader(oldcl);
}
}
public ResourceSet findRequiredResourceSet(
String [][] requiredResources, String noteId, String paragraphId) {
if (requiredResources == null || requiredResources.length == 0) {
return new ResourceSet();
}
ResourceSet allResources;
if (resourcePool instanceof DistributedResourcePool) {
allResources = ((DistributedResourcePool) resourcePool).getAll(false);
} else {
allResources = resourcePool.getAll();
}
return findRequiredResourceSet(requiredResources, noteId, paragraphId, allResources);
}
static ResourceSet findRequiredResourceSet(String [][] requiredResources,
String noteId,
String paragraphId,
ResourceSet resources) {
ResourceSet args = new ResourceSet();
if (requiredResources == null || requiredResources.length == 0) {
return args;
}
resources = resources.filterByNoteId(noteId).filterByParagraphId(paragraphId);
for (String [] requires : requiredResources) {
args.clear();
for (String require : requires) {
boolean found = false;
for (Resource r : resources) {
if (require.startsWith(":") && r.getClassName().equals(require.substring(1))) {
found = true;
} else if (r.getResourceId().getName().equals(require)) {
found = true;
}
if (found) {
args.add(r);
break;
}
}
if (found == false) {
break;
}
}
if (args.size() == requires.length) {
return args;
}
}
return null;
}
private Class<Application> loadClass(HeliumPackage packageInfo) throws Exception {
if (cached.containsKey(packageInfo)) {
return cached.get(packageInfo);
}
// Create Application classloader
List<URL> urlList = new LinkedList<>();
// load artifact
if (packageInfo.getArtifact() != null) {
List<File> paths = depResolver.load(packageInfo.getArtifact());
if (paths != null) {
for (File path : paths) {
urlList.add(path.toURI().toURL());
}
}
}
URLClassLoader applicationClassLoader =
new URLClassLoader(
urlList.toArray(new URL[]{}),
Thread.currentThread().getContextClassLoader());
Class<Application> cls =
(Class<Application>) applicationClassLoader.loadClass(packageInfo.getClassName());
cached.put(packageInfo, cls);
return cls;
}
}