Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

persistence v3 mongo #228

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions content-for-demo/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/mongo
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you need a new gitignore here. That should also be configurable in the existing gitignore

13 changes: 13 additions & 0 deletions content-for-demo/docker-compose-mongodb.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3.8'
services:
mongodb:
image: mongo
hostname: "mongo"
restart: always
volumes:
- ./mongo/data:/data/db
ports:
- '27017:27017'
environment:
- MONGO_INITDB_ROOT_USERNAME=mongo
- MONGO_INITDB_ROOT_PASSWORD=mongo
4 changes: 3 additions & 1 deletion src/AasxServerBlazor/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
using Microsoft.Extensions.Hosting;
using System;
using System.IO;
using System.Linq;
using System.Threading;

namespace AasxServerBlazor
{
public class Program1
{

public static bool withMongodb = false;
public static void Main(string[] args)
{
Console.WriteLine(Directory.GetCurrentDirectory());
Expand All @@ -25,6 +26,7 @@ public static void Main(string[] args)
if (url[2] != null)
AasxServer.Program.blazorPort = url[2];

withMongodb = args.Contains("--with-mongodb");
var host = CreateHostBuilder(args).Build();

host.RunAsync();
Expand Down
2 changes: 1 addition & 1 deletion src/AasxServerBlazor/Properties/launchSettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
},
"AasxServerBlazor": {
"commandName": "Project",
"commandLineArgs": "--no-security --secret-string-api 1234 --aasx-in-memory 1000 --data-path \"C:\\Development\\Ronny\" --edit --external-blazor http://localhost:5001",
"commandLineArgs": "--no-security --aasx-in-memory 1000 --data-path \"C:\\GitClones\\aasx-server\\content-for-demo\\aasxs\" --edit --external-blazor http://localhost:5001 --with-mongodb \"mongodb://mongo:mongo@localhost:27017/\"",
Copy link
Contributor

Choose a reason for hiding this comment

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

please revert the changes for --data-path

"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
Expand Down
13 changes: 11 additions & 2 deletions src/AasxServerBlazor/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,17 @@ public void ConfigureServices(IServiceCollection services)
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IRegistryInitializerService, RegistryInitializerService>();
services.AddScoped(typeof(IAppLogger<>), typeof(LoggerAdapter<>));
services.AddTransient<IAssetAdministrationShellService, AssetAdministrationShellService>();
services.AddTransient<IAdminShellPackageEnvironmentService, AdminShellPackageEnvironmentService>();

if (Program1.withMongodb)
{
services.AddTransient<IAssetAdministrationShellService, AssetAdministrationShellServiceDB>();
services.AddTransient<IAdminShellPackageEnvironmentService, AdminShellPackageEnvironmentServiceDB>();
}
else
{
services.AddTransient<IAssetAdministrationShellService, AssetAdministrationShellService>();
services.AddTransient<IAdminShellPackageEnvironmentService, AdminShellPackageEnvironmentService>();
}
services.AddTransient<IIdShortPathParserService, IdShortPathParserService>();
services.AddTransient<ISubmodelService, SubmodelService>();
services.AddTransient<IConceptDescriptionService, ConceptDescriptionService>();
Expand Down
2 changes: 1 addition & 1 deletion src/AasxServerBlazor/startForDemo.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
dotnet AasxServerBlazor.dll --no-security --data-path ./aasxs --host 0.0.0.0 $OPTIONSAASXSERVER
dotnet AasxServerBlazor.dll --no-security --aasx-in-memory 1000 --data-path ./aasxs --edit --external-blazor http://localhost:5001 --with-mongodb "mongodb://mongo:mongo@mongodb-server:27017/" $OPTIONSAASXSERVER
2 changes: 2 additions & 0 deletions src/AasxServerStandardBib/AasxServerStandardBib.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.13.1" />
<PackageReference Include="MongoDB.Driver" Version="2.25.0" />
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the license for these package checked, to be compatible with the open source guidelines?

<PackageReference Include="MongoDB.Driver.GridFS" Version="2.25.0" />
<PackageReference Include="MQTTnet.NETStandard" Version="3.0.8" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
Expand Down
258 changes: 258 additions & 0 deletions src/AasxServerStandardBib/Database.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AasxServerStandardBib.Exceptions;
using MongoDB.Bson;
using MongoDB.Driver.Linq;
using System.Collections;
using AasCore.Aas3_0;
using AdminShellNS;
using MongoDB.Driver.GridFS;
using static AasxServerStandardBib.TimeSeriesPlotting.PlotArguments;
using System.IO;


//Author: Jonas Graubner
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't use personalised author tags. I know that we do not have a template for that yet, but creating your own one will be harder to resolve in the end. All your commits can be viewed in the git history and if you want to add yourself as a contributor, you can add a new entry in the CONTRIBUTORS.md

//contact: [email protected]
namespace AasxServerStandardBib
{
public interface IDatabase
Copy link
Contributor

Choose a reason for hiding this comment

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

Please avoid creating multiple classes/interfaces in a single file. This is considered a Code Smell. Although this exists in other parts of the project, let's not add new instances.

More info: Code Smell #228: Multiple Classes per File

{
void Initialize(String connectionString);

#region AssetAdministrationShell
public void WriteDBAssetAdministrationShell(IAssetAdministrationShell shell);
public IQueryable<AssetAdministrationShell> GetLINQAssetAdministrationShell();
public void UpdateDBAssetAdministrationShellById(IAssetAdministrationShell body, string aasIdentifier);
public bool DeleteDBAssetAdministrationShellById(IAssetAdministrationShell shell);
#endregion

#region Submodel
public void WriteDBSubmodel(ISubmodel submodel);
public IQueryable<Submodel> GetLINQSubmodel();
public void UpdateDBSubmodelById(string submodelIdentifier, ISubmodel newSubmodel);
public void DeleteDBSubmodelById(string submodelIdentifier);
#endregion

#region ConceptDescription
public void WriteDBConceptDescription(IConceptDescription conceptDescription);
public IQueryable<ConceptDescription> GetLINQConceptDescription();
public void UpdateDBConceptDescriptionById(IConceptDescription newConceptDescription, string cdIdentifier);
public void DeleteDBConceptDescriptionById(string conceptDescription);
#endregion

#region Filestream
public void WriteFile(Stream stream, string filename);
public Stream ReadFile(string filename);
public void DeleteFile(string filename);
#endregion

public void importAASCoreEnvironment(IEnvironment environment);
public void importAdminShellPackageEnv(AdminShellPackageEnv adminShellPackageEnv);
}


public class MongoDatabase : IDatabase
{
private MongoClient _client;
private IMongoDatabase _database;
private GridFSBucket _bucket;

public void Initialize(String connectionString)
{
//_client = new MongoClient("mongodb://AAS:[email protected]:27017/?authSource=AAS");
Copy link
Contributor

Choose a reason for hiding this comment

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

don't commit commented code. If you think this might be useful information as an example, please create a valid c# method summary

_client = new MongoClient(connectionString);
try
{
_client.StartSession();
}
catch (System.TimeoutException ex)
{
System.Console.WriteLine(ex.Message);
System.Environment.Exit(1);
}

_database = _client.GetDatabase("AAS");
Copy link
Contributor

Choose a reason for hiding this comment

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

If the values like AAS, AasCore, and the bucketName aasxFiles are constant, it's best practice to use class constants. This helps avoid "Magic Numbers" and improves code readability and maintainability.

For more information, see Code Smell: Magic Numbers.

var objectSerializer = new ObjectSerializer(type => ObjectSerializer.DefaultAllowedTypes(type) || type.FullName.StartsWith("AasCore") || type.FullName.StartsWith("MongoDB"));
BsonSerializer.RegisterSerializer(objectSerializer);
_bucket = new GridFSBucket(_database, new GridFSBucketOptions
{
BucketName = "aasxFiles",
});
}
private IMongoCollection<AssetAdministrationShell> getAasCollection()
{
return _database.GetCollection<AssetAdministrationShell>("AssetAdministrationShells");
}
private IMongoCollection<Submodel> getSubmodelCollection()
{
return _database.GetCollection<Submodel>("Submodels");
}
private IMongoCollection<ConceptDescription> getConceptDescriptionCollection()
{
return _database.GetCollection<ConceptDescription>("ConceptDescriptions");
}


#region AssetAdministrationShell
public void WriteDBAssetAdministrationShell(IAssetAdministrationShell shell)
{
try
{
getAasCollection().InsertOne((AssetAdministrationShell)shell);
}
catch (MongoWriteException)
{
}
}
public bool DeleteDBAssetAdministrationShellById(IAssetAdministrationShell shell)
{
throw new NotImplementedException();
}
public IQueryable<AssetAdministrationShell> GetLINQAssetAdministrationShell()
{
return getAasCollection().AsQueryable();
}
public async void UpdateDBAssetAdministrationShellById(IAssetAdministrationShell body, string aasIdentifier)
{
await getAasCollection().ReplaceOneAsync(r => r.Id.Equals(aasIdentifier), (AssetAdministrationShell)body);
}
#endregion

#region Submodel
public void WriteDBSubmodel(ISubmodel submodel)
{
try
{
getSubmodelCollection().InsertOne((Submodel)submodel);
}
catch (MongoWriteException)
{
}
}
public IQueryable<Submodel> GetLINQSubmodel()
{
return getSubmodelCollection().AsQueryable();
}
public async void UpdateDBSubmodelById(string submodelIdentifier, ISubmodel newSubmodel)
{
await getSubmodelCollection().ReplaceOneAsync(r => r.Id.Equals(submodelIdentifier), (Submodel)newSubmodel);
}
public async void DeleteDBSubmodelById(string submodelIdentifier)
{
await getSubmodelCollection().DeleteOneAsync(a => a.Id.Equals(submodelIdentifier));
}
#endregion

#region ConceptDescription
public void WriteDBConceptDescription(IConceptDescription conceptDescription)
{
try
{
getConceptDescriptionCollection().InsertOne((ConceptDescription)conceptDescription);
}
catch (MongoWriteException)
{
}
}
public IQueryable<ConceptDescription> GetLINQConceptDescription()
{
return getConceptDescriptionCollection().AsQueryable();
}
public async void UpdateDBConceptDescriptionById(IConceptDescription newConceptDescription, string cdIdentifier)
{
await getConceptDescriptionCollection().ReplaceOneAsync(r => r.Id.Equals(cdIdentifier), (ConceptDescription)newConceptDescription);
}
public async void DeleteDBConceptDescriptionById(string conceptDescription)
{
await getConceptDescriptionCollection().DeleteOneAsync(a => a.Id.Equals(conceptDescription));
}
#endregion

#region Filestream
public async void WriteFile(Stream stream, string filename)
{
if (stream != null)
{
Console.WriteLine("New File");
var id = await _bucket.UploadFromStreamAsync(filename, stream);
stream.Close();
}else
{
//throw new ArgumentNullException(nameof(stream));
Copy link
Contributor

Choose a reason for hiding this comment

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

It's important to handle exceptions appropriately rather than commenting them out. In your WriteFile method, you've commented out the ArgumentNullException handling for the stream parameter. This approach can lead to unexpected behavior or silent failures if stream is null, as it will not be properly handled or logged.

Best practice dictates that exceptions should be handled or propagated correctly to maintain application reliability and provide meaningful error messages for debugging. Commenting out exceptions can obscure issues and make troubleshooting more difficult, which is considered a code smell.

To improve, consider either uncommenting the exception handling and adding appropriate logging or refactor the method to handle null stream scenarios more gracefully.

}
}
public Stream ReadFile(string filename)
{
return _bucket.OpenDownloadStream(getFileIdFromFilename(filename));
}

public async void DeleteFile(string filename)
{
ObjectId fileId = getFileIdFromFilename(filename);
await _bucket.DeleteAsync(fileId);
}

private ObjectId getFileIdFromFilename(string filename)
{
var filter = Builders<GridFSFileInfo>.Filter.Eq(x => x.Filename, filename);
var sort = Builders<GridFSFileInfo>.Sort.Descending(x => x.UploadDateTime);
var options = new GridFSFindOptions
{
Limit = 1,
Sort = sort
};

using (var cursor = _bucket.Find(filter, options))
{
var fileInfo = cursor.ToList().FirstOrDefault();
// fileInfo either has the matching file information or is null
Copy link
Contributor

Choose a reason for hiding this comment

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

This could rather be used as a method summary.

/// <summary>
/// Retrieves the ObjectId of the latest file matching the specified filename in the GridFS bucket.
/// </summary>
/// <param name="filename">The filename of the file to retrieve.</param>
/// <returns>The ObjectId of the matching file, or ObjectId.Empty if no matching file is found.</returns>

return fileInfo.Id;
}
}


#endregion


public void importAASCoreEnvironment(IEnvironment environment)
{
environment.AssetAdministrationShells.ForEach(shell => {
WriteDBAssetAdministrationShell(shell);
});

environment.Submodels.ForEach(submodel =>
{
WriteDBSubmodel(submodel);
});

environment.ConceptDescriptions.ForEach(conceptDescription =>
{
WriteDBConceptDescription(conceptDescription);
});
}

public void importAdminShellPackageEnv(AdminShellPackageEnv adminShellPackageEnv)
Copy link
Contributor

Choose a reason for hiding this comment

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

Use the standard C# naming for methods, which is PascalCase please

{
importAASCoreEnvironment(adminShellPackageEnv.AasEnv);

//now import Files
var files = adminShellPackageEnv.GetListOfSupplementaryFiles();
var assetid = adminShellPackageEnv.AasEnv.AssetAdministrationShells[0].AssetInformation.GlobalAssetId; //unique identifier
foreach ( var file in files )
{
if (file.Location == AdminShellNS.AdminShellPackageSupplementaryFile.LocationType.InPackage)
{
WriteFile(adminShellPackageEnv.GetLocalStreamFromPackage(file.Uri.ToString()), assetid+file.Uri.ToString());
//ReadFile(assetid + file.Uri.ToString());
}
}

}
}
}
Loading