I am having some problems with Spring statemachine + Spring boot application.
I am using Redis for persistence, as in Event service demo. I have one action which is executed when state machine enters state 'VALIDATION'. In it, I modify extended state variables. Code example below is simplified, but tested:
This is main, calling method:
public void feedMachine(String event, int orderItem) throws Exception {
System.out.println("DEBUG FEEDMACHINE STARTED");
StateMachine<String, String> machine = persister.restore(stateMachine, orderItem);
machine.sendEvent(event);
System.out.println("DEBUG FEEDMACHINE... machine = " + machine.getUuid());
System.out.println("DEBUG FEEDMACHINE... variables = " + machine.getExtendedState().getVariables());
if (machine.getExtendedState().getVariables().containsKey("guard")
&& !machine.getExtendedState().get("guard", Boolean.class)) {
System.out.println("DEBUG FEEDMACHINE... guard = " + machine.getExtendedState().get("guard", Boolean.class));
throw new GuardedEventException();
}
persister.persist(machine, orderItem);
System.out.println("DEBUG FEEDMACHINE ENDED");
}
This is action:
#Bean
public Action<String, String> CustomerOrderValidatedEntryAction() {
return new Action<String, String>() {
#Override
public void execute(StateContext context) {
System.out.println("DEBUG ACTION STARTED");
System.out.println("DEBUG ACTION... machine = " + context.getStateMachine().getUuid());
int orderItem = context.getStateMachine().getExtendedState().get(ORDER_ITEM, Integer.class);
context.getExtendedState().getVariables().put("guard", false);
System.out.println("DEBUG ACTION... variables = " + context.getExtendedState().getVariables());
persister.persist(context.getStateMachine(), key);
System.out.println("DEBUG ACTION ENDED");
}
catch (Exception e) {
e.printStackTrace();
}
}
};
}
When I leave action, in feedMachine extendedState.getVariables does not contain guard and errors variable.
This is output:
DEBUG FEEDMACHINE STARTED
2016-08-16 11:46:17.101 INFO 26483 --- [nio-8081-exec-2] o.s.s.support.LifecycleObjectSupport : stopped org.springframework.statemachine.support.DefaultStateMachineExecutor#a85eba8
2016-08-16 11:46:17.102 INFO 26483 --- [nio-8081-exec-2] o.s.s.support.LifecycleObjectSupport : stopped ... / / uuid=336abc3e-708e-46ad-892f-5835b50713c8 / id=null
2016-08-16 11:46:17.102 INFO 26483 --- [nio-8081-exec-2] o.s.s.support.LifecycleObjectSupport : started org.springframework.statemachine.support.DefaultStateMachineExecutor#a85eba8
2016-08-16 11:46:17.102 INFO 26483 --- [nio-8081-exec-2] o.s.s.support.LifecycleObjectSupport : started ... / ORDER_RECEIVED / uuid=336abc3e-708e-46ad-892f-5835b50713c8 / id=null
DEBUG ACTION STARTED
DEBUG ACTION... machine = 336abc3e-708e-46ad-892f-5835b50713c8
DEBUG ACTION... guard = false
DEBUG ACTION... variables = {orderItem=0, guard=false}
DEBUG ACTION ENDED
DEBUG FEEDMACHINE... machine = 336abc3e-708e-46ad-892f-5835b50713c8
DEBUG FEEDMACHINE... variables = {orderItem=0}
DEBUG FEEDMACHINE ENDED
Any help would be appreciated. I am not sure if this is connected with Spring statemachine or with Spring beans or something else.
I apologize for primitive code (using println instead of log.debug etc.).
EDIT:
I finally found out where was the problem.
I was using in action context.getExtendedState().getVariables().put("guard", valid); instead of context.getStateMachine().getExtendedState().getVariables().put("guard", valid);. Now it works as expected. Problem was that those two variables lists were not same.
context.getExtendedState()'s variables list would get updated with "guard"
context.getStateMachine().getExtendedState()'s variables list would not get updated.
However, after creating minimal working example (only 3 states instead of 15+, no nested states), it was working correctly with context.getExtendedState().getVariables().put("guard", valid);.
I also checked several days ago whether context.getExtendedState() and context.getStateMachine().getExtendedState() are same, and I concluded that they were.
This seems to me as a bug.
Related
I have a controller:
#GetMapping("/startRoute")
#ApiOperation(value = "startDRoute",
response = ResponseEntity.class)
public ResponseEntity<BaseResponse<Boolean>> startRoute() {
logger.info("ReconciliationController - startDaily called");
ReconciliationBatchRequest reconciliationBatchRequest = new ReconciliationBatchRequest();
reconciliationBatchRequest.setDocumentTypeOfJob(0L);//hardcoded change
producerTemplate
.asyncSendBody(DOCUMENT_RECONCILIATION_ROUTE_START,
reconciliationBatchRequest);
logger.info("ReconciliationController - startDaily ended");
return ResponseEntity.ok(new BaseResponse<>(true));
}
it starts the camel route :
#Override
public void configure() {
logger.info("DocumentReconciliationRoute configure started");
onException(Exception.class)
.process(reconciliationExceptionProcessor)
// .handled(true).maximumRedeliveries(3).redeliveryDelay(0)
;
from("direct:DocumentReconciliationRoute.start")
.log(LoggingLevel.INFO, DOCUMENT_RECONCILIATION_ROUTE, "Daily camel route started")
.routeId("reconciliationCamelRoute")
.process(createFTPExpressionProcessor)
.to(GET_FILE_FROM_SFTP)
.log(LoggingLevel.INFO, DOCUMENT_RECONCILIATION_ROUTE, "Daily camel route ended")
// .end()
Beause it comes from controller async, it can be called multiple times. But i dont want this. It should not work if there is one running route.
I created this to stop if there is active route:
#GetMapping("/stop")
#ApiOperation(value = "stop",
notes = "For stopping reconciliation",
response = ResponseEntity.class)
public ResponseEntity<String> stop() throws Exception {
logger.info("ReconciliationController - stop called");
camelContext.stopRoute("reconciliationCamelRoute");
logger.info("ReconciliationController - stop ended");
return ResponseEntity.ok("ok");
}
but if there is runnin route, this does not stop. It waits :
Starting to graceful shutdown 1 routes (timeout 300 seconds)
How can I make only one instance is working even multiple people calls that controller endpoind?
I tried this:
boolean isRouteStarted =
((org.apache.camel.support.ServiceSupport)route).isStarted();
but is this safe?
Im running an embedded Debezium (1.2.0) in a Spring application, but it only captures changes when starting up
My setup looks like this:
final Properties props = new Properties();
props.setProperty("name", "engine");
props.setProperty("connector.class", "io.debezium.connector.sqlserver.SqlServerConnector");
props.setProperty("offset.storage", "org.apache.kafka.connect.storage.FileOffsetBackingStore");
props.setProperty("offset.storage.file.filename", "/tmp/offsets.dat");
props.setProperty("offset.flush.interval.ms", "60000");
/* begin connector properties */
props.setProperty("database.hostname", "xxxx");
props.setProperty("database.port", "xxxx");
props.setProperty("database.user", "xxxx");
props.setProperty("database.password", "xxxx");
props.setProperty("database.server.id", "xxxx");
props.setProperty("database.server.name", "xxxx");
props.setProperty("database.dbname", "xxxx");
props.setProperty("database.history", "io.debezium.relational.history.FileDatabaseHistory");
props.setProperty("database.history.file.filename", "~logs/dbhistory.dat");
props.setProperty("snapshot.lock.timeout.ms", "-1");
try (DebeziumEngine<ChangeEvent<String, String>> engine = DebeziumEngine.create(Json.class)
.using(props)
.notifying(this::handleEvent)
.build()) {
// Run the engine asynchronously ...
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(engine);
// Do something else or wait for a signal or an event
} catch (IOException | InterruptedException e) {
logger.error("Unable to start debezium " + e);
}
private void handleEvent(ChangeEvent<String, String> changeEvent) {
logger.info(changeEvent.toString());
}
When i boot the application it captures the latest changes, but ends with
INFO i.d.p.ChangeEventSourceCoordinator - Finished streaming
INFO i.d.p.m.StreamingChangeEventSourceMetrics - Connected metrics set to 'false'
Then no subsequently changes are captured until next application restart
No errors are thrown
you must not leave the try block as this would close the engine and stop the streaming. So in place of the comment // Do something else or wait for a signal or an event must be somewaiting logic or you should not place engine into try with resources for automated sopping.
We are having an issue unit test failing because previous tests haven't closed session of HttpSelfHostServer.
So the second time we try to open a connection to a sever we get this message:
System.InvalidOperationException : A registration already exists for URI 'http://localhost:1337/'.
This test forces the issue (as an example):
[TestFixture]
public class DuplicatHostIssue
{
public HttpSelfHostServer _server;
[Test]
public void please_work()
{
var config = new HttpSelfHostConfiguration("http://localhost:1337/");
_server = new HttpSelfHostServer(config);
_server.OpenAsync().Wait();
config = new HttpSelfHostConfiguration("http://localhost:1337/");
_server = new HttpSelfHostServer(config);
_server.OpenAsync().Wait();
}
}
So newing up a new instance of the server dosent seem to kill the previous session. Any idea how to force the desposal of the previous session?
Full exception if it helps?
System.AggregateException : One or more errors occurred. ----> System.InvalidOperationException : A registration already exists for URI 'http://localhost:1337/'.
at
System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait()
at ANW.API.Tests.Acceptance.DuplicatHostIssue.please_work() in DuplicatHostIssue.cs: line 32
--InvalidOperationException
at System.Runtime.AsyncResult.End(IAsyncResult result)
at System.ServiceModel.Channels.CommunicationObject.EndOpen(IAsyncResult result)
at System.Web.Http.SelfHost.HttpSelfHostServer.OpenListenerComplete(IAsyncResult result)
You might want to write a Dispose method like below and call it appropriately to avoid this issue
private static void HttpSelfHostServerDispose()
{
if (server != null)
{
_server.CloseAsync().Wait();
_server.Dispose();
_server = null;
}
}
This will clear the URI register.
In my WebApi code, I raise a HttpResponseException which short-circuits the request pipeline and generates a valid Http response. However, I'm trying to integrate webApi with elmah logging, yet the HttpResponseExeptions aren't showing up.
I have the web.config set-up for elmah and have the following code:
In Global.asx.cs:
static void ConfigureWebApi(HttpConfiguration config)
{
config.Filters.Add(new ServiceLayerExceptionFilter());
config.Filters.Add(new ElmahHandledErrorLoggerFilter());
config.DependencyResolver = new WebApiDependencyResolver(ObjectFactory.Container);
}
Filter:
public class ElmahHandledErrorLoggerFilter : ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext actionExecutedContext)
{
base.OnException(actionExecutedContext);
ErrorSignal.FromCurrentContext().Raise(actionExecutedContext.Exception);
}
}
Code where exception is raised:
public Task<FileUpModel> UploadFile()
{
if (Request.Content.IsMimeMultipartContent())
{
var provider = new TolMobileFormDataStreamProvider("C:\images\");
var task = Request.Content.ReadAsMultipartAsync(provider).ContinueWith(
t =>
{
if (t.IsFaulted || t.IsCanceled)
throw new HttpResponseException(HttpStatusCode.InternalServerError);
var fileInfo = provider.FileData.FirstOrDefault();
if (fileInfo == null)
// the exception here isn't logged by Elmah?!
throw new HttpResponseException(HttpStatusCode.InternalServerError);
var uploadModel = new FileUpModel { success = true };
return uploadModel;
});
return task;
}
else
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "This request is not properly formatted"));
}
}
Can anyone who has implemented this before let me know what I'm doing wrong?
As mentioned above, the Elmah filter does not catch and log anything when you raise a HttpResponseException. More specifically, if the following syntax is used:
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "It was a bad request");
or
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotAcceptable, "HttpResponseException - This request is not properly formatted"));
I wanted to trap and log an error in both cases. The way to do it is to use an "ActionFilterAttribute", override "OnActionExecuted", and check actionExecutedContext.Response.IsSuccessStatusCode.
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
// when actionExecutedContext.Response is null, the error will be caught and logged by the Elmah filter
if ((actionExecutedContext.Response != null) && !actionExecutedContext.Response.IsSuccessStatusCode)
{
try
{
var messages = (System.Web.Http.HttpError)((System.Net.Http.ObjectContent<System.Web.Http.HttpError>)actionExecutedContext.Response.Content).Value;
StringBuilder stringBuilder = new StringBuilder();
foreach (var keyValuePair in messages) {
stringBuilder.AppendLine("Message: Key - " + keyValuePair.Key + ", Value - " + keyValuePair.Value);
}
Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Web API Failed Status Code returned - " + stringBuilder.ToString()));
}
catch (Exception ex)
{
Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Error in OnActionExecuted - " + ex.ToString()));
}
}
}
On a side note, I also overwrote "OnActionExecuting" to validate the model state. This allowed me to remove all of the checks within my actions.
public override void OnActionExecuting(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.ModelState != null && !actionContext.ModelState.IsValid)
{
StringBuilder stringBuilder = new StringBuilder();
foreach (var obj in actionContext.ModelState.Values)
{
foreach (var error in obj.Errors)
{
if(!string.IsNullOrEmpty(error.ErrorMessage)) {
stringBuilder.AppendLine("Error: " + error.ErrorMessage);
}
}
}
Elmah.ErrorSignal.FromCurrentContext().Raise(new Exception("Invalid Model State -- " + stringBuilder.ToString()));
actionContext.Response = actionContext.Request.CreateErrorResponse(HttpStatusCode.BadRequest, actionContext.ModelState);
}
}
Of course, you will need to add the filter using "config.Filters.Add".
Web API special cases HttpResponseException thrown in action and converts into HttpResponseMessage and hence you are not seeing your exception filter getting invoked.
This is not true in the case of throwing HttpResponseException from filters. However, ideally one need not throw HttpResponseException from filters as you could short-circuit a request by setting the Response property on the supplied input context.
You need to turn on Elmah for HttpFilters in order to get this to work as you expect for WebApi.
Use Elmah.Contrib.WebApi available as a NuGet Package, it will wire include a class that you can then wire up following the instructions on the Elmah.Contrib.WebApi project site.
If you want to do this yourself, Capturing Unhandled Exceptions in ASP.NET Web API's with ELMAH walks you through what the Elmah.Contrib.WebApi is doing for you.
Additionally, I had to change the way that the error response is thrown for it to be picked by Elmah to:
throw new HttpException((int)HttpStatusCode.NotAcceptable, "This request is not properly formatted");
I would also recommend the use of the Elmah.MVC NuGet Package.
In JavaFx I can attach a listener to the load worker for a webEngine like this:
webEngine.getLoadWorker().stateProperty().addListener(
new ChangeListener<Worker.State>() {
public void changed(ObservableValue ov, Worker.State oldState, Worker.State newState) {
System.out.println("webEngine result "+ newState.toString());
}
});
However if I try to load a document at an https address such as:
https://SomeLocalMachine.com:9443/jts/admin#action=com.ibm.team.repository.manageUsers
all I get printed out on the console is:
webEngine result READY
webEngine result SCHEDULED
webEngine result RUNNING
webEngine result FAILED
(The same https address in Firefox or Chrome gets me a login page)
Does anyone know how I can get more detailed reports out of the JavaFx WebEngine. I don't want to just know that it failed - I need to know why. I can guess my error is SSL/certificate/HTTPS related but currently I'm quite in the dark as to which part of SSL caused it to 'FAIL'
You can use com.sun.javafx.webkit.WebConsoleListener. Downside is that it is JRE internal API.
WebConsoleListener.setDefaultListener(new WebConsoleListener(){
#Override
public void messageAdded(WebView webView, String message, int lineNumber, String sourceId) {
System.out.println("Console: [" + sourceId + ":" + lineNumber + "] " + message);
}
});
The best we ever got was:
if (webEngine.getLoadWorker().getException() != null && newState == State.FAILED) {
exceptionMessage = ", " + webEngine.getLoadWorker().getException().toString();
}
but that didn't help.
(Our error was caused by a missing CookieStore, it seems you don't get one for free - and have to set a default one: http://docs.oracle.com/javase/7/docs/api/java/net/CookieHandler.html)
Have you tried the following:
engine.getLoadWorker().exceptionProperty().addListener(new ChangeListener<Throwable>() {
#Override
public void changed(ObservableValue<? extends Throwable> ov, Throwable t, Throwable t1) {
System.out.println("Received exception: "+t1.getMessage());
}
});