blob: 8016c6adcc41ee5220f9098a030ff149ed8a99d8 [file] [log] [blame]
/*
* 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.
*/
/* TODO: - Complete deployment of items that are in the deployment
directory.
*/
package org.apache.river.container.deployer;
import java.io.File;
import java.io.FilePermission;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Principal;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.security.GrantPermission;
import net.jini.security.policy.DynamicPolicyProvider;
import net.jini.security.policy.UmbrellaGrantPermission;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileType;
import org.apache.river.container.ConfigurationException;
import org.apache.river.container.Context;
import org.apache.river.container.FileUtility;
import org.apache.river.container.Init;
import org.apache.river.container.Injected;
import org.apache.river.container.InjectionStyle;
import org.apache.river.container.LocalizedRuntimeException;
import org.apache.river.container.MessageNames;
import org.apache.river.container.Name;
import org.apache.river.container.PropertiesFileReader;
import org.apache.river.container.Strings;
import org.apache.river.container.Utils;
import org.apache.river.container.classloading.ClasspathFilter;
import org.apache.river.container.classloading.VirtualFileSystemClassLoader;
import org.apache.river.container.codebase.CodebaseContext;
import org.apache.river.container.codebase.CodebaseHandler;
import org.apache.river.container.el.ArgsParser;
import org.apache.river.container.liaison.VirtualFileSystemConfiguration;
import org.apache.river.container.work.ContextualWorkManager;
import org.apache.river.container.work.WorkManager;
/**
* Deployer that instantiates applications or services based on the
* com.sun.jini.starter API
*/
public class StarterServiceDeployer {
private static final Logger log
= Logger.getLogger(StarterServiceDeployer.class.getName(), MessageNames.BUNDLE_NAME);
@Injected(style = InjectionStyle.BY_TYPE)
private FileUtility fileUtility = null;
@Injected(style = InjectionStyle.BY_TYPE)
private Context context;
@Name
private String myName = null;
@Injected(style = InjectionStyle.BY_TYPE)
private CodebaseHandler codebaseHandler = null;
private String config = Strings.STARTER_SERVICE_DEPLOYER_CONFIG;
private ASTconfig configNode = null;
public String getConfig() {
return config;
}
public void setConfig(String config) {
this.config = config;
}
@Injected(style = InjectionStyle.BY_TYPE)
private PropertiesFileReader propertiesFileReader = null;
@Injected(style = InjectionStyle.BY_TYPE)
private ArgsParser argsParser = null;
@Injected(style = InjectionStyle.BY_TYPE)
WorkManager workManager = null;
@Injected(style = InjectionStyle.BY_TYPE)
ContextualWorkManager contextualWorkManager = null;
@Injected(style = InjectionStyle.BY_TYPE)
private DynamicPolicyProvider securityPolicy = null;
public void addPlatformCodebaseJars(CodebaseContext codebaseContext) throws IOException {
ASTcodebase codebaseNode = (ASTcodebase) configNode.search(new Class[]{
ASTconfig.class, ASTclassloader.class, ASTcodebase.class
}).get(0);
/*
Register the platform codebase jars with the codebase service.
*/
for (int i = 0; i < codebaseNode.jjtGetNumChildren(); i++) {
String jarFile = codebaseNode.jjtGetChild(i).toString();
FileObject fo = fileUtility.getLibDirectory().resolveFile(jarFile);
codebaseContext.addFile(fo);
log.log(Level.FINE, MessageNames.ADDED_PLATFORM_CODEBASE_JAR,
jarFile);
}
}
public String[] constructArgs(String argLine, String[] serviceArgs) {
String[] args = null;
if (argLine == null) {
args = new String[0];
} else {
args = argsParser.toArgs(argLine, serviceArgs);
}
return args;
}
public VirtualFileSystemClassLoader createServiceClassloader(FileObject serviceRoot, CodeSource codeSource) throws IOException, FileSystemException {
String parentLoaderName = configNode.search(
new Class[]{ASTconfig.class, ASTclassloader.class, ASTparent.class}).get(0).jjtGetChild(0).toString();
log.log(Level.FINE, MessageNames.SERVICE_PARENT_CLASSLOADER_IS, parentLoaderName);
boolean isAppPriority = false;
if (!configNode.search(new Class[]{ASTconfig.class, ASTclassloader.class, ASTappPriority.class}).isEmpty()) {
isAppPriority = true;
}
ClassLoader parentLoader = (ClassLoader) context.get(parentLoaderName);
VirtualFileSystemClassLoader cl
= createChildOfGivenClassloader(parentLoader, codeSource, isAppPriority);
/*
Include platform jars from the container's lib directory.
*/
List classpathNodes = configNode.search(new Class[]{ASTconfig.class,
ASTclassloader.class, ASTjars.class, ASTclasspath.class});
if (classpathNodes.size() > 0) {
ASTclasspath platformJarSpec = (ASTclasspath) classpathNodes.get(0);
addPlatformJarsToClassloader(platformJarSpec, cl);
}
addLibDirectoryJarsToClasspath(serviceRoot, cl);
return cl;
}
protected void addLibDirectoryJarsToClasspath(FileObject serviceRoot, VirtualFileSystemClassLoader cl) throws FileSystemException {
/*
Add the jar files from the service's 'lib' directory.
*/
FileObject libDir = serviceRoot.resolveFile(Strings.LIB);
List<FileObject> jarFiles = Utils.findChildrenWithSuffix(libDir,
Strings.DOT_JAR);
for (FileObject jarFile : jarFiles) {
cl.addClassPathEntry(libDir, jarFile.getName().getBaseName());
}
}
protected void addPlatformJarsToClassloader(ASTclasspath platformJarSpec, VirtualFileSystemClassLoader cl) throws IOException, LocalizedRuntimeException {
log.log(Level.FINE, MessageNames.ADDING_CLASSPATH_ENTRY, new Object[]{platformJarSpec.toString()});
List<ClasspathFilter> filters = ClasspathFilterBuilder.filtersFromClasspathExpression(platformJarSpec);
cl.addClasspathFilters(filters, fileUtility.getLibDirectory());
}
protected VirtualFileSystemClassLoader createChildOfGivenClassloader(ClassLoader parent, CodeSource codeSource, boolean isAppPriority) {
/*
Create the service classloader.
*/
VirtualFileSystemClassLoader cl
= new VirtualFileSystemClassLoader(null, parent, codeSource, isAppPriority);
return cl;
}
public void exportServiceCodebaseJars(FileObject serviceRoot, CodebaseContext codebaseContext) throws FileSystemException {
/*
Register the service's codebase jars with the codebase service.
*/
FileObject libDlDir = serviceRoot.resolveFile(Strings.LIB_DL);
/* Don't bother if there is no lib-dl (e.g. for simple clients) */
if (libDlDir.exists()) {
List<FileObject> dljarFiles = Utils.findChildrenWithSuffix(libDlDir,
Strings.DOT_JAR);
for (FileObject jarFile : dljarFiles) {
codebaseContext.addFile(jarFile);
}
}
}
@Init
public void init() {
try {
tryInitialize();
} catch (Throwable ex) {
log.log(Level.SEVERE, MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT,
ex);
throw new ConfigurationException(ex,
MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT);
}
}
public void launchService(final ApplicationEnvironment env, Properties startProps, final String[] args) throws ClassNotFoundException {
final String startClassName = startProps.getProperty(Strings.START_CLASS);
/*
Launch the service.
*/
log.log(Level.FINE, MessageNames.CALLING_MAIN, new Object[]{
startClassName, Utils.format(args)
});
Runnable task = null;
if (hasServiceStarterConstructor(env.getClassLoader(), startClassName)) {
task = new Runnable() {
@Override
public void run() {
try {
env.setServiceInstance(instantiateService(env.getClassLoader(), startClassName, args));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
} else if (hasMain(env.getClassLoader(), startClassName)) {
task = new Runnable() {
@Override
public void run() {
try {
callMain(env.getClassLoader(), startClassName, args);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
};
} else {
throw new UnsupportedOperationException();
}
env.getWorkingContext().getScheduledExecutorService().submit(task);
}
public Properties readStartProperties(FileObject serviceRoot) throws FileSystemException, LocalizedRuntimeException, IOException {
/*
Read the start.properties file.
*/
FileObject startProperties = serviceRoot.resolveFile(Strings.START_PROPERTIES);
if (startProperties == null || !startProperties.getType().equals(FileType.FILE)
|| !startProperties.isReadable()) {
throw new LocalizedRuntimeException(MessageNames.BUNDLE_NAME,
MessageNames.CANT_READ_START_PROPERTIES,
new Object[]{Strings.START_PROPERTIES,
serviceRoot.getName().getBaseName()});
}
Properties startProps = propertiesFileReader.getProperties(startProperties);
return startProps;
}
public void setupLiaisonConfiguration(ApplicationEnvironment env) throws ConfigurationException {
/*
Setup the liaison configuration.
*/
ClassLoader originalContextCl = Thread.currentThread().getContextClassLoader();
try {
Thread.currentThread().setContextClassLoader(env.getClassLoader());
File workingDir = null;
if (env.getServiceArchive() != null) {
/* This variable, and the method we're calling on VirtualFileSystemConfiguration,
is named in an unfortunate manner for
historical reasons. What we're really doing here is telling the
VirtualFileSystemConfiguration where to read its configuration
file from. Iternally to VFSConfig, it's called the "root" directory.
It has nothing to do with the "working" directory where the service
should be allowed to write its own data.
*/
workingDir = new File(env.getServiceArchive().getURL().toURI());
} else {
workingDir = new File(env.getServiceArchive().getURL().toURI());
}
grantPermissions(env.getClassLoader(),
new Permission[]{new FilePermission(workingDir.getAbsolutePath(), Strings.READ)});
Utils.logClassLoaderHierarchy(log, Level.FINE, this.getClass());
String configName = VirtualFileSystemConfiguration.class.getName();
invokeStatic(env.getClassLoader(), configName,
Strings.SET_WORKING_DIRECTORY,
workingDir);
/*
Setup the "special" variables in the configuration.
*/
ASTconfiguration configurationNode = (ASTconfiguration) configNode.search(new Class[]{ASTconfig.class, ASTconfiguration.class}).get(0);
for (int i = 0; i < configurationNode.jjtGetNumChildren(); i++) {
ASTconfigEntry cfgEntryNode = (ASTconfigEntry) configurationNode.jjtGetChild(i);
String varName = cfgEntryNode.jjtGetChild(0).toString();
String contextVarName = cfgEntryNode.jjtGetChild(1).toString();
Object contextValue = context.get(contextVarName);
if (contextValue != null) {
invokeStatic(env.getClassLoader(), configName,
Strings.PUT_SPECIAL_ENTRY,
new Class[]{String.class, Object.class},
Strings.DOLLAR + varName, contextValue);
} else {
log.log(Level.WARNING, MessageNames.MISSING_SPECIAL_VALUE,
new Object[]{getConfig(), varName, contextVarName});
}
}
/*
One extra "special" variable is the File that gives the working directory.
*/
invokeStatic(env.getClassLoader(), configName, Strings.PUT_SPECIAL_ENTRY,
new Class[]{String.class, Object.class},
Strings.DOLLAR + Strings.WORKING_DIRECTORY, env.getWorkingDirectory());
/* Install the Executor. */
log.log(Level.INFO, MessageNames.EXECUTOR_NAME_IS,
new Object[]{Strings.DOLLAR + Strings.EXECUTOR_NAME});
invokeStatic(env.getClassLoader(), configName,
Strings.PUT_SPECIAL_ENTRY,
new Class[]{String.class, Object.class
},
Strings.DOLLAR + Strings.EXECUTOR_NAME, env.getWorkingContext().getScheduledExecutorService()
);
} catch (Exception ex) {
log.log(Level.WARNING, MessageNames.EXCEPTION_THROWN, Utils.stackTrace(ex));
throw new ConfigurationException(ex,
MessageNames.STARTER_SERVICE_DEPLOYER_FAILED_INIT);
} finally {
Thread.currentThread().setContextClassLoader(originalContextCl);
}
}
private void tryInitialize() throws IOException, ParseException {
log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_STARTING, myName);
/*
Read and parse the configuration file.
*/
FileObject configFile = fileUtility.getProfileDirectory().resolveFile(config);
InputStream in = configFile.getContent().getInputStream();
configNode = DeployerConfigParser.parseConfig(in);
log.log(Level.FINE, MessageNames.STARTER_SERVICE_DEPLOYER_INITIALIZED,
new Object[]{myName});
}
public ServiceLifeCycle deployServiceArchive(String managerName, FileObject serviceArchive) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
// Create an application environment
ApplicationEnvironment env = new ApplicationEnvironment();
env.setApplicationManagerName(managerName);
env.setServiceArchive(serviceArchive);
env.setServiceRoot(
serviceArchive.getFileSystem().getFileSystemManager().createFileSystem(Strings.JAR, serviceArchive));
String serviceName = findServiceName(env.getServiceArchive(), env.getServiceRoot());
env.setServiceName(serviceName);
ServiceLifeCycle slc = StarterServiceLifeCycleSM.newStarterServiceLifeCycle(env, this);
return slc;
}
private String findServiceName(FileObject serviceArchive, FileObject serviceRoot) {
if (serviceArchive != null) {
return serviceArchive.getName().getBaseName();
}
return serviceRoot.getName().getBaseName();
}
private URL findServiceURL(FileObject serviceArchive, FileObject serviceRoot) throws FileSystemException {
if (serviceArchive != null) {
return serviceArchive.getURL();
}
return serviceRoot.getURL();
}
void prepareService(ApplicationEnvironment env) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
CodeSource serviceCodeSource
= new CodeSource(findServiceURL(env.getServiceArchive(), env.getServiceRoot()),
new Certificate[0]);
log.log(Level.INFO, MessageNames.CODESOURCE_IS,
new Object[]{env.getServiceName(), serviceCodeSource});
VirtualFileSystemClassLoader cl = createServiceClassloader(env.getServiceRoot(), serviceCodeSource);
env.setClassLoader(cl);
/*
Create a codebase context.
*/
CodebaseContext codebaseContext
= codebaseHandler.createContext(env.getServiceName());
env.setCodebaseContext(codebaseContext);
addPlatformCodebaseJars(codebaseContext);
exportServiceCodebaseJars(env.getServiceRoot(), codebaseContext);
/*
Setup the classloader's codebase annotation.
*/
cl.setCodebase(codebaseContext.getCodebaseAnnotation());
/*
Grant the appropriate permissions to the service's classloader and
protection domain.
*/
Permission[] perms = createPermissionsInClassloader(cl);
grantPermissions(cl, perms);
/*
Create the service's working directory and grant permissions to it.
*/
createWorkDirectoryFor(env);
grantPermissionsToWorkDirectoryFor(env);
/*
* Create a working context (work manager).
*/
env.setWorkingContext(contextualWorkManager.createContext(env.getServiceName(), env.getClassLoader()));
setupLiaisonConfiguration(env);
}
void launchService(ApplicationEnvironment env, String[] serviceArgs) throws FileSystemException, IOException, ClassNotFoundException {
Properties startProps = readStartProperties(env.getServiceRoot());
String argLine = startProps.getProperty(Strings.START_PARAMETERS);
final String[] args = constructArgs(argLine, serviceArgs);
launchService(env, startProps, args);
log.log(Level.INFO, MessageNames.COMPLETED_SERVICE_DEPLOYMENT, env.getServiceName());
}
Permission[] createPermissionsInClassloader(ClassLoader cl) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
List<Permission> perms = new ArrayList<Permission>();
// Get all the permission nodes from the config.
Class[] path = new Class[]{ASTconfig.class, ASTgrant.class, ASTpermission.class};
List<ASTNode> permNodes = configNode.search(path);
// Create a permission for each
for (ASTNode node : permNodes) {
String className = (String) ((ASTsymbol) node.jjtGetChild(0)).getValue();
Object permissionConstructorArgs[] = new String[node.jjtGetNumChildren() - 1];
for (int i = 0; i < permissionConstructorArgs.length; i++) {
permissionConstructorArgs[i] = (String) ((ASTliteral) node.jjtGetChild(i + 1)).getValue();
}
Permission perm = (Permission) invokeConstructor(cl, className, permissionConstructorArgs);
perms.add(perm);
}
return perms.toArray(new Permission[0]);
}
private Object invokeStatic(ClassLoader cl, String className, String methodName,
Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class clazz = Class.forName(className, true, cl);
Class[] parameterTypes = new Class[parms.length];
for (int i = 0; i < parms.length; i++) {
parameterTypes[i] = parms[i].getClass();
}
Method method = clazz.getMethod(methodName, parameterTypes);
return method.invoke(null, parms);
}
private Object invokeStatic(ClassLoader cl, String className, String methodName, Class[] argTypes,
Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class clazz = Class.forName(className, true, cl);
Method method = clazz.getMethod(methodName, argTypes);
return method.invoke(null, parms);
}
private Object invokeConstructor(ClassLoader cl, String className,
Object... parms) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException {
Class clazz = Class.forName(className, true, cl);
Class[] parameterTypes = new Class[parms.length];
for (int i = 0; i < parms.length; i++) {
parameterTypes[i] = parms[i].getClass();
}
Constructor method = clazz.getConstructor(parameterTypes);
return method.newInstance(parms);
}
private void grantPermissions(ClassLoader cl, Permission[] perms) {
try {
perms = expandUmbrella(perms);
Class clazz = Class.forName(VirtualFileSystemConfiguration.class.getName(), true, cl);
securityPolicy.grant(clazz, new Principal[0], perms);
} catch (Throwable t) {
throw new ConfigurationException(MessageNames.FAILED_DEPLOY_SERVICE, t);
}
}
private static Permission[] expandUmbrella(Permission[] perms) {
PermissionCollection pc = new Permissions();
for (Permission p : perms) {
pc.add(p);
}
if (pc.implies(new UmbrellaGrantPermission())) {
List l = Collections.list(pc.elements());
pc.add(new GrantPermission(
(Permission[]) l.toArray(new Permission[l.size()])));
}
List<Permission> permList = new ArrayList<Permission>();
for (Enumeration<Permission> en = pc.elements(); en.hasMoreElements();) {
permList.add(en.nextElement());
}
return permList.toArray(new Permission[0]);
}
private boolean hasServiceStarterConstructor(ClassLoader cl, String className) throws ClassNotFoundException {
Class clazz = Class.forName(className, true, cl);
log.log(Level.FINE, MessageNames.CLASSLOADER_IS,
new Object[]{clazz.getName(), clazz.getClassLoader().toString()});
// Get this through dynamic lookup becuase it won't be in the parent
// classloader!
Class lifeCycleClass = Class.forName(Strings.LIFECYCLE_CLASS, true, cl);
try {
Constructor constructor = clazz.getDeclaredConstructor(new Class[]{String[].class, lifeCycleClass});
return true;
} catch (NoSuchMethodException nsme) {
return false;
}
}
private boolean hasMain(ClassLoader cl, String className) throws ClassNotFoundException {
Class clazz = Class.forName(className, true, cl);
log.log(Level.FINE, MessageNames.CLASSLOADER_IS,
new Object[]{clazz.getName(), clazz.getClassLoader().toString()});
// Get this through dynamic lookup becuase it won't be in the parent
// classloader!
try {
Method main = clazz.getMethod(Strings.MAIN, new Class[]{String[].class});
return true;
} catch (NoSuchMethodException nsme) {
return false;
}
}
private void callMain(ClassLoader cl, String className, String[] args) throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Class clazz = Class.forName(className, true, cl);
log.log(Level.FINE, MessageNames.CLASSLOADER_IS,
new Object[]{clazz.getName(), clazz.getClassLoader().toString()});
// Get this through dynamic lookup becuase it won't be in the parent
// classloader!
try {
Method main = clazz.getMethod(Strings.MAIN, new Class[]{String[].class});
main.invoke(null, new Object[]{args});
} catch (NoSuchMethodException nsme) {
throw new RuntimeException(nsme);
}
}
private Object instantiateService(ClassLoader cl, String className, String[] parms)
throws ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, InstantiationException {
Class clazz = Class.forName(className, true, cl);
log.log(Level.FINE, MessageNames.CLASSLOADER_IS,
new Object[]{clazz.getName(), clazz.getClassLoader().toString()});
// Get this through dynamic lookup becuase it won't be in the parent
// classloader!
Class lifeCycleClass = Class.forName(Strings.LIFECYCLE_CLASS, true, cl);
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println("Class is " + clazz);
for (int i = 0; i < constructors.length; i++) {
Constructor constructor = constructors[i];
System.out.println("Found constructor " + constructor + " on " + className);
}
Constructor constructor = clazz.getDeclaredConstructor(new Class[]{String[].class, lifeCycleClass});
constructor.setAccessible(true);
return constructor.newInstance(parms, null);
}
/**
* Attempt to stop the service in an orderly fashion. Go to the service, see
* if it implements Administrable, then get the admin proxy and see if it
* implements DestroyAdmin. If so, call it.
*
* @param env
*/
public void stopService(ApplicationEnvironment env) {
/* Option 1 - Service has a getAdmin() method - it probably implements
* Administrable.
*/
Object serviceInstance = env.getServiceInstance();
Method getAdmin = null;
try {
getAdmin = serviceInstance.getClass().getMethod(Strings.GET_ADMIN, new Class[0]);
} catch (Exception ex) {
// Silent catch - leave it null;
}
if (getAdmin != null) {
}
}
void createWorkDirectoryFor(ApplicationEnvironment env) throws IOException {
FileObject managerDir=fileUtility.getWorkingDirectory(env.getApplicationManagerName());
FileObject workingDir=managerDir.resolveFile(env.getServiceName());
if (!workingDir.exists()) {
workingDir.createFolder();
}
File workingDirFile=new File(workingDir.getName().getPath());
env.setWorkingDirectory(workingDirFile);
}
void grantPermissionsToWorkDirectoryFor(ApplicationEnvironment env) {
Permission[] perms=new Permission[] {
new FilePermission(env.getWorkingDirectory().getAbsolutePath()+"/-","read,write,delete")
};
grantPermissions(env.getClassLoader(), perms);
}
}