ubic.gemma.core.analysis.report.WhatsNewServiceImpl.java Source code

Java tutorial

Introduction

Here is the source code for ubic.gemma.core.analysis.report.WhatsNewServiceImpl.java

Source

/*
 * The Gemma project
 *
 * Copyright (c) 2007 University of British Columbia
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package ubic.gemma.core.analysis.report;

import gemma.gsec.SecurityService;
import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;
import ubic.basecode.util.FileTools;
import ubic.gemma.model.common.Auditable;
import ubic.gemma.model.expression.arrayDesign.ArrayDesign;
import ubic.gemma.model.expression.experiment.ExpressionExperiment;
import ubic.gemma.model.genome.Taxon;
import ubic.gemma.persistence.service.common.auditAndSecurity.AuditEventService;
import ubic.gemma.persistence.service.expression.arrayDesign.ArrayDesignService;
import ubic.gemma.persistence.service.expression.experiment.ExpressionExperimentService;
import ubic.gemma.persistence.util.Settings;

import java.io.*;
import java.util.*;

/**
 * Service to collect data on object that are new in the system.
 *
 * @author pavlidis
 */
@Component
@SuppressWarnings({ "unused", "WeakerAccess" }) // Possible external use
public class WhatsNewServiceImpl implements InitializingBean, WhatsNewService {

    private static final String WHATS_NEW_CACHE = "WhatsNew";
    private static final String WHATS_NEW_DIR = "WhatsNew";
    private static final String WHATS_NEW_FILE = "WhatsNew";
    private static final Log log = LogFactory.getLog(WhatsNewServiceImpl.class.getName());
    private final String HOME_DIR = Settings.getString("gemma.appdata.home");
    @Autowired
    private ArrayDesignService arrayDesignService = null;
    @Autowired
    private AuditEventService auditEventService;
    @Autowired
    private ExpressionExperimentService expressionExperimentService = null;
    @Autowired
    private SecurityService securityService = null;
    @Autowired
    private CacheManager cacheManager = null;
    private Cache whatsNewCache;

    @Override
    public void afterPropertiesSet() {
        try {

            if (cacheManager.cacheExists(WhatsNewServiceImpl.WHATS_NEW_CACHE)) {
                return;
            }
            whatsNewCache = new Cache(WhatsNewServiceImpl.WHATS_NEW_CACHE, 1500, false, false, 12 * 3600,
                    12 * 3600);

            cacheManager.addCache(whatsNewCache);
            whatsNewCache = cacheManager.getCache(WhatsNewServiceImpl.WHATS_NEW_CACHE);

        } catch (CacheException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void generateWeeklyReport() {
        Calendar c = Calendar.getInstance();
        Date date = c.getTime();
        date = DateUtils.addDays(date, -7);
        this.saveReport(date);
    }

    /**
     * save the report from the date specified. This will be the report that will be used by the WhatsNew box.
     */
    @Override
    public void saveReport(Date date) {
        WhatsNew wn = this.getReport(date);
        this.initDirectories();
        this.saveFile(wn);
    }

    @Override
    public WhatsNew getReport() {
        Calendar c = Calendar.getInstance();
        Date date = c.getTime();
        date = DateUtils.addWeeks(date, -1);
        return this.getReport(date);
    }

    @Override
    public WhatsNew getReport(Date date) {
        WhatsNew wn = new WhatsNew(date);

        Collection<Auditable> updatedObjects = auditEventService.getUpdatedSinceDate(date);
        wn.setUpdatedObjects(updatedObjects);
        WhatsNewServiceImpl.log.info(wn.getUpdatedObjects().size() + " updated objects since " + date);

        Collection<Auditable> newObjects = auditEventService.getNewSinceDate(date);
        wn.setNewObjects(newObjects);
        WhatsNewServiceImpl.log.info(wn.getNewObjects().size() + " new objects since " + date);

        Collection<ExpressionExperiment> updatedExpressionExperiments = this
                .getExpressionExperiments(updatedObjects);
        Collection<ExpressionExperiment> newExpressionExperiments = this.getExpressionExperiments(newObjects);
        Collection<ArrayDesign> updatedArrayDesigns = this.getArrayDesigns(updatedObjects);
        Collection<ArrayDesign> newArrayDesigns = this.getArrayDesigns(newObjects);

        // don't show things that are "new" as "updated" too (if they were updated after being loaded)
        updatedExpressionExperiments.removeAll(newExpressionExperiments);
        updatedArrayDesigns.removeAll(newArrayDesigns);

        // build total, new and updated counts by taxon to display in data summary widget on front page
        wn.setNewEEIdsPerTaxon(this.getExpressionExperimentIdsByTaxon(newExpressionExperiments));
        wn.setUpdatedEEIdsPerTaxon(this.getExpressionExperimentIdsByTaxon(updatedExpressionExperiments));

        wn.setNewBioMaterialCount(this.getBioMaterialCount(newExpressionExperiments));

        return wn;
    }

    /**
     * Retrieve the latest WhatsNew report.
     *
     * @return WhatsNew the latest WhatsNew report cache.
     */
    @Override
    public WhatsNew retrieveReport() {
        WhatsNew wn = new WhatsNew();
        try {
            File newObjects = new File(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR
                    + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_FILE + ".new");
            File updatedObjects = new File(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR
                    + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_FILE + ".updated");
            if (!newObjects.exists() && !updatedObjects.exists()) {
                return null;
            }

            // load up all new objects
            if (newObjects.exists()) {
                Collection<AuditableObject> aos = this.loadAuditableObjects(newObjects);

                for (AuditableObject object : aos) {
                    Auditable auditable = this.fetch(object);

                    if (auditable == null)
                        continue;

                    wn.addNewObjects(auditable);
                    this.updateDate(wn, object);
                }

            }

            // load up all updated objects
            if (updatedObjects.exists()) {

                Collection<AuditableObject> aos = this.loadAuditableObjects(updatedObjects);
                for (AuditableObject object : aos) {

                    /*
                     * This call takes ~ 15-20 ms but it can be called many times if there are a lot of updated
                     * experiments, meaning this loop can take >8500 ms (over tunnel for ~450 experiments).
                     *
                     * Loading objects could be avoided since we only need ids on the front end, but we would need to
                     * refactor the cache, because object-type is used to calculate counts for updated array design
                     * objects vs updated experiments
                     *
                     * This is probably not necessary because usually the number of updated or new experiments will be
                     * much lower than 450.
                     */

                    Auditable auditable = this.fetch(object);

                    if (auditable == null)
                        continue;

                    wn.addUpdatedObjects(auditable);

                    this.updateDate(wn, object);

                }
            }
            // build total, new and updated counts by taxon to display in data summary widget on front page
            wn.setNewEEIdsPerTaxon(this.getExpressionExperimentIdsByTaxon(wn.getNewExpressionExperiments()));
            wn.setUpdatedEEIdsPerTaxon(
                    this.getExpressionExperimentIdsByTaxon(wn.getUpdatedExpressionExperiments()));

        } catch (Throwable e) {
            WhatsNewServiceImpl.log.error(e, e);
            return null;
        }
        return wn;
    }

    /**
     * @param arrayDesignService the arrayDesignService to set
     */
    public void setArrayDesignService(ArrayDesignService arrayDesignService) {
        this.arrayDesignService = arrayDesignService;
    }

    public void setAuditEventService(AuditEventService auditEventService) {
        this.auditEventService = auditEventService;
    }

    /**
     * @param cacheManager the cacheManager to set
     */
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }

    /**
     * @param expressionExperimentService the expressionExperimentService to set
     */
    public void setExpressionExperimentService(ExpressionExperimentService expressionExperimentService) {
        this.expressionExperimentService = expressionExperimentService;
    }

    /**
     * @param securityService the securityService to set
     */
    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    private Auditable fetch(AuditableObject object) {

        if (object == null)
            return null;

        Auditable auditable = null;
        Element element = this.whatsNewCache.get(object);
        if (object.type.equalsIgnoreCase("ArrayDesign")) {
            if (element != null) {
                auditable = (Auditable) element.getObjectValue();
            } else {

                try {
                    auditable = arrayDesignService.load(object.getId());
                } catch (AccessDeniedException e) {
                    return null;
                }

                whatsNewCache.put(new Element(object, auditable));
            }

        } else if (object.type.equalsIgnoreCase("ExpressionExperiment")) {
            if (element != null) {
                auditable = (Auditable) element.getObjectValue();
            } else {
                // this is slower than loading them all at once but the cache saves even more time.

                try {
                    auditable = expressionExperimentService.load(object.getId());
                } catch (AccessDeniedException e) {
                    return null;
                }

                if (auditable == null) {
                    return null;
                }

                whatsNewCache.put(new Element(object, auditable));
            }
        }
        return auditable;
    }

    /**
     * @param items a collection of objects that may include array designs
     * @return the array design subset of the collection passed in
     */
    private Collection<ArrayDesign> getArrayDesigns(Collection<Auditable> items) {

        Collection<ArrayDesign> ads = new HashSet<>();
        for (Auditable auditable : items) {
            if (auditable instanceof ArrayDesign) {
                ads.add((ArrayDesign) auditable);
            }
        }
        return ads;
    }

    /**
     * @param ees a collection of expression experiments
     * @return the number of biomaterials in all the expression experiments passed in
     */
    private int getBioMaterialCount(Collection<ExpressionExperiment> ees) {

        int count = 0;
        for (ExpressionExperiment ee : ees) {
            count += this.expressionExperimentService.getBioMaterialCount(ee);
        }
        return count;
    }

    /**
     * Give breakdown by taxon. "Private" experiments are not included.
     */
    private Map<Taxon, Collection<Long>> getExpressionExperimentIdsByTaxon(Collection<ExpressionExperiment> ees) {
        /*
         * Sort taxa by name.
         */
        TreeMap<Taxon, Collection<Long>> eesPerTaxon = new TreeMap<>(new Comparator<Taxon>() {
            @Override
            public int compare(Taxon o1, Taxon o2) {
                if (o1 == null) {
                    return 1;
                } else if (o2 == null) {
                    return -1;
                } else {
                    return o1.getScientificName().compareTo(o2.getScientificName());
                }
            }
        });

        if (ees.isEmpty())
            return eesPerTaxon;

        Collection<ExpressionExperiment> publicEEs = securityService.choosePublic(ees);

        Map<ExpressionExperiment, Taxon> taxa = expressionExperimentService.getTaxa(publicEEs);

        // invert the map.
        for (ExpressionExperiment ee : taxa.keySet()) {
            Taxon t = taxa.get(ee);
            Collection<Long> ids;
            if (eesPerTaxon.containsKey(t)) {
                ids = eesPerTaxon.get(t);
            } else {
                ids = new ArrayList<>();
            }
            ids.add(ee.getId());
            eesPerTaxon.put(t, ids);
        }
        return eesPerTaxon;
    }

    /**
     * @param items a collection of objects that may include expression experiments
     * @return the expression experiment subset of the collection passed in
     */
    private Collection<ExpressionExperiment> getExpressionExperiments(Collection<Auditable> items) {

        Collection<ExpressionExperiment> ees = new HashSet<>();
        for (Auditable auditable : items) {
            if (auditable instanceof ExpressionExperiment) {
                ees.add((ExpressionExperiment) auditable);
            }
        }
        return ees;
    }

    private void initDirectories() {
        // check to see if the home directory exists. If it doesn't, create it.
        // check to see if the reports directory exists. If it doesn't, create it.
        FileTools.createDir(HOME_DIR);
        FileTools.createDir(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR);
        File f = new File(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR);
        Collection<File> files = new ArrayList<>();
        File[] fileArray = f.listFiles();
        if (fileArray != null) {
            Collections.addAll(files, fileArray);
        }
        // clear out all files
        FileTools.deleteFiles(files);
    }

    private Collection<AuditableObject> loadAuditableObjects(File newObjects)
            throws IOException, ClassNotFoundException {
        try (FileInputStream fis = new FileInputStream(newObjects);
                ObjectInputStream ois = new ObjectInputStream(fis)) {
            @SuppressWarnings("unchecked")
            Collection<AuditableObject> aos = (Collection<AuditableObject>) ois.readObject();
            return aos;
        }
    }

    private void saveFile(WhatsNew wn) {
        try {
            // remove file first
            File newOutput = new File(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR
                    + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_FILE + ".new");
            File updatedOutput = new File(HOME_DIR + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_DIR
                    + File.separatorChar + WhatsNewServiceImpl.WHATS_NEW_FILE + ".updated");
            if (newOutput.exists()) {
                if (!newOutput.delete()) {
                    WhatsNewServiceImpl.log.error("Could not delete " + newOutput.getName());
                }
            }
            if (updatedOutput.exists()) {
                if (!updatedOutput.delete()) {
                    WhatsNewServiceImpl.log.error("Could not delete " + updatedOutput.getName());
                }
            }
            Calendar c = Calendar.getInstance();
            Date date = c.getTime();

            Collection<ArrayDesign> ads = wn.getNewArrayDesigns();
            Collection<ExpressionExperiment> ees = wn.getNewExpressionExperiments();
            // save the IDs for new Auditables
            Collection<AuditableObject> newObjects = new ArrayList<>();
            this.addAllADs(date, ads, newObjects);
            this.addAllEEs(date, ees, newObjects);

            // save the ids for updated Auditables
            ads = wn.getUpdatedArrayDesigns();
            ees = wn.getUpdatedExpressionExperiments();
            // save the IDs for new Auditables
            Collection<AuditableObject> updatedObjects = new ArrayList<>();
            this.addAllADs(date, ads, updatedObjects);
            this.addAllEEs(date, ees, updatedObjects);
            try (FileOutputStream fos = new FileOutputStream(newOutput);
                    ObjectOutputStream oos = new ObjectOutputStream(fos)) {
                oos.writeObject(newObjects);
            }

            try (FileOutputStream fos = new FileOutputStream(updatedOutput);
                    ObjectOutputStream oos = new ObjectOutputStream(fos)) {
                oos.writeObject(updatedObjects);
            }

        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private void addAllADs(Date date, Collection<ArrayDesign> ads, Collection<AuditableObject> updatedObjects) {
        for (ArrayDesign ad : ads) {
            AuditableObject ao = new AuditableObject();
            ao.date = date;
            ao.type = "ArrayDesign";
            ao.id = ad.getId();
            updatedObjects.add(ao);
        }
    }

    private void addAllEEs(Date date, Collection<ExpressionExperiment> ees,
            Collection<AuditableObject> newObjects) {
        for (ExpressionExperiment ee : ees) {
            AuditableObject ao = new AuditableObject();
            ao.date = date;
            ao.type = "ExpressionExperiment";
            ao.id = ee.getId();
            newObjects.add(ao);
        }
    }

    /**
     * Sets the date to the earliest update date of any object that has been retrieved so far.
     */
    private void updateDate(WhatsNew wn, AuditableObject object) {
        if (object.getDate() != null && (wn.getDate() == null || wn.getDate().after(object.getDate()))) {
            wn.setDate(object.getDate());
        }
    }

}