/* ==================================================================
* SMAInverterDataSourceSupport.java - Mar 7, 2013 12:02:55 PM
*
* Copyright 2007-2013 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.hw.sma;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import net.solarnetwork.domain.GeneralDatumMetadata;
import net.solarnetwork.node.DatumDataSource;
import net.solarnetwork.node.DatumMetadataService;
import net.solarnetwork.node.Setting;
import net.solarnetwork.node.dao.SettingDao;
import net.solarnetwork.node.domain.Datum;
import net.solarnetwork.node.util.ClassUtils;
import net.solarnetwork.util.OptionalService;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Supporting class for SMA inverter data sources.
*
* <p>
* The configurable properties of this class are:
* </p>
*
* <dl class="class-properties">
* <dt>settingDao</dt>
* <dd>The {@link SettingDao} to persist settings with.</dd>
*
* <dt>sourceId</dt>
* <dd>A source ID value to use for captured datums. Defaults to
* {@link #DEFAULT_SOURCE_ID}.</dd>
*
* <dt>eventAdmin</dt>
* <dd>An optional {@link EventAdmin} service to use for posting events.</dd>
*
* <dt>datumMetadataService</dt>
* <dd>An optional {@link DatumMetadataService} to use for managing metadata.</dd>
* </dl>
*
* @author matt
* @version 1.4
*/
public abstract class SMAInverterDataSourceSupport {
/** The default value for the {@code sourceId} property. */
public static final String DEFAULT_SOURCE_ID = "Main";
protected final Logger log = LoggerFactory.getLogger(getClass());
private Set<String> channelNamesToMonitor = null;
private SettingDao settingDao = null;
private String sourceId = DEFAULT_SOURCE_ID;
private String groupUID = null;
private OptionalService<EventAdmin> eventAdmin;
private OptionalService<DatumMetadataService> datumMetadataService;
private final ConcurrentMap<String, GeneralDatumMetadata> sourceMetadataCache = new ConcurrentHashMap<String, GeneralDatumMetadata>(
4);
protected final String getSettingPrefixDayStartValue() {
return getClass().getSimpleName() + "." + sourceId + ".start:";
}
protected final String getSettingPrefixLastKnownValue() {
return getClass().getSimpleName() + "." + sourceId + ".value:";
}
protected final String getSettingKeyLastKnownDay() {
return getClass().getSimpleName() + "." + sourceId + ".knownDay";
}
/**
* Get a "volatile" setting, that is one that does not trigger an automatic
* settings backup.
*
* @param key
* the setting key
* @return the setting value, or <em>null</em> if not found
*/
protected final String getVolatileSetting(String key) {
return (settingDao == null ? null : settingDao.getSetting(key));
}
/**
* Save a "volatile" setting.
*
* @param key
* the setting key
* @param value
* the setting value
* @see #getVolatileSetting(String)
*/
protected final void saveVolatileSetting(String key, String value) {
if ( settingDao == null ) {
return;
}
settingDao.storeSetting(new Setting(key, null, value, EnumSet.of(Setting.SettingFlag.Volatile)));
}
/**
* Get the current day of the year as a String value.
*
* @return the day of the year
*/
protected String getDayOfYearValue() {
Calendar now = Calendar.getInstance();
return String.valueOf(now.get(Calendar.YEAR)) + "."
+ String.valueOf(now.get(Calendar.DAY_OF_YEAR));
}
/**
* Save today as the last known day.
*
* @see #getDayOfYearValue()
*/
protected final void storeLastKnownDay() {
String dayOfYear = getDayOfYearValue();
if ( log.isDebugEnabled() ) {
log.debug("Saving last known day as [" + dayOfYear + ']');
}
saveVolatileSetting(getSettingKeyLastKnownDay(), dayOfYear);
}
/**
* Get the last known day value, or <em>null</em> if not known.
*
* @return the date, or <em>null</em> if not known
*/
protected final Calendar getLastKnownDay() {
String lastKnownDayOfYear = getLastKnownDayOfYearValue();
Calendar day = null;
if ( lastKnownDayOfYear != null ) {
int dot = lastKnownDayOfYear.indexOf('.');
day = Calendar.getInstance();
day.set(Calendar.YEAR, Integer.parseInt(lastKnownDayOfYear.substring(0, dot)));
day.set(Calendar.DAY_OF_YEAR, Integer.parseInt(lastKnownDayOfYear.substring(dot + 1)));
}
return day;
}
protected final String getLastKnownDayOfYearValue() {
return getVolatileSetting(getSettingKeyLastKnownDay());
}
/**
* Test if today is a different day from the last known day.
*
* <p>
* If the {@code settingDao} to be configured, this method will use that to
* load a "last known day" value. If that value is not found, or is
* different from the current execution day, <em>true</em> will be returned.
* Otherwise, <em>false</em> is returned.
* </p>
*
* @return boolean
*/
protected final boolean isNewDay() {
if ( this.settingDao == null ) {
return false;
}
Calendar now = Calendar.getInstance();
Calendar then = getLastKnownDay();
if ( then == null || (now.get(Calendar.YEAR) != then.get(Calendar.YEAR))
|| (now.get(Calendar.DAY_OF_YEAR) != then.get(Calendar.DAY_OF_YEAR)) ) {
return true;
}
return false;
}
/**
* Parse a String into a Number of a specific type.
*
* @param numberType
* the type of Number to return
* @param numberString
* the String to parse
* @return the new Number instance
*/
protected final Number parseNumber(Class<? extends Number> numberType, String numberString) {
if ( Integer.class.isAssignableFrom(numberType) ) {
return Integer.valueOf(numberString);
} else if ( Float.class.isAssignableFrom(numberType) ) {
return Float.valueOf(numberString);
} else if ( Long.class.isAssignableFrom(numberType) ) {
return Long.valueOf(numberString);
}
return Double.valueOf(numberString);
}
/**
* Divide two {@link Number} instances using a specific implementation of
* Number.
*
* <p>
* Really the {@code numberType} argument should be considered a
* {@code Class<? extends Number>} but to simplify calling this method any
* Class is allowed.
* </p>
*
* @param numberType
* the type of Number to treat the dividend and divisor as
* @param dividend
* the dividend value
* @param divisor
* the divisor value
* @return a Number instance of type {@code numberType}
*/
protected final Number divide(Class<?> numberType, Number dividend, Number divisor) {
if ( Integer.class.isAssignableFrom(numberType) ) {
return dividend.intValue() / divisor.intValue();
} else if ( Float.class.isAssignableFrom(numberType) ) {
return dividend.floatValue() / divisor.floatValue();
} else if ( Long.class.isAssignableFrom(numberType) ) {
return dividend.longValue() / divisor.longValue();
}
return dividend.doubleValue() / divisor.doubleValue();
}
/**
* Subtract two Number instances.
*
* <p>
* The returned Number will be an instance of the {@code start} class.
* </p>
*
* @param start
* the starting number to subtract from
* @param offset
* the amount to subtract
* @return a Number instance of the same type as {@code start}
*/
protected final Number diff(Number start, Number offset) {
if ( start instanceof Integer ) {
return Integer.valueOf(start.intValue() - offset.intValue());
} else if ( start instanceof Float ) {
return Float.valueOf(start.floatValue() - offset.floatValue());
} else if ( start instanceof Long ) {
return Long.valueOf(start.longValue() - offset.longValue());
}
return Double.valueOf(start.doubleValue() - offset.doubleValue());
}
/**
* Multiply two Number instances.
*
* <p>
* The returned Number will be an instance of the {@code a} class.
* </p>
*
* @param a
* first number
* @param b
* second number
* @return a Number instance of the same type as {@code a}
*/
protected final Number mult(Number a, Number b) {
if ( a instanceof Integer ) {
return Integer.valueOf(a.intValue() * b.intValue());
} else if ( a instanceof Float ) {
return Float.valueOf(a.floatValue() * b.floatValue());
} else if ( a instanceof Long ) {
return Long.valueOf(a.longValue() * b.longValue());
}
return Double.valueOf(a.doubleValue() * b.doubleValue());
}
/**
* Post a {@link DatumDataSource#EVENT_TOPIC_DATUM_CAPTURED} {@link Event}.
*
* <p>
* This method calls {@link #createDatumCapturedEvent(Datum, Class)} to
* create the actual Event, which may be overridden by extending classes.
* </p>
*
* @param datum
* the {@link Datum} to post the event for
* @param eventDatumType
* the Datum class to use for the
* {@link DatumDataSource#EVENT_DATUM_CAPTURED_DATUM_TYPE} property
* @since 1.3
*/
protected final void postDatumCapturedEvent(final Datum datum,
final Class<? extends Datum> eventDatumType) {
EventAdmin ea = (eventAdmin == null ? null : eventAdmin.service());
if ( ea == null || datum == null ) {
return;
}
Event event = createDatumCapturedEvent(datum, eventDatumType);
ea.postEvent(event);
}
/**
* Create a new {@link DatumDataSource#EVENT_TOPIC_DATUM_CAPTURED}
* {@link Event} object out of a {@link Datum}.
*
* <p>
* This method will populate all simple properties of the given
* {@link Datum} into the event properties, along with the
* {@link DatumDataSource#EVENT_DATUM_CAPTURED_DATUM_TYPE}.
*
* @param datum
* the datum to create the event for
* @param eventDatumType
* the Datum class to use for the
* {@link DatumDataSource#EVENT_DATUM_CAPTURED_DATUM_TYPE} property
* @return the new Event instance
* @since 1.3
*/
protected Event createDatumCapturedEvent(final Datum datum,
final Class<? extends Datum> eventDatumType) {
Map<String, Object> props = ClassUtils.getSimpleBeanProperties(datum, null);
props.put(DatumDataSource.EVENT_DATUM_CAPTURED_DATUM_TYPE, eventDatumType.getName());
log.debug("Created {} event with props {}", DatumDataSource.EVENT_TOPIC_DATUM_CAPTURED, props);
return new Event(DatumDataSource.EVENT_TOPIC_DATUM_CAPTURED, props);
}
/**
* Add source metadata using the configured {@link DatumMetadataService} (if
* available). The metadata will be cached so that subseqent calls to this
* method with the same metadata value will not try to re-save the unchanged
* value. This method will catch all exceptions and silently discard them.
*
* @param sourceId
* the source ID to add metadata to
* @param meta
* the metadata to add
* @param returns
* <em>true</em> if the metadata was saved successfully, or does not
* need to be updated
*/
protected boolean addSourceMetadata(final String sourceId, final GeneralDatumMetadata meta) {
GeneralDatumMetadata cached = sourceMetadataCache.get(sourceId);
if ( cached != null && meta.equals(cached) ) {
// we've already posted this metadata... don't bother doing it again
log.debug("Source {} metadata already added, not posting again", sourceId);
return true;
}
DatumMetadataService service = null;
if ( datumMetadataService != null ) {
service = datumMetadataService.service();
}
if ( service == null ) {
return false;
}
try {
service.addSourceMetadata(sourceId, meta);
sourceMetadataCache.put(sourceId, meta);
return true;
} catch ( Exception e ) {
log.debug("Error saving source {} metadata: {}", sourceId, e.getMessage());
}
return false;
}
public Set<String> getChannelNamesToMonitor() {
return channelNamesToMonitor;
}
public void setChannelNamesToMonitor(Set<String> channelNamesToMonitor) {
this.channelNamesToMonitor = channelNamesToMonitor;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public void setSettingDao(SettingDao settingDao) {
this.settingDao = settingDao;
}
public String getUID() {
return getSourceId();
}
public String getGroupUID() {
return groupUID;
}
public void setGroupUID(String groupUID) {
this.groupUID = groupUID;
}
public OptionalService<EventAdmin> getEventAdmin() {
return eventAdmin;
}
public void setEventAdmin(OptionalService<EventAdmin> eventAdmin) {
this.eventAdmin = eventAdmin;
}
public OptionalService<DatumMetadataService> getDatumMetadataService() {
return datumMetadataService;
}
public void setDatumMetadataService(OptionalService<DatumMetadataService> datumMetadataService) {
this.datumMetadataService = datumMetadataService;
}
}