001/**
002 * Get more info at : www.jrebirth.org .
003 * Copyright JRebirth.org © 2011-2013
004 * Contact : sebastien.bordes@jrebirth.org
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.jrebirth.af.core.service;
019
020import java.lang.reflect.Method;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.HashMap;
024import java.util.List;
025
026import javafx.beans.property.StringProperty;
027import javafx.collections.FXCollections;
028import javafx.collections.ObservableMap;
029import javafx.scene.control.ProgressBar;
030
031import org.jrebirth.af.api.exception.CoreException;
032import org.jrebirth.af.api.exception.JRebirthThreadException;
033import org.jrebirth.af.api.facade.JRebirthEventType;
034import org.jrebirth.af.api.log.JRLogger;
035import org.jrebirth.af.api.service.Service;
036import org.jrebirth.af.api.service.ServiceTask;
037import org.jrebirth.af.api.wave.Wave;
038import org.jrebirth.af.api.wave.contract.WaveData;
039import org.jrebirth.af.core.component.behavior.AbstractBehavioredComponent;
040import org.jrebirth.af.core.concurrent.AbstractJrbRunnable;
041import org.jrebirth.af.core.concurrent.JRebirth;
042import org.jrebirth.af.core.concurrent.JrbReferenceRunnable;
043import org.jrebirth.af.core.log.JRLoggerFactory;
044import org.jrebirth.af.core.util.ClassUtility;
045import org.jrebirth.af.core.wave.JRebirthWaves;
046import org.jrebirth.af.core.wave.WBuilder;
047
048/**
049 *
050 * The class <strong>ServiceBase</strong>.
051 *
052 * Base implementation of the service.
053 *
054 * @author Sébastien Bordes
055 */
056public abstract class AbstractService extends AbstractBehavioredComponent<Service> implements Service, ServiceMessages {
057
058    /** The string used to separate the workdone and total work of a service task. */
059    private static final String RATIO_SEPARATOR = " / ";
060
061    /** The class logger. */
062    private static final JRLogger LOGGER = JRLoggerFactory.getLogger(AbstractService.class);
063
064    /** The map that stores pending tasks, useful to access to Worker implementation. */
065    private final ObservableMap<String, ServiceTask<?>> pendingTasks = FXCollections.observableMap(new HashMap<String, ServiceTask<?>>());
066
067    /**
068     * {@inheritDoc}
069     */
070    @Override
071    public final void ready() throws CoreException {
072
073        // Call the custom method
074        initService();
075    }
076
077    /**
078     * Custom method used to initialize the service.
079     *
080     * Called into JIT by ready method.
081     */
082    protected abstract void initService();
083
084    /**
085     * {@inheritDoc}
086     */
087    @Override
088    protected void finalize() throws Throwable {
089        localFacade().getGlobalFacade().trackEvent(JRebirthEventType.DESTROY_SERVICE, null, this.getClass());
090        super.finalize();
091    }
092
093    /**
094     * {@inheritDoc}
095     */
096    @Override
097    public <T extends Object> ServiceTask<T> returnData(final Wave sourceWave) {
098
099        ServiceTask<T> task = null;
100        try {
101            // Build parameter list of the searched method
102            final List<Object> parameterValues = new ArrayList<>();
103            for (final WaveData<?> wd : sourceWave.waveDatas()) {
104                // Add only wave items defined as parameter
105                if (wd.key().isParameter()) {
106                    parameterValues.add(wd.value());
107                }
108            }
109            // Add the current wave to process
110            // parameterValues.add(wave);
111
112            if (sourceWave.waveType() == null) {
113                LOGGER.error(NO_WAVE_TYPE_DEFINED, this.getClass().getSimpleName());
114            }
115
116            // Search the wave handler method
117            final Method method = ClassUtility.getMethodByName(this.getClass(), ClassUtility.underscoreToCamelCase(sourceWave.waveType().toString()));
118            if (method != null) {
119
120                // final Class<T> returnClass = (Class<T>) method.getReturnType();
121
122                task = runTask(sourceWave, method, parameterValues.toArray());
123
124            }
125        } catch (final NoSuchMethodException e) {
126            // If no method was found, call the default method
127            processWave(sourceWave);
128        }
129        return task;
130    }
131
132    /**
133     * Run the wave type method.
134     *
135     * @param sourceWave the source wave
136     * @param parameterValues values to pass to the method
137     * @param method method to call
138     *
139     * @param <T> the type of the returned type
140     *
141     * @return the service task created
142     */
143    private <T> ServiceTask<T> runTask(final Wave sourceWave, final Method method, final Object[] parameterValues) {
144
145        // Allow to remove the pending task when the service is finished
146        sourceWave.addWaveListener(new ServiceTaskWaveListener());
147
148        // Create a new ServiceTask to handle this request and follow progression
149        final ServiceTaskBase<T> task = new ServiceTaskBase<T>(this, method, parameterValues, sourceWave);
150
151        // Store the task into the pending map
152        this.pendingTasks.put(sourceWave.wUID(), task);
153
154        // Attach ServiceTask to the source wave
155        sourceWave.addDatas(WBuilder.waveData(JRebirthWaves.SERVICE_TASK, task));
156
157        // Bind ProgressBar
158        if (sourceWave.containsNotNull(JRebirthWaves.PROGRESS_BAR)) { // Check double call
159            bindProgressBar(task, sourceWave.getData(JRebirthWaves.PROGRESS_BAR).value());
160        }
161
162        // Bind Title
163        if (sourceWave.containsNotNull(JRebirthWaves.TASK_TITLE)) {
164            bindTitle(task, sourceWave.getData(JRebirthWaves.TASK_TITLE).value());
165        }
166
167        // Bind ProgressBar
168        if (sourceWave.containsNotNull(JRebirthWaves.TASK_MESSAGE)) {
169            bindMessage(task, sourceWave.getData(JRebirthWaves.TASK_MESSAGE).value());
170        }
171
172        // Call the task into the JRebirth Thread Pool
173        JRebirth.runIntoJTP(task);
174
175        return task;
176    }
177
178    /**
179     * Bind a task to a progress bar widget to follow its progression.
180     *
181     * @param task the service task that we need to follow the progression
182     * @param progressBar graphical progress bar
183     */
184    private void bindProgressBar(final ServiceTaskBase<?> task, final ProgressBar progressBar) {
185
186        // Perform this binding into the JAT to respect widget and task API
187        JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind ProgressBar to " + task.getServiceHandlerName()) {
188
189            /**
190             * {@inheritDoc}
191             */
192            @Override
193            protected void runInto() throws JRebirthThreadException {
194                // Avoid the progress bar to display 100% at start up
195                task.updateProgress(0, 0);
196                // Bind the progress bar
197                progressBar.progressProperty().bind(task.workDoneProperty().divide(task.totalWorkProperty()));
198            }
199        });
200
201    }
202
203    /**
204     * Bind a task to a string property that will display the task title.
205     *
206     * @param task the service task that we need to follow the progression
207     * @param titleProperty the title presenter
208     */
209    private void bindTitle(final ServiceTask<?> task, final StringProperty titleProperty) {
210
211        // Perform this binding into the JAT to respect widget and task API
212        JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind Title for " + task.getServiceHandlerName()) {
213
214            /**
215             * {@inheritDoc}
216             */
217            @Override
218            protected void runInto() throws JRebirthThreadException {
219                // Bind the task title
220                titleProperty.bind(task.titleProperty());
221            }
222        });
223    }
224
225    /**
226     * Bind a task to a string property that will display the task message.
227     *
228     * @param task the service task that we need to follow the progression
229     * @param messageProperty the message presenter
230     */
231    private void bindMessage(final ServiceTask<?> task, final StringProperty messageProperty) {
232
233        // Perform this binding into the JAT to respect widget and task API
234        JRebirth.runIntoJAT(new AbstractJrbRunnable("Bind Message for " + task.getServiceHandlerName()) {
235
236            /**
237             * {@inheritDoc}
238             */
239            @Override
240            protected void runInto() throws JRebirthThreadException {
241                // Bind the task title
242                messageProperty.bind(task.messageProperty());
243            }
244        });
245    }
246
247    /**
248     * {@inheritDoc}
249     */
250    @Override
251    public ObservableMap<String, ServiceTask<?>> pendingTasksProperty() {
252        return this.pendingTasks;
253    }
254
255    /**
256     * {@inheritDoc}
257     */
258    @Override
259    public Collection<ServiceTask<?>> getPendingTaskList() {
260        return this.pendingTasks.values();
261    }
262
263    /**
264     * {@inheritDoc}
265     */
266    @Override
267    public void removePendingTask(final String taskKey) {
268        this.pendingTasks.remove(taskKey);
269    }
270
271    /**
272     * {@inheritDoc}
273     */
274    @Override
275    public ServiceTask<?> getPendingTask(final String taskKey) {
276        return this.pendingTasks.get(taskKey);
277    }
278
279    /**
280     * Update the progress of the service task related to the given wave.
281     *
282     * This method will use a 1.0 block increment
283     *
284     * @param wave the wave that trigger the service task call
285     * @param workDone the amount of overall work done
286     * @param totalWork the amount of total work to do
287     */
288    public void updateProgress(final Wave wave, final long workDone, final long totalWork) {
289        updateProgress(wave, workDone, totalWork, 1.0);
290    }
291
292    /**
293     * Update the progress of the service task related to the given wave.
294     *
295     * @param wave the wave that trigger the service task call
296     * @param workDone the amount of overall work done
297     * @param totalWork the amount of total work to do
298     * @param progressIncrement the value increment used to filter useless progress event
299     */
300    public void updateProgress(final Wave wave, final long workDone, final long totalWork, final double progressIncrement) {
301
302        if (wave.get(JRebirthWaves.SERVICE_TASK).checkProgressRatio(workDone, totalWork, progressIncrement)) {
303
304            // Increase the task progression
305            JRebirth.runIntoJAT(new AbstractJrbRunnable("ServiceTask Workdone (lng) " + workDone + RATIO_SEPARATOR + totalWork + "[" + workDone * 100 / totalWork + "%]") {
306
307                /**
308                 * {@inheritDoc}
309                 */
310                @Override
311                protected void runInto() throws JRebirthThreadException {
312
313                    wave.get(JRebirthWaves.SERVICE_TASK).updateProgress(workDone, totalWork);
314                }
315            });
316        }
317    }
318
319    /**
320     * Update the progress of the service task related to the given wave.
321     *
322     * This method will use a 1.0 block increment
323     *
324     * @param wave the wave that trigger the service task call
325     * @param workDone the amount of overall work done
326     * @param totalWork the amount of total work to do
327     */
328    public void updateProgress(final Wave wave, final double workDone, final double totalWork) {
329        updateProgress(wave, workDone, totalWork, 1.0);
330    }
331
332    /**
333     * Update the progress of the service task related to the given wave.
334     *
335     * @param wave the wave that trigger the service task call
336     * @param workDone the amount of overall work done
337     * @param totalWork the amount of total work to do
338     * @param progressIncrement the value increment used to filter useless progress event
339     */
340    public void updateProgress(final Wave wave, final double workDone, final double totalWork, final double progressIncrement) {
341
342        if (wave.get(JRebirthWaves.SERVICE_TASK).checkProgressRatio(workDone, totalWork, progressIncrement)) {
343
344            // Increase the task progression
345            JRebirth.runIntoJAT(new JrbReferenceRunnable("ServiceTask Workdone (dbl) " + workDone + RATIO_SEPARATOR + totalWork,
346                                                         () -> wave.get(JRebirthWaves.SERVICE_TASK).updateProgress(workDone, totalWork)));
347        }
348    }
349
350    /**
351     * Update the current message of the service task related to the given wave.
352     *
353     * @param wave the wave that trigger the service task call
354     * @param message the current message of the service task processed
355     */
356    public void updateMessage(final Wave wave, final String message) {
357
358        // Update the current task message
359        JRebirth.runIntoJAT(new JrbReferenceRunnable("Service Task Mesage => " + message,
360                                                     () -> wave.get(JRebirthWaves.SERVICE_TASK).updateMessage(message)));
361    }
362
363    /**
364     * Update the current message of the service task related to the given wave.
365     *
366     * @param wave the wave that trigger the service task call
367     * @param title the title of the service task processed
368     */
369    public void updateTitle(final Wave wave, final String title) {
370
371        // Update the task title into JAT
372        JRebirth.runIntoJAT(new JrbReferenceRunnable("Service Task Title => " + title,
373                                                     () -> wave.get(JRebirthWaves.SERVICE_TASK).updateTitle(title)));
374    }
375}