org.tdar.core.service.resource.ResourceService.java Source code

Java tutorial

Introduction

Here is the source code for org.tdar.core.service.resource.ResourceService.java

Source

package org.tdar.core.service.resource;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.tdar.core.bean.HasResource;
import org.tdar.core.bean.cache.HomepageGeographicKeywordCache;
import org.tdar.core.bean.cache.HomepageResourceCountCache;
import org.tdar.core.bean.cache.WeeklyPopularResourceCache;
import org.tdar.core.bean.collection.ResourceCollection;
import org.tdar.core.bean.collection.ResourceCollection.CollectionType;
import org.tdar.core.bean.coverage.LatitudeLongitudeBox;
import org.tdar.core.bean.entity.AuthorizedUser;
import org.tdar.core.bean.entity.Person;
import org.tdar.core.bean.entity.ResourceCreator;
import org.tdar.core.bean.entity.TdarUser;
import org.tdar.core.bean.keyword.GeographicKeyword;
import org.tdar.core.bean.keyword.Keyword;
import org.tdar.core.bean.resource.InformationResource;
import org.tdar.core.bean.resource.Project;
import org.tdar.core.bean.resource.Resource;
import org.tdar.core.bean.resource.ResourceNote;
import org.tdar.core.bean.resource.ResourceNoteType;
import org.tdar.core.bean.resource.ResourceRevisionLog;
import org.tdar.core.bean.resource.ResourceType;
import org.tdar.core.bean.resource.Status;
import org.tdar.core.bean.resource.datatable.DataTable;
import org.tdar.core.bean.statistics.AggregateDownloadStatistic;
import org.tdar.core.bean.statistics.AggregateViewStatistic;
import org.tdar.core.bean.statistics.ResourceAccessStatistic;
import org.tdar.core.configuration.TdarConfiguration;
import org.tdar.core.dao.GenericDao.FindOptions;
import org.tdar.core.dao.resource.DataTableDao;
import org.tdar.core.dao.resource.DatasetDao;
import org.tdar.core.dao.resource.ProjectDao;
import org.tdar.core.dao.resource.stats.DateGranularity;
import org.tdar.core.dao.resource.stats.ResourceSpaceUsageStatistic;
import org.tdar.core.exception.TdarRecoverableRuntimeException;
import org.tdar.core.exception.TdarRuntimeException;
import org.tdar.core.service.DeleteIssue;
import org.tdar.core.service.EntityService;
import org.tdar.core.service.GenericService;
import org.tdar.core.service.ResourceCreatorProxy;
import org.tdar.core.service.SerializationService;
import org.tdar.core.service.external.AuthorizationService;
import org.tdar.search.geosearch.GeoSearchService;
import org.tdar.search.query.SearchResultHandler;
import org.tdar.utils.PersistableUtils;

import com.opensymphony.xwork2.TextProvider;
import com.redfin.sitemapgenerator.GoogleImageSitemapGenerator;

@Service
public class ResourceService extends GenericService {

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

    public enum ErrorHandling {
        NO_VALIDATION, VALIDATE_SKIP_ERRORS, VALIDATE_WITH_EXCEPTION
    }

    @Autowired
    private transient SerializationService serializationService;

    @Autowired
    private transient EntityService entityService;

    @Autowired
    private DatasetDao datasetDao;
    @Autowired
    private ProjectDao projectDao;
    @Autowired
    private DataTableDao dataTableDao;

    @Autowired
    private AuthorizationService authenticationAndAuthorizationService;
    @Autowired
    private GeoSearchService geoSearchService;

    @Transactional(readOnly = true)
    public List<Resource> findSkeletonsForSearch(boolean trustCache, Long... ids) {
        return datasetDao.findSkeletonsForSearch(trustCache, ids);
    }

    @Transactional(readOnly = true)
    public List<Resource> findOld(Long... ids) {
        return datasetDao.findOld(ids);
    }

    /**
     * Find all @Link Resource Ids submitted by @link Person
     * 
     * @param person
     * @return
     */
    @Transactional(readOnly = true)
    public Set<Long> findResourcesSubmittedByUser(Person person) {
        return datasetDao.findResourcesSubmittedByUser(person);
    }

    /**
     * Find @link Resource by Id only.
     * 
     * @param id
     * @return
     */
    @SuppressWarnings("unchecked")
    @Transactional(readOnly = true)
    public <R extends Resource> R find(Long id) {
        if (id == null) {
            return null;
        }
        ResourceType rt = datasetDao.findResourceType(id);
        logger.trace("finding resource " + id + " type:" + rt);
        if (rt == null) {
            return null;
        }
        return (R) datasetDao.find(rt.getResourceClass(), id);
    }

    /**
     * Finds all Resources within tDAR and populates them with sparse data -- Title, Description, Date.
     * 
     * @return
     */
    @Transactional(readOnly = true)
    public List<Resource> findAllSparseActiveResources() {
        return datasetDao.findAllSparseActiveResources();
    }

    /**
     * Finds @link Resource entries that have been modified recently.
     */
    public List<Resource> findRecentlyUpdatedItemsInLastXDays(int days) {
        return datasetDao.findRecentlyUpdatedItemsInLastXDays(days);
    }

    /**
     * Adds a @link ResourceRevisionLog entry for the resource based on the message.
     * 
     * @param <T>
     * @param modifiedResource
     * @param message
     * @param payload
     */
    @Transactional
    public <T extends Resource> void logResourceModification(T modifiedResource, TdarUser person, String message) {
        logResourceModification(modifiedResource, person, message, null);
    }

    /**
     * Adds a @link ResourceRevisionLog entry for the resource based on the message.
     * 
     * @param <T>
     * @param modifiedResource
     * @param message
     * @param payload
     */
    @Transactional
    public <T extends Resource> void logResourceModification(T modifiedResource, TdarUser person, String message,
            String payload) {
        ResourceRevisionLog log = new ResourceRevisionLog();
        log.setLogMessage(message);
        log.setResource(modifiedResource);
        log.setPerson(person);
        log.setTimestamp(new Date());
        log.setPayload(payload);
        save(log);
    }

    /**
     * Lists all tDAR @link Status entries.
     * 
     * @return
     */
    public List<Status> findAllStatuses() {
        return Arrays.asList(Status.values());
    }

    /**
     * For a given @link Resource, increment the Access Count by creating a @link ResourceAccessStatistic Entry. This is a service-layer function because (a)
     * this may happen when the session is not writable, and we're going to make the explicit bean writable, and (b) because it's a transient (separate) value.
     * 
     * @param r
     */
    @Transactional(readOnly = false)
    public void incrementAccessCounter(Resource r) {
        ResourceAccessStatistic rac = new ResourceAccessStatistic(new Date(), r);
        datasetDao.markWritable(rac);
        save(rac);
    }

    /**
     * Updates the transient access count entry on @link Resource
     * 
     * @param resource
     */
    @Transactional(readOnly = true)
    public void updateTransientAccessCount(Resource resource) {
        resource.setTransientAccessCount(datasetDao.getAccessCount(resource).longValue());
    }

    /**
     * Provides a count of the total number of active resources.
     * 
     * @param type
     * @return
     */
    @Transactional(readOnly = true)
    public Number countActiveResources(ResourceType type) {
        return datasetDao.countActiveResources(type);
    }

    /**
     * Provides a count of the total number of active resources with files.
     * 
     * @param type
     * @return
     */
    @Transactional(readOnly = true)
    public Number countActiveResourcesWithFiles(ResourceType type) {
        return datasetDao.countActiveResourcesWithFiles(type);
    }

    /**
     * Provides counts of Country Codes in tDAR based on the Managed Geographic Keywords which are generated by the tDAR GIS Database and lookups comparing a
     * LatLong to a shapefile. These are used on the homepage map.
     * 
     * @return
     */
    @Transactional(readOnly = true)
    public List<HomepageGeographicKeywordCache> getISOGeographicCounts() {
        return datasetDao.getISOGeographicCounts();
    }

    /**
     * For a given @link Resource and set of @link LatitudeLogitudeBox entries, pass the LatitudeLogitudeBoxes to the @link GeographicSearchService to generate
     * GeographicKeyword entries from the LatitudeLogitudeBox. The implementation will clear the old keywords when creating the new ones. It will use Shapefiles
     * and the tdar_gis database (tdar.support repo) to look up where the LatitudeLogitudeBox is.
     * 
     * @param resource
     * @param allLatLongBoxes
     */
    @Transactional
    public void processManagedKeywords(Resource resource, Collection<LatitudeLongitudeBox> allLatLongBoxes) {
        // needed in cases like the APIController where the collection is not properly initialized
        if (resource.getManagedGeographicKeywords() == null) {
            resource.setManagedGeographicKeywords(new LinkedHashSet<GeographicKeyword>());
        }

        Set<GeographicKeyword> kwds = new HashSet<GeographicKeyword>();
        for (LatitudeLongitudeBox latLong : allLatLongBoxes) {
            Set<GeographicKeyword> managedKeywords = geoSearchService.extractAllGeographicInfo(latLong);
            logger.debug(resource.getId() + " :  " + managedKeywords + " " + managedKeywords.size());
            kwds.addAll(datasetDao.findByExamples(GeographicKeyword.class, managedKeywords,
                    Arrays.asList(Keyword.IGNORE_PROPERTIES_FOR_UNIQUENESS), FindOptions.FIND_FIRST_OR_CREATE));
        }
        PersistableUtils.reconcileSet(resource.getManagedGeographicKeywords(), kwds);
    }

    @Transactional
    /**
     * Given a collection of hibernate-managed beans (the 'current' collection)  and another collection of transient beans (the 'incoming' collection),
     * update the current collection to match the contents of the incoming collection. This method will associate all elements in the incoming collection 
     * with the specified resource.  Contents of both collections should satisfy the HasResource interface.
     * 
     * @param resource  the 'owner' of the elements in the incoming collection.  This method will associate all elements of the incoming collection with this resource.
     * @param shouldSave if true,  this method will persist elements of the incoming collection.
     * @param validateMethod determines what validation steps (if any) to perform on each element of the incoming collection
     * @param incoming_ the incoming collection of HasResource elements.  
     * @param current the current collection of HasResource elements.  This method will modify collection to contain the same elements as the incoming collection.
     * @param cls type of the collection elements.
     */
    public <H extends HasResource<R>, R extends Resource> void saveHasResources(R resource, boolean shouldSave,
            ErrorHandling validateMethod, Collection<H> incoming_, Set<H> current, Class<H> cls) {
        if (CollectionUtils.isEmpty(incoming_) && CollectionUtils.isEmpty(current)) {
            // skip a complete no-op
            return;
        }

        if (incoming_ == null) {
            incoming_ = new ArrayList<H>();
        }
        Collection<H> incoming = incoming_;
        // there are cases where current and incoming_ are the same object, if that's the case
        // then we need to copy incoming_ before
        if ((incoming_ == current) && !CollectionUtils.isEmpty(incoming_)) {
            incoming = new ArrayList<H>();
            incoming.addAll(incoming_);
            current.clear();
        }

        // assume everything that's incoming is valid or deduped and tied back into tDAR entities/beans
        logger.debug("Current Collection of {}s ({}) : {} ",
                new Object[] { cls.getSimpleName(), current.size(), current });

        /*
         * Because we're using ID for the equality and hashCode, we have no way to avoid deleting everything and re-adding it.
         * This is an issue as what'll end up happening otherwise is something like editing a Date results in no persisted change because the
         * "retainAll" below keeps the older version
         */

        current.retainAll(incoming);
        Map<Long, H> idMap = PersistableUtils.createIdMap(current);
        if (CollectionUtils.isNotEmpty(incoming)) {
            logger.debug("Incoming Collection of {}s ({})  : {} ",
                    new Object[] { cls.getSimpleName(), incoming.size(), incoming });
            Iterator<H> incomingIterator = incoming.iterator();
            while (incomingIterator.hasNext()) {
                H hasResource_ = incomingIterator.next();

                if (hasResource_ != null) {

                    // attach the incoming notes to a hibernate session
                    logger.trace("adding {} to {} ", hasResource_, current);
                    H existing = idMap.get(hasResource_.getId());
                    /*
                     * If we're not transient, compare the two beans on all of their local properties (non-recursive) -- if there are differences
                     * copy. otherwise, move on. Question -- it may be more work to compare than to just "copy"... is it worth it?
                     */
                    if (PersistableUtils.isNotNullOrTransient(existing)
                            && !EqualsBuilder.reflectionEquals(existing, hasResource_)) {
                        try {
                            logger.trace("copying bean properties for entry in existing set");
                            BeanUtils.copyProperties(existing, hasResource_);
                        } catch (Exception e) {
                            logger.error("exception setting bean property", e);
                        }
                    }

                    if (validateMethod != ErrorHandling.NO_VALIDATION) {
                        boolean isValid = false;
                        if (hasResource_ instanceof ResourceCreator) {
                            isValid = ((ResourceCreator) hasResource_).isValidForResource(resource);
                        } else {
                            isValid = hasResource_.isValid();
                        }

                        if (!isValid) {
                            logger.debug("skipping: {} - INVALID", hasResource_);
                            if (validateMethod == ErrorHandling.VALIDATE_WITH_EXCEPTION) {
                                throw new TdarRecoverableRuntimeException(hasResource_ + " is not valid");
                            }
                            continue;
                        }
                    }

                    current.add(hasResource_);

                    // if (shouldSave) {
                    // getGenericDao().saveOrUpdate(hasResource_);
                    // }
                }
            }
        }
        logger.debug("Resulting Collection of {}s ({}) : {} ",
                new Object[] { cls.getSimpleName(), current.size(), current });
    }

    /**
     * Find all of the Resource Counts for the homepage which are cached in @link HomepageResourceCountCache entries because the counts take too long to load
     * dynamically.
     * 
     * @return
     */
    public List<HomepageResourceCountCache> getResourceCounts() {
        return datasetDao.getResourceCounts();
    }

    /**
     * For the Dashboard, create a Map<> of ResourceType to Status & Count for a given @link Person User. This map can be used to generate the two graphs on the
     * dashboard.
     * 
     * @param p
     * @param resourceTypes
     * @return
     */
    public Map<ResourceType, Map<Status, Long>> getResourceCountAndStatusForUser(Person p,
            List<ResourceType> resourceTypes) {
        return datasetDao.getResourceCountAndStatusForUser(p, resourceTypes);
    }

    /**
     * For a given @link ResourceType and @link Status provide a count of @link Resource entries.
     * 
     * @param resourceType
     * @param status
     * @return
     */
    public Long getResourceCount(ResourceType resourceType, Status status) {
        return datasetDao.getResourceCount(resourceType, status);
    }

    /**
     * Use by the @link BulkUploadService, we use a proxy @link Resource (image) to create a new @link Resource of the specified type.
     * 
     * @param proxy
     * @param resourceClass
     * @return
     */
    @Transactional
    public <T extends Resource> T createResourceFrom(Resource proxy, Class<T> resourceClass) {
        return createResourceFrom(proxy, resourceClass, true);
    }

    /**
     * Use by the @link BulkUploadService, we use a proxy @link Resource (image) to create a new @link Resource of the specified type.
     * 
     * @param proxy
     * @param resourceClass
     * @return
     */
    @Transactional
    public <T extends Resource> T createResourceFrom(Resource proxy, Class<T> resourceClass, boolean save) {
        try {
            T resource = resourceClass.newInstance();
            resource.setTitle(proxy.getTitle());
            resource.setDescription(proxy.getDescription());
            if (StringUtils.isEmpty(resource.getDescription())) {
                resource.setDescription(" ");
            }
            resource.setDateCreated(proxy.getDateCreated());
            resource.markUpdated(proxy.getSubmitter());
            resource.setStatus(proxy.getStatus());
            if (save) {
                datasetDao.save(resource);
            }
            resource.getMaterialKeywords().addAll(proxy.getMaterialKeywords());
            resource.getTemporalKeywords().addAll(proxy.getTemporalKeywords());
            resource.getInvestigationTypes().addAll(proxy.getInvestigationTypes());
            resource.getCultureKeywords().addAll(proxy.getCultureKeywords());
            resource.getOtherKeywords().addAll(proxy.getOtherKeywords());
            resource.getSiteNameKeywords().addAll(proxy.getSiteNameKeywords());
            resource.getSiteTypeKeywords().addAll(proxy.getSiteTypeKeywords());
            resource.getGeographicKeywords().addAll(proxy.getGeographicKeywords());
            resource.getManagedGeographicKeywords().addAll(proxy.getManagedGeographicKeywords());
            // CLONE if internal, otherwise just add

            for (ResourceCollection collection : proxy.getResourceCollections()) {
                if (collection.isInternal()) {
                    logger.info("cloning collection: {}", collection);
                    ResourceCollection newInternal = new ResourceCollection(CollectionType.INTERNAL);
                    newInternal.setName(collection.getName());
                    TdarUser owner = collection.getOwner();
                    refresh(owner);
                    newInternal.markUpdated(owner);
                    if (save) {
                        datasetDao.save(newInternal);
                    }
                    for (AuthorizedUser proxyAuthorizedUser : collection.getAuthorizedUsers()) {
                        AuthorizedUser newAuthorizedUser = new AuthorizedUser(proxyAuthorizedUser.getUser(),
                                proxyAuthorizedUser.getGeneralPermission());
                        newInternal.getAuthorizedUsers().add(newAuthorizedUser);
                    }
                    resource.getResourceCollections().add(newInternal);
                    newInternal.getResources().add(resource);
                } else {
                    logger.info("adding to shared collection : {} ", collection);
                    if (collection.isTransient() && save) {
                        save(collection);
                    }
                    collection.getResources().add(resource);
                    resource.getResourceCollections().add(collection);
                }
            }

            cloneSet(resource, resource.getCoverageDates(), proxy.getCoverageDates());
            cloneSet(resource, resource.getLatitudeLongitudeBoxes(), proxy.getLatitudeLongitudeBoxes());
            cloneSet(resource, resource.getResourceCreators(), proxy.getResourceCreators());
            cloneSet(resource, resource.getResourceAnnotations(), proxy.getResourceAnnotations());
            cloneSet(resource, resource.getResourceNotes(), proxy.getResourceNotes());
            cloneSet(resource, resource.getRelatedComparativeCollections(),
                    proxy.getRelatedComparativeCollections());
            cloneSet(resource, resource.getSourceCollections(), proxy.getSourceCollections());

            if ((resource instanceof InformationResource) && (proxy instanceof InformationResource)) {
                InformationResource proxyInformationResource = (InformationResource) proxy;
                InformationResource informationResource = (InformationResource) resource;
                informationResource.setDate(proxyInformationResource.getDate());
                // force project into the session
                if (PersistableUtils.isNotNullOrTransient(proxyInformationResource.getProject())) {
                    Project project = proxyInformationResource.getProject();
                    // refresh(project);
                    informationResource.setProject(project);
                }
                informationResource.setPublisher(proxyInformationResource.getPublisher());
                informationResource.setCopyrightHolder(proxyInformationResource.getCopyrightHolder());
                informationResource.setLicenseText(proxyInformationResource.getLicenseText());
                informationResource.setLicenseType(proxyInformationResource.getLicenseType());
                informationResource.setPublisherLocation(proxyInformationResource.getPublisherLocation());
                informationResource
                        .setResourceProviderInstitution(proxyInformationResource.getResourceProviderInstitution());
                informationResource.setResourceLanguage(proxyInformationResource.getResourceLanguage());
                informationResource.setMetadataLanguage(proxyInformationResource.getMetadataLanguage());
                informationResource.setInheritingCulturalInformation(
                        proxyInformationResource.isInheritingCulturalInformation());
                informationResource.setInheritingInvestigationInformation(
                        proxyInformationResource.isInheritingInvestigationInformation());
                informationResource.setInheritingMaterialInformation(
                        proxyInformationResource.isInheritingMaterialInformation());
                informationResource
                        .setInheritingOtherInformation(proxyInformationResource.isInheritingOtherInformation());
                informationResource
                        .setInheritingSiteInformation(proxyInformationResource.isInheritingSiteInformation());
                informationResource
                        .setInheritingSpatialInformation(proxyInformationResource.isInheritingSpatialInformation());
                informationResource.setInheritingTemporalInformation(
                        proxyInformationResource.isInheritingTemporalInformation());
                informationResource.setInheritingIdentifierInformation(
                        proxyInformationResource.isInheritingIdentifierInformation());
                informationResource
                        .setInheritingNoteInformation(proxyInformationResource.isInheritingNoteInformation());
                informationResource.setInheritingCollectionInformation(
                        proxyInformationResource.isInheritingCollectionInformation());
                informationResource.setInheritingIndividualAndInstitutionalCredit(
                        proxyInformationResource.isInheritingIndividualAndInstitutionalCredit());
            }
            if (save) {
                datasetDao.saveOrUpdate(resource);
                return datasetDao.merge(resource);
            }
            return resource;
            // NOTE: THIS SHOULD BE THE LAST THING DONE AS IT BRINGS EVERYTHING BACK ONTO THE SESSION PROPERLY
        } catch (Exception exception) {
            throw new TdarRuntimeException(exception);
        }
    }

    /**
     * Given a Set of objects that support @link HasResource, clone the bean and attach it to the new Set
     * 
     * @param resource
     * @param targetCollection
     * @param sourceCollection
     * @return
     */
    @Transactional
    public <T extends HasResource<Resource>> Set<T> cloneSet(Resource resource, Set<T> targetCollection,
            Set<T> sourceCollection) {
        logger.trace("cloning: " + sourceCollection);
        for (T t : sourceCollection) {
            datasetDao.detachFromSessionAndWarn(t);
            try {
                @SuppressWarnings("unchecked")
                T clone = (T) BeanUtils.cloneBean(t);
                targetCollection.add(clone);
            } catch (Exception e) {
                logger.warn("Exception in clone set: {} ", e);
            }
        }
        // getDao().save(targetCollection);
        return targetCollection;
    }

    /**
     * For a given list of People, Resources, Collections, Projects, and Statuses, return a @link ResourceSpaceUsageStatistic object for how much space,
     * resources, and files are used.
     * 
     * @param personId
     * @param resourceId
     * @param collectionId
     * @param projectId
     * @param statuses
     * @return
     */
    @Transactional
    public ResourceSpaceUsageStatistic getResourceSpaceUsageStatistics(List<Long> personId, List<Long> resourceId,
            List<Long> collectionId, List<Long> projectId, List<Status> statuses) {
        return datasetDao.getResourceSpaceUsageStatistics(personId, resourceId, collectionId, projectId, statuses);
    }

    /**
     * Find the count of views for all resources for a given date range, limited by the minimum occurrence count.
     * 
     * @param granularity
     * @param start
     * @param end
     * @param minCount
     * @return
     */
    @Transactional
    public List<AggregateViewStatistic> getAggregateUsageStats(DateGranularity granularity, Date start, Date end,
            Long minCount) {
        return datasetDao.getAggregateUsageStats(granularity, start, end, minCount);
    }

    /**
     * Find the count of downloads for all InformationResourceFiles for a given date range, limited by the minimum occurrence count.
     * 
     * @param granularity
     * @param start
     * @param end
     * @param minCount
     * @return
     */
    @Transactional
    public List<AggregateViewStatistic> getOverallUsageStats(Date start, Date end, Long max) {
        return datasetDao.getOverallUsageStats(start, end, max);
    }

    @Transactional
    public List<AggregateDownloadStatistic> getAggregateDownloadStats(DateGranularity granularity, Date start,
            Date end, Long minCount) {
        return datasetDao.getAggregateDownloadStats(granularity, start, end, minCount);
    }

    /**
     * Find the count of downloads for a specified @link InformationResourceFile for a given date range, limited by the minimum occurrence count.
     * 
     * @param granularity
     * @param start
     * @param end
     * @param minCount
     * @param iRFileId
     * @return
     */
    @Transactional
    public List<AggregateDownloadStatistic> getAggregateDownloadStatsForFile(DateGranularity granularity,
            Date start, Date end, Long minCount, Long iRFileId) {
        return datasetDao.getDownloadStatsForFile(granularity, start, end, minCount, iRFileId);
    }

    /**
     * Find the count of views for the specified resources for a given date range, limited by the minimum occurrence count.
     * 
     * @param granularity
     * @param start
     * @param end
     * @param minCount
     * @param resourceIds
     * @return
     */
    @Transactional
    public List<AggregateViewStatistic> getUsageStatsForResources(DateGranularity granularity, Date start, Date end,
            Long minCount, List<Long> resourceIds) {
        return datasetDao.getUsageStatsForResource(granularity, start, end, minCount, resourceIds);
    }

    /**
     * Return all resource revision log entries for a specified @link Resource.
     * 
     * @param resource
     * @return
     */
    @Transactional
    public List<ResourceRevisionLog> getLogsForResource(Resource resource) {
        if (PersistableUtils.isNullOrTransient(resource)) {
            return Collections.emptyList();
        }
        return datasetDao.getLogEntriesForResource(resource);
    }

    /**
     * Find all ids of @link InformationResource entries that actually have files.
     * 
     * @return
     */
    @Transactional
    public List<Long> findAllResourceIdsWithFiles() {
        return datasetDao.findAllResourceIdsWithFiles();
    }

    /**
     * Find all @link ResourceType entries that are active within the system (as opposed to exist).
     * 
     * @return
     */
    public List<ResourceType> getAllResourceTypes() {
        ArrayList<ResourceType> arrayList = new ArrayList<>(Arrays.asList(ResourceType.values()));
        if (!TdarConfiguration.getInstance().isVideoEnabled()) {
            arrayList.remove(ResourceType.VIDEO);
            arrayList.remove(ResourceType.AUDIO);
        }

        if (!TdarConfiguration.getInstance().isArchiveFileEnabled()) {
            arrayList.remove(ResourceType.ARCHIVE);
        }
        return arrayList;
    }

    @Transactional
    public int findAllResourcesWithPublicImagesForSitemap(GoogleImageSitemapGenerator gisg) {
        return datasetDao.findAllResourcesWithPublicImagesForSitemap(gisg);

    }

    @Transactional
    public void setupWorldMap(HashMap<String, HomepageGeographicKeywordCache> worldMapData) {
        Long countryTotal = 0l;
        Double countryLogTotal = 0d;
        for (HomepageGeographicKeywordCache item : findAllWithL2Cache(HomepageGeographicKeywordCache.class)) {
            Long count = item.getCount();
            Double logCount = item.getLogCount();
            if (logCount > countryLogTotal) {
                countryLogTotal = logCount;
            }
            if (count > countryTotal) {
                countryTotal = count;
            }
            worldMapData.put(item.getKey(), item);
        }
        for (Entry<String, HomepageGeographicKeywordCache> entrySet : worldMapData.entrySet()) {
            entrySet.getValue().setTotalCount(countryTotal);
            entrySet.getValue().setTotalLogCount(countryLogTotal);
        }
    }

    @Transactional(readOnly = true)
    public List<Resource> findByTdarYear(SearchResultHandler<Resource> resultHandler, int year) {
        return datasetDao.findByTdarYear(resultHandler, year);
    }

    @Transactional(readOnly = true)
    public List<Resource> getWeeklyPopularResources(int count) {
        List<Resource> featured = new ArrayList<>();
        try {
            int cacheCount = 0;
            for (WeeklyPopularResourceCache cache : datasetDao.findAll(WeeklyPopularResourceCache.class)) {
                Resource key = cache.getKey();
                if (key instanceof Resource) {
                    authenticationAndAuthorizationService.applyTransientViewableFlag(key, null);
                }
                if (key.isActive()) {
                    if (cacheCount == count) {
                        break;
                    }
                    cacheCount++;
                    featured.add(key);
                }
            }
        } catch (IndexOutOfBoundsException ioe) {
            logger.debug("no featured resources found");
        }
        return featured;
    }

    @Transactional(readOnly = false)
    public void saveResourceCreatorsFromProxies(Collection<ResourceCreatorProxy> allProxies, Resource resource,
            boolean shouldSaveResource) {
        logger.info("ResourceCreators before DB lookup: {} ", allProxies);
        int sequence = 0;
        List<ResourceCreator> incomingResourceCreators = new ArrayList<>();
        // convert the list of proxies to a list of resource creators
        for (ResourceCreatorProxy proxy : allProxies) {
            if ((proxy != null) && proxy.isValid()) {
                ResourceCreator resourceCreator = proxy.getResourceCreator();
                resourceCreator.setSequenceNumber(sequence++);
                logger.trace("{} - {}", resourceCreator, resourceCreator.getCreatorType());

                entityService.findOrSaveResourceCreator(resourceCreator);
                incomingResourceCreators.add(resourceCreator);
                logger.trace("{} - {}", resourceCreator, resourceCreator.getCreatorType());
            } else {
                logger.trace("can't create creator from proxy {} {}", proxy);
            }
        }

        // FIXME: Should this throw errors?
        saveHasResources(resource, shouldSaveResource, ErrorHandling.VALIDATE_SKIP_ERRORS, incomingResourceCreators,
                resource.getResourceCreators(), ResourceCreator.class);

    }

    @Transactional(readOnly = true)
    public DeleteIssue getDeletionIssues(TextProvider provider, Resource persistable) {
        DeleteIssue issue = new DeleteIssue();
        switch (persistable.getResourceType()) {
        case PROJECT:
            Set<InformationResource> inProject = projectDao.findAllResourcesInProject((Project) persistable,
                    Status.ACTIVE, Status.DRAFT);
            if (CollectionUtils.isNotEmpty(inProject)) {
                issue.getRelatedItems().addAll(inProject);
                issue.setIssue(provider.getText("resourceDeleteController.delete_project"));
                return issue;
            }
            return null;
        case CODING_SHEET:
        case ONTOLOGY:
            List<DataTable> related = dataTableDao.findDataTablesUsingResource(persistable);
            if (CollectionUtils.isNotEmpty(related)) {
                for (DataTable table : related) {
                    if (!table.getDataset().isDeleted()) {
                        issue.getRelatedItems().add(table.getDataset());
                    }
                }
                issue.setIssue(provider.getText("abstractSupportingInformationResourceController.remove_mappings"));
                return issue;
            }
            return null;
        default:
            return null;
        }
    }

    @Transactional(readOnly = false)
    public void deleteForController(Resource resource, String reason, TdarUser authUser) {
        if (StringUtils.isNotEmpty(reason)) {
            ResourceNote note = new ResourceNote(ResourceNoteType.ADMIN, reason);
            resource.getResourceNotes().add(note);
            save(note);
        } else {
            reason = "reason not specified";
        }
        String logMessage = String.format("%s id:%s deleted by:%s reason: %s", resource.getResourceType().name(),
                resource.getId(), authUser, reason);
        logResourceModification(resource, authUser, logMessage);
        delete(resource);
    }
}