예제 #1
0
  /**
   * ** Gets a virtual DBRecord from the specified remote service ** @param servReq The remote web
   * service ** @return The virtual DBRecord (cannot be saved or reloaded)
   */
  @SuppressWarnings("unchecked")
  public gDBR getVirtualDBRecord(final ServiceRequest servReq) throws DBException {
    String CMD_dbget = DBFactory.CMD_dbget;
    String TAG_Response = servReq.getTagResponse();
    String TAG_Record = DBFactory.TAG_Record;
    String ATTR_command = servReq.getAttrCommand();
    String ATTR_result = servReq.getAttrResult();

    /* send request / get response */
    Document xmlDoc = null;
    try {
      xmlDoc =
          servReq.sendRequest(
              CMD_dbget,
              new ServiceRequest.RequestBody() {
                public StringBuffer appendRequestBody(StringBuffer sb, int indent) {
                  return DBRecordKey.this.toRequestXML(sb, indent);
                }
              });
    } catch (IOException ioe) {
      Print.logException("Error", ioe);
      throw new DBException("Request read error", ioe);
    }

    /* parse 'GTSResponse' */
    Element gtsResponse = xmlDoc.getDocumentElement();
    if (!gtsResponse.getTagName().equalsIgnoreCase(TAG_Response)) {
      Print.logError("Request XML does not start with '%s'", TAG_Response);
      throw new DBException("Response XML does not begin eith '" + TAG_Response + "'");
    }

    /* request command/argument */
    String cmd = StringTools.trim(gtsResponse.getAttribute(ATTR_command));
    String result = StringTools.trim(gtsResponse.getAttribute(ATTR_result));
    if (StringTools.isBlank(result)) {
      result = StringTools.trim(gtsResponse.getAttribute("type"));
    }
    if (!result.equalsIgnoreCase("success")) {
      Print.logError("Response indicates failure");
      throw new DBException("Response does not indicate 'success'");
    }

    /* Record */
    NodeList rcdList = XMLTools.getChildElements(gtsResponse, TAG_Record);
    if (rcdList.getLength() <= 0) {
      Print.logError("No 'Record' tags");
      throw new DBException("GTSResponse does not contain any 'Record' tags");
    }
    Element rcdElem = (Element) rcdList.item(0);

    /* return DBRecord */
    gDBR dbr = (gDBR) DBFactory.parseXML_DBRecord(rcdElem);
    dbr.setVirtual(true);
    return dbr;
  }
예제 #2
0
 public GoogleSig getSignature() {
   if (!this.signature_init) {
     this.signature_init = true;
     String key = this.getAuthorization();
     if (!StringTools.isBlank(key) && key.startsWith(CLIENT_ID_PREFIX)) {
       String sigKey = this.getProperties().getString(PROP_signatureKey, "");
       if (!StringTools.isBlank(sigKey)) {
         Print.logWarn("Setting SignatureKey: " + sigKey);
         this.signature = new GoogleSig(sigKey);
       } else {
         Print.logWarn("No signatureKey ...");
       }
     }
   }
   return this.signature;
 }
예제 #3
0
 private Document GetXMLDocument(String url) {
   try {
     DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
     DocumentBuilder db = dbf.newDocumentBuilder();
     InputStream input = HTMLTools.inputStream_GET(url, 5000);
     InputStreamReader reader = new InputStreamReader(input, ENCODING_UTF8);
     InputSource inSrc = new InputSource(reader);
     inSrc.setEncoding(ENCODING_UTF8);
     return db.parse(inSrc);
   } catch (ParserConfigurationException pce) {
     Print.logError("Parse error: " + pce);
     return null;
   } catch (SAXException se) {
     Print.logError("Parse error: " + se);
     return null;
   } catch (IOException ioe) {
     Print.logError("IO error: " + ioe);
     return null;
   }
 }
예제 #4
0
  /** ** Main entery point for debugging/testing */
  public static void main(String argv[]) {
    RTConfig.setCommandLineArgs(argv);
    Print.setAllOutputToStdout(true);
    Print.setEncoding(ENCODING_UTF8);
    String accountID = RTConfig.getString(ARG_ACCOUNT, "demo");
    GoogleGeocodeV2 gn = new GoogleGeocodeV2("google", null, null);

    /* reverse geocode */
    if (RTConfig.hasProperty(ARG_REVGEOCODE)) {
      GeoPoint gp = new GeoPoint(RTConfig.getString(ARG_REVGEOCODE, null));
      if (!gp.isValid()) {
        Print.logInfo("Invalid GeoPoint specified");
        System.exit(1);
      }
      Print.logInfo("Reverse-Geocoding GeoPoint: " + gp);
      Print.sysPrintln(
          "RevGeocode = " + gn.getReverseGeocode(gp, null /*localeStr*/, false /*cache*/));
      // Note: Even though the values are printed in UTF-8 character encoding, the
      // characters may not appear to be property displayed if the console display
      // does not support UTF-8.
      System.exit(0);
    }

    /* no options */
    Print.sysPrintln("No options specified");
    System.exit(1);
  }
예제 #5
0
  /** ** Main entery point for debugging/testing */
  public static void main(String argv[]) {
    RTConfig.setCommandLineArgs(argv);
    Print.setAllOutputToStdout(true);
    Print.setEncoding(ENCODING_UTF8);

    /* host */
    String host = RTConfig.getString("host", null);
    if (!StringTools.isBlank(host)) {
      HOST_PRIMARY = host;
    }

    /* GeoPoint */
    GeoPoint gp = new GeoPoint(RTConfig.getString("gp", null));
    if (!gp.isValid()) {
      Print.logInfo("Invalid GeoPoint specified");
      System.exit(1);
    }
    Print.logInfo("Reverse-Geocoding GeoPoint: " + gp);

    /* Reverse Geocoding */
    Nominatim gn = new Nominatim("nominatim", null, RTConfig.getCommandLineProperties());
    Print.sysPrintln("RevGeocode = " + gn.getReverseGeocode(gp, null /*localeStr*/));
  }
예제 #6
0
  /**
   * ** Returns true if the parent records in their respective parent tables exist. ** @return True
   * if the parent records exist.
   */
  public boolean parentsExist() throws DBException {
    DBFactory<gDBR> dbFact = this.getFactory();
    DBFieldValues myFldVals = this.getFieldValues();
    java.util.List<String> parentList = dbFact.getParentTables();
    for (String parentTable : parentList) {

      /* get parent table DBFactory */
      Print.logInfo("[%s] Parent table: %s", this.getTableName(), parentTable);
      DBFactory parentFact = DBFactory.getFactoryByName(parentTable);
      if (parentFact == null) {
        Print.logError("Unexpected error finding parent table: " + parentTable);
        return false;
      }

      /* create parent record key with fields from this key */
      DBRecordKey parentKey = parentFact.createKey(); // an empty key
      DBField parentKeyFlds[] = parentFact.getKeyFields();
      for (DBField pkf : parentKeyFlds) {
        String pfn = pkf.getName();

        /* get this DBField */
        DBField myKeyFld = this.getField(pfn);
        if (myKeyFld == null) {
          Print.logError("Unexpected error finding field: [" + this.getTableName() + "] " + pfn);
          return false;
        }

        /* get parent key field value */
        Object pkv = myFldVals.getFieldValue(pfn);
        if (pkv == null) {
          Print.logError("Unexpected error finding parent field: [" + parentTable + "] " + pfn);
          return false;
        }
        if (myKeyFld.isDefaultValue(pkv)) {
          Print.logInfo("This key contains a global value, skipping parent check: " + parentTable);
          parentKey = null;
          break;
        }
        parentKey.setFieldValue(pfn, pkv);
      }

      /* check parent existence */
      if ((parentKey != null) && !parentKey.exists()) {
        Print.logError("Parent record does not exist: [" + parentTable + "] " + parentKey);
        return false;
      }
    }
    return true;
  }
예제 #7
0
  /**
   * ** Gets the DBRecord associated with this key ** @param reload If the record should be reloaded
   * before it is returned ** @return The DBRecord associated with this key
   */
  public gDBR getDBRecord(boolean reload) {
    // returns null if there is an error

    /* create record */
    if (this.record == null) {
      try {
        this.record = DBRecord._createDBRecord(this);
      } catch (DBException dbe) {
        // Implementation error (this should never occur)
        // an NPE will likely follow
        Print.logStackTrace("Implementation error - cant' create DB record", dbe);
        return null;
      }
    }

    /* reload */
    if (reload) {
      // 'reload' is ignored if key does not exist
      this.record.reload();
    }

    /* return record (never null) */
    return this.record;
  }
예제 #8
0
  /**
   * ** Return the 'WHERE' clause for this key [CHECK] ** @param altIndexName The alternate index
   * name. If null or blank, uses ** primary keys instead ** @param whereKeyType The where key type.
   * One of the constants from DBWhere ** @return The 'WHERE' clause for this key
   */
  protected String _getWhereClause(
      String altIndexName, int whereKeyType) // boolean fullKeyRequired)
      throws DBException {

    /* key fields */
    boolean usePrimaryKey = StringTools.isBlank(altIndexName);
    DBField keyFlds[] = usePrimaryKey ? this.getKeyFields() : this.getAltKeyFields(altIndexName);
    if (ListTools.isEmpty(keyFlds)) {
      throw new DBException("No keys found!");
    }

    /* WHERE */
    DBWhere dwh = new DBWhere(this.getFactory());
    DBFieldValues fldVals = this.getFieldValues();
    int keyCnt = 0;
    boolean hasPartialKey = false;
    for (int i = 0; i < keyFlds.length; i++) {
      String fldName = keyFlds[i].getName();
      if (fldVals.hasFieldValue(fldName)) {
        if (!hasPartialKey || (whereKeyType == DBWhere.KEY_PARTIAL_ALL)) {
          String fev = dwh.EQ(fldName, fldVals.getFieldValueAsString(fldName));
          if (keyCnt > 0) {
            dwh.append(dwh.AND_(fev));
          } else {
            dwh.append(fev);
          }
          keyCnt++;
        } else {
          // whereKeyType == DBWhere.KEY_PARTIAL_FIRST, and we found a subsequent key
          String m =
              "Additional partial key in 'WHERE' clause! ["
                  + this.getTableName()
                  + "."
                  + fldName
                  + "]";
          // throw new DBException(m); // TODO:
          Print.logWarn("******************************************************************");
          Print.logWarn(m);
          // Print.logWarn(StringTools.join(keyFlds,","));
          // Print.logStackTrace(m);
          Print.logWarn("******************************************************************");
        }
      } else if ((i == 0) && (whereKeyType != DBWhere.KEY_PARTIAL_ALL_EMPTY)) { //
        // missing first key
        if (keyFlds[i].isAutoIncrement()) {
          // first key is an "auto_increment" and it is not present
          // assume that we are expecting the DB server to create this value for us, thus the key
          // dow not exist
          // However, there is nothing we can do about this here.
          String m =
              "First key field for 'WHERE' clause is 'auto_increment' and field is not present ["
                  + this.getTableName()
                  + "."
                  + fldName
                  + "]";
          throw new DBException(m);
        } else {
          String m =
              "Missing first key field for 'WHERE' clause! ["
                  + this.getTableName()
                  + "."
                  + fldName
                  + "]";
          throw new DBException(m);
        }
      } else if (whereKeyType == DBWhere.KEY_FULL) {
        // missing a key when all keys are required
        String m = "Missing key for 'WHERE' clause! [" + this.getTableName() + "." + fldName + "]";
        throw new DBException(m);
      } else {
        // only a portion of the key has been specified.
        // This is a common occurance deleting an Account/Device with sub-dependencies
        // Print.logWarn("Key field not specified: " + this.getTableName() + "." + fldName);
        hasPartialKey = true;
      }
    }

    return (keyCnt > 1) ? dwh.WHERE(dwh.toString()) : dwh.WHERE_(dwh.toString());
  }
예제 #9
0
  /**
   * ** Returns true if the specified key attribute exists in the table ** @param altIndexName The
   * alternate index name, or null to use the primary index ** @param whereKeyType The partial key
   * match type ** @return True if the specified key attribute exists in the table, false otherwise
   */
  protected boolean _exists(String altIndexName, int whereKeyType)
      throws SQLException, DBException {

    /* key fields */
    boolean usePrimaryKey = StringTools.isBlank(altIndexName);
    DBField kfld[] = usePrimaryKey ? this.getKeyFields() : this.getAltKeyFields(altIndexName);
    if (ListTools.isEmpty(kfld)) {
      throw new DBException("No keys found!");
    }

    /* check last key for "auto_increment" */
    if (whereKeyType == DBWhere.KEY_FULL) {
      DBField lastField = kfld[kfld.length - 1];
      if (lastField.isAutoIncrement()
          && !this.getFieldValues().hasFieldValue(lastField.getName())) {
        // full key requested and last key is auto_increment, which is missing
        return false;
      }
    }

    // DBSelect: SELECT <Keys> FROM <TableName> <KeyWhere>
    String firstKey = kfld[0].getName();
    DBSelect<gDBR> dsel = new DBSelect<gDBR>(this.getFactory());
    dsel.setSelectedFields(firstKey);
    dsel.setWhere(this._getWhereClause(altIndexName, whereKeyType));

    /* get keyed record */
    DBConnection dbc = null;
    Statement stmt = null;
    ResultSet rs = null;
    boolean exists = false;
    try {
      dbc = DBConnection.getDefaultConnection();
      stmt = dbc.execute(dsel.toString()); // may throw DBException
      rs = stmt.getResultSet();
      exists = rs.next();
    } catch (SQLException sqe) {
      if (sqe.getErrorCode() == DBFactory.SQLERR_TABLE_NOTLOCKED) {
        // MySQL: This case has been seen on rare occasions.  Not sure what causes it.
        Print.logError("SQL Lock Error: " + sqe);
        Print.logError("Hackery! Forcing lock on table: " + this.getTableName());
        if (DBProvider.lockTableForRead(this.getTableName(), true)) { // may throw DBException
          stmt = dbc.execute(dsel.toString()); // may throw SQLException, DBException
          rs = stmt.getResultSet(); // SQLException
          exists = rs.next(); // SQLException
          DBProvider.unlockTables(); // DBException
        }
      } else {
        throw sqe;
      }
    } finally {
      if (rs != null) {
        try {
          rs.close();
        } catch (Throwable t) {
        }
      }
      if (stmt != null) {
        try {
          stmt.close();
        } catch (Throwable t) {
        }
      }
      DBConnection.release(dbc);
    }

    return exists;
  }
예제 #10
0
  /**
   * ** Returns a ReverseGeocode instance containing address information ** @param gp The GeoPoint
   * ** @return The ReverseGeocode instance
   */
  private ReverseGeocode getAddressReverseGeocode(GeoPoint gp, String localeStr) {

    /* URL */
    String url = this.getAddressReverseGeocodeURL(gp);
    Print.logInfo("Address URL: " + url);

    /* create XML document */
    Document xmlDoc = GetXMLDocument(url);
    if (xmlDoc == null) {
      return null;
    }

    /* create ReverseGeocode response */
    Element reversegeocode = xmlDoc.getDocumentElement();
    if (!reversegeocode.getTagName().equalsIgnoreCase(TAG_reversegeocode)) {
      return null;
    }

    /* init */
    String address_val = null; // null address
    String house_val = null; // house number
    String road_val = null; // street name
    String city_val = null; // city name
    String county_val = null; // county name
    String state_val = null; // state/province
    String postcode_val = null; // postal code
    String country_name_val = null; // country name
    String country_code_val = null; // country code

    // full address
    NodeList resultList = XMLTools.getChildElements(reversegeocode, TAG_result);
    for (int r = 0; r < resultList.getLength(); r++) {
      Element result = (Element) resultList.item(r);
      // String osmType = XMLTools.getAttribute(result, ATTR_osm_type, null, false);
      address_val = XMLTools.getNodeText(result, " ", false);
      break; // only the first element
    }

    // address components
    NodeList addresspartsList = XMLTools.getChildElements(reversegeocode, TAG_addressparts);
    for (int a = 0; (a < addresspartsList.getLength()); a++) {
      Element addressparts = (Element) addresspartsList.item(a);
      NodeList addresspartsChildren = addressparts.getChildNodes();
      for (int ac = 0; ac < addresspartsChildren.getLength(); ac++) {
        Node child = addresspartsChildren.item(ac);
        if (!(child instanceof Element)) {
          continue;
        }
        Element elem = (Element) child;
        String elemName = elem.getNodeName();
        if (elemName.equalsIgnoreCase(TAG_house)) {
          house_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_tram)) {
          // ignore
        } else if (elemName.equalsIgnoreCase(TAG_road)) {
          road_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_residential)) {
          // ignore
        } else if (elemName.equalsIgnoreCase(TAG_village)) {
          // ignore
        } else if (elemName.equalsIgnoreCase(TAG_town)) {
          if (StringTools.isBlank(city_val)) {
            city_val = XMLTools.getNodeText(elem, " ", false);
          }
        } else if (elemName.equalsIgnoreCase(TAG_city)) {
          city_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_county)) {
          county_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_postcode)) {
          postcode_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_state)) {
          state_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_country)) {
          country_name_val = XMLTools.getNodeText(elem, " ", false);
        } else if (elemName.equalsIgnoreCase(TAG_country_code)) {
          country_code_val = StringTools.trim(XMLTools.getNodeText(elem, " ", false)).toUpperCase();
        } else {
          // elemName unrecognized
        }
      }
      break; // only the first element
    }

    /* populate ReverseGeocode instance */
    ReverseGeocode rg = new ReverseGeocode();
    StringBuffer addr = new StringBuffer();
    // house number /road
    if (!StringTools.isBlank(house_val)) {
      addr.append(house_val);
      if (!StringTools.isBlank(road_val)) {
        addr.append(" ");
        addr.append(road_val);
        rg.setStreetAddress(house_val + " " + road_val);
      } else {
        rg.setStreetAddress(house_val);
      }
    } else if (!StringTools.isBlank(road_val)) {
      addr.append(road_val);
      rg.setStreetAddress(road_val);
    }
    // city/county
    if (!StringTools.isBlank(city_val)) {
      if (addr.length() > 0) {
        addr.append(", ");
      }
      addr.append(city_val);
      rg.setCity(city_val);
    }
    if (!StringTools.isBlank(county_val)) {
      if (StringTools.isBlank(city_val)) {
        // "city" not provided, at least include the "county"
        if (addr.length() > 0) {
          addr.append(", ");
        }
        addr.append("[").append(county_val).append("]");
      }
      // rg.setCounty(county_val);
    }
    // state/province
    if (!StringTools.isBlank(state_val)) {
      if (addr.length() > 0) {
        addr.append(", ");
      }
      addr.append(state_val);
      rg.setStateProvince(state_val);
      if (!StringTools.isBlank(postcode_val)) {
        addr.append(" ").append(postcode_val);
        rg.setPostalCode(postcode_val);
      }
    } else {
      if (!StringTools.isBlank(postcode_val)) {
        if (addr.length() > 0) {
          addr.append(", ");
        }
        addr.append(postcode_val);
        rg.setPostalCode(postcode_val);
      }
    }
    // country
    if (!StringTools.isBlank(country_code_val)) {
      if (country_code_val.equalsIgnoreCase("US")) {
        // if (addr.length() > 0) { addr.append(", "); }
        // addr.append("USA");
      } else if (!StringTools.isBlank(country_name_val)) {
        if (addr.length() > 0) {
          addr.append(", ");
        }
        addr.append(country_name_val);
      } else {
        if (addr.length() > 0) {
          addr.append(", ");
        }
        addr.append(country_code_val);
      }
      rg.setCountryCode(country_code_val);
    }
    // full address
    rg.setFullAddress(addr.toString());

    return rg;
  }
예제 #11
0
  // http://code.google.com/apis/maps/documentation/geocoding/index.html
  public GeoPoint getGeocode(String address, String country) {

    /* URL */
    String url = this.getGeoPointGeocodeURL(address, country);
    Print.logDebug("Google GC URL: " + url);

    /* create XML document */
    Document xmlDoc = GetXMLDocument(url, this.getGeocodeTimeout());
    if (xmlDoc == null) {
      return null;
    }

    /* vars */
    String errCode = null;
    GeoPoint geoPoint = null;

    /* parse "xml" */
    Element kml = xmlDoc.getDocumentElement();
    if (kml.getTagName().equalsIgnoreCase(TAG_kml)) {
      NodeList ResponseList = XMLTools.getChildElements(kml, TAG_Response);
      for (int g = 0; (g < ResponseList.getLength()); g++) {
        Element response = (Element) ResponseList.item(g);
        NodeList responseNodes = response.getChildNodes();
        for (int n = 0; n < responseNodes.getLength(); n++) {
          Node responseNode = responseNodes.item(n);
          if (!(responseNode instanceof Element)) {
            continue;
          }
          Element responseElem = (Element) responseNode;
          String responseNodeName = responseElem.getNodeName();
          if (responseNodeName.equalsIgnoreCase(TAG_name)) {
            // <name>1600 Amphitheatre Parkway, Mountain View, CA</name>
          } else if (responseNodeName.equalsIgnoreCase(TAG_Status)) {
            // <Status> ... </Status>
            NodeList statusNodes = responseElem.getChildNodes();
            for (int st = 0; st < statusNodes.getLength(); st++) {
              Node statusNode = statusNodes.item(st);
              if (!(statusNode instanceof Element)) {
                continue;
              }
              Element statusElem = (Element) statusNode;
              String statusNodeName = statusElem.getNodeName();
              if (statusNodeName.equalsIgnoreCase(TAG_code)) {
                errCode = StringTools.trim(GoogleGeocodeV2.GetNodeText(statusElem)); // expect "200"
                break; // we only care about the 'code'
              }
            }
          } else if (responseNodeName.equalsIgnoreCase(TAG_Placemark)) {
            // <Placemark> ... </Placemark>
            String id = responseElem.getAttribute(ATTR_id);
            if ((id != null) && id.equals("p1")) {
              NodeList placemarkNodes = responseElem.getChildNodes();
              for (int pm = 0; (geoPoint == null) && (pm < placemarkNodes.getLength()); pm++) {
                Node placemarkNode = placemarkNodes.item(pm);
                if (!(placemarkNode instanceof Element)) {
                  continue;
                }
                Element placemarkElem = (Element) placemarkNode;
                String placemarkNodeName = placemarkElem.getNodeName();
                if (placemarkNodeName.equalsIgnoreCase(TAG_Point)) {
                  NodeList pointNodes = placemarkElem.getChildNodes();
                  for (int ptn = 0; (geoPoint == null) && (ptn < pointNodes.getLength()); ptn++) {
                    Node pointNode = pointNodes.item(ptn);
                    if (!(pointNode instanceof Element)) {
                      continue;
                    }
                    Element pointElem = (Element) pointNode;
                    String pointNodeName = pointElem.getNodeName();
                    if (pointNodeName.equalsIgnoreCase(TAG_coordinates)) {
                      String ll[] = StringTools.split(GoogleGeocodeV2.GetNodeText(pointElem), ',');
                      if (ll.length >= 2) {
                        double lon = StringTools.parseDouble(ll[0], 0.0); // longitude is first
                        double lat = StringTools.parseDouble(ll[1], 0.0);
                        if (GeoPoint.isValid(lat, lon)) {
                          geoPoint = new GeoPoint(lat, lon);
                          break; // we only care about the 'GeoPoint'
                        }
                      }
                    }
                  }
                }
              }
            } else {
              // Print.logInfo("Skipping Placemark ID = %s", id);
            }
          }
        }
      }
    }

    /* create address */
    if (geoPoint != null) {
      // GeoPoint found
      return geoPoint;
    }

    /* check for Google reverse-geocode limit exceeded */
    if ((errCode != null) && errCode.equals("620")) {
      Print.logError("!!!! Google Reverse-Geocode Limit Exceeded [Error 620] !!!!");
    }

    /* no reverse-geocode available */
    return null;
  }
예제 #12
0
  /* return reverse-geocode using nearest address */
  public ReverseGeocode getAddressReverseGeocode(GeoPoint gp, String localeStr, boolean cache) {

    /* check for failover mode */
    if (this.isReverseGeocodeFailoverMode()) {
      ReverseGeocodeProvider frgp = this.getFailoverReverseGeocodeProvider();
      return frgp.getReverseGeocode(gp, localeStr, cache);
    }

    /* URL */
    String url = this.getAddressReverseGeocodeURL(gp, localeStr);
    Print.logDebug("Google RG URL: " + url);
    // byte xmlBytes[] = HTMLTools.readPage(url);

    /* create XML document */
    Document xmlDoc = GetXMLDocument(url, this.getReverseGeocodeTimeout());
    if (xmlDoc == null) {
      return null;
    }

    /* vars */
    String errCode = null;
    String address = null;

    /* parse "xml" */
    Element kml = xmlDoc.getDocumentElement();
    if (kml.getTagName().equalsIgnoreCase(TAG_kml)) {
      NodeList ResponseList = XMLTools.getChildElements(kml, TAG_Response);
      for (int g = 0; (g < ResponseList.getLength()); g++) {
        Element response = (Element) ResponseList.item(g);
        NodeList responseNodes = response.getChildNodes();
        for (int n = 0; n < responseNodes.getLength(); n++) {
          Node responseNode = responseNodes.item(n);
          if (!(responseNode instanceof Element)) {
            continue;
          }
          Element responseElem = (Element) responseNode;
          String responseNodeName = responseElem.getNodeName();
          if (responseNodeName.equalsIgnoreCase(TAG_name)) {
            // <name>40.479581,-117.773438</name>
          } else if (responseNodeName.equalsIgnoreCase(TAG_Status)) {
            // <Status> ... </Status>
            NodeList statusNodes = responseElem.getChildNodes();
            for (int st = 0; st < statusNodes.getLength(); st++) {
              Node statusNode = statusNodes.item(st);
              if (!(statusNode instanceof Element)) {
                continue;
              }
              Element statusElem = (Element) statusNode;
              String statusNodeName = statusElem.getNodeName();
              if (statusNodeName.equalsIgnoreCase(TAG_code)) {
                errCode = StringTools.trim(GoogleGeocodeV2.GetNodeText(statusElem)); // expect "200"
                break; // we only care about the 'code'
              }
            }
          } else if (responseNodeName.equalsIgnoreCase(TAG_Placemark)) {
            // <Placemark> ... </Placemark>
            String id = responseElem.getAttribute(ATTR_id);
            if ((id != null) && id.equals("p1")) {
              NodeList placemarkNodes = responseElem.getChildNodes();
              for (int pm = 0; pm < placemarkNodes.getLength(); pm++) {
                Node placemarkNode = placemarkNodes.item(pm);
                if (!(placemarkNode instanceof Element)) {
                  continue;
                }
                Element placemarkElem = (Element) placemarkNode;
                String placemarkNodeName = placemarkElem.getNodeName();
                if (placemarkNodeName.equalsIgnoreCase(TAG_address)) {
                  address = GoogleGeocodeV2.GetNodeText(placemarkElem);
                  break; // we only care about the 'address'
                }
              }
            } else {
              // Print.logInfo("Skipping Placemark ID = %s", id);
            }
          }
        }
      }
    }

    /* create address */
    if (FAILOVER_DEBUG) {
      errCode = "620";
    } else if (!StringTools.isBlank(address)) {
      // address found
      ReverseGeocode rg = new ReverseGeocode();
      rg.setFullAddress(address);
      return rg;
    }

    /* check for Google reverse-geocode limit exceeded */
    if ((errCode != null) && errCode.equals("620")) {
      Print.logError("!!!! Google Reverse-Geocode Limit Exceeded [Error 620] !!!!");
      if (this.hasFailoverReverseGeocodeProvider()) {
        this.startReverseGeocodeFailoverMode();
        ReverseGeocodeProvider frgp = this.getFailoverReverseGeocodeProvider();
        Print.logWarn("Failing over to '" + frgp.getName() + "'");
        return frgp.getReverseGeocode(gp, localeStr, cache);
      }
    }

    /* no reverse-geocode available */
    return null;
  }