/* ==================================================================
* JobSettingSpecifierProvider.java - Mar 20, 2012 9:10:44 PM
*
* Copyright 2007-2012 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.runtime;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.quartz.CronTrigger;
import org.quartz.Trigger;
import org.springframework.context.MessageSource;
import org.springframework.context.NoSuchMessageException;
import org.springframework.context.support.AbstractMessageSource;
import org.springframework.util.StringUtils;
import net.solarnetwork.node.job.TriggerAndJobDetail;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.TextFieldSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
/**
* {@link SettingSpecifierProvider} for Quartz job triggers.
*
* <p>
* This class allows a set of related Quartz Trigger instances to be exposed as
* a {@link SettingSpecifierProvider} and managed by the node's setting UI. For
* example the triggers might be grouped by the bundle they were published from.
* It works with {@link TriggerAndJobDetail} services. Call
* {@link #addSpecifier(TriggerAndJobDetail)} and
* {@link #removeSpecifier(TriggerAndJobDetail)} as {@link TriggerAndJobDetail}
* services are registered/unregistered with the system.
* </p>
*
* <p>
* Currently only {@link CronTrigger} triggers are supported. Localized messages
* are not supported.
* </p>
*
* @author matt
* @version 2.0
*/
public class JobSettingSpecifierProvider implements SettingSpecifierProvider {
/** The suffix added to display titles. */
public static final String TITLE_SUFFIX = " Jobs";
/** The suffix to remove from display titles in setting UIDs. */
public static final String JOBS_PID_SUFFIX = ".JOBS";
/** The prefix to remove for display titles in setting UIDs. */
public static final String SN_NODE_PREFIX = "net.solarnetwork.node.";
private String settingUID;
private MessageSource messageSource = null;
private List<SettingSpecifier> specifiers = new ArrayList<SettingSpecifier>();
private final Map<String, MessageFormat> messages = new HashMap<String, MessageFormat>();
/**
* Construct with settings UID.
*
* @param settingUID
* the setting UID
*/
public JobSettingSpecifierProvider(String settingUID) {
this(settingUID, null);
}
/**
* Construct with settings UID.
*
* @param settingUID
* the setting UID
* @param source
* a message source
*/
public JobSettingSpecifierProvider(String settingUID, MessageSource source) {
super();
this.settingUID = settingUID;
this.messageSource = source;
if ( source == null || !hasMessage(source, "title") ) {
messages.put("title", new MessageFormat(titleValue(settingUID)));
}
AbstractMessageSource msgSource = new AbstractMessageSource() {
@Override
protected MessageFormat resolveCode(String code, Locale locale) {
return messages.get(code);
}
};
msgSource.setParentMessageSource(source);
this.messageSource = msgSource;
}
private static boolean hasMessage(MessageSource source, String key) {
if ( source == null ) {
return false;
}
try {
source.getMessage(key, null, Locale.getDefault());
return true;
} catch ( NoSuchMessageException e ) {
return false;
}
}
/**
* Construct a display title based on a setting UID.
*
* <p>
* The display title is generated from the setting UID itself, by first
* removing the {@link #SN_NODE_PREFIX} prefix and {@link #JOBS_PID_SUFFIX}
* suffix, capitalizing the remaining value, and appending
* {@link #TITLE_SUFFIX}. For example, the UID
* <code>net.solarnetwork.node.power.JOBS</code> will result in
* <code>Power Jobs</code>.
* </p>
*
*
* @param settingUID
* the setting UID value
* @return the generated title value
*/
private static String titleValue(String settingUID) {
if ( settingUID.startsWith(SN_NODE_PREFIX) && settingUID.length() > SN_NODE_PREFIX.length() ) {
String subPackage = settingUID.substring(SN_NODE_PREFIX.length());
if ( subPackage.endsWith(JOBS_PID_SUFFIX)
&& subPackage.length() > JOBS_PID_SUFFIX.length() ) {
subPackage = subPackage.substring(0, subPackage.length() - JOBS_PID_SUFFIX.length());
}
if ( subPackage.indexOf('.') < 0 ) {
// capitalize first letter
subPackage = Character.toUpperCase(subPackage.charAt(0)) + subPackage.substring(1)
+ TITLE_SUFFIX;
return subPackage;
}
return subPackage;
}
return settingUID;
}
/**
* Create appropriate {@link SettingSpecifier} instances for a given
* {@link TriggerAndJobDetail}.
*
* <p>
* Call this method for every {@link TriggerAndJobDetail} published in the
* system.
* </p>
*
* @param trigJob
* the service to generate specifiers for
*/
public void addSpecifier(TriggerAndJobDetail trigJob) {
Trigger trig = trigJob.getTrigger();
if ( trig instanceof CronTrigger ) {
CronTrigger ct = (CronTrigger) trig;
final String key = JobUtils.triggerKey(ct);
BasicTextFieldSettingSpecifier tf = new BasicTextFieldSettingSpecifier(key,
ct.getCronExpression());
tf.setTitle(ct.getKey().getName());
final String labelKey = key + ".key";
final String descKey = key + ".desc";
if ( !hasMessage(this.messageSource, labelKey) ) {
if ( hasMessage(trigJob.getMessageSource(), labelKey) ) {
messages.put(labelKey, new MessageFormat(
trigJob.getMessageSource().getMessage(labelKey, null, Locale.getDefault())));
} else {
messages.put(labelKey, new MessageFormat(ct.getKey().getName()));
}
}
if ( !hasMessage(this.messageSource, descKey) ) {
if ( hasMessage(trigJob.getMessageSource(), labelKey) ) {
messages.put(descKey, new MessageFormat(
trigJob.getMessageSource().getMessage(descKey, null, Locale.getDefault())));
} else {
messages.put(descKey, new MessageFormat(
StringUtils.hasText(ct.getDescription()) ? ct.getDescription() : ""));
}
}
synchronized ( specifiers ) {
specifiers.add(tf);
}
}
}
/**
* Remove {@link SettingSpecifier} instances previously registered via
* {@link #addSpecifier(TriggerAndJobDetail)} for a given
* {@link TriggerAndJobDetail}.
*
* <p>
* Call this method for every {@link TriggerAndJobDetail} unregistered in
* the system.
* </p>
*
* @param trigJob
* the service to generate specifiers for
*/
public void removeSpecifier(TriggerAndJobDetail trigJob) {
Trigger trig = trigJob.getTrigger();
if ( trig instanceof CronTrigger ) {
CronTrigger ct = (CronTrigger) trig;
final String key = JobUtils.triggerKey(ct);
synchronized ( specifiers ) {
for ( Iterator<SettingSpecifier> itr = specifiers.iterator(); itr.hasNext(); ) {
TextFieldSettingSpecifier tf = (TextFieldSettingSpecifier) itr.next();
if ( tf.getKey().equals(key) ) {
itr.remove();
break;
}
}
}
}
}
@Override
public String getSettingUID() {
return settingUID;
}
@Override
public String getDisplayName() {
return settingUID;
}
@Override
public MessageSource getMessageSource() {
return messageSource;
}
@Override
public List<SettingSpecifier> getSettingSpecifiers() {
synchronized ( specifiers ) {
return Collections.unmodifiableList(specifiers);
}
}
public List<SettingSpecifier> getSpecifiers() {
return specifiers;
}
public void setSpecifiers(List<SettingSpecifier> specifiers) {
this.specifiers = specifiers;
}
}