/* ==================================================================
* CentralSystemServiceFactorySupport.java - 9/06/2015 11:05:18 am
*
* Copyright 2007-2015 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.ocpp.support;
import java.util.ArrayList;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.SimpleTrigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import net.solarnetwork.node.Identifiable;
import net.solarnetwork.node.ocpp.CentralSystemServiceFactory;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTitleSettingSpecifier;
import net.solarnetwork.util.FilterableService;
/**
* A base helper class for services that require use of
* {@link CentralSystemServiceFactory}.
*
* <p>
* The {@link FilterableService} API can be used to allow dynamic runtime
* resolution of which central service to use, if more than one are deployed.
* </p>
*
* <p>
* This class also implements {@link Identifiable} and will delegate those
* methods to the configured {@link CentralSystemServiceFactory} if not
* explicitly defined on this class.
* </p>
*
* @author matt
* @version 1.1
*/
public abstract class CentralSystemServiceFactorySupport
implements SettingSpecifierProvider, Identifiable {
private CentralSystemServiceFactory centralSystem;
private MessageSource messageSource;
private String uid;
private String groupUID;
private final DatatypeFactory datatypeFactory;
private final GregorianCalendar utcCalendar;
protected final Logger log = LoggerFactory.getLogger(getClass());
/**
* Default constructor.
*/
public CentralSystemServiceFactorySupport() {
super();
utcCalendar = new GregorianCalendar(TimeZone.getTimeZone("UTC"));
try {
datatypeFactory = DatatypeFactory.newInstance();
} catch ( DatatypeConfigurationException e ) {
throw new RuntimeException(e);
}
}
/**
* Get a {@link DatatypeFactory} instance.
*
* @return The factory.
*/
protected final DatatypeFactory getDatatypeFactory() {
return datatypeFactory;
}
/**
* Get a {@link XMLGregorianCalendar} for the current time, set to the UTC
* time zone.
*
* @return A new calendar instance.
*/
protected final XMLGregorianCalendar newXmlCalendar() {
return newXmlCalendar(System.currentTimeMillis());
}
/**
* Get a {@link XMLGregorianCalendar} for a specific time, set to the UTC
* time zone.
*
* @param date
* The date, in milliseconds since the epoch.
* @return A new calendar instance.
*/
protected final XMLGregorianCalendar newXmlCalendar(long date) {
GregorianCalendar now = (GregorianCalendar) utcCalendar.clone();
now.setTimeInMillis(date);
return datatypeFactory.newXMLGregorianCalendar(now);
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
List<SettingSpecifier> results = new ArrayList<SettingSpecifier>(3);
results.add(new BasicTitleSettingSpecifier("info", getInfoMessage(Locale.getDefault()), true));
results.add(new BasicTextFieldSettingSpecifier("filterableCentralSystem.propertyFilters['UID']",
"OCPP Central System"));
return results;
}
/**
* Get a status message to display as a read-only setting.
*
* @param locale
* The desired locale of the message.
* @return The status message.
*/
protected abstract String getInfoMessage(Locale locale);
/**
* Get the {@link CentralSystemServiceFactory} to use.
*
* @return The configured central system.
*/
public final CentralSystemServiceFactory getCentralSystem() {
return centralSystem;
}
/**
* Set the {@link CentralSystemServiceFactory} to use. If the provided
* object also implements {@link FilterableService} then it will be passed
* to {@link #setFilterableCentralSystem(FilterableService)} as well.
*
* @param centralSystem
* The central system to use.
*/
public final void setCentralSystem(CentralSystemServiceFactory centralSystem) {
this.centralSystem = centralSystem;
}
/**
* Get the central system to use, as a {@link FilterableService}. If the
* configured central system does not also implement
* {@link FilterableService} this method returns <em>null</em>. This method
* is designed to assist with dynamic runtime configuration, where more than
* one {@link CentralSystemServiceFactory} may be available and a filter is
* needed to choose the appropriate one to use.
*
* @return The central system, as a {@link FilterableService}.
*/
public final FilterableService getFilterableCentralSystem() {
CentralSystemServiceFactory central = centralSystem;
if ( central instanceof FilterableService ) {
return (FilterableService) central;
}
return null;
}
@Override
public final MessageSource getMessageSource() {
return messageSource;
}
/**
* Set the {@link MessageSource} to use for resolving settings messages.
*
* @param messageSource
* The message source to use.
*/
public final void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
/**
* Returns the {@code uid} value if configured, or else falls back to
* returning {@link CentralSystemServiceFactory#getUID()}.
*/
@Override
public final String getUID() {
String id = getUid();
if ( id == null ) {
CentralSystemServiceFactory system = centralSystem;
if ( system != null ) {
try {
id = system.getUID();
} catch ( RuntimeException e ) {
log.debug("Error getting central system UID: {}", e.getMessage());
}
}
}
return id;
}
/**
* Returns the {@code groupUID} value if configured, or else falls back to
* returning {@link CentralSystemServiceFactory#getGroupUID()}.
*/
@Override
public final String getGroupUID() {
String id = groupUID;
if ( id == null ) {
CentralSystemServiceFactory system = centralSystem;
if ( system != null ) {
try {
id = system.getGroupUID();
} catch ( RuntimeException e ) {
log.debug("Error getting central system Group UID: {}", e.getMessage());
}
}
}
return id;
}
/**
* Manage a scheduled job based on a repeating interval.
*
* This method will return the created trigger, which should be passed on
* subsequent calls if the interval is to be changed or unscheduled.
*
* @param scheduler
* The scheduler to use.
* @param interval
* The interval, in seconds, for the job to be scheduled at. Pass
* {@code 0} or less to unschedule the job.
* @param currTrigger
* The current job trigger, or {@code null} if not scheduled.
* @param jobKey
* The key to use for the scheduled job.
* @param jobClass
* The class of the {@code Job} to schedule.
* @param jobData
* A map of data to associate with the job.
* @param jobDescription
* A description to use for the job. This value is included in log
* messages.
* @return The scheduled job trigger, or {@code null} if an error occurs or
* the job is unscheduled.
* @since 1.1
*/
protected SimpleTrigger scheduleIntervalJob(final Scheduler scheduler, final int interval,
final SimpleTrigger currTrigger, final JobKey jobKey, final Class<? extends Job> jobClass,
final JobDataMap jobData, final String jobDescription) {
if ( scheduler == null ) {
log.warn("No scheduler avaialable, cannot schedule {} job", jobDescription);
return null;
}
SimpleTrigger trigger = currTrigger;
if ( trigger != null ) {
// check if interval actually changed
if ( trigger.getRepeatInterval() == interval ) {
log.debug("{} job interval unchanged at {}s", jobDescription, interval);
return currTrigger;
}
// trigger has changed!
if ( interval == 0 ) {
try {
scheduler.unscheduleJob(trigger.getKey());
} catch ( SchedulerException e ) {
log.error("Error unscheduling {} job", jobDescription, e);
} finally {
trigger = null;
}
} else {
trigger = TriggerBuilder.newTrigger().withIdentity(trigger.getKey()).forJob(jobKey)
.startAt(new Date(System.currentTimeMillis() + interval)).usingJobData(jobData)
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(interval)).build();
try {
scheduler.rescheduleJob(trigger.getKey(), trigger);
} catch ( SchedulerException e ) {
log.error("Error rescheduling {} job", jobDescription, e);
}
}
return trigger;
}
if ( interval == 0 ) {
// asked to unschedule, but not scheduled so nothing more to do
return null;
}
synchronized ( scheduler ) {
try {
JobDetail jobDetail = scheduler.getJobDetail(jobKey);
if ( jobDetail == null ) {
jobDetail = JobBuilder.newJob(jobClass).withIdentity(jobKey).storeDurably().build();
scheduler.addJob(jobDetail, true);
}
final TriggerKey triggerKey = new TriggerKey(jobKey.getName() + getUID(),
jobKey.getGroup());
trigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).forJob(jobKey)
.startAt(new Date(System.currentTimeMillis() + interval)).usingJobData(jobData)
.withSchedule(SimpleScheduleBuilder.repeatSecondlyForever(interval)
.withMisfireHandlingInstructionNextWithExistingCount())
.build();
scheduler.scheduleJob(trigger);
return trigger;
} catch ( Exception e ) {
log.error("Error scheduling {} job", jobDescription, e);
return null;
}
}
}
public void setGroupUID(String groupUID) {
this.groupUID = groupUID;
}
}