Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit of work from 2012 NPSP Sprint
- Loading branch information
Evan Callahan
committed
May 22, 2012
1 parent
634722d
commit 564c77d
Showing
41 changed files
with
3,026 additions
and
0 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>25.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>25.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
public with sharing class GeocoderUsService extends GeocodingService { | ||
/*----------------------------------------------------------------------------------------------- | ||
* interface to geocoder.us web service | ||
* give it any address, get back latitude, longitude, and a parsed address | ||
* | ||
* if you do not provide a username and password for the geocoder service, the class uses the | ||
* free version which is for non-commercial use only. please note that you can only use the free | ||
* version once every 15 seconds from a given IP address | ||
* | ||
* @author Evan Callahan, Todd Reasinger | ||
-----------------------------------------------------------------------------------------------*/ | ||
|
||
private static final Integer DEFAULT_TIMEOUT = 2000; | ||
|
||
// endpoints | ||
private static final String publicEndpoint = 'rpc.geocoder.us/service/csv'; | ||
private static final String authEndpoint = 'geocoder.us/member/service/csv/geocode'; | ||
|
||
protected String username; | ||
protected String password; | ||
public Integer timeout = DEFAULT_TIMEOUT; | ||
|
||
// properties | ||
public string response { get; private set; } | ||
public string error { get; private set; } | ||
public List<Geo_Data__c> locations { get; private set; } | ||
|
||
// need to special case the test | ||
final string testResp = '47.618967,-122.348993,123 4th Ave N,Seattle,WA,98109'; | ||
|
||
// track this so we don't call the services once we are shut out | ||
boolean outOfGeocoderRequests = false; | ||
|
||
// constructors | ||
public GeocoderUsService() { | ||
} | ||
|
||
public override Geo_Data__c[] getGeodata(String address) { | ||
|
||
// if we do not already have credentials, query for them | ||
if (credentials == null) { | ||
geoDataCredentials__c[] providers = | ||
[SELECT API_Key__c, Name, Password__c, Request_Timeout__c, User_Id__c, endpoint__c | ||
FROM geoDataCredentials__c | ||
WHERE name like 'GeocoderUs%' and IsActive__c = true | ||
ORDER BY priority__c LIMIT 1]; | ||
if (!providers.isEmpty()) | ||
credentials = providers[0]; | ||
} | ||
|
||
if (credentials != null) { | ||
username = credentials.user_id__c; | ||
password = credentials.password__c; | ||
if (credentials.request_timeout__c != null) | ||
timeout = (Integer)credentials.Request_Timeout__c; | ||
} | ||
|
||
// initialize | ||
locations = new List<Geo_Data__c>(); | ||
response = error = null; | ||
|
||
if (address != null && address.trim() != '' && !outOfGeocoderRequests) { | ||
String configuredEndpoint = (credentials != null && credentials.endpoint__c != null) ? credentials.endpoint__c : (username == null ? authEndpoint : publicEndpoint); | ||
String endpoint = ((username != null && password != null) ? | ||
(EncodingUtil.URLEncode(username, 'UTF-8') + ':' + | ||
EncodingUtil.URLEncode(password, 'UTF-8') + '@' ) : '') | ||
+ configuredEndpoint; | ||
|
||
Http h = new Http(); | ||
HttpRequest req = new HttpRequest(); | ||
req.setEndpoint('http://' + endpoint + '?address=' + EncodingUtil.URLEncode(address, 'UTF-8')); | ||
req.setMethod('GET'); | ||
req.setTimeout(timeout); | ||
|
||
try { | ||
HttpResponse resp; | ||
Integer status; | ||
if (Test.isRunningTest()) { | ||
response = testResp; | ||
status = 200; | ||
} else { | ||
resp = h.send(req); | ||
response = resp.getBody(); | ||
status = resp.getStatusCode(); | ||
} | ||
if (status == 200) { | ||
if (response != null) { | ||
for (string addr : response.split('\n')) { | ||
string[] parsed = addr.split(','); | ||
if (parsed.size() == 6) { | ||
|
||
Geo_Data__c gd = new Geo_Data__c(); | ||
gd.geoLat__c = Decimal.valueOf(parsed[0]); | ||
gd.geoLong__c = Decimal.valueOf(parsed[1]); | ||
gd.Street__c = parsed[2]; | ||
gd.City__c = parsed[3]; | ||
gd.State__c = parsed[4]; | ||
gd.Zip_Postal_Code__c = parsed[5]; | ||
locations.add(gd); | ||
|
||
} | ||
} | ||
if (locations.isEmpty()) | ||
error = 'Response from geocoding service: ' + response; | ||
} else { | ||
error = 'No response from geocoding service.'; | ||
} | ||
} else { | ||
error = 'Unexpected response from geocoding service (STATUS ' + string.valueOf(status) + '): \n' + response; | ||
} | ||
|
||
} catch( System.Exception e) { | ||
if (e.getMessage().startsWith('Unauthorized endpoint')) { | ||
error = 'Before using the geocoder.us service, an administrator must go to Setup => Security => Remote Site Settings ' + | ||
'and add the following endpoint: http://' + ((username != null) ? authEndpoint : publicEndpoint); | ||
} else { | ||
error = 'Error communicating with geocoding service: ' + e.getMessage(); | ||
outOfGeocoderRequests = (error.contains('Read timed out')); | ||
} | ||
} finally { | ||
if (error != null) | ||
system.debug(loggingLevel.ERROR, error); | ||
} | ||
} | ||
|
||
return locations; | ||
} | ||
|
||
public override Boolean providesDatatype(PROVIDER_DATA_TYPES datatype) { | ||
return (datatype == PROVIDER_DATA_TYPES.GEOCODING); | ||
} | ||
|
||
public override PROVIDER_DATA_TYPES[] getAvailableDatatypes() { | ||
return new PROVIDER_DATA_TYPES[]{ PROVIDER_DATA_TYPES.GEOCODING }; | ||
} | ||
|
||
public override Geo_Data__c getGeodata(Decimal lat, Decimal lng) { | ||
return null; | ||
} | ||
|
||
// for public version, you can make calls only once every 15 seconds | ||
public override integer getMaxCallsForBatch() { return (username != null && password != null) ? 9 : 1; } | ||
public override integer getMinDelay() { return (username != null && password != null) ? null : 15; } | ||
|
||
public override integer getAvailableCallouts() { return null; } // no way to know the remaining credits | ||
|
||
public static testMethod void testGeocoder() { | ||
insert new geoDataCredentials__c(name = 'GeocoderUsService', User_Id__c = 'test', priority__c = 1, IsActive__c = true); | ||
|
||
GeocoderUsService gc = new GeocoderUsService(); | ||
gc.getGeodata('123 4th Ave, Seattle, WA'); | ||
system.debug(loggingLevel.WARN, gc.locations); | ||
system.assertEquals(47.618967, gc.locations[0].geoLat__c); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>24.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
public with sharing abstract class GeocodingService { | ||
/*----------------------------------------------------------------------------------------------- | ||
* abstract interface to geodata services | ||
* each implementation implements methods to get geodata from an address or lat/long pair | ||
* credentials for services, and priority, are stored in custom setting | ||
* | ||
* @author Evan Callahan, Todd Reasinger | ||
-----------------------------------------------------------------------------------------------*/ | ||
|
||
public enum PROVIDER_DATA_TYPES {GEOCODING, REVERSE_GEOCODING, NATIONAL_LEGISLATIVE, STATE_LEGISLATIVE, | ||
COUNTY_NAME, NEIGHBORHOOD, WATERSHED, SCHOOL_DISTRICT, CENSUS_DISTRICT} | ||
|
||
protected geoDataCredentials__c credentials; | ||
protected PROVIDER_DATA_TYPES[] requestedDatatypes; | ||
|
||
public class GeocodingException extends Exception {} | ||
|
||
public static GeocodingService getProvider(PROVIDER_DATA_TYPES[] dataTypes) { | ||
|
||
GeocodingService service = null; | ||
geoDataCredentials__c[] providers = | ||
[SELECT API_Key__c, Name, Password__c, Request_Timeout__c, User_Id__c, endpoint__c | ||
FROM geoDataCredentials__c | ||
WHERE IsActive__c = true | ||
ORDER BY priority__c]; | ||
System.debug(LoggingLevel.ERROR, providers); | ||
|
||
for (geoDataCredentials__c c : providers) { | ||
System.debug(LoggingLevel.WARN, 'Getting service for ' + c.name); | ||
Type t = Type.forName(c.name); | ||
if (t != null) service = (GeocodingService) t.newInstance(); | ||
|
||
if (t == null || service == null) { | ||
System.debug(LoggingLevel.ERROR, 'Error trying to create geocoding service for ' + c.name); | ||
} | ||
|
||
if (service != null && dataTypes != null) { | ||
// use this service ONLY if it provides ALL requested data types | ||
for (PROVIDER_DATA_TYPES pdt : dataTypes) { | ||
System.debug(LoggingLevel.WARN, 'does it provide ' + pdt); | ||
if (!service.providesDatatype(pdt)) { | ||
System.debug(LoggingLevel.WARN, 'no'); | ||
service = null; | ||
continue; | ||
} | ||
} | ||
} | ||
if (service != null) { | ||
service.credentials = c; | ||
service.requestedDatatypes = dataTypes; | ||
break; | ||
} | ||
} | ||
return service; | ||
} | ||
|
||
protected GeocodingService() {} | ||
|
||
/* | ||
* Methods that must be implemented by any provider. | ||
*/ | ||
public abstract Boolean providesDatatype(PROVIDER_DATA_TYPES datatype); | ||
public abstract PROVIDER_DATA_TYPES[] getAvailableDatatypes(); | ||
public abstract Geo_Data__c[] getGeodata(String addr); | ||
public abstract Geo_Data__c getGeodata(Decimal lat, Decimal lng); | ||
public abstract integer getMaxCallsForBatch(); // this tells us whether we can do 1 per apex transaction or up to 10 | ||
public abstract integer getMinDelay(); // this tells us the number of seconds we need to wait before making more calls | ||
public abstract integer getAvailableCallouts(); // returns the total number of callout "credits," or null if unlimited | ||
|
||
/* | ||
* Utility method that combines data into a single geodata. | ||
*/ | ||
public void mergeData(Geo_data__c masterData, Geo_data__c mergeData) { | ||
if (masterData.geoDataTopCandidate__c == null) masterData.geoDataTopCandidate__c = mergeData.geoDataTopCandidate__c; | ||
if (masterData.Contact__c == null) masterData.Contact__c = mergeData.Contact__c; | ||
if (masterData.geoAddressComplete__c == null) masterData.geoAddressComplete__c = mergeData.geoAddressComplete__c; | ||
if (masterData.geoAddressScorePercent__c == null) masterData.geoAddressScorePercent__c = mergeData.geoAddressScorePercent__c; | ||
if (masterData.geoLat__c == null) masterData.geoLat__c = mergeData.geoLat__c; | ||
if (masterData.geoLong__c == null) masterData.geoLong__c = mergeData.geoLong__c; | ||
if (masterData.Street__c == null) masterData.Street__c = mergeData.Street__c; | ||
if (masterData.City__c == null) masterData.City__c = mergeData.City__c; | ||
if (masterData.State__c == null) masterData.State__c = mergeData.State__c; | ||
if (masterData.County__c == null) masterData.County__c = mergeData.County__c; | ||
if (masterData.Country_Short_Code__c == null) masterData.Country_Short_Code__c = mergeData.Country_Short_Code__c; | ||
if (masterData.Country_Long_Name__c == null) masterData.Country_Long_Name__c = mergeData.Country_Long_Name__c; | ||
if (masterData.Zip_Postal_Code__c == null) masterData.Zip_Postal_Code__c = mergeData.Zip_Postal_Code__c; | ||
if (masterData.Neighborhood__c == null) masterData.Neighborhood__c = mergeData.Neighborhood__c; | ||
if (masterData.Federal_District__c == null) masterData.Federal_District__c = mergeData.Federal_District__c; | ||
if (masterData.Federal_Display_Name__c == null) masterData.Federal_Display_Name__c = mergeData.Federal_Display_Name__c; | ||
if (masterData.Upper_District__c == null) masterData.Upper_District__c = mergeData.Upper_District__c; | ||
if (masterData.Lower_District__c == null) masterData.Lower_District__c = mergeData.Lower_District__c; | ||
if (masterData.Upper_Display_Name__c == null) masterData.Upper_Display_Name__c = mergeData.Upper_Display_Name__c; | ||
if (masterData.Lower_Display_Name__c == null) masterData.Lower_Display_Name__c = mergeData.Lower_Display_Name__c; | ||
} | ||
|
||
public static String packAddress(String street, String city, String state, String postalCode) { | ||
String addr = isNotEmpty(street) ? street : ''; | ||
addr += isNotEmpty(city) ? (isNotEmpty(addr) ? (', ' + city) : city) : ''; | ||
addr += isNotEmpty(state) ? (isNotEmpty(addr) ? (', ' + state) : state) : ''; | ||
//addr += isNotEmpty(postalCode) ? (isNotEmpty(addr) ? (', ' + postalCode) : postalCode) : ''; | ||
addr += isNotEmpty(postalCode) ? (isNotEmpty(addr) ? (' ' + postalCode) : postalCode) : ''; | ||
return addr; | ||
} | ||
|
||
private static Boolean isNotEmpty(String s) { | ||
return (s != null && s != ''); | ||
} | ||
|
||
/* | ||
* Implementation used for testing. | ||
*/ | ||
private class TestGeocodingService extends GeocodingService { | ||
private PROVIDER_DATA_TYPES[] requestedDatatypes; | ||
|
||
public override Boolean providesDatatype(PROVIDER_DATA_TYPES datatype) { | ||
return datatype == PROVIDER_DATA_TYPES.GEOCODING; | ||
} | ||
|
||
public override PROVIDER_DATA_TYPES[] getAvailableDatatypes() { | ||
return new PROVIDER_DATA_TYPES[]{ PROVIDER_DATA_TYPES.GEOCODING }; | ||
} | ||
|
||
public override Geo_Data__c[] getGeodata(String addr) { | ||
Geo_Data__c gd = new Geo_data__c(); | ||
gd.geoAddressComplete__c = addr; | ||
return new Geo_Data__c[]{gd}; | ||
} | ||
|
||
public override Geo_Data__c getGeodata(Decimal lat, Decimal lng) { | ||
Geo_Data__c gd = new Geo_data__c(); | ||
gd.geoLat__c = lat; | ||
gd.geoLong__c = lng; | ||
return gd; | ||
} | ||
|
||
public override integer getMaxCallsForBatch() { return 9; } | ||
public override integer getMinDelay() { return null; } | ||
public override integer getAvailableCallouts() { return null; } | ||
} | ||
|
||
private static final String TEST_ADDRESS = '123 Main Street, Pittsburgh, PA'; | ||
public testMethod static void testGeocodingService() { | ||
|
||
geoDataCredentials__c invalidCreds = new geoDataCredentials__c(); | ||
invalidCreds.Name = 'noClass'; | ||
invalidCreds.IsActive__c = true; | ||
invalidCreds.Priority__c = 1; | ||
insert invalidCreds; | ||
|
||
geoDataCredentials__c creds = new geoDataCredentials__c(); | ||
creds.Name = TestGeocodingService.class.getName(); | ||
creds.IsActive__c = true; | ||
creds.Priority__c = 2; | ||
insert creds; | ||
|
||
GeocodingService s = getProvider( new PROVIDER_DATA_TYPES[]{ PROVIDER_DATA_TYPES.NATIONAL_LEGISLATIVE }); | ||
System.assertEquals(null, s); | ||
s = getProvider(new PROVIDER_DATA_TYPES[]{ PROVIDER_DATA_TYPES.GEOCODING }); | ||
System.assertNotEquals(null, s); | ||
Geo_data__c[] data = s.getGeodata(TEST_ADDRESS); | ||
System.assert(data != null && data.size() == 1); | ||
System.assertEquals(TEST_ADDRESS, data[0].geoAddressComplete__c); | ||
Geo_data__c oneData = s.getGeodata(1.01, -3.45); | ||
System.assert(oneData != null); | ||
} | ||
|
||
static testMethod void testPackAddress() { | ||
System.assertEquals('123 Main, San Francisco, CA', packAddress('123 Main', 'San Francisco', 'CA', null)); | ||
System.assertEquals('San Francisco, CA', packAddress(null, 'San Francisco', 'CA', '')); | ||
System.assertEquals('94105', packAddress(null, '', null, '94105')); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata"> | ||
<apiVersion>25.0</apiVersion> | ||
<status>Active</status> | ||
</ApexClass> |
Oops, something went wrong.