Data API
Access and manipulate share data programmatically with advanced storage options
Data API
MC 1.21.1MC 1.21.2MC 1.21.4MC 1.21.5MC 1.21.6
The Showcase Data API provides comprehensive access to share data, player statistics, and storage systems. It enables developers to build custom data providers, implement advanced analytics, and create sophisticated integrations.
Data Provider Interface
Custom Storage Implementation
public class DatabaseDataProvider implements DataProvider {
private final DataSource dataSource;
public DatabaseDataProvider(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void saveShare(ShareData share) {
try (Connection conn = dataSource.getConnection()) {
// Save main share data
saveShareMetadata(conn, share);
// Save type-specific content
switch (share.getType()) {
case ITEM:
saveItemData(conn, share.getId(), share.getItem());
break;
case INVENTORY:
saveInventoryData(conn, share.getId(), share.getItems());
break;
case STATS:
saveStatsData(conn, share.getId(), share.getStats());
break;
case CONTAINER:
saveContainerData(conn, share.getId(), share.getContainer());
break;
}
// Save receivers
saveReceivers(conn, share.getId(), share.getReceivers());
} catch (SQLException e) {
throw new DataStorageException("Failed to save share: " + share.getId(), e);
}
}
@Override
public Optional<ShareData> loadShare(String shareId) {
try (Connection conn = dataSource.getConnection()) {
return loadShareFromDatabase(conn, shareId);
} catch (SQLException e) {
throw new DataStorageException("Failed to load share: " + shareId, e);
}
}
@Override
public List<ShareData> getPlayerShares(UUID playerId) {
try (Connection conn = dataSource.getConnection()) {
return loadPlayerShares(conn, playerId);
} catch (SQLException e) {
throw new DataStorageException("Failed to load player shares", e);
}
}
@Override
public void cleanup() {
try (Connection conn = dataSource.getConnection()) {
cleanupExpiredShares(conn);
} catch (SQLException e) {
throw new DataStorageException("Failed to cleanup shares", e);
}
}
// Implementation details...
private void saveShareMetadata(Connection conn, ShareData share) throws SQLException {
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO shares (id, creator_uuid, type, created_at, expires_at, description, view_count) " +
"VALUES (?, ?, ?, ?, ?, ?, ?) " +
"ON DUPLICATE KEY UPDATE " +
"description = VALUES(description), expires_at = VALUES(expires_at), view_count = VALUES(view_count)"
);
stmt.setString(1, share.getId());
stmt.setString(2, share.getCreator().getUuidAsString());
stmt.setString(3, share.getType().name());
stmt.setTimestamp(4, Timestamp.from(share.getCreatedAt()));
stmt.setTimestamp(5, Timestamp.from(share.getExpiresAt()));
stmt.setString(6, share.getDescription());
stmt.setInt(7, share.getViewCount());
stmt.executeUpdate();
}
}
Redis Data Provider
public class RedisDataProvider implements DataProvider {
private final RedisTemplate<String, Object> redisTemplate;
private final ObjectMapper objectMapper;
public RedisDataProvider(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
this.objectMapper = new ObjectMapper();
}
@Override
public void saveShare(ShareData share) {
try {
String key = "share:" + share.getId();
ShareDataDTO dto = ShareDataDTO.fromShareData(share);
String json = objectMapper.writeValueAsString(dto);
// Store with expiration
Duration ttl = Duration.between(Instant.now(), share.getExpiresAt());
redisTemplate.opsForValue().set(key, json, ttl);
// Add to player index
String playerKey = "player_shares:" + share.getCreator().getUuidAsString();
redisTemplate.opsForSet().add(playerKey, share.getId());
// Add to type index
String typeKey = "shares_by_type:" + share.getType().name();
redisTemplate.opsForSet().add(typeKey, share.getId());
} catch (JsonProcessingException e) {
throw new DataStorageException("Failed to serialize share data", e);
}
}
@Override
public Optional<ShareData> loadShare(String shareId) {
try {
String key = "share:" + shareId;
String json = (String) redisTemplate.opsForValue().get(key);
if (json != null) {
ShareDataDTO dto = objectMapper.readValue(json, ShareDataDTO.class);
return Optional.of(dto.toShareData());
}
return Optional.empty();
} catch (JsonProcessingException e) {
throw new DataStorageException("Failed to deserialize share data", e);
}
}
@Override
public List<ShareData> getPlayerShares(UUID playerId) {
String playerKey = "player_shares:" + playerId.toString();
Set<Object> shareIds = redisTemplate.opsForSet().members(playerKey);
return shareIds.stream()
.map(id -> loadShare((String) id))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
@Override
public void cleanup() {
// Redis automatically expires keys, but we can clean up indexes
cleanupPlayerIndexes();
cleanupTypeIndexes();
}
}
Advanced Data Operations
Bulk Operations
public class BulkDataOperations {
public void bulkSaveShares(List<ShareData> shares) {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
if (provider instanceof BulkDataProvider) {
// Use bulk operations if supported
((BulkDataProvider) provider).bulkSave(shares);
} else {
// Fallback to individual saves
shares.parallelStream().forEach(provider::saveShare);
}
}
public Map<String, ShareData> bulkLoadShares(List<String> shareIds) {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
if (provider instanceof BulkDataProvider) {
return ((BulkDataProvider) provider).bulkLoad(shareIds);
} else {
return shareIds.parallelStream()
.collect(Collectors.toMap(
id -> id,
id -> provider.loadShare(id).orElse(null)
))
.entrySet().stream()
.filter(entry -> entry.getValue() != null)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
));
}
}
}
public interface BulkDataProvider extends DataProvider {
void bulkSave(List<ShareData> shares);
Map<String, ShareData> bulkLoad(List<String> shareIds);
void bulkDelete(List<String> shareIds);
}
Data Migration
public class DataMigration {
public void migrateFromFileToDatabase() {
ShowcaseAPI api = ShowcaseAPI.getInstance();
// Get current provider (file-based)
DataProvider oldProvider = api.getDataProvider();
// Create new provider (database)
DataProvider newProvider = new DatabaseDataProvider(dataSource);
// Migrate all data
try {
migrateShares(oldProvider, newProvider);
migratePlayerStats(oldProvider, newProvider);
migrateConfiguration(oldProvider, newProvider);
// Switch to new provider
api.setDataProvider(newProvider);
System.out.println("Migration completed successfully");
} catch (Exception e) {
System.err.println("Migration failed: " + e.getMessage());
throw new RuntimeException("Data migration failed", e);
}
}
private void migrateShares(DataProvider from, DataProvider to) {
// This would need to be implemented based on the old provider's API
// For example, if migrating from JSON files:
List<ShareData> allShares = loadAllSharesFromFiles(from);
for (ShareData share : allShares) {
to.saveShare(share);
}
}
}
Data Serialization
Custom Serializers
public class ShareDataSerializer {
public static class ShareDataDTO {
public String id;
public String creatorUuid;
public String type;
public String createdAt;
public String expiresAt;
public String description;
public Set<String> receivers;
public Object content; // Type-specific content
public int viewCount;
public List<String> viewers;
public static ShareDataDTO fromShareData(ShareData share) {
ShareDataDTO dto = new ShareDataDTO();
dto.id = share.getId();
dto.creatorUuid = share.getCreator().getUuidAsString();
dto.type = share.getType().name();
dto.createdAt = share.getCreatedAt().toString();
dto.expiresAt = share.getExpiresAt().toString();
dto.description = share.getDescription();
dto.receivers = share.getReceivers().stream()
.map(UUID::toString)
.collect(Collectors.toSet());
dto.viewCount = share.getViewCount();
dto.viewers = share.getViewers().stream()
.map(UUID::toString)
.collect(Collectors.toList());
// Serialize type-specific content
dto.content = serializeContent(share);
return dto;
}
public ShareData toShareData() {
// Deserialize back to ShareData
// Implementation depends on your ShareData constructor
return ShareData.builder()
.id(id)
.creator(getPlayerByUuid(UUID.fromString(creatorUuid)))
.type(ShareType.valueOf(type))
.createdAt(Instant.parse(createdAt))
.expiresAt(Instant.parse(expiresAt))
.description(description)
.receivers(receivers.stream().map(UUID::fromString).collect(Collectors.toSet()))
.content(deserializeContent(content, ShareType.valueOf(type)))
.viewCount(viewCount)
.viewers(viewers.stream().map(UUID::fromString).collect(Collectors.toList()))
.build();
}
private static Object serializeContent(ShareData share) {
switch (share.getType()) {
case ITEM:
return ItemStackSerializer.serialize(share.getItem());
case INVENTORY:
case HOTBAR:
case ENDERCHEST:
return ItemStackSerializer.serializeArray(share.getItems());
case STATS:
return PlayerStatsSerializer.serialize(share.getStats());
case CONTAINER:
return ContainerDataSerializer.serialize(share.getContainer());
default:
return null;
}
}
}
}
NBT Data Handling
public class NBTDataHandler {
public static NbtCompound serializeShareToNBT(ShareData share) {
NbtCompound nbt = new NbtCompound();
nbt.putString("id", share.getId());
nbt.putUuid("creator", share.getCreator().getUuid());
nbt.putString("type", share.getType().name());
nbt.putLong("createdAt", share.getCreatedAt().toEpochMilli());
nbt.putLong("expiresAt", share.getExpiresAt().toEpochMilli());
if (share.getDescription() != null) {
nbt.putString("description", share.getDescription());
}
// Serialize receivers
NbtList receiversList = new NbtList();
for (UUID receiver : share.getReceivers()) {
receiversList.add(NbtHelper.fromUuid(receiver));
}
nbt.put("receivers", receiversList);
// Serialize content based on type
switch (share.getType()) {
case ITEM:
nbt.put("item", share.getItem().writeNbt(new NbtCompound()));
break;
case INVENTORY:
case HOTBAR:
case ENDERCHEST:
NbtList itemsList = new NbtList();
for (ItemStack item : share.getItems()) {
if (item != null && !item.isEmpty()) {
itemsList.add(item.writeNbt(new NbtCompound()));
}
}
nbt.put("items", itemsList);
break;
case STATS:
nbt.put("stats", PlayerStatsSerializer.toNBT(share.getStats()));
break;
case CONTAINER:
nbt.put("container", ContainerDataSerializer.toNBT(share.getContainer()));
break;
}
return nbt;
}
public static ShareData deserializeShareFromNBT(NbtCompound nbt) {
String id = nbt.getString("id");
UUID creatorUuid = nbt.getUuid("creator");
ShareType type = ShareType.valueOf(nbt.getString("type"));
Instant createdAt = Instant.ofEpochMilli(nbt.getLong("createdAt"));
Instant expiresAt = Instant.ofEpochMilli(nbt.getLong("expiresAt"));
String description = nbt.contains("description") ? nbt.getString("description") : null;
// Deserialize receivers
Set<UUID> receivers = new HashSet<>();
NbtList receiversList = nbt.getList("receivers", NbtElement.INT_ARRAY_TYPE);
for (int i = 0; i < receiversList.size(); i++) {
receivers.add(NbtHelper.toUuid(receiversList.get(i)));
}
// Get creator player
ServerPlayerEntity creator = getPlayerByUuid(creatorUuid);
// Build ShareData based on type
ShareData.Builder builder = ShareData.builder()
.id(id)
.creator(creator)
.type(type)
.createdAt(createdAt)
.expiresAt(expiresAt)
.description(description)
.receivers(receivers);
// Deserialize content
switch (type) {
case ITEM:
ItemStack item = ItemStack.fromNbt(nbt.getCompound("item"));
builder.item(item);
break;
case INVENTORY:
case HOTBAR:
case ENDERCHEST:
NbtList itemsList = nbt.getList("items", NbtElement.COMPOUND_TYPE);
ItemStack[] items = new ItemStack[itemsList.size()];
for (int i = 0; i < itemsList.size(); i++) {
items[i] = ItemStack.fromNbt(itemsList.getCompound(i));
}
builder.items(items);
break;
case STATS:
PlayerStats stats = PlayerStatsSerializer.fromNBT(nbt.getCompound("stats"));
builder.stats(stats);
break;
case CONTAINER:
ContainerData container = ContainerDataSerializer.fromNBT(nbt.getCompound("container"));
builder.container(container);
break;
}
return builder.build();
}
}
Analytics and Reporting
Data Analytics
public class ShareAnalytics {
public AnalyticsReport generateReport(Duration period) {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
Instant since = Instant.now().minus(period);
List<ShareData> shares = getSharesSince(provider, since);
return AnalyticsReport.builder()
.period(period)
.totalShares(shares.size())
.sharesByType(groupByType(shares))
.sharesByPlayer(groupByPlayer(shares))
.topSharedItems(getTopSharedItems(shares))
.averageViewsPerShare(calculateAverageViews(shares))
.mostActiveUsers(getMostActiveUsers(shares))
.peakSharingTimes(getPeakSharingTimes(shares))
.build();
}
public Map<ShareType, Integer> groupByType(List<ShareData> shares) {
return shares.stream()
.collect(Collectors.groupingBy(
ShareData::getType,
Collectors.summingInt(share -> 1)
));
}
public Map<UUID, Integer> groupByPlayer(List<ShareData> shares) {
return shares.stream()
.collect(Collectors.groupingBy(
share -> share.getCreator().getUuid(),
Collectors.summingInt(share -> 1)
));
}
public List<String> getTopSharedItems(List<ShareData> shares) {
return shares.stream()
.filter(share -> share.getType() == ShareType.ITEM)
.map(share -> share.getItem().getName().getString())
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
.entrySet().stream()
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
.limit(10)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
}
public double calculateAverageViews(List<ShareData> shares) {
return shares.stream()
.mapToInt(ShareData::getViewCount)
.average()
.orElse(0.0);
}
}
Performance Metrics
public class PerformanceMetrics {
public void trackDataOperations() {
ShowcaseAPI api = ShowcaseAPI.getInstance();
MetricsCollector metrics = api.getMetricsCollector();
// Track save operations
Timer.Sample sample = Timer.start(metrics.getRegistry());
try {
// Perform save operation
api.getDataProvider().saveShare(shareData);
sample.stop(Timer.builder("showcase.data.save")
.tag("operation", "save")
.tag("type", shareData.getType().name())
.register(metrics.getRegistry()));
} catch (Exception e) {
metrics.counter("showcase.data.errors",
"operation", "save",
"error", e.getClass().getSimpleName())
.increment();
throw e;
}
}
public void generatePerformanceReport() {
ShowcaseAPI api = ShowcaseAPI.getInstance();
MetricsCollector metrics = api.getMetricsCollector();
PerformanceReport report = PerformanceReport.builder()
.averageSaveTime(getAverageTime(metrics, "showcase.data.save"))
.averageLoadTime(getAverageTime(metrics, "showcase.data.load"))
.errorRate(getErrorRate(metrics))
.throughput(getThroughput(metrics))
.cacheHitRate(getCacheHitRate(metrics))
.build();
System.out.println("Performance Report:");
System.out.println("Average Save Time: " + report.getAverageSaveTime() + "ms");
System.out.println("Average Load Time: " + report.getAverageLoadTime() + "ms");
System.out.println("Error Rate: " + report.getErrorRate() + "%");
System.out.println("Throughput: " + report.getThroughput() + " ops/sec");
System.out.println("Cache Hit Rate: " + report.getCacheHitRate() + "%");
}
}
Data Export/Import
Export Functionality
public class DataExportImport {
public void exportAllData(Path exportPath) throws IOException {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
ExportData exportData = new ExportData();
// Export all shares
exportData.shares = getAllShares(provider);
// Export player statistics
exportData.playerStats = getAllPlayerStats(provider);
// Export configuration
exportData.configuration = api.getConfig();
// Export analytics data
exportData.analytics = generateAnalyticsData();
// Write to file
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(exportPath.toFile(), exportData);
System.out.println("Data exported to: " + exportPath);
}
public void importData(Path importPath) throws IOException {
ObjectMapper mapper = new ObjectMapper();
ExportData importData = mapper.readValue(importPath.toFile(), ExportData.class);
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
// Import shares
for (ShareData share : importData.shares) {
provider.saveShare(share);
}
// Import player statistics
for (Map.Entry<UUID, PlayerStats> entry : importData.playerStats.entrySet()) {
savePlayerStats(provider, entry.getKey(), entry.getValue());
}
System.out.println("Data imported from: " + importPath);
}
public static class ExportData {
public List<ShareData> shares;
public Map<UUID, PlayerStats> playerStats;
public ShowcaseConfig configuration;
public AnalyticsData analytics;
}
}
Backup and Restore
public class BackupRestore {
public void createBackup() {
ShowcaseAPI api = ShowcaseAPI.getInstance();
try {
String timestamp = Instant.now().toString().replaceAll(":", "-");
Path backupPath = Paths.get("backups", "showcase-backup-" + timestamp + ".json");
Files.createDirectories(backupPath.getParent());
DataExportImport exporter = new DataExportImport();
exporter.exportAllData(backupPath);
// Compress backup
compressBackup(backupPath);
System.out.println("Backup created: " + backupPath);
} catch (IOException e) {
System.err.println("Failed to create backup: " + e.getMessage());
}
}
public void restoreBackup(Path backupPath) {
try {
// Decompress if needed
if (backupPath.toString().endsWith(".gz")) {
backupPath = decompressBackup(backupPath);
}
DataExportImport importer = new DataExportImport();
importer.importData(backupPath);
// Reload API to reflect changes
ShowcaseAPI.getInstance().reload();
System.out.println("Backup restored from: " + backupPath);
} catch (IOException e) {
System.err.println("Failed to restore backup: " + e.getMessage());
}
}
public void scheduleAutomaticBackups() {
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
// Create backup every 6 hours
scheduler.scheduleAtFixedRate(this::createBackup, 0, 6, TimeUnit.HOURS);
// Clean old backups every day
scheduler.scheduleAtFixedRate(this::cleanOldBackups, 1, 24, TimeUnit.HOURS);
}
private void cleanOldBackups() {
try {
Path backupDir = Paths.get("backups");
if (!Files.exists(backupDir)) return;
Instant cutoff = Instant.now().minus(Duration.ofDays(7)); // Keep 7 days
Files.list(backupDir)
.filter(path -> path.getFileName().toString().startsWith("showcase-backup-"))
.filter(path -> {
try {
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
return attrs.creationTime().toInstant().isBefore(cutoff);
} catch (IOException e) {
return false;
}
})
.forEach(path -> {
try {
Files.delete(path);
System.out.println("Deleted old backup: " + path.getFileName());
} catch (IOException e) {
System.err.println("Failed to delete backup: " + path.getFileName());
}
});
} catch (IOException e) {
System.err.println("Failed to clean old backups: " + e.getMessage());
}
}
}
Data Validation
Integrity Checks
public class DataIntegrityChecker {
public ValidationResult validateDataIntegrity() {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
ValidationResult result = new ValidationResult();
// Check for orphaned data
result.orphanedShares = findOrphanedShares(provider);
// Check for corrupted shares
result.corruptedShares = findCorruptedShares(provider);
// Check for expired shares that weren't cleaned up
result.expiredShares = findExpiredShares(provider);
// Check data consistency
result.inconsistencies = findDataInconsistencies(provider);
return result;
}
private List<String> findOrphanedShares(DataProvider provider) {
// Find shares that reference non-existent players
return getAllShareIds(provider).stream()
.filter(id -> {
Optional<ShareData> shareOpt = provider.loadShare(id);
if (shareOpt.isPresent()) {
ShareData share = shareOpt.get();
return getPlayerByUuid(share.getCreator().getUuid()) == null;
}
return false;
})
.collect(Collectors.toList());
}
private List<String> findCorruptedShares(DataProvider provider) {
return getAllShareIds(provider).stream()
.filter(id -> {
try {
Optional<ShareData> shareOpt = provider.loadShare(id);
if (shareOpt.isPresent()) {
ShareData share = shareOpt.get();
return !validateShareData(share);
}
return true; // If it can't be loaded, it's corrupted
} catch (Exception e) {
return true; // Exception during loading indicates corruption
}
})
.collect(Collectors.toList());
}
private boolean validateShareData(ShareData share) {
// Validate share has required fields
if (share.getId() == null || share.getCreator() == null || share.getType() == null) {
return false;
}
// Validate timestamps
if (share.getCreatedAt().isAfter(share.getExpiresAt())) {
return false;
}
// Validate type-specific content
switch (share.getType()) {
case ITEM:
return share.getItem() != null && !share.getItem().isEmpty();
case INVENTORY:
case HOTBAR:
case ENDERCHEST:
return share.getItems() != null && share.getItems().length > 0;
case STATS:
return share.getStats() != null;
case CONTAINER:
return share.getContainer() != null;
default:
return true;
}
}
public void repairDataIntegrity(ValidationResult result) {
ShowcaseAPI api = ShowcaseAPI.getInstance();
DataProvider provider = api.getDataProvider();
// Remove orphaned shares
for (String shareId : result.orphanedShares) {
provider.deleteShare(shareId);
System.out.println("Removed orphaned share: " + shareId);
}
// Remove corrupted shares
for (String shareId : result.corruptedShares) {
provider.deleteShare(shareId);
System.out.println("Removed corrupted share: " + shareId);
}
// Clean up expired shares
provider.cleanup();
System.out.println("Data integrity repair completed");
}
public static class ValidationResult {
public List<String> orphanedShares = new ArrayList<>();
public List<String> corruptedShares = new ArrayList<>();
public List<String> expiredShares = new ArrayList<>();
public List<String> inconsistencies = new ArrayList<>();
public boolean hasIssues() {
return !orphanedShares.isEmpty() || !corruptedShares.isEmpty() ||
!expiredShares.isEmpty() || !inconsistencies.isEmpty();
}
}
}
Related Topics
- Java API - Core API integration
- Events - Event-driven data handling
- Configuration - Data-related settings
- API Overview - Complete developer guide