/* ==================================================================
* MetserviceSupport.java - Oct 18, 2011 2:47:44 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.weather.nz.metservice;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.solarnetwork.node.domain.Datum;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.support.BasicMultiValueSettingSpecifier;
import net.solarnetwork.node.settings.support.BasicTextFieldSettingSpecifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.MessageSource;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;
import com.fasterxml.jackson.databind.ObjectMapper;
/**
* Base class to support MetService Day and Weather data sources.
*
* @param T
* the datum type
* @author matt
* @version 2.0
*/
public abstract class MetserviceSupport<T extends Datum> {
/** The default value for the {@code locationIdentifier} property. */
public static final String DEFAULT_LOCATION_IDENTIFIER = "wellington-city";
/** A key to use for the "last datum" in the local cache. */
protected static final String LAST_DATUM_CACHE_KEY = "last";
private String uid;
private String groupUID;
private String locationIdentifier;
private MetserviceClient client;
private MessageSource messageSource;
private final Map<String, T> datumCache;
/** A class-level logger. */
protected Logger log = LoggerFactory.getLogger(getClass());
public MetserviceSupport() {
datumCache = new ConcurrentHashMap<String, T>(2);
locationIdentifier = DEFAULT_LOCATION_IDENTIFIER;
client = new BasicMetserviceClient();
}
/**
* Get a list of setting specifiers suitable for configuring this class.
*
* @return List of setting specifiers.
*/
public List<SettingSpecifier> getSettingSpecifiers() {
MetserviceDayDatumDataSource defaults = new MetserviceDayDatumDataSource();
List<SettingSpecifier> result = new ArrayList<SettingSpecifier>(8);
result.add(new BasicTextFieldSettingSpecifier("uid", null));
result.add(new BasicTextFieldSettingSpecifier("groupUID", null));
List<NewZealandWeatherLocation> locs = availableWeatherLocations();
if ( locs != null ) {
// drop-down menu for all possible location keys
BasicMultiValueSettingSpecifier menuSpec = new BasicMultiValueSettingSpecifier(
"locationIdentifier", defaults.getLocationIdentifier());
Map<String, String> menuValues = new LinkedHashMap<String, String>(3);
for ( NewZealandWeatherLocation loc : locs ) {
menuValues.put(loc.getKey(), loc.getName());
}
menuSpec.setValueTitles(menuValues);
result.add(menuSpec);
} else {
// fall back to manual value
result.add(new BasicTextFieldSettingSpecifier("locationIdentifier", defaults
.getLocationIdentifier()));
}
return result;
}
private static final Logger LOG = LoggerFactory.getLogger(MetserviceSupport.class);
/** The default weather location data column mappings to parse. */
public static final String[] DEFAULT_LOCATION_CSV_HEADERS = new String[] { "island", "name", "key" };
/**
* Get an ordered list of known weather locations. The
* {@link NewZealandWeatherLocation#getKey()} value can then be passed to
* other methods requiring a location key.
*
* @return The list of known weather locations.
*/
public static List<NewZealandWeatherLocation> availableWeatherLocations() {
InputStream in = MetserviceSupport.class.getResourceAsStream("metservice-locations.csv");
if ( in == null ) {
LOG.warn("Metservice location CSV data not available.");
return Collections.emptyList();
}
Reader reader = null;
try {
reader = new InputStreamReader(in, "UTF-8");
return parseCSVWeatherLocations(reader, DEFAULT_LOCATION_CSV_HEADERS);
} catch ( IOException e ) {
LOG.error("Unable to import weather CSV data: {}", e.getMessage());
} finally {
if ( reader != null ) {
try {
reader.close();
} catch ( IOException e ) {
// ignore
}
}
}
return Collections.emptyList();
}
public static List<NewZealandWeatherLocation> parseCSVWeatherLocations(Reader in, String[] headers) {
final List<NewZealandWeatherLocation> result = new ArrayList<NewZealandWeatherLocation>();
final ICsvBeanReader reader = new CsvBeanReader(in, CsvPreference.STANDARD_PREFERENCE);
try {
NewZealandWeatherLocation loc = null;
while ( (loc = reader.read(NewZealandWeatherLocation.class, headers)) != null ) {
if ( loc.getKey() == null ) {
continue;
}
result.add(loc);
}
Collections.sort(result);
} catch ( IOException e ) {
LOG.error("Unable to import weather CSV data: {}", e.getMessage());
} finally {
try {
if ( reader != null ) {
reader.close();
}
} catch ( IOException e ) {
// ingore
}
}
return result;
}
/**
* Get a cache to support queries resulting in unchanged data.
*
* @return
*/
protected Map<String, T> getDatumCache() {
return datumCache;
}
/**
* The base URL for queries to MetService.
*
* @param baseUrl
* The base URL to use.
* @deprecated Configure {@link #setClient(MetserviceClient)} instead.
*/
@Deprecated
public void setBaseUrl(String baseUrl) {
if ( client instanceof BasicMetserviceClient ) {
((BasicMetserviceClient) client).setBaseUrl(baseUrl);
}
}
/**
* Set the {@link ObjectMapper} to use for parsing JSON.
*
* @param objectMapper
* The object mapper.
* @deprecated Configure {@link #setClient(MetserviceClient)} instead.
*/
@Deprecated
public void setObjectMapper(ObjectMapper objectMapper) {
if ( client instanceof BasicMetserviceClient ) {
((BasicMetserviceClient) client).setObjectMapper(objectMapper);
}
}
private void setLocationIdentifierFromSuffix(final String prefix, final String value) {
if ( prefix == null || value == null ) {
return;
}
if ( value.startsWith(prefix) && value.length() > prefix.length() ) {
setLocationIdentifier(value.substring(prefix.length()));
}
}
/**
* The name of the "localObs" file to parse.
*
* @param localObs
* The file name to use.
* @deprecated Configure {@link #setLocationIdentifier(String)} instead.
*/
@Deprecated
public void setLocalObs(String localObs) {
setLocationIdentifierFromSuffix("localObs_", localObs);
}
/**
* The name of the "localForecast" file to parse.
*
* @param localForecast
* The file name to use.
* @deprecated Configure {@link #setLocationIdentifier(String)} instead.
*/
@Deprecated
public void setLocalForecastTemplate(String localForecast) {
setLocationIdentifierFromSuffix("localForecast", localForecast);
}
/**
* The name of the "riseSet" file to parse.
*
* @param riseSet
* The file name to use.
* @deprecated Configure {@link #setLocationIdentifier(String)} instead.
*/
@Deprecated
public void setRiseSetTemplate(String riseSet) {
setLocationIdentifierFromSuffix("riseSet_", riseSet);
}
/**
* The {@link SimpleDateFormat} date format to use to parse the day date.
*
* @param dayDateFormat
* The date format to use.
* @deprecated Configure {@link #setClient(MetserviceClient)} instead.
*/
@Deprecated
public void setDayDateFormat(String dayDateFormat) {
if ( client instanceof BasicMetserviceClient ) {
((BasicMetserviceClient) client).setDayDateFormat(dayDateFormat);
}
}
/**
* Set a {@link SimpleDateFormat} time format to use to parse sunrise/sunset
* times.
*
* @param timeDateFormat
* The date format to use.
* @deprecated Configure {@link #setClient(MetserviceClient)} instead.
*/
@Deprecated
public void setTimeDateFormat(String timeDateFormat) {
if ( client instanceof BasicMetserviceClient ) {
((BasicMetserviceClient) client).setTimeDateFormat(timeDateFormat);
}
}
/**
* Set a {@link SimpleDateFormat} date and time pattern for parsing the
* information date from the {@code oneMinObs} file.
*
* @param timestampDateFormat
* The date format to use.
* @deprecated Configure {@link #setClient(MetserviceClient)} instead.
*/
@Deprecated
public void setTimestampDateFormat(String timestampDateFormat) {
if ( client instanceof BasicMetserviceClient ) {
((BasicMetserviceClient) client).setTimestampDateFormat(timestampDateFormat);
}
}
public MessageSource getMessageSource() {
return messageSource;
}
/**
* Set a message source to use for resolving messages.
*
* @param messageSource
* The message source to use.
*/
public void setMessageSource(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getUID() {
return getUid();
}
public String getUid() {
return uid;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getGroupUID() {
return groupUID;
}
public void setGroupUID(String groupUID) {
this.groupUID = groupUID;
}
public String getLocationIdentifier() {
return locationIdentifier;
}
/**
* Set the Metservice weather location identifer to use, which determines
* the URL to use for loading weather data files. This should be one of the
* keys returned by {@link MetserviceSupport#availableWeatherLocations()}.
*
* @param locationIdentifier
* The location identifier to use.
*/
public void setLocationIdentifier(String locationIdentifier) {
this.locationIdentifier = locationIdentifier;
}
/**
* Get the Metservice client.
*
* @return The Metservice client.
*/
public MetserviceClient getClient() {
return client;
}
/**
* Set the Metservice client.
*
* @param client
* The client to use.
*/
public void setClient(MetserviceClient client) {
this.client = client;
}
}