I'm working on making a custom properties provider to load the contents of a Spring cloud config server at startup. I need to make a single call at the initialization of the provider to fetch these properties, and would like to use the Mule HttpService in order to make the http client for this call, instead of creating my own. Unfortunately, whenever I try this, it seems the HttpService hasn't been created yet and so throws an NPE once it's referenced.
CustomConfigurationPropertiesProviderFactory.java
public class CustomConfigurationPropertiesProviderFactory implements ConfigurationPropertiesProviderFactory {
public static final String EXTENSION_NAMESPACE = "custom-properties";
public static final String CONFIGURATION_PROPERTIES_ELEMENT = "config";
public static final ComponentIdentifier CUSTOM_CONFIGURATION_PROPERTIES =
builder().namespace(EXTENSION_NAMESPACE).name(CONFIGURATION_PROPERTIES_ELEMENT).build();
#Inject
HttpService httpService;
#Override
public ComponentIdentifier getSupportedComponentIdentifier() {
return CUSTOM_CONFIGURATION_PROPERTIES;
}
#Override
public ConfigurationPropertiesProvider createProvider(ConfigurationParameters parameters,
ResourceProvider externalResourceProvider) {
String url = parameters.getStringParameter("url");
return new CustomConfigurationPropertiesProvider(url, httpService);
}
}
CustomConfigurationPropertiesProvider.java
public class CustomConfigurationPropertiesProvider implements ConfigurationPropertiesProvider {
private final static String PREFIX = "custom::";
private Properties properties = null;
public CustomConfigurationPropertiesProvider(String url, HttpService httpService) {
HttpClientConfiguration.Builder builder = new HttpClientConfiguration.Builder();
builder.setName("customProperties");
HttpClient client = httpService.getClientFactory().create(builder.build()); //NPE here
client.start();
// proceed to create and execute request, then load into properties
}
#Override
public Optional<ConfigurationProperty> getConfigurationProperty(String configurationAttributeKey) {
if (configurationAttributeKey.startsWith(PREFIX)) {
String effectiveKey = configurationAttributeKey.substring(PREFIX.length());
if (properties != null && !properties.isEmpty()) {
return Optional.of(new ConfigurationProperty() {
#Override
public Object getSource() {...}
#Override
public Object getRawValue() { return properties.getProperty(effectiveKey); }
#Override
public String getKey() { return effectiveKey; }
});
}
}
return Optional.empty();
}
}
What do I need to change to properly inject this service?
I've been following the advice from these two bits of documentation, for reference:
https://docs.mulesoft.com/mule-runtime/4.2/custom-configuration-properties-provider
https://docs.mulesoft.com/mule-sdk/1.1/mule-service-injection
Related
In my asp.net core 6 mvc project, I have a transient service ContentService. This service is inherited from IContentService and added to Program.cs using
builder.Services.AddTransient<IContentService, ContentService>();
Here is the implementation of the service:
public interface IContentService
{
public string GetText(int id);
}
public class ContentService : IContentService
{
private readonly ContentDbContext _context;
private List<string> textList;
public ContentService(ContentDbContext context)
{
_context = context;
ReadTexts();
}
public void ReadTexts()
{
// Reads text from the database and fills the textList.
// THIS CODE MUST RUN ONLY FOR THE FIRST REQUEST.
}
public string GetText(int id)
{
return textList(id);
}
}
The problem is every time ContentService is requested, the constructor runs and ReadTexts() is called, which is a heavy function that must only run for the first request. Is there anyway to implement a transient service with a code block that runs only for the first request? Note that I cannot use AddSingleton.
You have to make textList as static if you want it to hold data which should be read only on first request.
try this.
public class ContentService : IContentService
{
private readonly ContentDbContext _context;
private static List<string> textList = new List<string>();
public ContentService(ContentDbContext context)
{
_context = context;
ReadTexts();
}
public void ReadTexts()
{
if(!textList.Any())
{
// Reads text from the database and fills the textList.
// THIS CODE MUST RUN ONLY FOR THE FIRST REQUEST.
}
}
public string GetText(int id)
{
return textList(id);
}
}
I have an intranet application that uses the Windows username and passes that to a procedure to return data.
I'm using dependency injection, but I don't believe I have the method to get the username separated properly.
I'm trying to keep this secure by not passing in the username as a parameter, but I also want to be able to impersonate (or bypass my GetWindowsUser() method) and send in another username so I can test results for other users.
One idea I had for this was to set a session variable in another page with another (impersonated) username, then check if that session variable exists first before grabbing the actual user name, but I couldn't figure out how to access the session variable in the repository.
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
return _dropDownDataRepository.MyList();
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
}
REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
public HttpResponseMessage MyList()
{
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
//Grab the windowsUser from the method
var windowsUser = utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
}
INTERFACE
public interface IDropDownDataRepository : IDisposable
{
HttpResponseMessage MyList();
}
UTILITY CLASS
public class Utility
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UPDATE 1
In addition to what Nikolai and Brendt posted below, the following is also needed to allow Web Api controllers work with the session state.
Accessing Session Using ASP.NET Web API
Abstract the Utility class and inject it into the repository.
Then you can stub or mock for testing.
public interface IUtility
{
string GetWindowsUser();
}
public class TestUtility : IUtility
{
public string GetWindowsUser()
{
return "TestUser";
}
}
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private IUtility _utility;
public DropDownDataRepository(IUtility utility)
{
_utility = utility;
}
}
EDIT
Also the repository should not return an HTTPResponseMessage type it should just return a List<T> of the domain model you're accessing.
i.e.
public List<Model> MyList()
{
//Grab the windowsUser from the method
var windowsUser = _utility.GetWindowsUser();
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.myProcedure(windowsUser)
select p).ToList();
return sourceQuery
}
Then move the JSON portion to the controller.
One idea I had for this was to set a session variable in another page
with another (impersonated) username, then check if that session
variable exists first before grabbing the actual user name, but I
couldn't figure out how to access the session variable in the
repository.
Potentially, if you add in a dependency to session, you need to isolate it, e.g.
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private ISession session;
public DropDownDataRepository(ISession session)
{
this.session = session;
}
public HttpResponseMessage MyList()
{
var myUserName = this.session.UserName;
// ... etc
With ISession being something like:
public interface ISession
{
string UserName { get; }
}
Implemented as:
public class MySession : ISession
{
public string UserName
{
get
{
// potentially do some validation and return a sensible default if not present in session
return HttpContext.Current.Session["UserName"].ToString();
}
}
}
Of course there is the potential to decouple this MySession class from HttpContext if desired.
With regards to this:
//(This should be separated somehow, right?)
//Create a new instance of the Utility class
Utility utility = new Utility();
Yes, anytime you create a new object you are tightly coupling them together, which will give you issues, for example, if you try to unit test it in isolation.
In this instance you could extract an IUtility interface from Utility:
public class Utility : IUtility
{
string GetWindowsUser();
}
Then:
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
// ... other fields
private IUtility utility;
public DropDownDataRepository(IUtility utility)
{
this.utility = utility;
// .... etc
Then you have removed the depenedency between Utility and DropDownDataRepository, and can substitute in another type or mock with ease.
I got a lot of help from Nikolai and Brent and got most of the way there with their posted answers, but ended up figuring out the complete answer on my own. The problems I was having were related to not being able to access session variables in a WebAPI. So, I'm sure there are cleaner solutions to this, but I definitely improved what I had and came up with the following code, which works.
This answer was needed to allow access to the session variable in Web Api - Accessing Session Using ASP.NET Web API
GLOBAL.asax.cs
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
UnityConfig.RegisterComponents();
AreaRegistration.RegisterAllAreas();
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
//Added to allow use of session state in Web API
protected void Application_PostAuthorizeRequest()
{
if (IsWebApiRequest())
{
HttpContext.Current.SetSessionStateBehavior(SessionStateBehavior.Required);
}
}
//Added to allow use of session state in Web API
private bool IsWebApiRequest()
{
return HttpContext.Current.Request.AppRelativeCurrentExecutionFilePath.StartsWith(WebApiConfig.UrlPrefixRelative);
}
protected void Session_Start(Object sender, EventArgs e)
{
//Default set the session variable to none
Session["_impersonatedUser"] = "none";
}
protected void Session_End(Object sender, EventArgs e)
{
//Reset the session variable to blank
Session["_impersonatedUser"] = "";
}
}
UNITY.config
public static class UnityConfig
{
public static void RegisterComponents()
{
var container = new UnityContainer();
// register all your components with the container here
// it is NOT necessary to register your controllers
// e.g. container.RegisterType<ITestService, TestService>();
container.RegisterType<IDropDownDataRepository, DropDownDataRepository>();
container.RegisterType<IUtilityRepository, UtilityRepository>();
container.RegisterType<ISessionRepository, SessionRepository>();
//MVC5
//Unity.MVC5 NuGet Package
DependencyResolver.SetResolver(new Unity.Mvc5.UnityDependencyResolver(container));
//WEB API
//Unity.WebApi NuGet Package
GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
}
}
WEB API CONTROLLER
public class DropDownDataController : ApiController
{
private IDropDownDataRepository _dropDownDataRepository;
//Dependency Injection using Unity.WebAPI NuGet Package
public DropDownDataController(IDropDownDataRepository dropDownDataRepository)
{
_dropDownDataRepository = dropDownDataRepository;
}
[HttpGet]
public HttpResponseMessage MyList()
{
try
{
var sourceQuery = _dropDownDataRepository.MyList();
//JSON stuff moved to controller
string result = JsonConvert.SerializeObject(sourceQuery);
var response = new HttpResponseMessage();
response.Content = new StringContent(result, System.Text.Encoding.Unicode, "application/json");
return response;
}
catch
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
}
}
protected override void Dispose(bool disposing)
{
_dropDownDataRepository.Dispose();
base.Dispose(disposing);
}
}
DROPDOWNDATA REPOSITORY
public class DropDownDataRepository : IDropDownDataRepository, IDisposable
{
private DatabaseEntities db = new DatabaseEntities();
private IUtilityRepository _utilityRepository;
private ISessionRepository _sessionRepository;
//Dependency Injection of Utility and Session
public DropDownDataRepository(IUtilityRepository utilityRepository, ISessionRepository sessionRepository)
{
_utilityRepository = utilityRepository;
_sessionRepository = sessionRepository;
}
//Changed to a list here
public List<MyProcedure> MyList()
{
string windowsUser;
//Check the session variable to see if a user is being impersonated
string impersonatedUser = _sessionRepository.ImpersonatedUser;
//Grab the windowsUser from the Utility Repository
windowsUser = _utilityRepository.GetWindowsUser();
if (impersonatedUser != "none")
{
windowsUser = impersonatedUser;
}
//Pass windowsUser parameter to the procedure
var sourceQuery = (from p in db.MyProcedure(windowsUser)
select p).ToList();
return sourceQuery;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
DROPDOWNDATA INTERFACE
public interface IDropDownDataRepository : IDisposable
{
//Changed to list here
List<MyProcedure> MyList();
}
UTILITY REPOSITORY
public class UtilityRepository : IUtilityRepository
{
public string GetWindowsUser()
{
//Get the current windows user
string windowsUser = HttpContext.Current.User.Identity.Name;
return windowsUser;
}
}
UTILITY INTERFACE
public interface IUtilityRepository
{
string GetWindowsUser();
}
SESSION REPOSITORY
public class SessionRepository : ISessionRepository
{
public string ImpersonatedUser
{
get
{
return HttpContext.Current.Session["_impersonatedUser"].ToString();
}
}
}
SESSION INTERFACE
public interface ISessionRepository
{
string ImpersonatedUser { get; }
}
We have a multi-database solution and are passing the connection string to a factory function like so:
container.Register<IDbContextFactory>(
f => new DynamicDbContextFactory(ClientConfig.GetConnectionString()),
new PerScopeLifetime());
ClientConfig contains a static dictionary that gets populated on app start that maps a sub domain to a connection string. It seems that this approach is causing a memory leak (not 100% sure about this causing the leak but there is a leak).
public class ClientConfig
{
private static ConcurrentDictionary<string, string> ConnectionStringManager
{
get;
set;
}
// etc.
}
My question is in MVC what is the best way to hold a list of connection strings that can be easily looked up on each request in order to pass that down the chain.
Edit : The question was initially tagged with Autofac
With Autofac you don't have to use a dictionary and something like that to do what you want. You can use a custom parameter :
public class ConnectionStringParameter : Parameter
{
public override Boolean CanSupplyValue(ParameterInfo pi,
IComponentContext context,
out Func<Object> valueProvider)
{
valueProvider = null;
if (pi.ParameterType == typeof(String)
&& String.Equals(pi.Name, "connectionString",
StringComparison.OrdinalIgnoreCase))
{
valueProvider = () =>
{
// get connectionstring based on HttpContext.Current.Request.Url.Host
return String.Empty;
};
}
return valueProvider != null;
}
}
Then register your Parameter using a Module
public class ConnectionStringModule : Autofac.Module
{
protected override void AttachToComponentRegistration(
IComponentRegistry componentRegistry, IComponentRegistration registration)
{
registration.Preparing += registration_Preparing;
}
private void registration_Preparing(Object sender, PreparingEventArgs e)
{
Parameter[] parameters = new Parameter[] { new ConnectionStringParameter() };
e.Parameters = e.Parameters.Concat(parameters);
}
}
Module you have to register inside your container using
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
Each time Autofac have to resolve a parameter of type String named connectionString it will used the custom parameter and get your connectionstring based on what you want.
By the way this code sample use HttpContext.Current. In case of a multithreaded process it may return null. I don't recommend using HttpContext.Current for such things. You can use an intermediate class instead of accessing it, for example a IConnectionstringProvider interface.
public interface IConnectionstringProvider
{
String ConnectionString { get; }
}
public class ConnectionStringProvider : IConnectionstringProvider
{
public ConnectionStringProvider(Strong host)
{
// get connectionstring based on host
this._connectionString = String.Empty;
}
private readonly String _connectionString;
public String ConnectionString
{
get { return this._connectionString; }
}
}
Inside your Parameter you will have to change the valueProvider by
valueProvider = () =>
{
return context.Resolve<IConnectionstringProvider>().ConnectionString;
};
And finally you will have to register your IConnectionstringProvider at the beginning of the request lifetimescope :
class Program
{
static void Main(string[] args)
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterModule(new ConnectionStringModule());
IContainer container = builder.Build();
container.ChildLifetimeScopeBeginning += container_ChildLifetimeScopeBeginning;
}
private static void container_ChildLifetimeScopeBeginning(
Object sender, LifetimeScopeBeginningEventArgs e)
{
String host = HttpContext.Current.Request.Url.Host;
ContainerBuilder childLifetimeScopeBuilder = new ContainerBuilder();
childLifetimeScopeBuilder.RegisterInstance(new ConnectionStringProvider(host))
.As<IConnectionstringProvider>()
.SingleInstance();
childLifetimeScopeBuilder.Update(e.LifetimeScope.ComponentRegistry);
}
}
Of course there is many way to do it but you have the idea
I would like to create custom image component .So I followed step by step from Integrating with Server-side. For basic or first-step , I created for the test Server to Client RPC call as following steps.
MyComponentWidget.java
public class MyComponentWidget extends HTML {
public MyComponentWidget() {
getElement().setAttribute("class", "thumbnail");
}
public final void createCustomImage(final String url) {
getElement().setInnerHTML("<div class='delete-block'></div><img src=" + url + " />");
}
}
MyComponentState.java
public class MyComponentState extends AbstractComponentState {
private String url;
private String html;
public final String getUrl() {
return url;
}
public final void setUrl(final String url) {
this.url = url;
}
public final String getHtml() {
return html;
}
public final void setHtml(final String html) {
this.html = html;
}
}
MyComponentConnector.java
public class MyComponentConnector extends AbstractComponentConnector {
public MyComponentConnector() {
registerRpc(MyComponentClientRpc.class, new MyComponentClientRpc() {
#Override
public void getMessage() {
// never reach to this place
System.err.println("Reach Here !");
getState().setHtml(getWidget().getHTML());
}
});
}
#Override
public final MyComponentWidget getWidget() {
return (MyComponentWidget) super.getWidget();
}
#Override
public final MyComponentState getState() {
return (MyComponentState) super.getState();
}
#OnStateChange("url")
final void updateText() {
getWidget().createCustomImage(getState().getUrl());
}
}
MyComponentClientRpc.java
import com.vaadin.shared.communication.ClientRpc;
public interface MyComponentClientRpc extends ClientRpc {
void getMessage();
}
MyComponent.java
public class MyComponent extends AbstractComponent {
public MyComponent(final String url) {
getState().setUrl(url);
}
public final MyComponentState getState() {
return (MyComponentState) super.getState();
}
public final String getHTML() {
getRpcProxy(MyComponentClientRpc.class).getMessage();
return getState().getHtml();
}
}
and call as
MyComponent image = new MyComponent("myImageUrl");
System.out.println(image.getHTML());
My problem is why I always get null value at my console ? I can see the image at browser but System.out.println(image.getHTML()); produces null. What am I missing ?
To make a rpc call from client to server, you must extend the ServerRpc interface, for example:
package com.example.client.MyServerRpc
public interface MyServerRpc extends com.vaadin.shared.communication.ServerRpc {
void sendHTML(String html);
}
In your connector your register the rpc:
private MyServerRpc rpc = RpcProxy.create(MyServerRpc.class, this);
And then you can send a value by using the registered rpc in your connector:
rpc.sendHTML(html);
To receive the value on your component's or extension's server-side class, you must create an instance of the rpc interface:
private MyServerRpc rpc = new MyServerRpc() {
#Override
public void sendHTML(String html) {
// this method will be called!
}
};
and register that in the constructor:
registerRpc(rpc);
After these steps RPC from client to server should work.
Using db4o client/server, updates are not working for collection properties of an object. I'm using transparent persistence, but that's not helping. Then, I changed my Collection property to ActivatableCollection, but no luck.
This is the server setup:
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
This is the entity that I want to save:
public class Application : ActivatableEntity
And this is the property within the Application entity:
public ActivatableCollection<TaskBase> Tasks { get; private set; }
This is the client code to update each object within the collection:
Application application = (from Application app in db
where app.Name == "Foo"
select app).FirstOrDefault();
foreach (TaskBase task in application.Tasks)
{
task.Description += ".";
}
db.Store(application);
Curiously, db.Commit() didn't work either.
There are two work-arounds, but I'd rather do this the "right" way.
Work-around 1: Call db.Store(task) on each task as the change is made.
Work-around 2: Before calling db.Store(), do this:
db.Ext().Configure().UpdateDepth(5);
Can anyone tell me why the list isn't updating?
If it helps, here is the ActivatableCollection class:
public class ActivatableCollection<T> : Collection<T>, IActivatable
{
[Transient]
private IActivator _activator;
/// <summary>
/// Activates the specified purpose.
/// </summary>
/// <param name="purpose">The purpose.</param>
public void Activate(ActivationPurpose purpose)
{
if (this._activator != null)
{
this._activator.Activate(purpose);
}
}
/// <summary>
/// Binds the specified activator.
/// </summary>
/// <param name="activator">The activator.</param>
public void Bind(IActivator activator)
{
if (_activator == activator) { return; }
if (activator != null && null != _activator) { throw new System.InvalidOperationException(); }
_activator = activator;
}
}
Indeed, transparent persistence needs a call to it's activator before every field access. However the intentions is that you do this with the enhancer-tool instead of implementing manually.
Another note: When you're using CascadeOnUpdate(true) everywhere db4o will end up storing every reachable activated object. If the object-graph is huge, this can be a major performance bottle-neck.
I was able to get transparent activation and persistence to work. I decided not to go with the approach for the reasons mentioned in my comment above. I think the easiest way to handle cascading updates is to simply use a client config like this:
IClientConfiguration clientConfig = Db4oClientServer.NewClientConfiguration();
And then either a bunch of these (this isn't so bad because we can add an attribute to every domain entity, then reflectively do this on each one):
clientConfig.Common.ObjectClass(typeof(Application)).CascadeOnUpdate(true);
Or this:
clientConfig.Common.UpdateDepth = 10;
return Db4oClientServer.OpenClient(clientConfig, databaseServerName, databaseServerPort, databaseUser, databasePassword);
Now, here is the server config that allowed me to get transparent persistence working.
private void StartDatabase()
{
IServerConfiguration serverConfiguration = Db4oClientServer.NewServerConfiguration();
serverConfiguration.Networking.MessageRecipient = this;
serverConfiguration.Common.Add(new TransparentActivationSupport());
serverConfiguration.Common.Add(new TransparentPersistenceSupport());
string db4oDatabasePath = AppDomain.CurrentDomain.BaseDirectory;
string db4oDatabaseFileName = ConfigurationManager.AppSettings["db4oDatabaseFileName"];
int databaseServerPort = Convert.ToInt32(ConfigurationManager.AppSettings["databaseServerPort"], CultureInfo.InvariantCulture);
_db4oServer = Db4oClientServer.OpenServer(serverConfiguration, db4oDatabasePath + db4oDatabaseFileName, databaseServerPort);
string databaseUser = ConfigurationManager.AppSettings["databaseUser"];
string databasePassword = ConfigurationManager.AppSettings["databasePassword"];
_db4oServer.GrantAccess(databaseUser, databasePassword);
}
Hope this helps someone.
I had the same problem with Transparent Activation and Persistence in java. I managed to get it to work cleaning the database and starting from scratch. However, no works by calling commit() after changing the object graph. You must call store() on the root object.
This is a simple example:
/*************** Item.java ******************************************/
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Item implements Activatable {
private String name;
private transient Activator activator;
public Item(String name) {
this.name = name;
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Item [name=" + name + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ActivatableSupport.validateForBind(this.activator, activator);
}
}
/******************* Container.java *********************************/
import java.util.Set;
import com.db4o.activation.ActivationPurpose;
import com.db4o.activation.Activator;
import com.db4o.collections.ActivatableHashSet;
import com.db4o.collections.ActivatableSupport;
import com.db4o.ta.Activatable;
public class Container implements Activatable {
private String name;
private Set<Item> items;
private transient Activator activator;
public Container() {
items = new ActivatableHashSet<Item>();
}
public String getName() {
activate(ActivationPurpose.READ);
return name;
}
public void setName(String name) {
activate(ActivationPurpose.WRITE);
this.name = name;
}
public void addItem(Item item) {
activate(ActivationPurpose.WRITE);
items.add(item);
}
public Set<Item> getItems() {
activate(ActivationPurpose.READ);
return items;
}
#Override
public String toString() {
activate(ActivationPurpose.READ);
return "Container [items=" + items + "]";
}
public void activate(ActivationPurpose purpose) {
ActivatableSupport.activate(this.activator, purpose);
}
public void bind(Activator activator) {
this.activator = ctivatableSupport.validateForBind(this.activator, activator);
}
}
/************* Main.java ********************************************/
import com.db4o.Db4oEmbedded;
import com.db4o.ObjectContainer;
import com.db4o.ObjectSet;
import com.db4o.config.EmbeddedConfiguration;
import com.db4o.ta.TransparentActivationSupport;
import com.db4o.ta.TransparentPersistenceSupport;
public class Main {
public static void main() {
EmbeddedConfiguration config = Db4oEmbedded.newConfiguration();
config.common().add(new TransparentActivationSupport());
config.common().add(new TransparentPersistenceSupport());
ObjectContainer db = Db4oEmbedded.openFile(config, System.getProperty("user.home") + "/testTP.db4o");
Container c = new Container();
c.setName("Container0");
ObjectSet<Container> result = db.queryByExample(c);
if(result.hasNext()) {
c = result.next();
System.out.println(c);
}
c.addItem(new Item("Item" + c.getItems().size()));
db.store(c);
System.out.println(c);
db.close();
}
}