/**
* 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;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.EnumSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import net.sf.ehcache.concurrent.Sync;
import org.openmrs.OpenmrsObject;
import org.openmrs.Patient;
import org.openmrs.annotation.Authorized;
import org.openmrs.annotation.Logging;
import org.openmrs.api.APIException;
import org.openmrs.api.db.DAOException;
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.SyncStatistic;
import org.openmrs.module.sync.SyncUtil;
import org.openmrs.module.sync.ingest.SyncImportRecord;
import org.openmrs.module.sync.server.RemoteServer;
import org.openmrs.module.sync.server.SyncServerRecord;
import org.springframework.transaction.annotation.Transactional;
/**
* Database related methods for the Synchronization module.
*/
@Transactional
public interface SyncService {
/**
* Create a new SyncRecord
*
* @param SyncRecord The SyncRecord to create
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void createSyncRecord(SyncRecord record) throws APIException;
/**
* Auto generated method comment
*
* @param record
* @param originalUuid
*/
public void createSyncRecord(SyncRecord record, String originalUuid);
/**
* Update a SyncRecord
*
* @param SyncRecord The SyncRecord to update
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void updateSyncRecord(SyncRecord record) throws APIException;
/**
* Delete a SyncRecord
*
* @param SyncRecord The SyncRecord to delete
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void deleteSyncRecord(SyncRecord record) throws APIException;
/**
* @param keyword the search string to match
* @return a list of sync records or an empty list if none
* @throws APIException
* @should find a record given a string in its payload
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords(String keyword) throws APIException;
/**
* @param syncRecordId of the SyncRecord to retrieve
* @return SyncRecord The SyncRecord or null if not found
* @throws APIException
* @should get a record by its primary key
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public SyncRecord getSyncRecord(Integer syncRecordId) throws APIException;
/**
* @param uuid of the SyncRecord to retrieve
* @return SyncRecord The SyncRecord or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public SyncRecord getSyncRecord(String uuid) throws APIException;
@Transactional(readOnly = true)
public SyncRecord getSyncRecordByOriginalUuid(String originalUuid) throws APIException;
/**
* @return SyncRecord The latest SyncRecord or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public SyncRecord getLatestRecord() throws APIException;
/**
* @param afterDate Optional. If specified, will get the earliest record after the given date
* @return SyncRecord The earliest SyncRecord or null if not found
* @throws APIException
*/
public SyncRecord getEarliestRecord(Date afterDate) throws APIException;
/**
* @return the next sync record after the passed in record
*/
public SyncRecord getNextRecord(SyncRecord record);
/**
* @return the previous sync record before the passed in record
*/
public SyncRecord getPreviousRecord(SyncRecord record);
/**
* Returns a sync record which is older than the given sync record and is in one of the given
* states.
*
* @param syncRecord
* @param states
* @return the sync record or null if not found
* @throws APIException
*/
@Authorized({ SyncConstants.PRIV_VIEW_SYNC_RECORDS })
@Transactional(readOnly = true)
public SyncRecord getOlderSyncRecordInState(SyncRecord syncRecord, EnumSet<SyncRecordState> states) throws APIException;
/**
* Create a new SyncImportRecord
*
* @param SyncImportRecord The SyncImportRecord to create
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void createSyncImportRecord(SyncImportRecord record) throws APIException;
/**
* Update a SyncImportRecord
*
* @param SyncImportRecord The SyncImportRecord to update
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void updateSyncImportRecord(SyncImportRecord record) throws APIException;
/**
* Delete a SyncImportRecord
*
* @param SyncImportRecord The SyncImportRecord to delete
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void deleteSyncImportRecord(SyncImportRecord record) throws APIException;
/**
* Deletes SyncImportRecords by ServerId
*
* @param serverId The serverId of SyncImportRecords to delete
* @throws APIException
*/
public void deleteSyncImportRecordsByServer(Integer serverId) throws APIException;
/**
* @param uuid of the SyncImportRecord to retrieve
* @return SyncRecord The SyncImportRecord or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public SyncImportRecord getSyncImportRecord(String uuid) throws APIException;
/**
* Get all SyncImportRecords in a specific SyncRecordState
*
* @param state SyncRecordState for the SyncImportRecords to be returned
* @return SyncRecord A list containing all SyncImportRecords with the given state
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncImportRecord> getSyncImportRecords(SyncRecordState... state) throws APIException;
/**
* Returns the first SyncRecord in either the PENDING SEND or the NEW state
*
* @return SyncRecord The first SyncRecord matching the criteria, or null if none matches
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public SyncRecord getFirstSyncRecordInQueue() throws APIException;
/**
* Get all SyncRecords
*
* @return SyncRecord A list containing all SyncRecords
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords() throws APIException;
/**
* Get all SyncRecords in a specific SyncRecordState
*
* @param state SyncRecordState for the SyncRecords to be returned
* @return SyncRecord A list containing all SyncRecords with the given state
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords(SyncRecordState state) throws APIException;
/**
* Get all SyncRecords in a specific SyncRecordStates
*
* @param states SyncRecordStates for the SyncRecords to be returned
* @param maxSyncRecords the number of results to restrict to. (optional/nullable)
* @param firstRecordId The index in the search results to start returning from. if null, assumes 0
* @return SyncRecord A list containing all SyncRecords with the given states
* @throws APIException
*/
@Authorized({ "View Synchronization Records" })
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords(SyncRecordState[] states, Integer maxSyncRecords, Integer firstRecordId) throws APIException;
/**
* Get all SyncRecords in a specific SyncRecordStates, that the server allows sending for
* (per-server basis). Filters out records with classes that are not sync-able (see
* RemoteServr.getClassesSent() for more info on how this works). Updates status of filtered out
* classes to 'not_supposed_to_sync'.
*
* @param states SyncRecordStates for the SyncRecords to be returned
* @param server Server these records will be sent to, so we can filter on Class
* @param maxSyncRecords
* @param firstRecordId the start of the result set. if null, starts from 0
* @return SyncRecord A list containing all SyncRecords with the given states
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
public List<SyncRecord> getSyncRecords(SyncRecordState[] states, RemoteServer server, Integer maxSyncRecords, Integer firstRecordId)
throws APIException;
/**
* Get all SyncRecords in a specific SyncRecordStates
*
* @param states SyncRecordStates for the SyncRecords to be returned
* @param inverse
* @param maxSyncRecords
* @param firstRecordId the syncrecord id of the first record to return
* @return SyncRecord A list containing all SyncRecords with the given states
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords(SyncRecordState[] states, boolean inverse, Integer maxSyncRecords, Integer firstRecordId)
throws APIException;
/**
* Get all SyncRecords after a given timestamp
*
* @param from Timestamp specifying lower bound, not included.
* @return SyncRecord A list containing all SyncRecords with a timestamp after the given
* timestamp
* @throws APIException
*/
//Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecordsSince(Date from) throws APIException;
/**
* Get all SyncRecords between two timestamps, including the to-timestamp.
*
* @param from Timestamp specifying lower bound, not included.
* @param to Timestamp specifying upper bound, included.
* @return SyncRecord A list containing all SyncRecords with a timestamp between the from
* timestamp and up to and including the to timestamp
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecordsBetween(Date from, Date to) throws APIException;
/**
* @param server optional server to restrict this to
* @param from the start date
* @param to the end date
* @param states optional states to restrict this to
* @return the number of records
* @throws APIException
*/
@Transactional(readOnly = true)
public Long getCountOfSyncRecords(RemoteServer server, Date from, Date to, SyncRecordState... states)
throws APIException;
/**
* Get the most recent sync records
*
* @param firstRecordId the first SyncRecord#getRecordId() to return
* @param numberToReturn the max number of records to return
* @return SyncRecord A list containing all SyncRecords ordered from most recent to oldest
* @throws APIException
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public List<SyncRecord> getSyncRecords(Integer firstRecordId, Integer numberToReturn) throws APIException;
/**
* Deletes all {@link SyncRecord}s that have the given states (optional) and are before the
* given date. <br/>
* <br/>
* If <code>states</code> is null, then automatic detection of the current server setup is done
* and the appropriate states are chosen to be deleted:
* <dl>
* <dt>if server has a parent (meaning this server is a leaf node)</dt>
* <dd>sync_server_record will be empty, thus what we need to do is to delete all sync_record
* rows that are COMMITTED or NOT_SUPPOSED_TO_SYNC</dd>
* <dt>if server is root node (no parent, only children)</dt>
* <dd>sync_record has only rows with 'NEW' (or NOT_SUPPOSED_TO_SYNC) status delete
* sync_server_record rows first that are safe to delete then delete rows irrespective of status
* in sync_record that have *no* rows in sync_server_record</dd>
* </dl>
* <br/>
* All {@link SyncServerRecord}s are deleted that are before the given date and have are either
* {@link SyncRecordState#COMMITTED} or {@link SyncRecordState#NOT_SUPPOSED_TO_SYNC}.
*
* @param states the states on {@link SyncServerRecord} to delete (or null if automatic
* selection should be done)
* @param to the date to delete before
* @return the number of delete records
* @throws DAOException
* @should delete all sync records if server is root node
* @should only delete committed sync records if child node
*/
public Integer deleteSyncRecords(SyncRecordState[] states, Date to) throws APIException;
/**
* Retrieve value of given global property using synchronization data access mechanisms.
*
* @param propertyName
* @return
*/
//@Authorized({"View Synchronization Records"})
@Transactional(readOnly = true)
public String getGlobalProperty(String propertyName) throws APIException;
/**
* Set global property related to synchronization; notably bypasses any changeset recording
* mechanisms.
*
* @param propertyName String specifying property name which value is to be set.
* @param propertyValue String specifying property value to be set.
* @throws APIException
*/
//@Authorized({"Manage Synchronization Records"})
public void setGlobalProperty(String propertyName, String propertyValue) throws APIException;
/**
* Update or create a server (child or parent)
*
* @param server The RemoteServer to persist in the database
* @return RemoteServer The RemoteServer created or updated
* @throws APIException
*/
//@Authorized({"Manage Synchronization Servers"})
public RemoteServer saveRemoteServer(RemoteServer server) throws APIException;
/**
* Delete a RemoteServer
*
* @param server The RemoteServer to delete
* @throws APIException
*/
//@Authorized({"Manage Synchronization Servers"})
public void deleteRemoteServer(RemoteServer server) throws APIException;
/**
* @param serverId of the RemoteServer to retrieve
* @return RemoteServer The RemoteServer or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Servers"})
@Transactional(readOnly = true)
public RemoteServer getRemoteServer(Integer serverId) throws APIException;
/**
* @param uuid of the RemoteServer to retrieve
* @return RemoteServer The RemoteServer or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Servers"})
@Transactional(readOnly = true)
public RemoteServer getRemoteServer(String uuid) throws APIException;
/**
* @param username child_username of the RemoteServer to retrieve
* @return RemoteServer The RemoteServer or null if not found
* @throws APIException
*/
//@Authorized({"View Synchronization Servers"})
@Transactional(readOnly = true)
public RemoteServer getRemoteServerByUsername(String username) throws APIException;
/**
* @return List of all {@link RemoteServer}s defined -- both parent and child.
* @throws APIException
*/
//@Authorized({"View Synchronization Servers"})
@Transactional(readOnly = true)
public List<RemoteServer> getRemoteServers() throws APIException;
/**
* @return RemoteServer The RemoteServer defined as the parent to this current server or null if
* this server is the root of all other servers
* @throws APIException
*/
//@Authorized({"View Synchronization Servers"})
@Transactional(readOnly = true)
public RemoteServer getParentServer() throws APIException;
/**
* Retrieves globally unique id of the server.
*
* @return uuid of the server. String representation of java.util.UUID.
* @throws APIException
*/
@Transactional(readOnly = true)
public String getServerUuid() throws APIException;
/**
* Sets globally unique id of the server. WARNING: Use only during initial server setup.
* WARNING: DO NOT CALL this method unless you fully understand the implication of this action.
* Specifically, changing already assigned UUID for a server will cause it to loose its link to
* history of changes that may be designated for this server.
*
* @param uuid unique UUID of the server. String representation of java.util.UUID.
* @throws APIException
*/
public void saveServerUuid(String uuid) throws APIException;
/**
* Retrieve user friendly nickname for the server that is (by convention) unique for the given
* sync network of servers.
*
* @return name of the server.
* @throws APIException
*/
@Transactional(readOnly = true)
public String getServerName() throws APIException;
/**
* Sets friendly server name. WARNING: Use only during initial server setup. WARNING: DO NOT
* CALL this method unless you fully understand the implication of this action. Similarly to
* {@link #setServerUuid(String)} some data loss may occur if called while server is functioning
* as part of the sync network.
*
* @param name new server name
* @throws APIException
*/
public void saveServerName(String name) throws APIException;
/**
* Get the stored administrative email address or null if none
*
* @return admin email address or null
* @throws APIException
*/
public String getAdminEmail() throws APIException;
/**
* Save the admin email address for this server
*
* @param email the admin's email address
* @throws APIException
*/
public void saveAdminEmail(String email) throws APIException;
/**
* Update or create a SyncClass
*
* @param SyncClass The SyncClass to update
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
public void saveSyncClass(SyncClass syncClass) throws APIException;
/**
* Delete a SyncClass
*
* @param SyncClass The SyncClass to delete
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
public void deleteSyncClass(SyncClass syncClass) throws APIException;
/**
* @param syncClassId of the SyncClass to retrieve
* @return SyncClass The SyncClass or null if not found
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
@Transactional(readOnly = true)
public SyncClass getSyncClass(Integer syncClassId) throws APIException;
/**
* @return List<SyncClass> The latest default {@link SyncClass}es
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
@Transactional(readOnly = true)
public List<SyncClass> getSyncClasses() throws APIException;
/**
* @param String of the String class name to retrieve
* @return SyncClass The SyncClass or null if not found
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
@Transactional(readOnly = true)
public SyncClass getSyncClassByName(String className) throws APIException;
/**
* Deletes instance of OpenmrsObject from data storage.
*
* @param o instance to delete
* @throws APIException
*/
//@Authorized({"Manage Synchronization"})
@Transactional
public void deleteOpenmrsObject(OpenmrsObject o) throws APIException;
/**
* Exposes ability to change persistence flush semantics.
*
* @throws APIException
* @see org.openmrs.module.sync.api.db.SyncDAO#setFlushModeManual()
*/
public void setFlushModeManual() throws APIException;
/**
* Exposes ability to change persistence flush semantics.
*
* @throws APIException
* @see org.openmrs.module.sync.api.db.SyncDAO#setFlushModeAutomatic()
*/
public void setFlushModeAutomatic() throws APIException;
public void flushSession() throws APIException;
/**
* 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
*/
public void saveOrUpdate(OpenmrsObject object) throws APIException;
/**
* @param fromDate start date
* @param toDate end date
* @return
* @throws DAOException
*/
public Map<RemoteServer, LinkedHashSet<SyncStatistic>> getSyncStatistics(Date fromDate, Date toDate) throws DAOException;
/**
* Gets any type of OpenmrsObject given a class and a UUID
*
* @param <T> works for any OpenmrsObject subclass
* @param clazz
* @param uuid
* @return
* @should get any openmrs object by its uuid
*/
@Transactional(readOnly = true)
public <T extends OpenmrsObject> T getOpenmrsObjectByUuid(Class<T> clazz, String uuid);
/**
* Get all possible classes that extend OpenmrsObject in the system
*
* @return a list of {@link OpenmrsObject}
*/
@Transactional(readOnly = true)
public List<Class<OpenmrsObject>> getAllOpenmrsObjects();
/**
* Dumps the entire database, much like what you'd get from the mysqldump command, and adds a
* few insert lines to set the child's UUID, and delete sync history. This is slightly slower
* than the {@link #generateDataFile()} method but not specific to mysql.
*
* @param uuidForChild if not null, use this as the uuid for the child server, otherwise
* autogenerate one
* @param out where to write the sql
* @throws APIException
*/
// @Authorized({"Backup Entire Database"})
@Transactional(readOnly = true)
public void exportChildDB(String uuidForChild, OutputStream os) throws APIException;
/**
* imports a synchronization database backup from the parent
*
* @throws DAOException
*/
public void importParentDB(InputStream in) throws APIException;
/**
* Dumps the entire database with the mysqldump command to a file.
*
* @return the file pointer to the database dump
*/
@Transactional(readOnly = true)
public File generateDataFile() throws APIException;
/**
* Executes a sql file on the database. <br/>
* The sync global properties and sync records are cleared out after importing the sql.
*
* @param fileToExec the file to run
* @throws APIException
*/
public void execGeneratedFile(File fileToExec) throws APIException;
/**
* Determines if given object should be recorded for synchronization
*
* @param Object to be tested
* @throws APIException
* @return true if the object should be recored for sync
*/
@Transactional(readOnly = true)
@Logging(ignore = true)
public Boolean shouldSynchronize(Object entity) throws APIException;
/**
* Gets the value of the non-incrementing primary key
*
* @param obj the object
* @return the primary key value as a string
* @throws APIException
* @see {@link SyncUtil#hasNoAutomaticPrimaryKey(String)}
*/
@Transactional(readOnly = true)
@Logging(ignoredArgumentIndexes = 0)
public String getPrimaryKey(OpenmrsObject obj) throws APIException;
/**
* Handles the odd case of saving patient who already has person record. See {@link SyncSubclassStub}
* class comments for detailed description of how this works. Note this service is marked as
* transactional read only to avoid spring trying to flush/commit on exit.
*
* @see SyncSubclassStub
* @param p Patient for which stub ought to be created
* @throws APIException
*/
@Transactional(readOnly = true)
// because things are not actually written to the db, just memory
@Logging(ignoreAllArgumentValues = true)
public void handleInsertPatientStubIfNeeded(Patient p) throws APIException;
/**
* Handles the odd case of saving patient who already has person record (or
* a concept who is a concept numeric already). See {@link SyncSubclassStub}
* class comments for detailed description of how this works. Note this
* service is marked as transactional read only to avoid spring trying to
* flush/commit on exit.
*
* @see SyncSubclassStub
* @param stub
* a SyncPatientStub class containing any Auditable object for
* which stub ought to be created
* @throws APIException
*/
@Transactional(readOnly = true)
// because things are not actually written to the db, just memory
@Logging(ignoreAllArgumentValues = true)
public void handleInsertSubclassIfNeeded(SyncSubclassStub stub) throws APIException;
/**
* This method copies SyncRecords after the given <code>date</code> into SyncServerRecords for the given <code>server</code>.
* This is needed when a server is using data that was copied BEFORE the server was set up in the sync admin pages.
*
* @param server the server to copy the records tos
* @param date the exact datetime to start copying records
* @return the number of records changed
*/
public Integer backportSyncRecords(RemoteServer server, Date date);
/**
* Gets the Most recent successfully committed record
* @return record id of the most successful most recent committed record
*/
@Transactional(readOnly = true)
public int getMostRecentFullyCommittedRecordId();
/**
* Gets the SyncServerRecord with a matching syncServerRecordId
*
* @param syncServerRecordId of the SyncServerRecord to retrieve
* @return The SyncServerRecord or null if not found
* @throws APIException
* @should get a syncServerRecord by its primary key
*/
@Transactional(readOnly = true)
public SyncServerRecord getSyncServerRecord(Integer syncServerRecordId) throws APIException;
}