net.solarnetwork.node.support.LocationDatumDataSource.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.node.support.LocationDatumDataSource.java

Source

/* ==================================================================
 * LocationDatumDataSource.java - Feb 21, 2011 5:23:28 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.support;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import net.solarnetwork.domain.GeneralLocationSourceMetadata;
import net.solarnetwork.node.DatumDataSource;
import net.solarnetwork.node.LocationService;
import net.solarnetwork.node.MultiDatumDataSource;
import net.solarnetwork.node.domain.BasicGeneralLocation;
import net.solarnetwork.node.domain.Datum;
import net.solarnetwork.node.domain.GeneralLocation;
import net.solarnetwork.node.domain.GeneralLocationDatum;
import net.solarnetwork.node.domain.GeneralNodeDatum;
import net.solarnetwork.node.domain.Location;
import net.solarnetwork.node.domain.PriceLocation;
import net.solarnetwork.node.domain.PricedDatum;
import net.solarnetwork.node.settings.KeyedSettingSpecifier;
import net.solarnetwork.node.settings.LocationLookupSettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifier;
import net.solarnetwork.node.settings.SettingSpecifierProvider;
import net.solarnetwork.node.settings.support.BasicLocationLookupSettingSpecifier;
import net.solarnetwork.node.util.PrefixedMessageSource;
import net.solarnetwork.util.OptionalService;
import net.solarnetwork.util.OptionalServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;

/**
 * {@link DatumDataSource} that augments some other data source's datum values
 * with location IDs.
 * 
 * <p>
 * This is to be used to easily augment various datum that relate to a location
 * with the necessary {@link Location#getLocationId()} ID. This class also
 * implements the {@link MultiDatumDataSource} API, and will call the methods of
 * that API on the configured {@code delegate} if that also implements
 * {@link MultiDatumDataSource}. If the {@code delegate} does not implement
 * {@link MultiDatumDataSource} this class will "fake" that API by calling
 * {@link DatumDataSource#readCurrentDatum()} and returning that object in a
 * Collection.
 * </p>
 * 
 * <p>
 * The configurable properties of this class are:
 * </p>
 * 
 * <dl class="class-properties">
 * <dt>delegate</dt>
 * <dd>The {@link DatumDataSource} to delegate to.</dd>
 * 
 * <dt>locationType</dt>
 * <dd>The type of location to search for. Defaults to {@link PriceLocation}.</dd>
 * 
 * <dt>locationService</dt>
 * <dd>The {@link LocationService} to use to lookup {@link Location} instances
 * via the configured {@code locationId} property.</dd>
 * 
 * <dt>locationId</dt>
 * <dd>The {@link Location} ID to assign.</dd>
 * 
 * <dt>locationIdPropertyName</dt>
 * <dd>The JavaBean property name to set the found
 * {@link Location#getLocationId()} to on the {@link Datum} returned from the
 * configured {@code delegate}. The object must support a JavaBean setter method
 * for this property. Defaults to {@link #DEFAULT_LOCATION_ID_PROP_NAME}.</dd>
 * 
 * <dt>sourceIdId</dt>
 * <dd>The location source ID to assign.</dd>
 * 
 * <dt>sourceIdPropertyName</dt>
 * <dd>The JavaBean property name to set the found
 * {@link Location#getSourceId()} to on the {@link Datum} returned from the
 * configured {@code delegate}. The object must support a JavaBean setter method
 * for this property. Defaults to {@link #DEFAULT_SOURCE_ID_PROP_NAME}.</dd>
 * 
 * <dt>requireLocationService</dt>
 * <dd>If configured as <em>true</em> then return <em>null</em> data only
 * instead of calling the delegate. This is designed for services that require a
 * location ID to be set, for example a Location Datum logger. Defaults to
 * <em>false</em>.</dd>
 * 
 * <dt>messageBundleBasename</dt>
 * <dd>The message bundle basename to use. This can be customized so different
 * messages can be shown for different uses of this proxy. Defaults to
 * {@link #PRICE_LOCATION_MESSAGE_BUNDLE}.</dd>
 * </dl>
 * 
 * @author matt
 * @version 1.5
 */
public class LocationDatumDataSource<T extends Datum>
        implements DatumDataSource<T>, MultiDatumDataSource<T>, SettingSpecifierProvider {

    /** Default value for the {@code locationIdPropertyName} property. */
    public static final String DEFAULT_LOCATION_ID_PROP_NAME = "locationId";

    /** Default value for the {@code sourceIdPropertyName} property. */
    public static final String DEFAULT_SOURCE_ID_PROP_NAME = "locationSourceId";

    /** Bundle name for price location lookup messages. */
    public static final String PRICE_LOCATION_MESSAGE_BUNDLE = "net.solarnetwork.node.support.PriceLocationDatumDataSource";

    private DatumDataSource<T> delegate;
    private OptionalService<LocationService> locationService;
    private String locationType = Location.PRICE_TYPE;
    private String locationIdPropertyName = DEFAULT_LOCATION_ID_PROP_NAME;
    private String sourceIdPropertyName = DEFAULT_SOURCE_ID_PROP_NAME;
    private boolean requireLocationService = false;
    private String messageBundleBasename = PRICE_LOCATION_MESSAGE_BUNDLE;
    private Long locationId = null;
    private String sourceId = null;
    private Set<String> datumClassNameIgnore;

    private GeneralLocation location = null;
    private MessageSource messageSource;

    private final Logger log = LoggerFactory.getLogger(getClass());

    /**
     * Factory method.
     * 
     * <p>
     * This method exists to work around issues with wiring this class via
     * Gemini Blueprint 2.2. It throws a
     * {@code SpringBlueprintConverterService$BlueprintConverterException} if
     * the delegate parameter is defined as {@code DatumDataSource}.
     * </p>
     * 
     * @param delegate
     *        the delegate, must implement
     *        {@code DatumDataSource<? extends Datum>}
     * @param locationService
     *        the location service
     * @return the data source
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static LocationDatumDataSource<? extends Datum> getInstance(Object delegate,
            OptionalServiceTracker<LocationService> locationService) {
        LocationDatumDataSource<? extends Datum> ds = new LocationDatumDataSource<Datum>();
        ds.setDelegate((DatumDataSource) delegate);
        ds.setLocationService(locationService);
        return ds;
    }

    @Override
    public Class<? extends T> getDatumType() {
        return delegate.getDatumType();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Class<? extends T> getMultiDatumType() {
        if (delegate instanceof MultiDatumDataSource) {
            return ((MultiDatumDataSource<T>) delegate).getMultiDatumType();
        }
        return delegate.getDatumType();
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<T> readMultipleDatum() {
        Collection<T> results = null;
        if (delegate instanceof MultiDatumDataSource) {
            results = ((MultiDatumDataSource<T>) delegate).readMultipleDatum();
        } else {
            // fake multi API
            results = new ArrayList<T>(1);
            T datum = delegate.readCurrentDatum();
            if (datum != null) {
                results.add(datum);
            }
        }
        if (results != null && locationId != null) {
            for (T datum : results) {
                populateLocation(datum);
            }
        } else if (results != null && results.size() > 0 && locationId == null && requireLocationService) {
            log.warn("Location required but not available, discarding datum: {}", results);
            results = Collections.emptyList();
        }
        return results;
    }

    @Override
    public T readCurrentDatum() {
        T datum = delegate.readCurrentDatum();
        if (datum != null && locationId != null) {
            populateLocation(datum);
        } else if (datum != null && locationId == null && requireLocationService) {
            log.warn("LocationService required but not available, discarding datum: {}", datum);
            datum = null;
        }
        return datum;
    }

    private void populateLocation(T datum) {
        if (locationId != null && sourceId != null && !shouldIgnoreDatum(datum)) {
            log.debug("Augmenting datum {} with locaiton ID {} ({})", datum, locationId, sourceId);
            if (datum instanceof GeneralLocationDatum) {
                GeneralLocationDatum gDatum = (GeneralLocationDatum) datum;
                gDatum.setLocationId(locationId);
                gDatum.setSourceId(sourceId);
            } else if (datum instanceof GeneralNodeDatum) {
                GeneralNodeDatum gDatum = (GeneralNodeDatum) datum;
                gDatum.putStatusSampleValue(PricedDatum.PRICE_LOCATION_KEY, locationId);
                gDatum.putStatusSampleValue(PricedDatum.PRICE_SOURCE_KEY, sourceId);
            } else {
                BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(datum);
                if (bean.isWritableProperty(locationIdPropertyName)
                        && bean.isWritableProperty(sourceIdPropertyName)) {
                    bean.setPropertyValue(locationIdPropertyName, locationId);
                    bean.setPropertyValue(sourceIdPropertyName, sourceId);
                }
            }
        }
    }

    private boolean shouldIgnoreDatum(T datum) {
        return (datumClassNameIgnore != null && datumClassNameIgnore.contains(datum.getClass().getName()));
    }

    @Override
    public String toString() {
        return delegate != null ? delegate.toString() + "[LocationDatumDataSource proxy]"
                : "LocationDatumDataSource";
    }

    @Override
    public String getUID() {
        return delegate.getUID();
    }

    @Override
    public String getGroupUID() {
        return delegate.getGroupUID();
    }

    @Override
    public String getSettingUID() {
        if (delegate instanceof SettingSpecifierProvider) {
            return ((SettingSpecifierProvider) delegate).getSettingUID();
        }
        return getClass().getName();
    }

    @Override
    public String getDisplayName() {
        if (delegate instanceof SettingSpecifierProvider) {
            return ((SettingSpecifierProvider) delegate).getDisplayName();
        }
        return null;
    }

    @Override
    public synchronized MessageSource getMessageSource() {
        if (messageSource == null) {
            MessageSource other = null;
            if (delegate instanceof SettingSpecifierProvider) {
                other = ((SettingSpecifierProvider) delegate).getMessageSource();
            }
            PrefixedMessageSource delegateSource = null;
            if (other != null) {
                delegateSource = new PrefixedMessageSource();
                delegateSource.setDelegate(other);
                delegateSource.setPrefix("delegate.");
            }

            ResourceBundleMessageSource proxySource = new ResourceBundleMessageSource();
            proxySource.setBundleClassLoader(getClass().getClassLoader());
            proxySource.setBasename(messageBundleBasename);
            if (delegateSource != null) {
                proxySource.setParentMessageSource(delegateSource);
            }

            messageSource = proxySource;
        }
        return messageSource;
    }

    public void setMessageSource(MessageSource messageSource) {
        this.messageSource = messageSource;
    }

    @Override
    public List<SettingSpecifier> getSettingSpecifiers() {
        List<SettingSpecifier> result = new ArrayList<SettingSpecifier>();
        result.add(getLocationSettingSpecifier());
        if (delegate instanceof SettingSpecifierProvider) {
            List<SettingSpecifier> delegateResult = ((SettingSpecifierProvider) delegate).getSettingSpecifiers();
            if (delegateResult != null) {
                for (SettingSpecifier spec : delegateResult) {
                    if (spec instanceof KeyedSettingSpecifier<?>) {
                        KeyedSettingSpecifier<?> keyedSpec = (KeyedSettingSpecifier<?>) spec;
                        result.add(keyedSpec.mappedTo("delegate."));
                    } else {
                        result.add(spec);
                    }
                }
            }
        }
        return result;
    }

    private LocationLookupSettingSpecifier getLocationSettingSpecifier() {
        if (location == null && locationService != null && locationId != null && sourceId != null) {
            LocationService service = locationService.service();
            if (service != null) {
                GeneralLocationSourceMetadata meta = service.getLocationMetadata(locationId, sourceId);
                BasicGeneralLocation loc = new BasicGeneralLocation();
                loc.setLocationId(locationId);
                loc.setSourceId(sourceId);
                loc.setSourceMetadata(meta);
                location = loc;
            }
        }
        return new BasicLocationLookupSettingSpecifier("locationKey", locationType, location);
    }

    public DatumDataSource<T> getDelegate() {
        return delegate;
    }

    public void setDelegate(DatumDataSource<T> delegate) {
        this.delegate = delegate;
    }

    public OptionalService<LocationService> getLocationService() {
        return locationService;
    }

    public void setLocationService(OptionalService<LocationService> locationService) {
        this.locationService = locationService;
    }

    public String getLocationIdPropertyName() {
        return locationIdPropertyName;
    }

    public void setLocationIdPropertyName(String locationIdPropertyName) {
        this.locationIdPropertyName = locationIdPropertyName;
    }

    public boolean isRequireLocationService() {
        return requireLocationService;
    }

    public void setRequireLocationService(boolean requireLocationService) {
        this.requireLocationService = requireLocationService;
    }

    public String getLocationType() {
        return locationType;
    }

    public void setLocationType(String locationType) {
        this.locationType = locationType;
    }

    public String getMessageBundleBasename() {
        return messageBundleBasename;
    }

    public void setMessageBundleBasename(String messageBundleBaseName) {
        this.messageBundleBasename = messageBundleBaseName;
    }

    /**
     * Set the location ID and source ID as a single string value. The format of
     * the key is {@code locationId:sourceId}.
     * 
     * @param key
     *        the location and source ID key
     */
    public void setLocationKey(String key) {
        Long newLocationId = null;
        String newSourceId = null;
        if (key != null) {
            int idx = key.indexOf(':');
            if (idx > 0 && idx + 1 < key.length()) {
                newLocationId = Long.valueOf(key.substring(0, idx));
                newSourceId = key.substring(idx + 1);
            }
        }
        setLocationId(newLocationId);
        setSourceId(newSourceId);
    }

    public Long getLocationId() {
        return locationId;
    }

    public void setLocationId(Long locationId) {
        if (this.location != null && locationId != null && !locationId.equals(this.location.getLocationId())) {
            this.location = null; // set to null so we re-fetch from server
        }
        this.locationId = locationId;
    }

    public GeneralLocation getLocation() {
        return location;
    }

    public Set<String> getDatumClassNameIgnore() {
        return datumClassNameIgnore;
    }

    public void setDatumClassNameIgnore(Set<String> datumClassNameIgnore) {
        this.datumClassNameIgnore = datumClassNameIgnore;
    }

    public String getSourceId() {
        return sourceId;
    }

    public void setSourceId(String sourceId) {
        if (this.location != null && sourceId != null && !sourceId.equals(this.location.getSourceId())) {
            this.location = null; // set to null so we re-fetch from server
        }
        this.sourceId = sourceId;
    }

    public String getSourceIdPropertyName() {
        return sourceIdPropertyName;
    }

    public void setSourceIdPropertyName(String sourceIdPropertyName) {
        this.sourceIdPropertyName = sourceIdPropertyName;
    }

}