/*
* Copyright 2008 the original author or authors.
*
* 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.rioproject.impl.exec;
import com.sun.jini.config.Config;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.export.Exporter;
import net.jini.id.Uuid;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import org.rioproject.impl.config.ExporterConfig;
import org.rioproject.impl.container.DiscardManager;
import org.rioproject.impl.container.ServiceLogUtil;
import org.rioproject.deploy.ServiceBeanInstance;
import org.rioproject.deploy.ServiceBeanInstantiationException;
import org.rioproject.deploy.ServiceRecord;
import org.rioproject.exec.ExecDescriptor;
import org.rioproject.exec.ServiceBeanExecListener;
import org.rioproject.exec.ServiceBeanExecutor;
import org.rioproject.impl.fdh.FaultDetectionListener;
import org.rioproject.opstring.OperationalStringManager;
import org.rioproject.opstring.ServiceElement;
import org.rioproject.impl.util.FileUtils;
import org.rioproject.impl.util.RMIServiceNameHelper;
import org.rioproject.rmi.RegistryUtil;
import org.rioproject.system.capability.PlatformCapability;
import org.rioproject.util.PropertyHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.UnknownHostException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ExportException;
import java.util.Map;
/**
* Creates and manages a service bean in it's own process
*
* @author Dennis Reedy
*/
public class ServiceBeanExecHandler {
private final ServiceElement sElem;
private final Configuration config;
private final Uuid uuid;
private ServiceBeanExecutor execHandler;
private ServiceBeanInstance instance;
private ServiceRecord serviceRecord;
private ProcessManager manager;
/** The amount of time to wait for a forked service to be created */
private int forkedServiceWaitTime = 60; // number of seconds
private static final String COMPONENT = "org.rioproject.exec";
private static final Logger logger = LoggerFactory.getLogger(ServiceBeanExecHandler.class);
public ServiceBeanExecHandler(final ServiceElement sElem, final Configuration config, final Uuid uuid) {
this.sElem = sElem;
this.config = config;
this.uuid = uuid;
try {
forkedServiceWaitTime = Config.getIntEntry(config,
COMPONENT,
"forkedServiceWaitTime",
60, //default is 1 minute
5, //minimum of 5 second wait
60*5); // max of 5 minute wait
} catch(ConfigurationException e) {
logger.warn("Getting forkedServiceWaitTime, using default", e);
}
}
public ServiceBeanExecutor getServiceBeanExecutor() {
return execHandler;
}
public ServiceBeanInstance exec(final OperationalStringManager opStringMgr,
final DiscardManager discardManager,
final PlatformCapability[] installedPlatformCapabilities) throws Exception {
RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
return exec(opStringMgr, discardManager, installedPlatformCapabilities, runtime.getClassPath());
}
public ServiceBeanInstance exec(final OperationalStringManager opStringMgr,
final DiscardManager discardManager,
final PlatformCapability[] installedPlatformCapabilities,
final String classPath) throws Exception {
ExecDescriptor exDesc = new ExecDescriptor();
String rioHome = System.getProperty("rio.home");
if(rioHome==null)
throw new ServiceBeanInstantiationException(String.format("Cannot exec service [%s], unknown RIO_HOME system property",
sElem.getName()));
String classPathToUse;
if(classPath==null)
classPathToUse = CommandLineHelper.generateRioStarterClassPath();
else
classPathToUse = classPath;
/* Create a normalized service name, translating " " to "_" and
* appending the instance ID to the name. This will be used for the
* log name and the registry bind name */
String normalizedServiceName = RMIServiceNameHelper.createNormalizedServiceName(sElem);
String serviceBindName = RMIServiceNameHelper.createBindName(sElem);
/* Get the Cybernode's RMI Registry */
int regPort = RegistryUtil.checkRegistry();
String sPort = Integer.toString(regPort);
String logDir = getLogDirectory(config, sElem.getOperationalStringName());
if(!logDir.endsWith(File.separator))
logDir = logDir+File.separator;
logger.info("Logging for {} will be sent to {}", sElem.getName(), logDir);
/* Create command line */
exDesc.setCommandLine(CommandLineHelper.getJava());
/* Create input args */
StringBuilder inputArgsBuilder = new StringBuilder();
inputArgsBuilder.append(CommandLineHelper.getClassPath(classPathToUse));
String jvmOptions = (sElem.getExecDescriptor()==null? null: sElem.getExecDescriptor().getInputArgs());
inputArgsBuilder.append(CommandLineHelper.createInputArgs(normalizedServiceName,
serviceBindName,
sPort,
jvmOptions,
logDir));
inputArgsBuilder.append(CommandLineHelper.getStarterClass()).append(" ");
String serviceBeanExecStarter = CommandLineHelper.getStarterConfig(rioHome);
logger.trace("Using service bean exec starter: {}", serviceBeanExecStarter);
inputArgsBuilder.append(serviceBeanExecStarter);
exDesc.setInputArgs(inputArgsBuilder.toString());
exDesc.setWorkingDirectory(System.getProperty("user.dir"));
/* If we have an exec descriptor, make add any environment settings the
* service has declared */
if(sElem.getExecDescriptor()!=null) {
Map<String, String> env = sElem.getExecDescriptor().getEnvironment();
for(Map.Entry<String, String> entry : env.entrySet()) {
env.put(entry.getKey(), PropertyHelper.expandProperties(entry.getValue()));
}
exDesc.setEnvironment(env);
}
String serviceOut = logDir+normalizedServiceName+".out";
exDesc.setStdErrFileName(serviceOut);
exDesc.setStdOutFileName(serviceOut);
try {
Registry registry = LocateRegistry.getRegistry(regPort);
ForkedServiceBeanListener forkedServiceListener = new ForkedServiceBeanListener(discardManager);
ServiceBeanExecListener listener = forkedServiceListener.getServiceBeanExecListener();
long start = System.currentTimeMillis();
Shell shell = ShellFactory.createShell();
try {
String shellTemplate = (String)config.getEntry(COMPONENT,
"serviceBeanExecShellTemplate",
String.class,
null);
if(shellTemplate!=null)
shell.setShellTemplate(shellTemplate);
} catch (ConfigurationException e) {
logger.warn("Cannot get shell template from configuration, continue with default");
}
logger.info("Invoke {}.exec for {}", shell.getClass().getName(), ServiceLogUtil.logName(sElem));
if(logger.isDebugEnabled()) {
logger.debug("{}, working directory {}", ServiceLogUtil.logName(sElem), exDesc.getWorkingDirectory());
}
manager = shell.exec(exDesc);
forkedServiceListener.setName(serviceBindName);
forkedServiceListener.setRegistryPort(regPort);
long wait = 0;
while (wait < (forkedServiceWaitTime*10)) {
try {
execHandler = (ServiceBeanExecutor)registry.lookup(serviceBindName);
forkedServiceListener.createFDH(execHandler);
execHandler.setUuid(uuid);
execHandler.setServiceBeanExecListener(listener);
if(installedPlatformCapabilities!=null && installedPlatformCapabilities.length>0)
execHandler.applyPlatformCapabilities(installedPlatformCapabilities);
instance = execHandler.instantiate(sElem, opStringMgr);
long activationTime = System.currentTimeMillis()-start;
logger.info("Forked instance created for [{}], pid=[{}], activation time={} seconds",
serviceBindName, manager.getPid(), (activationTime/1000));
break;
} catch (NotBoundException e) {
try {
Thread.sleep(100);
wait++;
} catch (InterruptedException e1) {
logger.warn("Interrupted waiting for ServiceBean [{}] to register into Registry",
serviceBindName);
}
}
}
if (instance==null) {
logger.warn("Timed out waiting for [{}]. Waited [{}] seconds, configured wait " +
"time is [{}] seconds. Killing spawned process and unregistering from local " +
"registry. Check the service's output log to determine root cause(s)",
serviceBindName, wait, forkedServiceWaitTime);
manager.destroy(true);
throw new ServiceBeanInstantiationException("Failed to fork");
}
} catch (Exception e) {
unregister(regPort, serviceBindName);
logger.info("Terminate process for ServiceBean [{}]", serviceBindName);
if(manager!=null)
manager.destroy(true);
throw e;
}
return instance;
}
public ServiceRecord getServiceRecord() {
return serviceRecord;
}
private void unregister(final int regPort, final String name) {
try {
Registry registry = LocateRegistry.getRegistry(regPort);
registry.unbind(name);
logger.info("Unbound failed ServiceBean fork for [{}]", name);
} catch (Exception e1) {
// ignore
}
}
private String getLogDirectory(final Configuration config,
final String opstringName) throws ConfigurationException, IOException {
String logDirDefault = System.getProperty("rio.log.dir");
if(logDirDefault==null)
logDirDefault = System.getProperty("rio.home")+File.separator+"logs";
String serviceLogRootDirectory = (String)config.getEntry(COMPONENT,
"serviceLogRootDirectory",
String.class,
logDirDefault);
File rootDir = new File(serviceLogRootDirectory);
FileUtils.checkDirectory(rootDir, "service log root");
File serviceLogDir = new File(rootDir, "service_logs");
FileUtils.checkDirectory(serviceLogDir, "service logs");
String s = opstringName.replaceAll(" ", "_").replaceAll("\\(", "" ).replaceAll("\\)", "" );
File opstringDir = new File(serviceLogDir, s);
FileUtils.checkDirectory(opstringDir, "opstring log root");
return opstringDir.getAbsolutePath();
}
class ForkedServiceBeanListener implements ServiceBeanExecListener, FaultDetectionListener<String> {
final DiscardManager discardManager;
Exporter exporter;
ServiceBeanExecListener listener;
int registryPort;
String name;
ForkedServiceBeanListener(final DiscardManager discardManager) {
this.discardManager = discardManager;
}
void setRegistryPort(final int registryPort) {
this.registryPort = registryPort;
}
void createFDH(final ServiceBeanExecutor execHandler) {
try {
JVMProcessMonitor.getInstance().monitor(execHandler.getID(), this);
} catch (RemoteException e) {
serviceFailure(execHandler, null);
}
}
void setName(String name) {
this.name = name;
}
ServiceBeanExecListener getServiceBeanExecListener() throws ConfigurationException, ExportException, UnknownHostException {
if(listener==null) {
exporter = ExporterConfig.getExporter(config,
"org.rioproject.cybernode",
"exporter");
if(exporter==null)
exporter = new BasicJeriExporter(ExporterConfig.getServerEndpoint(), new BasicILFactory());
listener = (ServiceBeanExecListener)exporter.export(this);
}
return listener;
}
public void serviceInstantiated(final ServiceRecord record) {
serviceRecord = record;
if(manager!=null) {
serviceRecord.setPid(manager.getPid());
}
logger.debug("Instantiation notification for {}", ServiceLogUtil.logName(sElem));
}
public void serviceDiscarded(final ServiceRecord record) {
logger.info("Discard notification for {}/{}", sElem.getOperationalStringName(), sElem.getName());
discardManager.discard();
}
public void serviceFailure(final Object service, final String serviceID) {
try {
Registry registry = LocateRegistry.getRegistry(registryPort);
registry.unbind(name);
logger.info("Terminated ServiceBean fork for [{}] unbound from local registry", name);
} catch (Exception e) {
// ignore
}
serviceDiscarded(null);
}
}
}