mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/devel' into CB-2571-license-access-determine-license-role-for-user-session
This commit is contained in:
@@ -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);
|
||||
}
|
||||
+44
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
@@ -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 {
|
||||
|
||||
+51
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
@@ -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;
|
||||
|
||||
|
||||
+3
-1
@@ -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);
|
||||
|
||||
+25
-2
@@ -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 {
|
||||
|
||||
+5
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user