EVOLUTION-MANAGER
Edit File: ogrgmedatasource.cpp
/****************************************************************************** * $Id$ * * Project: Google Maps Engine API Driver * Purpose: OGRGMEDataSource Implementation. * Author: Frank Warmerdam <warmerdam@pobox.com> * (derived from GFT driver by Even) * ****************************************************************************** * Copyright (c) 2013, Frank Warmerdam <warmerdam@pobox.com> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "ogr_gme.h" #include "ogrgmejson.h" #include "cpl_multiproc.h" CPL_CVSID("$Id$"); #define GDAL_API_KEY "AIzaSyA_2h1_wXMOLHNSVeo-jf1ACME-M1XMgP0" #define GME_TABLE_SCOPE_RO "https://www.googleapis.com/auth/mapsengine.readonly" #define GME_TABLE_SCOPE "https://www.googleapis.com/auth/mapsengine" /************************************************************************/ /* OGRGMEDataSource() */ /************************************************************************/ OGRGMEDataSource::OGRGMEDataSource() { papoLayers = NULL; nLayers = 0; pszName = NULL; bReadWrite = FALSE; bUseHTTPS = FALSE; bMustCleanPersistant = FALSE; nRetries = 0; } /************************************************************************/ /* ~OGRGMEDataSource() */ /************************************************************************/ OGRGMEDataSource::~OGRGMEDataSource() { for( int i = 0; i < nLayers; i++ ) delete papoLayers[i]; CPLFree( papoLayers ); if (bMustCleanPersistant) { char** papszOptions = CSLAddString(NULL, CPLSPrintf("CLOSE_PERSISTENT=GME:%p", this)); CPLHTTPFetch( GetAPIURL(), papszOptions); CSLDestroy(papszOptions); } CPLFree( pszName ); } /************************************************************************/ /* TestCapability() */ /************************************************************************/ int OGRGMEDataSource::TestCapability( const char * pszCap ) { if( EQUAL(pszCap,ODsCCreateLayer) ) return TRUE; //else if( bReadWrite && EQUAL(pszCap,ODsCDeleteLayer) ) // return TRUE; //else return FALSE; } /************************************************************************/ /* GetLayer() */ /************************************************************************/ OGRLayer *OGRGMEDataSource::GetLayer( int iLayer ) { if( iLayer < 0 || iLayer >= nLayers ) return NULL; else return papoLayers[iLayer]; } /************************************************************************/ /* OGRGMEGetOptionValue() */ /************************************************************************/ CPLString OGRGMEGetOptionValue(const char* pszFilename, const char* pszOptionName) { CPLString osOptionName(pszOptionName); osOptionName += "="; const char* pszOptionValue = strstr(pszFilename, osOptionName); if (!pszOptionValue) return ""; CPLString osOptionValue(pszOptionValue + strlen(osOptionName)); const char* pszSpace = strchr(osOptionValue.c_str(), ' '); if (pszSpace) osOptionValue.resize(pszSpace - osOptionValue.c_str()); return osOptionValue; } /************************************************************************/ /* Open() */ /************************************************************************/ int OGRGMEDataSource::Open( const char * pszFilename, int bUpdateIn) { if (!EQUALN(pszFilename, "GME:", 4)) return FALSE; bReadWrite = bUpdateIn; pszName = CPLStrdup( pszFilename ); osAuth = OGRGMEGetOptionValue(pszFilename, "auth"); if (osAuth.size() == 0) osAuth = CPLGetConfigOption("GME_AUTH", ""); osRefreshToken = OGRGMEGetOptionValue(pszFilename, "refresh"); if (osRefreshToken.size() == 0) osRefreshToken = CPLGetConfigOption("GME_REFRESH_TOKEN", ""); osAPIKey = CPLGetConfigOption("GME_APIKEY", GDAL_API_KEY); CPLString osTables = OGRGMEGetOptionValue(pszFilename, "tables"); osProjectId = OGRGMEGetOptionValue(pszFilename, "projectId"); osSelect = OGRGMEGetOptionValue(pszFilename, "select"); osWhere = OGRGMEGetOptionValue(pszFilename, "where"); CPLString osBatchPatchSize; osBatchPatchSize = OGRGMEGetOptionValue(pszFilename, "batchpatchsize"); if (osBatchPatchSize.size() == 0) { osBatchPatchSize = CPLGetConfigOption("GME_BATCH_PATCH_SIZE","50"); } int iBatchPatchSize = atoi( osBatchPatchSize.c_str() ); bUseHTTPS = TRUE; osAccessToken = OGRGMEGetOptionValue(pszFilename, "access"); if (osAccessToken.size() == 0) osAccessToken = CPLGetConfigOption("GME_ACCESS_TOKEN",""); if (osAccessToken.size() == 0 && osRefreshToken.size() > 0) { osAccessToken.Seize(GOA2GetAccessToken(osRefreshToken, GME_TABLE_SCOPE)); // TODO if (osAccessToken.size() == 0) { CPLDebug( "GME", "Cannot get access token"); return FALSE; } } if (osAccessToken.size() == 0 && osAuth.size() > 0) { osRefreshToken.Seize(GOA2GetRefreshToken(osAuth, GME_TABLE_SCOPE)); // TODO if (osRefreshToken.size() == 0) CPLDebug( "GME", "Cannot get refresh token"); return FALSE; } if ((osAccessToken.size() ==0) && (osTables.size() == 0)) { CPLError(CE_Failure, CPLE_AppDefined, "Unauthenticated access requires explicit tables= parameter"); return FALSE; } osTraceToken = OGRGMEGetOptionValue(pszFilename, "trace"); if (osTraceToken.size() == 0) { CPLDebug("GME", "Looking for GME_TRACE_TOKEN"); osTraceToken = CPLGetConfigOption("GME_TRACE_TOKEN", ""); } if (osTraceToken.size() != 0) { CPLDebug("GME", "Found trace token %s", osTraceToken.c_str()); } if (osTables.size() != 0) { char** papszTables = CSLTokenizeString2(osTables, ",", 0); for(int i=0;papszTables && papszTables[i];i++) { papoLayers = (OGRLayer**) CPLRealloc(papoLayers, (nLayers + 1) * sizeof(OGRLayer*)); OGRGMELayer *poGMELayer = new OGRGMELayer(this, papszTables[i]); poGMELayer->SetBatchPatchSize(iBatchPatchSize); if (poGMELayer->GetLayerDefn()) { papoLayers[nLayers ++] = poGMELayer; } else { delete poGMELayer; } } CSLDestroy(papszTables); if ( nLayers == 0 ) { CPLError(CE_Failure, CPLE_AppDefined, "Could not find any tables."); return FALSE; } CPLDebug("GME", "Found %d layers", nLayers); return TRUE; } else if (osProjectId.size() != 0) { CPLDebug("GME", "We have a projectId: %s. Use CreateLayer to create tables.", osProjectId.c_str()); return TRUE; } CPLDebug("GME", "No table no project, giving up!"); return FALSE; } /************************************************************************/ /* CreateLayer() */ /************************************************************************/ OGRLayer *OGRGMEDataSource::CreateLayer( const char *pszName, CPL_UNUSED OGRSpatialReference *poSpatialRef, OGRwkbGeometryType eGType, char ** papszOptions ) { if (!bReadWrite) { CPLError(CE_Failure, CPLE_AppDefined, "Operation not available in read-only mode"); return NULL; } if (osAccessToken.size() == 0) { CPLError(CE_Failure, CPLE_AppDefined, "Operation not available in unauthenticated mode"); return NULL; } if ((CSLFetchNameValue( papszOptions, "projectId" ) == NULL) && (osProjectId.size() != 0)) { papszOptions = CSLAddNameValue( papszOptions, "projectId", osProjectId.c_str() ); } osTraceToken = OGRGMEGetOptionValue(pszName, "trace"); if (osTraceToken.size() == 0) { osTraceToken = CPLGetConfigOption("GME_TRACE_TOKEN", ""); } if (osTraceToken.size() != 0) { CPLDebug("GME", "Found trace token %s", osTraceToken.c_str()); } OGRGMELayer* poLayer = new OGRGMELayer(this, pszName, papszOptions); poLayer->SetGeometryType(eGType); papoLayers = (OGRLayer**) CPLRealloc(papoLayers, (nLayers + 1) * sizeof(OGRLayer*)); papoLayers[nLayers ++] = poLayer; return poLayer; } /************************************************************************/ /* GetAPIURL() */ /************************************************************************/ const char* OGRGMEDataSource::GetAPIURL() const { const char* pszAPIURL = CPLGetConfigOption("GME_API_URL", NULL); if (pszAPIURL) return pszAPIURL; else if (bUseHTTPS) return "https://www.googleapis.com/mapsengine/v1"; else return "http://www.googleapis.com/mapsengine/v1"; } /************************************************************************/ /* AddHTTPOptions() */ /************************************************************************/ void OGRGMEDataSource::AddHTTPOptions(CPLStringList &oOptions) { bMustCleanPersistant = TRUE; if (strlen(osAccessToken) > 0) oOptions.AddString( CPLSPrintf("HEADERS=Authorization: Bearer %s", osAccessToken.c_str())); oOptions.AddString(CPLSPrintf("PERSISTENT=GME:%p", this)); } /************************************************************************/ /* MakeRequest() */ /************************************************************************/ CPLHTTPResult * OGRGMEDataSource::MakeRequest(const char *pszRequest, const char *pszMoreOptions) { /* -------------------------------------------------------------------- */ /* Provide the API Key - used to rate limit access (see */ /* GME_APIKEY config) */ /* -------------------------------------------------------------------- */ CPLString osQueryFields; osQueryFields += "key="; osQueryFields += osAPIKey; if (pszMoreOptions) osQueryFields += pszMoreOptions; /* -------------------------------------------------------------------- */ /* Collect the header options. */ /* -------------------------------------------------------------------- */ CPLStringList oOptions; oOptions.AddString("CUSTOMREQUEST=GET"); AddHTTPOptions(oOptions); /* -------------------------------------------------------------------- */ /* Build URL */ /* -------------------------------------------------------------------- */ CPLString osURL = GetAPIURL(); osURL += "/"; osURL += pszRequest; if (osURL.find("?") == std::string::npos) { osURL += "?"; } else { osURL += "?"; } osURL += osQueryFields; // Trace the request if we have a tracing token if (osTraceToken.size() != 0) { CPLDebug("GME", "Using trace token %s", osTraceToken.c_str()); osURL += "&trace="; osURL += osTraceToken; } CPLDebug( "GME", "Sleep for 1s to try and avoid qps limiting errors."); CPLSleep( 1.0 ); CPLHTTPResult * psResult = CPLHTTPFetch(osURL, oOptions); /* -------------------------------------------------------------------- */ /* Check for some error conditions and report. HTML Messages */ /* are transformed info failure. */ /* -------------------------------------------------------------------- */ if (psResult && psResult->pszContentType && strncmp(psResult->pszContentType, "text/html", 9) == 0) { CPLDebug( "GME", "MakeRequest HTML Response: %s", psResult->pabyData ); CPLError(CE_Failure, CPLE_AppDefined, "HTML error page returned by server"); if (nRetries < 5) { CPLDebug("GME", "Sleeping 30s and retrying"); nRetries ++; CPLSleep( 30.0 ); psResult = MakeRequest(pszRequest, pszMoreOptions); if (psResult) CPLDebug( "GME", "Got a result after %d retries", nRetries ); else CPLDebug( "GME", "Didn't get a result after %d retries", nRetries ); nRetries--; } else { CPLDebug("GME", "I've waited too long on GME. Giving up!"); CPLHTTPDestroyResult(psResult); psResult = NULL; } return psResult; } if (psResult && psResult->pszErrBuf != NULL) { CPLDebug( "GME", "MakeRequest Error Message: %s", psResult->pszErrBuf ); CPLDebug( "GME", "error doc:\n%s\n", psResult->pabyData ? (const char*)psResult->pabyData : "null"); json_object *error_response = OGRGMEParseJSON((const char *) psResult->pabyData); CPLHTTPDestroyResult(psResult); psResult = NULL; if( error_response != NULL ) { json_object *error_doc = json_object_object_get(error_response, "error"); json_object *errors_doc = json_object_object_get(error_doc, "errors"); array_list *errors_array = json_object_get_array(errors_doc); int nErrors = array_list_length(errors_array); for (int i = 0; i < nErrors; i++) { json_object *error_obj = (json_object *)array_list_get_idx(errors_array, i); const char* reason = OGRGMEGetJSONString(error_obj, "reason", ""); const char* domain = OGRGMEGetJSONString(error_obj, "domain", ""); const char* message = OGRGMEGetJSONString(error_obj, "message", ""); const char* locationType = OGRGMEGetJSONString(error_obj, "locationType", ""); const char* location = OGRGMEGetJSONString(error_obj, "location", ""); if ((nRetries < 10) && EQUAL(reason, "rateLimitExceeded")) { // Sleep nRetries * 1.0s and retry nRetries ++; CPLDebug( "GME", "Got a %s (%d) times.", reason, nRetries ); CPLDebug( "GME", "Sleep for %2.2f to try and avoid qps limiting errors.", 1.0 * nRetries ); CPLSleep( 1.0 * nRetries ); psResult = MakeRequest(pszRequest, pszMoreOptions); if (psResult) CPLDebug( "GME", "Got a result after %d retries", nRetries ); else CPLDebug( "GME", "Didn't get a result after %d retries", nRetries ); nRetries = 0; } else if (EQUAL(reason, "authError")) { CPLDebug( "GME", "Failed to GET %s: %s", pszRequest, message ); CPLError( CE_Failure, CPLE_OpenFailed, "GME: %s", message); } else if (EQUAL(reason, "backendError")) { CPLDebug( "GME", "Backend error retrying: GET %s: %s", pszRequest, message ); psResult = MakeRequest(pszRequest, pszMoreOptions); } else { int code = 444; json_object *code_child = json_object_object_get(error_doc, "code"); if (code_child != NULL ) code = json_object_get_int(code_child); CPLDebug( "GME", "MakeRequest Error for %s: %s:%d", pszRequest, reason, code); CPLError( CE_Failure, CPLE_AppDefined, "GME: %s %s %s: %s - %s", domain, reason, locationType, location, message ); } } json_object_put(error_response); } return psResult; } else if (psResult && psResult->nStatus != 0) { CPLDebug( "GME", "MakeRequest Error Status:%d", psResult->nStatus ); } return psResult; } /************************************************************************/ /* AddHTTPPostOptions() */ /************************************************************************/ void OGRGMEDataSource::AddHTTPPostOptions(CPLStringList &oOptions) { bMustCleanPersistant = TRUE; if (strlen(osAccessToken) > 0) oOptions.AddString( CPLSPrintf("HEADERS=Content-type: application/json\n" "Authorization: Bearer %s", osAccessToken.c_str())); oOptions.AddString(CPLSPrintf("PERSISTENT=GME:%p", this)); } /************************************************************************/ /* PostRequest() */ /************************************************************************/ CPLHTTPResult * OGRGMEDataSource::PostRequest(const char *pszRequest, const char *pszBody) { /* -------------------------------------------------------------------- */ /* Provide the API Key - used to rate limit access (see */ /* GME_APIKEY config) */ /* -------------------------------------------------------------------- */ CPLString osQueryFields; osQueryFields += "key="; osQueryFields += osAPIKey; /* -------------------------------------------------------------------- */ /* Collect the header options. */ /* -------------------------------------------------------------------- */ CPLStringList oOptions; oOptions.AddString("CUSTOMREQUEST=POST"); CPLString osPostFields = "POSTFIELDS="; osPostFields += pszBody; oOptions.AddString(osPostFields); AddHTTPPostOptions(oOptions); /* -------------------------------------------------------------------- */ /* Build URL */ /* -------------------------------------------------------------------- */ CPLString osURL = GetAPIURL(); osURL += "/"; osURL += pszRequest; if (osURL.find("?") == std::string::npos) { osURL += "?"; } else { osURL += "?"; } osURL += osQueryFields; // Trace the request if we have a tracing token if (osTraceToken.size() != 0) { CPLDebug("GME", "Using trace token %s", osTraceToken.c_str()); osURL += "&trace="; osURL += osTraceToken; } CPLDebug( "GME", "Sleep for 1s to try and avoid qps limiting errors."); CPLSleep( 1.0 ); CPLDebug( "GME", "Posting to %s.", osURL.c_str()); CPLHTTPResult * psResult = CPLHTTPFetch(osURL, oOptions); /* -------------------------------------------------------------------- */ /* Check for some error conditions and report. HTML Messages */ /* are transformed info failure. */ /* -------------------------------------------------------------------- */ if (psResult && psResult->pszContentType && strncmp(psResult->pszContentType, "text/html", 9) == 0) { CPLDebug( "GME", "PostRequest HTML Response:%s", psResult->pabyData ); CPLError(CE_Failure, CPLE_AppDefined, "HTML error page returned by server"); CPLHTTPDestroyResult(psResult); psResult = NULL; } if (psResult && psResult->pszErrBuf != NULL) { CPLDebug( "GME", "PostRequest Error Message: %s", psResult->pszErrBuf ); CPLDebug( "GME", "error doc:\n%s\n", psResult->pabyData); json_object *error_response = OGRGMEParseJSON((const char *) psResult->pabyData); CPLHTTPDestroyResult(psResult); psResult = NULL; json_object *error_doc = json_object_object_get(error_response, "error"); json_object *errors_doc = json_object_object_get(error_doc, "errors"); array_list *errors_array = json_object_get_array(errors_doc); int nErrors = array_list_length(errors_array); for (int i = 0; i < nErrors; i++) { json_object *error_obj = (json_object *)array_list_get_idx(errors_array, i); const char* reason = OGRGMEGetJSONString(error_obj, "reason", ""); const char* domain = OGRGMEGetJSONString(error_obj, "domain", ""); const char* message = OGRGMEGetJSONString(error_obj, "message", ""); const char* locationType = OGRGMEGetJSONString(error_obj, "locationType", ""); const char* location = OGRGMEGetJSONString(error_obj, "location", ""); if ((nRetries < 10) && EQUAL(reason, "rateLimitExceeded")) { // Sleep nRetries * 1.0s and retry nRetries ++; CPLDebug( "GME", "Got a %s (%d) times.", reason, nRetries ); CPLDebug( "GME", "Sleep for %2.2f to try and avoid qps limiting errors.", 1.0 * nRetries ); CPLSleep( 1.0 * nRetries ); psResult = PostRequest(pszRequest, pszBody); if (psResult) CPLDebug( "GME", "Got a result after %d retries", nRetries ); else CPLDebug( "GME", "Didn't get a result after %d retries", nRetries ); nRetries = 0; } else if (EQUAL(reason, "authError")) { CPLDebug( "GME", "Failed to GET %s: %s", pszRequest, message ); CPLError( CE_Failure, CPLE_OpenFailed, "GME: %s", message); } else if (EQUAL(reason, "backendError")) { CPLDebug( "GME", "Backend error retrying: GET %s: %s", pszRequest, message ); psResult = PostRequest(pszRequest, pszBody); } else { int code = 444; json_object *code_child = json_object_object_get(error_doc, "code"); if (code_child != NULL ) code = json_object_get_int(code_child); CPLError( CE_Failure, CPLE_AppDefined, "GME: %s %s %s: %s - %s", domain, reason, locationType, location, message ); if ((code == 400) && (EQUAL(reason, "invalid")) && (EQUAL(location, "id"))) { CPLDebug("GME", "Got the notorious 400 - invalid id, retrying in 10s"); CPLSleep( 10.0 ); psResult = PostRequest(pszRequest, pszBody); } else { CPLDebug( "GME", "PostRequest Error for %s: %s:%d", pszRequest, reason, code); } } } return psResult; } else if (psResult && psResult->nStatus != 0) { CPLDebug( "GME", "PostRequest Error Status:%d", psResult->nStatus ); } return psResult; }