This commit is contained in:
16
opentcs-kernel-extension-rmi-services/build.gradle
Normal file
16
opentcs-kernel-extension-rmi-services/build.gradle
Normal 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
|
||||
}
|
||||
40
opentcs-kernel-extension-rmi-services/gradle.properties
Normal file
40
opentcs-kernel-extension-rmi-services/gradle.properties
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# SPDX-FileCopyrightText: The openTCS Authors
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
org.opentcs.kernel.extensions.rmi.RmiServicesModule
|
||||
@@ -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)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
@@ -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()));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user