Merge remote-tracking branch 'origin/devel' into CB-2571-license-access-determine-license-role-for-user-session

This commit is contained in:
Aleksandr Skoblikov
2022-10-17 18:38:52 +02:00
27 changed files with 748 additions and 8 deletions
@@ -20,6 +20,7 @@ Export-Package: io.cloudbeaver,
io.cloudbeaver.auth.provider,
io.cloudbeaver.auth.provider.fa,
io.cloudbeaver.auth.provider.local,
io.cloudbeaver.events,
io.cloudbeaver.model,
io.cloudbeaver.model.app,
io.cloudbeaver.model.rm,
@@ -3,4 +3,5 @@
<plugin>
<extension-point id="io.cloudbeaver.service" name="Web services" schema="schema/io.cloudbeaver.service.exsd"/>
<extension-point id="io.cloudbeaver.event.handler" name="Wev event handler" schema="schema/io.cloudbeaver.event.handler.exsd"/>
</plugin>
@@ -0,0 +1,11 @@
<?xml version='1.0' encoding='UTF-8'?>
<schema targetNamespace="org.jkiss.dbeaver.core" xmlns="http://www.w3.org/2001/XMLSchema">
<annotation>
<appInfo>
<meta.schema plugin="io.cloudbeaver.event.handler" id="io.cloudbeaver.event.handler" name="Web events handler"/>
</appInfo>
<documentation>
Web events handler
</documentation>
</annotation>
</schema>
@@ -0,0 +1,50 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events;
import org.jkiss.code.NotNull;
import java.util.Map;
/**
* CloudBeaver event
*/
public class CBEvent {
@NotNull
private final String eventType;
@NotNull
private final Map<String, Object> eventData;
public CBEvent(@NotNull String eventType) {
this(eventType, Map.of());
}
public CBEvent(@NotNull String eventType, @NotNull Map<String, Object> eventData) {
this.eventType = eventType;
this.eventData = eventData;
}
@NotNull
public String getEventType() {
return eventType;
}
@NotNull
public Map<String, Object> getEventData() {
return eventData;
}
}
@@ -0,0 +1,24 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events;
/**
* CB event constants
*/
public interface CBEventConstants {
String CLOUDBEAVER_CONFIG_CHANGED = "cb_config_changed";
}
@@ -0,0 +1,86 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events;
import io.cloudbeaver.events.registry.CBEventHandlersRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.runtime.AbstractJob;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class CBEventController {
private final Map<String, List<CBEventHandler>> eventHandlersByType = new HashMap<>();
public CBEventController() {
var eventHandlers = CBEventHandlersRegistry.getInstance().getEventHandlers();
eventHandlers
.forEach(handler -> eventHandlersByType.computeIfAbsent(handler.getSupportedEventType(), x -> new ArrayList<>()).add(handler));
}
private final List<CBEvent> eventsPool = new ArrayList<>();
/**
* Add cb event to the event pool
*/
public void addEvent(@NotNull CBEvent event) {
synchronized (eventsPool) {
eventsPool.add(event);
}
}
/**
* Add cb event to the event pool
*/
public void scheduleCheckJob() {
new CBEventCheckJob().schedule();
}
private class CBEventCheckJob extends AbstractJob {
private static final long CHECK_PERIOD = 1000;
protected CBEventCheckJob() {
super("CloudBeaver events job");
}
@Override
protected IStatus run(DBRProgressMonitor monitor) {
List<CBEvent> events;
synchronized (eventsPool) {
events = List.copyOf(eventsPool);
eventsPool.clear();
}
if (events.isEmpty()) {
schedule(CHECK_PERIOD);
return Status.OK_STATUS;
}
for (CBEvent event : events) {
eventHandlersByType.getOrDefault(event.getEventType(), List.of())
.forEach(handler -> handler.handleEvent(event));
}
schedule(CHECK_PERIOD);
return Status.OK_STATUS;
}
}
}
@@ -0,0 +1,35 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events;
import org.jkiss.code.NotNull;
/**
* CB web event handlers
*/
public interface CBEventHandler {
/**
* Expected cb event type
*/
@NotNull
String getSupportedEventType();
/**
* Handle associated cb event
*/
void handleEvent(@NotNull CBEvent event);
}
@@ -0,0 +1,44 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events.registry;
import io.cloudbeaver.events.CBEventHandler;
import org.eclipse.core.runtime.IConfigurationElement;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.impl.AbstractDescriptor;
/**
* CB event handler descriptor
*/
public class CBEventHandlerDescriptor extends AbstractDescriptor {
private final ObjectType implType;
protected CBEventHandlerDescriptor(IConfigurationElement contributorConfig) {
super(contributorConfig);
this.implType = new ObjectType(contributorConfig, "class");
}
@NotNull
public CBEventHandler getInstance() {
try {
return implType.createInstance(CBEventHandler.class);
} catch (DBException e) {
throw new IllegalStateException("Can not instantiate event handler '" + implType.getImplName() + "'", e);
}
}
}
@@ -0,0 +1,61 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.events.registry;
import io.cloudbeaver.events.CBEventHandler;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.jkiss.code.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class CBEventHandlersRegistry {
private static final String EXTENSION_ID = "io.cloudbeaver.event.handler";
private static final String EVENT_HANDLER_TAG = "eventHandler";
private static CBEventHandlersRegistry instance = null;
public synchronized static CBEventHandlersRegistry getInstance() {
if (instance == null) {
instance = new CBEventHandlersRegistry();
}
return instance;
}
@NotNull
public List<CBEventHandler> getEventHandlers() {
List<CBEventHandlerDescriptor> eventHandlerDescriptors = readDescriptors();
return eventHandlerDescriptors.stream()
.map(CBEventHandlerDescriptor::getInstance)
.collect(Collectors.toList());
}
@NotNull
private List<CBEventHandlerDescriptor> readDescriptors() {
var result = new ArrayList<CBEventHandlerDescriptor>();
var registry = Platform.getExtensionRegistry();
for (IConfigurationElement ext : registry.getConfigurationElementsFor(EXTENSION_ID)) {
// Load webServices
if (EVENT_HANDLER_TAG.equals(ext.getName())) {
result.add(new CBEventHandlerDescriptor(ext));
}
}
return result;
}
}
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.DataSourceFilter;
import io.cloudbeaver.VirtualProjectImpl;
import io.cloudbeaver.events.CBEvent;
import io.cloudbeaver.model.WebAsyncTaskInfo;
import io.cloudbeaver.model.WebConnectionInfo;
import io.cloudbeaver.model.WebServerMessage;
@@ -124,6 +125,7 @@ public class WebSession extends AbstractSessionPersistent
private final WebApplication application;
private final Map<String, DBWSessionHandler> sessionHandlers;
private final WebUserContext userContext;
private final List<CBEvent> sessionEvents = new ArrayList<>();
public WebSession(HttpSession httpSession,
WebApplication application,
@@ -723,6 +725,27 @@ public class WebSession extends AbstractSessionPersistent
addSessionMessage(new WebServerMessage(WebServerMessage.MessageType.INFO, message));
}
public void addSessionEvent(CBEvent event) {
synchronized (sessionEvents) {
sessionEvents.add(event);
}
}
public List<CBEvent> getSessionEvents(int eventsCount) {
synchronized (sessionEvents) {
List<CBEvent> result;
if (sessionEvents.size() <= eventsCount) {
result = List.copyOf(sessionEvents);
sessionEvents.clear();
} else {
var subList = sessionEvents.subList(0, eventsCount - 1);
result = List.copyOf(subList);
subList.clear();
}
return result;
}
}
public List<WebServerMessage> readLog(Integer maxEntries, Boolean clearLog) {
synchronized (sessionMessages) {
List<WebServerMessage> messages = new ArrayList<>();
@@ -15,6 +15,7 @@
</factory>
</extension>
<!-- Needed to instantiate connections thru navigator model -->
<extension point="org.jkiss.dbeaver.service">
<service name="org.jkiss.dbeaver.runtime.DBServiceConnections" class="io.cloudbeaver.server.WebServiceConnectionsImpl"/>
@@ -22,13 +23,16 @@
<extension point="io.cloudbeaver.service">
<service id="core" label="Core service" description="Core services" class="io.cloudbeaver.service.core.WebServiceBindingCore">
<permission id="public" label="Public access" category="general" scope="subject" description="Provides access to the data management interface"/>
<permission id="public" label="Public access" category="general" scope="subject"
description="Provides access to the data management interface"/>
<permission id="access" label="Data source access" category="general" scope="datasource"/>
</service>
<service id="navigator" label="Database navigator" description="Database navigator services" class="io.cloudbeaver.service.navigator.WebServiceBindingNavigator">
<service id="navigator" label="Database navigator" description="Database navigator services"
class="io.cloudbeaver.service.navigator.WebServiceBindingNavigator">
<premission id="navigate" label="Navigate over database metadata"/>
</service>
<service id="sql" label="SQL processing" description="SQL processing and data read services" class="io.cloudbeaver.service.sql.WebServiceBindingSQL">
<service id="sql" label="SQL processing" description="SQL processing and data read services"
class="io.cloudbeaver.service.sql.WebServiceBindingSQL">
<premission id="data.read" label="Data read" scope="data"/>
<premission id="data.update" label="Data update" scope="data"/>
<premission id="data.insert" label="Data insert" scope="data"/>
@@ -44,4 +48,8 @@
<serializer type="geometry" class="io.cloudbeaver.server.data.WebGeometryValueSerializer"/>
</extension>
<extension point="io.cloudbeaver.event.handler">
<eventHandler class="io.cloudbeaver.server.events.CBConfigurationChangedEventHandler"/>
</extension>
</plugin>
@@ -386,6 +386,15 @@ type LogEntry {
stackTrace: String
}
enum CBEventType {
cb_config_changed
}
type CBEvent {
eventType: CBEventType!
eventData: Object!
}
####################################################
# Input types
####################################################
@@ -503,6 +512,8 @@ extend type Query {
listProjects: [ ProjectInfo! ]!
readSessionLog(maxEntries: Int, clearEntries: Boolean): [ LogEntry! ]!
readSessionEvents(maxEntries: Int!): [ CBEvent! ]!
}
extend type Mutation {
@@ -21,6 +21,9 @@ import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.auth.NoAuthCredentialsProvider;
import io.cloudbeaver.events.CBEvent;
import io.cloudbeaver.events.CBEventConstants;
import io.cloudbeaver.events.CBEventController;
import io.cloudbeaver.model.app.BaseWebApplication;
import io.cloudbeaver.model.app.WebAuthApplication;
import io.cloudbeaver.model.app.WebAuthConfiguration;
@@ -121,6 +124,8 @@ public class CBApplication extends BaseWebApplication implements WebAuthApplicat
private String localHostAddress;
private final List<InetAddress> localInetAddresses = new ArrayList<>();
protected final CBEventController eventController = new CBEventController();
private WebSessionManager sessionManager;
public CBApplication() {
@@ -342,6 +347,8 @@ public class CBApplication extends BaseWebApplication implements WebAuthApplicat
System.setSecurityManager(new SecurityManager());
}
eventController.scheduleCheckJob();
runWebServer();
log.debug("Shutdown");
@@ -440,7 +447,7 @@ public class CBApplication extends BaseWebApplication implements WebAuthApplicat
@NotNull
private File getRuntimeAppConfigFile() {
return getDataDirectory(true).resolve(CBConstants.RUNTIME_APP_CONFIG_FILE_NAME).toFile();
return getDataDirectory(true).resolve(CBConstants.RUNTIME_APP_CONFIG_FILE_NAME).toFile();
}
@NotNull
@@ -789,6 +796,7 @@ public class CBApplication extends BaseWebApplication implements WebAuthApplicat
}
configurationMode = CommonUtils.isEmpty(serverName);
eventController.addEvent(new CBEvent(CBEventConstants.CLOUDBEAVER_CONFIG_CHANGED));
}
protected Map<String, Object> readRuntimeConfigurationProperties() throws DBException {
@@ -0,0 +1,51 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.server.events;
import io.cloudbeaver.events.CBEvent;
import io.cloudbeaver.events.CBEventConstants;
import io.cloudbeaver.events.CBEventHandler;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.server.CBPlatform;
import io.cloudbeaver.service.session.WebSessionManager;
import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.Log;
import java.util.Collection;
/**
* Notify all active user session that configuration has been changed
*/
public class CBConfigurationChangedEventHandler implements CBEventHandler {
private static final Log log = Log.getLog(CBConfigurationChangedEventHandler.class);
@NotNull
@Override
public String getSupportedEventType() {
return CBEventConstants.CLOUDBEAVER_CONFIG_CHANGED;
}
@Override
public void handleEvent(@NotNull CBEvent event) {
log.debug(getSupportedEventType() + " event handled");
Collection<WebSession> allSessions = CBPlatform.getInstance().getSessionManager().getAllActiveSessions();
for (WebSession activeUserSession : allSessions) {
activeUserSession.addSessionEvent(event);
}
}
}
@@ -20,6 +20,7 @@ import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebAction;
import io.cloudbeaver.WebObjectId;
import io.cloudbeaver.WebProjectAction;
import io.cloudbeaver.events.CBEvent;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.DBWService;
@@ -84,6 +85,9 @@ public interface DBWServiceCore extends DBWService {
@WebAction
List<WebServerMessage> readSessionLog(@NotNull WebSession webSession, Integer maxEntries, Boolean clearEntries) throws DBWebException;
@WebAction(requirePermissions = {})
List<CBEvent> readSessionEvents(@Nullable WebSession webSession, Integer maxEntries) throws DBWebException;
@WebAction(requirePermissions = {})
boolean closeSession(HttpServletRequest request) throws DBWebException;
@@ -73,7 +73,9 @@ public class WebServiceBindingCore extends WebServiceBindingBase<DBWServiceCore>
getWebSession(env), getProjectReference(env), env.getArgument("id")))
.dataFetcher("listProjects", env -> getService(env).getProjects(getWebSession(env)))
.dataFetcher("readSessionEvents", env -> getService(env).readSessionEvents(
findWebSession(env), env.getArgument("maxEntries")
))
.dataFetcher("readSessionLog", env -> {
// CB-90. Log read mustn't extend session lifetime and mustn't fail if there is no session.
WebSession session = findWebSession(env);
@@ -20,6 +20,7 @@ package io.cloudbeaver.service.core.impl;
import io.cloudbeaver.DBWConstants;
import io.cloudbeaver.DBWebException;
import io.cloudbeaver.WebServiceUtils;
import io.cloudbeaver.events.CBEvent;
import io.cloudbeaver.model.*;
import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.registry.WebHandlerRegistry;
@@ -51,6 +52,7 @@ import org.jkiss.dbeaver.model.net.DBWTunnel;
import org.jkiss.dbeaver.model.net.ssh.SSHImplementation;
import org.jkiss.dbeaver.model.rm.RMProjectType;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.registry.BaseProjectImpl;
import org.jkiss.dbeaver.registry.DataSourceDescriptor;
import org.jkiss.dbeaver.registry.DataSourceProviderRegistry;
import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor;
@@ -177,12 +179,25 @@ public class WebServiceCore implements DBWServiceCore {
public List<WebConnectionFolderInfo> getConnectionFolders(
@NotNull WebSession webSession, @Nullable String projectId, @Nullable String id
) throws DBWebException {
if (projectId == null) {
return webSession.getAccessibleProjects().stream()
.flatMap(pr -> getConnectionFoldersFromProject(webSession, pr).stream())
.collect(Collectors.toList());
}
if (id != null) {
WebConnectionFolderInfo folderInfo = WebConnectionFolderUtils.getFolderInfo(webSession, projectId, id);
return Collections.singletonList(folderInfo);
}
return webSession.getProjectById(projectId).getDataSourceRegistry().getAllFolders().stream()
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
DBPProject project = webSession.getProjectById(projectId);
return getConnectionFoldersFromProject(webSession, project);
}
private List<WebConnectionFolderInfo> getConnectionFoldersFromProject(
@NotNull WebSession webSession,
@NotNull DBPProject project
) {
return project.getDataSourceRegistry().getAllFolders().stream()
.map(f -> new WebConnectionFolderInfo(webSession, f)).collect(Collectors.toList());
}
@Override
@@ -225,6 +240,14 @@ public class WebServiceCore implements DBWServiceCore {
return webSession.readLog(maxEntries, clearEntries);
}
@Override
public List<CBEvent> readSessionEvents(@Nullable WebSession webSession, Integer maxEntries) throws DBWebException {
if (webSession == null) {
return Collections.emptyList();
}
return webSession.getSessionEvents(maxEntries);
}
@Override
public boolean closeSession(HttpServletRequest request) throws DBWebException {
try {
@@ -188,4 +188,9 @@ public class WebSessionManager {
}
}
public Collection<WebSession> getAllActiveSessions() {
synchronized (sessionMap) {
return sessionMap.values();
}
}
}
@@ -0,0 +1,32 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import {
CbEvent,
ResourceEventHandler
} from '@cloudbeaver/core-sdk';
import { SessionEvent, SessionEventSource, SessionEventType } from './SessionEventSource';
@injectable()
export class ServerConfigEventHandler extends ResourceEventHandler<null, SessionEvent> {
constructor(
sessionEventSource: SessionEventSource
) {
super(sessionEventSource);
}
map(event: CbEvent): null {
return null;
}
filter(event: CbEvent): boolean {
return event.eventType === SessionEventType.CbConfigChanged;
}
}
@@ -13,6 +13,7 @@ import { GraphQLService, CachedDataResource, ServerConfig, ServerConfigInput, Na
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { isNavigatorViewSettingsEqual } from './ConnectionNavigatorViewSettings';
import { ServerConfigEventHandler } from './ServerConfigEventHandler';
@injectable()
export class ServerConfigResource extends CachedDataResource<ServerConfig | null> {
@@ -20,7 +21,8 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
navigatorSettingsUpdate: NavigatorSettingsInput;
constructor(
private readonly graphQLService: GraphQLService
private readonly graphQLService: GraphQLService,
serverConfigEventHandler: ServerConfigEventHandler,
) {
super(null);
@@ -41,6 +43,8 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
unlinkUpdate: action,
syncUpdateData: action,
});
serverConfigEventHandler.on(this, () => undefined);
}
get redirectOnFederatedAuth(): boolean {
@@ -0,0 +1,36 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import {
GraphQLService,
EventSource,
CbEvent,
CbEventType as SessionEventType
} from '@cloudbeaver/core-sdk';
export type SessionEvent = CbEvent;
export { SessionEventType };
@injectable()
export class SessionEventSource extends EventSource<SessionEvent> {
constructor(
private readonly graphQLService: GraphQLService
) {
super(5000);
}
protected async listener(): Promise<void> {
const { events } = await this.graphQLService.sdk.getSessionEvents({
maxEntries: 1000,
});
for (const event of events) {
this.event(event);
}
}
}
@@ -12,11 +12,13 @@ import { FeaturesResource } from './FeaturesResource';
import { NetworkStateService } from './NetworkStateService';
import { PermissionsService } from './PermissionsService';
import { QuotasService } from './QuotasService';
import { ServerConfigEventHandler } from './ServerConfigEventHandler';
import { ServerConfigResource } from './ServerConfigResource';
import { ServerService } from './ServerService';
import { ServerSettingsService } from './ServerSettingsService';
import { SessionActionService } from './SessionActionService';
import { SessionDataResource } from './SessionDataResource';
import { SessionEventSource } from './SessionEventSource';
import { SessionExpireService } from './SessionExpireService';
import { SessionPermissionsResource } from './SessionPermissionsResource';
import { SessionResource } from './SessionResource';
@@ -46,5 +48,7 @@ export const manifest: PluginManifest = {
SessionSettingsService,
WindowEventsService,
QuotasService,
ServerConfigEventHandler,
SessionEventSource,
],
};
@@ -0,0 +1,107 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import type { CachedResource } from './CachedResource';
export type EventSourceCallback<T> = (event: T) => any;
export interface IEventSource<TEvent> {
on<T>(
resource: EventSourceCallback<T>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void;
}
const DEFAULT_INTERVAL = 1000;
export abstract class EventSource<TEvent> implements IEventSource<TEvent> {
onEvent: ISyncExecutor<TEvent>;
private listening: boolean;
private readonly interval: number;
constructor(interval?: number) {
this.onEvent = new SyncExecutor();
this.interval = interval ?? DEFAULT_INTERVAL;
this.listening = false;
}
on<T>(
resource: EventSourceCallback<T>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void;
on<T>(
resource: CachedResource<any, T, any, any>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void;
on<T>(
resource: EventSourceCallback<T> | CachedResource<any, T, any, any>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void {
if (typeof resource === 'function') {
this.onEvent.addHandler(event => {
if (!filter || filter(event)) {
resource(mapTo(event));
}
});
} else {
this.onEvent.addHandler(event => {
if (!filter || filter(event)) {
resource.markOutdated(mapTo(event));
}
});
}
if (!this.listening) {
this.listen();
}
}
listen() {
if (this.listening) {
console.warn('Already listening events');
return;
}
this.listening = true;
let failedRequests = 0;
let interval = this.interval;
const iteration = async () => {
try {
await this.listener();
failedRequests = 0;
} catch (exception: any) {
console.error(exception);
failedRequests++;
}
interval = this.interval * Math.min((failedRequests || 1), 10);
if (this.listening) {
setTimeout(iteration, interval);
}
};
iteration();
}
stop() {
this.listening = false;
}
event(event: TEvent) {
this.onEvent.execute(event);
}
protected abstract listener(): Promise<void>;
}
@@ -0,0 +1,64 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2022 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor';
import type { CachedResource } from './CachedResource';
import type { IEventSource, EventSourceCallback } from './EventSource';
export abstract class ResourceEventHandler<TEvent, SourceEvent = void> implements IEventSource<TEvent> {
onEvent: ISyncExecutor<TEvent>;
private subscribed: boolean;
constructor(private readonly source?: IEventSource<SourceEvent>) {
this.onEvent = new SyncExecutor();
this.subscribed = false;
}
on<T>(
resource: EventSourceCallback<T>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void;
on<T>(
resource: CachedResource<any, T, any, any>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void;
on<T>(
resource: CachedResource<any, T, any, any> | EventSourceCallback<T>,
mapTo: (param: TEvent) => T,
filter?: (param: TEvent) => boolean,
): void {
if (typeof resource === 'function') {
this.onEvent.addHandler(event => {
if (!filter || filter(event)) {
resource(mapTo(event));
}
});
} else {
this.onEvent.addHandler(event => {
if (!filter || filter(event)) {
resource.markOutdated(mapTo(event));
}
});
}
if (!this.subscribed) {
this.subscribed = true;
this.source?.on(this.event.bind(this), this.map.bind(this), this.filter.bind(this));
}
}
event(event: TEvent) {
this.onEvent.execute(event);
}
abstract map(event: SourceEvent): TEvent;
abstract filter(event: SourceEvent): boolean;
}
+2
View File
@@ -5,6 +5,8 @@ export * from './Resource/CachedMapResource';
export * from './Resource/CachedResource';
export * from './Resource/CachedResourceIncludes';
export * from './Resource/ResourceKeyList';
export * from './Resource/EventSource';
export * from './Resource/ResourceEventHandler';
export * from './CustomGraphQLClient';
export * from './DetailsError';
export * from './EnvironmentService';
@@ -0,0 +1,10 @@
query getSessionEvents(
$maxEntries: Int!
) {
events: readSessionEvents(
maxEntries: $maxEntries
) {
eventType
eventData
}
}
+33
View File
@@ -163,6 +163,15 @@ export enum AuthStatus {
Success = 'SUCCESS'
}
export interface CbEvent {
eventData: Scalars['Object'];
eventType: CbEventType;
}
export enum CbEventType {
CbConfigChanged = 'cb_config_changed'
}
export interface ConnectionConfig {
authModelId?: InputMaybe<Scalars['ID']>;
configurationType?: InputMaybe<DriverConfigurationType>;
@@ -909,6 +918,7 @@ export interface Query {
navNodeParents: Array<NavigatorNodeInfo>;
navRefreshNode?: Maybe<Scalars['Boolean']>;
networkHandlers: Array<NetworkHandlerDescriptor>;
readSessionEvents: Array<CbEvent>;
readSessionLog: Array<LogEntry>;
revokeUserTeam?: Maybe<Scalars['Boolean']>;
rmListProjectGrantedPermissions: Array<AdminObjectGrantInfo>;
@@ -1151,6 +1161,11 @@ export interface QueryNavRefreshNodeArgs {
}
export interface QueryReadSessionEventsArgs {
maxEntries: Scalars['Int'];
}
export interface QueryReadSessionLogArgs {
clearEntries?: InputMaybe<Scalars['Boolean']>;
maxEntries?: InputMaybe<Scalars['Int']>;
@@ -2639,6 +2654,13 @@ export type ChangeSessionLanguageMutationVariables = Exact<{
export type ChangeSessionLanguageMutation = { changeSessionLanguage?: boolean };
export type GetSessionEventsQueryVariables = Exact<{
maxEntries: Scalars['Int'];
}>;
export type GetSessionEventsQuery = { events: Array<{ eventType: CbEventType, eventData: any }> };
export type OpenSessionMutationVariables = Exact<{
defaultLocale?: InputMaybe<Scalars['String']>;
}>;
@@ -4215,6 +4237,14 @@ export const ChangeSessionLanguageDocument = `
changeSessionLanguage(locale: $locale)
}
`;
export const GetSessionEventsDocument = `
query getSessionEvents($maxEntries: Int!) {
events: readSessionEvents(maxEntries: $maxEntries) {
eventType
eventData
}
}
`;
export const OpenSessionDocument = `
mutation openSession($defaultLocale: String) {
session: openSession(defaultLocale: $defaultLocale) {
@@ -4735,6 +4765,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper =
changeSessionLanguage(variables: ChangeSessionLanguageMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<ChangeSessionLanguageMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<ChangeSessionLanguageMutation>(ChangeSessionLanguageDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'changeSessionLanguage', 'mutation');
},
getSessionEvents(variables: GetSessionEventsQueryVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<GetSessionEventsQuery> {
return withWrapper((wrappedRequestHeaders) => client.request<GetSessionEventsQuery>(GetSessionEventsDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'getSessionEvents', 'query');
},
openSession(variables?: OpenSessionMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<OpenSessionMutation> {
return withWrapper((wrappedRequestHeaders) => client.request<OpenSessionMutation>(OpenSessionDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'openSession', 'mutation');
},