/**
* Get more info at : www.jrebirth.org .
* Copyright JRebirth.org © 2011-2013
* Contact : sebastien.bordes@jrebirth.org
*
* 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.
*/
package org.jrebirth.af.core.link;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.jrebirth.af.core.command.Command;
import org.jrebirth.af.core.command.basic.showmodel.ShowModelWaveBuilder;
import org.jrebirth.af.core.concurrent.JRebirth;
import org.jrebirth.af.core.exception.JRebirthThreadException;
import org.jrebirth.af.core.exception.WaveException;
import org.jrebirth.af.core.facade.AbstractGlobalReady;
import org.jrebirth.af.core.facade.GlobalFacade;
import org.jrebirth.af.core.facade.WaveReady;
import org.jrebirth.af.core.log.JRLogger;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.resource.provided.JRebirthParameters;
import org.jrebirth.af.core.service.Service;
import org.jrebirth.af.core.service.ServiceTask;
import org.jrebirth.af.core.service.basic.TaskTrackerService;
import org.jrebirth.af.core.ui.Model;
import org.jrebirth.af.core.wave.JRebirthWaves;
import org.jrebirth.af.core.wave.Wave;
import org.jrebirth.af.core.wave.Wave.Status;
import org.jrebirth.af.core.wave.WaveType;
import org.jrebirth.af.core.wave.checker.WaveChecker;
/**
*
* The class <strong>NotifierImpl</strong>.
*
* An implementation that allow to send and to rpocess wave message.
*
* @author Sébastien Bordes
*/
public class NotifierBase extends AbstractGlobalReady implements Notifier, LinkMessages {
/** The class logger. */
private static final JRLogger LOGGER = JRLoggerFactory.getLogger(NotifierBase.class);
/** The map that store link between wave type and objects interested. */
private final Map<WaveType, WaveSubscription> notifierMap = new HashMap<>();
/** The handler used to handle unprocessed waves. */
private final UnprocessedWaveHandler unprocessedWaveHandler;
/**
* Default Constructor.
*
* @param globalFacade the global facade of the application
*/
public NotifierBase(final GlobalFacade globalFacade) {
super(globalFacade);
UnprocessedWaveHandler waveHandler;
try {
waveHandler = (UnprocessedWaveHandler) JRebirthParameters.UNPROCESSED_WAVE_HANDLER.get().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
LOGGER.error(USE_DEFAULT_WAVE_HANDLER, e);
waveHandler = new DefaultUnprocessedWaveHandler();
}
// Attach the right Component Factory
this.unprocessedWaveHandler = waveHandler;
}
/**
* {@inheritDoc}
*/
@Override
public void sendWave(final Wave wave) throws JRebirthThreadException {
wave.setStatus(Status.Processing);
JRebirth.checkJIT();
// Perform different action according to Wave Group used
try {
switch (wave.getWaveGroup()) {
case CALL_COMMAND:
callCommand(wave);
break;
case RETURN_DATA:
returnData(wave);
break;
case ATTACH_UI:
displayUi(wave);
break;
case UNDEFINED:
default:
processUndefinedWave(wave);
}
} catch (final WaveException e) {
LOGGER.error(WAVE_SENDING_ERROR, e);
}
}
/**
* Call dynamically a command.
*
* According to its runIntoType the command will be run into JAT, JIT or a Thread Pool
*
* Each time a new fresh command will be retrieved.
*
* This method is called from the JIT (JRebirth Internal Thread)<br>
*
* @param wave the wave that contains all informations
*/
@SuppressWarnings("unchecked")
private void callCommand(final Wave wave) {
// Use the Wave UID to guarantee that a new fresh command is built and used !
final Command command = wave.contains(JRebirthWaves.REUSE_COMMAND) && wave.get(JRebirthWaves.REUSE_COMMAND)
? getGlobalFacade().getCommandFacade().retrieve((Class<Command>) wave.getRelatedClass())
: getGlobalFacade().getCommandFacade().retrieve((Class<Command>) wave.getRelatedClass(), wave.getWUID());
if (command == null) {
LOGGER.error(COMMAND_NOT_FOUND_ERROR, wave.toString());
if (JRebirthParameters.DEVELOPER_MODE.get()) {
this.unprocessedWaveHandler.manageUnprocessedWave(COMMAND_NOT_FOUND_MESSAGE.getText(), wave);
}
} else {
// Run the command into the predefined thread
command.run(wave);
}
}
/**
* Call a service method by using a task worker.
*
* The same service will be retrieved each time this method is called.
*
* This method is called from the JIT (JRebirth Internal Thread)<br />
*
* @param wave the wave that contains all informations
*/
@SuppressWarnings("unchecked")
private void returnData(final Wave wave) {
// Use only the Service class to retrieve the same instance each time
final Service service = getGlobalFacade().getServiceFacade().retrieve((Class<Service>) wave.getRelatedClass());
if (service == null) {
LOGGER.error(SERVICE_NOT_FOUND_ERROR, wave.toString());
if (JRebirthParameters.DEVELOPER_MODE.get()) {
this.unprocessedWaveHandler.manageUnprocessedWave(SERVICE_NOT_FOUND_MESSAGE.getText(), wave);
}
} else {
// The inner task will be run into the JRebirth Thread Pool
final ServiceTask<?> task = service.returnData(wave);
if (task != null && JRebirthParameters.FOLLOW_UP_SERVICE_TASKS.get()) {
getGlobalFacade().getServiceFacade().retrieve(TaskTrackerService.class).trackTask(task);
}
}
}
/**
* Display dynamically an Ui model.<br>
*
* This method is called from the JIT (JRebirth Internal Thread)<br>
*
* Creates the model and its root node.<br>
* Then attach it according to the placeholder defined into the wave:<br>
* <ul>
* <li>JRebirthWaves.ATTACH_UI_NODE_PLACEHOLDER : to replace a property node by the model's root node</li>
* <li>JRebirthWaves.ADD_UI_CHILDREN_PLACEHOLDER : to add the model's root node into a children list</li>
* </ul>
*
* @param wave the wave that contains all informations
*/
@SuppressWarnings("unchecked")
private void displayUi(final Wave wave) {
if (wave.getRelatedClass() != null) {
LOGGER.error(MODEL_NOT_FOUND_ERROR, wave.toString());
if (JRebirthParameters.DEVELOPER_MODE.get()) {
this.unprocessedWaveHandler.manageUnprocessedWave(MODEL_NOT_FOUND_MESSAGE.getText(), wave);
}
}
// This key method could be managed in another way (fully sync with JAT), to see if it could be useful
// Build the wave used to call the required command
final ShowModelWaveBuilder smwb = ShowModelWaveBuilder.create()
.showModelKey(getGlobalFacade().getUiFacade().buildKey((Class<Model>) wave.getRelatedClass()));
if (wave.contains(JRebirthWaves.ATTACH_UI_NODE_PLACEHOLDER)) {
// Add the Ui view into the place holder provided
smwb.uniquePlaceHolder(wave.get(JRebirthWaves.ATTACH_UI_NODE_PLACEHOLDER));
} else if (wave.contains(JRebirthWaves.ADD_UI_CHILDREN_PLACEHOLDER)) {
// Add the Ui view into the children list of its parent container
smwb.childrenPlaceHolder(wave.get(JRebirthWaves.ADD_UI_CHILDREN_PLACEHOLDER));
}
// Call the command that manage the display UI in 2 steps
// 1 - Create the model into the Thread Pool
// 2 - Attach it to the graphical tree model according to their placeholder type
callCommand(smwb.build());
}
/**
* Dispatch a standard wave which could be handled by a custom method of the component.
*
* This method is called from the JIT (JRebirth Internal Thread)<br>
*
* @param wave the wave that contains all information
*
* @throws WaveException if wave dispatching fails
*/
private void processUndefinedWave(final Wave wave) throws WaveException {
// Retrieve all interested object from the map
if (this.notifierMap.containsKey(wave.getWaveType())) {
final WaveSubscription ws = this.notifierMap.get(wave.getWaveType());
// For each object interested in that wave type, process the action
for (final WaveHandler waveHandler : ws.getWaveHandlers()) {
if (waveHandler.check(wave)) {
waveHandler.handle(wave);
// // If the notified class is part of the UI
// // We must perform this action into the JavaFX Application Thread
// if (waveHandler.getWaveReady() instanceof Model) {
// JRebirth.runIntoJAT(LoopBuilder.newRunnable(waveHandler.getWaveReady(), wave));
// } else {
// // Otherwise can perform it right now into the current thread (JRebirthThread - JIT)
// waveHandler.getWaveReady().handle(wave);
// }
}
}
} else {
LOGGER.warn(NO_WAVE_LISTENER, wave.getWaveType().toString());
if (JRebirthParameters.DEVELOPER_MODE.get()) {
this.unprocessedWaveHandler.manageUnprocessedWave(NO_WAVE_LISTENER.getText(wave.getWaveType().toString()), wave);
}
}
LOGGER.info(NOTIFIER_CONSUMES, wave.toString());
wave.setStatus(Status.Consumed);
}
// /**
// * {@inheritDoc}
// */
// @Override
// public void listen(final WaveReady<?> linkedObject, final WaveType... waveTypes) throws JRebirthThreadException {
// // Call the other method with null waveChecker
// listen(linkedObject, null, null, waveTypes);
// }
//
// /**
// * {@inheritDoc}
// */
// @Override
// public void listen(final WaveReady<?> linkedObject, final Method method, final WaveType... waveTypes) throws JRebirthThreadException {
// // Call the other method with null waveChecker
// listen(linkedObject, null, method, waveTypes);
// }
/**
* {@inheritDoc}
*/
@Override
public void listen(final WaveReady<?> linkedObject, final WaveChecker waveChecker, final Method method, final WaveType... waveTypes) throws JRebirthThreadException {
JRebirth.checkJIT();
// For each given wave type, add linked Object to call
for (final WaveType waveType : waveTypes) {
// IF this wave type isn't registered into the map, we add it with an empty list of LinkedObject
if (!this.notifierMap.containsKey(waveType)) {
this.notifierMap.put(waveType, LoopBuilder.newSubscription(waveType));
}
// Retrieve he list associated to this Wave Type
final WaveSubscription ws = this.notifierMap.get(waveType);
// Remove the linked object to unregister it
boolean contains = false;
for (int i = 0; !contains && i < ws.getWaveHandlers().size(); i++) {
if (ws.getWaveHandlers().get(i).getWaveReady().equals(linkedObject)) {
contains = true;
}
}
if (ws.getWaveHandlers().isEmpty() || !contains) {
// Add the linked object if the list is empty or if the object isn't yet contained
ws.getWaveHandlers().add(LoopBuilder.newHandler(linkedObject, waveChecker, method));
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void unlisten(final WaveReady<?> linkedObject, final WaveType... waveTypes) throws JRebirthThreadException {
JRebirth.checkJIT();
// For each given wave type, remove linked Object to avoid calling them anymore
for (final WaveType waveType : waveTypes) {
WaveSubscription ws;
if (this.notifierMap.containsKey(waveType)) {
// Retrieve the list of linked object associated to this Wave Type
ws = this.notifierMap.get(waveType);
// When the linkedObject is removed stop the iteration
boolean removed = false;
// Remove the linked object to unregister it
for (int i = ws.getWaveHandlers().size() - 1; !removed && i >= 0; i--) {
if (ws.getWaveHandlers().get(i).getWaveReady().equals(linkedObject)) {
ws.getWaveHandlers().remove(i);
removed = true;
}
}
// Remove the Wave Type from the map if there isn't any linked object left
if (ws.getWaveHandlers().isEmpty()) {
this.notifierMap.remove(waveType);
}
}
}
}
/**
* The class <strong>LoopBuilder</strong>.
*
* Used to instantiate object into loop.
*
* @author Sébastien Bordes
*/
private static final class LoopBuilder {
/**
* Private Constructor.
*/
private LoopBuilder() {
// Nothing to do
}
/**
* Build a new {@link WaveSubscription}.
*
* @param waveType the Wave Type listened
*
* @return a new {@link WaveSubscription} instance
*/
public static WaveSubscription newSubscription(final WaveType waveType) {
return new WaveSubscription(waveType, new ArrayList<WaveHandler>());
}
/**
* Build a new empty component wrapper.
*
* @param linkedObject the object to wrap into an handler
* @param waveChecker the wave checker
* @param method the method used to handle the wave (could be null)
*
* @return a new instance of WaveHandler
*/
public static WaveHandler newHandler(final WaveReady<?> linkedObject, final WaveChecker waveChecker, final Method method) {
return new WaveHandler(linkedObject, waveChecker, method);
}
}
}