/* * Copyright (c) 2010, Paul Merlin. All Rights Reserved. * * 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.qi4j.library.scheduler; import org.qi4j.api.configuration.Configuration; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.This; import org.qi4j.api.object.ObjectBuilderFactory; import org.qi4j.api.query.Query; import org.qi4j.api.service.Activatable; import org.qi4j.api.unitofwork.UnitOfWork; import org.qi4j.api.unitofwork.UnitOfWorkCompletionException; import org.qi4j.api.unitofwork.UnitOfWorkFactory; import org.qi4j.library.scheduler.schedule.ScheduleEntity; import org.qi4j.library.scheduler.schedule.ScheduleRepository; import org.qi4j.library.scheduler.slaves.SchedulerGarbageCollector; import org.qi4j.library.scheduler.slaves.SchedulerPulse; import org.qi4j.library.scheduler.slaves.SchedulerWorkQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Handle the {@link Scheduler} activation and passivation. */ public class SchedulerActivation implements Activatable { private static final Logger LOGGER = LoggerFactory.getLogger( Scheduler.class ); private static final int DEFAULT_PULSE_RHYTHM = 60; private static final int DEFAULT_GC_RHYTHM = 600; private static final int DEFAULT_WORKERS_COUNT = Runtime.getRuntime().availableProcessors(); private static final int DEFAULT_WORKQUEUE_SIZE = 10; @Structure private ObjectBuilderFactory obf; @Structure private UnitOfWorkFactory uowf; @This private Configuration<SchedulerConfiguration> config; @This private SchedulerService me; @Service private ScheduleRepository scheduleRepository; private SchedulerWorkQueue workQueue; private SchedulerPulse pulse; private SchedulerGarbageCollector gc; private Thread pulseThread; private Thread gcThread; public void activate() throws Exception { // Handle configuration defaults SchedulerConfiguration configuration = config.configuration(); Integer workersCount = configuration.workersCount().get(); Integer workQueueSize = configuration.workQueueSize().get(); Integer pulseRhythmSeconds = configuration.pulseRhythmSeconds().get(); Integer gcRhythmSeconds = configuration.garbageCollectorRhythmSeconds().get(); if ( workersCount == null ) { workersCount = DEFAULT_WORKERS_COUNT; LOGGER.debug( "Workers count absent from configuration, falled back to default: {} workers", DEFAULT_WORKERS_COUNT ); } if ( workQueueSize == null ) { workQueueSize = DEFAULT_WORKQUEUE_SIZE; LOGGER.debug( "WorkQueue size absent from configuration, falled back to default: {}", DEFAULT_WORKQUEUE_SIZE ); } if ( pulseRhythmSeconds == null ) { pulseRhythmSeconds = DEFAULT_PULSE_RHYTHM; LOGGER.debug( "Pulse rythm absent from configuration, falled back to default: {} seconds", DEFAULT_PULSE_RHYTHM ); } if ( gcRhythmSeconds == null ) { gcRhythmSeconds = DEFAULT_GC_RHYTHM; LOGGER.debug( "Garbage Collector rythm absent from configuration, falled back to default: {} seconds", DEFAULT_GC_RHYTHM ); } removeNonDurableSchedules(); setAllSchedulesAsNotRunning(); startGarbageCollector( gcRhythmSeconds ); startPulse( workersCount, workQueueSize, pulseRhythmSeconds ); LOGGER.debug( "Activated" ); } public void passivate() throws Exception { // Handle configuration defaults SchedulerConfiguration configuration = config.configuration(); Boolean stopViolently = configuration.stopViolently().get(); if ( stopViolently == null ) { stopViolently = Boolean.FALSE; } stopGarbageCollector(); stopPulse( stopViolently ); LOGGER.debug( "Passivated" ); } private void removeNonDurableSchedules() throws UnitOfWorkCompletionException { // Remove not durable schedules UnitOfWork uow = uowf.newUnitOfWork(); Query<ScheduleEntity> notDurableQuery = scheduleRepository.findNotDurable( me.identity().get() ); long notDurableCount = notDurableQuery.count(); if ( notDurableCount > 0 ) { LOGGER.debug( "Found {} not durable schedules at activation, removing them", notDurableCount ); for ( ScheduleEntity eachNotDurable : notDurableQuery ) { uow.remove( eachNotDurable ); } } uow.complete(); } private void setAllSchedulesAsNotRunning() throws UnitOfWorkCompletionException { // Handling schedules that were running when last activated UnitOfWork uow = uowf.newUnitOfWork(); Query<ScheduleEntity> runningQuery = scheduleRepository.findRunning( me.identity().get() ); long runningCount = runningQuery.count(); if ( runningCount > 0 ) { LOGGER.debug( "Found {} running schedules at activation, setting them back to not running", runningCount ); for ( ScheduleEntity eachRunning : runningQuery ) { eachRunning.running().set( false ); } } uow.complete(); } private void startPulse( Integer workersCount, Integer workQueueSize, Integer pulseRhythmSeconds ) { workQueue = obf.newObjectBuilder( SchedulerWorkQueue.class ). use( me.identity().get() ). use( workersCount ). use( workQueueSize ). newInstance(); pulse = obf.newObjectBuilder( SchedulerPulse.class ). use( Long.valueOf( pulseRhythmSeconds * 1000 ) ). use( workQueue ). newInstance(); pulseThread = new Thread( pulse, me.identity().get() + "-Pulse" ); pulseThread.start(); LOGGER.debug( "Pulsing every {}s", pulseRhythmSeconds ); } private void stopPulse( Boolean stopViolently ) { pulse.suicideAfterCurrentCycle(); if ( stopViolently ) { pulseThread.interrupt(); } else { try { pulseThread.join(); } catch ( InterruptedException ex ) { LOGGER.warn( "Pulse was interrupted while waiting for running tasks" ); } } pulse = null; pulseThread = null; } private void startGarbageCollector( Integer gcRhythmSeconds ) { gc = obf.newObjectBuilder( SchedulerGarbageCollector.class ). use( Long.valueOf( gcRhythmSeconds * 1000 ) ). newInstance(); gcThread = new Thread( gc, me.identity().get() + "-GC" ); gcThread.start(); LOGGER.debug( "Garbage Collecting every {}s", gcRhythmSeconds ); } private void stopGarbageCollector() { gc.suicideAfterCurrentCycle(); gcThread.interrupt(); gc = null; gcThread = null; } }