Repository Design: Sharing a transaction - servicestack

I am implementing a Rest service using ServiceStack. We use the repository pattern and auto-wire repositories into services via IOC.
Currently, we have a naive approach where one db model is paired with one repository. This means that whenever more than one entity is manipulated in one service, no transactional boundaries are used. Repositories are invoked sequentially: if one or more steps along the way fail, one has to "rollback" the db to its initial state, manually. Worst case scenario, if the request thread dies, or if an unchecked exception occurs (e.g., OutOfMemoryException) the database will be left in an inconsistent state.
I have a set of hypothetical solutions, but i regard none as adequate:
Open a connection and start a transaction at the Service Level. Invoke repositories, passing them the connection. This is obviously wrong as it goes against all the ddd design guidelines. The whole point is for the upper layers to be completely ignorant about concrete persistence. Moreover, it would mess up unit testing.
Have the first repository starting a transaction. Other dependent repositories would be invoked, but passing the already opened connection. This also sounds like bad design.
Defining aggregates. I'm not a great fan of this one as I'm not a domain modelling expert, and I feel that by introducing aggregates, I am liable to introduce designs errors. One advantage of the current model is that it is simple.
Any one has suggestions for this problem?
Thanks in advance

You can use a pass through class usually called UnitOfWork, where you will open and close the "connection". Search for "Unit of work" you will find many examples. You can customize the below snippet to include transactions.
public class UnitOfWork : IUnitOfWork
{
readonly CompanyDbContext _context;
public UnitOfWork()
{
_context = new CompanyDbContext ();
}
private bool _disposed;
protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
if (disposing)
{
_context.Dispose();
}
}
_disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Save()
{
_context.SaveChanges();
}
public IProductRepository ProductRepository
{
get { return new ProductRepository(_context); }
}
public ICartRepository CartRepository
{
get { return new CartRepository(_context); }
}
}
then you can do multiple transactions like below
using (_unitOfWork)
{
var p = _unitOfWork.ProductRepository.SingleOrDefault(a => a.id ==1);
_unitOfWork.CartRepository.Add(p);
_unitOfWork.Save();
}

In order to use Transactions effectively with Ormlite, you need to create custom DBManager class which can hold the connection object for each thread (use a ThreadStatic). Then you can use this custom DBManager in your repository to call different ormlite function.
Part of the code that I use is (you need modify the code to work properly):
public class ThreadSpecificDBManager : IDisposable, IDBManager
{
[ThreadStatic]
private static int connectionCount = 0;
[ThreadStatic]
private static int transactionCount = 0;
[ThreadStatic]
private static IDbConnection connection = null;
public string ConnectionString { get; set; }
public IDbConnection Connection { get { EnsureOpenConnection(); return connection; } }
static ThreadSpecificDBManager()
{
}
private void EnsureOpenConnection()
{
if ((connection == null) || (connection.State == ConnectionState.Closed))
{
OrmLiteConfig.TSTransaction = null;
transactionCount = 0;
connectionCount = 0;
connection = (DbConnection)ConnectionString.OpenDbConnection();
//if (ConfigBase.EnableWebProfiler == true)
// connection = new ProfiledDbConnection((DbConnection)connection, MiniProfiler.Current);
}
}
public ThreadSpecificDBManager(string connectionString)
{
ConnectionString = connectionString;
connectionCount++;
EnsureOpenConnection();
}
public void Dispose()
{
if (transactionCount > 0)
{
//Log.Error("Uncommitted Transaction is left");
}
connectionCount--;
if (connectionCount < 1)
{
if ((connection != null) && (connection.State == ConnectionState.Open))
connection.Close();
if (connection != null)
connection.Dispose();
connection = null;
}
}
public void BeginTransaction()
{
if (transactionCount == 0)
{
//Log.SqlBeginTransaction(0, true);
OrmLiteConfig.TSTransaction = Connection.BeginTransaction();
}
else
{
//Log.SqlBeginTransaction(transactionCount, false);
}
transactionCount = transactionCount + 1;
}
public void RollbackTransaction()
{
try
{
if (transactionCount == 0)
{
//Log.SqlError("Transaction Rollback called without a begin transaction call.");
return;
}
if (OrmLiteConfig.TSTransaction == null)
{
//Log.SqlError("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
throw new Exception("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
}
if (transactionCount == 1)
{
transactionCount = 0;
try
{
//Log.SqlRollbackTransaction(transactionCount, true);
OrmLiteConfig.TSTransaction.Rollback();
}
catch (Exception ex1)
{
//Log.SqlError(ex1,"Error when rolling back the transaction");
}
OrmLiteConfig.TSTransaction.Dispose();
OrmLiteConfig.TSTransaction = null;
}
else
{
//Log.SqlRollbackTransaction(transactionCount, false);
transactionCount = transactionCount - 1;
}
}
finally
{
}
}
public void CommitTransaction()
{
try
{
if (transactionCount == 0)
{
//Log.SqlError("Transaction Rollback called without a begin transaction call.");
return;
}
if (OrmLiteConfig.TSTransaction == null)
{
//Log.SqlError("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
throw new Exception("Transaction is not saved in the Thread Safe variable- so it cannot be rollbacked.");
}
if (transactionCount == 1)
{
//Log.SqlCommitTransaction(transactionCount,true);
transactionCount = 0;
OrmLiteConfig.TSTransaction.Commit();
OrmLiteConfig.TSTransaction.Dispose();
OrmLiteConfig.TSTransaction = null;
}
else
{
//Log.SqlCommitTransaction(transactionCount, false);
transactionCount = transactionCount - 1 ;
}
}
finally
{
}
}
}

Related

Sending message to IoTHub fails

I have been working on a device which is sending some data to an Azure IoT hub
The device is doing this on two different locations in the code. On one side it works perfectly and I can connect to the Hub via Connection String and transport type MQTT_WebSocket_Only.
public static class Mqtt2IoTNew
{
private static string _DeviceConnectionString = Properties.Settings.Default.MqttUri;
private static TransportType _TransportType = TransportType.Mqtt_WebSocket_Only;
public static void Send(object argEntry, bool argIsList)
{
var deviceClient = DeviceClient.CreateFromConnectionString(_DeviceConnectionString, _TransportType);
deviceClient.ReceiveAsync(TimeSpan.FromSeconds(2)).Wait();
var message = new Message(deviceClient, argEntry, argIsList);
message.RunAsync().GetAwaiter().GetResult();
}
}
internal class Message
{
private DeviceClient _DeviceClient;
private readonly string _Message;
public Message(DeviceClient argDeviceClient, object argEntry, bool isList)
{
_DeviceClient = argDeviceClient;
StringBuilder stb = new StringBuilder();
if (isList)
{
foreach (var entity in (List<object>) argEntry)
{
stb.Append("<entity>").Append(JsonConvert.SerializeObject(entity)).Append("</entity>\n");
}
}
else
{
stb.Append(JsonConvert.SerializeObject(argEntry));
}
_Message = stb.ToString();
}
public async Task RunAsync()
{
await SendEvent().ConfigureAwait(false);
}
private async Task SendEvent()
{
Microsoft.Azure.Devices.Client.Message eventMessage = new Microsoft.Azure.Devices.Client.Message(Encoding.UTF8.GetBytes(_Message));
await _DeviceClient.SendEventAsync(eventMessage).ConfigureAwait(false);
}
}
//Call of method that does not work
protected override void DoOnCompleted(IRepository argRepository)
{
if (_CurrentlySendingTreadId.HasValue)
{
if (_CurrentlySendingTreadId.Value == Thread.CurrentThread.ManagedThreadId)
{
return;
}
}
TaskFactoryProvider.GetFactory().StartNew(()=>SendBatchProtocols());
}
public bool SendBatchProtocols()
{
using (var repository = RepositoryProviderHolder.RepositoryProvider.GetRepository(Constants.CONTAINERCONTRACT_PRODUCTIONREPOSITORY))
{
IQueryable<BatchProtocol> batchProtocolQuery = repository.GetQuery<BatchProtocol>().OrderBy(bp => bp.InternalNoInteger);
batchProtocolQuery = batchProtocolQuery.Where(bp => !bp.IsArchived).Take(1);
if (!batchProtocolQuery.Any()) return false;
var batchProtocols = batchProtocolQuery.ToList();
IsBatchProtocolSend = false;
try
{
foreach (var bps in batchProtocols)
{
Mqtt2IoTNew.Send(bps,false);
}
IsBatchProtocolSend = true;
}
catch (Exception ex)
{
throw;
}
}
return IsBatchProtocolSend;
}
//Call of Method that does work
private void AddEntitiesAndSaveChanges(IEnumerable argEntities)
{
if (argEntities == null)
{
return;
}
lock (_UnderlyingRepositoryAccessLockObject)
{
#region Log2DornerIoT
if (Properties.Settings.Default.Log2DornerIoT)
{
List<object> entities = new List<object>();
int i = 0;
foreach (var entity in argEntities)
{
if (i < 100)
{
entities.Add(entity);
i++;
}
else
{
try
{
Mqtt2IoTNew.Send(entities, true);
}
catch (Exception e)
{
throw;
}
entities.Clear();
i = 0;
}
}
}
}
on the other part of the code, I am only colling the same class to use to send method in the same way but here I get an exception which says "TLS authentication error" and the inner exception "Unable to connect to the remote server", "The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel".
But: I never used any kind of authorization not in the first part which works perfectly neither in the second.
I would be very happy if someone could help me. I have found nothing so fare regarding this issue.
Thanks for your time.
Michael
I found the reason why it didn't work. There was a Persmissice Certificate Policy applied that blocked the certificate at one side of the project. I disabled it and now it works perfectly fine.
Thanks for the help anyway.

JavaFX FX application Thread Issue

I'm doing a desktop application and I'm performing a heavy task in background. I want a progress bar to be updated. My program works and I can see the progress bar here isn't my problem. My problem is that I use 2 tasks that I run in 2 thread in order to make both the update of the progress bar and the heavy task. My question is : Is there a better way to do in oder to avoid the error "Exception in thread "Thread-5" java.lang.IllegalStateException: Not on FX application thread; currentThread = Thread-5".
Of course I already check on Internet and I always find : better use Platform.runLater. Ok but in both new thread I need attribute of my class, eg I can't access for example "this.myAttribute" when I use Platform.runLater((new Runnable()...)). Is RunLater the solution and I can't see it ?
Here is a bunch of code, the method setConnection is called in JavaFX thread, and I create 2 other. One for progressbar, the other for my task :
#FXML
private void setConnection() {
try {
this.onOffButton.setSelected(false);
if (!this.hubModel.isConnected()) {
this.progressBar.progressProperty().unbind();
#SuppressWarnings("unchecked")
OperationTask progressBarOperationTask = new OperationTask(this) {
#Override
public Void call() {
HubController hubController = (HubController) this.getHubController();
hubController.getProgressBar().setVisible(true);
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(25);
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
updateProgress(i + 1, 100);
}
hubController.getProgressBar().setVisible(false);
return null;
}
};
this.progressBar.progressProperty().bind(progressBarOperationTask.progressProperty());
Thread timeThread = new Thread(progressBarOperationTask);
timeThread.setDaemon(true);
timeThread.start();
}
#SuppressWarnings("unchecked")
OperationTask connectionOperationTask = new OperationTask(this) {
#Override
protected Object call() throws Exception {
HubController hubController = (HubController) this.getHubController();
if (hubController.getUserID().getText().equals("") || hubController.getUserPW().getText().equals("")) {
hubController.getCommentBottom().setText("Please enter a user name and a password.");
hubController.getOnOffButton().setSelected(false);
} else {
hubController.getHubModel().setIdUser(hubController.getUserID().getText());
hubController.getHubModel().setPwUser(hubController.getUserPW().getText());
String comment = hubController.getHubModel().setConnection();
if (!comment.equals("Connection established.")) {
hubController.getOnOffButton().setSelected(false);
}
if (hubController.getHubModel().isConnected()) {
hubController.getConnectionStatus().setText("Connected");
hubController.getConnectionStatus().setStyle("-fx-font-weight: bold");
String commentProject = hubController.getHubModel().getAllProjects();
if (commentProject.equals("")) {
TextFields.bindAutoCompletion(hubController.getCloneAndMoveController().getNewProjectNameTextField(), hubController.getHubModel().getProjectsList());
} else {
comment = commentProject;
}
hubController.getOnOffButton().setSelected(true);
} else {
hubController.getConnectionStatus().setText("Not connected");
hubController.getConnectionStatus().setStyle("-fx-font-weight: regular");
}
hubController.getCommentBottom().setText(comment);
}
return null;
}
};
Thread connectionThread = new Thread(connectionOperationTask);
connectionThread.setDaemon(true);
connectionThread.start();
} catch (Exception e) {
e.printStackTrace();
}
}
Moreover if you see something that could be improved, I would appreciate (I'm new with java)
Thank you.
You can access your object from Platform.runLater(). New Runnable which you create for it has access to this instance of your object. See in an example:
private String myAttribute = "hello";
#Override
public void randomMethod() {
//...
Platform.runLater(new Runnable() {
#Override
public void run() {
System.out.println(myAttribute);
}
});
}

Entity Framework The context cannot be used while the model is being created

My unit of work class is mentioned below and I am using Ninject and I have tried injecting IUnitOfWork per request per thread scope, transient etc. but I am still getting error which is:
"Message":"An error has occurred.","ExceptionMessage":"The context cannot be used while the model is being created. This exception may be thrown if the context is used inside the OnModelCreating method or if the same context instance is accessed by multiple threads concurrently. Note that instance members of DbContext and related classes are not guaranteed to be thread safe.","ExceptionType":"System.InvalidOperationException
I get this error when i make two web API (get) calls at the same time using angularJS and it shows error at the point _context.Set<TEntity>().FirstOrDefault(match);
public class UnitOfWork : IUnitOfWork, IDisposable
{
private My_PromotoolEntities _uowDbContext = new My_PromotoolEntities();
private Dictionary<string, object> _repositories;
// Do it like this if no specific class file
private GenericRepository<MysPerson> _personRepository;
//private GenericRepository<MysDataSource> dataSourcesRepository;
//private GenericRepository<MysCountry> countryMasterRepository;
// Or like this if with specific class file.
private DataSourceRepository _dataSourcesRepository;
private CustomerRepository _customerRepository;
private DeviceRepository _deviceRepository;
private DeviceRegistrationRepository _deviceRegistrationRepository;
private EmailQueueRepository _emailQueueRepository;
public void SetContext(My_PromotoolEntities context)
{
_uowDbContext = context;
}
public void CacheThis(object cacheThis, string keyName, TimeSpan howLong)
{
Cacheing.StaticData.CacheStaticData(cacheThis, keyName, howLong);
}
public object GetFromCache(string keyName)
{
return Cacheing.StaticData.GetFromCache(keyName);
}
public GenericRepository<T> GenericRepository<T>() where T : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(T).Name;
if (!_repositories.ContainsKey(type))
{
var repositoryType = typeof(GenericRepository<>);
var repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(T)), _uowDbContext);
_repositories.Add(type, repositoryInstance);
}
return (GenericRepository<T>)_repositories[type];
}
public GenericRepository<MysPerson> PersonRepository
{
get
{
if (this._personRepository == null)
{
this._personRepository = new GenericRepository<MysPerson>(_uowDbContext);
}
return _personRepository;
}
}
public DataSourceRepository DataSourcesRepository
{
get
{
if (this._dataSourcesRepository == null)
{
this._dataSourcesRepository = new DataSourceRepository(_uowDbContext);
}
return _dataSourcesRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this._customerRepository == null)
{
this._customerRepository = new CustomerRepository(_uowDbContext);
}
return _customerRepository;
}
}
public DeviceRepository DeviceRepository
{
get
{
if (this._deviceRepository == null)
{
this._deviceRepository = new DeviceRepository(_uowDbContext);
}
return _deviceRepository;
}
}
public DeviceRegistrationRepository DeviceRegistrationRepository
{
get
{
if (this._deviceRegistrationRepository == null)
{
this._deviceRegistrationRepository = new DeviceRegistrationRepository(_uowDbContext);
}
return _deviceRegistrationRepository;
}
}
public EmailQueueRepository emailQueueRepository
{
get
{
if (this._emailQueueRepository == null)
{
this._emailQueueRepository = new EmailQueueRepository(_uowDbContext);
}
return _emailQueueRepository;
}
}
/// <summary>
/// Commits all changes to the db. Throws exception if fails. Call should be in a try..catch.
/// </summary>
public void Save()
{
try
{
_uowDbContext.SaveChanges();
}
catch (DbEntityValidationException dbevex)
{
// Entity Framework specific errors:
StringBuilder sb = new StringBuilder();
var eve = GetValidationErrors();
if (eve.Count() > 0)
{
eve.ForEach(error => sb.AppendLine(error));
}
ClearContext();
// Throw a new exception with original as inner.
var ex = new Exception(sb.ToString(), dbevex);
ex.Source = "DbEntityValidationException";
throw ex;
}
catch (Exception)
{
ClearContext();
throw;
}
}
private void ClearContext()
{
DetachAll();
}
private void DetachAll()
{
foreach (DbEntityEntry dbEntityEntry in _uowDbContext.ChangeTracker.Entries())
{
if (dbEntityEntry.Entity != null)
{
dbEntityEntry.State = EntityState.Detached;
}
}
}
/// <summary>
/// Checks for EF DbEntityValidationException(s).
/// </summary>
/// <returns>Returns a List of string containing the EF DbEntityValidationException(s).</returns>
public List<string> GetValidationErrors()
{
if (_uowDbContext.GetValidationErrors().Count() != 0)
{
return _uowDbContext.GetValidationErrors().Select(e => string.Join(Environment.NewLine, e.ValidationErrors.Select(v => string.Format("{0} - {1}", v.PropertyName, v.ErrorMessage)))).ToList();
}
return null;
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
_uowDbContext.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
You should never use a context in 2 places at the same time, that's exactly why you are getting this error. From the MSDN documentation:
Thread Safety: Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.
It is a little hard to make suggestions without a repro but there is a brute force approach that should resolve the issue. If you have an interception point before/during DI setup then you can cause all the context initialization etc to happen by creating an instance of your context and calling ctx.Database.Initialize(force: false); Passing 'force: false' will ensure that the initialization still only happens once per AppDomain

JavaFX Concurrency and Tasks (running threads in Task)

I am new to JavaFx/Concurrency so I read the tutorial over at Concurrency in JavaFX but I am still a little confused about the implementation of background threads in a JavaFX Gui.
I'm trying to write a small GUI that interfaces with some serial devices (using JSSC-2.8) and that updates the GUI based on the responses from those devices. But, there's a lag between when the message is written and when the device responds, and using Thread.sleep() for an arbitrary amount of time wasn't a reliable way for me program it. So instead I want to use wait() and notify() methods from the concurrency package (with all the appropriate synchronizations), but I am not sure how to implement it. What I initially did is create another Thread, inside the Task, that would write the messages and wait for the responses, and using some bindings, would update the GUI. I've included my code at the end. Here is a short form of the pseudocode I am trying to implement:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
notify()
But what's been happening is, as soon as I call the wait(), the entire application idles and then when notify() is called (after the response fires and event), it doesn't continue where it left off in the recipe() loop, or the startTdk() loop for that matter, it's just idle. Have I implements the threads wrong? When I am calling the wait(), is it a possibility that I cause the EventDispatch or JavaFX Application Thread to pause?
I hope the question is clear, if there are any clarifications needed I can update the post.
public class OmicronRecipe extends Service<String> implements Runnable{
private final String SEPERATOR=";";
private final Tdk tdk;
private final Pvci pvci;
private final SimpleStringProperty data = new SimpleStringProperty("");
private final Float MAX_V = 26.0f,UHV=1e-8f;
private boolean isTdkOn=false, isPvciOn=false;
private String power;
private Float temp,press,maxT, setT;
private int diffMaxT,diffP,diffPow, diffT, index=0;
public OmicronRecipe(){
tdk = new Tdk("COM4");
pvci = new Pvci("COM5");
}
private synchronized void recipe(){
while (true){
try {
sendMessages();
data.set(power+SEPERATOR+temp+SEPERATOR+press);
calcDiffs();
if (diffPow < 0){
if(diffMaxT < 0){
if(diffT < 0){
if (diffP < 0){
if(!rampPow()){
//Max Power reached
}
}else{
//Wait for pressure drop
}
}
}else{
//Wait until quit
}
}else{
//Max power reached
}
Thread.sleep(5000);
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private synchronized boolean rampPow(){
boolean isRamped=false;
Float setPow = tdk.getSetPow(index), curPow;
setT = tdk.getSetT(index);
curPow = Float.parseFloat(power);
if(curPow.compareTo(setPow) < 0){
do{
curPow += 0.1f;
tdk.sendMessage("PV "+curPow+"\r");
try {
wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
curPow = Float.parseFloat(power);
}while(curPow.compareTo(setPow) < 0);
index++;
isRamped=true;
}
return isRamped;
}
public synchronized boolean connect(){
if(!isTdkOn && !isPvciOn){
isTdkOn = tdk.connect();
isPvciOn = pvci.connect();
}
return isTdkOn && isPvciOn;
}
public synchronized boolean disconnect(){
if(tdk!=null && pvci !=null){
isTdkOn = tdk.disconnect();
isPvciOn = pvci.disconnect();
}
return !isTdkOn && !isPvciOn;
}
public synchronized StringProperty getData(){
return data;
}
public void setMaxT(Float maxT){
this.maxT = maxT;
}
private synchronized void calcDiffs(){
Float pow = Float.parseFloat(power);
diffPow = pow.compareTo(MAX_V);
diffMaxT = temp.compareTo(maxT);
diffT = temp.compareTo(100f);
diffP = press.compareTo(UHV);
}
private synchronized void setListeners(){
tdk.getLine().addListener((ov,t, t1)-> {
synchronized (this){
System.out.println("New Power: "+t1);
power = t1;
this.notify();
}
});
pvci.getLine().addListener((ov,t,t1) ->{
synchronized (this){
String[] msg = t1.split(SEPERATOR);
if(msg.length == 2){
switch(msg[0]){
case "temperature":
System.out.println("Temperaute");
temp = Float.parseFloat(msg[1]);
break;
case "pressure":
System.out.println("Pressure");
press = Float.parseFloat(msg[1]);
break;
default:
System.out.println("Nothing; Something went wrong");
break;
}
}
this.notify();
}
});
}
private synchronized void sendMessages(){
try {
tdk.sendMessage("PV?\r");
this.wait();
pvci.sendMessage("temperature");
this.wait();
pvci.sendMessage("pressure");
this.wait();
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
private synchronized boolean startTdk(){
boolean isOut=false;
if(isTdkOn){
try {
tdk.sendMessage("ADR 06\r");
this.wait();
System.out.println("Power: "+power);
if(power.equals("OK")){
tdk.sendMessage("OUT?\r");
this.wait();
if(power.equals("OFF")){
tdk.sendMessage("OUT ON\r");
this.wait();
isOut = power.equals("ON");
}
else{
isOut = power.equals("ON");
}
}
} catch (InterruptedException ex) {
Logger.getLogger(OmicronRecipe.class.getName()).log(Level.SEVERE, null, ex);
}
}
return isOut;
}
#Override
protected Task<String> createTask() {
return new Task<String>() {
#Override
protected String call() throws IOException{
new Thread(new OmicronRecipe()).start();
return "";
}
};
}
#Override
public void run() {
if (connect()){
setListeners();
if(startTdk()){
recipe();
}
}
}
}
I won't include the Pvci class, because it just a copy of the Tdk class but with specific message sequences to talk with that machine.
public class Tdk {
private SerialPort tdkPort;
private final String portName;
private StringBuilder sb = new StringBuilder("");;
private final StringProperty line = new SimpleStringProperty("");
private final HashMap<Float,Float> calibMap;
private ArrayList<Float> list ;
private boolean isEnd=false;
public Tdk(String portName){
this.portName = portName;
System.out.println("TDK at "+portName);
calibMap = new HashMap();
setMap();
}
public synchronized boolean connect(){
tdkPort = new SerialPort(portName);
try {
System.out.println("Connecting");
tdkPort.openPort();
tdkPort.setParams(9600,
SerialPort.DATABITS_8,
SerialPort.STOPBITS_1,
SerialPort.PARITY_NONE);
tdkPort.setEventsMask(SerialPort.MASK_RXCHAR);
tdkPort.addEventListener(event -> {
if(event.isRXCHAR()){
if(event.getPortName().equals(portName)){
try {
if(!isEnd){
int[] str = tdkPort.readIntArray();
if(str!=null)
hexToString(str);
}
if(isEnd){
System.out.println("Here: "+sb.toString());
isEnd=false;
String d = sb.toString();
sb = new StringBuilder("");
line.setValue(d);
}
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
}
});
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
return tdkPort !=null && tdkPort.isOpened();
}
public synchronized boolean disconnect(){
if(tdkPort!=null) {
try {
tdkPort.removeEventListener();
if (tdkPort.isOpened())
tdkPort.closePort();
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
System.out.println("Disconnecting");
}
return tdkPort.isOpened();
}
public synchronized void sendMessage(String message){
try {
tdkPort.writeBytes(message.getBytes());
} catch (SerialPortException e) {
Logger.getLogger(Tdk.class.getName()).log(Level.SEVERE, null, e);
}
}
private void setMap(){
calibMap.put(1.0f, 25.0f);
calibMap.put(7.0f, 125.0f);
calibMap.put(9.8f, 220.0f);
list = new ArrayList(calibMap.keySet());
}
public Float getSetPow(int index){
return list.get(index);
}
public Float getSetT(int index){
return calibMap.get(list.get(index));
}
public synchronized StringProperty getLine(){
return line;
}
private synchronized void hexToString(int[] hexVal){
for(int i : hexVal){
if(i != 13){
sb.append((char)i);
}else{
isEnd=true;
}
}
System.out.println("Turning: "+Arrays.toString(hexVal)+" to String: "+sb.toString()+" End: "+isEnd);
}
Freeze
Your UI freezes most probably because you are waiting on the FX Apllication Thread, to solve this there are different approaches:
JavaFX Application Thread
You can delegate some work to the FX Application Thread, therefore see Platform.runLater
Not everything can be run on this thread, but for example, in your DeviceController, you can wait until the message appears and then call Platform.runLater() and update the field (you should therefor oc hand the field over to the controller).
DataBinding
What you are describing can also be realised with DataBinding.
With this you could define a SimpleStringProperty, which is bound to your UI Label (.bind() Method). If the controller must fire its message you can set the StringProperty and the UI will update itself.
The scenario you described could be used like this:
start Task:
connect to serial devices
synchronized loop:
send messages
wait() for event to fire
**updateDate the DataBounded fields**
We are taught that, Concurrency notify/wait
Concurrency on level wait()/notify() is very low level. You should try to work with higher level synchronisation methods or helpers (where people have already solved your problems :))

BlackBerry - cancel a thread executed in another class to refresh location

How can I cancel a thread from another class fetching/refreshing location. I am able to cancel a thread from within the same class. But I am unable to do this across classes. Declaring the GPSThread static did not help. Can anyone please guide?
Class1:
public class GPSListener {
/* Other instantiation code */
Dialog busyDialog1 = new Dialog("Refreshing Location...",
new String [] { "Cancel" },
new int [] { Dialog.CANCEL},
Dialog.CANCEL,
Bitmap.getPredefinedBitmap(Bitmap.HOURGLASS))
{
public void fieldChanged(Field field1, int context1)
{
GPSHandler.requestStop();
busyDialog1.cancel();
}
};
public String refreshCoordinates() {
String test = "nothing";
if (GPSHandler.isStopRequested())
{
GPSHandler.stopRequested = false;
return null;
}
GPSHandler.getInstance().setListener(this);
GPSHandler.getInstance().requestLocationUpdates();
if (GPSHandler.isStopRequested())
{
GPSHandler.stopRequested = false;
return null;
}
busyDialog1.setEscapeEnabled(false);
busyDialog1.show();
return test;
}
public void onLocationReceived(Coordinates location) {
lblLatitude.setText(Double.toString(location.getLatitude()));
lblLongitude.setText(Double.toString(location.getLongitude()));
busyDialog1.cancel();
}
}
Class 2:
public class GPSHandler {
private GPSThread _gpsThread;
private Coordinates _location;
private boolean _gotLocation;
private GPSListener _listener;
/** this class will be a Singleton, as the device only has one GPS system */
private static GPSHandler _instance;
/** #return the Singleton instance of the GPSHandler */
public static GPSHandler getInstance() {
if (_instance == null) {
_instance = new GPSHandler();
}
return _instance;
}
public static boolean stopRequested = false;
public synchronized static void requestStop() {
stopRequested = true;
}
public synchronized static boolean isStopRequested() {
return stopRequested;
}
/** not publicly accessible ... use getInstance() */
private GPSHandler() {
}
/** call this to trigger a new location fix */
public void requestLocationUpdates() {
if (_gpsThread == null || !_gpsThread.isAlive()) {
_gpsThread = new GPSThread();
_gpsThread.start();
}
}
public void setListener(GPSListener listener) {
// only supports one listener this way
_listener = listener;
}
private void setLocation(final Coordinates value) {
_location = value;
if (value.getLatitude() != 0.0 || value.getLongitude() != 0.0) {
_gotLocation = true;
if (_listener != null) {
// this assumes listeners are UI listeners, and want callbacks on the UI thread:
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
_listener.onLocationReceived(value);
}
});
}
}
}
private class GPSThread extends Thread {
private void getLocationFromGoogle() {
try {
int cellID = GPRSInfo.getCellInfo().getCellId();
int lac = GPRSInfo.getCellInfo().getLAC();
String urlString2 = "http://www.google.com/glm/mmap";
// Open a connection to Google Maps API
ConnectionFactory connFact = new ConnectionFactory();
ConnectionDescriptor connDesc;
connDesc = connFact.getConnection(urlString2);
HttpConnection httpConn2;
httpConn2 = (HttpConnection)connDesc.getConnection();
httpConn2.setRequestMethod("POST");
// Write some custom data to Google Maps API
OutputStream outputStream2 = httpConn2.openOutputStream();//getOutputStream();
writeDataGoogleMaps(outputStream2, cellID, lac);
// Get the response
InputStream inputStream2 = httpConn2.openInputStream();//getInputStream();
DataInputStream dataInputStream2 = new DataInputStream(inputStream2);
// Interpret the response obtained
dataInputStream2.readShort();
dataInputStream2.readByte();
final int code = dataInputStream2.readInt();
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert(code + "");
}
});
if (code == 0) {
final double latitude = dataInputStream2.readInt() / 1000000D;
final double longitude = dataInputStream2.readInt() / 1000000D;
setLocation(new Coordinates(latitude, longitude, 0.0f));
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert(latitude+"-----"+longitude);
}
});
dataInputStream2.readInt();
dataInputStream2.readInt();
dataInputStream2.readUTF();
} else {
System.out.println("Error obtaining Cell Id ");
}
outputStream2.close();
inputStream2.close();
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
private void tryGetLocationFromDevice() {
_gotLocation = false;
try {
Criteria myCriteria = new Criteria();
myCriteria.setCostAllowed(false);
LocationProvider myLocationProvider = LocationProvider.getInstance(myCriteria);
try {
Location myLocation = myLocationProvider.getLocation(300);
setLocation(myLocation.getQualifiedCoordinates());
} catch ( InterruptedException iex ) {
System.out.println(iex.getMessage());
} catch ( LocationException lex ) {
System.out.println(lex.getMessage());
}
} catch ( LocationException lex ) {
System.out.println(lex.getMessage());
}
if (!_gotLocation) {
getLocationFromGoogle();
}
}
public void run() {
int bbMapsHandle = CodeModuleManager.getModuleHandle("net_rim_bb_lbs"); // OS 4.5 - 6.0
int bbMapsHandle60 = CodeModuleManager.getModuleHandle("net_rim_bb_maps"); // OS 6.0
if (bbMapsHandle > 0 || bbMapsHandle60 > 0) {
tryGetLocationFromDevice();
} else {
getLocationFromGoogle();
}
}
}
private void writeDataGoogleMaps(OutputStream out, int cellID, int lac) throws IOException {
DataOutputStream dataOutputStream = new DataOutputStream(out);
dataOutputStream.writeShort(21);
dataOutputStream.writeLong(0);
dataOutputStream.writeUTF("en");
dataOutputStream.writeUTF("Android");
dataOutputStream.writeUTF("1.0");
dataOutputStream.writeUTF("Web");
dataOutputStream.writeByte(27);
dataOutputStream.writeInt(0);
dataOutputStream.writeInt(0);
dataOutputStream.writeInt(3);
dataOutputStream.writeUTF("");
dataOutputStream.writeInt(cellID);
dataOutputStream.writeInt(lac);
dataOutputStream.writeInt(0);
dataOutputStream.writeInt(0);
dataOutputStream.writeInt(0);
dataOutputStream.writeInt(0);
dataOutputStream.flush();
}
}
Your GPSThread object is currently declared as a private inner class within GPSHandler. If you want to stop execution (or indeed do anything with it) from outside the scope of GPSHandler you will need to mark it as public. You will also need to provide some public mechanism (e.g. a stop() method) to cancel the thread execution.
The most common way of doing this is to have a boolean flag inside your thread (e.g shouldStop) which is checked within your main execution loop inside run() to see if it should stop. When the stop() method is called shouldStop is set to true and your Thread will stop.
Here's a good example: How to stop threads in Java?
There's two groups of changes you should make.
Change the Stop Requested Flag
First, remember that encapsulation is a good thing in Object-Oriented languages. The isStopRequested() method, or stopRequested variable of the GPSHandler should not be used outside of that class. Your UI's GPSListener should not attempt to use either of those. I would change your GPSHandler to use this:
private static boolean stopRequested = false;
public synchronized static void requestStop() {
stopRequested = true;
}
private synchronized static boolean isStopRequested() {
return stopRequested;
}
Only requestStop() should be public. It looks like you made stopRequested public to allow the GPSListener to reset it. If it needs resetting, let the class that owns that variable do the resetting. For example, in GPSHandler:
/** call this to trigger a new location fix */
public void requestLocationUpdates() {
if (_gpsThread == null || !_gpsThread.isAlive()) {
// reset this stop flag:
stopRequested = false;
_gpsThread = new GPSThread();
_gpsThread.start();
}
}
requestLocationUpdates() is really the method that starts the thread, so it should be where stopRequested gets reset to false.
Also, another reason that you should not make stopRequested public and allow other classes to use it is that this is not generally thread-safe. One of the reasons to wrap stopRequested with the requestStop() and isStopRequested() methods is to add thread-safety. There's many ways to do that, but those two methods achieve thread-safety by being marked with the synchronized keyword.
Change How/Where You Check the Flag
After you make these fixes, you need to change where you check if a stop has been requested. You don't really want to check isStopRequested() in the refreshCoordinates() method. That method involves almost no work. Even though it starts the process of getting a location fix, that only starts a thread, but the actual work of getting the location is done on a background thread (your GPSThread). If requestStop() is called, it's very unlikely that it will be called in the middle of refreshCoordinates(), so that's not where you should check it.
Check isStopRequested() multiple times within the GPSHandler class's methods tryGetLocationFromDevice() and getLocationFromGoogle(). Those are the methods that perform slow processing. Those are the ones you might want to interrupt in the middle. So, something like this:
private void getLocationFromGoogle() {
try {
int cellID = GPRSInfo.getCellInfo().getCellId();
int lac = GPRSInfo.getCellInfo().getLAC();
String urlString2 = "http://www.google.com/glm/mmap";
if (isStopRequested()) return;
// Open a connection to Google Maps API
ConnectionFactory connFact = new ConnectionFactory();
ConnectionDescriptor connDesc;
connDesc = connFact.getConnection(urlString2);
HttpConnection httpConn2;
httpConn2 = (HttpConnection)connDesc.getConnection();
httpConn2.setRequestMethod("POST");
// Write some custom data to Google Maps API
OutputStream outputStream2 = httpConn2.openOutputStream();//getOutputStream();
writeDataGoogleMaps(outputStream2, cellID, lac);
if (isStopRequested()) return;
// Get the response
InputStream inputStream2 = httpConn2.openInputStream();//getInputStream();
DataInputStream dataInputStream2 = new DataInputStream(inputStream2);
// Interpret the response obtained
dataInputStream2.readShort();
dataInputStream2.readByte();
if (isStopRequested()) return;
final int code = dataInputStream2.readInt();
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run() {
Dialog.alert(code + "");
}
});
And in tryGetLocationFromDevice(), you could do this (make sure to add the member variable and new method below):
private LocationProvider _locationProvider; // must be a member variable!
public void requestStop() {
if (_locationProvider != null) {
// this will interrupt the _locationProvider.getLocation(300) call
_locationProvider.reset();
}
}
private void tryGetLocationFromDevice() {
_gotLocation = false;
try {
Criteria myCriteria = new Criteria();
myCriteria.setCostAllowed(false);
_locationProvider = LocationProvider.getInstance(myCriteria);
try {
Location myLocation = _locationProvider.getLocation(300);
setLocation(myLocation.getQualifiedCoordinates());
} catch ( InterruptedException iex ) {
// this may be caught if stop requested!!!!
System.out.println(iex.getMessage());
} catch ( LocationException lex ) {
System.out.println(lex.getMessage());
}
} catch ( LocationException lex ) {
System.out.println(lex.getMessage());
}
if (!_gotLocation && !isStopRequested()) {
getLocationFromGoogle();
}
}
Then, call the GPSThread.requestStop() method from the outer GPSHandler.requestStop() method:
public synchronized static void requestStop() {
stopRequested = true;
if (_gpsThread != null) {
_gpsThread.requestStop();
}
}

Resources