org.sputnikdev.bluetooth.manager.impl.BluetoothObjectGovernor Maven / Gradle / Ivy
package org.sputnikdev.bluetooth.manager.impl;
/*-
* #%L
* org.sputnikdev:bluetooth-manager
* %%
* Copyright (C) 2017 Sputnik Dev
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sputnikdev.bluetooth.URL;
import org.sputnikdev.bluetooth.manager.BluetoothGovernor;
import org.sputnikdev.bluetooth.manager.GovernorListener;
import org.sputnikdev.bluetooth.manager.NotReadyException;
/**
* A root class for all governors in the system. Defines lifecycle and error handling/recovery processes for governors.
*
* The lifecycle is divided on the following stages:
*
* - Native object acquisition
* - Error handling and recovery
* - Governing/maintaining state of the bluetooth object
*
*
* All the stages above are handled in {@link #update()} method. When invoked, this method tries to acquire
* a native object for corresponding device first. When acquired, {@link #init(BluetoothObject)} method is called
* to perform initialisation (setting initial state, subscribing to the object events), then all registered governor
* listeners are notified that the governor is "ready" by triggering {@link GovernorListener#ready(boolean)} method.
* Once initialised, the {@link #update()} method switches to the next stage - maintaining state of the bluetooth
* object by invoking {@link #update(BluetoothObject)} method, this is where main manipulations with
* the bluetooth object are happening. If any exception occurs in the {@link #update(BluetoothObject)} method,
* the Error handling stage begins by triggering {@link #reset(BluetoothObject)} method, which must revert
* internal state of the governor to the initial state - the Native object acquisition.
*
* In short this looks like that:
*
* The update method is called outside of the governor (by a separate thread):
* governor.update();
*
* then the following happens:
*
* {@link #findBluetoothObject()}
* {@link #init(BluetoothObject)}
* {@link GovernorListener#ready(boolean)} with argument - true
* {@link #update(BluetoothObject)}
*
* if the {@link #update(BluetoothObject)} method throws any exception, then
* {@link #reset(BluetoothObject)}
* {@link GovernorListener#ready(boolean)} with argument - false
* are invoked, which brings the governor to its initial state, where everything begins from the start.
*
* In order to help to release resources (native objects) {@link #reset(BluetoothObject)} method is used,
* which must release any acquired resources, disconnect and unsubscribe from notifications.
* It is recommended to call this method when, for example, a program exists or in any similar cases.
*
* @author Vlad Kolotov
*/
abstract class BluetoothObjectGovernor implements BluetoothGovernor {
private Logger logger = LoggerFactory.getLogger(BluetoothObjectGovernor.class);
protected final BluetoothManagerImpl bluetoothManager;
protected final URL url;
private T bluetoothObject;
private Date lastActivity = new Date();
private final List governorListeners = new ArrayList<>();
BluetoothObjectGovernor(BluetoothManagerImpl bluetoothManager, URL url) {
this.bluetoothManager = bluetoothManager;
this.url = url;
}
@Override
public URL getURL() {
return url;
}
@Override
public boolean isReady() {
return bluetoothObject != null;
}
@Override
public void addGovernorListener(GovernorListener listener) {
synchronized (this.governorListeners) {
this.governorListeners.add(listener);
}
}
@Override
public void removeGovernorListener(GovernorListener listener) {
synchronized (this.governorListeners) {
this.governorListeners.remove(listener);
}
}
@Override
public Date getLastActivity() {
return lastActivity;
}
abstract T findBluetoothObject();
abstract void init(T object);
abstract void update(T object);
abstract void reset(T object);
T getBluetoothObject() throws NotReadyException {
if (bluetoothObject == null) {
throw new NotReadyException("Bluetooth object is not ready: " + url);
}
return bluetoothObject;
}
final synchronized void update() {
T bluetoothObject = getOrFindBluetoothObject();
if (bluetoothObject == null) {
return;
}
try {
logger.info("Updating governor state: {}", url);
update(bluetoothObject);
updateLastChanged();
notifyLastChanged();
} catch (Exception ex) {
logger.error("Could not update governor state.", ex);
reset();
}
}
final void reset() {
logger.info("Resetting governor: " + url);
if (this.bluetoothObject != null) {
reset(this.bluetoothObject);
notifyReady(false);
}
this.bluetoothObject = null;
logger.info("Governor has been reset: " + url);
}
void updateLastChanged() {
this.lastActivity = new Date();
}
void notifyReady(boolean ready) {
synchronized (this.governorListeners) {
for (GovernorListener listener : this.governorListeners) {
try {
listener.ready(ready);
} catch (Exception ex) {
logger.error("Execution error of a governor listener: ready", ex);
}
}
}
}
void notifyLastChanged() {
synchronized (this.governorListeners) {
for (GovernorListener listener : this.governorListeners) {
try {
listener.lastUpdatedChanged(this.lastActivity);
} catch (Exception ex) {
logger.error("Execution error of a governor listener: last changed", ex);
}
}
}
}
synchronized private T getOrFindBluetoothObject() {
if (bluetoothObject == null) {
this.bluetoothObject = findBluetoothObject();
if (this.bluetoothObject != null) {
init(this.bluetoothObject);
notifyReady(true);
}
}
return bluetoothObject;
}
}