// Copyright 2015 The Project Buendia Authors
//
// 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 distrib-
// uted 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
// specific language governing permissions and limitations under the License.
package org.projectbuendia.openmrs.api.db.hibernate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Restrictions;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.Type;
import org.openmrs.BaseOpenmrsData;
import org.openmrs.Obs;
import org.openmrs.Order;
import org.openmrs.Patient;
import org.projectbuendia.openmrs.api.SyncToken;
import org.projectbuendia.openmrs.api.db.ProjectBuendiaDAO;
import org.projectbuendia.openmrs.api.db.SyncPage;
import org.projectbuendia.openmrs.sync.ObsSyncParameters;
import org.projectbuendia.openmrs.sync.OrderSyncParameters;
import org.projectbuendia.openmrs.sync.PatientSyncParameters;
import org.projectbuendia.openmrs.sync.SyncParameters;
import javax.annotation.Nullable;
import javax.validation.constraints.Null;
import java.util.ArrayList;
import java.util.List;
import static org.hibernate.criterion.Order.asc;
import static org.hibernate.criterion.Restrictions.eq;
import static org.hibernate.criterion.Restrictions.in;
import static org.hibernate.criterion.Restrictions.ne;
import static org.hibernate.criterion.Restrictions.sqlRestriction;
/** Default implementation of {@link ProjectBuendiaDAO}. */
public class HibernateProjectBuendiaDAO implements ProjectBuendiaDAO {
protected final Log log = LogFactory.getLog(this.getClass());
private SessionFactory sessionFactory;
/** @return the sessionFactory */
public SessionFactory getSessionFactory() {
return sessionFactory;
}
/** @param sessionFactory the sessionFactory to set */
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
@Override
public SyncPage<Obs> getObservationsModifiedAfter(
@Nullable SyncToken syncToken, boolean includeVoided, int maxResults) {
//noinspection unchecked
return fetchSyncPage(
(Class<SyncParameters<Obs>>) (Class<?>) ObsSyncParameters.class,
syncToken, null, includeVoided, maxResults);
}
@Override
public SyncPage<Patient> getPatientsModifiedAfter(
@Nullable SyncToken syncToken, boolean includeVoided, int maxResults) {
//noinspection unchecked
return fetchSyncPage(
(Class<SyncParameters<Patient>>) (Class<?>) PatientSyncParameters.class,
syncToken, null, includeVoided, maxResults);
}
@Override
public SyncPage<Order> getOrdersModifiedAtOrAfter(
@Nullable SyncToken syncToken, boolean includeVoided, int maxResults,
@Nullable Order.Action[] allowedOrderTypes) {
final Criterion itemFilter = allowedOrderTypes != null
? in("action", allowedOrderTypes)
: null;
//noinspection unchecked
return fetchSyncPage(
(Class<SyncParameters<Order>>)(Class<?>) OrderSyncParameters.class,
syncToken, itemFilter, includeVoided, maxResults);
}
private <T extends BaseOpenmrsData> SyncPage<T> fetchSyncPage(
Class<SyncParameters<T>> clazz, @Nullable SyncToken syncToken, Criterion restriction,
boolean includeVoided, int maxResults) {
List<SyncParameters<T>> dbList =
fetchResults(clazz, syncToken, restriction, includeVoided, maxResults);
return resultsToSyncPage(dbList);
}
private <T extends SyncParameters> List<T> fetchResults(
Class<T> clazz, @Nullable SyncToken syncToken,
@Nullable Criterion restriction, boolean includeVoided, int maxResults) {
Session session = sessionFactory.getCurrentSession();
Criteria criteria = session.createCriteria(clazz);
if (syncToken != null) {
// (a, b) > (x, y) is equivalent to (a > x) OR ((a = x) AND (b > y)). See
// http://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html#operator_greater-than
criteria.add(sqlRestriction(
// {alias} is substituted for the table alias that hibernate uses for the type.
"({alias}.date_updated, {alias}.uuid) > (?, ?)",
new Object[]{
syncToken.greaterThanOrEqualToTimestamp,
// If syncToken.greaterThanUuid is null, we use the empty string, which
// is 'smaller' than every other string in terms of sort order.
syncToken.greaterThanUuid == null ? "" : syncToken.greaterThanUuid},
new Type[] {StandardBasicTypes.TIMESTAMP, StandardBasicTypes.STRING}));
}
Criteria subCriteria = criteria.createCriteria("item");
if (restriction != null) {
subCriteria.add(restriction);
}
if (!includeVoided) {
subCriteria.add(eq("voided", false));
}
criteria.addOrder(asc("dateUpdated"))
.addOrder(asc("uuid"));
if (maxResults > 0) {
criteria.setMaxResults(maxResults);
}
//noinspection unchecked
return criteria.list();
}
private <T extends BaseOpenmrsData> SyncPage<T> resultsToSyncPage(
List<SyncParameters<T>> dbResults) {
// SyncParameters<T> --> SyncPage<T>
ArrayList<T> items = new ArrayList<>(dbResults.size());
for (SyncParameters<T> params : dbResults) {
items.add(params.getItem());
}
SyncToken token = null;
if (dbResults.size() > 0) {
SyncParameters<T> lastEntry = dbResults.get(dbResults.size() - 1);
token = new SyncToken(lastEntry.getDateUpdated(), lastEntry.getUuid());
}
return new SyncPage<>(items, token);
}
}