Package org.fluxtream.core.services.impl

Source Code of org.fluxtream.core.services.impl.PhotoServiceImpl$PhotoFinder

package org.fluxtream.core.services.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
import org.fluxtream.core.OutsideTimeBoundariesException;
import org.fluxtream.core.SimpleTimeInterval;
import org.fluxtream.core.TimeInterval;
import org.fluxtream.core.TimeUnit;
import org.fluxtream.core.aspects.FlxLogger;
import org.fluxtream.core.connectors.Connector;
import org.fluxtream.core.connectors.ObjectType;
import org.fluxtream.core.connectors.annotations.ObjectTypeSpec;
import org.fluxtream.core.connectors.vos.AbstractFacetVO;
import org.fluxtream.core.connectors.vos.AbstractInstantFacetVO;
import org.fluxtream.core.connectors.vos.AbstractPhotoFacetVO;
import org.fluxtream.core.domain.AbstractFacet;
import org.fluxtream.core.domain.ApiKey;
import org.fluxtream.core.domain.CoachingBuddy;
import org.fluxtream.core.domain.GuestSettings;
import org.fluxtream.core.domain.PhotoFacetFinderStrategy;
import org.fluxtream.core.domain.TagFilter;
import org.fluxtream.core.services.GuestService;
import org.fluxtream.core.services.PhotoService;
import org.fluxtream.core.services.SettingsService;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
* @author Chris Bartley (bartley@cmu.edu)
*/
@Service
@Component
public class PhotoServiceImpl implements PhotoService {

    private static final FlxLogger LOG = FlxLogger.getLogger("Fluxtream");
    private static final FlxLogger LOG_DEBUG = FlxLogger.getLogger("Fluxtream");

    @Autowired
    SettingsService settingsService;

    @Autowired
    GuestService guestService;

    @Autowired
    BeanFactory beanFactory;

    private static abstract class PhotoFinder {
        final Map<ObjectType, List<AbstractFacet>> find(final ApiKey apiKey, @Nullable TagFilter tagFilter) {
            final Map<ObjectType, List<AbstractFacet>> facets = new HashMap<ObjectType, List<AbstractFacet>>();
            if (apiKey.getConnector() != null) {
                final ObjectType[] objectTypes = apiKey.getConnector().objectTypes();
                if (objectTypes != null) {
                    for (final ObjectType objectType : objectTypes) {
                        if (objectType.isImageType()) {
                            facets.put(objectType, find(apiKey, objectType, tagFilter));
                        }
                    }
                }
            }
            return facets;
        }

        protected abstract List<AbstractFacet> find(final ApiKey apiKey,
                                                    final ObjectType objectType,
                                                    @Nullable TagFilter tagFilter);
    }

    @Override
    public SortedSet<Photo> getPhotos(long guestId,
                                      TimeInterval timeInterval) throws ClassNotFoundException, IllegalAccessException, InstantiationException, OutsideTimeBoundariesException {
        return getPhotos(guestId, timeInterval, ALL_DEVICES_NAME, DEFAULT_PHOTOS_CHANNEL_NAME, null, null);
    }

    @Override
    public SortedSet<Photo> getPhotos(final long guestId,
                                      final TimeInterval timeInterval,
                                      final String connectorPrettyName,
                                      final String objectTypeName,
                                      @Nullable TagFilter tagFilter) throws ClassNotFoundException, IllegalAccessException, InstantiationException, OutsideTimeBoundariesException {

        return getPhotos(guestId, timeInterval, connectorPrettyName, objectTypeName, tagFilter, new PhotoFinder() {
            public List<AbstractFacet> find(final ApiKey apiKey,
                                            final ObjectType objectType,
                                            @Nullable TagFilter tagFilter) {

                LOG_DEBUG.debug("PhotoServiceImpl.find(): finding photos for ApiKey [" + apiKey + "] and ObjectType [" + objectType + "] having TagFilter [" + tagFilter + "]");

                final PhotoFacetFinderStrategy photoFacetFinderStrategy = getPhotoFacetFinderStrategyFromObjectType(objectType);
                if (photoFacetFinderStrategy != null) {
                    return photoFacetFinderStrategy.findAll(apiKey, objectType, timeInterval, tagFilter);
                }
                return new ArrayList<AbstractFacet>(0);
            }
        });
    }

    @Override
    public SortedSet<Photo> getPhotos(final long guestId,
                                      final long timeInMillis,
                                      final String connectorPrettyName,
                                      final String objectTypeName,
                                      final int desiredCount,
                                      final boolean isGetPhotosBeforeTime,
                                      @Nullable TagFilter tagFilter) throws InstantiationException, IllegalAccessException, ClassNotFoundException, OutsideTimeBoundariesException {

        // make sure the count is >= 1
        final int cleanedDesiredCount = Math.max(1, desiredCount);

        final SortedSet<Photo> photos = getPhotos(guestId, null, connectorPrettyName, objectTypeName, tagFilter, new PhotoFinder() {

            public List<AbstractFacet> find(final ApiKey apiKey,
                                            final ObjectType objectType,
                                            @Nullable TagFilter tagFilter) {

                LOG_DEBUG.debug("PhotoServiceImpl.find(): finding photos for ApiKey [" + apiKey + "] and ObjectType [" + objectType + "] having TagFilter [" + tagFilter + "]");

                final PhotoFacetFinderStrategy photoFacetFinderStrategy = getPhotoFacetFinderStrategyFromObjectType(objectType);
                if (photoFacetFinderStrategy != null) {
                    if (isGetPhotosBeforeTime) {
                        return photoFacetFinderStrategy.findBefore(apiKey, objectType, timeInMillis, cleanedDesiredCount, tagFilter);
                    }
                    else {
                        return photoFacetFinderStrategy.findAfter(apiKey, objectType, timeInMillis, cleanedDesiredCount, tagFilter);
                    }
                }
                return new ArrayList<AbstractFacet>(0);
            }
        });

        // Make sure we don't return more than requested (which may happen if we're merging from multiple photo channels,
        // which can happen for the All.photos device/channel).
        if (photos.size() > cleanedDesiredCount) {

            // first convert to a list for easier extraction
            List<Photo> photosList = new ArrayList<Photo>(photos);

            final SortedSet<Photo> photosSubset = new TreeSet<Photo>();
            if (isGetPhotosBeforeTime) {
                // get the last N photos
                photosSubset.addAll(photosList.subList((photosList.size() - cleanedDesiredCount), photosList.size()));
            }
            else {
                // get the first N photos
                photosSubset.addAll(photosList.subList(0, cleanedDesiredCount));
            }
            return photosSubset;
        }

        return photos;
    }

    @Override
    public Map<String, TimeInterval> getPhotoChannelTimeRanges(final long guestId, final CoachingBuddy coachee) {
        // TODO: This could really benefit from some caching.  The time ranges can only change upon updating a photo
        // connector so it would be better to cache this info and then just refresh it whenever the connector is updated

        Map<String, TimeInterval> photoChannelTimeRanges = new HashMap<String, TimeInterval>();

        List<ApiKey> userKeys = guestService.getApiKeys(guestId);
        for (ApiKey apiKey : userKeys) {
            Connector connector = null;
            if (apiKey != null) {
                connector = apiKey.getConnector();
            }
            if (connector != null && connector.getName() != null &&
                (coachee == null || coachee.hasAccessToConnector(connector.getName())) && connector.hasImageObjectType()) {
                // Check the object types, if any, to find the image object type(s)
                ObjectType[] objectTypes = apiKey.getConnector().objectTypes();
                if (objectTypes == null) {
                    final String channelName = constructChannelName(connector, null);
                    final TimeInterval timeInterval = constructTimeIntervalFromOldestAndNewestFacets(apiKey, null);
                    photoChannelTimeRanges.put(channelName, timeInterval);
                }
                else {
                    for (ObjectType objectType : objectTypes) {
                        if (objectType.isImageType()) {
                            final String channelName = constructChannelName(connector, objectType);
                            final TimeInterval timeInterval = constructTimeIntervalFromOldestAndNewestFacets(apiKey, objectType);
                            photoChannelTimeRanges.put(channelName, timeInterval);
                        }
                    }
                }
            }
        }
        return photoChannelTimeRanges;
    }

    private PhotoFacetFinderStrategy getPhotoFacetFinderStrategyFromObjectType(final ObjectType objectType) {
        if (objectType != null) {
            try {
                final Class<? extends AbstractFacet> facetClass = objectType.facetClass();
                final ObjectTypeSpec objectTypeSpec = facetClass.getAnnotation(ObjectTypeSpec.class);
                final Class<? extends PhotoFacetFinderStrategy> photoFacetFinderStrategyClass = objectTypeSpec.photoFacetFinderStrategy();
                return beanFactory.getBean(photoFacetFinderStrategyClass);
            }
            catch (Exception e) {
                LOG.error("Exception caught while trying trying to instantiate the PhotoFacetFinderStrategy from objectType [" + objectType + "].  Returning null.", e);
            }
        }
        return null;
    }

    /**
     * Returns all photos for {@link Connector}(s) specified by the given <code>connectorPrettyName</code>, and
     * optionally narrowed by the {@link ObjectType} specified by the given <code>objectTypeName</code>.  If the
     * <code>connectorPrettyName</code> is equal to the {@link #ALL_DEVICES_NAME}, then this method checks every
     * Connector for whether it has an image ObjectType and, if so, adds the relevant photos from each image ObjectType
     * belonging to the Connector.  If the <code>connectorPrettyName</code> is not equal to the
     * {@link #ALL_DEVICES_NAME}, then this method finds the specified Connector and ObjectType and adds the photos.
     * Furthermore, if the objectTypeName does not specify an existing ObjectType for the Connector, then this method
     * returns photos from from all ObjectTypes which are of {@link ObjectType#isImageType() image type}.  The set of
     * returned photos may also be optionally filtered by the given <code>tags</code> and <code>tagFilteringStrategy</code>.
     *
     * May return an empty {@link SortedSet}, but guaranteeed to not return <code>null</code>.
     */
    private SortedSet<Photo> getPhotos(final long guestId,
                                       final TimeInterval timeInterval,
                                       final String connectorPrettyName,
                                       final String objectTypeName,
                                       @Nullable TagFilter tagFilter,
                                       final PhotoFinder facetFinderStrategy)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException, OutsideTimeBoundariesException
    {

        SortedSet<Photo> photos = new TreeSet<Photo>();

        if (ALL_DEVICES_NAME.equals(connectorPrettyName)) {
            List<ApiKey> userKeys = guestService.getApiKeys(guestId);
            for (ApiKey apiKey : userKeys) {
                Connector connector = null;
                if (apiKey != null && apiKey.getConnector() != null) {
                    connector = apiKey.getConnector();
                }
                if (connector != null && connector.hasImageObjectType()) {
                    final ObjectType[] objectTypes = connector.objectTypes();
                    if (objectTypes != null) {
                        for (ObjectType objectType : objectTypes) {
                            if (objectType.isImageType()) {
                                List<AbstractFacet> facets = facetFinderStrategy.find(apiKey, objectType, tagFilter);
                                photos.addAll(convertFacetsToPhotos(apiKey, timeInterval, facets, connector, objectType));
                            }
                        }
                    }
                }
            }
        }
        else {
            final ApiKey apiKey = findConnectorApiKeyByPrettyName(guestId, connectorPrettyName);
            if (apiKey != null && apiKey.getConnector() != null) {
                final Connector connector = apiKey.getConnector();
                final ObjectType desiredObjectType = findObjectTypeByName(connector, objectTypeName);

                if (desiredObjectType == null) {
                    final Map<ObjectType, List<AbstractFacet>> facetsByObjectType = facetFinderStrategy.find(apiKey, tagFilter);
                    if ((facetsByObjectType != null) && (!facetsByObjectType.isEmpty())) {
                        for (final ObjectType objectType : facetsByObjectType.keySet()) {
                            final List<AbstractFacet> facets = facetsByObjectType.get(objectType);
                            if (facets != null) {
                                photos.addAll(convertFacetsToPhotos(apiKey, timeInterval, facets, connector, objectType));
                            }
                        }
                    }
                }
                else if (desiredObjectType.isImageType()) {
                    final List<AbstractFacet> facets = facetFinderStrategy.find(apiKey, desiredObjectType, tagFilter);
                    if (facets != null) {
                        photos.addAll(convertFacetsToPhotos(apiKey, timeInterval, facets, connector, desiredObjectType));
                    }
                }
            }
        }

        return photos;
    }

    /** Returns the Connector having the given pretty name.  Returns <code>null</code> if no such connector exists. */
    private ApiKey findConnectorApiKeyByPrettyName(final long guestId, final String connectorPrettyName) {
        List<ApiKey> userKeys = guestService.getApiKeys(guestId);
        for (ApiKey key : userKeys) {
            if (key != null) {
                final Connector connector = key.getConnector();
                if (connector != null && ((connector.prettyName() != null && connector.prettyName().equals(connectorPrettyName)) || (connector.getName().equals(connectorPrettyName)))) {
                    return key;
                }
            }
        }

        return null;
    }

    /**
     * Returns the ObjectType for the given Connector having the given name.  If no such ObjectType exists, or
     * the connector doesn't have any ObjectTypes, or then this method returns <code>null</code>.
     */
    private ObjectType findObjectTypeByName(final Connector connector, final String objectTypeName) {
        if (connector != null && objectTypeName != null) {
            ObjectType[] objectTypes = connector.objectTypes();
            if (objectTypes != null) {
                for (final ObjectType objectType : objectTypes) {
                    if (objectTypeName.equals(objectType.getName())) {
                        return objectType;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Converts {@link AbstractFacet}s to {@link Photo}s.  If the given {@link SimpleTimeInterval} is <code>null</code>, this
     * method creates a new one for each {@link AbstractFacet} using the facet's start time.
     */
    private SortedSet<Photo> convertFacetsToPhotos(final ApiKey apiKey,
                                                   final TimeInterval timeInterval,
                                                   final List<AbstractFacet> facets,
                                                   final Connector connector,
                                                   final ObjectType objectType)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException, OutsideTimeBoundariesException
    {

        SortedSet<Photo> photos = new TreeSet<Photo>();

        GuestSettings settings = settingsService.getSettings(apiKey.getGuestId());
        for (AbstractFacet facet : facets) {
            Class<? extends AbstractFacetVO<AbstractFacet>> jsonFacetClass = AbstractFacetVO.getFacetVOClass(facet);
            AbstractInstantFacetVO<AbstractFacet> facetVo = (AbstractInstantFacetVO<AbstractFacet>)jsonFacetClass.newInstance();

            final TimeInterval actualTimeInterval;
            if (timeInterval == null) {
                actualTimeInterval = new SimpleTimeInterval(facet.start, facet.start, TimeUnit.ARBITRARY, TimeZone.getTimeZone("UTC"));
            }
            else {
                actualTimeInterval = timeInterval;
            }

            facetVo.extractValues(facet, actualTimeInterval, settings);
            photos.add(new PhotoImpl((AbstractPhotoFacetVO)facetVo, connector, objectType));
        }

        return photos;
    }

    /**
     * Constructs a channel name as the concatenation of the Connector pretty name and the ObjectType name.  Uses
     * the {@link #DEFAULT_PHOTOS_CHANNEL_NAME} if the ObjectType is <code>null</code>.
     */
    private String constructChannelName(final Connector connector, final ObjectType objectType) {
        return connector.prettyName() + "." + (objectType == null ? DEFAULT_PHOTOS_CHANNEL_NAME : objectType.getName());
    }

    /**
     * Returns the {@link SimpleTimeInterval} for the oldest and newest facets.  Returns <code>null</code> if no facets exist.
     */
    private TimeInterval constructTimeIntervalFromOldestAndNewestFacets(final ApiKey apiKey,
                                                                        final ObjectType objectType) {
        final PhotoFacetFinderStrategy photoFacetFinderStrategy = getPhotoFacetFinderStrategyFromObjectType(objectType);
        final AbstractFacet oldestFacet = photoFacetFinderStrategy.findOldest(apiKey, objectType);
        final AbstractFacet newestFacet = photoFacetFinderStrategy.findLatest(apiKey, objectType);

        if (oldestFacet != null && newestFacet != null) {
            return new SimpleTimeInterval(oldestFacet.start, newestFacet.start, TimeUnit.ARBITRARY, TimeZone.getTimeZone("UTC"));
        }

        return null;
    }

    private static final class PhotoImpl implements Photo, Comparable<Photo> {

        private final AbstractPhotoFacetVO facetVo;
        private final Connector connector;
        private final ObjectType objectType;

        private PhotoImpl(final AbstractPhotoFacetVO facetVo, final Connector connector, final ObjectType objectType) {
            this.facetVo = facetVo;
            this.connector = connector;
            this.objectType = objectType;
        }

        @Override
        public AbstractPhotoFacetVO getAbstractPhotoFacetVO() {
            return facetVo;
        }

        @Override
        public Connector getConnector() {
            return connector;
        }

        @Override
        public ObjectType getObjectType() {
            return objectType;
        }

        @Override
        public int compareTo(final Photo that) {

            final Long thisStart = this.getAbstractPhotoFacetVO().start;
            final Long thatStart = that.getAbstractPhotoFacetVO().start;
            int comparison = thisStart.compareTo(thatStart);
            if (comparison != 0) {
                return comparison;
            }

            final String thisId = this.getConnector().getName() + "." + this.getObjectType().getName() + "." + this.getAbstractPhotoFacetVO().id + "." + this.getAbstractPhotoFacetVO().getPhotoUrl();
            final String thatId = that.getConnector().getName() + "." + that.getObjectType().getName() + "." + that.getAbstractPhotoFacetVO().id + "." + that.getAbstractPhotoFacetVO().getPhotoUrl();

            return thisId.compareTo(thatId);
        }

        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            final PhotoImpl photo = (PhotoImpl)o;

            if (connector != null ? !connector.equals(photo.connector) : photo.connector != null) {
                return false;
            }
            if (facetVo != null ? !facetVo.equals(photo.facetVo) : photo.facetVo != null) {
                return false;
            }
            if (objectType != null ? !objectType.equals(photo.objectType) : photo.objectType != null) {
                return false;
            }

            return true;
        }

        @Override
        public int hashCode() {
            int result = facetVo != null ? facetVo.hashCode() : 0;
            result = 31 * result + (connector != null ? connector.hashCode() : 0);
            result = 31 * result + (objectType != null ? objectType.hashCode() : 0);
            return result;
        }
    }
}
TOP

Related Classes of org.fluxtream.core.services.impl.PhotoServiceImpl$PhotoFinder

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.