Skip to content

Commit

Permalink
Merge pull request openmrs#366 from rowanseymour/master
Browse files Browse the repository at this point in the history
TRUNK-3589: Add LocationService method to search for a location by a location attribute
  • Loading branch information
dkayiwa committed Jul 22, 2013
2 parents 66d994e + b21b6c7 commit fc49c70
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 14 deletions.
25 changes: 25 additions & 0 deletions api/src/main/java/org/openmrs/api/LocationService.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.openmrs.api;

import java.util.List;
import java.util.Map;

import org.openmrs.Address;
import org.openmrs.Location;
Expand Down Expand Up @@ -155,17 +156,41 @@ public interface LocationService extends OpenmrsService {
* returned if there are no locations. Search is case insensitive. matching this
* <code>nameFragment</code>. If start and length are not specified, then all matches are
* returned
*
* @deprecated replaced by {@link LocationService#getLocations(String, org.openmrs.Location, java.util.Map, boolean, Integer, Integer)}
*
* @param nameFragment is the string used to search for locations
* @param includeRetired Specifies if retired locations should be returned
* @param start the beginning index
* @param length the number of matching locations to return
* @since 1.8
*/
@Deprecated
@Authorized( { PrivilegeConstants.GET_LOCATIONS })
public List<Location> getLocations(String nameFragment, boolean includeRetired, Integer start, Integer length)
throws APIException;

/**
* Gets the locations matching the specified arguments. A null list will never be returned. An empty list will be
* returned if there are no locations. Search is case insensitive. matching this <code>nameFragment</code>. If start
* and length are not specified, then all matches are returned.
*
* @param nameFragment is the string used to search for locations
* @param parent only return children of this parent
* @param attributeValues the attribute values
* @param includeRetired specifies if retired locations should also be returned
* @param start the beginning index
* @param length the number of matching locations to return
* @return the list of locations
* @should return empty list when no location has matching attribute values
* @should get locations having all matching attribute values
* @since 1.10
*/
@Authorized( { PrivilegeConstants.GET_LOCATIONS })
public List<Location> getLocations(String nameFragment, Location parent,
Map<LocationAttributeType, Object> attributeValues, boolean includeRetired, Integer start, Integer length)
throws APIException;

/**
* Returns locations that contain the given tag.
*
Expand Down
18 changes: 13 additions & 5 deletions api/src/main/java/org/openmrs/api/db/LocationDAO.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.openmrs.api.db;

import java.util.List;
import java.util.Map;

import org.hibernate.SessionFactory;
import org.openmrs.Location;
Expand Down Expand Up @@ -68,12 +69,19 @@ public interface LocationDAO {
public List<Location> getAllLocations(boolean includeRetired);

/**
* Returns a specified number of locations starting with a given string from the specified index
*
* @see LocationService#getLocations(String, boolean, Integer, Integer)
* Gets the locations matching the specified arguments
*
* @param nameFragment is the string used to search for locations
* @param parent only return children of this parent
* @param serializedAttributeValues the serialized attribute values
* @param includeRetired specifies if retired locations should also be returned
* @param start the beginning index
* @param length the number of matching locations to return
* @return the list of locations
*/
public List<Location> getLocations(String nameFragment, boolean includeRetired, Integer start, Integer length)
throws DAOException;
public List<Location> getLocations(String nameFragment, Location parent,
Map<LocationAttributeType, String> serializedAttributeValues, boolean includeRetired, Integer start,
Integer length) throws DAOException;

/**
* Completely remove the location from the database.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.openmrs.api.db.hibernate;

import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
Expand Down Expand Up @@ -202,19 +203,29 @@ public Long getCountOfLocations(String nameFragment, Boolean includeRetired) {
}

/**
* @see LocationDAO#getLocations(String, Integer, Integer)
* @see LocationDAO#getLocations(String, org.openmrs.Location, java.util.Map, boolean, Integer, Integer)
*/
@SuppressWarnings("unchecked")
@Override
public List<Location> getLocations(String nameFragment, boolean includeRetired, Integer start, Integer length)
throws DAOException {
public List<Location> getLocations(String nameFragment, Location parent,
Map<LocationAttributeType, String> serializedAttributeValues, boolean includeRetired, Integer start,
Integer length) {

Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Location.class);
if (!includeRetired)
criteria.add(Restrictions.eq("retired", false));

if (StringUtils.isNotBlank(nameFragment))
if (StringUtils.isNotBlank(nameFragment)) {
criteria.add(Restrictions.ilike("name", nameFragment, MatchMode.START));
}

if (parent != null) {
criteria.add(Restrictions.eq("parentLocation", parent));
}

if (serializedAttributeValues != null) {
HibernateUtil.addAttributeCriteria(criteria, serializedAttributeValues);
}

if (!includeRetired)
criteria.add(Restrictions.eq("retired", false));

criteria.addOrder(Order.asc("name"));
if (start != null)
Expand Down
33 changes: 33 additions & 0 deletions api/src/main/java/org/openmrs/api/db/hibernate/HibernateUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,23 @@

import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Conjunction;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Property;
import org.hibernate.criterion.Restrictions;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.HSQLDialect;
import org.hibernate.engine.SessionFactoryImplementor;
import org.openmrs.Location;
import org.openmrs.attribute.AttributeType;

/**
* This class holds common methods and utilities that are used across the hibernate related classes
Expand Down Expand Up @@ -108,4 +117,28 @@ public static String escapeSqlWildcards(String oldString, Connection connection)
return oldString;
}

/**
* Adds attribute value criteria to the given criteria query
* @param criteria the criteria
* @param serializedAttributeValues the serialized attribute values
* @param <AT> the attribute type
*/
public static <AT extends AttributeType> void addAttributeCriteria(Criteria criteria,
Map<AT, String> serializedAttributeValues) {
Conjunction conjunction = Restrictions.conjunction();
int a = 0;

for (Map.Entry<AT, String> entry : serializedAttributeValues.entrySet()) {
String alias = "attributes" + (a++);
DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Location.class).setProjection(Projections.id());
detachedCriteria.createAlias("attributes", alias);
detachedCriteria.add(Restrictions.eq(alias + ".attributeType", entry.getKey()));
detachedCriteria.add(Restrictions.eq(alias + ".valueReference", entry.getValue()));
detachedCriteria.add(Restrictions.eq(alias + ".voided", false));

conjunction.add(Property.forName("id").in(detachedCriteria));
}

criteria.add(conjunction);
}
}
19 changes: 17 additions & 2 deletions api/src/main/java/org/openmrs/api/impl/LocationServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.openmrs.Address;
import org.openmrs.Location;
Expand Down Expand Up @@ -175,7 +176,7 @@ public List<Location> getAllLocations(boolean includeRetired) throws APIExceptio
*/
@Transactional(readOnly = true)
public List<Location> getLocations(String nameFragment) throws APIException {
return getLocations(nameFragment, false, null, null);
return getLocations(nameFragment, null, null, false, null, null);
}

/**
Expand Down Expand Up @@ -345,10 +346,24 @@ public Integer getCountOfLocations(String nameFragment, Boolean includeRetired)
* @see LocationService#getLocations(String, boolean, Integer, Integer)
*/
@Override
@Deprecated
@Transactional(readOnly = true)
public List<Location> getLocations(String nameFragment, boolean includeRetired, Integer start, Integer length)
throws APIException {
return dao.getLocations(nameFragment, includeRetired, start, length);
return dao.getLocations(nameFragment, null, null, includeRetired, start, length);
}

/**
* @see LocationService#getLocations(String, org.openmrs.Location, java.util.Map, boolean, Integer, Integer)
*/
@Override
public List<Location> getLocations(String nameFragment, Location parent,
Map<LocationAttributeType, Object> attributeValues, boolean includeRetired, Integer start, Integer length) {

Map<LocationAttributeType, String> serializedAttributeValues = CustomDatatypeUtil
.getValueReferences(attributeValues);

return dao.getLocations(nameFragment, parent, serializedAttributeValues, includeRetired, start, length);
}

/**
Expand Down
75 changes: 75 additions & 0 deletions api/src/test/java/org/openmrs/api/LocationServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,16 @@

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.openmrs.GlobalProperty;
import org.openmrs.Location;
import org.openmrs.LocationAttribute;
import org.openmrs.LocationAttributeType;
import org.openmrs.LocationTag;
import org.openmrs.api.context.Context;
Expand Down Expand Up @@ -302,6 +305,78 @@ public void getLocations_shouldReturnEmptyListWhenNoLocationMatchTheNameFragment
Assert.assertEquals(0, Context.getLocationService().getLocations("Mansion").size());
}

/**
* @see LocationService#getLocations(String, org.openmrs.Location, java.util.Map, boolean, Integer, Integer)
*/
@Test
@Verifies(value = "should return empty list when no location has matching attribute values", method = "getLocations(String,Location,Map,boolean,Integer,Integer)")
public void getLocations_shouldNotFindAnyLocationsIfNoneHaveGivenAttributeValues() {
// Save new phone number attribute type
LocationAttributeType phoneAttrType = new LocationAttributeType();
phoneAttrType.setName("Facility Phone");
phoneAttrType.setMinOccurs(0);
phoneAttrType.setMaxOccurs(1);
phoneAttrType.setDatatypeClassname("org.openmrs.customdatatype.datatype.FreeTextDatatype");
Context.getLocationService().saveLocationAttributeType(phoneAttrType);

Map<LocationAttributeType, Object> attrValues = new HashMap<LocationAttributeType, Object>();
attrValues.put(phoneAttrType, "xxxxxx");
Assert.assertEquals(0, Context.getLocationService().getLocations(null, null, attrValues, true, null, null).size());
}

/**
* @see LocationService#getLocations(String, org.openmrs.Location, java.util.Map, boolean, Integer, Integer)
*/
@Test
@Verifies(value = "should get locations having all matching attribute values", method = "getLocations(String,Location,Map,boolean,Integer,Integer)")
public void getLocations_shouldGetLocationsHavingAllMatchingAttributeValues() {
// Save new phone number attribute type
LocationAttributeType phoneAttrType = new LocationAttributeType();
phoneAttrType.setName("Facility Phone");
phoneAttrType.setMinOccurs(0);
phoneAttrType.setMaxOccurs(1);
phoneAttrType.setDatatypeClassname("org.openmrs.customdatatype.datatype.FreeTextDatatype");
Context.getLocationService().saveLocationAttributeType(phoneAttrType);

// Save new email address attribute type
LocationAttributeType emailAttrType = new LocationAttributeType();
emailAttrType.setName("Facility Email");
emailAttrType.setMinOccurs(0);
emailAttrType.setMaxOccurs(1);
emailAttrType.setDatatypeClassname("org.openmrs.customdatatype.datatype.FreeTextDatatype");
Context.getLocationService().saveLocationAttributeType(emailAttrType);

// Assign phone number 0123456789 and email address [email protected] to location #1
Location location1 = Context.getLocationService().getLocation(1);
LocationAttribute la1a = new LocationAttribute();
la1a.setAttributeType(phoneAttrType);
la1a.setValue("0123456789");
location1.addAttribute(la1a);
LocationAttribute la1b = new LocationAttribute();
la1b.setAttributeType(emailAttrType);
la1b.setValue("[email protected]");
location1.addAttribute(la1b);
Context.getLocationService().saveLocation(location1);

// Assign same phone number 0123456789 to location #2
Location location2 = Context.getLocationService().getLocation(2);
LocationAttribute la2 = new LocationAttribute();
la2.setAttributeType(phoneAttrType);
la2.setValue("0123456789");
location2.addAttribute(la2);
Context.getLocationService().saveLocation(location2);

// Search for location #1 by phone number AND email address
Map<LocationAttributeType, Object> attrValues = new HashMap<LocationAttributeType, Object>();
attrValues.put(phoneAttrType, "0123456789");
attrValues.put(emailAttrType, "[email protected]");

// Check that only location #1 is returned
List<Location> locations = Context.getLocationService().getLocations(null, null, attrValues, false, null, null);
Assert.assertEquals(1, locations.size());
Assert.assertEquals(location1, locations.get(0));
}

/**
* Get locations that have a specified tag among its child tags.
*
Expand Down

0 comments on commit fc49c70

Please sign in to comment.