package org.activityinfo.server.command.handler.sync;
/*
* #%L
* ActivityInfo Server
* %%
* Copyright (C) 2009 - 2013 UNICEF
* %%
* 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 3 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, see
* <http://www.gnu.org/licenses/gpl-3.0.html>.
* #L%
*/
import com.bedatadriven.rebar.sync.server.JpaUpdateBuilder;
import com.google.inject.Inject;
import org.activityinfo.legacy.shared.command.GetSyncRegionUpdates;
import org.activityinfo.legacy.shared.command.result.SyncRegionUpdate;
import org.activityinfo.server.database.hibernate.dao.HibernateDAOProvider;
import org.activityinfo.server.database.hibernate.dao.UserDatabaseDAO;
import org.activityinfo.server.database.hibernate.entity.*;
import org.json.JSONException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
public class SchemaUpdateBuilder implements UpdateBuilder {
private final UserDatabaseDAO userDatabaseDAO;
private final EntityManager entityManager;
private final Set<Integer> countryIds = new HashSet<Integer>();
private final List<Country> countries = new ArrayList<Country>();
private final List<AdminLevel> adminLevels = new ArrayList<AdminLevel>();
private List<UserDatabase> databases = new ArrayList<UserDatabase>();
private final Set<Integer> partnerIds = new HashSet<Integer>();
private final List<Partner> partners = new ArrayList<Partner>();
private final List<Activity> activities = new ArrayList<Activity>();
private final List<Indicator> indicators = new ArrayList<Indicator>();
private final Set<IndicatorLinkEntity> indicatorLinks = new HashSet<IndicatorLinkEntity>();
private final Set<Integer> attributeGroupIds = new HashSet<Integer>();
private final List<AttributeGroup> attributeGroups = new ArrayList<AttributeGroup>();
private final List<Attribute> attributes = new ArrayList<Attribute>();
private final Set<Integer> userIds = new HashSet<Integer>();
private final List<User> users = new ArrayList<User>();
private final List<LocationType> locationTypes = new ArrayList<LocationType>();
private List<UserPermission> userPermissions;
private static final Logger LOGGER = Logger.getLogger(SchemaUpdateBuilder.class.getName());
private final Class[] schemaClasses = new Class[]{Country.class,
AdminLevel.class,
LocationType.class,
UserDatabase.class,
Partner.class,
Activity.class,
Indicator.class,
AttributeGroup.class,
Attribute.class,
User.class,
UserPermission.class,
LockedPeriod.class,
Project.class};
private final List<LockedPeriod> allLockedPeriods = new ArrayList<LockedPeriod>();
private final List<Project> projects = new ArrayList<Project>();
@Inject
public SchemaUpdateBuilder(EntityManagerFactory entityManagerFactory) {
// create a new, unfiltered entity manager so we can see deleted records
this.entityManager = entityManagerFactory.createEntityManager();
this.userDatabaseDAO = HibernateDAOProvider.makeImplementation(UserDatabaseDAO.class,
UserDatabase.class,
entityManager);
}
@SuppressWarnings("unchecked") @Override
public SyncRegionUpdate build(User user, GetSyncRegionUpdates request) throws JSONException {
try {
// get the permissions before we apply the filter
// otherwise they will be excluded
userPermissions = entityManager.createQuery("select p from UserPermission p where p.user.id = ?1")
.setParameter(1, user.getId())
.getResultList();
DomainFilters.applyUserFilter(user, entityManager);
databases = userDatabaseDAO.queryAllUserDatabasesAlphabetically();
long localVersion = request.getLocalVersion() == null ? 0 : Long.parseLong(request.getLocalVersion());
long serverVersion = getCurrentSchemaVersion();
LOGGER.info("Schema versions: local = " + localVersion + ", server = " + serverVersion);
SyncRegionUpdate update = new SyncRegionUpdate();
update.setVersion(Long.toString(serverVersion));
update.setComplete(true);
if (localVersion < serverVersion) {
makeEntityLists();
update.setSql(buildSql());
}
return update;
} finally {
entityManager.close();
}
}
private String buildSql() throws JSONException {
JpaUpdateBuilder builder = new JpaUpdateBuilder();
for (Class schemaClass : schemaClasses) {
builder.createTableIfNotExists(schemaClass);
// Special case: we never delete partners, only add them. This way we always have a label for Partners
// See : LocalSiteCreateTest.siteRemovePartnerConflict
if (!schemaClass.equals(Partner.class)) {
builder.deleteAll(schemaClass);
}
}
builder.insert(Country.class, countries);
builder.insert(AdminLevel.class, adminLevels);
builder.insert(UserDatabase.class, databases);
builder.insert(" or replace ", Partner.class, partners);
builder.insert(Activity.class, activities);
builder.insert(Indicator.class, indicators);
builder.insert(AttributeGroup.class, attributeGroups);
builder.insert(Attribute.class, attributes);
builder.insert(LocationType.class, locationTypes);
builder.insert(User.class, users);
builder.insert(UserPermission.class, userPermissions);
builder.insert(Project.class, projects);
builder.insert(LockedPeriod.class, allLockedPeriods);
// TODO: this needs to be actually synchronized
builder.executeStatement(
"CREATE TABLE IF NOT EXISTS target (targetId int, name text, date1 text, date2 text, projectId int, " +
"partnerId int, adminEntityId int, databaseId int)");
builder.executeStatement("CREATE TABLE IF NOT EXISTS targetvalue (targetId int, IndicatorId int, value real)");
builder.createTableIfNotExists(Location.class);
builder.executeStatement(
"create table if not exists LocationAdminLink (LocationId integer, AdminEntityId integer)");
createAndSyncIndicatorlinks(builder);
createAndSyncPartnerInDatabase(builder);
createAndSyncAttributeGroupInActivity(builder);
return builder.asJson();
}
private void createAndSyncIndicatorlinks(JpaUpdateBuilder builder) throws JSONException {
builder.executeStatement(
"create table if not exists IndicatorLink (SourceIndicatorId int, DestinationIndicatorId int) ");
builder.executeStatement("delete from IndicatorLink");
if (!indicatorLinks.isEmpty()) {
builder.beginPreparedStatement(
"insert into IndicatorLink (SourceIndicatorId, DestinationIndicatorId) values (?, ?) ");
for (IndicatorLinkEntity il : indicatorLinks) {
builder.addExecution(il.getId().getSourceIndicatorId(), il.getId().getDestinationIndicatorId());
}
builder.finishPreparedStatement();
}
}
private void createAndSyncPartnerInDatabase(JpaUpdateBuilder builder) throws JSONException {
builder.executeStatement("create table if not exists PartnerInDatabase (DatabaseId integer, PartnerId int)");
builder.executeStatement("delete from PartnerInDatabase");
if (anyPartners()) {
builder.beginPreparedStatement("insert into PartnerInDatabase (DatabaseId, PartnerId) values (?, ?) ");
for (UserDatabase db : databases) {
for (Partner partner : db.getPartners()) {
builder.addExecution(db.getId(), partner.getId());
}
}
builder.finishPreparedStatement();
}
}
private void createAndSyncAttributeGroupInActivity(JpaUpdateBuilder builder) throws JSONException {
builder.executeStatement(
"create table if not exists AttributeGroupInActivity (ActivityId integer, AttributeGroupId integer)");
builder.executeStatement("delete from AttributeGroupInActivity");
if (anyAttributes()) {
builder.beginPreparedStatement(
"insert into AttributeGroupInActivity (ActivityId, AttributeGroupId) values (?,?)");
for (UserDatabase db : databases) {
for (Activity activity : db.getActivities()) {
for (AttributeGroup group : activity.getAttributeGroups()) {
builder.addExecution(activity.getId(), group.getId());
}
}
}
builder.finishPreparedStatement();
}
}
private boolean anyPartners() {
for (UserDatabase db : databases) {
if (!db.getPartners().isEmpty()) {
return true;
}
}
return false;
}
private boolean anyAttributes() {
for (UserDatabase db : databases) {
for (Activity activity : db.getActivities()) {
if (!activity.getAttributeGroups().isEmpty()) {
return true;
}
}
}
return false;
}
private void makeEntityLists() {
for (UserDatabase database : databases) {
if (!userIds.contains(database.getOwner().getId())) {
User u = database.getOwner();
// don't send hashed password to client
// EEK i think hibernate will persist this to the database
// automatically if we change it here!!
// u.setHashedPassword("");
users.add(u);
userIds.add(u.getId());
}
if (!countryIds.contains(database.getCountry().getId())) {
countries.add(database.getCountry());
adminLevels.addAll(database.getCountry().getAdminLevels());
countryIds.add(database.getCountry().getId());
for (org.activityinfo.server.database.hibernate.entity.LocationType l : database.getCountry()
.getLocationTypes()) {
locationTypes.add(l);
}
}
for (Partner partner : database.getPartners()) {
if (!partnerIds.contains(partner.getId())) {
partners.add(partner);
partnerIds.add(partner.getId());
}
}
projects.addAll(new ArrayList<Project>(database.getProjects()));
allLockedPeriods.addAll(database.getLockedPeriods());
for (Project project : database.getProjects()) {
allLockedPeriods.addAll(project.getLockedPeriods());
}
for (Activity activity : database.getActivities()) {
allLockedPeriods.addAll(activity.getLockedPeriods());
activities.add(activity);
for (Indicator indicator : activity.getIndicators()) {
indicators.add(indicator);
List<IndicatorLinkEntity> links = findIndicatorLinks(indicator);
if (links != null && !links.isEmpty()) {
indicatorLinks.addAll(links);
}
}
for (AttributeGroup g : activity.getAttributeGroups()) {
if (!attributeGroupIds.contains(g.getId())) {
attributeGroups.add(g);
attributeGroupIds.add(g.getId());
for (Attribute a : g.getAttributes()) {
attributes.add(a);
}
}
}
}
}
}
@SuppressWarnings("unchecked")
private List<IndicatorLinkEntity> findIndicatorLinks(Indicator indicator) {
return entityManager.createQuery(
"select il from IndicatorLinkEntity il where il.id.sourceIndicatorId = ?1 or il.id" +
".destinationIndicatorId = ?2")
.setParameter(1, indicator.getId())
.setParameter(2, indicator.getId())
.getResultList();
}
public long getCurrentSchemaVersion() {
long currentVersion = 1;
for (UserDatabase db : databases) {
if (db.getVersion() > currentVersion) {
currentVersion = db.getVersion();
}
}
for (UserPermission perm : userPermissions) {
if (perm.getVersion() > currentVersion) {
currentVersion = perm.getVersion();
}
}
return currentVersion;
}
}