I've a simple rule like this:
rule "First Rule" //You do not talk about Fight Club
when
MyInp(id=="1")
then
insert(new MyOut(true));
end
What I want is, getting the created MyOut object from a Java class.
Is there a way to do this or do I have to pass a global variable and update it inside the rule?
With a stateless session you can modify a fact which you insert, or register a global which you update.
If you have a stateful session then there are a few more options:
Fetching fact from stateful session of drools
If you're looking for a general means of examining what existed in working memory during your stateless session,
StatelessKnowledgeSession ksession =
kbase.newStatelessKnowledgeSession();
TrackingWorkingMemoryEventListener listener =
new TrackingWorkingMemoryEventListener();
ksession.addEventListener(listener);
List<Object> facts = new ArrayList<Object>();
facts.add(myRequestFact);
ksession.execute(facts);
List<ObjectInsertedEvent> insertions = listener.getInsertions();
It's handy for debugging and audit purposes, but I wouldn't recommend it as a means of getting the actual results out of a request. Example code (by me) for a tracking WorkingMemoryEventListener can be found here:
https://github.com/gratiartis/sctrcd-payment-validation-web/blob/master/src/main/java/com/sctrcd/drools/util/TrackingWorkingMemoryEventListener.java
Related
I have a simple rule like the one below:
package rules
dialect "mvel"
declare MyEvent
#role( event )
#expires( 2d )
id String
value: Double
end
rule "My Rule"
when
MyEvent($value : value)
then
System.out.println("My event with value: " + $value);
end
I create a persistent session and call fireAllRules() on it. Then, I insert a MyEvent fact, and as expected the rule is evaluated, matched and the action is executed. If I call fireAllRules() again the rule is not matched, as expected because it has already matched for the same fact. At this point everything is fine.
Then I kill the JVM and run the app again. At startup the app loads the session like this:
kieSession = kieServices.getStoreServices().loadKieSession(KIE_SESSION_ID, kieBase, kieSessionConfiguration, kieEnvironment);
The session gets loaded successfully, and then fireAllRules() is called again. Since the rule has already matched for the inserted event, I am expecting that it does not match again. However I can see the message in the rule action is printed again. Why does Drools match the rule for the same eventagaian? To me it looks like the session state is not properly saved to database. I mean, the event is saved, but Drools can not recogonize that it has already matched the rule. When I load a persistent session I expect to recover exactly the same state that the session had in the previous running instance. Is my assumption wrong? or Am I doing something wrong for the expected behaviour?
Running:
JavaSE 11
SpringBoot 2.3
Drools 7.53.0
Setting:
One db with many tables, grouped by modules. The moduleTableGroups are independent, there are no relationships in between.
Design:
1: The modules have each there own Context, to keep them small and clear.
2: Because it is an very old Database with manual administration of PKeys and sequences it must be possible to nest SaveChanges, when handling those things in the context.
E.G. AContext has a table with an entity which has a ValueGenerator which reads an incremented value from a table. Because it should be finished before AContext.SaveChanges(), is called, it must be done in an seperate SeqContext in the same db which can handle the concurency issues which might occur.
The SeqContext depends on the DbOptionsContext of AContext.
Wishes:
1: Instead of create Options for each Context which is currently needed, becaus a context seems only to accept context specific options, I'd like to either use the same DbContextOptions for many context or copy or cast the options to a new .
DbContextOptions<AContext> aContextOptions = new DbContextOptionsBuilder<AContext>(). . .Options;
// casting:
DbContextOptions<BContext> bContextOptions = (DbContextOptions<BContext>) aContextOptions;
// copy construction:
DbContextOptions<BContext> bContextOptions = new DbContextOptionsBuilder<BContext>(aContextOption).Options;
2: Because of the dependency of SeqContext to AContext especially in testing scenarios it would be useful to do something from within a AContext like:
SeqContext = new SeqContext((DbContextOptions<SeqContext>) this.getOptions());
You might get the idea. How can I archive to share options between different TContext right now?
I don't need to save revision in some cases for entity, but how to handle this way? I want to just skip creation of revision, already reviewed source codes, no chance.
If u try to make DB changes in custom EnversPostInsertEventListenerImpl, for example, make calls in same transaction like that (in onPostInsert):
PersistedProperty property = dao.findClass(PersistedProperty.class, "where propertyTemplate.id = ? and fileEntry.id = ?",
propertyTemplate.getId(), f.getId());
String v = convertObject2String(propertyTemplate, value);
if (property == null) {
property = new PersistedProperty(propertyTemplate, v, f);
} else {
property.setValue(v);
}
dao.merge(property);
Then u will get next exception:
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
at java.util.ArrayList$Itr.next(ArrayList.java:851)
at java.util.Collections$UnmodifiableCollection$1.next(Collections.java:1042)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:587)
at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:463)
at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1435)
at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:491)
at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3201)
at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2411)
at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:467)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:146)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:220)
at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:517)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:761)
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:730)
at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:504)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:292)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:673)
Problem is deep repeat of same instance of ActionQueue in AbstractSaveEventListener (line 318) and final modification of ActionQueue by inner transaction.
In the documentation, there is a section called Conditional Auditing that discusses this.
The basic notion is you will need to override the Hibernate event listeners that Envers registers to handle triggering the auditing with your own custom implementations.
Your implementation will basically need to extend the Envers listeners and:
Check that the event is for the entity-type you're interested in skipping.
Check the modified state to see if only the condition that implies skipping occurred.
If you want to skip, simply don't delegate to the super implementation.
If you are using the ValidityAuditStrategy instead of the DefaultAuditStrategy, I would highly recommend you avoid using conditional auditing for INSERT operations.
Using conditional auditing on UPDATE operations from what I can tell will work, but doing so on the original inserts will cause the ADD row never to be added and thus the ValidityAuditStrategy will assert on the first UPDATE you attempt to audit for that entity row.
The goal in Hibernate 6 is to move beyond the need for users to manipulate the event listeners for conditional auditing and move toward a more JPA event listener annotation like approach. For more details, see HHH-11326.
I want Administrators to enable/disable logging at runtime by changing the enabled property of the LogEnabledFilter in the config.
There are several threads on SO that explain workarounds, but I want it this way.
I tried to change the Logging Enabled Filter like this:
private static void FileConfigurationSourceChanged(object sender, ConfigurationSourceChangedEventArgs e)
{
var fcs = sender as FileConfigurationSource;
System.Diagnostics.Debug.WriteLine("----------- FileConfigurationSourceChanged called --------");
LoggingSettings currentLogSettings = e.ConfigurationSource.GetSection("loggingConfiguration") as LoggingSettings;
var fdtl = currentLogSettings.TraceListeners.Where(tld => tld is FormattedDatabaseTraceListenerData).FirstOrDefault();
var currentLogFileFilter = currentLogSettings.LogFilters.Where(lfd => { return lfd.Name == "Logging Enabled Filter"; }).FirstOrDefault();
var filterNewValue = (bool)currentLogFileFilter.ElementInformation.Properties["enabled"].Value;
var runtimeFilter = Logger.Writer.GetFilter<LogEnabledFilter>("Logging Enabled Filter");
runtimeFilter.Enabled = filterNewValue;
var test = Logger.Writer.IsLoggingEnabled();
}
But test reveals always the initially loaded config value, it does not change.
I thought, that when changing the value in the config the changes will be propagated automatically to the runtime configuration. But this isn't the case!
Setting it programmatically as shown in the code above, doesn't work either.
It's time to rebuild Enterprise Library or shut it down.
You are right that the code you posted does not work. That code is using a config file (FileConfigurationSource) as the method to configure Enterprise Library.
Let's dig a bit deeper and see if programmatic configuration will work.
We will use the Fluent API since it is the preferred method for programmatic configuration:
var builder = new ConfigurationSourceBuilder();
builder.ConfigureLogging()
.WithOptions
.DoNotRevertImpersonation()
.FilterEnableOrDisable("EnableOrDisable").Enable()
.LogToCategoryNamed("General")
.WithOptions.SetAsDefaultCategory()
.SendTo.FlatFile("FlatFile")
.ToFile(#"fluent.log");
var configSource = new DictionaryConfigurationSource();
builder.UpdateConfigurationWithReplace(configSource);
var defaultWriter = new LogWriterFactory(configSource).Create();
defaultWriter.Write("Test1", "General");
var filter = defaultWriter.GetFilter<LogEnabledFilter>();
filter.Enabled = false;
defaultWriter.Write("Test2", "General");
If you try this code the filter will not be updated -- so another failure.
Let's try to use the "old school" programmatic configuration by using the classes directly:
var flatFileTraceListener = new FlatFileTraceListener(
#"program.log",
"----------------------------------------",
"----------------------------------------"
);
LogEnabledFilter enabledFilter = new LogEnabledFilter("Logging Enabled Filter", true);
// Build Configuration
var config = new LoggingConfiguration();
config.AddLogSource("General", SourceLevels.All, true)
.AddTraceListener(flatFileTraceListener);
config.Filters.Add(enabledFilter);
LogWriter defaultWriter = new LogWriter(config);
defaultWriter.Write("Test1", "General");
var filter = defaultWriter.GetFilter<LogEnabledFilter>();
filter.Enabled = false;
defaultWriter.Write("Test2", "General");
Success! The second ("Test2") message was not logged.
So, what is going on here? If we instantiate the filter ourselves and add it to the configuration it works but when relying on the Enterprise Library configuration the filter value is not updated.
This leads to a hypothesis: when using Enterprise Library configuration new filter instances are being returned each time which is why changing the value has no effect on the internal instance being used by Enterprise Library.
If we dig into the Enterprise Library code we (eventually) hit on LoggingSettings class and the BuildLogWriter method. This is used to create the LogWriter. Here's where the filters are created:
var filters = this.LogFilters.Select(tfd => tfd.BuildFilter());
So this line is using the configured LogFilterData and calling the BuildFilter method to instantiate the applicable filter. In this case the BuildFilter method of the configuration class LogEnabledFilterData BuildFilter method returns an instance of the LogEnabledFilter:
return new LogEnabledFilter(this.Name, this.Enabled);
The issue with this code is that this.LogFilters.Select returns a lazy evaluated enumeration that creates LogFilters and this enumeration is passed into the LogWriter to be used for all filter manipulation. Every time the filters are referenced the enumeration is evaluated and a new Filter instance is created! This confirms the original hypothesis.
To make it explicit: every time LogWriter.Write() is called a new LogEnabledFilter is created based on the original configuration. When the filters are queried by calling GetFilter() a new LogEnabledFilter is created based on the original configuration. Any changes to the object returned by GetFilter() have no affect on the internal configuration since it's a new object instance and, anyway, internally Enterprise Library will create another new instance on the next Write() call anyway.
Firstly, this is just plain wrong but it is also inefficient to create new objects on every call to Write() which could be invoked many times..
An easy fix for this issue is to evaluate the LogFilters enumeration by calling ToList():
var filters = this.LogFilters.Select(tfd => tfd.BuildFilter()).ToList();
This evaluates the enumeration only once ensuring that only one filter instance is created. Then the GetFilter() and update filter value approach posted in the question will work.
Update:
Randy Levy provided a fix in his answer above.
Implement the fix and recompile the enterprise library.
Here is the answer from Randy Levy:
Yes, you can disable logging by setting the LogEnabledFiter. The main
way to do this would be to manually edit the configuration file --
this is the main intention of that functionality (developers guide
references administrators tweaking this setting). Other similar
approaches to setting the filter are to programmatically modify the
original file-based configuration (which is essentially a
reconfiguration of the block), or reconfigure the block
programmatically (e.g. using the fluent interface). None of the
programmatic approaches are what I would call simple – Randy Levy 39
mins ago
If you try to get the filter and disable it I don't think it has any
affect without a reconfiguration. So the following code still ends up
logging: var enabledFilter = logWriter.GetFilter();
enabledFilter.Enabled = false; logWriter.Write("TEST"); One non-EntLib
approach would just to manage the enable/disable yourself with a bool
property and a helper class. But I think the priority approach is a
pretty straight forward alternative.
Conclusion:
In your custom Logger class implement a IsLoggenabled property and change/check this one at runtime.
This won't work:
var runtimeFilter = Logger.Writer.GetFilter<LogEnabledFilter>("Logging Enabled Filter");
runtimeFilter.Enabled = false/true;
My question is this: How can you implement a default server-side "filter" for a navigation property?
In our application we seldom actually delete anything from the database. Instead, we implement "soft deletes" where each table has a Deleted bit column. If this column is true the record has been "deleted". If it is false, it has not.
This allows us to easily "undelete" records accidentally deleted by the client.
Our current ASP.NET Web API returns only "undeleted" records by default, unless a deleted argument is sent as true from the client. The idea is that the consumer of the service doesn't have to worry about specifying that they only want undeleted items.
Implementing this same functionality in Breeze is quite simple, at least for base entities. For example, here would be the implementation of the classic Todo's example, adding a "Deleted" bit field:
// Note: Will show only undeleted items by default unless you explicitly pass deleted = true.
[HttpGet]
public IQueryable<BreezeSampleTodoItem> Todos(bool deleted = false) {
return _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
}
On the client, all we need to do is...
var query = breeze.EntityQuery.from("Todos");
...to get all undeleted Todos, or...
var query = breeze.EntityQuery.from("Todos").withParameters({deleted: true})
...to get all deleted Todos.
But let's say that a BreezeSampleTodoItem has a child collection for the tools that are needed to complete that Todo. We'll call this "Tools". Tools also implements soft deletes. When we perform a query that uses expand to get a Todo with its Tools, it will return all Tools - "deleted" or not.
But how can I filter out these records by default when Todo.Tools is expanded?
It has occurred to me to have separate Web API methods for each item that may need expanded, for example:
[HttpGet]
public IQueryable<Todo> TodoAndTools(bool deletedTodos = false, bool deletedTools = false)
{
return // ...Code to get filtered Todos with filtered Tools
}
I found some example code of how to do this in another SO post, but it requires hand-coding each property of Todo. The code from the above-mentioned post also returns a List, not an IQueryable. Furthermore this requires methods to be added for every possible expansion which isn't cool.
Essentially what I'm looking for is some way to define a piece of code that gets called whenever Todos is queried, and another for whenever Tools is queried - preferably being able to pass an argument that defines if it should return Deleted items. This could be anywhere on the server-side stack - be it in the Web API method, itself, or maybe part of Entity Framework (note that filtering Include extensions is not supported in EF.)
Breeze cannot do exactly what you are asking for right now, although we have discussed the idea of allowing the filtering of "expands", but we really need more feedback as to whether the community would find this useful. Please add this to the breeze User Voice and vote for it. We take these suggestions very seriously.
Moreover, as you point out, EF does not support this.
But... what you can do is use a projection instead of an expand to do something very similar:
public IQueryable<Object> TodoAndTools(bool deleted = false
,bool deletedTools = false) {
var baseQuery = _contextProvider.Context.Todos.Where(td => td.Deleted == deleted);
return baseQuery.Select(t => new {
Todo: t,
Tools: t.Tools.Where( tool => tool.Deleted = deletedTools);
});
}
Several things to note here:
1) We are returning an IQueryable of Object instead of IQueryable of ToDo
2) Breeze will inspect the returned payload and automatically create breeze entities for any 'entityTypes' returned (even within a projection). So the result of this query will be an array of javascript objects each with two properties; 'ToDo' and 'Tools' where Tools is an array of 'Tool' entities. The nice thing is that both ToDo and Tool entities returned within the projection will be 'full' breeze entities.
3) You can still pass client side filters based on the projected property names. i.e.
var query = EntityQuery.from("TodoAndTools")
.where("Todo.Description", "startsWith", "A")
.using(em);
4) EF does support this.