package org.activityinfo.server.digest.geo; import com.google.common.annotations.VisibleForTesting; import com.google.inject.Inject; import com.google.inject.Provider; import org.activityinfo.legacy.shared.command.DimensionType; import org.activityinfo.legacy.shared.command.Filter; import org.activityinfo.legacy.shared.command.GenerateElement; import org.activityinfo.legacy.shared.command.GetSchema; import org.activityinfo.legacy.shared.reports.content.MapContent; import org.activityinfo.legacy.shared.reports.model.MapReportElement; import org.activityinfo.legacy.shared.reports.model.clustering.AutomaticClustering; import org.activityinfo.legacy.shared.reports.model.labeling.ArabicNumberSequence; import org.activityinfo.legacy.shared.reports.model.layers.BubbleMapLayer; import org.activityinfo.server.command.DispatcherSync; import org.activityinfo.server.database.hibernate.entity.User; import org.activityinfo.server.database.hibernate.entity.UserDatabase; import org.activityinfo.server.digest.DigestModelBuilder; import org.activityinfo.server.digest.UserDigest; import org.activityinfo.server.digest.geo.GeoDigestModel.DatabaseModel; import org.activityinfo.server.report.output.StorageProvider; import org.activityinfo.server.report.output.TempStorage; import org.activityinfo.server.report.renderer.image.ImageMapRenderer; import org.activityinfo.server.util.date.DateFormatter; import javax.persistence.EntityManager; import javax.persistence.Query; import java.io.IOException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Logger; public class GeoDigestModelBuilder implements DigestModelBuilder { private static final String BUBBLE_COLOR = "67a639"; private static final int BUBBLE_SIZE = 20; private static final Logger LOGGER = Logger.getLogger(GeoDigestModelBuilder.class.getName()); private final Provider<EntityManager> entityManager; private final DispatcherSync dispatcher; private final ImageMapRenderer imageMapRenderer; private final StorageProvider storageProvider; @Inject public GeoDigestModelBuilder(Provider<EntityManager> entityManager, DispatcherSync dispatcher, ImageMapRenderer imageMapRenderer, StorageProvider storageProvider) { this.entityManager = entityManager; this.dispatcher = dispatcher; this.imageMapRenderer = imageMapRenderer; this.storageProvider = storageProvider; } @Override public GeoDigestModel createModel(UserDigest userDigest) throws IOException { GeoDigestModel model = new GeoDigestModel(userDigest); List<UserDatabase> databases = findDatabases(userDigest.getUser()); LOGGER.finest("found " + databases.size() + " database(s) for user " + userDigest.getUser().getId()); if (!databases.isEmpty()) { model.setSchemaDTO(dispatcher.execute(new GetSchema())); for (UserDatabase database : databases) { createDatabaseModel(model, database); } } return model; } private void createDatabaseModel(GeoDigestModel model, UserDatabase database) throws IOException { DatabaseModel databaseModel = new DatabaseModel(model, database); List<Integer> siteIds = findSiteIds(database, model.getUserDigest().getFrom()); LOGGER.finest("rendering geo digest for user " + model.getUserDigest().getUser().getId() + " and database " + database.getId() + " - found " + siteIds.size() + " site(s) that were edited since " + DateFormatter.formatDateTime(model.getUserDigest().getFrom())); if (!siteIds.isEmpty()) { MapReportElement reportModel = new MapReportElement(); reportModel.setMaximumZoomLevel(9); BubbleMapLayer layer = createLayer(siteIds); reportModel.setLayers(layer); MapContent content = dispatcher.execute(new GenerateElement<MapContent>(reportModel)); databaseModel.setContent(content); if (!content.getMarkers().isEmpty()) { reportModel.setContent(content); TempStorage storage = storageProvider.allocateTemporaryFile("image/png", "map.png"); imageMapRenderer.render(reportModel, storage.getOutputStream()); storage.getOutputStream().close(); databaseModel.setUrl(storage.getUrl()); } } } private BubbleMapLayer createLayer(List<Integer> siteIds) { Filter filter = new Filter(); filter.addRestriction(DimensionType.Site, siteIds); BubbleMapLayer layer = new BubbleMapLayer(filter); layer.setLabelSequence(new ArabicNumberSequence()); layer.setClustering(new AutomaticClustering()); layer.setMinRadius(BUBBLE_SIZE); layer.setMaxRadius(BUBBLE_SIZE); layer.setBubbleColor(BUBBLE_COLOR); return layer; } /** * @return all UserDatabases for the contextual user where the user is the database owner, or where the database has * a UserPermission for the specified user with allowView set to true. If the user happens to have his * emailnotification preference set to false, an empty list is returned. */ @VisibleForTesting @SuppressWarnings("unchecked") List<UserDatabase> findDatabases(User user) { // sanity check if (!user.isEmailNotification()) { return new ArrayList<UserDatabase>(); } Query query = entityManager.get() .createQuery("select distinct d from UserDatabase d left join d.userPermissions p " + "where (d.owner = :user or (p.user = :user and p.allowView = true)) " + "and d.dateDeleted is null " + "order by d.name"); query.setParameter("user", user); return query.getResultList(); } /** * @param database the database the sites should be linked to (via an activity) * @param from the timestamp (millis) to start searching from for edited sites * @return the siteIds linked to the specified database that were edited since the specified timestamp */ @VisibleForTesting @SuppressWarnings("unchecked") List<Integer> findSiteIds(UserDatabase database, long from) { Query query = entityManager.get().createQuery("select distinct s.id from Site s " + "join s.siteHistories h " + "where s.activity.database = :database " + "and h.timeCreated >= :from"); query.setParameter("database", database); query.setParameter("from", from); return query.getResultList(); } }