/** * The contents of this file are subject to the OpenMRS Public License * Version 1.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://license.openmrs.org * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the * License for the specific language governing rights and limitations * under the License. * * Copyright (C) OpenMRS, LLC. All Rights Reserved. */ package org.openmrs.module.sync.api.impl; import java.io.File; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.openmrs.GlobalProperty; import org.openmrs.OpenmrsObject; import org.openmrs.Patient; import org.openmrs.Privilege; import org.openmrs.Role; import org.openmrs.api.APIException; import org.openmrs.api.AdministrationService; import org.openmrs.api.context.Context; import org.openmrs.api.db.DAOException; import org.openmrs.api.db.SerializedObjectDAO; import org.openmrs.module.sync.SyncClass; import org.openmrs.module.sync.SyncConstants; import org.openmrs.module.sync.SyncSubclassStub; import org.openmrs.module.sync.SyncRecord; import org.openmrs.module.sync.SyncRecordState; import org.openmrs.module.sync.SyncServerClass; import org.openmrs.module.sync.SyncStatistic; import org.openmrs.module.sync.SyncUtil; import org.openmrs.module.sync.api.SyncService; import org.openmrs.module.sync.api.db.SyncDAO; import org.openmrs.module.sync.api.db.hibernate.HibernateSyncInterceptor; import org.openmrs.module.sync.ingest.SyncImportRecord; import org.openmrs.module.sync.server.RemoteServer; import org.openmrs.module.sync.server.RemoteServerType; import org.openmrs.module.sync.server.SyncServerRecord; import org.openmrs.util.OpenmrsConstants; /** * Default implementation of the {@link SyncService} */ public class SyncServiceImpl implements SyncService { private SyncDAO dao; private List<Class<OpenmrsObject>> allOpenmrsObjects; private final Log log = LogFactory.getLog(getClass()); private static Set<String> serverClassesCollection; private SerializedObjectDAO serializedObjectDao; public void setSerializedObjectDao(SerializedObjectDAO serializedObjectDao) { this.serializedObjectDao = serializedObjectDao; } public SerializedObjectDAO getSerializedObjectDao() { return serializedObjectDao; } private SyncDAO getSynchronizationDAO() { return dao; } public void setSyncDAO(SyncDAO dao) { this.dao = dao; } public void setAllObjectsObjects(List<Class<OpenmrsObject>> openmrsObjects) { log.fatal("Got openmrs objects: " + openmrsObjects); this.allOpenmrsObjects = openmrsObjects; } public List<Class<OpenmrsObject>> getAllOpenmrsObjects() { return this.allOpenmrsObjects; } /** * @see org.openmrs.api.SyncService#createSyncRecord(org.openmrs.module.sync.SyncRecord) */ public void createSyncRecord(SyncRecord record) throws APIException { this.createSyncRecord(record, record.getOriginalUuid()); } public void createSyncRecord(SyncRecord record, String originalUuidPassed) throws APIException { if (record != null) { // here is a hack to get around the fact that hibernate decides to commit transactions when it feels like it // otherwise, we could run this in the ingest methods RemoteServer origin = null; int idx = originalUuidPassed.indexOf("|"); if (idx > -1) { log.debug("originalPassed is " + originalUuidPassed); String originalUuid = originalUuidPassed.substring(0, idx); String serverUuid = originalUuidPassed.substring(idx + 1); log.debug("serverUuid is " + serverUuid + ", and originalUuid is " + originalUuid); record.setOriginalUuid(originalUuid); origin = Context.getService(SyncService.class).getRemoteServer(serverUuid); if (origin != null) { if (origin.getServerType().equals(RemoteServerType.PARENT)) { record.setState(SyncRecordState.COMMITTED); } } else { log.warn("Could not get remote server by uuid: " + serverUuid); } } // before creation, we need to make sure that we create matching entries for each server (server-record relationship) Set<SyncServerRecord> serverRecords = record.getServerRecords(); if (serverRecords == null) { log.debug("IN createSyncRecord(), SERVERRECORDS ARE NULL, SO SETTING DEFAULTS"); serverRecords = new HashSet<SyncServerRecord>(); List<RemoteServer> servers = this.getRemoteServers(); if (servers != null) { for (RemoteServer server : servers) { // we only need to create extra server-records for servers that are NOT the parent - the parent state is kept in the actual sync record if (!server.getServerType().equals(RemoteServerType.PARENT)) { SyncServerRecord serverRecord = new SyncServerRecord(server, record); // can't compare with .equals because of so many variables in it. SYNC-227 if (server != null && origin != null && server.getServerId().equals(origin.getServerId())) { log.info("this record came from server " + origin.getNickname() + ", so we will set its status to commmitted"); serverRecord.setState(SyncRecordState.COMMITTED); } serverRecords.add(serverRecord); } } } record.setServerRecords(serverRecords); } getSynchronizationDAO().createSyncRecord(record); } } /** * @see org.openmrs.api.SyncService#createSyncImportRecord(org.openmrs.module.sync.SyncImportRecord) */ public void createSyncImportRecord(SyncImportRecord record) throws APIException { getSynchronizationDAO().createSyncImportRecord(record); } /** * @see org.openmrs.api.SyncService#getNextSyncRecord() */ public SyncRecord getFirstSyncRecordInQueue() throws APIException { return getSynchronizationDAO().getFirstSyncRecordInQueue(); } /** * @see org.openmrs.api.SyncService#getSyncRecords(java.lang.String) */ public List<SyncRecord> getSyncRecords(String query) throws APIException { return getSynchronizationDAO().getSyncRecords(query); } /** * @see org.openmrs.api.SyncService#getSyncRecord(java.lang.Integer) */ public SyncRecord getSyncRecord(Integer id) throws APIException { return getSynchronizationDAO().getSyncRecord(id); } /** * @see org.openmrs.api.SyncService#getSyncRecord(java.lang.String) */ public SyncRecord getSyncRecord(String uuid) throws APIException { return getSynchronizationDAO().getSyncRecord(uuid); } public SyncRecord getSyncRecordByOriginalUuid(String originalUuid) throws APIException { return getSynchronizationDAO().getSyncRecordByOriginalUuid(originalUuid); } /** * @see org.openmrs.api.SyncService#getLatestRecord() */ public SyncRecord getLatestRecord() throws APIException { return getSynchronizationDAO().getLatestRecord(); } /** * @see org.openmrs.api.SyncService#getEarliestRecord(Date) */ public SyncRecord getEarliestRecord(Date afterDate) throws APIException { return getSynchronizationDAO().getEarliestRecord(afterDate); } /** * @see SyncService#getNextRecord(SyncRecord) */ public SyncRecord getNextRecord(SyncRecord record) { return getSynchronizationDAO().getNextRecord(record); } /** * @see SyncService#getPreviousRecord(SyncRecord) */ public SyncRecord getPreviousRecord(SyncRecord record) { return getSynchronizationDAO().getPreviousRecord(record); } /** * @see org.openmrs.api.SyncService#getSyncRecord(java.lang.String) */ public SyncImportRecord getSyncImportRecord(String uuid) throws APIException { return getSynchronizationDAO().getSyncImportRecord(uuid); } /** * @see org.openmrs.module.sync.api.SyncService#getOlderSyncRecordInState(org.openmrs.module.sync.SyncRecord, * java.util.EnumSet) */ public SyncRecord getOlderSyncRecordInState(SyncRecord syncRecord, EnumSet<SyncRecordState> states) throws APIException { return getSynchronizationDAO().getOlderSyncRecordInState(syncRecord, states); } /** * @see org.openmrs.api.SyncService#getSyncImportRecords(org.openmrs.module.sync.engine.SyncRecordState) */ public List<SyncImportRecord> getSyncImportRecords(SyncRecordState... state) throws APIException { return getSynchronizationDAO().getSyncImportRecords(state); } /** * @see org.openmrs.api.SyncService#getSyncRecords() */ public List<SyncRecord> getSyncRecords() throws APIException { return getSynchronizationDAO().getSyncRecords(); } /** * @see org.openmrs.api.SyncService#getSyncRecords(org.openmrs.module.sync.engine.SyncRecordState) */ public List<SyncRecord> getSyncRecords(SyncRecordState state) throws APIException { return getSynchronizationDAO().getSyncRecords(state); } /** * @see org.openmrs.api.SyncService#getSyncRecords(org.openmrs.module.sync.engine.SyncRecordState, Integer maxSyncRecords, Integer) */ public List<SyncRecord> getSyncRecords(SyncRecordState[] states, Integer maxSyncRecords, Integer firstRecordId) throws APIException { return this.getSyncRecords(states, false, maxSyncRecords, firstRecordId); } /** * @see org.openmrs.module.sync.api.SyncService#getSyncRecords(org.openmrs.module.sync.SyncRecordState[], * org.openmrs.module.sync.server.RemoteServer, java.lang.Integer, java.lang.Integer) */ public List<SyncRecord> getSyncRecords(SyncRecordState[] states, RemoteServer server, Integer maxSyncRecords, Integer firstRecordId) throws APIException { List<SyncRecord> temp = null; List<SyncRecord> ret = null; if (server != null) { if (server.getServerType().equals(RemoteServerType.PARENT)) { ret = this.getSyncRecords(states, maxSyncRecords, firstRecordId); } else { ret = getSynchronizationDAO().getSyncRecords(states, false, maxSyncRecords, server, firstRecordId); } } // filter out classes that are not supposed to be sent to the specified server // and update their status if (ret != null) { temp = new ArrayList<SyncRecord>(); for (SyncRecord record : ret) { if (server.shouldBeSentSyncRecord(record)) { record.setForServer(server); temp.add(record); } else { log.warn("Omitting record with " + record.getContainedClasses() + " for server: " + server.getNickname() + " with server type: " + server.getServerType()); if (server.getServerType().equals(RemoteServerType.PARENT)) { record.setState(SyncRecordState.NOT_SUPPOSED_TO_SYNC); } else { // if not the parent, we have to update the record for this specific server Set<SyncServerRecord> records = record.getServerRecords(); for (SyncServerRecord serverRecord : records) { if (serverRecord.getSyncServer().equals(server)) { serverRecord.setState(SyncRecordState.NOT_SUPPOSED_TO_SYNC); } } record.setServerRecords(records); } this.updateSyncRecord(record); } } ret = temp; } return ret; } /** * @see org.openmrs.module.sync.api.SyncService#getSyncRecords(org.openmrs.module.sync.SyncRecordState[], * boolean, java.lang.Integer) */ public List<SyncRecord> getSyncRecords(SyncRecordState[] states, boolean inverse, Integer maxSyncRecords, Integer firstRecordId) throws APIException { return getSynchronizationDAO().getSyncRecords(states, inverse, maxSyncRecords, null, firstRecordId); } /** * @see org.openmrs.api.SyncService#updateSyncRecord(org.openmrs.module.sync.SyncRecord) */ public void updateSyncRecord(SyncRecord record) throws APIException { getSynchronizationDAO().updateSyncRecord(record); } /** * @see org.openmrs.api.SyncService#deleteSyncRecord(org.openmrs.module.sync.SyncRecord) */ public void deleteSyncRecord(SyncRecord record) throws APIException { getSynchronizationDAO().deleteSyncRecord(record); } /** * @see org.openmrs.api.SyncService#updateSyncImportRecord(org.openmrs.module.sync.SyncImportRecord) */ public void updateSyncImportRecord(SyncImportRecord record) throws APIException { getSynchronizationDAO().updateSyncImportRecord(record); } /** * @see org.openmrs.api.SyncService#deleteSyncRecord(org.openmrs.module.sync.SyncRecord) */ public void deleteSyncImportRecord(SyncImportRecord record) throws APIException { getSynchronizationDAO().deleteSyncImportRecord(record); } /** * @see org.openmrs.api.SyncService#deleteSyncImportRecordsByServer(java.lang.Integer) */ public void deleteSyncImportRecordsByServer(Integer serverId) throws APIException { getSynchronizationDAO().deleteSyncImportRecordsByServer(serverId); } /** * @see org.openmrs.api.SyncService#getSyncRecordsSince(java.util.Date) */ public List<SyncRecord> getSyncRecordsSince(Date from) throws APIException { return getSynchronizationDAO().getSyncRecords(from, null, null, null, true); } /** * @see org.openmrs.api.SyncService#getSyncRecordsBetween(java.util.Date, java.util.Date) */ public List<SyncRecord> getSyncRecordsBetween(Date from, Date to) throws APIException { return getSynchronizationDAO().getSyncRecords(from, to, null, null, true); } /** * @see org.openmrs.module.sync.api.SyncService#getSyncRecords(java.lang.Integer, * java.lang.Integer) */ public List<SyncRecord> getSyncRecords(Integer firstRecordId, Integer numberToReturn) throws APIException { return getSynchronizationDAO().getSyncRecords(null, null, firstRecordId, numberToReturn, false); } /** * @see org.openmrs.module.sync.api.SyncService#deleteSyncRecords(org.openmrs.module.sync.SyncRecordState[], * java.util.Date) */ public Integer deleteSyncRecords(SyncRecordState[] states, Date to) throws APIException { // if no states passed in, then decide based on current server setup if (states == null || states.length == 0) { if (getParentServer() == null) { // if server is not a leaf node (only a parent) // state does not matter (but will always be NEW) states = new SyncRecordState[] { SyncRecordState.NOT_SUPPOSED_TO_SYNC, SyncRecordState.NEW }; } else { // if a server is a leaf node, then only delete states that // have been successfully sent to the parent already states = new SyncRecordState[] { SyncRecordState.NOT_SUPPOSED_TO_SYNC, SyncRecordState.COMMITTED }; } } return getSynchronizationDAO().deleteSyncRecords(states, to); } /** * @see org.openmrs.api.SyncService#getGlobalProperty(java.lang.String) */ public String getGlobalProperty(String propertyName) throws APIException { return getSynchronizationDAO().getGlobalProperty(propertyName); } /** * @see org.openmrs.api.SyncService#setGlobalProperty(String propertyName, String propertyValue) */ public void setGlobalProperty(String propertyName, String propertyValue) throws APIException { getSynchronizationDAO().setGlobalProperty(propertyName, propertyValue); } /** * @see org.openmrs.api.SyncService#saveRemoteServer(org.openmrs.module.sync.engine.RemoteServer) */ public RemoteServer saveRemoteServer(RemoteServer server) throws APIException { if (server != null) { Set<SyncServerClass> serverClasses = server.getServerClasses(); if (serverClasses == null) { log.warn("IN CREATEREMOTESERVER(), SERVERCLASSES ARE NULL, SO SETTING DEFAULTS"); serverClasses = new HashSet<SyncServerClass>(); List<SyncClass> classes = this.getSyncClasses(); if (classes != null) { for (SyncClass syncClass : classes) { SyncServerClass serverClass = new SyncServerClass(server, syncClass); serverClasses.add(serverClass); } } server.setServerClasses(serverClasses); } server = getSynchronizationDAO().saveRemoteServer(server); refreshServerClassesCollection(); return server; } return null; } /** * @see org.openmrs.api.SyncService#deleteRemoteServer(org.openmrs.module.sync.engine.RemoteServer) */ public void deleteRemoteServer(RemoteServer server) throws APIException { getSynchronizationDAO().deleteRemoteServer(server); } public RemoteServer getRemoteServer(Integer serverId) throws APIException { return getSynchronizationDAO().getRemoteServer(serverId); } public RemoteServer getRemoteServer(String uuid) throws APIException { return getSynchronizationDAO().getRemoteServer(uuid); } public RemoteServer getRemoteServerByUsername(String username) throws APIException { return getSynchronizationDAO().getRemoteServerByUsername(username); } public List<RemoteServer> getRemoteServers() throws APIException { return getSynchronizationDAO().getRemoteServers(); } public RemoteServer getParentServer() throws APIException { return getSynchronizationDAO().getParentServer(); } /** * Returns globally unique identifier of the local server. This value uniquely indentifies * server in all data exchanges with other servers. */ public String getServerUuid() throws APIException { return Context.getAdministrationService().getGlobalProperty(SyncConstants.PROPERTY_SERVER_UUID); } /** * Updates globally unique identifier of the local server. */ public void saveServerUuid(String uuid) throws APIException { Context.getService(SyncService.class).setGlobalProperty(SyncConstants.PROPERTY_SERVER_UUID, uuid); } /** * Returns server friendly name for sync purposes. It should be assigned by convention to be * unique in the synchronization network of servers. This value can be used to scope values that * are otherwise unique only locally (such as integer primary keys). */ public String getServerName() throws APIException { return Context.getAdministrationService().getGlobalProperty(SyncConstants.PROPERTY_SERVER_NAME); } /** * Updates/saves the user friendly server name for sync purposes. */ public void saveServerName(String name) throws APIException { Context.getService(SyncService.class).setGlobalProperty(SyncConstants.PROPERTY_SERVER_NAME, name); } public String getAdminEmail() { return Context.getService(SyncService.class).getGlobalProperty(SyncConstants.PROPERTY_SYNC_ADMIN_EMAIL); } public void saveAdminEmail(String email) { Context.getService(SyncService.class).setGlobalProperty(SyncConstants.PROPERTY_SYNC_ADMIN_EMAIL, email); } /** * @see org.openmrs.api.SyncService#saveSyncClass(org.openmrs.module.sync.SyncClass) */ public void saveSyncClass(SyncClass syncClass) throws APIException { getSynchronizationDAO().saveSyncClass(syncClass); refreshServerClassesCollection(); } /** * @see org.openmrs.api.SyncService#deleteSyncClass(org.openmrs.module.sync.SyncClass) */ public void deleteSyncClass(SyncClass syncClass) throws APIException { getSynchronizationDAO().deleteSyncClass(syncClass); refreshServerClassesCollection(); } public SyncClass getSyncClass(Integer syncClassId) throws APIException { return getSynchronizationDAO().getSyncClass(syncClassId); } public List<SyncClass> getSyncClasses() throws APIException { return getSynchronizationDAO().getSyncClasses(); } public SyncClass getSyncClassByName(String className) throws APIException { return getSynchronizationDAO().getSyncClassByName(className); } /** * @see org.openmrs.api.SyncService#deleteOpenmrsObject(org.openmrs.synchronization.OpenmrsObject) */ public void deleteOpenmrsObject(OpenmrsObject o) throws APIException { getSynchronizationDAO().deleteOpenmrsObject(o); } /** * Changes flush sematics, delegating directly to the corresponsing DAO method. * * @see org.openmrs.api.SyncService#setFlushModeManual() * @see org.openmrs.api.db.hibernate.HibernateSyncDAO#setFlushModeManual() */ public void setFlushModeManual() throws APIException { getSynchronizationDAO().setFlushModeManual(); } /** * Changes flush sematics, delegating directly to the corresponsing DAO method. * * @see org.openmrs.api.SyncService#setFlushModeAutomatic() * @see org.openmrs.api.db.hibernate.HibernateSyncDAO#setFlushModeAutomatic() */ public void setFlushModeAutomatic() throws APIException { getSynchronizationDAO().setFlushModeAutomatic(); } /** * Performs peristence layer flush, delegating directly to the corresponsing DAO method. * * @see org.openmrs.api.SyncService#flushSession() * @see org.openmrs.api.db.hibernate.HibernateSyncDAO#flushSession() */ public void flushSession() throws APIException { getSynchronizationDAO().flushSession(); } /** * Processes save/update to instance of OpenmrsObject by persisting it into local persistance * store. * * @param object instance of OpenmrsObject to be processed. * @return * @throws APIException */ //@Authorized({"Manage Synchronization Records"}) public void saveOrUpdate(OpenmrsObject object) throws APIException { getSynchronizationDAO().saveOrUpdate(object); } /** * Gets stats for the server: 1. Sync Records count by server by state 2. If any sync records * are in 'pending'/failed state and it has been > 24hrs, add statistic for it 3. count of * 'pending' sync records (i.e. the ones that are not in complete or error state * * @param fromDate start date * @param toDate end date * @return * @throws DAOException */ public Map<RemoteServer, LinkedHashSet<SyncStatistic>> getSyncStatistics(Date fromDate, Date toDate) throws DAOException { Map<RemoteServer, LinkedHashSet<SyncStatistic>> stats = getSynchronizationDAO().getSyncStatistics(fromDate, toDate); //check out the info for the servers: if any records are pending and are older than 1 day, add flag to stats for (Map.Entry<RemoteServer, LinkedHashSet<SyncStatistic>> entry1 : stats.entrySet()) { Long pendingCount = 0L; for (SyncStatistic syncStat : entry1.getValue()) { if (syncStat.getType() == SyncStatistic.Type.SYNC_RECORD_COUNT_BY_STATE) { if (syncStat.getName() != SyncRecordState.ALREADY_COMMITTED.toString() && syncStat.getName() != SyncRecordState.COMMITTED.toString() && syncStat.getName() != SyncRecordState.NOT_SUPPOSED_TO_SYNC.toString()) { pendingCount = pendingCount + ((syncStat.getValue() == null) ? 0L : Long.parseLong(syncStat.getValue().toString())); } } } //add pending count entry1.getValue().add( new SyncStatistic(SyncStatistic.Type.SYNC_RECORDS_PENDING_COUNT, SyncStatistic.Type.SYNC_RECORDS_PENDING_COUNT.toString(), pendingCount)); //careful, manipulating live collection //if some 'stale' records found see if it has been 24hrs since last sync RemoteServer server = entry1.getKey(); if (server.getLastSync() != null) { Calendar lastSync = Calendar.getInstance(); lastSync.setTime(server.getLastSync()); Calendar threeDayThreshold = Calendar.getInstance(); threeDayThreshold.add(Calendar.HOUR, -72); // check if last sync is more than 3 days ago Calendar oneDayThreshold = Calendar.getInstance(); oneDayThreshold.add(Calendar.HOUR, -24); // check if last sync is more than 3 days ago if (lastSync.before(threeDayThreshold)) { entry1.getValue().add( new SyncStatistic(SyncStatistic.Type.LAST_SYNC_REALLY_LONG_TIME_AGO, SyncStatistic.Type.LAST_SYNC_REALLY_LONG_TIME_AGO.toString(), pendingCount)); //careful, manipulating live collection } else if (lastSync.before(oneDayThreshold)) { entry1.getValue().add( new SyncStatistic(SyncStatistic.Type.LAST_SYNC_TIME_SOMEWHAT_TROUBLESOME, SyncStatistic.Type.LAST_SYNC_TIME_SOMEWHAT_TROUBLESOME.toString(), pendingCount)); //careful, manipulating live collection } } } return stats; } public <T extends OpenmrsObject> T getOpenmrsObjectByUuid(Class<T> clazz, String uuid) { T ret = dao.getOpenmrsObjectByUuid(clazz, uuid); if (ret == null) { try { ret = serializedObjectDao.getObjectByUuid(clazz, uuid); //sync-205 } catch (Exception ex) { //pass -- not sure if catch/try is necessary } } return ret; } /** * @see org.openmrs.api.SynchronizationService#exportChildDB(java.lang.String, * java.io.OutputStream) */ public void exportChildDB(String guidForChild, OutputStream os) throws APIException { getSynchronizationDAO().exportChildDB(guidForChild, os); } /** * @see org.openmrs.api.SynchronizationService#importParentDB(java.io.InputStream) */ public void importParentDB(InputStream in) throws APIException { getSynchronizationDAO().importParentDB(in); //Delete any data kept into sync journal after clone of the parent DB for (SyncRecord record : this.getSynchronizationDAO().getSyncRecords()) { this.getSynchronizationDAO().deleteSyncRecord(record); } } /** * @see org.openmrs.module.sync.api.SyncService#generateDataFile() */ public File generateDataFile() throws APIException { File dir = SyncUtil.getSyncApplicationDir(); String fileName = SyncConstants.CLONE_IMPORT_FILE_NAME + SyncConstants.SYNC_FILENAME_MASK.format(new Date()) + ".sql"; String[] ignoreTables = { "hl7_in_archive", "hl7_in_queue", "hl7_in_error", "formentry_archive", "formentry_queue", "formentry_error", "sync_class", "sync_import", "sync_record", "sync_server", "sync_server_class", "sync_server_record" }; File outputFile = new File(dir, fileName); getSynchronizationDAO().generateDataFile(outputFile, ignoreTables); return outputFile; } /** * @see org.openmrs.module.sync.api.SyncService#execGeneratedFile(java.io.File) */ public void execGeneratedFile(File file) throws APIException { AdministrationService adminService = Context.getAdministrationService(); // preserve this server's sync settings List<GlobalProperty> syncGPs = adminService.getGlobalPropertiesByPrefix("sync."); getSynchronizationDAO().execGeneratedFile(file); // save those GPs again for (GlobalProperty gp : syncGPs) { adminService.saveGlobalProperty(gp); } //Delete any data in sync record after import of the parent DB for (SyncRecord record : this.getSynchronizationDAO().getSyncRecords()) { this.getSynchronizationDAO().deleteSyncRecord(record); } } /** * Determines if given object is to be sync-ed assuming sync as a feature is turned on. This is * done by: <br/> * 1. type has to implement OpenmrsObject interface 2. comparing the type of the object against * the types in the DB configured for exclusion from sync. * * @see org.openmrs.module.sync.api.SyncService#execGeneratedFile(java.io.File) */ public Boolean shouldSynchronize(Object entity) throws APIException { Boolean ret = true; // OpenmrsObject *only*. if (!(entity instanceof OpenmrsObject)) { if (log.isDebugEnabled()) log.debug("Do nothing. Flush with type that does not implement OpenmrsObject, type is:" + entity.getClass().getName()); return false; } //if the server classes haven't been loaded yet, do it now if (serverClassesCollection == null) { refreshServerClassesCollection(); } //now verify if (serverClassesCollection != null) { String type = entity.getClass().getName(); for (String temp : serverClassesCollection) { if (type.startsWith(temp)) { ret = false; break; } } } return ret; } /*** * Refreshes static helper collection. This is a perf optimization to avoid fetching the * sync_server_classes on every call to {@link #shouldSynchronize(Object)} Remarks:<br/> * The algorithm is as follows: - if no servers to talk to are setup (i.e. no rows in * sync_server_class) then use sync_class only - else only use the classes that are setup in all * servers (i.e.) for the class/type to be excluded it has to be setup for exclusion in all * servers */ public static synchronized void refreshServerClassesCollection() { List<RemoteServer> servers = Context.getService(SyncService.class).getRemoteServers(); Set<String> serverClasses = new HashSet<String>(); if (servers == null || servers.size() == 0) { //this is easy, just use the defaults for (SyncClass sc : Context.getService(SyncService.class).getSyncClasses()) { if (!sc.getDefaultReceiveFrom() && !sc.getDefaultSendTo()) serverClasses.add(sc.getName()); } } else { //some sync servers are set up Map<String, Integer> helperMap = new HashMap<String, Integer>(); //crank through and count up the types & occurrences for (RemoteServer server : servers) { for (String temp : server.getClassesNotReceived()) { if (helperMap.containsKey(temp)) { //already there, just increment the count Integer iTemp = helperMap.get(temp) + 1; helperMap.put(temp, iTemp); } else { //not there yet, just add with count of 0 helperMap.put(temp, 1); } } for (String temp : server.getClassesNotSent()) { if (helperMap.containsKey(temp)) { //already there, just increment the count Integer iTemp = helperMap.get(temp) + 1; helperMap.put(temp, iTemp); } else { //not there yet, just add with count of 0 helperMap.put(temp, 1); } } } //now, walk the map and only use the types where occurrence count = 2 x nbr or servers //i.e. the type was listed on all servers as both don't send and don't receive int targetCount = servers.size() * 2; for (String type : helperMap.keySet()) { if (helperMap.get(type).equals(targetCount)) { serverClasses.add(type); } } } //now assign serverClassesCollection = serverClasses; } public String getPrimaryKey(OpenmrsObject obj) { if (obj instanceof Privilege) { return ((Privilege) obj).getPrivilege(); } else if (obj instanceof Role) { return ((Role) obj).getRole(); } else if (obj instanceof GlobalProperty) { return ((GlobalProperty) obj).getProperty(); } else { return null; } } /** * Handles the odd case of saving patient who already has person record. This method is invoked * by sync AOP advice on save of new patient (see * {@link org.openmrs.module.sync.advice.SavePatientAdvice}) in order to generate a necessary * sync item for the actions taken inside of * {@link org.openmrs.api.db.hibernate.HibernatePatientDAO#savePatient(Patient)}. The * compensating logic resides in * {@link HibernateSyncInterceptor#addSyncItemForSubclassStub(SyncSubclassStub)}. */ public void handleInsertPatientStubIfNeeded(Patient p) throws APIException { SyncSubclassStub stub = new SyncSubclassStub(p, "person", "person_id", "patient", "patient_id", null, null, null); stub.addColumn("voided", 0); Integer userId = 0; if (p.getCreator() != null) userId = p.getCreator().getUserId(); else userId = Context.getAuthenticatedUser().getUserId(); stub.addColumn("creator", userId); stub.addColumn("date_created", p.getDateCreated()); handleInsertSubclassIfNeeded(stub); } public void handleInsertSubclassIfNeeded(SyncSubclassStub stub) { if (stub == null || stub.getId() == null || stub.getUuid() == null) { return; } // changing the flush mode temporarily so that nothing is flushed // to the db while we are checking for the uuids boolean wasFlushModeManualAlready = dao.setFlushModeManual(); try { //check if person obj exists Object parentId = null; Object subclassId = null; // TODO: Fix this logic when patient_id != person_id anymore List<List<Object>> rows = executeSQLPrivilegeSafe("select " + stub.getParentTableId() + " from " + stub.getParentTable() + " where uuid = '" + stub.getUuid() + "'", true); if (rows.size() > 0) parentId = rows.get(0).get(0); rows = executeSQLPrivilegeSafe( "select " + stub.getSubclassTableId() + " from " + stub.getSubclassTable() + " where " + stub.getSubclassTableId() + " = (select " + stub.getParentTableId() + " from " + stub.getParentTable() + " where uuid = '" + stub.getUuid() + "')", true); if (rows.size() > 0) subclassId = rows.get(0).get(0); if (parentId != null && subclassId == null) { //bingo! log.info("Create of new parent " + stub.getParentTable() + " who is already other object detected, uuid: " + stub.getUuid()); HibernateSyncInterceptor.addSyncItemForSubclassStub(stub); } } finally { // only reset this if we really changed it when setting it to manual if (!wasFlushModeManualAlready) dao.setFlushModeAutomatic(); } return; } public Long getCountOfSyncRecords(RemoteServer server, Date from, Date to, SyncRecordState... states) throws APIException { return dao.getCountOfSyncRecords(server, from, to, states); } /** * Utility method for wrapping executeSQL calls in SQL LEVEL ACCESS privilege, if necessary * * @param sql * @param selectOnly * @return */ private List<List<Object>> executeSQLPrivilegeSafe(String sql, boolean selectOnly) { String privilege = OpenmrsConstants.PRIV_SQL_LEVEL_ACCESS; if (!Context.isAuthenticated() || !Context.hasPrivilege(privilege)) { try { Context.addProxyPrivilege(privilege); return Context.getAdministrationService().executeSQL(sql, selectOnly); } finally { Context.removeProxyPrivilege(privilege); } } else return Context.getAdministrationService().executeSQL(sql, selectOnly); } public Integer backportSyncRecords(RemoteServer server, Date date) { int count = 0; SyncRecord firstRecord = getEarliestRecord(date); SyncRecord latestRecord = getLatestRecord(); // we have no sync records, quit early if (firstRecord == null) return 0; // not sure how this would happen without the previous one, but just in case. if (latestRecord == null) return 0; Integer firstRecordId = 0; Integer latestRecordId = latestRecord.getRecordId(); System.out.println("first record id: " + firstRecord.getRecordId()); System.out.println("latest record id: " + latestRecord.getRecordId()); boolean recordsFound = false; do { recordsFound = false; // only getting a small number at a time so that we don't get an OOM for (SyncRecord record : getSynchronizationDAO().getSyncRecords(null, null, firstRecordId, 35, true)) { recordsFound = true; System.out.println("record id: " + record.getRecordId()); firstRecordId = record.getRecordId(); SyncServerRecord serverRecord = record.getServerRecord(server); if (serverRecord == null) { // if this record is not being sent to this server yet, add it as a SyncServerRecord and send it record.addServerRecord(server); updateSyncRecord(record); // persist to the db count++; System.out.println("saved record id: " + record.getRecordId()); } if (count % 50 == 0) { // flush every so often so we don't get an OOM Context.flushSession(); Context.clearSession(); } } } while (recordsFound == true && firstRecordId != latestRecordId); return count; } /** * @see SyncService#getMostRecentFullyCommittedRecordId() */ public int getMostRecentFullyCommittedRecordId() { return getSynchronizationDAO().getMostRecentFullyCommittedRecordId(); } /** * @see org.openmrs.module.sync.api.SyncService#getSyncServerRecord(java.lang.Integer) */ public SyncServerRecord getSyncServerRecord(Integer syncServerRecordId) throws APIException { return getSynchronizationDAO().getSyncServerRecord(syncServerRecordId); } }