Skip to content

Commit aee6564

Browse files
authored
feat: Complete Google Cloud Firestore integration for AFS system (#28)
1 parent b15a994 commit aee6564

4 files changed

Lines changed: 426 additions & 2 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ The module structure exactly mirrors the Eclipse Store Java repository for famil
5151
-**`afs/blobstore`** - Complete Abstract File System blob storage backend
5252
-**`afs/aws/s3`** - Complete AWS S3 storage backend
5353
-**`afs/azure/storage`** - Complete Azure Storage backend
54-
- 🚧 **`afs/googlecloud/firestore`** - Google Cloud Firestore integration (in progress)
54+
- **`afs/googlecloud/firestore`** - Complete Google Cloud Firestore integration
5555
-**`gigamap/gigamap`** - Complete high-performance indexed collections with:
5656
-**Advanced indexing system** (bitmap, hash, unique indices)
5757
-**Full LINQ support** for querying (Eclipse Store compatible)
@@ -125,7 +125,7 @@ The .NET implementation maintains the same module structure, interfaces, and des
125125
-**Blob storage** support for large object handling
126126
-**AWS S3** storage backend for cloud storage
127127
-**Azure Storage** backend for Microsoft cloud
128-
- 🚧 **Google Cloud Firestore** integration (in progress)
128+
- **Google Cloud Firestore** integration
129129

130130
## Architecture
131131

afs/blobstore/src/AfsStorageConnection.cs

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using System.Linq;
5+
using System.Reflection;
56
using System.Threading.Tasks;
67
using MessagePack;
78
using NebulaStore.Storage;
@@ -212,10 +213,126 @@ private static IBlobStoreConnector CreateConnector(IEmbeddedStorageConfiguration
212213
"blobstore" => new LocalBlobStoreConnector(
213214
configuration.AfsConnectionString ?? configuration.StorageDirectory,
214215
configuration.AfsUseCache),
216+
"firestore" => CreateFirestoreConnector(configuration),
217+
"azure.storage" => CreateAzureStorageConnector(configuration),
218+
"s3" => CreateS3Connector(configuration),
215219
_ => throw new NotSupportedException($"AFS storage type '{configuration.AfsStorageType}' is not supported")
216220
};
217221
}
218222

223+
/// <summary>
224+
/// Creates a Google Cloud Firestore connector.
225+
/// </summary>
226+
/// <param name="configuration">The storage configuration</param>
227+
/// <returns>The Firestore connector</returns>
228+
private static IBlobStoreConnector CreateFirestoreConnector(IEmbeddedStorageConfiguration configuration)
229+
{
230+
try
231+
{
232+
// Use reflection to avoid hard dependency on Google Cloud Firestore
233+
var firestoreAssembly = System.Reflection.Assembly.LoadFrom("NebulaStore.Afs.GoogleCloud.Firestore.dll");
234+
var connectorType = firestoreAssembly.GetType("NebulaStore.Afs.GoogleCloud.Firestore.GoogleCloudFirestoreConnector");
235+
236+
if (connectorType == null)
237+
throw new TypeLoadException("GoogleCloudFirestoreConnector type not found");
238+
239+
// Create FirestoreDb instance
240+
var firestoreDbType = Type.GetType("Google.Cloud.Firestore.FirestoreDb, Google.Cloud.Firestore");
241+
if (firestoreDbType == null)
242+
throw new TypeLoadException("Google.Cloud.Firestore.FirestoreDb type not found. Make sure Google.Cloud.Firestore package is installed.");
243+
244+
var createMethod = firestoreDbType.GetMethod("Create", new[] { typeof(string) });
245+
if (createMethod == null)
246+
throw new MethodAccessException("FirestoreDb.Create method not found");
247+
248+
var projectId = configuration.AfsConnectionString ?? throw new ArgumentException("Project ID must be specified in AfsConnectionString for Firestore storage");
249+
var firestoreDb = createMethod.Invoke(null, new object[] { projectId });
250+
251+
// Create connector
252+
var factoryMethod = configuration.AfsUseCache
253+
? connectorType.GetMethod("Caching", new[] { firestoreDbType })
254+
: connectorType.GetMethod("New", new[] { firestoreDbType });
255+
256+
if (factoryMethod == null)
257+
throw new MethodAccessException($"GoogleCloudFirestoreConnector factory method not found");
258+
259+
var connector = factoryMethod.Invoke(null, new[] { firestoreDb });
260+
return (IBlobStoreConnector)connector!;
261+
}
262+
catch (Exception ex) when (!(ex is ArgumentException))
263+
{
264+
throw new NotSupportedException(
265+
"Google Cloud Firestore connector could not be created. " +
266+
"Make sure NebulaStore.Afs.GoogleCloud.Firestore and Google.Cloud.Firestore packages are installed.", ex);
267+
}
268+
}
269+
270+
/// <summary>
271+
/// Creates an Azure Storage connector.
272+
/// </summary>
273+
/// <param name="configuration">The storage configuration</param>
274+
/// <returns>The Azure Storage connector</returns>
275+
private static IBlobStoreConnector CreateAzureStorageConnector(IEmbeddedStorageConfiguration configuration)
276+
{
277+
try
278+
{
279+
// Use reflection to avoid hard dependency on Azure Storage
280+
var azureAssembly = System.Reflection.Assembly.LoadFrom("NebulaStore.Afs.Azure.Storage.dll");
281+
var connectorType = azureAssembly.GetType("NebulaStore.Afs.Azure.Storage.AzureStorageConnector");
282+
283+
if (connectorType == null)
284+
throw new TypeLoadException("AzureStorageConnector type not found");
285+
286+
var connectionString = configuration.AfsConnectionString ?? throw new ArgumentException("Connection string must be specified in AfsConnectionString for Azure storage");
287+
288+
var factoryMethod = connectorType.GetMethod("FromConnectionString", new[] { typeof(string), typeof(bool) });
289+
if (factoryMethod == null)
290+
throw new MethodAccessException("AzureStorageConnector.FromConnectionString method not found");
291+
292+
var connector = factoryMethod.Invoke(null, new object[] { connectionString, configuration.AfsUseCache });
293+
return (IBlobStoreConnector)connector!;
294+
}
295+
catch (Exception ex) when (!(ex is ArgumentException))
296+
{
297+
throw new NotSupportedException(
298+
"Azure Storage connector could not be created. " +
299+
"Make sure NebulaStore.Afs.Azure.Storage and Azure.Storage.Blobs packages are installed.", ex);
300+
}
301+
}
302+
303+
/// <summary>
304+
/// Creates an AWS S3 connector.
305+
/// </summary>
306+
/// <param name="configuration">The storage configuration</param>
307+
/// <returns>The S3 connector</returns>
308+
private static IBlobStoreConnector CreateS3Connector(IEmbeddedStorageConfiguration configuration)
309+
{
310+
try
311+
{
312+
// Use reflection to avoid hard dependency on AWS S3
313+
var s3Assembly = System.Reflection.Assembly.LoadFrom("NebulaStore.Afs.Aws.S3.dll");
314+
var connectorType = s3Assembly.GetType("NebulaStore.Afs.Aws.S3.AwsS3Connector");
315+
316+
if (connectorType == null)
317+
throw new TypeLoadException("AwsS3Connector type not found");
318+
319+
var bucketName = configuration.AfsConnectionString ?? throw new ArgumentException("Bucket name must be specified in AfsConnectionString for S3 storage");
320+
321+
var factoryMethod = connectorType.GetMethod("FromBucketName", new[] { typeof(string), typeof(bool) });
322+
if (factoryMethod == null)
323+
throw new MethodAccessException("AwsS3Connector.FromBucketName method not found");
324+
325+
var connector = factoryMethod.Invoke(null, new object[] { bucketName, configuration.AfsUseCache });
326+
return (IBlobStoreConnector)connector!;
327+
}
328+
catch (Exception ex) when (!(ex is ArgumentException))
329+
{
330+
throw new NotSupportedException(
331+
"AWS S3 connector could not be created. " +
332+
"Make sure NebulaStore.Afs.Aws.S3 and AWSSDK.S3 packages are installed.", ex);
333+
}
334+
}
335+
219336
/// <summary>
220337
/// Registers type handlers for GigaMap serialization.
221338
/// This enables automatic persistence of GigaMap instances following Eclipse Store patterns.
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using System;
2+
using Google.Cloud.Firestore;
3+
using NebulaStore.Afs.GoogleCloud.Firestore;
4+
using NebulaStore.Storage.Embedded;
5+
using NebulaStore.Storage.EmbeddedConfiguration;
6+
7+
namespace NebulaStore.Afs.GoogleCloud.Firestore.Examples;
8+
9+
/// <summary>
10+
/// Example demonstrating how to use NebulaStore with Google Cloud Firestore.
11+
/// </summary>
12+
public class FirestoreExample
13+
{
14+
/// <summary>
15+
/// Example data class for storage.
16+
/// </summary>
17+
public class Person
18+
{
19+
public string Name { get; set; } = string.Empty;
20+
public int Age { get; set; }
21+
public string Email { get; set; } = string.Empty;
22+
}
23+
24+
/// <summary>
25+
/// Example using the configuration-based approach.
26+
/// </summary>
27+
public static void ConfigurationBasedExample()
28+
{
29+
Console.WriteLine("=== Configuration-Based Firestore Example ===");
30+
31+
// Create configuration for Firestore storage
32+
var config = EmbeddedStorageConfiguration.New()
33+
.SetStorageDirectory("firestore-storage")
34+
.UseFirestore("your-project-id", useCache: true)
35+
.SetChannelCount(4)
36+
.Build();
37+
38+
try
39+
{
40+
// Start storage with Firestore backend
41+
using var storage = EmbeddedStorage.Foundation(config).Start();
42+
43+
// Create and store data
44+
var root = storage.Root<Person>();
45+
if (root == null)
46+
{
47+
root = new Person
48+
{
49+
Name = "John Doe",
50+
Age = 30,
51+
Email = "john.doe@example.com"
52+
};
53+
storage.SetRoot(root);
54+
}
55+
56+
// Modify and store
57+
root.Age = 31;
58+
storage.StoreRoot();
59+
60+
Console.WriteLine($"Stored person: {root.Name}, Age: {root.Age}");
61+
}
62+
catch (Exception ex)
63+
{
64+
Console.WriteLine($"Error: {ex.Message}");
65+
Console.WriteLine("Make sure you have:");
66+
Console.WriteLine("1. A Google Cloud Project with Firestore enabled");
67+
Console.WriteLine("2. Proper authentication configured");
68+
Console.WriteLine("3. The Google.Cloud.Firestore package installed");
69+
}
70+
}
71+
72+
/// <summary>
73+
/// Example using the convenience extension methods.
74+
/// </summary>
75+
public static void ConvenienceMethodExample()
76+
{
77+
Console.WriteLine("\n=== Convenience Method Firestore Example ===");
78+
79+
try
80+
{
81+
// Start with Firestore using convenience method
82+
using var storage = EmbeddedStorageFirestoreExtensions.StartWithFirestore("your-project-id");
83+
84+
var root = storage.Root<Person>();
85+
if (root == null)
86+
{
87+
root = new Person
88+
{
89+
Name = "Jane Smith",
90+
Age = 25,
91+
Email = "jane.smith@example.com"
92+
};
93+
storage.SetRoot(root);
94+
}
95+
96+
Console.WriteLine($"Retrieved person: {root.Name}, Age: {root.Age}");
97+
}
98+
catch (Exception ex)
99+
{
100+
Console.WriteLine($"Error: {ex.Message}");
101+
}
102+
}
103+
104+
/// <summary>
105+
/// Example using direct AFS file system operations.
106+
/// </summary>
107+
public static void DirectAfsExample()
108+
{
109+
Console.WriteLine("\n=== Direct AFS Firestore Example ===");
110+
111+
try
112+
{
113+
// Create Firestore connection
114+
var firestore = FirestoreDb.Create("your-project-id");
115+
116+
// Create file system with Firestore backend
117+
using var fileSystem = EmbeddedStorageFirestoreExtensions.CreateFirestoreFileSystem("your-project-id");
118+
119+
// Perform direct file operations
120+
var path = NebulaStore.Afs.Blobstore.BlobStorePath.New("my-collection", "folder", "file.txt");
121+
var data = System.Text.Encoding.UTF8.GetBytes("Hello, Firestore!");
122+
123+
fileSystem.IoHandler.WriteData(path, data);
124+
var readData = fileSystem.IoHandler.ReadData(path, 0, -1);
125+
var content = System.Text.Encoding.UTF8.GetString(readData);
126+
127+
Console.WriteLine($"Stored and retrieved: {content}");
128+
}
129+
catch (Exception ex)
130+
{
131+
Console.WriteLine($"Error: {ex.Message}");
132+
}
133+
}
134+
135+
/// <summary>
136+
/// Example with authentication setup.
137+
/// </summary>
138+
public static void AuthenticationExample()
139+
{
140+
Console.WriteLine("\n=== Authentication Setup Example ===");
141+
142+
try
143+
{
144+
// Option 1: Using service account key file
145+
var firestoreWithKey = new FirestoreDbBuilder
146+
{
147+
ProjectId = "your-project-id",
148+
CredentialsPath = "path/to/service-account-key.json"
149+
}.Build();
150+
151+
using var connector1 = GoogleCloudFirestoreConnector.New(firestoreWithKey);
152+
Console.WriteLine("Created connector with service account key");
153+
154+
// Option 2: Using environment variable for credentials
155+
Environment.SetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS", "path/to/service-account-key.json");
156+
var firestoreWithEnv = FirestoreDb.Create("your-project-id");
157+
158+
using var connector2 = GoogleCloudFirestoreConnector.Caching(firestoreWithEnv);
159+
Console.WriteLine("Created connector with environment credentials");
160+
161+
// Option 3: Using emulator for testing
162+
Environment.SetEnvironmentVariable("FIRESTORE_EMULATOR_HOST", "localhost:8080");
163+
var firestoreEmulator = FirestoreDb.Create("test-project");
164+
165+
using var connector3 = GoogleCloudFirestoreConnector.New(firestoreEmulator);
166+
Console.WriteLine("Created connector for emulator");
167+
}
168+
catch (Exception ex)
169+
{
170+
Console.WriteLine($"Error: {ex.Message}");
171+
}
172+
}
173+
174+
/// <summary>
175+
/// Main entry point for the examples.
176+
/// </summary>
177+
public static void Main(string[] args)
178+
{
179+
Console.WriteLine("NebulaStore Google Cloud Firestore Integration Examples");
180+
Console.WriteLine("=====================================================");
181+
182+
ConfigurationBasedExample();
183+
ConvenienceMethodExample();
184+
DirectAfsExample();
185+
AuthenticationExample();
186+
187+
Console.WriteLine("\nFor more information, see the README.md file.");
188+
}
189+
}

0 commit comments

Comments
 (0)