/*
* NodeRuntimeImpl.java
*
* Created on Apr 27, 2010, 10:55:43 PM
*
* Description: Provides runtime support for nodes in the local JVM.
*
* Copyright (C) Apr 27, 2010, Stephen L. Reed.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.texai.ahcs.impl;
import de.uniba.wiai.lspi.chord.data.URL;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyStore;
import java.security.KeyStore.PasswordProtection;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.SignatureException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertPath;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.UUID;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import net.jcip.annotations.NotThreadSafe;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.log4j.Logger;
import org.openrdf.model.URI;
import org.openrdf.model.impl.URIImpl;
import org.texai.ahcs.router.MessageRouter;
import org.texai.ahcsSupport.AHCSConstants;
import org.texai.ahcsSupport.AlbusMessageDispatcher;
import org.texai.ahcsSupport.Message;
import org.texai.ahcsSupport.NodeAccess;
import org.texai.ahcsSupport.NodeRuntime;
import org.texai.ahcsSupport.NodeTypeInitializer;
import org.texai.ahcsSupport.RoleInfo;
import org.texai.ahcsSupport.RoleTypeInitializer;
import org.texai.ahcsSupport.domainEntity.Node;
import org.texai.ahcsSupport.domainEntity.NodeRuntimeConfiguration;
import org.texai.ahcsSupport.domainEntity.NodeType;
import org.texai.ahcsSupport.domainEntity.Role;
import org.texai.kb.CacheInitializer;
import org.texai.kb.Constants;
import org.texai.kb.persistence.DistributedRepositoryManager;
import org.texai.kb.persistence.RDFEntityManager;
import org.texai.kb.persistence.RDFEntityPersister;
import org.texai.util.NetworkUtils;
import org.texai.util.StringUtils;
import org.texai.util.TexaiException;
import org.texai.x509.X509SecurityInfo;
import org.texai.x509.X509Utils;
/** Provides runtime support for nodes in the local JVM.
*
* @author reed
*/
@NotThreadSafe
public class NodeRuntimeImpl implements NodeRuntime, AlbusMessageDispatcher {
/** the logger */
private static final Logger LOGGER = Logger.getLogger(NodeRuntimeImpl.class);
/** the role remote messaging dictionary, role id --> role */
private final Map<URI, Role> roleRemoteMessagingDictionary = new HashMap<>();
/** the executor */
private final ExecutorService executor = Executors.newCachedThreadPool();
/** the role id that identifies this node runtime when it communicates on behalf of itself */
private URI roleId;
/** the dictionary of replyWith values used to suspend message-sending threads while awaiting response messages,
* replyWith --> lock object
*/
private final Map<UUID, Object> replyWithsDictionary = new HashMap<>();
/** the in-reply-to message dictionary, in-reply-to UUID --> message */
private final Map<UUID, Message> inReplyToDictionary = new HashMap<>();
/** the X.509 security information for this node runtime */
private X509SecurityInfo x509SecurityInfo;
/** the X.509 certificate dictionary, role id --> X.509 certificate */
private final Map<URI, X509Certificate> x509CertificateDictionary = new HashMap<>();
/** the name of the cache for the X.509 certificates, remote role id --> X.509 certificate */
public static final String CACHE_X509_CERTIFICATES = "X.509 certificates";
/** the names of caches used in the node runtime */
static final String[] NAMED_CACHES = {
CACHE_X509_CERTIFICATES
};
/** the node runtime application thread */
private Thread nodeRuntimeApplicationThread;
/** the message router */
private MessageRouter messageRouter;
/** the node runtime keystore, which contains the single X509 certificate chain used by SSL client authentication */
private KeyStore nodeRuntimeKeyStore;
/** the node runtime keystore lock */
private final Object nodeRuntimeKeyStore_lock = new Object();
/** the role keystore, which contains all the local role X509 certificates */
private KeyStore roleKeyStore;
/** the role keystore lock */
private final Object roleKeyStore_lock = new Object();
/** the node runtime RDF entity manager */
private RDFEntityManager rdfEntityManager;
/** the node access object */
private final NodeAccess nodeAccess;
// the intermediate certificate-signing certificate
/** the signing private key */
private PrivateKey certificateSigningPrivateKey;
/** the signing X.509 certificate path */
private CertPath certificateSigningCertPath;
/** the signing X.509 certificate */
private X509Certificate certificateSigningX509Certificate;
/** the node runtime key store file path */
private String nodeRuntimeKeyStoreFilePath;
/** the role key store file path */
private String roleKeyStoreFilePath;
/** the key store password */
//TODO substitute user-specified password
public static final char[] KEY_STORE_PASSWORD = "node-runtime-keystore-password".toCharArray();
/** the node runtime X.509 certificate */
private X509Certificate nodeRuntimeX509Certificate;
/** the indicator to quit this application */
private AtomicBoolean isQuit = new AtomicBoolean(false);
/** the launcher role id */
private final URI launcherRoleId;
/** the indicator whether finalization has occurred */
private AtomicBoolean isFinalized = new AtomicBoolean(false);
/** the indicator that initialization has completed, and that the node runtime should be persisted upon shutdown */
private AtomicBoolean isInitialized = new AtomicBoolean(false);
/** the shutdown hook */
private ShutdownHook shutdownHook;
/** the local area network ID */
private final UUID localAreaNetworkID;
/** the TCP port as presented to the Internet */
private final int externalPort;
/** the host address as presented to the LAN, e.g. turing */
private final String internalHostName = NetworkUtils.getHostName();
/** the TCP port as presented to the LAN */
private final int internalPort;
/** the timer */
private final Timer timer = new Timer();
/** the node runtime configuration */
private final NodeRuntimeConfiguration nodeRuntimeConfiguration;
/** the operations to be logged */
private final Set<String> loggedOperations = new HashSet<>();
/** the indicator to reload the node and role type definitions */
private static final boolean IS_RELOAD_NODE_AND_ROLE_TYPES = false;
/** Constructs a new singleton NodeRuntime instance.
*
* @param launcherRoleId the launcher role id
* @param nodeRuntimeId the node runtime id
* @param internalPort the internal port
* @param externalPort the external port
* @param localURL the URL of this node in the Chord network
* @param bootstrapURL the bootstrap URL, or null if this is the first node in the Chord network
* @param localAreaNetworkID the local area network ID
*/
public NodeRuntimeImpl(
final URI launcherRoleId,
final URI nodeRuntimeId,
final int internalPort,
final int externalPort,
final URL localURL,
final URL bootstrapURL,
final UUID localAreaNetworkID) {
//Preconditions
assert launcherRoleId != null : "launcherRoleId must not be null";
assert nodeRuntimeId != null : "nodeRuntimeId must not be null";
assert internalPort > 0 : "internalPort must be positive";
assert externalPort > 0 : "externalPort must be positive";
assert localURL != null : "localURL must not be null";
assert localAreaNetworkID != null : "localAreaNetworkID must not be null";
this.launcherRoleId = launcherRoleId;
this.internalPort = internalPort;
this.externalPort = externalPort;
this.localAreaNetworkID = localAreaNetworkID;
assert !Logger.getLogger(RDFEntityPersister.class).isInfoEnabled();
// configure a shutdown hook to run the finalization method in case the JVM is abnormally ended
shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
CacheInitializer.initializeCaches();
CacheInitializer.addNamedCaches(NAMED_CACHES);
rdfEntityManager = new RDFEntityManager();
nodeAccess = new NodeAccess(rdfEntityManager);
assert !Logger.getLogger(RDFEntityPersister.class).isInfoEnabled();
if (IS_RELOAD_NODE_AND_ROLE_TYPES) {
// load the repositories with the node and role types
LOGGER.info("reloading node types and role types");
DistributedRepositoryManager.clearNamedRepository("NodeRoleTypes");
final RoleTypeInitializer roleTypeInitializer = new RoleTypeInitializer();
roleTypeInitializer.initialize(rdfEntityManager);
if ((new File("data/role-types.xml")).exists()) {
roleTypeInitializer.process("data/role-types.xml");
} else {
roleTypeInitializer.process("../Main/data/role-types.xml");
}
roleTypeInitializer.finalization();
final NodeTypeInitializer nodeTypeInitializer = new NodeTypeInitializer();
nodeTypeInitializer.initialize(rdfEntityManager);
if ((new File("data/node-types.xml")).exists()) {
nodeTypeInitializer.process("data/node-types.xml");
} else {
nodeTypeInitializer.process("../Main/data/node-types.xml");
}
nodeTypeInitializer.finalization();
}
// instantiate the node runtime configuration: nodes, roles, and state/value bindings
nodeRuntimeConfiguration = nodeAccess.getNodeRuntimeConfiguration(nodeRuntimeId);
assert nodeRuntimeConfiguration != null : "configuration not found for " + nodeRuntimeId;
LOGGER.info("instantiating " + nodeRuntimeConfiguration);
assert !Logger.getLogger(RDFEntityPersister.class).isInfoEnabled();
nodeRuntimeConfiguration.instantiate();
initializeX509SecurityInfo();
messageRouter = new MessageRouter(
this,
internalPort,
externalPort,
localURL,
bootstrapURL);
if (getNode(AHCSConstants.NODE_NICKNAME_TOPPER) == null) {
// first time direct assembly of the top friendship node - the Bootstrap skill does the rest
installTopFriendshipNode();
} else {
postLoadingDependencyInjection();
}
// start up
isInitialized.set(true);
nodeRuntimeApplicationThread = new Thread(new RunApplication(this));
nodeRuntimeApplicationThread.start();
}
/** Finalizes this application. */
private void finalization() {
isFinalized.getAndSet(true);
LOGGER.info("finalization");
if (isInitialized.get()) {
LOGGER.info("persisting nodes, roles, and state values");
nodeAccess.persistNodeRuntimeConfiguration(nodeRuntimeConfiguration);
}
if (messageRouter != null) {
messageRouter.finalization();
}
executor.shutdownNow();
LOGGER.info("shutting down the node runtime");
if (rdfEntityManager != null) {
rdfEntityManager.close();
}
CacheManager.getInstance().shutdown();
DistributedRepositoryManager.shutDown();
// advance the random number generator and save it
for (int i = 0; i < 100; i++) {
X509Utils.getSecureRandom().nextInt();
}
X509Utils.serializeSecureRandom(X509Utils.DEFAULT_SECURE_RANDOM_PATH);
LOGGER.info("node runtime completed");
if (!Thread.currentThread().equals(shutdownHook)) {
System.exit(0);
}
}
/** Adds the given operation to the list of logged operations.
*
* @param operation the given operation
*/
public void addLoggedOperation(final String operation) {
//Preconditions
assert operation != null : "operation must not be null";
assert !operation.isEmpty() : "operation must not be empty";
synchronized (loggedOperations) {
loggedOperations.add(operation);
}
}
/** Removes the given operation from the list of logged operations.
*
* @param operation the given operation
*/
public void removeLoggedOperation(final String operation) {
//Preconditions
assert operation != null : "operation must not be null";
assert !operation.isEmpty() : "operation must not be empty";
synchronized (loggedOperations) {
loggedOperations.remove(operation);
}
}
/** Returns whether the given message is to be logged.
*
* @param message the given message
* @return whether the given message is to be logged
*/
public boolean isMessageLogged(final Message message) {
//Preconditions
assert message != null : "message must not be null";
synchronized (loggedOperations) {
return loggedOperations.contains(message.getOperation());
}
}
/** Gets the local area network ID.
*
* @return the local area network ID
*/
@Override
public UUID getLocalAreaNetworkID() {
return localAreaNetworkID;
}
/** Gets the host address as presented to the Internet, e.g. texai.dyndns.org.
*
* @return the host address as presented to the Internet
*/
@Override
public String getExternalHostName() {
return messageRouter.getExternalHostName();
}
/** Gets the TCP port as presented to the Internet.
*
* @return the TCP port as presented to the Internet
*/
@Override
public int getExternalPort() {
return externalPort;
}
/** Gets the host address as presented to the LAN, e.g. turing.
*
* @return the host address as presented to the LAN
*/
@Override
public String getInternalHostName() {
return internalHostName;
}
/** Gets the TCP port as presented to the LAN.
*
* @return the TCP port as presented to the LAN
*/
@Override
public int getInternalPort() {
return internalPort;
}
/** Gets the top friendship role.
*
* @return the top friendship role
*/
@Override
public Role getTopFriendshipRole() {
return nodeRuntimeConfiguration.getTopFriendshipRole();
}
/** Gets the node access object.
*
* @return the node access object
*/
public NodeAccess getNodeAccess() {
return nodeAccess;
}
/** Gets the node runtime id.
*
* @return the node runtime id
*/
@Override
public URI getNodeRuntimeId() {
//Preconditions
assert nodeRuntimeConfiguration != null : "nodeRuntimeConfiguration must not be null";
return nodeRuntimeConfiguration.getNodeRuntimeId();
}
/** Shuts down the node runtime. */
@Override
public void shutdown() {
finalization();
}
/** Gets the node runtime configuration.
*
* @return the node runtime configuration
*/
public NodeRuntimeConfiguration getNodeRuntimeConfiguration() {
return nodeRuntimeConfiguration;
}
/** Persists the node runtime configuration, including nodes, roles and state values. */
public void persistNodeRuntimeConfiguration() {
nodeAccess.persistNodeRuntimeConfiguration(nodeRuntimeConfiguration);
}
/** Returns the id of the role having the given type contained in the node having the given nickname.
*
* @param nodeNickname the given nickname
* @param roleTypeName the given role type
* @return the id of the role having the given type contained in the node having the given nickname, or null if not found
*/
@Override
public URI getRoleId(
final String nodeNickname,
final String roleTypeName) {
//Preconditions
assert StringUtils.isNonEmptyString(nodeNickname) : "nodeNickname must be a non-empty string";
assert StringUtils.isNonEmptyString(roleTypeName) : "roleTypeName must be a non-empty string";
final Node node = getNode(nodeNickname);
if (node == null) {
return null;
}
final Role role = node.getRoleForTypeName(roleTypeName);
if (role == null) {
return null;
} else {
return role.getId();
}
}
/** Provides a shutdown hook to finalize resources when the JVM is unexpectedly shutdown. */
class ShutdownHook extends Thread {
@Override
public void run() {
Thread.currentThread().setName("shutdown");
if (!isFinalized.get()) {
LOGGER.warn("***** shutdown, finalizing resources *****");
finalization();
}
}
}
/** Registers the given role.
*
* @param role the given role
*/
@Override
public void registerRole(final Role role) {
//Preconditions
assert role != null : "role must not be null";
assert role.getId() != null : "role must have been persisted, and thus have an id";
nodeRuntimeConfiguration.addRole(role);
final URI registeredRoleId = role.getId();
synchronized (x509CertificateDictionary) {
x509CertificateDictionary.put(registeredRoleId, role.getX509Certificate());
}
}
/** Unregisters the given role.
*
* @param role the given role
*/
@Override
public void unregisterRole(final Role role) {
//Preconditions
assert role != null : "role must not be null";
assert role.getId() != null : "role must have been persisted, and thus have an id";
final URI registeredRoleId = role.getId();
synchronized (roleRemoteMessagingDictionary) {
assert !roleRemoteMessagingDictionary.containsKey(registeredRoleId) : "role must not be enabled for remote messaging";
}
nodeRuntimeConfiguration.removeRole(role);
synchronized (x509CertificateDictionary) {
x509CertificateDictionary.remove(registeredRoleId);
}
}
/** Registers the role for remote communications.
*
* @param roleInfo the role information
*/
@Override
public void registerRoleForRemoteCommunications(final RoleInfo roleInfo) {
//Preconditions
assert roleInfo != null : "roleInfo must not be null";
messageRouter.registerRoleForRemoteCommunications(roleInfo);
}
/** Unregisters the role for remote communications.
*
* @param roleInfo the role information
*/
@Override
public void unregisterRoleForRemoteCommunications(final RoleInfo roleInfo) {
//Preconditions
assert roleInfo != null : "roleInfo must not be null";
messageRouter.unregisterRoleForRemoteCommunications(roleInfo);
}
/** Gets an unmodifiable copy of the local nodes.
*
* @return the local nodes
*/
public Set<Node> getNodes() {
return nodeRuntimeConfiguration.getNodes();
}
/** Returns the node having the given nickname.
*
* @param nodeNickname the given nickname
* @return the node having the given nickname, or null if not found
*/
public final Node getNode(final String nodeNickname) {
//Preconditions
assert nodeNickname != null : "nodeNickname must not be null";
assert !nodeNickname.isEmpty() : "nodeNickname must not be empty";
return nodeRuntimeConfiguration.getNode(nodeNickname);
}
/** Adds the given local node.
*
* @param node the local node to add
*/
public void addNode(final Node node) {
//Preconditions
assert node != null : "node must not be null";
// persist the node to ensure that it has an id before adding it to the set of nodes
nodeAccess.persistNode(node);
assert node.getId() != null;
nodeRuntimeConfiguration.addNode(node);
}
/** Removes the given local node.
*
* @param node the given local node to remove
*/
public void removeNode(final Node node) {
//Preconditions
assert node != null : "node must not be null";
nodeRuntimeConfiguration.removeNode(node);
}
/** Sets the indicator to quit.
*
* @param isQuit whether to quit
*/
public void setIsQuit(final boolean isQuit) {
this.isQuit.set(isQuit);
}
/** Dispatches a message in an Albus hierarchical control system.
*
* @param message the Albus message
*/
@Override
public void dispatchAlbusMessage(final Message message) {
//Preconditions
assert message != null : "message must not be null";
final URI senderRoleId = message.getSenderRoleId();
if (!x509CertificateDictionary.containsKey(senderRoleId)) {
final X509Certificate x509Certificate = getRemoteRoleX509Certificate(senderRoleId);
assert x509Certificate != null : "X509 certificate not found for " + senderRoleId;
x509CertificateDictionary.put(senderRoleId, x509Certificate);
}
if (!isLocalRole(senderRoleId)) {
// messages from non-local roles must be signed
message.verify(x509CertificateDictionary.get(senderRoleId));
}
assert nodeAccess != null : "nodeAccess must not be null";
if (isQuit.get()) {
LOGGER.info("quitting, ignoring message:\n " + message.toString(this));
return;
}
final URI recipientRoleId = message.getRecipientRoleId();
final Role role;
role = nodeRuntimeConfiguration.getLocalRole(recipientRoleId);
if (role == null) {
// remote role
if (isMessageLogged(message) || LOGGER.isDebugEnabled()) {
LOGGER.info("relaying message with non-local role to router:\n " + message.toString());
}
messageRouter.dispatchAlbusMessage(message);
} else {
// local role
if (isMessageLogged(message) || LOGGER.isDebugEnabled()) {
LOGGER.info("relaying message to local role " + message.toString(this));
}
role.dispatchAlbusMessage(message);
}
}
/** Gets the local role having the given id.
*
* @param roleId the role id
* @return the local role having the given id, or null if not found
*/
public Role getLocalRole(final URI roleId) {
//Preconditions
assert roleId != null : "roleId must not be null";
return nodeRuntimeConfiguration.getLocalRole(roleId);
}
/** Returns whether the given role id belongs to a local role.
*
* @param roleId the given role id
* @return whether the given role id belongs to a local role
*/
public boolean isLocalRole(final URI roleId) {
//Preconditions
assert roleId != null : "roleId must not be null";
return nodeRuntimeConfiguration.isLocalRole(roleId);
}
/** Resumes the thread that was suspended awaiting the reply message.
*
* @param message the reply message
*/
private void resumeThread(final Message message) {
//Preconditions
assert message != null : "message must not be null";
final UUID inReplyTo = message.getInReplyTo();
synchronized (inReplyToDictionary) {
inReplyToDictionary.put(inReplyTo, message);
}
Object threadLock = null;
synchronized (replyWithsDictionary) {
threadLock = replyWithsDictionary.get(inReplyTo);
replyWithsDictionary.remove(inReplyTo);
}
assert threadLock != null;
LOGGER.info("reply received, resuming suspended thread");
synchronized (threadLock) {
threadLock.notifyAll();
}
}
/** Obtains the X.509 certificate that is owned by the identified role from the message router.
*
* @param ownerRoleId the owner's role id
*/
private X509Certificate getRemoteRoleX509Certificate(final URI ownerRoleId) {
//Preconditions
assert ownerRoleId != null : "ownerRoleId must not be null";
LOGGER.info("getting the X509Certificate for remote role " + ownerRoleId);
final X509Certificate x509Certificate;
final Cache cache = CacheManager.getInstance().getCache(CACHE_X509_CERTIFICATES);
assert cache != null : "cache not found for: " + CACHE_X509_CERTIFICATES;
Element element = cache.get(ownerRoleId);
if (element == null) {
// obtain x509Certificate from the message router
final RoleInfo roleInfo = messageRouter.getRoleInfo(ownerRoleId);
x509Certificate = roleInfo.getRoleX509Certificate();
assert x509Certificate != null : "X509 certificate not found in Chord for " + ownerRoleId;
element = new Element(ownerRoleId, x509Certificate);
cache.put(element);
} else {
x509Certificate = (X509Certificate) element.getValue();
}
//Postconditions
assert x509Certificate != null : "x509Certificate must not be null";
return x509Certificate;
}
/** Sets X.509 security information and the id for the given new role.
*
* @param role the given unpersisted role
*/
@Override
public void setX509SecurityInfoAndIdForRole(final Role role) {
//Preconditions
assert role != null : "role must not be null";
final String roleAlias = generateAlias();
role.setRoleAlias(roleAlias);
role.setId(new URIImpl(
Constants.TEXAI_NAMESPACE
+ role.getClass().getName()
+ "_"
+ roleAlias));
role.setX509SecurityInfo(X509Utils.getX509SecurityInfo(
roleKeyStoreFilePath,
KEY_STORE_PASSWORD,
roleAlias));
}
/** Returns the next preexisting X509 certificate alias, or creates a new certificate and returns its alias.
*
* @return the X509 certificate alias
*/
public String generateAlias() {
final String alias;
final KeyPair roleKeyPair;
final CertPath roleCertPath;
try {
LOGGER.info("generating public and private keys for a new role certificate");
roleKeyPair = X509Utils.generateRSAKeyPair2048();
roleCertPath = X509Utils.generateX509CertificatePath(
roleKeyPair.getPublic(),
certificateSigningPrivateKey,
certificateSigningX509Certificate,
certificateSigningCertPath, null);
assert !roleCertPath.getCertificates().isEmpty();
final X509Certificate roleX509Certificate = (X509Certificate) roleCertPath.getCertificates().get(0);
alias = X509Utils.getUUID(roleX509Certificate).toString();
setRoleKeyStore(X509Utils.addEntryToKeyStore( // refresh loaded keystore
roleKeyStoreFilePath,
KEY_STORE_PASSWORD,
alias, // alias
roleCertPath,
roleKeyPair.getPrivate())); // privateKey
roleKeyStore.store(new FileOutputStream(getRoleKeyStoreFilePath()), getKeyStorePassword());
} catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException | SignatureException | InvalidKeyException | IOException | CertificateException | KeyStoreException ex) {
throw new TexaiException(ex);
}
return alias;
}
/** When implemented by a message router, registers the given SSL proxy.
*
* @param sslProxy the given SSL proxy
*/
@Override
public void registerSSLProxy(final Object sslProxy) {
throw new UnsupportedOperationException("Not implemented.");
}
/** Gets the role id that identifies this node runtime when it communicates on behalf of itself.
*
* @return the role id that identifies this node runtime when it communicates on behalf of itself
*/
@Override
public URI getRoleId() {
return roleId;
}
/** Sets the role id that identifies this node runtime when it communicates on behalf of itself.
*
* @param roleId the role id that identifies this node runtime when it communicates on behalf of itself
*/
public void setRoleId(final URI roleId) {
//Preconditions
assert roleId != null : "roleId must not be null";
this.roleId = roleId;
}
/** Gets the X.509 security information for this node runtime.
*
* @return the X.509 security information for this node runtime
*/
public X509SecurityInfo getX509SecurityInfo() {
return x509SecurityInfo;
}
/** Sets the X.509 security information for this node runtime.
*
* @param x509SecurityInfo the X.509 security information for this node runtime
*/
public void setX509SecurityInfo(final X509SecurityInfo x509SecurityInfo) {
//Preconditions
assert x509SecurityInfo != null : "x509SecurityInfo must not be null";
this.x509SecurityInfo = x509SecurityInfo;
}
/** Gets the executor.
* @return the executor
*/
public Executor getExecutor() {
return executor;
}
/** Gets the timer.
*
* @return the timer
*/
public Timer getTimer() {
return timer;
}
/** Gets the node runtime keystore.
*
* @return the node runtime keystore
*/
public KeyStore getNodeRuntimeKeyStore() {
synchronized (nodeRuntimeKeyStore_lock) {
return nodeRuntimeKeyStore;
}
}
/** Sets the node runtime keystore.
*
* @param nodeRuntimeKeyStore the node runtime keystore
*/
public void setNodeRuntimeKeyStore(final KeyStore nodeRuntimeKeyStore) {
//Preconditions
assert nodeRuntimeKeyStore != null : "nodeRuntimeKeyStore must not be null";
synchronized (nodeRuntimeKeyStore_lock) {
this.nodeRuntimeKeyStore = nodeRuntimeKeyStore;
}
}
/** Gets the role keystore.
*
* @return the role keystore
*/
public KeyStore getRoleKeyStore() {
synchronized (roleKeyStore_lock) {
return roleKeyStore;
}
}
/** Sets the role keystore.
*
* @param roleKeyStore the role keystore
*/
public void setRoleKeyStore(final KeyStore roleKeyStore) {
//Preconditions
assert roleKeyStore != null : "roleKeyStore must not be null";
synchronized (roleKeyStore_lock) {
this.roleKeyStore = roleKeyStore;
}
}
/** Gets the node runtime RDF entity manager.
*
* @return the node runtime RDF entity manager
*/
@Override
public RDFEntityManager getRdfEntityManager() {
return rdfEntityManager;
}
/** Gets the certificate-signing private key.
*
* @return the certificate-signing private key
*/
public PrivateKey getCertificateSigningPrivateKey() {
return certificateSigningPrivateKey;
}
/** Sets the certificate-signing private key.
*
* @param certificateSigningPrivateKey the certificate-signing private key
*/
public void setCertificateSigningPrivateKey(final PrivateKey certificateSigningPrivateKey) {
//Preconditions
assert certificateSigningPrivateKey != null : "certificateSigningPrivateKey must not be null";
this.certificateSigningPrivateKey = certificateSigningPrivateKey;
}
/** Gets the certificate-signing X.509 certificate path.
*
* @return the certificate-signing X.509 certificate path
*/
public CertPath getCertificateSigningCertPath() {
return certificateSigningCertPath;
}
/** Sets the certificate-signing X.509 certificate path.
*
* @param certificateSigningCertPath the certificate-signing X.509 certificate path
*/
public void setCertificateSigningCertPath(final CertPath certificateSigningCertPath) {
//Preconditions
assert certificateSigningCertPath != null : "certificateSigningCertPath must not be null";
this.certificateSigningCertPath = certificateSigningCertPath;
}
/** Gets the certificate-signing X.509 certificate.
*
* @return the certificate-signing X.509 certificate
*/
public X509Certificate getCertificateSigningX509Certificate() {
return certificateSigningX509Certificate;
}
/** Sets the certificate-signing X.509 certificate.
*
* @param certificateSigningX509Certificate the certificate-signing X.509 certificate
*/
public void setCertificateSigningX509Certificate(final X509Certificate certificateSigningX509Certificate) {
//Preconditions
assert certificateSigningX509Certificate != null : "certificateSigningX509Certificate must not be null";
this.certificateSigningX509Certificate = certificateSigningX509Certificate;
}
/** Gets the node runtime key store file path.
*
* @return the node runtime key store file path
*/
public String getNodeRuntimeKeyStoreFilePath() {
return nodeRuntimeKeyStoreFilePath;
}
/** Sets the node runtime key store file path.
*
* @param nodeRuntimeKeyStoreFilePath the node runtime key store file path
*/
public void setNodeRuntimeKeyStoreFilePath(final String nodeRuntimeKeyStoreFilePath) {
//Preconditions
assert nodeRuntimeKeyStoreFilePath != null : "nodeRuntimeKeyStoreFilePath must not be null";
assert !nodeRuntimeKeyStoreFilePath.isEmpty() : "nodeRuntimeKeyStoreFilePath must not be empty";
this.nodeRuntimeKeyStoreFilePath = nodeRuntimeKeyStoreFilePath;
}
/** Gets the role key store file path.
*
* @return the role key store file path
*/
public String getRoleKeyStoreFilePath() {
return roleKeyStoreFilePath;
}
/** Sets the role key store file path.
*
* @param roleKeyStoreFilePath the role key store file path
*/
public void setRoleKeyStoreFilePath(final String roleKeyStoreFilePath) {
//Preconditions
assert roleKeyStoreFilePath != null : "roleKeyStoreFilePath must not be null";
assert !roleKeyStoreFilePath.isEmpty() : "roleKeyStoreFilePath must not be empty";
this.roleKeyStoreFilePath = roleKeyStoreFilePath;
}
/** Gets the key store password.
*
* @return the key store password
*/
public char[] getKeyStorePassword() {
return KEY_STORE_PASSWORD;
}
/** Gets the node runtime X.509 certificate.
*
* @return the node runtime X.509 certificate
*/
public X509Certificate getNodeRuntimeX509Certificate() {
return nodeRuntimeX509Certificate;
}
/** Sets the node runtime X.509 certificate.
*
* @param nodeRuntimeX509Certificate the node runtime X.509 certificate
*/
public void setNodeRuntimeX509Certificate(final X509Certificate nodeRuntimeX509Certificate) {
//Preconditions
assert nodeRuntimeX509Certificate != null : "nodeRuntimeX509Certificate must not be null";
this.nodeRuntimeX509Certificate = nodeRuntimeX509Certificate;
}
/** Initializes the X.509 certificate dictionary with the node runtime role id and certificate. */
public void initializeX509CertificateDictionary() {
//Preconditions
assert roleId != null : "roleId must not be null";
assert nodeRuntimeX509Certificate != null : "nodeRuntimeX509Certificate must not be null";
synchronized (x509CertificateDictionary) {
x509CertificateDictionary.put(roleId, nodeRuntimeX509Certificate);
}
}
/** Gets the node runtime key store entry alias.
*
* @return the node runtime key store entry alias
*/
public String getNodeRuntimeKeyStoreEntryAlias() {
return nodeRuntimeConfiguration.getNodeRuntimeKeyStoreEntryAlias();
}
/** Gets the certificate-signing key store entry alias.
*
* @return the certificate-signing key store entry alias
*/
public String getCertificateSigningKeyStoreEntryAlias() {
return nodeRuntimeConfiguration.getCertificateSigningKeyStoreEntryAlias();
}
/** Gets the top friendship role id.
*
* @return the topFriendshipRoleId
*/
public URI getTopFriendshipRoleId() {
return nodeRuntimeConfiguration.getTopFriendshipRole().getId();
}
/** Gets the launcher role id.
*
* @return the launcher role id
*/
public URI getLauncherRoleId() {
return launcherRoleId;
}
/** Sets up the node runtime's X.509 security information either after a new installation, or during a restart. */
private void initializeX509SecurityInfo() {
//Preconditions
assert X509Utils.isJCEUnlimitedStrengthPolicy() : "the JCE unlimited strength jurisdiction policy files must be installed";
assert nodeAccess != null : "nodeAccess must not be null";
LOGGER.info("initializing node runtime X.509 security information");
final String basePath;
if (System.getProperty("org.texai.basepath") == null) {
basePath = ".";
} else {
basePath = System.getProperty("org.texai.basepath");
}
LOGGER.info("basePath: " + basePath);
nodeRuntimeKeyStoreFilePath = basePath + "/data/node-runtime-keystore.uber";
LOGGER.info("nodeRuntimeKeyStoreFilePath " + getNodeRuntimeKeyStoreFilePath());
try {
nodeRuntimeKeyStore = X509Utils.findKeyStore(
nodeRuntimeKeyStoreFilePath,
getKeyStorePassword());
Enumeration<String> aliases = getNodeRuntimeKeyStore().aliases();
while (aliases.hasMoreElements()) {
final String existingAlias = aliases.nextElement();
LOGGER.info(" node runtime X509 certificate alias: " + existingAlias);
}
// node runtime certificate
final PasswordProtection passwordProtection = new PasswordProtection(getKeyStorePassword());
PrivateKeyEntry privateKeyEntry = (PrivateKeyEntry) getNodeRuntimeKeyStore().getEntry(
getNodeRuntimeKeyStoreEntryAlias(), // alias
passwordProtection);
assert privateKeyEntry != null;
setNodeRuntimeX509Certificate((X509Certificate) privateKeyEntry.getCertificate());
LOGGER.info("node runtime certificate principal: " + getNodeRuntimeX509Certificate().getSubjectX500Principal());
LOGGER.info(" issuer: " + getNodeRuntimeX509Certificate().getIssuerX500Principal());
setRoleId(new URIImpl(Constants.TEXAI_NAMESPACE + "NodeRuntime_" + getNodeRuntimeKeyStoreEntryAlias()));
LOGGER.info("node runtime roleId " + getRoleId());
initializeX509CertificateDictionary();
LOGGER.info("setting the X.509 security information for the node runtime");
setX509SecurityInfo(X509Utils.getX509SecurityInfo(
getNodeRuntimeKeyStoreFilePath(),
getKeyStorePassword(),
getNodeRuntimeKeyStoreEntryAlias()));
roleKeyStoreFilePath = basePath + "/data/role-keystore.uber";
roleKeyStore = X509Utils.findKeyStore(
roleKeyStoreFilePath,
getKeyStorePassword());
if (LOGGER.isDebugEnabled()) {
LOGGER.info("roleKeyStoreFilePath " + getRoleKeyStoreFilePath());
aliases = getRoleKeyStore().aliases();
while (aliases.hasMoreElements()) {
final String existingAlias = aliases.nextElement();
LOGGER.info(" role X509 certificate alias: " + existingAlias);
}
}
// signing certificate
privateKeyEntry =
(PrivateKeyEntry) getRoleKeyStore().getEntry(
getCertificateSigningKeyStoreEntryAlias(), // alias
passwordProtection);
assert privateKeyEntry != null;
setCertificateSigningX509Certificate((X509Certificate) privateKeyEntry.getCertificate());
setCertificateSigningPrivateKey(privateKeyEntry.getPrivateKey());
setCertificateSigningCertPath(X509Utils.generateCertPath(privateKeyEntry.getCertificateChain()));
} catch (TexaiException | KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException | NoSuchProviderException | UnrecoverableEntryException ex) {
throw new TexaiException(ex);
}
//Postconditions
assert getX509SecurityInfo() != null;
}
/** Installs the top friendship node - if this is the first execution of this node runtime. */
private void installTopFriendshipNode() {
// directly assemble the top friendship node
LOGGER.info("assembling top friendship node");
final NodeType topFriendshipNodeType = getNodeAccess().findNodeType(AHCSConstants.TOP_FRIENDSHIP_NODE_TYPE);
assert topFriendshipNodeType != null;
LOGGER.info(" top friendship node type: " + topFriendshipNodeType);
final Node topFriendshipNode = new Node(topFriendshipNodeType, this);
LOGGER.info(" top friendship node: " + topFriendshipNode);
topFriendshipNode.setNodeNickname(AHCSConstants.NODE_NICKNAME_TOPPER);
topFriendshipNode.installRoles(getNodeAccess());
rdfEntityManager.persist(topFriendshipNode);
addNode(topFriendshipNode);
//Postconditions
assert getTopFriendshipRole() != null;
}
/** Perform post-loading node, role and skill dependency injection. */
private void postLoadingDependencyInjection() {
LOGGER.info("installing roles and their skills, from the existing node runtime configuration");
for (final Node node : getNodeRuntimeConfiguration().getNodes()) {
node.setNodeRuntime(this);
node.installRoles(nodeAccess);
for (final Role role : node.getRoles()) {
role.setNodeRuntime(this);
role.setNode(node);
role.setX509SecurityInfo(X509Utils.getX509SecurityInfo(
roleKeyStoreFilePath,
KEY_STORE_PASSWORD,
role.getRoleAlias()));
}
}
}
/** Provides a thread to run the node runtime application, by initializing it and thereafter sleeping to keep the it from
* otherwise terminating.
*/
static class RunApplication implements Runnable {
/** the containing node runtime */
private final NodeRuntimeImpl nodeRuntime;
/** Constructs a new RunApplication instance.
*
* @param nodeRuntime the containing node runtime
*/
RunApplication(final NodeRuntimeImpl nodeRuntime) {
//Preconditions
assert nodeRuntime != null : "nodeRuntime must not be null";
this.nodeRuntime = nodeRuntime;
}
/** Executes the the node runtime application. */
@Override
public void run() {
Thread.currentThread().setName("Node Runtime");
LOGGER.info("starting the node runtime");
// send an initialize task message to the BootstrapRole, which configures and initializes the remainder of the nodes
final Node topFriendshipNode = nodeRuntime.getNode(AHCSConstants.NODE_NICKNAME_TOPPER);
assert topFriendshipNode != null;
final Role bootstrapRole = topFriendshipNode.getRoleForTypeName(AHCSConstants.BOOTSTRAP_ROLE_TYPE);
assert bootstrapRole != null;
final Message message1 = new Message(
nodeRuntime.getRoleId(), // senderRoleId
nodeRuntime.getClass().getName(), // senderService
bootstrapRole.getId(), // recipientRoleId
"org.texai.skill.lifecycle.Bootstrap", // service
AHCSConstants.AHCS_INITIALIZE_TASK); // operation
message1.sign(nodeRuntime.getX509SecurityInfo().getPrivateKey());
nodeRuntime.dispatchAlbusMessage(message1);
while (!nodeRuntime.isQuit.get()) {
try {
// wait here until the runtime quits
Thread.sleep(30000);
} catch (InterruptedException ex) {
}
}
nodeRuntime.finalization();
}
}
}