Initial commit
Some checks failed
Gradle Build / build (push) Has been cancelled

This commit is contained in:
CaiXiang
2024-11-30 18:36:13 +08:00
commit aa56926258
2134 changed files with 232943 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
apply from: "${rootDir}/gradle/java-project.gradle"
apply from: "${rootDir}/gradle/java-codequality.gradle"
apply from: "${rootDir}/gradle/guice-project.gradle"
apply from: "${rootDir}/gradle/publishing-java.gradle"
dependencies {
api project(':opentcs-api-injection')
api project(':opentcs-common')
}
task release {
dependsOn build
}

View File

@@ -0,0 +1,40 @@
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAnnotationArgs=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineMethodParams=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAfterDotInChainedMethodCalls=false
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineDisjunctiveCatchTypes=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineFor=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineImplements=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapFor=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.sortMembersByVisibility=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.visibilityOrder=PUBLIC;PROTECTED;DEFAULT;PRIVATE
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeFinallyOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapMethodParams=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.enable-indent=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineArrayInit=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineCallArgs=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapDisjunctiveCatchTypes=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.keepGettersAndSettersTogether=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsList=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsKeyword=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapExtendsImplementsKeyword=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.classMembersOrder=STATIC FIELD;FIELD;STATIC_INIT;CONSTRUCTOR;INSTANCE_INIT;STATIC METHOD;METHOD;STATIC CLASS;CLASS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapEnumConstants=WRAP_ALWAYS
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapCommentText=false
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapThrowsList=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.wrapAssert=WRAP_IF_LONG
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.importGroupsOrder=*
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.continuationIndentSize=4
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeElseOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.placeCatchOnNewLine=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineAnnotationArgs=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineTryResources=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.preserveNewLinesInComments=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineParenthesized=true
netbeans.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.alignMultilineThrows=true
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=2
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=100
netbeans.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=true
netbeans.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project

View File

@@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import com.google.inject.multibindings.Multibinder;
import jakarta.inject.Singleton;
import org.opentcs.access.rmi.factories.NullSocketFactoryProvider;
import org.opentcs.access.rmi.factories.SecureSocketFactoryProvider;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.customizations.kernel.KernelInjectionModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Configures the RMI services extension.
*/
public class RmiServicesModule
extends
KernelInjectionModule {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(RmiServicesModule.class);
/**
* Creates a new instance.
*/
public RmiServicesModule() {
}
@Override
protected void configure() {
RmiKernelInterfaceConfiguration configuration
= getConfigBindingProvider().get(
RmiKernelInterfaceConfiguration.PREFIX,
RmiKernelInterfaceConfiguration.class
);
if (!configuration.enable()) {
LOG.info("RMI services disabled by configuration.");
return;
}
bind(RmiKernelInterfaceConfiguration.class)
.toInstance(configuration);
bind(RegistryProvider.class)
.in(Singleton.class);
bind(UserManager.class)
.in(Singleton.class);
bind(UserAccountProvider.class)
.to(DefaultUserAccountProvider.class);
if (configuration.useSsl()) {
bind(SocketFactoryProvider.class)
.to(SecureSocketFactoryProvider.class)
.in(Singleton.class);
}
else {
LOG.warn("SSL encryption disabled, connections will not be secured!");
bind(SocketFactoryProvider.class)
.to(NullSocketFactoryProvider.class)
.in(Singleton.class);
}
Multibinder<KernelRemoteService> remoteServices
= Multibinder.newSetBinder(binder(), KernelRemoteService.class);
remoteServices.addBinding().to(StandardRemotePlantModelService.class);
remoteServices.addBinding().to(StandardRemoteTransportOrderService.class);
remoteServices.addBinding().to(StandardRemoteVehicleService.class);
remoteServices.addBinding().to(StandardRemoteNotificationService.class);
remoteServices.addBinding().to(StandardRemoteRouterService.class);
remoteServices.addBinding().to(StandardRemoteDispatcherService.class);
remoteServices.addBinding().to(StandardRemoteQueryService.class);
remoteServices.addBinding().to(StandardRemotePeripheralService.class);
remoteServices.addBinding().to(StandardRemotePeripheralJobService.class);
remoteServices.addBinding().to(StandardRemotePeripheralDispatcherService.class);
extensionsBinderAllModes().addBinding()
.to(StandardRemoteKernelClientPortal.class)
.in(Singleton.class);
}
}

View File

@@ -0,0 +1,4 @@
# SPDX-FileCopyrightText: The openTCS Authors
# SPDX-License-Identifier: MIT
org.opentcs.kernel.extensions.rmi.RmiServicesModule

View File

@@ -0,0 +1,36 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import org.opentcs.common.GuestUserCredentials;
/**
* The default impelementation of {@link UserAccountProvider}.
* Provides only one (guest) user account.
*
* @see GuestUserCredentials
*/
public class DefaultUserAccountProvider
implements
UserAccountProvider {
public DefaultUserAccountProvider() {
}
@Override
public Set<UserAccount> getUserAccounts() {
return new HashSet<>(
Arrays.asList(
new UserAccount(
GuestUserCredentials.USER,
GuestUserCredentials.PASSWORD,
EnumSet.allOf(UserPermission.class)
)
)
);
}
}

View File

@@ -0,0 +1,165 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import jakarta.annotation.Nonnull;
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
import org.opentcs.data.TCSObjectEvent;
import org.opentcs.util.event.EventHandler;
/**
* Stores events and keeps them until a client fetches them.
*/
public class EventBuffer
implements
EventHandler {
/**
* The buffered events.
*/
private final Deque<Object> events = new LinkedList<>();
/**
* This buffer's event filter.
*/
private Predicate<Object> eventFilter;
/**
* A flag indicating whether this event buffer's client is currently waiting for an event.
*/
private boolean waitingClient;
/**
* Creates a new instance
*
* @param eventFilter This buffer's initial event filter.
*/
public EventBuffer(
@Nonnull
Predicate<Object> eventFilter
) {
this.eventFilter = requireNonNull(eventFilter, "eventFilter");
}
// Methods declared in interface EventListener start here
@Override
public void onEvent(Object event) {
requireNonNull(event, "event");
synchronized (events) {
if (eventFilter.test(event)) {
if (!tryMergeWithPreviousEvent(event)) {
events.add(event);
}
// If the client is waiting for an event, wake it up, since there is one now.
if (waitingClient) {
events.notify();
}
}
}
}
// Methods not declared in any interface start here
/**
* Returns a list of events that are currently stored in this buffer and
* clears the buffer.
* If the buffer is currently empty, block until an event arrives, or for the
* specified amount of time to pass, whichever occurs first.
*
* @param timeout The maximum amount of time (in ms) to wait for an event to
* arrive. Must be at least 0 (in which case this method will return
* immediately, without waiting for an event to arrive).
* @return A list of events that are currently stored in this buffer.
* @throws IllegalArgumentException If <code>timeout</code> is less than 0.
*/
public List<Object> getEvents(long timeout)
throws IllegalArgumentException {
checkArgument(timeout >= 0, "timeout < 0: %s", timeout);
synchronized (events) {
if (timeout > 0 && events.isEmpty()) {
waitingClient = true;
try {
events.wait(timeout);
}
catch (InterruptedException exc) {
throw new IllegalStateException("Unexpectedly interrupted", exc);
}
finally {
waitingClient = false;
}
}
List<Object> result = new ArrayList<>(events);
events.clear();
return result;
}
}
/**
* Checks whether a client is currently waiting for events arriving in this
* buffer.
*
* @return <code>true</code> if a client is currently waiting, else
* <code>false</code>.
*/
public boolean hasWaitingClient() {
synchronized (events) {
return waitingClient;
}
}
/**
* Sets this buffer's event filter.
*
* @param eventFilter This buffer's new event filter.
*/
public void setEventFilter(
@Nonnull
Predicate<Object> eventFilter
) {
synchronized (events) {
this.eventFilter = requireNonNull(eventFilter);
}
}
/**
* If possible, merge the given new event with the previous one in the buffer.
*
* @param event The new event.
* @return <code>true</code> if the new event was merged with the previous one.
*/
private boolean tryMergeWithPreviousEvent(Object event) {
if (!(event instanceof TCSObjectEvent currentEvent)
|| !(events.peekLast() instanceof TCSObjectEvent previousEvent)) {
return false;
}
if (currentEvent.getType() != TCSObjectEvent.Type.OBJECT_MODIFIED
|| previousEvent.getType() != TCSObjectEvent.Type.OBJECT_MODIFIED) {
return false;
}
if (!Objects.equals(
currentEvent.getCurrentObjectState().getReference(),
previousEvent.getCurrentObjectState().getReference()
)) {
return false;
}
events.removeLast();
events.add(
new TCSObjectEvent(
currentEvent.getCurrentObjectState(),
previousEvent.getPreviousObjectState(),
TCSObjectEvent.Type.OBJECT_MODIFIED
)
);
return true;
}
}

View File

@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import java.util.concurrent.ExecutionException;
import org.opentcs.access.KernelRuntimeException;
import org.opentcs.components.Lifecycle;
/**
* A base class for kernel-side implementations of remote services.
*/
public abstract class KernelRemoteService
implements
Lifecycle {
/**
* The message to log when a service method execution failed.
*/
private static final String EXECUTION_FAILED_MESSAGE = "Failed to execute service method";
/**
* Wraps the given exception into a suitable {@link RuntimeException}.
*
* @param exc The exception to find a runtime exception for.
* @return The runtime exception.
*/
protected RuntimeException findSuitableExceptionFor(Exception exc) {
if (exc instanceof InterruptedException) {
return new IllegalStateException("Unexpectedly interrupted");
}
if (exc instanceof ExecutionException
&& exc.getCause() instanceof RuntimeException) {
return (RuntimeException) exc.getCause();
}
return new KernelRuntimeException(EXECUTION_FAILED_MESSAGE, exc.getCause());
}
}

View File

@@ -0,0 +1,113 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nonnull;
import jakarta.inject.Inject;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.components.Lifecycle;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides the one {@link Registry} instance used for RMI communication.
*/
public class RegistryProvider
implements
Lifecycle {
/**
* This class' logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(RegistryProvider.class);
/**
* Provides socket factories used to create RMI registries.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* This class' configuration.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* The actual registry instance.
*/
private Registry registry;
/**
* Whether this provider is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param configuration This class' configuration.
*/
@Inject
public RegistryProvider(
@Nonnull
SocketFactoryProvider socketFactoryProvider,
@Nonnull
RmiKernelInterfaceConfiguration configuration
) {
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.configuration = requireNonNull(configuration, "configuration");
}
@Override
public void initialize() {
if (isInitialized()) {
LOG.debug("Already initialized.");
return;
}
installRegistry();
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
LOG.debug("Not initialized.");
return;
}
registry = null;
initialized = false;
}
@Nonnull
public Registry get() {
return registry;
}
private void installRegistry() {
try {
LOG.debug("Trying to create a local registry...");
registry = LocateRegistry.createRegistry(
configuration.registryPort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
// Make sure the registry is running
registry.list();
}
catch (RemoteException ex) {
LOG.error("Couldn't create a working local registry.");
registry = null;
throw new RuntimeException(ex);
}
}
}

View File

@@ -0,0 +1,140 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import org.opentcs.access.rmi.services.RemoteKernelServicePortal;
import org.opentcs.configuration.ConfigurationEntry;
import org.opentcs.configuration.ConfigurationPrefix;
/**
* Provides methods to configure the {@link RemoteKernelServicePortal} and the
* {@link KernelRemoteService}s.
*/
@ConfigurationPrefix(RmiKernelInterfaceConfiguration.PREFIX)
public interface RmiKernelInterfaceConfiguration {
/**
* This configuration's prefix.
*/
String PREFIX = "rmikernelinterface";
@ConfigurationEntry(
type = "Boolean",
description = {"Whether to enable the interface."},
orderKey = "0",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START
)
Boolean enable();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the RMI.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_1"
)
int registryPort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote kernel service portal.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_3"
)
int remoteKernelServicePortalPort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote plant model service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_4"
)
int remotePlantModelServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote transport order service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_5"
)
int remoteTransportOrderServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote vehicle service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_6"
)
int remoteVehicleServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote notification service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_7"
)
int remoteNotificationServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote scheduler service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_8"
)
int remoteSchedulerServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote router service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_9"
)
int remoteRouterServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote dispatcher service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_10"
)
int remoteDispatcherServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote query service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_11"
)
int remoteQueryServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote peripheral service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_12"
)
int remotePeripheralServicePort();
@ConfigurationEntry(
type = "Integer",
description = "The TCP port of the remote peripheral job service.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_13"
)
int remotePeripheralJobServicePort();
@ConfigurationEntry(
type = "Long",
description = "The interval for cleaning out inactive clients (in ms).",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "2_sweeping"
)
long clientSweepInterval();
@ConfigurationEntry(
type = "Boolean",
description = "Whether to use SSL to encrypt connections.",
changesApplied = ConfigurationEntry.ChangesApplied.ON_APPLICATION_START,
orderKey = "0_address_11"
)
boolean useSsl();
}

View File

@@ -0,0 +1,247 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteDispatcherService;
import org.opentcs.components.kernel.services.DispatcherService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.ReroutingType;
import org.opentcs.data.order.TransportOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteDispatcherService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_DISPATCHER_SERVICE}.
* </p>
*/
public class StandardRemoteDispatcherService
extends
KernelRemoteService
implements
RemoteDispatcherService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemoteDispatcherService.class);
/**
* The dispatcher service to invoke methods on.
*/
private final DispatcherService dispatcherService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param dispatcherService The dispatcher service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteDispatcherService(
DispatcherService dispatcherService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.dispatcherService = requireNonNull(dispatcherService, "dispatcherService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteDispatcherServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_DISPATCHER_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_DISPATCHER_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public void dispatch(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.dispatch()).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void withdrawByVehicle(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
boolean immediateAbort
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.withdrawByVehicle(ref, immediateAbort))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void withdrawByTransportOrder(
ClientID clientId,
TCSObjectReference<TransportOrder> ref,
boolean immediateAbort
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.withdrawByTransportOrder(ref, immediateAbort))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void reroute(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
ReroutingType reroutingType
)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.reroute(ref, reroutingType))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void rerouteAll(ClientID clientId, ReroutingType reroutingType)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.rerouteAll(reroutingType))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void assignNow(ClientID clientId, TCSObjectReference<TransportOrder> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> dispatcherService.assignNow(ref))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,233 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import org.opentcs.access.CredentialsException;
import org.opentcs.access.Kernel;
import org.opentcs.access.KernelRuntimeException;
import org.opentcs.access.LocalKernel;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteKernelServicePortal;
import org.opentcs.components.kernel.KernelExtension;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.kernel.extensions.rmi.UserManager.ClientEntry;
import org.opentcs.util.event.EventHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteKernelServicePortal} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_KERNEL_CLIENT_PORTAL}.
* </p>
*/
public class StandardRemoteKernelClientPortal
implements
RemoteKernelServicePortal,
KernelExtension {
/**
* This class' logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemoteKernelClientPortal.class);
/**
* The kernel.
*/
private final Kernel kernel;
/**
* The kernel's remote services.
*/
private final Set<KernelRemoteService> remoteServices;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote portal registers.
*/
private final RegistryProvider registryProvider;
/**
* The event handler to publish events to.
*/
private final EventHandler eventHandler;
/**
* The registry with which this remote portal registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote portal is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param kernel The kernel.
* @param remoteServices The kernel's remote services.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote portal registers.
* @param eventHandler The event handler to publish events to.
*/
@Inject
public StandardRemoteKernelClientPortal(
LocalKernel kernel,
Set<KernelRemoteService> remoteServices,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@ApplicationEventBus
EventHandler eventHandler
) {
this.kernel = requireNonNull(kernel, "kernel");
this.remoteServices = requireNonNull(remoteServices, "remoteServices");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.eventHandler = requireNonNull(eventHandler, "eventHandler");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
registryProvider.initialize();
userManager.initialize();
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteKernelServicePortalPort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_KERNEL_CLIENT_PORTAL, this);
LOG.debug("Bound instance {} with registry {}.", rmiRegistry.list(), rmiRegistry);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
for (KernelRemoteService remoteService : remoteServices) {
remoteService.initialize();
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
for (KernelRemoteService remoteService : remoteServices) {
remoteService.terminate();
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_KERNEL_CLIENT_PORTAL);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
userManager.terminate();
registryProvider.terminate();
initialized = false;
}
@Override
public ClientID login(String userName, String password, Predicate<Object> eventFilter)
throws CredentialsException {
requireNonNull(userName, "userName");
requireNonNull(password, "password");
UserAccount account = userManager.getUser(userName);
if (account == null || !account.getPassword().equals(password)) {
LOG.debug("Authentication failed for user {}.", userName);
throw new CredentialsException("Authentication failed for user " + userName);
}
// Generate a new ID for the client.
ClientID clientId = new ClientID(userName);
// Add an entry for the newly connected client.
ClientEntry clientEntry = new ClientEntry(userName, account.getPermissions());
clientEntry.getEventBuffer().setEventFilter(eventFilter);
userManager.registerClient(clientId, clientEntry);
LOG.debug("New client named {} logged in", clientId.getClientName());
return clientId;
}
@Override
public void logout(ClientID clientID) {
requireNonNull("clientID");
// Forget the client so it won't be able to call methods on this kernel and won't receive
// events any more.
userManager.unregisterClient(clientID);
LOG.debug("Client named {} logged out", clientID.getClientName());
}
@Override
public Kernel.State getState(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return kernel.getState();
}
@Override
public List<Object> fetchEvents(ClientID clientId, long timeout)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return userManager.pollEvents(clientId, timeout);
}
@Override
public void publishEvent(ClientID clientId, Object event)
throws KernelRuntimeException {
userManager.verifyCredentials(clientId, UserPermission.PUBLISH_MESSAGES);
eventHandler.onEvent(event);
}
}

View File

@@ -0,0 +1,178 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteNotificationService;
import org.opentcs.components.kernel.services.NotificationService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.notification.UserNotification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteNotificationService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_NOTIFICATION_SERVICE}.
* </p>
*/
public class StandardRemoteNotificationService
extends
KernelRemoteService
implements
RemoteNotificationService {
/**
* This class's logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(StandardRemoteNotificationService.class);
/**
* The notification service to invoke methods on.
*/
private final NotificationService notificationService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param notificationService The notification service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteNotificationService(
NotificationService notificationService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.notificationService = requireNonNull(notificationService, "plantModelService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteNotificationServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_NOTIFICATION_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_NOTIFICATION_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public List<UserNotification> fetchUserNotifications(
ClientID clientId,
Predicate<UserNotification> predicate
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return notificationService.fetchUserNotifications(predicate);
}
@Override
public void publishUserNotification(ClientID clientId, UserNotification notification) {
userManager.verifyCredentials(clientId, UserPermission.PUBLISH_MESSAGES);
try {
kernelExecutor.submit(() -> notificationService.publishUserNotification(notification)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,194 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemotePeripheralDispatcherService;
import org.opentcs.components.kernel.services.PeripheralDispatcherService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.data.peripherals.PeripheralJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemotePeripheralDispatcherService}
* interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_PERIPHERAL_DISPATCHER_SERVICE}.
* </p>
*/
public class StandardRemotePeripheralDispatcherService
extends
KernelRemoteService
implements
RemotePeripheralDispatcherService {
/**
* This class's logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(StandardRemotePeripheralDispatcherService.class);
/**
* The peripheral dispatcher service to invoke methods on.
*/
private final PeripheralDispatcherService dispatcherService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param dispatcherService The peripheral dispatcher service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemotePeripheralDispatcherService(
PeripheralDispatcherService dispatcherService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.dispatcherService = requireNonNull(dispatcherService, "dispatcherService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteDispatcherServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_PERIPHERAL_DISPATCHER_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_PERIPHERAL_DISPATCHER_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public void dispatch(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERAL_JOBS);
try {
kernelExecutor.submit(() -> dispatcherService.dispatch()).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void withdrawByLocation(ClientID clientId, TCSResourceReference<Location> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERAL_JOBS);
try {
kernelExecutor.submit(() -> dispatcherService.withdrawByLocation(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void withdrawByPeripheralJob(ClientID clientId, TCSObjectReference<PeripheralJob> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERAL_JOBS);
try {
kernelExecutor.submit(() -> dispatcherService.withdrawByPeripheralJob(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,169 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemotePeripheralJobService;
import org.opentcs.access.to.peripherals.PeripheralJobCreationTO;
import org.opentcs.components.kernel.services.PeripheralJobService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.peripherals.PeripheralJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemotePeripheralJobService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_PERIPHERAL_JOB_SERVICE}.
* </p>
*/
public class StandardRemotePeripheralJobService
extends
StandardRemoteTCSObjectService
implements
RemotePeripheralJobService {
/**
* This class's logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(StandardRemotePeripheralJobService.class);
/**
* The peripheral job service to invoke methods on.
*/
private final PeripheralJobService peripheralJobService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param peripheralJobService The peripheral job service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemotePeripheralJobService(
PeripheralJobService peripheralJobService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
super(peripheralJobService, userManager, kernelExecutor);
this.peripheralJobService = requireNonNull(peripheralJobService, "transportOrderService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remotePeripheralJobServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_PERIPHERAL_JOB_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_PERIPHERAL_JOB_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public PeripheralJob createPeripheralJob(ClientID clientId, PeripheralJobCreationTO to)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERAL_JOBS);
try {
return kernelExecutor.submit(() -> peripheralJobService.createPeripheralJob(to)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,235 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemotePeripheralService;
import org.opentcs.components.kernel.services.PeripheralService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.drivers.peripherals.PeripheralAdapterCommand;
import org.opentcs.drivers.peripherals.PeripheralCommAdapterDescription;
import org.opentcs.drivers.peripherals.PeripheralProcessModel;
import org.opentcs.drivers.peripherals.management.PeripheralAttachmentInformation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemotePeripheralService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_PERIPHERAL_SERVICE}.
* </p>
*/
public class StandardRemotePeripheralService
extends
StandardRemoteTCSObjectService
implements
RemotePeripheralService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemotePeripheralService.class);
/**
* The peripheral service to invoke methods on.
*/
private final PeripheralService peripheralService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param peripheralService The peripheral service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemotePeripheralService(
PeripheralService peripheralService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
super(peripheralService, userManager, kernelExecutor);
this.peripheralService = requireNonNull(peripheralService, "peripheralService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remotePeripheralServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_PERIPHERAL_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_PERIPHERAL_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public void attachCommAdapter(
ClientID clientId,
TCSResourceReference<Location> ref,
PeripheralCommAdapterDescription description
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERALS);
try {
kernelExecutor.submit(() -> peripheralService.attachCommAdapter(ref, description)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void disableCommAdapter(ClientID clientId, TCSResourceReference<Location> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERALS);
try {
kernelExecutor.submit(() -> peripheralService.disableCommAdapter(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void enableCommAdapter(ClientID clientId, TCSResourceReference<Location> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERALS);
try {
kernelExecutor.submit(() -> peripheralService.enableCommAdapter(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public PeripheralAttachmentInformation fetchAttachmentInformation(
ClientID clientId,
TCSResourceReference<Location> ref
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return peripheralService.fetchAttachmentInformation(ref);
}
@Override
public PeripheralProcessModel fetchProcessModel(
ClientID clientId,
TCSResourceReference<Location> ref
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return peripheralService.fetchProcessModel(ref);
}
@Override
public void sendCommAdapterCommand(
ClientID clientId,
TCSResourceReference<Location> ref,
PeripheralAdapterCommand command
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_PERIPHERALS);
try {
kernelExecutor.submit(() -> peripheralService.sendCommAdapterCommand(ref, command)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,227 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemotePlantModelService;
import org.opentcs.access.to.model.PlantModelCreationTO;
import org.opentcs.components.kernel.services.PlantModelService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Location;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.PlantModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemotePlantModelService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_PLANT_MODEL_SERVICE}.
* </p>
*/
public class StandardRemotePlantModelService
extends
StandardRemoteTCSObjectService
implements
RemotePlantModelService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemotePlantModelService.class);
/**
* The plant model service to invoke methods on.
*/
private final PlantModelService plantModelService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param plantModelService The plant model service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemotePlantModelService(
PlantModelService plantModelService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
super(plantModelService, userManager, kernelExecutor);
this.plantModelService = requireNonNull(plantModelService, "plantModelService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remotePlantModelServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_PLANT_MODEL_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_PLANT_MODEL_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public PlantModel getPlantModel(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
return kernelExecutor.submit(() -> plantModelService.getPlantModel()).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void createPlantModel(ClientID clientId, PlantModelCreationTO to) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> plantModelService.createPlantModel(to)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public String getModelName(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return plantModelService.getModelName();
}
@Override
public Map<String, String> getModelProperties(ClientID clientId) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return plantModelService.getModelProperties();
}
@Override
public void updateLocationLock(
ClientID clientId,
TCSObjectReference<Location> ref,
boolean locked
)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> plantModelService.updateLocationLock(ref, locked)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updatePathLock(ClientID clientId, TCSObjectReference<Path> ref, boolean locked)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> plantModelService.updatePathLock(ref, locked)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,167 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteQueryService;
import org.opentcs.components.kernel.Query;
import org.opentcs.components.kernel.services.QueryService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteQueryService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_QUERY_SERVICE}.
* </p>
*/
public class StandardRemoteQueryService
extends
KernelRemoteService
implements
RemoteQueryService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemoteQueryService.class);
/**
* The query service to invoke methods on.
*/
private final QueryService queryService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param queryService The query service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteQueryService(
QueryService queryService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.queryService = requireNonNull(queryService, "queryService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteQueryServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_QUERY_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_QUERY_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public <T> T query(ClientID clientId, Query<T> query) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
try {
return kernelExecutor.submit(() -> queryService.query(query))
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,199 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteRouterService;
import org.opentcs.components.kernel.services.RouterService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Path;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.TCSResourceReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.Route;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteRouterService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_ROUTER_SERVICE}.
* </p>
*/
public class StandardRemoteRouterService
extends
KernelRemoteService
implements
RemoteRouterService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemoteRouterService.class);
/**
* The scheduler service to invoke methods on.
*/
private final RouterService routerService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param routerService The router service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteRouterService(
RouterService routerService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.routerService = requireNonNull(routerService, "routerService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteRouterServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_ROUTER_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_ROUTER_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public void updateRoutingTopology(ClientID clientId, Set<TCSObjectReference<Path>> refs)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> routerService.updateRoutingTopology(refs)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public Map<TCSObjectReference<Point>, Route> computeRoutes(
ClientID clientId,
TCSObjectReference<Vehicle> vehicleRef,
TCSObjectReference<Point> sourcePointRef,
Set<TCSObjectReference<Point>> destinationPointRefs,
Set<TCSResourceReference<?>> resourcesToAvoid
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
return kernelExecutor.submit(
() -> routerService.computeRoutes(
vehicleRef,
sourcePointRef,
destinationPointRefs,
resourcesToAvoid
)
)
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.annotation.Nullable;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.function.Predicate;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.services.RemoteTCSObjectService;
import org.opentcs.components.kernel.services.TCSObjectService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.ObjectHistory;
import org.opentcs.data.TCSObject;
import org.opentcs.data.TCSObjectReference;
/**
* This class is the standard implementation of the {@link RemoteTCSObjectService} interface.
*/
public abstract class StandardRemoteTCSObjectService
extends
KernelRemoteService
implements
RemoteTCSObjectService {
/**
* The object service to invoke methods on.
*/
private final TCSObjectService objectService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* Creates a new instance.
*
* @param objectService The object service.
* @param userManager The user manager.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
public StandardRemoteTCSObjectService(
TCSObjectService objectService,
UserManager userManager,
@KernelExecutor
ExecutorService kernelExecutor
) {
this.objectService = requireNonNull(objectService, "objectService");
this.userManager = requireNonNull(userManager, "userManager");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public <T extends TCSObject<T>> T fetchObject(
ClientID clientId, Class<T> clazz,
TCSObjectReference<T> ref
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return objectService.fetchObject(clazz, ref);
}
@Override
public <T extends TCSObject<T>> T fetchObject(ClientID clientId, Class<T> clazz, String name) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return objectService.fetchObject(clazz, name);
}
@Override
public <T extends TCSObject<T>> Set<T> fetchObjects(ClientID clientId, Class<T> clazz) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return objectService.fetchObjects(clazz);
}
@Override
public <T extends TCSObject<T>> Set<T> fetchObjects(
ClientID clientId,
Class<T> clazz,
Predicate<? super T> predicate
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return objectService.fetchObjects(clazz, predicate);
}
@Override
public void updateObjectProperty(
ClientID clientId,
TCSObjectReference<?> ref,
String key,
@Nullable
String value
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> objectService.updateObjectProperty(ref, key, value)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void appendObjectHistoryEntry(
ClientID clientId,
TCSObjectReference<?> ref,
ObjectHistory.Entry entry
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_MODEL);
try {
kernelExecutor.submit(() -> objectService.appendObjectHistoryEntry(ref, entry)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,217 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteTransportOrderService;
import org.opentcs.access.to.order.OrderSequenceCreationTO;
import org.opentcs.access.to.order.TransportOrderCreationTO;
import org.opentcs.components.kernel.services.TransportOrderService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.order.OrderSequence;
import org.opentcs.data.order.TransportOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteTransportOrderService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_TRANSPORT_ORDER_SERVICE}.
* </p>
*/
public class StandardRemoteTransportOrderService
extends
StandardRemoteTCSObjectService
implements
RemoteTransportOrderService {
/**
* This class's logger.
*/
private static final Logger LOG
= LoggerFactory.getLogger(StandardRemoteTransportOrderService.class);
/**
* The transport order service to invoke methods on.
*/
private final TransportOrderService transportOrderService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param transportOrderService The transport order service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteTransportOrderService(
TransportOrderService transportOrderService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
super(transportOrderService, userManager, kernelExecutor);
this.transportOrderService = requireNonNull(transportOrderService, "transportOrderService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteTransportOrderServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_TRANSPORT_ORDER_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_TRANSPORT_ORDER_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public OrderSequence createOrderSequence(ClientID clientId, OrderSequenceCreationTO to) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
return kernelExecutor.submit(() -> transportOrderService.createOrderSequence(to)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public TransportOrder createTransportOrder(ClientID clientId, TransportOrderCreationTO to) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
return kernelExecutor.submit(() -> transportOrderService.createTransportOrder(to)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void markOrderSequenceComplete(ClientID clientId, TCSObjectReference<OrderSequence> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(() -> transportOrderService.markOrderSequenceComplete(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateTransportOrderIntendedVehicle(
ClientID clientId,
TCSObjectReference<TransportOrder> orderRef,
TCSObjectReference<Vehicle> vehicleRef
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_ORDER);
try {
kernelExecutor.submit(
() -> transportOrderService.updateTransportOrderIntendedVehicle(
orderRef,
vehicleRef
)
).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,347 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import jakarta.inject.Inject;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.access.rmi.factories.SocketFactoryProvider;
import org.opentcs.access.rmi.services.RegistrationName;
import org.opentcs.access.rmi.services.RemoteVehicleService;
import org.opentcs.components.kernel.services.VehicleService;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.data.TCSObjectReference;
import org.opentcs.data.model.Vehicle;
import org.opentcs.data.model.Vehicle.EnergyLevelThresholdSet;
import org.opentcs.drivers.vehicle.AdapterCommand;
import org.opentcs.drivers.vehicle.VehicleCommAdapterDescription;
import org.opentcs.drivers.vehicle.management.VehicleAttachmentInformation;
import org.opentcs.drivers.vehicle.management.VehicleProcessModelTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is the standard implementation of the {@link RemoteVehicleService} interface.
* <p>
* Upon creation, an instance of this class registers itself with the RMI registry by the name
* {@link RegistrationName#REMOTE_VEHICLE_SERVICE}.
* </p>
*/
public class StandardRemoteVehicleService
extends
StandardRemoteTCSObjectService
implements
RemoteVehicleService {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(StandardRemoteVehicleService.class);
/**
* The vehicle service to invoke methods on.
*/
private final VehicleService vehicleService;
/**
* The user manager.
*/
private final UserManager userManager;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides socket factories used for RMI.
*/
private final SocketFactoryProvider socketFactoryProvider;
/**
* Provides the registry with which this remote service registers.
*/
private final RegistryProvider registryProvider;
/**
* Executes tasks modifying kernel data.
*/
private final ExecutorService kernelExecutor;
/**
* The registry with which this remote service registers.
*/
private Registry rmiRegistry;
/**
* Whether this remote service is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param vehicleService The vehicle service.
* @param userManager The user manager.
* @param configuration This class' configuration.
* @param socketFactoryProvider The socket factory provider used for RMI.
* @param registryProvider The provider for the registry with which this remote service registers.
* @param kernelExecutor Executes tasks modifying kernel data.
*/
@Inject
public StandardRemoteVehicleService(
VehicleService vehicleService,
UserManager userManager,
RmiKernelInterfaceConfiguration configuration,
SocketFactoryProvider socketFactoryProvider,
RegistryProvider registryProvider,
@KernelExecutor
ExecutorService kernelExecutor
) {
super(vehicleService, userManager, kernelExecutor);
this.vehicleService = requireNonNull(vehicleService, "vehicleService");
this.userManager = requireNonNull(userManager, "userManager");
this.configuration = requireNonNull(configuration, "configuration");
this.socketFactoryProvider = requireNonNull(socketFactoryProvider, "socketFactoryProvider");
this.registryProvider = requireNonNull(registryProvider, "registryProvider");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
}
@Override
public void initialize() {
if (isInitialized()) {
return;
}
rmiRegistry = registryProvider.get();
// Export this instance via RMI.
try {
LOG.debug("Exporting proxy...");
UnicastRemoteObject.exportObject(
this,
configuration.remoteVehicleServicePort(),
socketFactoryProvider.getClientSocketFactory(),
socketFactoryProvider.getServerSocketFactory()
);
LOG.debug("Binding instance with RMI registry...");
rmiRegistry.rebind(RegistrationName.REMOTE_VEHICLE_SERVICE, this);
}
catch (RemoteException exc) {
LOG.error("Could not export or bind with RMI registry", exc);
return;
}
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
return;
}
try {
LOG.debug("Unbinding from RMI registry...");
rmiRegistry.unbind(RegistrationName.REMOTE_VEHICLE_SERVICE);
LOG.debug("Unexporting RMI interface...");
UnicastRemoteObject.unexportObject(this, true);
}
catch (RemoteException | NotBoundException exc) {
LOG.warn("Exception shutting down RMI interface", exc);
}
initialized = false;
}
@Override
public void attachCommAdapter(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
VehicleCommAdapterDescription description
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(() -> vehicleService.attachCommAdapter(ref, description)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void disableCommAdapter(ClientID clientId, TCSObjectReference<Vehicle> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(() -> vehicleService.disableCommAdapter(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void enableCommAdapter(ClientID clientId, TCSObjectReference<Vehicle> ref) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(() -> vehicleService.enableCommAdapter(ref)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public VehicleAttachmentInformation fetchAttachmentInformation(
ClientID clientId,
TCSObjectReference<Vehicle> ref
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return vehicleService.fetchAttachmentInformation(ref);
}
@Override
public VehicleProcessModelTO fetchProcessModel(
ClientID clientId,
TCSObjectReference<Vehicle> ref
) {
userManager.verifyCredentials(clientId, UserPermission.READ_DATA);
return vehicleService.fetchProcessModel(ref);
}
@Override
public void sendCommAdapterCommand(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
AdapterCommand command
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(() -> vehicleService.sendCommAdapterCommand(ref, command)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void sendCommAdapterMessage(
ClientID clientId,
TCSObjectReference<Vehicle> vehicleRef,
Object message
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(() -> vehicleService.sendCommAdapterMessage(vehicleRef, message)).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateVehicleIntegrationLevel(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
Vehicle.IntegrationLevel integrationLevel
)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(
() -> vehicleService.updateVehicleIntegrationLevel(ref, integrationLevel)
).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateVehiclePaused(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
boolean paused
)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(
() -> vehicleService.updateVehiclePaused(ref, paused)
).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateVehicleEnergyLevelThresholdSet(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
EnergyLevelThresholdSet energyLevelThresholdSet
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(
() -> vehicleService.updateVehicleEnergyLevelThresholdSet(ref, energyLevelThresholdSet)
)
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateVehicleAllowedOrderTypes(
ClientID clientId, TCSObjectReference<Vehicle> ref,
Set<String> allowedOrderTypes
) {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(
() -> vehicleService.updateVehicleAllowedOrderTypes(ref, allowedOrderTypes)
)
.get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
@Override
public void updateVehicleEnvelopeKey(
ClientID clientId,
TCSObjectReference<Vehicle> ref,
String envelopeKey
)
throws RemoteException {
userManager.verifyCredentials(clientId, UserPermission.MODIFY_VEHICLES);
try {
kernelExecutor.submit(
() -> vehicleService.updateVehicleEnvelopeKey(ref, envelopeKey)
).get();
}
catch (InterruptedException | ExecutionException exc) {
throw findSuitableExceptionFor(exc);
}
}
}

View File

@@ -0,0 +1,88 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import java.io.Serializable;
import java.util.Set;
/**
* Instances of this class store user account data, including name, password
* and granted permissions of the user.
*/
public class UserAccount
implements
Serializable {
/**
* The user's name.
*/
private final String userName;
/**
* The user's password.
*/
private String password;
/**
* The user's permissions.
*/
private Set<UserPermission> permissions;
/**
* Creates a new instance of UserAccount.
*
* @param userName The user's name.
* @param password The user's password.
* @param perms The user's permissions.
*/
public UserAccount(String userName, String password, Set<UserPermission> perms) {
this.userName = requireNonNull(userName, "userName");
this.password = requireNonNull(password, "password");
this.permissions = requireNonNull(perms, "perms");
}
/**
* Return the user's name.
*
* @return The user's name.
*/
public String getUserName() {
return userName;
}
/**
* Return the user's password.
*
* @return The user's password.
*/
public String getPassword() {
return password;
}
/**
* Set the user's password.
*
* @param pass The user's password.
*/
public void setPassword(String pass) {
password = pass;
}
/**
* Returns the user's permissions.
*
* @return The user's permissions.
*/
public Set<UserPermission> getPermissions() {
return permissions;
}
/**
* Set the user's permissions.
*
* @param permissions The user's new permissions.
*/
public void setPermissions(Set<UserPermission> permissions) {
this.permissions = requireNonNull(permissions, "permissions");
}
}

View File

@@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import java.util.Set;
/**
* Provides user account data.
*/
public interface UserAccountProvider {
Set<UserAccount> getUserAccounts();
}

View File

@@ -0,0 +1,434 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static java.util.Objects.requireNonNull;
import static org.opentcs.util.Assertions.checkArgument;
import static org.opentcs.util.Assertions.checkInRange;
import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;
import jakarta.inject.Inject;
import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.opentcs.access.CredentialsException;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.components.Lifecycle;
import org.opentcs.customizations.ApplicationEventBus;
import org.opentcs.customizations.ApplicationHome;
import org.opentcs.customizations.kernel.KernelExecutor;
import org.opentcs.util.event.EventHandler;
import org.opentcs.util.event.EventSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Manages users allowed to connect/operate with the kernel and authenticated clients.
*/
public class UserManager
implements
EventHandler,
Lifecycle {
/**
* This class's logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(UserManager.class);
/**
* Where we register for application events.
*/
private final EventSource eventSource;
/**
* The kernel's executor.
*/
private final ScheduledExecutorService kernelExecutor;
/**
* Provides configuration data.
*/
private final RmiKernelInterfaceConfiguration configuration;
/**
* Provides user account data.
*/
private final UserAccountProvider userAccountProvider;
/**
* The directory of users allowed to connect/operate with the kernel.
*/
private final Map<String, UserAccount> knownUsers = new HashMap<>();
/**
* The directory of authenticated clients (a mapping of ClientIDs to user names).
*/
private final Map<ClientID, ClientEntry> knownClients = new HashMap<>();
/**
* A handle for the task that periodically cleans up known clients and event buffers.
*/
private ScheduledFuture<?> cleanerTaskFuture;
/**
* Whether this kernel extension is initialized or not.
*/
private boolean initialized;
/**
* Creates a new instance.
*
* @param homeDirectory The kernel's home directory (for saving user account data). Will be
* created if it doesn't exist, yet.
* @param eventSource Where this instance registers for application events.
* @param kernelExecutor The kernel's executor.
* @param configuration This class' configuration.
* @param userAccountProvider Provides user account data.
*/
@Inject
public UserManager(
@ApplicationHome
File homeDirectory,
@ApplicationEventBus
EventSource eventSource,
@KernelExecutor
ScheduledExecutorService kernelExecutor,
RmiKernelInterfaceConfiguration configuration,
UserAccountProvider userAccountProvider
) {
requireNonNull(homeDirectory, "homeDirectory");
this.eventSource = requireNonNull(eventSource, "eventSource");
this.kernelExecutor = requireNonNull(kernelExecutor, "kernelExecutor");
this.configuration = requireNonNull(configuration, "configuration");
this.userAccountProvider = requireNonNull(userAccountProvider, "userAccountProvider");
}
@Override
public void initialize() {
if (isInitialized()) {
LOG.debug("Already initialized.");
return;
}
// Register the user manager as an event listener so that the user manager can collect events
// and pass them to known clients polling events.
eventSource.subscribe(this);
knownUsers.clear();
for (UserAccount curAccount : userAccountProvider.getUserAccounts()) {
knownUsers.put(curAccount.getUserName(), curAccount);
}
// Start the thread that periodically cleans up the list of known clients and event buffers.
LOG.debug("Starting cleaner task...");
cleanerTaskFuture = kernelExecutor.scheduleWithFixedDelay(
new ClientCleanerTask(),
configuration.clientSweepInterval(),
configuration.clientSweepInterval(),
TimeUnit.MILLISECONDS
);
initialized = true;
}
@Override
public boolean isInitialized() {
return initialized;
}
@Override
public void terminate() {
if (!isInitialized()) {
LOG.debug("Not initialized.");
return;
}
LOG.debug("Terminating cleaner task...");
cleanerTaskFuture.cancel(false);
cleanerTaskFuture = null;
knownUsers.clear();
eventSource.unsubscribe(this);
initialized = false;
}
@Override
public void onEvent(Object event) {
// Forward the event to all clients' event buffers.
synchronized (knownClients) {
for (ClientEntry curEntry : knownClients.values()) {
curEntry.getEventBuffer().onEvent(event);
}
}
}
/**
* Returns the directory of users allowed to connect/operate with the kernel.
*
* @return The directory of users allowed to connect/operate with the kernel.
*/
public Map<String, UserAccount> getKnownUsers() {
return Collections.unmodifiableMap(knownUsers);
}
/**
* Returns the directory of authenticated clients (a mapping of ClientIDs to user names).
*
* @return The directory of authenticated clients (a mapping of ClientIDs to user names).
*/
public Map<ClientID, ClientEntry> getKnownClients() {
return Collections.unmodifiableMap(knownClients);
}
/**
* Returns the {@link UserAccount} for the given user name.
*
* @param userName The user name to get the user account for.
* @return The user account or {@code null}, if there isn't an account associated to the given
* user name.
*/
@Nullable
public UserAccount getUser(String userName) {
return knownUsers.get(userName);
}
/**
* Returns the {@link ClientEntry} for the given client id.
*
* @param clientID The client id to get the client entry for.
* @return The client entry or {@code null}, if there isn't an entry associated to the given
* client id.
*/
@Nullable
public ClientEntry getClient(ClientID clientID) {
return knownClients.get(clientID);
}
/**
* Adds a new ClientEntry to the map of all known and authenticated clients.
*
* @param clientID The client id to identify the given ClientEntry.
* @param clientEntry The ClientEntry object to be registered.
*/
public void registerClient(
@Nonnull
ClientID clientID,
@Nonnull
ClientEntry clientEntry
) {
requireNonNull(clientID, "clientID");
requireNonNull(clientEntry, "clientEntry");
synchronized (knownClients) {
if (isClientRegistered(clientID)) {
return;
}
knownClients.put(clientID, clientEntry);
}
}
/**
* Removes the given client from the map of known clients.
*
* @param clientID The client id to be removed.
*/
public void unregisterClient(
@Nonnull
ClientID clientID
) {
requireNonNull(clientID, "clientID");
synchronized (knownClients) {
knownClients.remove(clientID);
}
}
public List<Object> pollEvents(ClientID clientID, long timeout) {
requireNonNull(clientID, "clientID");
checkInRange(timeout, 0, Long.MAX_VALUE, "timeout");
ClientEntry clientEntry;
EventBuffer eventBuffer;
synchronized (knownClients) {
clientEntry = getClient(clientID);
checkArgument(clientEntry != null, "Unknown client ID: %s", clientID);
eventBuffer = clientEntry.getEventBuffer();
}
// Get events or wait for one to arrive if none is currently there.
List<Object> events = eventBuffer.getEvents(timeout);
// Set the client's 'alive' flag.
synchronized (knownClients) {
clientEntry.setAlive(true);
}
return events;
}
/**
* Check whether the user described by the given credentials is granted permissions according to
* the specified user role.
* <p>
* This method also sets the 'alive' flag of the client's entry to prevent it from being removed
* by the cleaner thread.
* </p>
*
* @param clientID The client's identification object.
* @param requiredPermission The required role/permission.
* @return <code>true</code> if, and only if, the given client ID exists and the client has the
* given permission.
*/
private boolean checkCredentialsForRole(ClientID clientID, UserPermission requiredPermission) {
requireNonNull(clientID, "clientID");
requireNonNull(requiredPermission, "requiredPermission");
synchronized (knownClients) {
ClientEntry clientEntry = getClient(clientID);
// Check if the client is known.
if (clientEntry == null) {
return false;
}
// Set the 'alive' flag for the cleaning thread.
clientEntry.setAlive(true);
// Check if the user's permissions are sufficient.
Set<UserPermission> providedPerms = clientEntry.getPermissions();
if (!providedPerms.contains(requiredPermission)) {
return false;
}
}
return true;
}
/**
* Ensures the given client has the required permissions.
*
* @param clientID The client's identification object.
* @param requiredPermission The required role/permission.
* @throws CredentialsException If the client's permissions are insufficient.
*/
public void verifyCredentials(ClientID clientID, UserPermission requiredPermission)
throws CredentialsException {
requireNonNull(clientID, "clientID");
requireNonNull(requiredPermission, "requiredPermission");
if (!checkCredentialsForRole(clientID, requiredPermission)) {
throw new CredentialsException("Client permissions insufficient.");
}
}
private boolean isClientRegistered(
@Nonnull
ClientID clientID
) {
return knownClients.containsKey(clientID);
}
/**
* Instances of this class are used as containers for data kept about known clients.
*/
public static final class ClientEntry {
/**
* The name of the user that connected with the client.
*/
private final String userName;
/**
* The client's permissions/privilege level.
*/
private final Set<UserPermission> permissions;
/**
* The client's event buffer.
*/
private final EventBuffer eventBuffer = new EventBuffer(event -> false);
/**
* The client's alive flag.
*/
private boolean alive = true;
/**
* Creates a new ClientEntry.
*
* @param name The client's name.
* @param perms The client's permissions.
*/
public ClientEntry(String name, Set<UserPermission> perms) {
userName = requireNonNull(name, "name");
permissions = requireNonNull(perms, "perms");
}
/**
* Checks whether the client has been seen since the last sweep of the cleaner task.
*
* @return <code>true</code> if, and only if, the client has been seen recently.
*/
public boolean isAlive() {
return alive;
}
/**
* Sets this client's <em>alive</em> flag.
*
* @param isAlive The client's new <em>alive</em> flag.
*/
public void setAlive(boolean isAlive) {
alive = isAlive;
}
public String getUserName() {
return userName;
}
public EventBuffer getEventBuffer() {
return eventBuffer;
}
public Set<UserPermission> getPermissions() {
return permissions;
}
}
/**
* A task for cleaning out stale client entries.
*/
private class ClientCleanerTask
implements
Runnable {
/**
* Creates a new instance.
*/
private ClientCleanerTask() {
}
@Override
public void run() {
LOG.debug("Sweeping client entries...");
synchronized (knownClients) {
Iterator<Map.Entry<ClientID, ClientEntry>> clientIter = knownClients.entrySet().iterator();
while (clientIter.hasNext()) {
Map.Entry<ClientID, ClientEntry> curEntry = clientIter.next();
ClientEntry clientEntry = curEntry.getValue();
// Only touch the entry if the buffer not currently in use by a
// client.
if (!clientEntry.getEventBuffer().hasWaitingClient()) {
// If the client has been seen since the last run, reset the
// 'alive' flag.
if (clientEntry.isAlive()) {
clientEntry.setAlive(false);
}
// If the client hasn't been seen since the last run, remove its
// ID from the list of known clients - the client has been
// inactive for long enough.
else {
LOG.debug(
"Removing inactive client entry (client user: {})",
clientEntry.getUserName()
);
clientIter.remove();
}
}
}
}
}
}
}

View File

@@ -0,0 +1,59 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
/**
* Defines the possible permission flags of kernel clients.
*/
public enum UserPermission {
/**
* Indicates the client may retrieve any data from the kernel.
*/
READ_DATA,
/**
* Indicates the client may change the kernel's state.
*/
CHANGE_KERNEL_STATE,
/**
* Indicates the client may change the kernel's configuration items.
*/
CHANGE_CONFIGURATION,
/**
* Indicates the client may load another model.
*/
LOAD_MODEL,
/**
* Indicates the client may save the current model (under any name).
*/
SAVE_MODEL,
/**
* Indicates the client may modify any data of the current model.
*/
MODIFY_MODEL,
/**
* Indicates the client may add or remove temporary path locks.
*/
LOCK_PATH,
/**
* Indicates the client may move/place vehicles and modify their states
* explicitly.
*/
MODIFY_VEHICLES,
/**
* Indicates the client may create/modify transport orders.
*/
MODIFY_ORDER,
/**
* Indicates the client may modify peripheral states.
*/
MODIFY_PERIPHERALS,
/**
* Indicates the client may create/modify peripheral jobs.
*/
MODIFY_PERIPHERAL_JOBS,
/**
* Indicates the client may publish messages via the kernel.
*/
PUBLISH_MESSAGES
}

View File

@@ -0,0 +1,175 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.theInstance;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentcs.data.TCSObjectEvent;
import org.opentcs.data.model.BoundingBox;
import org.opentcs.data.model.Point;
import org.opentcs.data.model.Vehicle;
/**
* Unit tests for {@link EventBuffer}.
*/
class EventBufferTest {
private EventBuffer eventBuffer;
@BeforeEach
void setUp() {
eventBuffer = new EventBuffer(event -> true);
}
@Test
void checkGetEventsShouldReturnCorrectAmountOfEvents() {
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
assertThat(eventBuffer.getEvents(0), hasSize(3));
}
@Test
void checkGetEventsShouldReturnEmptyList() {
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
assertThat(eventBuffer.getEvents(0), hasSize(3));
assertThat(eventBuffer.getEvents(0), is(empty()));
}
@Test
void checkSetEventFilterShouldChangeEventFilter() {
eventBuffer.setEventFilter(i -> false);
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
eventBuffer.onEvent(new Object());
assertThat(eventBuffer.getEvents(0), is(empty()));
}
@Test
void checkGetEventsShouldWorkWhenTimeoutGreaterThanZero() {
eventBuffer.onEvent(new Object());
assertThat(eventBuffer.getEvents(1000), hasSize(1));
assertFalse(eventBuffer.hasWaitingClient());
}
@Test
void aggregateConsecutiveTcsObjectEventsForSameObjects() {
Point point = new Point("point");
Point pointA = point.withType(Point.Type.PARK_POSITION);
Point pointB = pointA.withProperty("some-key", "some-value");
Point pointC = pointB.withProperty("some-other-key", "some-other-value");
TCSObjectEvent event1 = new TCSObjectEvent(
pointA,
point,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
TCSObjectEvent event5 = new TCSObjectEvent(
pointB,
pointA,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
TCSObjectEvent event6 = new TCSObjectEvent(
pointC,
pointB,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
Vehicle vehicle = new Vehicle("vehicle");
Vehicle vehicleA = vehicle.withEnergyLevel(42);
Vehicle vehicleB = vehicleA.withIntegrationLevel(Vehicle.IntegrationLevel.TO_BE_UTILIZED);
Vehicle vehicleC = vehicleB.withBoundingBox(new BoundingBox(1382, 1000, 1000));
TCSObjectEvent event2 = new TCSObjectEvent(
vehicleA,
vehicle,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
TCSObjectEvent event3 = new TCSObjectEvent(
vehicleB,
vehicleA,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
TCSObjectEvent event4 = new TCSObjectEvent(
vehicleC,
vehicleB,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
eventBuffer.onEvent(event1);
eventBuffer.onEvent(event2);
eventBuffer.onEvent(event3);
eventBuffer.onEvent(event4);
eventBuffer.onEvent(event5);
eventBuffer.onEvent(event6);
List<Object> result = eventBuffer.getEvents(0);
assertThat(result, hasSize(3));
assertThat(result.get(0), is(theInstance(event1)));
assertThat(
((TCSObjectEvent) result.get(1)).getPreviousObjectState(),
is(theInstance(vehicle))
);
assertThat(
((TCSObjectEvent) result.get(1)).getCurrentObjectState(),
is(theInstance(vehicleC))
);
assertThat(
((TCSObjectEvent) result.get(2)).getPreviousObjectState(),
is(theInstance(pointA))
);
assertThat(
((TCSObjectEvent) result.get(2)).getCurrentObjectState(),
is(theInstance(pointC))
);
}
@Test
void dontAggregateEventsOfTypeCreateOrRemoved() {
Vehicle vehicle = new Vehicle("vehicle");
Vehicle vehicleA = vehicle.withEnergyLevel(42);
TCSObjectEvent event1 = new TCSObjectEvent(
vehicle,
null,
TCSObjectEvent.Type.OBJECT_CREATED
);
TCSObjectEvent event2 = new TCSObjectEvent(
vehicleA,
vehicle,
TCSObjectEvent.Type.OBJECT_MODIFIED
);
TCSObjectEvent event3 = new TCSObjectEvent(
null,
vehicleA,
TCSObjectEvent.Type.OBJECT_REMOVED
);
eventBuffer.onEvent(event1);
eventBuffer.onEvent(event2);
eventBuffer.onEvent(event3);
List<Object> result = eventBuffer.getEvents(0);
assertThat(result, hasSize(3));
assertThat(result.get(0), is(equalTo(event1)));
assertThat(result.get(1), is(equalTo(event2)));
assertThat(result.get(2), is(equalTo(event3)));
}
}

View File

@@ -0,0 +1,183 @@
// SPDX-FileCopyrightText: The openTCS Authors
// SPDX-License-Identifier: MIT
package org.opentcs.kernel.extensions.rmi;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.anEmptyMap;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.mock;
import java.io.File;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.opentcs.access.CredentialsException;
import org.opentcs.access.rmi.ClientID;
import org.opentcs.util.event.EventSource;
/**
* Unit tests for {@link UserManager}.
*/
class UserManagerTest {
private File homedirectory;
private EventSource eventSource;
private ScheduledExecutorService kernelExecutor;
private RmiKernelInterfaceConfiguration configuration;
private UserAccountProvider userAccountProvider;
private UserAccount account1;
private UserManager.ClientEntry client1;
private ClientID id1;
private UserManager manager;
@BeforeEach
void setUp() {
homedirectory = mock();
eventSource = mock();
kernelExecutor = Executors.newSingleThreadScheduledExecutor();
configuration = mock();
userAccountProvider = mock();
Set<UserPermission> permissions = EnumSet.of(UserPermission.READ_DATA);
account1 = new UserAccount("peter", "123", permissions);
Set<UserAccount> userAccounts = Set.of(account1);
client1 = new UserManager.ClientEntry("auto", permissions);
id1 = new ClientID("auto");
given(userAccountProvider.getUserAccounts())
.willReturn(userAccounts);
given(configuration.clientSweepInterval())
.willReturn(1000L);
manager = new UserManager(
homedirectory,
eventSource,
kernelExecutor,
configuration,
userAccountProvider
);
manager.initialize();
}
@AfterEach
void tearDown() {
kernelExecutor.shutdown();
}
@Test
void testIfUserManagerIsInitialized() {
assertThat(manager.isInitialized(), is(true));
}
@Test
void testIfCleanerTaskIsTerminated() {
manager.terminate();
assertThat(manager.isInitialized(), is(false));
then(eventSource).should().unsubscribe(manager);
}
@Test
void checkGetKnownUsers() {
assertThat(manager.getKnownUsers(), is(aMapWithSize(1)));
assertThat(manager.getKnownUsers(), hasEntry("peter", account1));
}
@Test
void checkGetUserShouldReturnRightUser() {
assertThat(manager.getUser("peter"), is(account1));
}
@Test
void checkGetUserShouldReturnNullForUnknownUser() {
assertNull(manager.getUser("marie"));
}
@Test
void checkGetClientShouldReturnRightClient() {
manager.registerClient(id1, client1);
assertThat(manager.getClient(id1), is(client1));
}
@Test
void checkGetClientShouldReturnNull() {
manager.registerClient(id1, client1);
assertNull(manager.getClient(new ClientID("jet")));
}
@Test
void checkIfPollEventsReturnsTheCorrectEventList() {
manager.registerClient(id1, client1);
client1.getEventBuffer().setEventFilter(event -> true);
Object event1 = new Object();
manager.onEvent(event1);
List<Object> eventList = manager.pollEvents(id1, 0);
assertThat(eventList, hasSize(1));
assertThat(eventList, contains(event1));
}
@Test
void checkVerifyCredentialsShouldThrowExceptionIfClientHasNoPermission() {
manager.registerClient(id1, client1);
assertThrows(
CredentialsException.class,
() -> manager.verifyCredentials(id1, UserPermission.SAVE_MODEL)
);
}
@Test
void checkVerifyCredentialsShouldThrowExceptionIfClientDoesNotExist() {
assertThrows(
CredentialsException.class,
() -> manager.verifyCredentials(
new ClientID("unknown-client"),
UserPermission.SAVE_MODEL
)
);
}
@Test
void checkVerifyCredentialsShouldThrowNoException() {
manager.registerClient(id1, client1);
assertDoesNotThrow(
() -> manager.verifyCredentials(id1, UserPermission.READ_DATA)
);
}
@Test
void checkRegisterClient() {
manager.registerClient(id1, client1);
assertThat(manager.getKnownClients(), is(aMapWithSize(1)));
assertThat(manager.getKnownClients(), hasEntry(id1, client1));
}
@Test
void checkUnregisterClient() {
manager.registerClient(id1, client1);
manager.unregisterClient(id1);
assertThat(manager.getKnownClients(), is(anEmptyMap()));
}
}