/* ==================================================================
* InstructionExecutionJob.java - Oct 1, 2011 8:11:50 PM
*
* Copyright 2007-2011 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.node.reactor;
import java.util.List;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.PersistJobDataAfterExecution;
import net.solarnetwork.node.job.AbstractJob;
import net.solarnetwork.node.reactor.InstructionStatus.InstructionState;
/**
* Job to look for received instructions and pass to handlers for execution.
*
* <p>
* This job uses the {@link InstructionDao} to query for all {@link Instruction}
* entities with a state of {@link InstructionState#Received}. For each
* {@link Instruction} found, it will attempt to find an
* {@link InstructionHandler} that can execute the instruction.
* </p>
*
* <p>
* This job will pass {@link Instruction#getTopic()} to every configured
* {@link InstructionHandler#handlesTopic(String)} and if that returns
* <em>true</em> the handler will be given the opportunity to handle the
* instruction via {@link InstructionHandler#processInstruction(Instruction)}.
* If the handler returns <em>null</em> or {@link InstructionState#Received}
* (because it does not support the given Instruction) this job will move on to
* the next configured handler and try it.
* </p>
*
* <p>
* The first handler to return an {@link InstructionState} other than
* {@link InstructionState#Received} will cause this job to persist the updated
* Instruction state and not pass the instruction to any other handlers.
* </p>
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>instructionDao</dt>
* <dd>The {@link InstructionDao} to manage {@link Instruction} entities
* with.</dd>
*
* <dt>executionReceivedHourLimit</dt>
* <dd>The minimum amount of time to wait before forcing instructions into the
* {@link InstructionState#Declined} state. This prevents instructions not
* handled by any handler from sticking around on the node indefinitely.
* Defaults to {@link #DEFAULT_EXECUTION_RECEIVED_HOUR_LIMIT}.</dd>
*
* <dt>handlers</dt>u
* <dd>List of {@link InstructionHandler} instances
* </dl>
*
* @author matt
* @version 2.0
*/
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class InstructionExecutionJob extends AbstractJob {
/** Default value for the {@code executionReceivedHourLimit} property. */
public static final int DEFAULT_EXECUTION_RECEIVED_HOUR_LIMIT = 24;
private InstructionDao instructionDao;
private List<InstructionHandler> handlers;
private int executionReceivedHourLimit = DEFAULT_EXECUTION_RECEIVED_HOUR_LIMIT;
@Override
protected void executeInternal(JobExecutionContext jobContext) throws Exception {
List<Instruction> instructions = instructionDao
.findInstructionsForState(InstructionState.Received);
log.debug("Found {} instructions in Received state", instructions.size());
final long now = System.currentTimeMillis();
final long timeLimitMs = executionReceivedHourLimit * 60 * 60 * 1000;
for ( Instruction instruction : instructions ) {
boolean handled = false;
log.trace("Passing instruction {} {} to {} handlers",
new Object[] { instruction.getId(), instruction.getTopic(), handlers.size() });
for ( InstructionHandler handler : handlers ) {
log.trace("Handler {} handles topic {}: {}", new Object[] { handler,
instruction.getTopic(), handler.handlesTopic(instruction.getTopic()) });
if ( handler.handlesTopic(instruction.getTopic()) ) {
InstructionState state = handler.processInstruction(instruction);
if ( state != null && !InstructionState.Received.equals(state) ) {
log.info("Instruction {} state changed to {}", instruction.getId(), state);
instructionDao.storeInstructionStatus(instruction.getId(),
instruction.getStatus().newCopyWithState(state));
handled = true;
}
}
}
if ( !handled && instruction.getInstructionDate() != null ) {
long diffMs = now - instruction.getInstructionDate().getTime();
if ( diffMs > timeLimitMs ) {
log.info("Instruction {} not handled within {} hours; declining",
instruction.getId(), executionReceivedHourLimit);
instructionDao.storeInstructionStatus(instruction.getId(),
instruction.getStatus().newCopyWithState(InstructionState.Declined));
}
}
}
}
public InstructionDao getInstructionDao() {
return instructionDao;
}
public void setInstructionDao(InstructionDao instructionDao) {
this.instructionDao = instructionDao;
}
public List<InstructionHandler> getHandlers() {
return handlers;
}
public void setHandlers(List<InstructionHandler> handlers) {
this.handlers = handlers;
}
public int getExecutionReceivedHourLimit() {
return executionReceivedHourLimit;
}
public void setExecutionReceivedHourLimit(int executionReceivedHourLimit) {
this.executionReceivedHourLimit = executionReceivedHourLimit;
}
}