package org.openntf.domdisc.general; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.net.QuotedPrintableCodec; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.openntf.domdisc.db.DatabaseManager; import org.openntf.domdisc.model.DiscussionDatabase; import org.openntf.domdisc.model.DiscussionEntry; import org.openntf.domdisc.model.DiscussionEntryForSubmitting; import org.openntf.domdisc.tools.UserSessionTools; import org.springframework.http.ContentCodingType; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; /** * Classe for replicating Domino Discussion databases */ public class DiscussionReplicator { Context context; private boolean shouldCommitToLog = false; private static final String loginPath = "/names.nsf?Login"; private static final String loginRedirectTo = "/icons/ecblank.gif"; // Used when authenticating. On Succesful login, the Domino server will redirect (302) to this file public DiscussionReplicator(Context context) { super(); this.context = context; shouldCommitToLog = getLogALot(context); } /** * Activate to replicate all known Discussion Databases */ public void replicateDiscussionDatabases() { } /** * Activate to replicate one Discussion database */ public synchronized void replicateDiscussionDatabase(DiscussionDatabase discussionDatabase) { String authenticationCookie = ""; DatabaseManager.init(context); ApplicationLog.i("Replicate " + discussionDatabase.getName()); if (UserSessionTools.haveInternet(context) == false) { ApplicationLog.i("Internet connection not available - Replication not possible"); } else { ApplicationLog.d("Internet connection is available- Will replicate", shouldCommitToLog); String hostName = discussionDatabase.getHostName(); String dbPath = discussionDatabase.getDbPath(); String httpPort = discussionDatabase.getHttpPort(); String password = discussionDatabase.getPassword(); String userName = discussionDatabase.getUserName(); String httpType = ""; if (discussionDatabase.isUseSSL()) { httpType = "https"; } else { httpType = "http"; } boolean disableComputeWithForm = discussionDatabase.isDisableComputeWithForm(); String urlForDocuments = httpType + "://" + hostName; if (httpPort.contentEquals("80") || httpPort.contentEquals("")) { urlForDocuments = urlForDocuments + dbPath + "/api/data/documents/"; } else { urlForDocuments = urlForDocuments + ":" + httpPort + dbPath + "/api/data/documents/"; } ApplicationLog.d("Starting", shouldCommitToLog); // getAuthenticationToken ApplicationLog.d("Activating getAuthenticationToken now", shouldCommitToLog); authenticationCookie = getAuthenticationToken(hostName, httpPort, userName, password, discussionDatabase.isUseSSL()); if (authenticationCookie.equals("")) { ApplicationLog .w("Unable to start replication as Authentication with the server was not established. Stopping."); } else { replicateLocalDatabaseToServer(discussionDatabase, hostName,urlForDocuments, authenticationCookie, disableComputeWithForm); // Any entries submitted will be deleted when the replicateServerToLocalDatabase runs (because the locally created entries' unids are not found in the downloaded entries) if (replicateServerToLocalDatabase(discussionDatabase, hostName,urlForDocuments, authenticationCookie)) { ApplicationLog.d(getClass().getSimpleName() + " Replication OK", shouldCommitToLog); ApplicationLog.d(getClass().getSimpleName() + " - - - -", shouldCommitToLog); } else { ApplicationLog.w(getClass().getSimpleName() + " Replication server->database failed for " + discussionDatabase.getName()); } } } } /** * Handles replication FROM the local database TO the server * @param discussionDatabase * @param hostName * @param urlForDocuments * @param authenticationCookie * @return True if replication went as expected */ private boolean replicateLocalDatabaseToServer(DiscussionDatabase discussionDatabase, String hostName, String urlForDocuments, String authenticationCookie, boolean disableComputeWithForm) { ApplicationLog.d(getClass().getSimpleName() + " start", shouldCommitToLog); // Bruge en metode der kan oprette en body int succesfulUploadCount = 0; int failedUploadCount = 0; ArrayList<DiscussionEntry> discussionEntriesForSubmitting = (ArrayList<DiscussionEntry>) DatabaseManager.getInstance().getDiscussionEntriesForSubmit(discussionDatabase); if (discussionEntriesForSubmitting == null ) { ApplicationLog.d(getClass().getSimpleName() + " Found no entries for submitting to the server", shouldCommitToLog); } else { ApplicationLog.d(getClass().getSimpleName() + " Found " + discussionEntriesForSubmitting.size() + " entries for submitting to the server", shouldCommitToLog); Iterator<DiscussionEntry> discussionEntriesForSubmittingIterator = discussionEntriesForSubmitting.iterator(); while (discussionEntriesForSubmittingIterator.hasNext()) { DiscussionEntry currentEntry = discussionEntriesForSubmittingIterator.next(); ApplicationLog.d(getClass().getSimpleName() + " Will submit " + currentEntry.getSubject(), shouldCommitToLog); String submittedLocation = submitDiscussionEntry(currentEntry, urlForDocuments, authenticationCookie, disableComputeWithForm); if (submittedLocation.length()> 0) { succesfulUploadCount++; ApplicationLog.d(getClass().getSimpleName() + " successfully submitted to " + submittedLocation, shouldCommitToLog); } else { failedUploadCount++; ApplicationLog.d(getClass().getSimpleName() + " submit failed", shouldCommitToLog); } } ApplicationLog.d(getClass().getSimpleName() + " total succesful submits to " + discussionDatabase.getName() + ": " + succesfulUploadCount , shouldCommitToLog); ApplicationLog.d(getClass().getSimpleName() + " total failed submits to " + discussionDatabase.getName() + ": " + failedUploadCount , shouldCommitToLog); } return (succesfulUploadCount > 0); } /** * @param discussionEntry * @param urlForDocuments * @param authenticationCookie * @return http location of the newly created DiscussionEntry. Empty String if unsuccesful. */ private String submitDiscussionEntry(DiscussionEntry discussionEntry, String urlForDocuments, String authenticationCookie, boolean disableComputeWithForm) { String returnString = ""; //Using a simple class to hold what gets submitted - keeps things simpler DiscussionEntryForSubmitting entryToSubmit = new DiscussionEntryForSubmitting(); entryToSubmit.createFromDiscussionEntry(discussionEntry); String formForSubmit = ""; String parentid = entryToSubmit.getParentid(); if (parentid != null && parentid.length() > 0) { formForSubmit = "Response"; } else { formForSubmit = "MainTopic"; } String url = urlForDocuments + "?form=" + formForSubmit + "&computewithform="; //String url = urlForDocuments + "?form=" + formForSubmit + "&computewithform=true"; //Having computewithform=true will let the application logic compute extra fields on the submitted documents //Disabling computewithform may make it possible to get the app working with discussion databases that have more logic than the standard // Discussion template if (disableComputeWithForm) { url = url + "false"; } else { url = url + "true"; } if (parentid != null && parentid.length() > 0) { url = url + "&parentid=" + parentid; } RestTemplate restTemplate = new RestTemplate(); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAcceptEncoding(ContentCodingType.GZIP); requestHeaders.setContentType(MediaType.APPLICATION_JSON); List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>(); acceptableMediaTypes.add(MediaType.APPLICATION_JSON); requestHeaders.setAccept(acceptableMediaTypes); requestHeaders.add("Cookie", authenticationCookie); HttpEntity<DiscussionEntryForSubmitting> requestEntity = new HttpEntity<DiscussionEntryForSubmitting>(entryToSubmit,requestHeaders); // ApplicationLog.d(getClass().getSimpleName() + " Adding MappingJackson2HttpMessageConverter AND StringHttpMessageConverter to restTemplate", shouldCommitToLog); restTemplate.getMessageConverters().add(new MappingJackson2HttpMessageConverter()); restTemplate.getMessageConverters().add(new StringHttpMessageConverter()); ApplicationLog.d(getClass().getSimpleName() + " POST to " + url, shouldCommitToLog); ResponseEntity<String> httpResponse; try { httpResponse = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class); ApplicationLog.d(getClass().getSimpleName() + " resultString: " + httpResponse.toString(), shouldCommitToLog); ApplicationLog.d(getClass().getSimpleName() + " statuscode: " + httpResponse.getStatusCode().value(), shouldCommitToLog); if (httpResponse.hasBody()) { ApplicationLog.d(getClass().getSimpleName() + " body: " + httpResponse.getBody().toString(), shouldCommitToLog); } else { ApplicationLog.d(getClass().getSimpleName() + " body: None received" , shouldCommitToLog); } HttpHeaders responseHeaders = httpResponse.getHeaders(); if (responseHeaders.isEmpty()) { ApplicationLog.d("No response headers - then the POST did not succeeed", shouldCommitToLog); } else { List<String> locationList = responseHeaders.get("Location"); if (null != locationList) { String location = locationList.get(0); //Assuming the header we are looking for will always be first - unlikely to have two ApplicationLog.d("Location of newly created Note: " + location, shouldCommitToLog); returnString = location; } } } catch (RestClientException e) { e.printStackTrace(); String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = "Error message not available"; } ApplicationLog.e("Exception: " + errorMessage); } return returnString; } /** * Handles replication FROM the server TO the database * @param discussionDatabase * @param hostName * @param urlForDocuments * @param authenticationCookie * @return True if replication went as expected */ private boolean replicateServerToLocalDatabase( DiscussionDatabase discussionDatabase, String hostName, String urlForDocuments, String authenticationCookie) { // String url = // "http://www.jens.bruntt.dk/androiddev/discussi.nsf/api/data/documents/"; boolean replicationOK = false; RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add( new StringHttpMessageConverter()); // Add the gzip Accept-Encoding header HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAcceptEncoding(ContentCodingType.GZIP); ApplicationLog.d("Setting Authentication token in request header: " + authenticationCookie, shouldCommitToLog); requestHeaders.add("Cookie", authenticationCookie); requestHeaders.add("Referer", urlForDocuments); requestHeaders.setCacheControl("max-age=0"); requestHeaders.set("Connection", "Close"); //to remove EOFException HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); ApplicationLog.d("HTTP connection now", shouldCommitToLog); String jsonString; try { ApplicationLog.d("Accesing " + urlForDocuments, shouldCommitToLog); // Make the HTTP GET request, marshaling the response to a // String ResponseEntity<String> response = restTemplate.exchange( urlForDocuments, HttpMethod.GET, requestEntity, String.class); jsonString = response.getBody(); ApplicationLog.d( "String received length: " + jsonString.length(), shouldCommitToLog); if (!isThisALoginForm(jsonString)) { try { JSONArray jsonArray = new JSONArray(jsonString); ApplicationLog.d( "Number of entries downloaded: " + jsonArray.length(), shouldCommitToLog); if (jsonArray.length() > 0) { /** * Because no entries were downloaded we will not enter here. This has the effect that if the server database * actually is empty, this will not reflect on the local database, because deletions are handled in handleJsonDiscussionEntries. * We assume that if we download 0 entries, something is wrong with the downloaded content, and we do not dare let * the code do deletions locally */ handleJsonDiscussionEntries(jsonArray, discussionDatabase, authenticationCookie); } else { ApplicationLog.i("No entries retrieved. Will not do any local deletions"); } replicationOK = true; } catch (Exception e) { replicationOK = false; ApplicationLog.e(getClass().getSimpleName() + " Exception" + e.getMessage()); } } else { ApplicationLog.e("There is a login issue when accessing " + urlForDocuments); ApplicationLog.e("The server at " + hostName + " prompts for login"); replicationOK = false; } } catch (RestClientException e1) { replicationOK = false; String errorMessage = e1.getMessage(); if (errorMessage == null) { errorMessage = "Error message not available"; } ApplicationLog.e(getClass().getSimpleName() + " Exception: " + errorMessage); if (errorMessage.contains("403")) { ApplicationLog.i("403 - Looks like the Domino Data Service is not enabled for the database " + discussionDatabase.getDbPath()); } else if (errorMessage.contains("404")) { String myErrorMessage = "404 - Looks like the Domino Database-path is wrong - typing error in the Configuration? "; ApplicationLog.i(myErrorMessage + " " + discussionDatabase.getDbPath()); } else { String localizedErrorMessage = e1.getLocalizedMessage(); if (localizedErrorMessage != null) { ApplicationLog.e("localizedErrorMessage: " + localizedErrorMessage); } else { ApplicationLog .e("Unable to get data from the database " + discussionDatabase.getDbPath()); } } e1.printStackTrace(); } catch (Exception e2) { replicationOK = false; String message = e2.getMessage(); if (message == null) { message = "Exception with no message"; } ApplicationLog.e(getClass().getSimpleName() + " " + message); e2.printStackTrace(); } return replicationOK; } /** * If String contains FORM tag and HTML tag - this is a Domino login form * * @param checkString * @return true if this is a login form */ private boolean isThisALoginForm(String checkString) { boolean returnValue = false; if (checkString.contains("<form") || checkString.contains("<FORM")) { if (checkString.contains("html") || checkString.contains("HTML")) { returnValue = true; } } return returnValue; } /** * Feed in a jsonArray from the /api/data/documents/ output and a * DiscussionDatabase this method will make sure that the database is * updated * * @param discussionArray * @param discussionDatabase */ private void handleJsonDiscussionEntries(JSONArray discussionArray, DiscussionDatabase discussionDatabase, String authenticationCookie) { // ArrayList<DiscussionEntry> serverDiscussionEntryList = new ArrayList<DiscussionEntry>(); HashMap<String, DiscussionEntry> serverDiscussionEntryMap = new HashMap<String, DiscussionEntry>(); for (int i = 0; i < discussionArray.length(); i++) { JSONObject jsonObject; try { jsonObject = discussionArray.getJSONObject(i); try { String modified = jsonObject.getString("@modified"); String unid = jsonObject.getString("@unid"); String href = jsonObject.getString("@href"); ApplicationLog.d("entry with unid: " + unid, shouldCommitToLog); DiscussionEntry discussionEntry = new DiscussionEntry(); discussionEntry.setHref(href); discussionEntry.setUnid(unid); discussionEntry.setModified(modified); // serverDiscussionEntryList.add(discussionEntry); // Should be removed serverDiscussionEntryMap.put(unid, discussionEntry); } catch (JSONException e) { ApplicationLog.e(getClass().getSimpleName() + " Error while accessing JSON object values"); e.printStackTrace(); } } catch (JSONException e) { e.printStackTrace(); } } if((serverDiscussionEntryMap == null) || (serverDiscussionEntryMap.isEmpty())) { ApplicationLog.i("The JSON retrievede did not contain any documents"); } else { /* * Check all retrieved DiscussionDatabaseEntries - do they exist in * database if no: retrieve content and add if yes: check if modified is * changed if no: next if yes: retrieve newer content and update */ ApplicationLog.d("Checking all downloaded entries: are they already stored locally?", shouldCommitToLog); Set<Entry<String, DiscussionEntry>> set = serverDiscussionEntryMap.entrySet(); Iterator<Entry<String, DiscussionEntry>> serverDiscussionEntryIterator = set.iterator(); while (serverDiscussionEntryIterator.hasNext()) { Map.Entry<String, DiscussionEntry> currentMapEntry = (Map.Entry<String, DiscussionEntry>)serverDiscussionEntryIterator.next(); DiscussionEntry currentEntry = currentMapEntry.getValue(); String unid = currentEntry.getUnid(); // Check if the entry is already in the database ApplicationLog.d("Lookup for unid: " + unid, shouldCommitToLog); DiscussionEntry dbEntry = DatabaseManager.getInstance().getDiscussionEntryWithId(unid); if (dbEntry == null) { ApplicationLog.d("This entry has not been stored before - retrieving full entry ", shouldCommitToLog); currentEntry.setDiscussionDatabase(discussionDatabase); DiscussionEntry fullDiscussionEntry = getFullEntryFromServer(currentEntry, authenticationCookie); String entryForm = fullDiscussionEntry.getForm(); if (isAcceptableFormType(entryForm)) { DatabaseManager.getInstance().createDiscussionEntry(fullDiscussionEntry); ApplicationLog.d("This entry has been stored with values: " + fullDiscussionEntry.getSubject(), shouldCommitToLog); updateParentThreadDates(fullDiscussionEntry); } else { ApplicationLog.d("This entry has not been stored before, but the Form Type is not one of the accepted types. Will not store", shouldCommitToLog); } } else { ApplicationLog.d("This entry is already in the database: " + dbEntry.getSubject(), shouldCommitToLog); ApplicationLog.d("Checking if modified dates are the same", shouldCommitToLog); String currentEntryModified = currentEntry.getModified(); String dbEntryModified = dbEntry.getModified(); if (currentEntryModified.contentEquals(dbEntryModified)) { ApplicationLog.d("Modified date is unchanged", shouldCommitToLog); } else { ApplicationLog.d( "Modified date is changed. Updating dbEntry", shouldCommitToLog); DiscussionEntry fullDiscussionEntry = getFullEntryFromServer(currentEntry, authenticationCookie); fullDiscussionEntry.setDiscussionDatabase(discussionDatabase); dbEntry = fullDiscussionEntry; DatabaseManager.getInstance().updateDiscussionEntry(dbEntry); updateParentThreadDates(dbEntry); } } } /** * Checking all locally stored entries - are they in the download * if not - delete the local entry */ ApplicationLog.d("Checking all database entries: should they be deleted?", shouldCommitToLog); ArrayList<DiscussionEntry> localDiscussionEntryList = new ArrayList<DiscussionEntry>(); try { localDiscussionEntryList = (ArrayList<DiscussionEntry>) discussionDatabase.getDiscussionEntries(); } catch (Exception e) { //This throws exception if the database has just done its first server-to-local replication ApplicationLog.w("Unable to retrieve local discussion entries. This is OK if it is the first replication of this database: " + discussionDatabase.getName()); } if (localDiscussionEntryList.size()>0) { //Example on using Map http://www.java-tips.org/java-se-tips/java.util/how-to-use-of-hashmap.html // Iterator<DiscussionEntry> localDiscussionEntryListIterator = localDiscussionEntryList.iterator(); while (localDiscussionEntryListIterator.hasNext()) { DiscussionEntry currentEntry = localDiscussionEntryListIterator.next(); String unid = currentEntry.getUnid(); // Check if the was downloaded ApplicationLog.d("Lookup for unid: " + unid, shouldCommitToLog); DiscussionEntry serverEntry = serverDiscussionEntryMap.get(unid); if (serverEntry != null) { ApplicationLog.d("Found the entry " + currentEntry.getSubject() + " in the downloaded list", shouldCommitToLog); } else { ApplicationLog.d("Did not find the entry " + currentEntry.getSubject() + " in the downloaded list. Deleting in local DB", shouldCommitToLog); DatabaseManager.getInstance().deleteDiscussionEntry(currentEntry); } } } } /** * Done checking if new or modified entries were downloaded */ } // } /** * @param entryForm * @return true if one of the Form types that we want to allow you to store has been used */ private boolean isAcceptableFormType(String entryForm) { if (entryForm == null) { return false; } if (entryForm.equalsIgnoreCase("maintopic")) { return true; } if (entryForm.equalsIgnoreCase("response")) { return true; } if (entryForm.equalsIgnoreCase("responsetoresponse")) { return true; } return false; } /** * Retrieve a full entry from the server * * @param discussionEntry * @param discussionDatabase * @return discussionDatabase */ private DiscussionEntry getFullEntryFromServer( DiscussionEntry discussionEntry, String authenticationCookie) { // if (UserSessionTools.haveInternet(context)) { // ApplicationLog // .i("Internet connection is available - will replicate"); DiscussionDatabase discussionDatabase = discussionEntry .getDiscussionDatabase(); String urlForDocuments = discussionEntry.getHref(); ApplicationLog.d("Accesing " + urlForDocuments, shouldCommitToLog); ApplicationLog.d("Starting", shouldCommitToLog); // Add the gzip Accept-Encoding header HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAcceptEncoding(ContentCodingType.GZIP); requestHeaders.set("Connection", "Close"); //to remove EOFException if (authenticationCookie != "") { ApplicationLog.d("Setting authentication token in request header", shouldCommitToLog); requestHeaders.add("Cookie", authenticationCookie); } HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders); RestTemplate restTemplate = new RestTemplate(); restTemplate.getMessageConverters().add( new StringHttpMessageConverter()); String jsonString; try { ResponseEntity<String> response = restTemplate.exchange( urlForDocuments, HttpMethod.GET, requestEntity, String.class); jsonString = response.getBody(); ApplicationLog.d("String received length: " + jsonString.length(),shouldCommitToLog); if (!isThisALoginForm(jsonString)) { try { JSONObject jsonDocument = new JSONObject(jsonString); if (jsonDocument.length() > 0) { discussionEntry = enrichDiscussionEntryFromJson( discussionEntry, jsonDocument); } else { ApplicationLog .i("No Document retrieved. Nothing to do"); } } catch (Exception e) { ApplicationLog.e(getClass().getSimpleName() + " Exception: " + e.getMessage()); } } else { ApplicationLog.e("There is a login issue when accessing " + urlForDocuments); ApplicationLog.e("The server prompts for login"); } } catch (RestClientException e1) { String errorMessage = e1.getMessage(); ApplicationLog.e("Exception: " + errorMessage); if (errorMessage.contains("403")) { ApplicationLog .i("403 - Looks like the Domino Data Service is not enabled for the database " + discussionDatabase.getDbPath()); } else { String localizedErrorMessage = e1.getLocalizedMessage(); if (localizedErrorMessage != null) { ApplicationLog.e("localizedErrorMessage: " + localizedErrorMessage); } else { ApplicationLog .e("Unable to get data from the database " + discussionDatabase.getDbPath()); } } e1.printStackTrace(); } catch(OutOfMemoryError e) { String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = "Unknown error message"; } ApplicationLog.e("Out of Memory Error: " + errorMessage + ". Possibly we ran into a very large document"); e.printStackTrace(); } catch (Exception e) { String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = "Unknown error message"; } ApplicationLog.e("Exception: " + errorMessage); e.printStackTrace(); } return discussionEntry; } private DiscussionEntry enrichDiscussionEntryFromJson( DiscussionEntry discussionEntry, JSONObject jsonDocument) { discussionEntry.setAbbreviateFrom(getDominoValueFromJson(jsonDocument, "AbbreviateFrom")); discussionEntry.setAbrFrom(getDominoValueFromJson(jsonDocument, "AbrFrom")); discussionEntry.setAbstractDoc(getDominoValueFromJson(jsonDocument, "AbstractDoc")); discussionEntry.setAltFrom(getDominoValueFromJson(jsonDocument, "AltFrom")); discussionEntry.setBody(getDominoValueFromJson(jsonDocument, "Body")); discussionEntry.setCategories(getDominoValueFromJson(jsonDocument, "Categories")); discussionEntry.setFrom(getDominoValueFromJson(jsonDocument, "From")); discussionEntry .setMainID(getDominoValueFromJson(jsonDocument, "MainID")); discussionEntry.setMimeVersion(getDominoValueFromJson(jsonDocument, "MimeVersion")); discussionEntry.setNewsLetterSubject(getDominoValueFromJson( jsonDocument, "NewsLetterSubject")); discussionEntry.setPath_Info(getDominoValueFromJson(jsonDocument, "Path_Info")); discussionEntry.setRemote_User(getDominoValueFromJson(jsonDocument, "Remote_User")); discussionEntry.setSubject(getDominoValueFromJson(jsonDocument, "Subject")); discussionEntry.setThreadId(getDominoValueFromJson(jsonDocument, "ThreadId")); discussionEntry.setWebCategories(getDominoValueFromJson(jsonDocument, "WebCategories")); // System fields below discussionEntry.setModified(getDominoValueFromJson(jsonDocument, "@modified")); discussionEntry.setHref(getDominoValueFromJson(jsonDocument, "@href")); discussionEntry.setUnid(getDominoValueFromJson(jsonDocument, "@unid")); discussionEntry.setCreated(getDominoValueFromJson(jsonDocument, "@created")); discussionEntry.setForm(getDominoValueFromJson(jsonDocument, "@form")); discussionEntry.setNoteid(getDominoValueFromJson(jsonDocument, "@noteid")); discussionEntry.setAuthors(getDominoValueFromJson(jsonDocument, "@authors")); discussionEntry.setParentid(getDominoValueFromJson(jsonDocument, "@parentid")); return discussionEntry; } private String getDominoValueFromJson(JSONObject jsonDocument, String fieldName) { String returnValue = ""; if (fieldName.contains("Body")) { // If the Body field is not just plain text we will go for the more complicated extraction ApplicationLog.d(getClass().getSimpleName() + " getDominoValueFromJson handling Body", shouldCommitToLog); returnValue = getBodyFieldValue(jsonDocument, fieldName, returnValue); } else { // All other fields than Body try { returnValue = jsonDocument.getString(fieldName); } catch (JSONException e) { // ApplicationLog.d("Unable to find field " + fieldName, shouldCommitToLog); returnValue = ""; } } return returnValue; } private String getBodyFieldValue(JSONObject jsonDocument, String fieldName, String returnValue) { ApplicationLog.d(getClass().getSimpleName() + " getBodyFiledValue start", shouldCommitToLog); try { JSONObject bodyObjectArray = jsonDocument.getJSONObject(fieldName); // If Body content is simple we will handle this in the JSONException JSONArray bodyArray = bodyObjectArray.getJSONArray("content"); for (int i = 0; i < bodyArray.length(); i++) { JSONObject bodyItem = bodyArray.getJSONObject(i); if (bodyItem.has("contentType")) { String thisContentType = bodyItem .getString("contentType"); if (thisContentType.contains("text/html")) { ApplicationLog.d("Found body item with html - adding", shouldCommitToLog); String bodyHtml = bodyItem.getString("data"); // Finding the charset - START int charSetPos = thisContentType.indexOf("charset="); // Returns the // position of the c in the string "charset=" 8 characters long int contentTypeLength = thisContentType.length(); String charsetValue = thisContentType.substring( charSetPos + 8, contentTypeLength); ApplicationLog.d("charsetValue: " + charsetValue, shouldCommitToLog); // Finding the charset - END // Checking for quoted-printable content - START //contentTransferEncoding String bodyHtmlDecoded = ""; if (bodyItem.has("contentTransferEncoding")) { // String contentTransferEncoding = bodyItem.getString("contentTransferEncoding"); // ApplicationLog.d("contentTransferEncoding is specified as " + contentTransferEncoding + " will do decoding", shouldCommitToLog); QuotedPrintableCodec dims = new QuotedPrintableCodec(); // Stripping newline characters as they will make QuotedPrintableCodec throw an Exception String newstr = bodyHtml.replaceAll("=\r\n", ""); bodyHtml = newstr; // ApplicationLog.d("decoding: " + bodyHtml, shouldCommitToLog); try { bodyHtmlDecoded = dims.decode(bodyHtml, charsetValue); } catch (DecoderException e) { // e.printStackTrace(); ApplicationLog.d("Exception: " + e.getMessage(), shouldCommitToLog); } catch (UnsupportedEncodingException e) { // e.printStackTrace(); ApplicationLog.d("Exception: " + e.getMessage(), shouldCommitToLog); } // ApplicationLog.d("decoded: " + bodyHtmlDecoded, shouldCommitToLog); } // Checking for quoted-printable content - END if (bodyHtmlDecoded.equals("")) { returnValue = bodyHtml; } else { returnValue = bodyHtmlDecoded; } } } } } catch (JSONException e) { // Most likely we're here because the Body item is not an array but a simple field instead ApplicationLog.d(fieldName + " is not an array - looking for simple field ", shouldCommitToLog); try { String contentValue = jsonDocument.getString(fieldName); String htmlValue = contentValue.replaceAll("\n", "<br>"); returnValue = htmlValue; } catch (JSONException e1) { // If we're here we give up trying ApplicationLog.d("Exception: " + e.getMessage(), shouldCommitToLog); ApplicationLog.d("Unable to find field " + fieldName, shouldCommitToLog); returnValue = ""; } } return returnValue; } private static boolean getLogALot(Context ctxt) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(ctxt); return prefs.getBoolean("checkbox_preference_logalot", false); } /** * @param hostName * @param httpPort * @param userName * @param password * @param useSSL * @return a Domino authentication token (or "" if not possible to authenticate) * examples: DominoAuthSessID=xyz OR LtpaToken=abc */ private String getAuthenticationToken(String hostName, String httpPort, String userName, String password, boolean useSSL) { String authenticationCookie = ""; String httpType = ""; if (useSSL) { httpType = "https"; } else { httpType = "http"; } String urlForLogin = httpType + "://" + hostName + ":" + httpPort + loginPath; ApplicationLog.d("URL for login: " + urlForLogin, shouldCommitToLog); RestTemplate template = new RestTemplate(); template.getMessageConverters().add(new FormHttpMessageConverter()); HttpHeaders requestHeaders = new HttpHeaders(); requestHeaders.setAcceptEncoding(ContentCodingType.GZIP); requestHeaders.set("Connection", "Close"); //to remove EOFException ApplicationLog.d("Getting LtpaToken and SessionID", shouldCommitToLog); MultiValueMap<String, String> requestBody = new LinkedMultiValueMap<String, String>(); requestBody.add("username", userName); requestBody.add("password", password); requestBody.add("redirectto", loginRedirectTo); HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<MultiValueMap<String, String>>( requestBody, requestHeaders); ApplicationLog.d("HTTP connection now", shouldCommitToLog); ResponseEntity<?> httpResponse; try { httpResponse = template.exchange(urlForLogin, HttpMethod.POST, request, null); HttpHeaders responseHeaders = httpResponse.getHeaders(); if (responseHeaders.isEmpty()) { ApplicationLog.d("No response headers", shouldCommitToLog); } else { //Working out if we already have a Cookie or if we have a Set-cookie to work with - START List<String> setCookieList = responseHeaders.get("Set-Cookie"); //Working out if we already have a Cookie or if we have a Set-cookie to work with - END if (null != setCookieList) { String cookie = setCookieList.get(0); //Assuming the cookie we are looking for will always be first ApplicationLog.d("Cookie: " + cookie, shouldCommitToLog); int indexOfEndPos = 0; //Position of last character in the coookie string if (cookie.contains(";")) { indexOfEndPos = cookie.indexOf(";"); } else { indexOfEndPos = cookie.length(); } if (cookie.startsWith("LtpaToken=")) { ApplicationLog.d("Cookie is an LtpaToken", shouldCommitToLog); } else if (cookie.startsWith("DomAuthSessID=")) { ApplicationLog.d("Cookie is a DomAuthSessID", shouldCommitToLog); } String actualToken = (String) cookie.subSequence(0,indexOfEndPos); if (actualToken != null) { ApplicationLog.d("Token value= " + actualToken, shouldCommitToLog); authenticationCookie = actualToken; } else { ApplicationLog.d("Did not get the Authetication token value", shouldCommitToLog); } } else { ApplicationLog.d("No Authentication Cookie available", shouldCommitToLog); } } } catch (RestClientException e) { String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = "Error message not available"; } ApplicationLog.e("RestClientException: " + errorMessage); e.printStackTrace(); } catch (Exception e) { String errorMessage = e.getMessage(); if (errorMessage == null) { errorMessage = "Error message not available"; } // Log.e(getClass().getSimpleName(), "getmessage: " + errorMessage); ApplicationLog.e("Exception: " + errorMessage); e.printStackTrace(); } if (authenticationCookie.contentEquals("")) { ApplicationLog.i("Did not get an authentication cookie"); } return authenticationCookie; } private void updateParentThreadDates(DiscussionEntry discussionEntry) { ApplicationLog.d(getClass().getSimpleName() + " updateParentThreadDates for " + discussionEntry.getSubject(), shouldCommitToLog); String parentId =discussionEntry.getParentid(); if (parentId != null && parentId.length()>0) { DiscussionEntry parentEntry = DatabaseManager.getInstance().getDiscussionEntryWithId(parentId); if (parentEntry != null) { String parentThreadLastModifiedDate = parentEntry.getThreadLastModifiedDate(); String entryThreadLastModifiedDate = discussionEntry.getThreadLastModifiedDate(); ApplicationLog.d("updateParentThreadDates", shouldCommitToLog); ApplicationLog.d("parentThreadLastModifiedDate: " + parentThreadLastModifiedDate, shouldCommitToLog); ApplicationLog.d("entryThreadLastModifiedDate: " + entryThreadLastModifiedDate, shouldCommitToLog); int compareValue = entryThreadLastModifiedDate.compareTo(parentThreadLastModifiedDate); ApplicationLog.d("compareValue: " + compareValue, shouldCommitToLog); if (compareValue>0) { ApplicationLog.d("Will update parent Thread last mod to: " + entryThreadLastModifiedDate, shouldCommitToLog); parentEntry.setThreadLastModifiedDate(entryThreadLastModifiedDate); DatabaseManager.getInstance().updateDiscussionEntry(parentEntry); updateParentThreadDates(parentEntry); } } else { ApplicationLog.d("Parent not found in local db", shouldCommitToLog); } } else { ApplicationLog.d("Does not have a parent", shouldCommitToLog); } } }