JNDI in Wildfly: Part 2 - The remote naming service
JNDI in Wildfly: Part 2 - The remote naming service
In this article, let’s check the Wildfly Remote Naming Service.
In the previous article, we have checked NamingStore
and NamingsService
. We understand that the purpose of the NamingService
is to enable NamingStore
during Wildfly startup process. In addition to this process, Wildfly also need to enable remote naming service, so the users can access the service from other machines via network.
Here is the relative configuration in standalone.xml
:
$ grep -C 2 'remote-naming' standalone.xml
</subsystem>
<subsystem xmlns="urn:jboss:domain:naming:2.0">
<remote-naming/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:pojo:1.0"/>
From the above configuration, we can see in basic standalone mode, the Wildfly naming subsystem will enable the ‘RemoteNamingServerService
will enable the remote naming service. Here is the class diagram of the RemoteNamingServerService
:
From the above diagram, we can see the the RemoteNamingServerService
contains the remoteNamingService
and namingStore
. We have learned the namingStore
is the backend repository of the registered naming resources. On the other hand, the remoteNamingService
is the class that provides naming service remote access ability.
In addition, RemoteNamingServerService
will inject NamingStore
into RemoteNamingService
. Here is the relative code in
theRemoteNamingServerService
class:
public synchronized void start(StartContext context) throws StartException {
try {
final Context namingContext = new NamingContext(namingStore.getValue(), new Hashtable<String, Object>());
remoteNamingService = new RemoteNamingService(namingContext, executorService.getValue(), RemoteNamingLogger.INSTANCE);
remoteNamingService.start(endpoint.getValue());
} catch (Exception e) {
throw new StartException("Failed to start remote naming service", e);
}
}
The above code shows the logic of the start(...)
method: It will create a namingContext
with namingStore
included, and then register the namingContentxt
into remoteNamingServer
. Finally it starts the remoteNamingService
by calling its start(...)
method.
So we should focus on checking the RemoteNamingService
class provided by the jboss-remote-naming
project to see how it’s implemented. Before that, we need to fetch the source code of the jboss-remote-naming
project. You can find the source code of this project on Github. Here is the URL of the project:
https://github.com/jbossas/jboss-remote-naming
You need to clone the above repository to your local machine to check the source code. Here is the screenshot of the project in my IDE:
From the above screenshot, you can see the project structure of the jboss-remote-naming
project.
Now let’s see the class diagram of the RemoteNamingService
class:
From the above diagram, we can see the RemoteNamingService
and its inner classes. We know the service loading process in Wildfly is asynchronous, so from above diagram, we can see RemoteNamingService
will use ChannelOpenListener
and ChannelCloseHandler
to manage the service lifecycle. There is an additional ClientVersionReceiver
inner class in the diagram, and we will check its usage later.
The next step we can check the start(...)
method of the RemoteNamingService
. Here is the sequence diagram of the method:
From the above sequence diagram, we can see the start(...)
method of RemoteNamingService
is just to register the ChannelOpenListener
. Now let’s see the code of ChannelOpenListener
:
private class ChannelOpenListener implements OpenListener {
public void channelOpened(Channel channel) {
log.debugf("Channel Opened - %s", channel);
channel.addCloseHandler(new ChannelCloseHandler());
try {
writeHeader(channel);
channel.receiveMessage(new ClientVersionReceiver());
} catch (IOException e) {
logger.failedToSendHeader(e);
IoUtils.safeClose(channel);
}
}
public void registrationTerminated() {
}
}
From the above code, we can see the ChannelOpenListener
uses ClientVersionReceiver
to handle the received message. From the above diagram, we can see the ClientVersionReceiver
has a handleMessage(...)
method. This method is used to handle the received message. Let’s see the code of handleMessage(...)
message:
public void handleMessage(Channel channel, MessageInputStream messageInputStream) {
DataInputStream dis = new DataInputStream(messageInputStream);
try {
byte[] namingHeader = new byte[6];
dis.read(namingHeader);
if (!Arrays.equals(namingHeader, NAMING)) {
throw new IOException("Invalid leading bytes in header.");
}
byte version = dis.readByte();
log.debugf("Chosen version 0x0%d", version);
Versions.getRemoteNamingServer(version, channel, RemoteNamingService.this);
} catch (IOException e) {
logger.failedToDetermineClientVersion(e);
} finally {
IoUtils.safeClose(dis);
}
}
The above code shows that it will read the namingHeader
from the received message via network, and determines the correct RemoteNamingServer
to use. Actually there is only one version of RemoteNamingServer
currently, and it’s named RemoteNamingServerV1
. Here is the class diagram of the RemoteNamingServer
and its implementation:
From the above diagram, we can see RemoteNamingServer
currently has one version of implementation, which is RemoteNamingServerV1
. In addition, we can see the RemoteNamingServerV1
contains MessageReceiver
to deal with the incoming request.
We can see the full package name of RemoteNamingServerV1
is org.jboss.naming.remote.protocol.v1
. In this package, it contains the current implementation of the remote naming server. In the package, it contains a Protocol
class that implements the relative JNDI operations. Here is the screenshot of the class:
From the above screenshot, we can see the operations implemented by the class. Because the Protocol
class is very big and it contains a thousand lines of code, so I won’t explain its detail implementation here.
Now let’s check the RemoteNamingStore
interface and its implementation RemoteNamingStoreV1
. Here is the class diagram of them:
From the above diagram, we can see the RemoteNamingStore
is used to store the naming resources provide the resources via JNDI operations.
What’s the relationship of the RemoteNamingStore
interface and the RemoteNamingServer
interface? To answer this question, we can check the ProtocolCommand
interface. Here is the class diagram of the interface:
From the above diagram, we can see the ProtocolCommand
defines two methods: one is handleServerMessage(...)
and the other is handleClientMessage(...)
.
In handleServerMessage(...)
method, it uses the remoteNamingService
(There is currently a typo in the class, and I have submitted the PR to fix it. See: fix a minor typo in ProtocolCommand #32) to interact with the naming service provider(The Wildfly server in our case).
In handleClientMessage(...)
method, it uses the namingStore
to deal with the user requests. This is very similar to the situation we have see in the previous article, which Wildfly uses the local NamingStore
to deal with the local JNDI requests.
The ProcotolCommand
interface is implemented in each JNDI operation defined in the Protocol
class. For example, here is the code in Protocol
class that implements the JNDI LOOKUP
operation:
class Protocol {
static ProtocolCommand<Object> LOOKUP = new BaseProtocolCommand<Object, ClassLoadingNamedIoFuture<Object>>((byte) 0x01) {
public void handleServerMessage(Channel channel, final DataInput input, final int correlationId, final RemoteNamingService remoteNamingService) throws IOException {
final Unmarshaller unmarshaller = prepareForUnMarshalling(input, this.getClass().getClassLoader());
Name name;
try {
byte paramType = unmarshaller.readByte();
if (paramType != NAME) {
remoteNamingService.getLogger().unexpectedParameterType(NAME, paramType);
}
name = unmarshaller.readObject(Name.class);
} catch (ClassNotFoundException cnfe) {
throw new IOException(cnfe);
} finally {
unmarshaller.close();
}
try {
final Object result = remoteNamingService.getLocalContext().lookup(name);
write(channel, new WriteUtil.Writer() {
public void write(DataOutput output) throws IOException {
output.writeByte(getCommandId());
output.writeInt(correlationId);
output.writeByte(SUCCESS);
if (result instanceof Context) {
output.writeByte(CONTEXT);
} else {
output.writeByte(OBJECT);
final Marshaller marshaller = prepareForMarshalling(output);
marshaller.writeObject(result);
marshaller.finish();
}
}
});
} catch (NamingException e) {
writeExceptionResponse(channel, e, getCommandId(), correlationId);
}
}
public void handleClientMessage(final DataInput input, final int correlationId, final RemoteNamingStore namingStore) throws IOException {
readResult(correlationId, input, new ValueReader<ClassLoadingNamedIoFuture<Object>>() {
public void read(final DataInput input, ClassLoadingNamedIoFuture<Object> future) throws IOException {
byte parameterType = input.readByte();
switch (parameterType) {
case OBJECT: {
try {
final Unmarshaller unmarshaller = prepareForUnMarshalling(input, future.getClassLoader());
future.setResult(unmarshaller.readObject());
unmarshaller.finish();
} catch (ClassNotFoundException e) {
throw new IOException(e);
} catch (ClassCastException e) {
throw new IOException(e);
}
break;
}
case CONTEXT: {
future.setResult(new RemoteContext(NamedIoFuture.class.cast(future).name, namingStore, new Hashtable<String, Object>()));
break;
}
default: {
throw new IOException("Unexpected response parameter received.");
}
}
}
});
}
};
}
I have removed unrelated code and in above you can see how does the LOOKUP
operation implements the ProtocolCommand
interface, and how the namingStore
and namingService
are used in the methods. We can see the Protocol
class uses the ProtocolCommand
to provide services to both server side (Wildfly) and user side. Compared with the local naming service in Wildfly source base as we see in last article, the remote design is more complex.
Above are all the topics I want to say in this article. In the next article, let’s see the network communication layer of the jboss-remote-naming
project.