Spring webflux : webClient put call - rest

I have an account service and a product service communicating. When a request comes from a user to purchase a product (I did not include the user service, it is working fine and not the issue), the product service checks to see if there are enough funds in the account, and if there is it updates the balances. The following code works fine:
#GetMapping("/account/{userId}/product/{productId}")
public Mono<ResponseEntity<Product>> checkAccount(#PathVariable("userId") int userId,#PathVariable("productId") int productId){
Mono<Account> account = webClientBuilder.build().get().uri("http://account-service/user/accounts/{userId}/",userId)
.retrieve().bodyToMono(Account.class);
Mono<Product> product = this.ps.findById(productId);
Mono<Boolean> result = account.zipWith(product,this::isAccountBalanceGreater);
Mono<ResponseEntity<Product>> p = result.zipWith(product,this::getResponse);
return p;
}
public boolean isAccountBalanceGreater(Account acc, Product prd) {
return(acc.getBalance()>=prd.getPrice()):
}
public ResponseEntity<Product> getResponse(boolean result,Product prod){
if(result) {
return ResponseEntity.accepted().body(prod);
}else {
return ResponseEntity.badRequest().body(prod);
}
}
My put method in the account service also works fine:
#PutMapping("/account/update/{accountId}")
public Mono<ResponseEntity<Account>> updateAccount(#PathVariable("accountId") int accountId, #RequestBody Account account) {
return as.findById(accountId)
.flatMap(oldAcc->{
oldAcc.setAccountId(account.getAccountId());
oldAcc.setAccountId(account.getAccountId());
oldAcc.setOwner(account.getOwner());
oldAcc.setPin(account.getPin());
oldAcc.setBalance(account.getBalance());
oldAcc.setUserId(account.getUserId());
return ar.save(oldAcc);
}).map(a -> ResponseEntity.ok(a))
.defaultIfEmpty(ResponseEntity.notFound().build());
}
Now I want to be able to update the balances, I have tried this in the isAccountBalancerGreater method:
public boolean isAccountBalanceGreater(Account acc, Product prd) {
if(acc.getBalance() >= prd.getPrice()) {
double newBuyerBalance =acc.getBalance() - prd.getPrice();
Account newOwnerAcc = new Account(acc.getAccountId(),acc.getOwner(),acc.getPin(),newBuyerBalance,acc.getUserId());
this.ps.removeProduct(prd.getProductId());
webClientBuilder.build().put().uri("http://account-service/account/update/{accountId}",acc.getAccountId()).body(newOwnerAcc,Account.class).exchange();
return true;
}
return false;
}
However this does not work, not error just nothing updates.
My test case works when I run the same code with a test account. I'm not sure why this is not executing. Any suggestions?

you have to think of reactive code as event chains or callbacks. So you need to respond to what you want something to do, after some other thing has been completed.
return webClientBuilder.build()
.put().uri("http://account-service/account/update/{accountId}",
acc.getAccountId())
.body(newOwnerAcc,Account.class)
.exchange()
.thenReturn(true); // if you really need to return a boolean
return a boolean is usually not semantically correct in a reactive world. Its very common to try to avoid if-else statements
One way is to return a Mono<Void> to mark that something has been completed, and trigger something chained onto it.
public Mono<Void> isAccountBalanceGreater(Account acc, Product prd) {
return webclient.put()
.uri( ... )
.retrieve()
.bodyToMono(Void.class)
.doOnError( // handle error )
}
// How to call for example
isAccountBalanceGreater(foo, bar)
.doOnSuccess( ... )
.doOnError( ... )

Related

how to merge the response of webClient call after calling 5 times and save the complete response in DB

i have scenario like:
i have to check the table if entry is available in DB then if available i need to call the same external api n times using webclient, collect all the response and save them in DB. if entry is not available in DB call the old flow.
here is my implementation. need suggestions to improve it. without for-each
public Mono<List<ResponseObject>> getdata(String id, Req obj) {
return isEntryInDB(id) //checking the entry in DB
.flatMap(
x -> {
final List<Mono<ResponseObject>> responseList = new ArrayList<>();
IntStream.range(0, obj.getQuantity()) // quantity decides how many times api call t happen
.forEach(
i -> {
Mono<ResponseObject> responseMono =
webClientCall(
id,
req.getType())
.map(
res ->
MapperForMappingDataToDesriedFormat(res));
responseList.add(responseMono);
});
return saveToDb(responseList);
})
.switchIfEmpty(oldFlow(id, req)); //if DB entry is not there take this existing flow.
need some suggestions to improve it without using foreach.
I would avoid using IntStream and rather use native operator to reactor called Flux in this case.
You can replace, InsStream.range with Flux.range. Something like this:
return isEntryPresent("123")
.flatMapMany(s -> Flux.range(0, obj.getQuantity())
.flatMap(this::callApi))
.collectList()
.flatMap(this::saveToDb)
.switchIfEmpty(Mono.defer(() ->oldFlow(id, req)));
private Mono<Object> saveToDb(List<String> stringList){
return Mono.just("done");
}
private Mono<String> callApi(int id) {
return Mono.just("iterating" + id);
}
private Mono<String> isEntryPresent(String id) {
return Mono.just("string");
}

How to set up and use the Kin blockchain in a Unity app - Step by step

What is a good step by step explanation on how to use the Kin Unity SDK in an empty project in Unity?
Option 1: Use a pre-made wrapper
Use the wrapper in this 10 minute set up code (client and server) and call it as follows:
kinWrapper.SendPayment(1.00M, "test send", address);
kinWrapper.Balance();
//etc
Here is a detailed tutorial on implmenting the wrapper.
Option 2: Create your own wrapper
The Kin blockchain is a fork of the stellar protocol. As such, operations in your app will boil down to the following:
Creating accounts on the blockchain
Funding accounts on the blockchain
Sending payments
You can clone a sample implementation here - or you can follow the steps below to get a basic understanding.
Installation
Create an empty Unity project, and install the Kin Unity Plugin and modify your gradle file as described here.
NOTE: The SDK only runs on an Android/iOS device or emulator. The SDK will not run in the unity editor or unity remote -so you will need to compile and run the code in an emulator or device.
Implementation
Create an empty MonoBehaviour script - name it KinWrapper, and declare the Kin namespace:
using Kin;
You can also enable blockchain listeners by adding the following to your MonoBehaviour :
public class KinWrapper : MonoBehaviour, IPaymentListener, IBalanceListener
Instantiating
Next declare instances of the Kin Client and Kin Account
private KinClient kinClient;
private KinAccount kinAccount;
The client handles the native code depending on the platform you are using. (Android or iOS). The account is the main interface you will be using.
Creating an account
Before using the account, you first have to instantiate it through the client.
kinClient = new KinClient(Kin.Environment.Test, "appId");
if (!kinClient.HasAccount())
{
kinAccount = kinClient.AddAccount();
}else{
kinAccount = kinClient.GetAccount(0);
}
The code above first checks to see if a local account exists on the device. If it doesn't, it creates an account on the device and returns that as a reference.
NOTE: A 'created account' is simply a public key and private key generated on the device. These local keys enable the device to communicate with the blockchain.
You can now get the public key (client's blockchain address) by calling:
kinAccount.GetPublicAddress();
Onboarding/ Funding an account
Because this key only exists locally, you need to 'on-board' it to the blockchain. In other words, a special function needs to be called, to fund and register it online. Before this step is done, the address is considered invalid on the blockchain, and cannot receive or make transactions. This is part of the stellar protocol to avoid spam and unnecessary account creation.
You can check if the account has been on-boarded and funded by calling the following function:
kinAccount.GetStatus(GetStatusCallback)
void GetStatusCallback(KinException ex, AccountStatus status)
{
if (status == AccountStatus.Created)
{
}
else
{
}
}
To on-board and fund an account on the test blockchain, you have 3 options:
Option 1: You can do this manually by pasting the address on Kin's friend-bot service. This will on-board the account for you automatically.
Option 2: You can call the friend-bot through your code as an http request
https://friendbot-testnet.kininfrastructure.com/?addr=address_here
public static IEnumerator FundAccount( string publicAddress, Action<bool> onComplete = null )
{
var url = "http://friendbot-testnet.kininfrastructure.com/?addr=" + publicAddress;
var req = UnityWebRequest.Get( url );
yield return req.SendWebRequest();
if( req.isNetworkError || req.isHttpError )
{
Debug.Log( req.error );
if( onComplete != null )
onComplete( false );
}
else
{
Debug.Log( "response code: " + req.responseCode );
Debug.Log( req.downloadHandler.text );
if( onComplete != null )
onComplete( true );
}
}
Option 3: You can use server side code to fund it yourself (which you must do in a production environment). Basically, your client will need to call your server and request to be on-boarded. Your server can then use Kin's python SDK or node SDK to perform the on-boarding.
Here is a handy python implementation or Node.js implementation you can use for your server.
Calling the implementation from Unity is as simple as follows:
public IEnumerator FundMe(decimal amount, Action<bool> fundCallback = null)
{
WWWForm form = new WWWForm();
form.AddField("address", kinAccount.GetPublicAddress());
form.AddField("amount", amount.ToString());
reqUrl = "http://address.to.your.server";
var req = UnityWebRequest.Post(reqUrl, form);
yield return req.SendWebRequest();
if (req.isNetworkError || req.isHttpError)
{
Debug.LogError(req.error);
fundCallback(false);
}
else
{
fundCallback(true);
}
}
NOTE: Kin has two blockchains. Production and Test. The test blockchain merely has "play currency" that you can run tests on without incurring costs. The production blockchain has real world value Kin
Calling Other Functions
GetBalance()
account.GetBalance(GetBalanceCallback);
void GetBalanceCallback(KinException ex, decimal balance)
{
if (ex == null)
{
Debug.Log( "Balance: " + balance );
}
else
{
Debug.LogError( "Get Balance Failed. " + ex );
}
}
DeleteAccount()
kinClient.DeleteAccount(0);
NOTE: GetBalance() needs a callback because it's communicating with the blockchain online. DeleteAccount() doesn't because it simply deletes the private and public keys on the client. Once deleted, these keys can not be recovered.
Handling transactions
Transactions have the following parameters:
Memo: an optional string with extra information.
Address: the address of the recipient
Amount: amount of Kin you are sending (excluding fees)
Fee: the fee you will pay for your transaction to be processed by the blockchain. The current fee is 100/100000 KIN. This fee protects the blockchain from spam.
NOTE: The fee is denominated in the smallest unit of Kin. (Quark). Just like the smallest unit of a dollar is a cent. So when calling a transaction, set the fee to 100 (quarks) - which is 100/100000 KIN. Setting the fee tells the blockchain you are explicitly agreeing to pay the transaction fee. If you set the fee too low, your transaction will be rejected.
You can view all transactions on Kin's blockchain at: https://laboratory.kin.org/index.html#explorer?resource=payments
To send a payment, first build the transaction locally :
kinAccount.BuildTransaction(address, amount, fee, memo, BuildTransactionCallBack);
Building a transaction performs several operations on the client to authorise it and sign it. After the transaction is built, use the following callback to send it to Kin's blockchain.
void BuildTransactionCallBack(KinException ex, Transaction transaction)
{
if (ex == null)
{
kinAccount.SendTransaction(transaction, SendTransactionCallback);
}
else
{
Debug.LogError("Build Transaction Failed. " + ex);
}
}
Once you have sent the transaction to Kin's blockchain, you can listen for the response, to make sure it went through correctly. A transaction typically takes less than 10 seconds to complete.
void SendTransactionCallback(KinException ex, String transactionId)
{
if (ex == null)
{
//Success
}
else
{
Debug.LogError("Send Transaction Failed. " + ex);
}
}
NOTE: You should use co-routines for functions that need to wait. This will prevent your code from hanging while waiting for responses, and will create a better user experience.
Sending transactions with zero fees
You can send transactions with zero fees by being whitelisted by the Kin Foundation. To get whitelisted, simply register with the Kin Developer Program. Once approved, you can perform an extra step to let your client send transactions with zero fees.
To send a zero-fee transaction after building the transaction:
The client will need to contact your whitelisted server.
Your server will approve/ sign the transaction and send it back to the client.
The client will then send this approved transaction to Kin's blockchain, and it will be processed for free.
We have already built the transaction, now we whitelist it
void BuildTransactionCallBack(KinException ex, Transaction transaction)
{
if (ex == null)
{
StartCoroutine(WhitelistTransaction(transaction, WhitelistTransactionCallback))
}
else
{
Debug.LogError("Build Transaction Failed. " + ex);
}
}
We are calling our whitelisted server to authorise the transaction
IEnumerator WhitelistTransaction(Transaction transaction, Action<string, string> onComplete)
{
var postDataObj = new WhitelistPostData(transaction);
var postData = JsonUtility.ToJson(postDataObj);
var rawPostData = Encoding.UTF8.GetBytes(postData);
// UnityWebRequest does not work correclty when posting a JSON string so we use a byte[] and a hacky workaround
var req = UnityWebRequest.Post(baseURL + whitelistURL, "POST");
req.SetRequestHeader("Content-Type", "application/json");
req.uploadHandler = new UploadHandlerRaw(rawPostData);
yield return req.SendWebRequest();
if (req.isNetworkError || req.isHttpError)
{
Debug.LogError(req.error);
onComplete(null, null);
}
else
{
onComplete(req.downloadHandler.text, transaction.Id);
}
}
Our server has approved the transaction and sent back a signed string. We now send this signed string to Kin's blockchain
void WhitelistTransactionCallback(string whitelistTransaction, string transactionId)
{
if (whitelistTransaction != null)
{
kinAccount.SendWhitelistTransaction(transactionId, whitelistTransaction, SendTransactionCallback);
}
else
{
Debug.LogError("Whitelisting Transaction Failed. ");
}
}
As usual, we wait to see if the transaction was successful
void SendTransactionCallback(KinException ex, String transactionId)
{
if (ex == null)
{
//Success
}
else
{
Debug.LogError("Send Transaction Failed. " + ex);
}
}
Before your server has been approved for whitelisting, you can use this pre-approved server in the TEST environment. http://34.239.111.38:3000/whitelist.
public static IEnumerator WhitelistTransaction( Transaction transaction, Action<string> onComplete = null )
{
var postDataObj = new WhitelistPostData( transaction );
var postData = JsonUtility.ToJson( postDataObj );
var rawPostData = Encoding.UTF8.GetBytes( postData );
// UnityWebRequest does not work correclty when posting a JSON string so we use a byte[] and a hacky workaround
var url = "http://34.239.111.38:3000/whitelist";
var req = UnityWebRequest.Post( url, "POST" );
req.SetRequestHeader( "Content-Type", "application/json" );
req.uploadHandler = new UploadHandlerRaw( rawPostData );
yield return req.SendWebRequest();
if( req.isNetworkError || req.isHttpError )
{
Debug.Log( req.error );
if( onComplete != null )
onComplete( null );
}
else
{
Debug.Log( "response code: " + req.responseCode );
Debug.Log( req.downloadHandler.text );
if( onComplete != null )
onComplete( req.downloadHandler.text );
}
}
Once approved, you can use the sample python implementation or Node.js implementation to approve transactions on your server. You can also use the node.js SDK.
Listening to events on the blockchain
You can add listeners to prevent your client from over-querying the blockchain. Simply add the following code
kinAccount.AddPaymentListener(this);
kinAccount.AddBalanceListener(this);
public void OnEvent(PaymentInfo data)
{
//Listening for incoming and outgoing payments
}
public void OnEvent(decimal balance)
{
//Listening for changes in client's balance
}
Putting it all together
You can find this code implemented on: https://github.com/hitwill/kin-sdk-unity-tutorial under the MIT license.
Developer Playbook
You can also check this Developer Playbook - for some higher level concepts of creating with Kin.
Best practices
UX
(placeholder)
Server side security
Here is a great writeup on server side security.
Client side security
(placeholder)
Reducing calls to the blockchain
(placeholder)

Non-blocking functional methods with Reactive Mongo and Web client

I have a micro service which reads objects from a database using a ReactiveMongoRepository interface.
The goal is to take each one of those objects and push it to a AWS Lambda function (after converting it to a DTO). If the result of that lambda function is in the 200 range, mark the object as being a success otherwise ignore.
In the old days of a simple Mongo Repository and a RestTemplate this is would be a trivial task. However I'm trying to understand this Reactive deal, and avoid blocking.
Here is the code I've come up with, I know I'm blocking on the webClient, but how do I avoid that?
#Override
public Flux<Video> index() {
return videoRepository.findAllByIndexedIsFalse().flatMap(video -> {
final SearchDTO searchDTO = SearchDTO.builder()
.name(video.getName())
.canonicalPath(video.getCanonicalPath())
.objectID(video.getObjectID())
.userId(video.getUserId())
.build();
// Blocking call
final HttpStatus httpStatus = webClient.post()
.uri(URI.create(LAMBDA_ENDPOINT))
.body(BodyInserters.fromObject(searchDTO)).exchange()
.block()
.statusCode();
if (httpStatus.is2xxSuccessful()) {
video.setIndexed(true);
}
return videoRepository.save(video);
});
}
I'm calling the above from a scheduled task, and I don't really care about that actual result of the index() method, just what happens during.
#Scheduled(fixedDelay = 60000)
public void indexTask() {
indexService
.index()
.log()
.subscribe();
}
I've read a bunch of blog posts etc on the subject but they're all just simple CRUD operations without anything happening in the middle so don't really give me a full picture of how to implement these things.
Any help?
Your solution is actually quite close.
In those cases, you should try and decompose the reactive chain in steps and not hesitate to turn bits into independent methods for clarity.
#Override
public Flux<Video> index() {
Flux<Video> unindexedVideos = videoRepository.findAllByIndexedIsFalse();
return unindexedVideos.flatMap(video -> {
final SearchDTO searchDTO = SearchDTO.builder()
.name(video.getName())
.canonicalPath(video.getCanonicalPath())
.objectID(video.getObjectID())
.userId(video.getUserId())
.build();
Mono<ClientResponse> indexedResponse = webClient.post()
.uri(URI.create(LAMBDA_ENDPOINT))
.body(BodyInserters.fromObject(searchDTO)).exchange()
.filter(res -> res.statusCode().is2xxSuccessful());
return indexedResponse.flatMap(response -> {
video.setIndexed(true);
return videoRepository.save(video);
});
});
my approach, maybe a little bit more readable. But I admit I didn't run it so not 100% guarantee that it will work.
public Flux<Video> index() {
return videoRepository.findAll()
.flatMap(this::callLambda)
.flatMap(videoRepository::save);
}
private Mono<Video> callLambda(final Video video) {
SearchDTO searchDTO = new SearchDTO(video);
return webClient.post()
.uri(URI.create(LAMBDA_ENDPOINT))
.body(BodyInserters.fromObject(searchDTO))
.exchange()
.map(ClientResponse::statusCode)
.filter(HttpStatus::is2xxSuccessful)
.map(t -> {
video.setIndexed(true);
return video;
});
}

Repeat Single based on onSuccess() value

I want to repeat a Single based on the single value emitted in onSuccess(). Here is a working example
import org.reactivestreams.Publisher;
import io.reactivex.Flowable;
import io.reactivex.Single;
import io.reactivex.functions.Function;
public class Temp {
void main() {
Job job = new Job();
Single.just(job)
.map(this::processJob)
.repeatWhen(new Function<Flowable<Object>, Publisher<?>>() {
#Override
public Publisher<?> apply(Flowable<Object> objectFlowable) throws Exception {
// TODO repeat when Single emits false
return null;
}
})
.subscribe();
}
/**
* returns true if process succeeded, false if failed
*/
boolean processJob(Job job) {
return true;
}
class Job {
}
}
I understand how repeatWhen works for Observables by relying on the "complete" notification. However since Single doesn't receive that notification I'm not sure what the Flowable<Object> is really giving me. Also why do I need to return a Publisher from this function?
Instead of relying on a boolean value, you could make your job throw an exception when it fails:
class Job {
var isSuccess: Boolean = false
}
fun processJob(job: Job): String {
if (job.isSuccess) {
return "job succeeds"
} else {
throw Exception("job failed")
}
}
val job = Job()
Single.just(job)
.map { processJob(it) }
.retry() // will resubscribe until your job succeeds
.subscribe(
{ value -> print(value) },
{ error -> print(error) }
)
i saw a small discrepancy in the latest docs and your code, so i did a little digging...
(side note - i think the semantics of retryWhen seem like the more appropriate operator for your case, so i've substituted it in for your usage of repeatWhen. but i think the root of your problem remains the same in either case).
the signature for retryWhen is:
retryWhen(Function<? super Flowable<Throwable>,? extends Publisher<?>> handler)
that parameter is a factory function whose input is a source that emits anytime onError is called upstream, giving you the ability to insert custom retry logic that may be influenced through interrogation of the underlying Throwable. this begins to answer your first question of "I'm not sure what the Flowable<Object> is really giving me" - it shouldn't be Flowable<Object> to begin with, it should be Flowable<Throwable> (for the reason i just described).
so where did Flowable<Object> come from? i managed to reproduce IntelliJ's generation of this code through it's auto-complete feature using RxJava version 2.1.17. upgrading to 2.2.0, however, produces the correct result of Flowable<Throwable>. so, see if upgrading to the latest version generates the correct result for you as well.
as for your second question of "Also why do I need to return a Publisher from this function?" - this is used to determine if re-subscription should happen. if the factory function returns a Publisher that emits a terminal state (ie calls onError() or onComplete()) re-subscription will not happen. however, if onNext() is called, it will. (this also explains why the Publisher isn't typed - the type doesn't matter. the only thing that does matter is what kind of notification it publishes).
another way to rewrite this, incorporating the above, might be as follows:
// just some type to use as a signal to retry
private class SpecialException extends RuntimeException {}
// job processing results in a Completable that either completes or
// doesn't (by way of an exception)
private Completable rxProcessJob(Job job) {
return Completable.complete();
// return Completable.error(new SpecialException());
}
...
rxProcessJob(new Job())
.retryWhen(errors -> {
return errors.flatMap(throwable -> {
if(throwable instanceof SpecialException) {
return PublishProcessor.just(1);
}
return PublishProcessor.error(throwable);
});
})
.subscribe(
() -> {
System.out.println("## onComplete()");
},
error -> {
System.out.println("## onError(" + error.getMessage() + ")");
}
);
i hope that helps!
The accepted answer would work, but is hackish. You don't need to throw an error; simply filter the output of processJob which converts the Single to a Maybe, and then use the repeatWhen handler to decide how many times, or with what delay, you may want to resubscribe. See Kotlin code below from a working example, you should be able to easily translate this to Java.
filter { it }
.repeatWhen { handler ->
handler.zipWith(1..3) { _, i -> i }
.flatMap { retryCount -> Flowable.timer(retryDelay.toDouble().pow(retryCount).toLong(), TimeUnit.SECONDS) }
.doOnNext { log.warn("Retrying...") }
}

Test Event expiration in Drools Fusion CEP

Ciao, I have tested in several ways, but I'm still unable to test and verify the Event expiration mechanism in Drools Fusion, so I'm looking for some little guidance, please?
I've read the manual and I'm interested in this feature:
In other words, one an event is inserted into the working memory, it is possible for the engine to find out when an event can no longer match other facts and automatically retract it, releasing its associated resources.
I'm using the Drools IDE in Eclipse, 5.4.0.Final and I modified the template code created by the "New Drools Project" wizard to test and verify for Event expiration.
The code below. The way I understood to make the "lifecycle" to work correctly is that:
You must setup the KBase in STREAM mode - check
You must Insert the Events in temporal order - check
You must define temporal constraints between Events - check in my case is last Message()
However, when I inspect the EventFactHandle at the end, none of the Event() has expired.
Thanks for your help.
Java:
public class DroolsTest {
public static final void main(String[] args) {
try {
KnowledgeBase kbase = readKnowledgeBase();
// I do want the pseudo clock
KnowledgeSessionConfiguration conf = KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
conf.setOption(ClockTypeOption.get("pseudo"));
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(conf, null);
SessionPseudoClock clock = ksession.getSessionClock();
KnowledgeRuntimeLogger logger = KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "test");
// Insert of 2 Event:
Message message = new Message();
message.setMessage("Message 1");
message.setStatus(Message.HELLO);
ksession.insert(message);
ksession.fireAllRules();
clock.advanceTime(1, TimeUnit.DAYS);
Message message2 = new Message();
message2.setMessage("Message 2");
message2.setStatus(Message.HELLO);
ksession.insert(message2);
ksession.fireAllRules();
clock.advanceTime(1, TimeUnit.DAYS);
ksession.fireAllRules();
// Now I do check what I have in the working memory and if EventFactHandle if it's expired or not:
for (FactHandle f : ksession.getFactHandles()) {
if (f instanceof EventFactHandle) {
System.out.println(((EventFactHandle)f)+" "+((EventFactHandle)f).isExpired());
} else {
System.out.println("not an Event: "+f);
}
}
logger.close();
} catch (Throwable t) {
t.printStackTrace();
}
}
private static KnowledgeBase readKnowledgeBase() throws Exception {
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add(ResourceFactory.newClassPathResource("Sample.drl"), ResourceType.DRL);
KnowledgeBuilderErrors errors = kbuilder.getErrors();
if (errors.size() > 0) {
for (KnowledgeBuilderError error: errors) {
System.err.println(error);
}
throw new IllegalArgumentException("Could not parse knowledge.");
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
// following 2 lines is the template code modified for STREAM configuration
KnowledgeBaseConfiguration config = KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
config.setOption( EventProcessingOption.STREAM );
return kbase;
}
/*
* This is OK from template, as from the doc:
* By default, the timestamp for a given event is read from the Session Clock and assigned to the event at the time the event is inserted into the working memory.
*/
public static class Message {
public static final int HELLO = 0;
public static final int GOODBYE = 1;
private String message;
private int status;
public String getMessage() {
return this.message;
}
public void setMessage(String message) {
this.message = message;
}
public int getStatus() {
return this.status;
}
public void setStatus(int status) {
this.status = status;
}
}
}
Drools:
package com.sample
import com.sample.DroolsTest.Message;
declare Message
#role(event)
end
declare window LastMessageWindow
Message() over window:length(1)
end
rule "Hello World"
when
accumulate( $m : Message(status==Message.HELLO) from window LastMessageWindow,
$messages : collectList( $m ) )
then
System.out.println( ((Message)$messages.get(0)).getMessage() );
end
Please note: even if I add expiration of 1second to the Message event, by
#expires(1s)
I still don't get the expected result that the very first Message event inserted, I would have expected is now expired? Thanks for your help.
Found solution! Obviously it was me being stupid and not realizing I was using Drools 5.4.0.Final while still referring to old documentation of 5.2.0.Final. In the updated documentation for Drools Fusion 5.4.0.Final, this box is added for 2.6.2. Sliding Length Windows:
Please note that length based windows do not define temporal constraints for event expiration from the session, and the engine will not consider them. If events have no other rules defining temporal constraints and no explicit expiration policy, the engine will keep them in the session indefinitely.
Therefore the 3rd requirement I originally enlisted of "You must define temporal constraints between Events" is obviously NOT met because I now understand Sliding Length Window in Drools 5.4.0.Final:
Message() over window:length(1)
are indeed NOT a definition of a temporal constraints for event expiration from the session.
Updating this answer hopefully somebody will find it helpful. Also, just so for your know, me being stupid actually for relying on googling in order to reach the doc, and sometimes you don't get redirected to the current release documentation, so it seems...