How does Object.notify() work with Object.wait()? - synchronized

I am trying to track a resource which behaves in an asymmetric manner. That is, it responds immediately to a start() request, but finishes processing a cancel() request at a significant delay.
For this purpose, I created an AtomicBoolean flag, with the intent to set it (true) immediately, but clear it (false) via a CountDownTimer.
protected AtomicBoolean mIsAsymmetricMode = new AtomicBoolean(false);
This flag is never set directly by any of the classes involved. It is always set and cleared via a dedicated method.
All callers of this setAsymmetricMode(boolean) method, run on the same thread - the main (UI) thread.
On a different thread I have an operation waiting for this flag to be cleared (false).
The following is a minimal working MainActivity.java that demonstrates the issue:
package com.example.basicactivity;
import static com.example.basicactivity.Log.logE;
import static com.example.basicactivity.Log.logW;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.os.CountDownTimer;
import android.view.View;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.AppBarConfiguration;
import androidx.navigation.ui.NavigationUI;
import com.example.basicactivity.databinding.ActivityMainBinding;
import android.view.Menu;
import android.view.MenuItem;
import java.util.concurrent.atomic.AtomicBoolean;
public class MainActivity extends AppCompatActivity {
private AppBarConfiguration appBarConfiguration;
private ActivityMainBinding binding;
protected final AtomicBoolean mIsAsymmetricMode = new AtomicBoolean(false);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
/*
logE() waiting for mIsAsymmetricMode set to FALSE
*/
binding.fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
logW("ASYMMETRIC", "^");
new Thread( () -> {
synchronized(mIsAsymmetricMode) {
while (mIsAsymmetricMode.get()) {
try {
mIsAsymmetricMode.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
logE("ASYMMETRIC", "mIsAsymmetricMode=false");
}
}).start();
logW("ASYMMETRIC", "$");
}
});
}
protected void setAsymmetricMode(boolean is) {
logW("ASYMMETRIC", "^ " + is);
if (is) {
mIsAsymmetricMode.compareAndSet(false, true);
}
else {
new CountDownTimer(100, 50) {
int tickCounter = 0;
public void onTick(long millUntilFinish) {
tickCounter++;
logW("ASYMMETRIC", "tickCounter=" + tickCounter);
}
public void onFinish() {
logW("ASYMMETRIC", "^");
synchronized(mIsAsymmetricMode) {
mIsAsymmetricMode.compareAndSet(true, false);
mIsAsymmetricMode.notifyAll();
}
logW("ASYMMETRIC", "$");
}
}.start();
}
logW("ASYMMETRIC", "$");
}
#Override // TRUE test (immediate)
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
setAsymmetricMode(true);
return true;
}
#Override // FALSE test (delayed)
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_settings) {
setAsymmetricMode(false);
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, appBarConfiguration) || super.onSupportNavigateUp();
}
}
When run, it presents a screen with two areas triggering listeners in this code:
The code works as expected, that is, when the "envelope button" is touched first, no logE() message is output to Logcat (it is waiting on the mIsAsymmetricMode flag), then when the "settings button" is touched, the logE() message is printed (last one, starting with "16:52:00.607 E T=8382"):
---------------------------- PROCESS STARTED (8121) for package com.example.basicactivity ----------------------------
16:51:22.206 W T=8121 MainActivity.setAsymmetricMode < MainActivity.onCreateOptionsMenu | ^ true
16:51:22.207 W T=8121 MainActivity.setAsymmetricMode < MainActivity.onCreateOptionsMenu | $
16:51:34.163 W T=8121 MainActivity$1.onClick < View.performClick | ^
16:51:34.165 W T=8121 MainActivity$1.onClick < View.performClick | $
16:52:00.504 W T=8121 MainActivity.setAsymmetricMode < MainActivity.onOptionsItemSelected | ^ false
16:52:00.508 W T=8121 MainActivity.setAsymmetricMode < MainActivity.onOptionsItemSelected | $
16:52:00.518 W T=8121 MainActivity$2.onTick < CountDownTimer$1.handleMessage | tickCounter=1
16:52:00.568 W T=8121 MainActivity$2.onTick < CountDownTimer$1.handleMessage | tickCounter=2
16:52:00.606 W T=8121 MainActivity$2.onFinish < CountDownTimer$1.handleMessage | ^
16:52:00.607 W T=8121 MainActivity$2.onFinish < CountDownTimer$1.handleMessage | $
16:52:00.607 E T=8382 MainActivity$1.lambda$onClick$0$com-example-basicactivity-MainActivity$1 < MainActivity$1$$ExternalSyntheticLambda0.run | mIsAsymmetricMode=false
Now... what I am trying to understand is why when I remove the
mIsAsymmetricMode.notifyAll();
statement in onFinish(), that logE() message is never released as it seems that the while loop gets stuck at the
mIsAsymmetricMode.wait();
and never gets to check for the flag and see that it changed to false.
In other words, are there any circumstances in which the while (mIsAsymmetricMode.get()) loop will terminate without issuing the mIsAsymmetricMode.notifyAll() (or mIsAsymmetricMode.notify()) call?
Update: After being helped by the accepted answer, I highly recommend testing the flag before entering the while() loop - despite it being tested by the while() loop anyway. The reason is that it allows placing debug logs as follows:
new Thread( () -> {
synchronized(mIsAsymmetricMode) {
if (mIsAsymmetricMode.get()) {
logD("ASYMMETRIC", "while() loop entered");
while (mIsAsymmetricMode.get()) {
try {
mIsAsymmetricMode.wait();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
else {
logW("ASYMMETRIC", "while() loop NOT entered");
}
}
}).start();

Here is how it works in general:
Thread acquires the lock on the object (mIsAsymmetricMode in your case).
Thread releases the lock on the object and enters the waiting state.
Other thread acquires the lock on the object and make changes to the object.
Other thread releases the lock on the object.
The thread that was waiting is notified via notify() or notifyAll().
The thread wakes up and re-acquires the lock on the object.
The thread checks the object state and exits the loop if the condition is met.
Notice #5 and #6: Without notify() or notifyAll(), there is no chance for the waiting thread of ever knowing that the object state has changed. Therefore it has no chance of exiting the loop via met condition.
This confirms what you are seeing on the code you posted. It works correctly.
If you have more complex code that exhibits a different behavior, a possible explanations could be a race condition, where the value of mIsAsymmetricMode is being changed to false right before the thread enters the wait state.
Also, check whether the AtomicBoolean in your other code is final.

Related

Aborting worker object in QThread

I know that there are many posts on this topic and i've read them and thought i also understood them. But I am still having a problem with aborting a QThread or rather a worker object in a QThread.
I have an GUI application and a library. The GUI can request the library to exec and abort worker objects (is connected to WorkerHandler slots). The WorkerHandler can create several WorkerObjects that all inherit from a base class. I tried to reduce the code for this example, but it's still some kind of verbose.
Gui.h
class Gui : public QMainWindow
{
Q_OBJECT
public:
Gui(QWidget *parent = 0);
~Gui();
private:
Ui::GuiClass ui;
QThread *workerHandlerThread;
WorkerHandler *workerHandler;
void connectActions();
signals:
void execWorker(WorkerParams _params);
void abortWorker(WorkerType type);
slots:
void buttonExecPressed();
void buttonAbortPressed();
}
Gui.cpp
void Gui::Gui()
{
ui.btnExecA->setProperty("type", QVariant::fromValue(WorkerType::A)); //WorkerType is just a enum, bin type to button
ui.btnExecB->setProperty("type", QVariant::fromValue(WorkerType::B));
ui.btnAbortA->setProperty("type", QVariant::fromValue(WorkerType::A));
ui.btnAbortB->setProperty("type", QVariant::fromValue(WorkerType::B));
connectActions();
workerHandlerThread = new QThread();
workerHandler = new WorkerHandler();
workerHandler->moveToThread(workerHandlerThread); // move worker execution to another thread
workerHandlerThread->start(); //start will call run and run will run the QEventLoop of QThread by calling exec
}
void Gui::~Gui()
{
workerHandlerThread->quit();
workerHandlerThread->wait();
delete workerHandlerThread;
delete workerHandler;
}
void Gui::connectActions()
{
connect(ui.btnExecA, &QPushButton::clicked, this, &Gui::buttonExecPressed);
connect(ui.btnExecB, &QPushButton::clicked, this, &Gui::buttonExecPressed);
connect(ui.btnAbortA, &QPushButton::clicked, this, &Gui::buttonAbortPressed);
connect(ui.btnAbortB, &QPushButton::clicked, this, &Gui::buttonAbortPressed);
connect(this, &Gui::execWorker, workerHandler, &WorkerHandler::execWorker);
connect(this, &Gui::abortWorker, workerHandler, &WorkerHandler::abortWorker);
}
void Gui::buttonExecPressed()
{
QPushButton* button = qobject_cast<QPushButton*>(sender());
if (button)
{
WorkerType type = button->property("type").value<WorkerType>(); //get worker type
WorkerParams params = WorkerParamsFactory::Get()->CreateParams(type); //WorkerParamsFactory cretes default parameters based on type
emit execWorker(params); //tell WorkerHandler to create a workerObject based on these parameters
}
}
void Gui::buttonAbortPressed()
{
QPushButton* button = qobject_cast<QPushButton*>(sender());
if (button)
{
WorkerType type = button->property("type").value<WorkerType>();
emit abortWorker(type); //tell WorkerHandler to abort a specific workerObject
}
}
WorkerHandler.h
class WorkerHandler : public QObject {
Q_OBJECT
public:
WorkerHandler(QObject * parent = Q_NULLPTR);
~WorkerHandler();
public slots:
void execWorker(WorkerParams _params);
void abortWorker(WorkerType type);
private:
QMap<WorkerType, WorkerObjectBase*> workerPool; //contains the workerobjects
};
WorkerHandler.cpp
void WorkerHandler::execWorker(WorkerParams _params)
{
QThread *thread = new QThread();
WorkerObjectBase *worker = WorkerObjectFactory::Get()->CreateWorker(_params); //Factory to create specific Worker Object based on given params
worker->moveToThread(thread);
connect(thread, &QThread::started, workerThread, &WorkerObjectBase::process);
connect(workerThread, &WorkerObjectBase::workerFinished, thread, &QThread::quit); //quit the QThread when worker is finished
connect(thread, &QThread::finished, thread, &QThread::deleteLater); //free resources when thread is finished
connect(thread, &QThread::finished, workerThread, &WorkerObjectBase::deleteLater); //free resources when thread is finished
workerPool.insert(_params.type, worker); //_params.type contains WorkerType
thread->start(); //will call run of qthread which will call exec
}
void WorkerHandler::abortWorker(WorkerType type)
{
WorkerObjectBase *worker = workerPool.value(type);
worker->requestAbort();
QThread *workerThread = worker->thread();
if (workerThread)
{
if (!workerThread->wait(10000)) //will always block the 10 seconds and terminate the thread. using just wait() will block forever
{
workerThread->terminate();
}
}
}
WorkerHandlerBase.h
class WorkerObjectBase : public QObject {
Q_OBJECT
public:
WorkerObjectBase(QObject * parent = Q_NULLPTR);
~WorkerObjectBase();
void requestAbort();
protected:
//some WorkerObject basic parameters
bool abortRequested();
public slots:
virtual void process();
signals:
void workerFinished();
private:
QMutex abortMutex;
bool abort = false;
};
WorkerHandlerBase.cpp
void WorkerObjectBase::requestAbort()
{
abortMutex.lock();
abort = true;
abortMutex.unlock();
}
bool WorkerObjectBase::abortRequested()
{
bool abortRequested;
abortMutex.lock();
abortRequested = abort;
abortMutex.unlock();
return abortRequested;
}
WorkerObjectA.h
class WorkerObjectA : public WorkerObjectBase {
Q_OBJECT
public:
WorkerObjectA(QObject * parent = Q_NULLPTR);
~WorkerObjectA();
protected:
//some WorkerObjectA parameters
public slots:
void process();
};
WorkerObjectA.cpp
void WorkerObjectA::process()
{
while(!abortRequested())
{
//do some stuff
}
emit workerFinished();
}
The problem is, when i use wait, it blocks the signal processing. workerFinished is not handled and QThread does not quit. But I still don't get why. When i create a new worker object, i move it to a different thread. When this thread is started, it runs its own QEventLoop as stated in QThread
5.5 Documentation:
void QThread::run()
The starting point for the thread. After calling start(), the newly
created thread calls this function. The default implementation simply
calls exec().
So even if my WorkerHandler thread is blocking because of calling wait, the QThread of the specific workerObject should still manage to get the workerFinished signal and call the quit slot. If i don't use wait at all, everything is fine. But when something unexpected happens in the worker object process method that keeps it from emitting workerFinished, i want to be able to kill the thread the hard way.
So, what am i doing wrong?

Updating GUI from another class which implements SerialPortEventListener (Java FX, FXML)

I am making an application which uses serial communication. In SerialEvent method of that class, I am awaiting for a input from COM port, and then I want to pass it to the controller class of an .fxml screen.
Input will always be 8 bytes, and it works correctly inside that thread (I read the input and by printing it to the output, I see that the String is correct). However, when I try to pass it "in real time" to the controller class, I have a problem.
If I pass it directly, it does receieve it, but I can't invoke anything later (Not on FX Application Thread exception), I know that I can't do it that way, that I need to use Platform.runLater or similair solution, but if I use it that way, my controller class never receives that input, textField which I am trying to update stays blank.
I will copy part of the code here, and I am hoping that someone tell me what I'm doing wrong.
SERIALEVENT METHOD OF ANOTHER CLASS
#Override
public void serialEvent(SerialPortEvent spe) {
if (spe.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
try {
byte singleData = (byte) input.read();
logText = new String(new byte[]{singleData});
bytes.add(logText);
if(bytes.size() == 8) {
for (int i = 0; i < bytes.size(); i++) {
inputText += bytes.get(i);
}
if(inputText.length() == 8) {
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(inputText);
}
});
}
bytes.clear();
inputText = "";
}
} catch (Exception e) {
logText = "Failed to read data. (" + e.toString() + ")";
controller.getInputString(logText);
}
}
}
GETINPUT METHOD OF THE CONTROLLER CLASS
#Override
public void getInputString(String input) {
firstSerialNumberField.setText(input);
}
When using it this way, my firstSerialNumberField never gets that input.
---EDIT---
SETCONTROLLER METHOD OF THE SERIALPORTLISTENER CLASS
public void setController(SerialController controller) {
this.controller = controller;
}
INITIALIZE SCREEN IN SCREEN HANDLER CLASS
serialCommunication = new SerialCommunication(this);
loader = new FXMLLoader();
loader.setLocation(getClass().getResource(path));
pane = loader.load(getClass().getResource(path).openStream());
serialController = (SerialController) loader.getController();
serialController.setScreenHandler(this);
serialController.setSerialCommunication(serialCommunication);
serialCommunication.setController(serialController);
parent = loader.getRoot();
stage = new Stage();
stage.setScene(new Scene(parent));
stage.setTitle(title);
stage.setResizable(false);
stage.sizeToScene();
stage.centerOnScreen();
stage.initModality(Modality.APPLICATION_MODAL);
stage.showAndWait();
You are passing a reference to inputText to the (inappropriately-named) getInputText() method in the controller. inputText is presumably a field in the class implementing the port listener. However, as soon as you pass it, you then set it back to an empty string:
if(inputText.length() == 8) {
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(inputText);
}
});
}
bytes.clear();
inputText = "";
Since inputText is being accessed from multiple threads, there is no guarantee as to which order things will happen: whether controller.getInputText(inputText) will execute first, or whether inputText = ""; will execute first. So you may end up setting the text field to an empty string.
What I think you intend to do is:
if(inputText.length() == 8) {
final String numberFieldText = inputText ;
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.getInputString(numberFieldText);
}
});
}
or more succinctly:
if(inputText.length() == 8) {
final String numberFieldText = inputText ;
Platform.runLater(() -> controller.getInputString(numberFieldText));
}

Is TextToSpeech supported on Google Glass?

I was wondering if TextToSpeech is supported on Google Glass?
I did something like this:
public class TextToSpeechController implements TextToSpeech.OnInitListener{
private Context mContext;
private TextToSpeech tts;
public TextToSpeechController(Context context) {
Log.e("TEXT TO SPEECH CONTROLLER", "controller");
mContext = context;
tts = new TextToSpeech(context, this);
}
#Override
public void onInit(int status) {
Log.e("INIT TTS", "INIT");
if (status == TextToSpeech.SUCCESS) {
int result = tts.setLanguage(Locale.ENGLISH);
if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) {
Toast.makeText(mContext, "This Language is not supported", Toast.LENGTH_LONG).show();
}
else {
Toast.makeText(mContext, "Ready to Speak", Toast.LENGTH_LONG).show();
speakTheText("Welcome to Vision Screening App");
}
}
else {
Toast.makeText(mContext, "Can Not Speak", Toast.LENGTH_LONG).show();
}
}
public void stopTTS(){
Log.e(".....TTS", "SHUTDOWN");
tts.stop();
tts.shutdown();
}
public void speakTheText(String str){
Log.e("SPEAK TEXT!!!!", "SPEAK TEXT");
tts.speak(str, TextToSpeech.QUEUE_FLUSH, null);
}
}
and in my Activity (onCreate) I have:
controller_tts = new TextToSpeechController(getApplicationContext());
I face several problems :
First of all the onInit method is not called at all, only at the moment when I exit the current Activity.
Somehow, after using TTS, the speeker's volume turns to mute and I cannot turn the volume back from the settings(only after I reboot the Glasses)
Am I doing something wrong? or simply Google Glass does not support TTS, even thought is hard to believe that.
Any suggestion is welcome! Thank you very much!:)
Is it possible that you are calling stopTTS before TextToSpeech is initialized?
This works just fine for me on Glass:
import android.app.Activity;
import android.os.Bundle;
import android.speech.tts.TextToSpeech;
import android.view.MotionEvent;
import android.widget.TextView;
import java.util.Locale;
public class TTSTestActivity extends Activity
implements TextToSpeech.OnInitListener {
private TextToSpeech tts;
private boolean initialized = false;
private String queuedText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView view = new TextView(this);
view.setText("Tap Me");
setContentView(view);
tts = new TextToSpeech(this /* context */, this /* listener */);
}
#Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
initialized = true;
tts.setLanguage(Locale.ENGLISH);
if (queuedText != null) {
speak(queuedText);
}
}
}
public void speak(String text) {
// If not yet initialized, queue up the text.
if (!initialized) {
queuedText = text;
return;
}
queuedText = null;
// Before speaking the current text, stop any ongoing speech.
tts.stop();
// Speak the text.
tts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
}
#Override
public boolean onGenericMotionEvent(MotionEvent event) {
// On any motion event (including touchpad tap), say 'Hello Glass'
speak("Hello Glass");
return true;
}
}
With this example, anytime you tap the touch pad (or cause any other type of motion event), you should hear "Hello Glass." Note that if text is provided before TextToSpeech has initialized, then this is queued and then spoken after initialization is a success.
This does not include any tear-down, but to do that you can always put stop/shutdown of TextToSpeech in onDestroy() of the activity.

Unpredictable result of DriveId.getResourceId() in Google Drive Android API

The issue is that the 'resourceID' from 'DriveId.getResourceId()' is not available (returns NULL) on newly created files (product of 'DriveFolder.createFile(GAC, meta, cont)'). If the file is retrieved by a regular list or query procedure, the 'resourceID' is correct.
I suspect it is a timing/latency issue, but it is not clear if there is an application action that would force refresh. The 'Drive.DriveApi.requestSync(GAC)' seems to have no effect.
UPDATE (07/22/2015)
Thanks to the prompt response from Steven Bazyl (see comments below), I finally have a satisfactory solution using Completion Events. Here are two minified code snippets that deliver the ResourceId to the app as soon as the newly created file is propagated to the Drive:
File creation, add change subscription:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "_X_";
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null,
new ExecutionOptions.Builder()
.setNotifyOnCompletion(true)
.build()
)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
DriveId driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), driveId);
file.addChangeSubscription(getGoogleApiClient());
}
}
});
}
}
Event Service, catches the completion:
public class ChngeSvc extends DriveEventService {
private static final String TAG = "_X_";
#Override
public void onCompletion(CompletionEvent event) { super.onCompletion(event);
DriveId driveId = event.getDriveId();
Log.d(TAG, "onComplete: " + driveId.getResourceId());
switch (event.getStatus()) {
case CompletionEvent.STATUS_CONFLICT: Log.d(TAG, "STATUS_CONFLICT"); event.dismiss(); break;
case CompletionEvent.STATUS_FAILURE: Log.d(TAG, "STATUS_FAILURE"); event.dismiss(); break;
case CompletionEvent.STATUS_SUCCESS: Log.d(TAG, "STATUS_SUCCESS "); event.dismiss(); break;
}
}
}
Under normal circumstances (wifi), I get the ResourceId almost immediately.
20:40:53.247﹕Created a empty file: DriveId:CAESABiiAiDGsfO61VMoAA==
20:40:54.305: onComplete, ResourceId: 0BxOS7mTBMR_bMHZRUjJ5NU1ZOWs
... done for now.
ORIGINAL POST, deprecated, left here for reference.
I let this answer sit for a year hoping that GDAA will develop a solution that works. The reason for my nagging is simple. If my app creates a file, it needs to broadcast this fact to its buddies (other devices, for instance) with an ID that is meaningful (that is ResourceId). It is a trivial task under the REST Api where ResourceId comes back as soon as the file is successfully created.
Needles to say that I understand the GDAA philosophy of shielding the app from network primitives, caching, batching, ... But clearly, in this situation, the ResourceID is available long before it is delivered to the app.
Originally, I implemented Cheryl Simon's suggestion and added a ChangeListener on a newly created file, hoping to get the ResourceID when the file is propagated. Using classic CreateEmptyFileActivity from android-demos, I smacked together the following test code:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "CreateEmptyFileActivity";
final private ChangeListener mChgeLstnr = new ChangeListener() {
#Override
public void onChange(ChangeEvent event) {
Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
}
};
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
DriveId driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);
}
}
});
}
}
... and was waiting for something to happen. File was happily uploaded to the Drive within seconds, but no onChange() event. 10 minutes, 20 minutes, ... I could not find any way how to make the ChangeListener to wake up.
So the only other solution, I could come up was to nudge the GDAA. So I implemented a simple handler-poker that tickles the metadata until something happens:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "CreateEmptyFileActivity";
final private ChangeListener mChgeLstnr = new ChangeListener() {
#Override
public void onChange(ChangeEvent event) {
Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
}
};
static DriveId driveId;
private static final int ENOUGH = 4; // nudge 4x, 1+2+3+4 = 10seconds
private static int mWait = 1000;
private int mCnt;
private Handler mPoker;
private final Runnable mPoke = new Runnable() { public void run() {
if (mPoker != null && driveId != null && driveId.getResourceId() == null && (mCnt++ < ENOUGH)) {
MetadataChangeSet meta = new MetadataChangeSet.Builder().build();
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).updateMetadata(getGoogleApiClient(), meta).setResultCallback(
new ResultCallback<DriveResource.MetadataResult>() {
#Override
public void onResult(DriveResource.MetadataResult result) {
if (result.getStatus().isSuccess() && result.getMetadata().getDriveId().getResourceId() != null)
Log.d(TAG, "resId COOL " + result.getMetadata().getDriveId().getResourceId());
else
mPoker.postDelayed(mPoke, mWait *= 2);
}
}
);
} else {
mPoker = null;
}
}};
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);
mCnt = 0;
mPoker = new Handler();
mPoker.postDelayed(mPoke, mWait);
}
}
});
}
}
And voila, 4 seconds (give or take) later, the ChangeListener delivers a new shiny ResourceId. Of course, the ChangeListener becomes thus obsolete, since the poker routine gets the ResourceId as well.
So this is the answer for those who can't wait for the ResourceId. Which brings up the follow-up question:
Why do I have to tickle metadata (or re-commit content), very likely creating unnecessary network traffic, to get onChange() event, when I see clearly that the file has been propagated a long time ago, and GDAA has the ResourceId available?
ResourceIds become available when the newly created resource is committed to the server. In the case of a device that is offline, this could be arbitrarily long after the initial file creation. It will happen as soon as possible after the creation request though, so you don't need to do anything to speed it along.
If you really need it right away, you could conceivably use the change notifications to listen for the resourceId to change.

Load external properties files into EJB 3 app running on WebLogic 11

Am researching the best way to load external properties files from and EJB 3 app whose EAR file is deployed to WebLogic.
Was thinking about using an init servlet but I read somewhere that it would be too slow (e.g. my message handler might receive a message from my JMS queue before the init servlet runs).
Suppose I have multiple property files or one file here:
~/opt/conf/
So far, I feel that the best possible solution is by using a Web Logic application lifecycle event where the code to read the properties files during pre-start:
import weblogic.application.ApplicationLifecycleListener;
import weblogic.application.ApplicationLifecycleEvent;
public class MyListener extends ApplicationLifecycleListener {
public void preStart(ApplicationLifecycleEvent evt) {
// Load properties files
}
}
See: http://download.oracle.com/docs/cd/E13222_01/wls/docs90/programming/lifecycle.html
What would happen if the server is already running, would post start be a viable solution?
Can anyone think of any alternative ways that are better?
It really depends on how often you want the properties to be reloaded. One approach I have taken is to have a properties file wrapper (singleton) that has a configurable parameter that defines how often the files should be reloaded. I would then always read properties through that wrapper and it would reload the properties ever 15 minutes (similar to Log4J's ConfigureAndWatch). That way, if I wanted to, I can change properties without changing the state of a deployed application.
This also allows you to load properties from a database, instead of a file. That way you can have a level of confidence that properties are consistent across the nodes in a cluster and it reduces complexity associated with managing a config file for each node.
I prefer that over tying it to a lifecycle event. If you weren't ever going to change them, then make them static constants somewhere :)
Here is an example implementation to give you an idea:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
/**
* User: jeffrey.a.west
* Date: Jul 1, 2011
* Time: 8:43:55 AM
*/
public class ReloadingProperties
{
private final String lockObject = "LockMe";
private long lastLoadTime = 0;
private long reloadInterval;
private String filePath;
private Properties properties;
private static final Map<String, ReloadingProperties> instanceMap;
private static final long DEFAULT_RELOAD_INTERVAL = 1000 * 60 * 5;
public static void main(String[] args)
{
ReloadingProperties props = ReloadingProperties.getInstance("myProperties.properties");
System.out.println(props.getProperty("example"));
try
{
Thread.sleep(6000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println(props.getProperty("example"));
}
static
{
instanceMap = new HashMap(31);
}
public static ReloadingProperties getInstance(String filePath)
{
ReloadingProperties instance = instanceMap.get(filePath);
if (instance == null)
{
instance = new ReloadingProperties(filePath, DEFAULT_RELOAD_INTERVAL);
synchronized (instanceMap)
{
instanceMap.put(filePath, instance);
}
}
return instance;
}
private ReloadingProperties(String filePath, long reloadInterval)
{
this.reloadInterval = reloadInterval;
this.filePath = filePath;
}
private void checkRefresh()
{
long currentTime = System.currentTimeMillis();
long sinceLastLoad = currentTime - lastLoadTime;
if (properties == null || sinceLastLoad > reloadInterval)
{
System.out.println("Reloading!");
lastLoadTime = System.currentTimeMillis();
Properties newProperties = new Properties();
FileInputStream fileIn = null;
synchronized (lockObject)
{
try
{
fileIn = new FileInputStream(filePath);
newProperties.load(fileIn);
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (fileIn != null)
{
try
{
fileIn.close();
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
properties = newProperties;
}
}
}
public String getProperty(String key, String defaultValue)
{
checkRefresh();
return properties.getProperty(key, defaultValue);
}
public String getProperty(String key)
{
checkRefresh();
return properties.getProperty(key);
}
}
Figured it out...
See the corresponding / related post on Stack Overflow.