/**
* 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.List;
import java.util.Map;
import org.jrebirth.af.api.command.Command;
import org.jrebirth.af.api.component.basic.Component;
import org.jrebirth.af.api.exception.JRebirthThreadException;
import org.jrebirth.af.api.facade.GlobalFacade;
import org.jrebirth.af.api.key.UniqueKey;
import org.jrebirth.af.api.link.Notifier;
import org.jrebirth.af.api.link.UnprocessedWaveHandler;
import org.jrebirth.af.api.log.JRLogger;
import org.jrebirth.af.api.service.Service;
import org.jrebirth.af.api.ui.Model;
import org.jrebirth.af.api.wave.Wave;
import org.jrebirth.af.api.wave.Wave.Status;
import org.jrebirth.af.api.wave.checker.WaveChecker;
import org.jrebirth.af.api.wave.contract.WaveType;
import org.jrebirth.af.core.command.basic.showmodel.DisplayModelWaveBean;
import org.jrebirth.af.core.command.basic.showmodel.ShowModelCommand;
import org.jrebirth.af.core.concurrent.JRebirth;
import org.jrebirth.af.core.exception.WaveException;
import org.jrebirth.af.core.facade.AbstractGlobalReady;
import org.jrebirth.af.core.key.Key;
import org.jrebirth.af.core.log.JRLoggerFactory;
import org.jrebirth.af.core.resource.provided.JRebirthParameters;
import org.jrebirth.af.core.service.ServiceTaskBase;
import org.jrebirth.af.core.service.basic.TaskTrackerService;
import org.jrebirth.af.core.util.ParameterUtility;
import org.jrebirth.af.core.wave.Builders;
import org.jrebirth.af.core.wave.JRebirthWaves;
/**
*
* 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);
// Attach the right EnhancedComponent Factory
this.unprocessedWaveHandler = (UnprocessedWaveHandler)
ParameterUtility.buildCustomizableClass(JRebirthParameters.UNPROCESSED_WAVE_HANDLER,
DefaultUnprocessedWaveHandler.class,
"UnprocessedWaveHandler");
}
/**
* {@inheritDoc}
*/
@Override
public void sendWave(final Wave wave) throws JRebirthThreadException {
wave.status(Status.Processing);
JRebirth.checkJIT();
// Perform different action according to Wave Group used
try {
switch (wave.waveGroup()) {
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.componentClass())
: getGlobalFacade().getCommandFacade().retrieve((Class<Command>) wave.componentClass(), 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.componentClass());
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 ServiceTaskBase<?> task = (ServiceTaskBase<?>) 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.componentClass() == 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
DisplayModelWaveBean displayModelWaveBean;
// Build the wave used to call the required command
// if (wave.contains(JRebirthWaves.EXTRA_WAVE_BEANS)) {
// waveBean = wave.getData(JRebirthWaves.EXTRA_WAVE_BEANS).getValue();
// } else {
displayModelWaveBean = DisplayModelWaveBean.create();
// }
displayModelWaveBean.showModelKey((UniqueKey<Model>) Key.create(wave.componentClass()));
if (wave.contains(JRebirthWaves.ATTACH_UI_NODE_PLACEHOLDER)) {
// Add the Ui view into the place holder provided
displayModelWaveBean.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
displayModelWaveBean.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
Class<? extends Command> showModelCommandClass;
if (wave.contains(JRebirthWaves.SHOW_MODEL_COMMAND)) {
showModelCommandClass = wave.getData(JRebirthWaves.SHOW_MODEL_COMMAND).getValue();
} else {
showModelCommandClass = ShowModelCommand.class;
}
callCommand(Builders.callCommand(showModelCommandClass)
// Add all extra wave beans
.waveBeanList(wave.getData(JRebirthWaves.EXTRA_WAVE_BEANS).getValue())
// Add also DisplayModel Wave Bean
.waveBean(displayModelWaveBean));
}
/**
* 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 {
LOGGER.info(NOTIFIER_CONSUMES, wave.toString());
wave.status(Status.Consumed);
// Retrieve all interested object from the map
if (this.notifierMap.containsKey(wave.waveType())) {
final WaveSubscription ws = this.notifierMap.get(wave.waveType());
// Store all Wave handler into the wave in order to know if there is any handler left before
wave.setWaveHandlers(new ArrayList<WaveHandler>(ws.getWaveHandlers()));
// 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.waveType().toString());
if (JRebirthParameters.DEVELOPER_MODE.get()) {
this.unprocessedWaveHandler.manageUnprocessedWave(NO_WAVE_LISTENER.getText(wave.waveType().toString()), wave);
}
}
LOGGER.info(NOTIFIER_HANDLES, wave.toString());
wave.status(Status.Handled);
}
/**
* {@inheritDoc}
*/
@Override
public void listen(final Component<?> 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 the 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));
}
// // Manage return wave type registration
// WaveType returnWaveType = waveType.returnWaveType();
// if (returnWaveType != null) {
// listen(linkedObject, waveChecker, method, returnWaveType);
// }
}
}
/**
* {@inheritDoc}
*/
@Override
public void unlisten(final Component<?> 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);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void unlistenAll(final Component<?> linkedObject) throws JRebirthThreadException {
// This method is called into JIT (all method call are serialized)
// to avoid any thread concurrency trouble
JRebirth.checkJIT();
final List<WaveSubscription> toRemove = new ArrayList<WaveSubscription>();
// Iterate over all WaveSubscription
for (final WaveSubscription ws : this.notifierMap.values()) {
for (int i = ws.getWaveHandlers().size() - 1; i >= 0; i--) {
final WaveHandler wh = ws.getWaveHandlers().get(i);
// If the WaveHandler concern the linked object
if (wh.getWaveReady() == linkedObject) {
// Remove it from registration list
ws.getWaveHandlers().remove(i);
}
}
// If this WaveSubscription doesn't contain any WaveHandler remove the entry.
if (ws.getWaveHandlers().isEmpty()) {
toRemove.add(ws);
}
}
for (final WaveSubscription ws : toRemove) {
this.notifierMap.remove(ws.getWaveType());
}
}
/**
* 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 Component<?> linkedObject, final WaveChecker waveChecker, final Method method) {
return new WaveHandler(linkedObject, waveChecker, method);
}
}
}