Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions server/src/main/java/com/arcadedb/server/ArcadeDBServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,38 @@ private void registerPlugins(final ServerPlugin.INSTALLATION_PRIORITY installati
}
}
}

// Auto-register backup scheduler plugin if backup.json exists and not already registered
if (installationPriority == ServerPlugin.INSTALLATION_PRIORITY.AFTER_DATABASES_OPEN
&& !plugins.containsKey("auto-backup")) {
registerAutoBackupPluginIfConfigured();
}
}

private void registerAutoBackupPluginIfConfigured() {
final File backupConfigFile = java.nio.file.Paths.get(serverRootPath, "config", "backup.json").toFile();
if (backupConfigFile.exists()) {
try {
final Class<ServerPlugin> c = (Class<ServerPlugin>) Class.forName(
"com.arcadedb.server.backup.AutoBackupSchedulerPlugin");
final ServerPlugin pluginInstance = c.getConstructor().newInstance();

pluginInstance.configure(this, configuration);
pluginInstance.startService();

plugins.put("auto-backup", pluginInstance);

LogManager.instance().log(this, Level.INFO, "- auto-backup plugin started (auto-detected config/backup.json)");

} catch (final ClassNotFoundException e) {
// Plugin class not available, skip silently
LogManager.instance().log(this, Level.FINE,
"Auto-backup plugin class not found, skipping auto-registration");
} catch (final Exception e) {
LogManager.instance().log(this, Level.WARNING,
"Error auto-registering backup plugin", e);
}
}
}

public synchronized void stop() {
Expand Down
176 changes: 176 additions & 0 deletions server/src/main/java/com/arcadedb/server/backup/AutoBackupConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
/*
* Copyright © 2021-present Arcade Data Ltd ([email protected])
*
* 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.
*
* SPDX-FileCopyrightText: 2021-present Arcade Data Ltd ([email protected])
* SPDX-License-Identifier: Apache-2.0
*/
package com.arcadedb.server.backup;

import com.arcadedb.serializer.json.JSONObject;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
* Server-level auto-backup configuration loaded from config/backup.json.
*
* @author Luca Garulli ([email protected])
*/
public class AutoBackupConfig {
public static final String CONFIG_FILE_NAME = "backup.json";
public static final int CURRENT_VERSION = 1;
public static final String DEFAULT_BACKUP_DIR = "./backups";
public static final String DEFAULT_RUN_SERVER = "$leader";
public static final int DEFAULT_FREQUENCY = 60;
public static final int DEFAULT_MAX_FILES = 10;

private int version = CURRENT_VERSION;
private boolean enabled = true;
private String backupDirectory = DEFAULT_BACKUP_DIR;
private DatabaseBackupConfig defaults;
private final Map<String, DatabaseBackupConfig> databases = new HashMap<>();

public static AutoBackupConfig fromJSON(final JSONObject json) {
final AutoBackupConfig config = new AutoBackupConfig();

if (json.has("version"))
config.version = json.getInt("version");

if (json.has("enabled"))
config.enabled = json.getBoolean("enabled");

if (json.has("backupDirectory"))
config.backupDirectory = json.getString("backupDirectory");

if (json.has("defaults"))
config.defaults = DatabaseBackupConfig.fromJSON("_defaults", json.getJSONObject("defaults"));

if (json.has("databases")) {
final JSONObject dbs = json.getJSONObject("databases");
for (final String dbName : dbs.keySet()) {
final DatabaseBackupConfig dbConfig = DatabaseBackupConfig.fromJSON(dbName, dbs.getJSONObject(dbName));
config.databases.put(dbName, dbConfig);
}
}

return config;
}

public static AutoBackupConfig createDefault() {
final AutoBackupConfig config = new AutoBackupConfig();
config.defaults = createDefaultDatabaseConfig();
return config;
}

private static DatabaseBackupConfig createDefaultDatabaseConfig() {
final DatabaseBackupConfig dbConfig = new DatabaseBackupConfig("_defaults");
dbConfig.setEnabled(true);
dbConfig.setRunOnServer(DEFAULT_RUN_SERVER);

final DatabaseBackupConfig.ScheduleConfig schedule = new DatabaseBackupConfig.ScheduleConfig();
schedule.setType(DatabaseBackupConfig.ScheduleConfig.Type.FREQUENCY);
schedule.setFrequencyMinutes(DEFAULT_FREQUENCY);
dbConfig.setSchedule(schedule);

final DatabaseBackupConfig.RetentionConfig retention = new DatabaseBackupConfig.RetentionConfig();
retention.setMaxFiles(DEFAULT_MAX_FILES);
dbConfig.setRetention(retention);

return dbConfig;
}

/**
* Gets the effective configuration for a specific database, merging database-specific
* settings with server-level defaults.
*/
public DatabaseBackupConfig getEffectiveConfig(final String databaseName) {
DatabaseBackupConfig dbConfig = databases.get(databaseName);

if (dbConfig == null) {
// No database-specific config, use defaults
dbConfig = new DatabaseBackupConfig(databaseName);
if (defaults != null) {
dbConfig.setEnabled(defaults.isEnabled());
dbConfig.setRunOnServer(defaults.getRunOnServer());
dbConfig.setSchedule(defaults.getSchedule());
dbConfig.setRetention(defaults.getRetention());
}
} else {
// Merge database-specific config with defaults
dbConfig.mergeWithDefaults(defaults);
}

return dbConfig;
Comment on lines +100 to +116
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The getEffectiveConfig method modifies the DatabaseBackupConfig instance from the databases map when merging with defaults. This side effect can be unexpected and makes the configuration mutable after being loaded. To promote immutability and prevent unintended side effects, consider creating a defensive copy of the DatabaseBackupConfig object before merging. This would require adding a copy mechanism (e.g., a copy constructor or a copy() method) to DatabaseBackupConfig and its nested configuration classes.

}

public int getVersion() {
return version;
}

public boolean isEnabled() {
return enabled;
}

public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}

public String getBackupDirectory() {
return backupDirectory;
}

public void setBackupDirectory(final String backupDirectory) {
this.backupDirectory = backupDirectory;
}

public DatabaseBackupConfig getDefaults() {
return defaults;
}

public void setDefaults(final DatabaseBackupConfig defaults) {
this.defaults = defaults;
}

public Map<String, DatabaseBackupConfig> getDatabases() {
return Collections.unmodifiableMap(databases);
}

public void addDatabaseConfig(final String databaseName, final DatabaseBackupConfig config) {
databases.put(databaseName, config);
}

/**
* Converts this configuration to a JSON object.
*/
public JSONObject toJSON() {
final JSONObject json = new JSONObject();
json.put("version", version);
json.put("enabled", enabled);
json.put("backupDirectory", backupDirectory);

if (defaults != null)
json.put("defaults", defaults.toJSON());

if (!databases.isEmpty()) {
final JSONObject dbs = new JSONObject();
for (final Map.Entry<String, DatabaseBackupConfig> entry : databases.entrySet())
dbs.put(entry.getKey(), entry.getValue().toJSON());
json.put("databases", dbs);
}

return json;
}
}
Loading
Loading