/*
* $Id$
*
* SARL is an general-purpose agent programming language.
* More details on http://www.sarl.io
*
* Copyright (C) 2014-2017 the original authors 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 io.janusproject.kernel;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.google.common.util.concurrent.Service;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Module;
import com.google.inject.Singleton;
import io.janusproject.JanusConfig;
import io.janusproject.kernel.services.jdk.spawn.CannotSpawnException;
import io.janusproject.services.IServiceManager;
import io.janusproject.services.Services;
import io.janusproject.services.logging.LogService;
import io.janusproject.services.spawn.KernelAgentSpawnListener;
import io.janusproject.services.spawn.SpawnService;
import io.janusproject.util.LoggerCreator;
import io.janusproject.util.TwoStepConstruction;
import io.sarl.lang.core.Agent;
import io.sarl.lang.core.AgentContext;
/**
* This class represents the Kernel of the Janus platform.
*
* <p><strong>The Kernel is a singleton.</strong>
*
* <p>The Kernel is assimilated to an agent that is omniscient and distributed other the network. It is containing all the other
* agents.
*
* <p>To create a Kernel, you should use the function {@link #create(Module...)}.
*
* @author $Author: srodriguez$
* @author $Author: ngaud$
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
@Singleton
@TwoStepConstruction(names = { "setJanusContext" })
public class Kernel {
private AgentContext janusContext;
private final IServiceManager serviceManager;
private final SpawnService spawnService;
private final LogService loggingService;
/**
* Logger used by the kernel, but not linked to the logging kernel's service.
*/
private Logger rawLogger;
private final AtomicBoolean isRunning = new AtomicBoolean(true);
/**
* Constructs a Janus kernel.
*
* @param serviceManager is the instance of the service manager that must be used by the kernel.
* @param spawnService is the instance of the spawn service.
* @param loggingService is the instance of the logging service.
* @param exceptionHandler is the handler of the uncaught exceptions.
*/
@Inject
Kernel(IServiceManager serviceManager, SpawnService spawnService, LogService loggingService,
UncaughtExceptionHandler exceptionHandler) {
// Initialize the fields
this.serviceManager = serviceManager;
this.spawnService = spawnService;
this.loggingService = loggingService;
// Ensure that all the threads has a default hander.
Thread.setDefaultUncaughtExceptionHandler(exceptionHandler);
// Listen on the kernel's events
this.spawnService.addKernelAgentSpawnListener(new KernelStoppingListener());
// Start the services NOW to ensure that the default context and space
// of the Janus agent are catched by the modules;
Services.startServices(this.serviceManager);
}
/**
* Create an instance of {@link Kernel}.
*
* @param modules - modules to link to the new kernel.
* @return the new kernel.
*/
public static final Kernel create(Module... modules) {
final Injector injector = Guice.createInjector(modules);
final Kernel k = injector.getInstance(Kernel.class);
return k;
}
/**
* Replies if the kernel is running or not.
*
* @return <code>true</code> if the kernel is running; <code>false</code> otherwise.
*/
public boolean isRunning() {
return this.isRunning.get();
}
/**
* Spawn an agent of the given type, and pass the parameters to its initialization function.
*
* @param agent - the type of the agent to spawn.
* @param params - the list of the parameters to pass to the agent initialization function.
* @return the identifier of the agent, never <code>null</code>.
*/
public UUID spawn(Class<? extends Agent> agent, Object... params) {
final List<UUID> ids = this.spawnService.spawn(1, null, this.janusContext, null, agent, params);
if (ids.isEmpty()) {
throw new CannotSpawnException(agent, null);
}
return ids.get(0);
}
/**
* Spawn agents of the given type, and pass the parameters to its initialization function.
*
* @param nbAgents - the number of agents to spawn.
* @param agent - the type of the agents to spawn.
* @param params - the list of the parameters to pass to the agent initialization function.
* @return the identifiers of the agents, never <code>null</code>.
*/
public List<UUID> spawn(int nbAgents, Class<? extends Agent> agent, Object... params) {
return this.spawnService.spawn(nbAgents, null, this.janusContext, null, agent, params);
}
/**
* Spawn an agent of the given type, and pass the parameters to its initialization function.
*
* @param agentID - the identifier of the agent to spawn. If <code>null</code> the identifier is randomly selected.
* @param agent - the type of the agent to spawn.
* @param params - the list of the parameters to pass to the agent initialization function.
* @return the identifier of the agent, never <code>null</code>.
*/
public UUID spawn(UUID agentID, Class<? extends Agent> agent, Object... params) {
final List<UUID> ids = this.spawnService.spawn(1, null, this.janusContext, agentID, agent, params);
if (ids.isEmpty()) {
return null;
}
return ids.get(0);
}
/**
* Replies a kernel service that is alive.
*
* @param <S> - type of the type to reply.
* @param type - type of the type to reply.
* @return the service, or <code>null</code>.
*/
public <S extends Service> S getService(Class<S> type) {
for (final Service serv : this.serviceManager.servicesByState().values()) {
if (serv.isRunning() && type.isInstance(serv)) {
return type.cast(serv);
}
}
return null;
}
/**
* Replies the logger used by the kernel.
*
* @return the logger of the kernel.
*/
public Logger getLogger() {
Logger log = this.loggingService.getLogger();
if (log == null) {
if (this.rawLogger == null) {
this.rawLogger = LoggerCreator.createLogger(JanusConfig.JANUS_DEFAULT_PLATFORM_NAME);
}
log = this.rawLogger;
}
return log;
}
/**
* Change the Janus context of the kernel.
*
* @param janusContext - the new janus kernel. It must be never <code>null</code>.
*/
@Inject
void setJanusContext(@io.janusproject.kernel.annotations.Kernel AgentContext janusContext) {
assert janusContext != null;
this.janusContext = janusContext;
}
/**
* Listener on platform events.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class KernelStoppingListener implements KernelAgentSpawnListener {
/**
* Construct.
*/
KernelStoppingListener() {
//
}
@Override
public void kernelAgentSpawn() {
//
}
@Override
public void kernelAgentDestroy() {
// CAUTION: EXECUTE THE STOP FUNCTION IN A THREAD THAT
// IS INDEPENDENT TO THE ONES FROM THE EXECUTORS
// CREATED BY THE EXECUTORSERVICE.
// THIS AVOID THE STOP FUNCTION TO BE INTERRUPTED
// BECAUSE THE EXECUTORSERVICE WAS SHUTTED DOWN.
final StopTheKernel t = new StopTheKernel();
t.start();
}
}
/**
* Runner for stopping the kernel.
*
* @author $Author: sgalland$
* @version $FullVersion$
* @mavengroupid $GroupId$
* @mavenartifactid $ArtifactId$
*/
private class StopTheKernel implements ThreadFactory, Runnable, UncaughtExceptionHandler {
/**
* Construct.
*/
StopTheKernel() {
//
}
/**
* Start the thread.
*/
public void start() {
final Thread t = newThread(this);
t.start();
}
@SuppressWarnings("synthetic-access")
@Override
public void run() {
final Logger logger = getLogger();
logger.info(Messages.Kernel_0);
Services.stopServices(Kernel.this.serviceManager);
logger.info(Messages.Kernel_1);
Kernel.this.isRunning.set(false);
}
@Override
public Thread newThread(Runnable runnable) {
final Thread t = Executors.defaultThreadFactory().newThread(runnable);
t.setName("Janus kernel shutdown"); //$NON-NLS-1$
t.setDaemon(false);
t.setUncaughtExceptionHandler(this);
return t;
}
@Override
public void uncaughtException(Thread thread, Throwable exception) {
assert thread != null;
assert exception != null;
final LogRecord record = new LogRecord(Level.SEVERE, exception.getLocalizedMessage());
record.setThrown(exception);
final StackTraceElement elt = exception.getStackTrace()[0];
assert elt != null;
record.setSourceClassName(elt.getClassName());
record.setSourceMethodName(elt.getMethodName());
final Logger logger = getLogger();
logger.log(record);
}
}
}