package pt.rupeal.invoicexpress.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.protocol.BasicHttpContext; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import pt.rupeal.invoicexpress.MainActivity; import pt.rupeal.invoicexpress.R; import pt.rupeal.invoicexpress.adapters.DocumentListRowAdapter; import pt.rupeal.invoicexpress.enums.DocumentTypeEnum; import pt.rupeal.invoicexpress.enums.FragmentTagsEnum; import pt.rupeal.invoicexpress.fragments.DocumentsListFragment; import pt.rupeal.invoicexpress.fragments.DocumentsListFragment.DocumentFilterFragment; import pt.rupeal.invoicexpress.model.ContactModel; import pt.rupeal.invoicexpress.model.DocumentModel; import pt.rupeal.invoicexpress.model.DocumentsFilterModel; import pt.rupeal.invoicexpress.model.DocumentsModel; import pt.rupeal.invoicexpress.model.ItemModel; import pt.rupeal.invoicexpress.utils.InvoiceXpressError.InvoiceXpressErrorType; import pt.rupeal.invoicexpress.utils.InvoiceXpressException; import android.content.Context; import android.os.Bundle; import android.util.Log; import android.widget.LinearLayout; import android.widget.TextView; public class DocumentsRestHandler extends AsyncTask<String, Void, DocumentsFilterModel> { private static final String REQUEST_NORMAL = "normal"; private static final String REQUEST_MORE = "more"; private static final String REQUEST_REFRESH = "refresh"; private DocumentListRowAdapter adapter; private LinearLayout listViewFooter; private String docType; private String contactId; // the attribute requestType can be a refresh, a moreDocuments or a first normal request private String requestType; private int requestedFilterCode; public DocumentsRestHandler(Context context) { this.context = context; this.requestType = REQUEST_NORMAL; } /** * Constructor for refresh requests. */ public DocumentsRestHandler(Context context, DocumentListRowAdapter adapter) { this.context = context; this.adapter = adapter; this.requestType = REQUEST_REFRESH; } /** * Constructor for more request */ public DocumentsRestHandler(Context context, DocumentListRowAdapter adapter, LinearLayout listViewFooter) { this.context = context; this.adapter = adapter; this.listViewFooter = listViewFooter; this.requestType = REQUEST_MORE; } @Override protected void onPostExecute(DocumentsFilterModel result) { super.onPostExecute(result); // check if there is an error if(existsError()) { processError(); return; } // set data depends of the request if(contactId.isEmpty()) { InvoiceXpress.getInstance().setDocuments(docType, result); } else { InvoiceXpress.getInstance().getContacts().get(contactId).setDocuments(result); } if(REQUEST_MORE.equals(requestType) || REQUEST_REFRESH.equals(requestType)) { // refreshing just update active view.. processMoreOrRefreshRequest(); } else { processNormalRequest(); } } private void processMoreOrRefreshRequest() { DocumentsModel documentsModel = getActualDocuments().getDocuments().get(requestedFilterCode); adapter.setDocuments(documentsModel.getDocumentsSorted()); adapter.notifyDataSetChanged(); // for more request if(REQUEST_MORE.equals(requestType)) { int downloadedDocumentsCount = documentsModel.getDownloadedDocuments(); ((TextView) listViewFooter.findViewById(R.id.documents_more_docs_downloaded)).setText( downloadedDocumentsCount + " " + context.getResources().getString(R.string.doc_more_downloaded)); int toUploadDocumentsCount = documentsModel.getTotalDocuments() - downloadedDocumentsCount; ((TextView) listViewFooter.findViewById(R.id.documents_more_docs_to_upload)).setText( toUploadDocumentsCount + " " + context.getResources().getString(R.string.doc_more_to_upload)); if(toUploadDocumentsCount == 0) { listViewFooter.setOnClickListener(null); } } } private void processNormalRequest() { // add a new fragment if(contactId.isEmpty()) { if(InvoiceXpress.DEBUG) { Log.d(this.getClass().getCanonicalName(), "Show documents"); } Bundle args = new Bundle(); args.putString(DocumentModel.DOC_TYPE, docType); args.putString(ContactModel.ID, ""); // call fragment ((MainActivity) context).addFragment(DocumentsListFragment.class, FragmentTagsEnum.DOCUMENTS_LIST, args); } else { if(InvoiceXpress.DEBUG) { Log.d(this.getClass().getCanonicalName(), "Show documents for client: " + contactId); } // request from Client // set extra contact id. this will be read in DocumentsListFragment Bundle args = new Bundle(); args.putString(DocumentModel.DOC_TYPE, docType); args.putString(ContactModel.ID, contactId); // call fragment ((MainActivity) context).addFragment(DocumentsListFragment.class, FragmentTagsEnum.DOCUMENTS_LIST, args); } } /* (non-Javadoc) * @see android.os.AsyncTask#doInBackground(Params[]) * * params[0] = docType; params[1] = client; params[2] = requestedFilterCode; params[3] = page */ @Override protected DocumentsFilterModel doInBackground(String... params) { // set docType docType = params[0]; if(InvoiceXpress.DEBUG) { Log.d(this.getClass().getCanonicalName(), "Documetn Type: " + docType); } // set contact id contactId = params[1]; if(InvoiceXpress.DEBUG) { Log.d(this.getClass().getCanonicalName(), "Contact id: " + contactId); } // get page String page = params[3]; if(InvoiceXpress.DEBUG) { Log.d(this.getClass().getCanonicalName(), "Page: " + page); } // get documents that will be updated DocumentsFilterModel documents = getActualDocuments(); try { // get requestedFilterCode requestedFilterCode = Integer.valueOf(params[2]); switch (requestedFilterCode) { case DocumentFilterFragment.NO_FILTER: // requestType == NORMAL doInBackgroundFiltered(documents, DocumentFilterFragment.FILTER_CODE_ARCHIVED, page); doInBackgroundFiltered(documents, DocumentFilterFragment.FILTER_CODE_ALL, page); doInBackgroundFiltered(documents, DocumentFilterFragment.FILTER_CODE_OVER_DUE, page); break; case DocumentFilterFragment.FILTER_CODE_ARCHIVED: doBackgroundRefreshOrMore(documents, DocumentFilterFragment.FILTER_CODE_ARCHIVED, page); break; case DocumentFilterFragment.FILTER_CODE_ALL: doBackgroundRefreshOrMore(documents, DocumentFilterFragment.FILTER_CODE_ALL, page); break; case DocumentFilterFragment.FILTER_CODE_OVER_DUE: doBackgroundRefreshOrMore(documents, DocumentFilterFragment.FILTER_CODE_OVER_DUE, page); break; default: break; } } catch(InvoiceXpressException e) { Log.e(this.getClass().getCanonicalName(), e.getMessage(), e); setError(e.getMessage(), e.getType()); } return documents; } private void doInBackgroundFiltered(DocumentsFilterModel documents, int filterCode, String page) throws InvoiceXpressException { try { HttpGet httpGet = new HttpGet(contactId.isEmpty() ? buildRequestHttpGet(filterCode, page) : buildContactRequestHttpGet(filterCode, page)); DefaultHttpClient httpClient = new DefaultHttpClient(InvoiceXpress.getHttpParameters()); HttpResponse response = httpClient.execute(httpGet, new BasicHttpContext()); BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); StringBuffer responseString = new StringBuffer(); String line = ""; while ((line = reader.readLine()) != null) { responseString.append(line); } if(InvoiceXpress.DEBUG) { Log.d(DocumentsRestHandler.class.getCanonicalName(), responseString.toString()); } // if there is documents then the application has to clear and replace oldest documents if((requestType.equals(REQUEST_MORE) || requestType.equals(REQUEST_REFRESH)) && documents.getDocuments().containsKey(filterCode)) { // get actual documentsModel DocumentsModel documentsModel = documents.getDocuments().get(filterCode); // get downloaded documentsModel DocumentsModel documentsModelDownloaded = parseDocumentsResponse(responseString.toString()); // set new info pages data from downloaded documents model to actual documents model documentsModel.setCurrentPage(documentsModelDownloaded.getCurrentPage()); documentsModel.setTotalDocuments(documentsModelDownloaded.getTotalDocuments()); documentsModel.setTotalPages(documentsModelDownloaded.getTotalPages()); // just add download documents if we have a more request if(requestType.equals(REQUEST_MORE)) { documentsModel.addDownloadedDocuments(documentsModelDownloaded.getDownloadedDocuments()); } // put download documents documentsModel.getDocuments().putAll(documentsModelDownloaded.getDocuments()); } else { // if there is not documents for filter code then the application just put the new documents list // i'm sure that it is the first request, request type is NORMAL // put download documents by filter documents.getDocuments().put(filterCode, parseDocumentsResponse(responseString.toString())); } } catch (ClientProtocolException e) { throw new InvoiceXpressException(context, R.string.error_documents_unexpected, InvoiceXpressErrorType.ERROR); } catch (IOException e) { throw new InvoiceXpressException(context, R.string.error_documents_unexpected, InvoiceXpressErrorType.ERROR); } catch (InvoiceXpressException e) { throw new InvoiceXpressException(context, R.string.error_documents_unexpected, InvoiceXpressErrorType.ERROR); } } private void doBackgroundRefreshOrMore(DocumentsFilterModel documents, int filterCode, String pageToMore) throws InvoiceXpressException { if(REQUEST_REFRESH.equals(requestType)) { documents.getDocuments().get(filterCode).getDocuments().clear(); int currentPage = documents.getDocuments().get(filterCode).getCurrentPage(); int pageNumber = 1; while(pageNumber <= currentPage) { doInBackgroundFiltered(documents, filterCode, String.valueOf(pageNumber)); pageNumber++; } } else if(REQUEST_MORE.equals(requestType)) { doInBackgroundFiltered(documents, filterCode, pageToMore); } } /** * Get documents. Getter from documents types fragment or get from specified contact. * @return documents */ private DocumentsFilterModel getActualDocuments() { if(contactId.isEmpty()) { // generic documents // return InvoiceXpress.getInstance().getDocuments(); return InvoiceXpress.getInstance().getDocuments(docType); } else { // contact documents // contact list already selected if(InvoiceXpress.getInstance().getContacts().containsKey(contactId)) { return InvoiceXpress.getInstance().getContacts().get(contactId).getDocuments(); } else { // there is no contact list yet return new DocumentsFilterModel(); } } } /** * Parse the xml response. * @param filterCode * @param xml * @return * @throws InvoiceXpressException */ private DocumentsModel parseDocumentsResponse(String xml) throws InvoiceXpressException { InvoiceXpressParser parser = new InvoiceXpressParser(context); Document documentDomElement = parser.getDomElement(xml); DocumentsModel documentsModel = new DocumentsModel(); readInfoPages(documentsModel, documentDomElement, parser); NodeList nodeList = documentDomElement.getElementsByTagName("invoice"); // empty list results if(nodeList.getLength() == 0) { return new DocumentsModel(); } Map<String, DocumentModel> documents = new HashMap<String, DocumentModel>(nodeList.getLength()); for (int i = 0; i < nodeList.getLength(); i++) { DocumentModel document = new DocumentModel(); Element elem = (Element) nodeList.item(i); document.setId(parser.getValue(elem, "id")); document.setSequenceNumber(parser.getValue(elem, "sequence_number")); document.setType(parser.getValue(elem, "type")); document.setStatus(parser.getValue(elem, "status")); document.setDate(parser.getValue(elem, "date")); document.setDueDate(parser.getValue(elem, "due_date")); document.setSum(Double.parseDouble(parser.getValue(elem, "sum"))); document.setDiscount(Double.parseDouble(parser.getValue(elem, "discount"))); document.setBeforeTaxes(Double.parseDouble(parser.getValue(elem, "before_taxes"))); document.setTaxes(Double.parseDouble(parser.getValue(elem, "taxes"))); document.setTotal(Double.parseDouble(parser.getValue(elem, "total"))); document.setObservations(parser.getValue(elem, "observations")); Node mb = parser.getNode(elem, "mb_reference"); if(mb != null) { document.setPayRef(parser.getValue((Element) mb, "reference")); document.setPayEntity(parser.getValue((Element) mb, "entity")); document.setPayValue(parser.getValue((Element) mb, "value")); } Node client = parser.getNode(elem, "client"); // for cash invoices or simplified invoices the client can be empty if(client != null) { document.setClientId(parser.getValue((Element) client, "id")); document.setClientName(parser.getValue((Element) client, "name")); document.setClientEmail(parser.getValue((Element) client, "email")); } else { document.setClientId(""); document.setClientName(context.getResources().getString(R.string.doc_details_final_costumer)); document.setClientEmail(""); } NodeList itemsNodeList = elem.getElementsByTagName("item"); HashMap<String, ItemModel> itemsHashMap = new HashMap<String, ItemModel>(itemsNodeList.getLength()); for (int j = 0; j < itemsNodeList.getLength(); j++) { Node itemNode = itemsNodeList.item(j); ItemModel itemDetails = new ItemModel(); itemDetails.setName(parser.getValue((Element) itemNode, "name")); itemDetails.setDescription(parser.getValue((Element) itemNode, "description")); itemDetails.setUnitPrice(Double.parseDouble(parser.getValue((Element) itemNode, "unit_price"))); itemDetails.setQuantity(Double.parseDouble(parser.getValue((Element) itemNode, "quantity"))); itemDetails.setTaxAmount(Double.parseDouble(parser.getValue((Element) itemNode, "tax_amount"))); itemDetails.setDiscount(Double.parseDouble(parser.getValue((Element) itemNode, "discount"))); itemDetails.setSubTotal(Double.parseDouble(parser.getValue((Element) itemNode, "subtotal"))); itemDetails.setTotal(Double.parseDouble(parser.getValue((Element) itemNode, "total"))); itemsHashMap.put(itemDetails.getName(), itemDetails); } document.setItems(itemsHashMap); document.setArchived(Boolean.parseBoolean(parser.getValue(elem, "archived"))); documents.put(document.getId(), document); } documentsModel.setDocuments(documents); documentsModel.setDownloadedDocuments(documents.size()); return documentsModel; } /** * Read info pages from response. The response can have two different xml specification that depends on the request. * * @param filterCode, filter code - 0 = archived, 1 = all and 2 = over due * @param documentDomElement, document dom element * @param parser, parser */ private void readInfoPages(DocumentsModel documentsModel, Document documentDomElement, InvoiceXpressParser parser) { // read info pages from contact details fragment if(documentDomElement.getElementsByTagName("results").item(0) != null) { Node resultNode = documentDomElement.getElementsByTagName("results").item(0); int currentPage = Integer.parseInt(parser.getValue((Element) resultNode, "current_page")); int totalPages = Integer.parseInt(parser.getValue((Element) resultNode, "total_pages")); int totalDocuments = Integer.parseInt(parser.getValue((Element) resultNode, "total_entries")); // DocumentsModel documentsContactModel = InvoiceXpress.getInstance().getContacts().get(contactId).getDocuments().getDocuments().get(filterCode); documentsModel.setCurrentPage(currentPage); documentsModel.setTotalPages(totalPages); documentsModel.setTotalDocuments(totalDocuments); } else { // read info pages from documents type fragment Node mainNode = documentDomElement.getElementsByTagName("invoices").item(0); int currentPage = Integer.parseInt(parser.getValue((Element) mainNode, "current_page")); int totalPages = Integer.parseInt(parser.getValue((Element) mainNode, "total_pages")); int totalDocuments = Integer.parseInt(parser.getValue((Element) mainNode, "total_entries")); // DocumentsModel documentsModel = InvoiceXpress.getInstance().getDocuments().getDocuments().get(filterCode); documentsModel.setCurrentPage(currentPage); documentsModel.setTotalPages(totalPages); documentsModel.setTotalDocuments(totalDocuments); } } private String buildRequestHttpGet(int filterCode, String page) { if(InvoiceXpress.DEBUG) { Log.d(DocumentsRestHandler.class.getCanonicalName(), "buildRequestHttpGet"); } StringBuffer request = new StringBuffer(InvoiceXpress.getInstance().getActiveAccount().getUrl()); request.append("/invoices.xml"); request.append("?api_key=" + InvoiceXpress.getInstance().getActiveAccount().getApiKey()); if(!docType.equals(DocumentTypeEnum.ALL.getValue())) { request.append("&filter[by_type][]=" + docType); } if(filterCode == DocumentFilterFragment.FILTER_CODE_ARCHIVED) { request.append("&filter[archived]=only_archived"); } else if(filterCode == DocumentFilterFragment.FILTER_CODE_OVER_DUE) { request.append("&filter[overdue]=1"); } if(page != null && !page.isEmpty()) { request.append("&page=" + page); } if(InvoiceXpress.DEBUG) { Log.d(DocumentsRestHandler.class.getCanonicalName(), request.toString()); } return request.toString(); } /** * Url example: https://screen-name.invoicexpress.net/clients/:client_id/invoices.xml * @return url */ private String buildContactRequestHttpGet(int filterCode, String page) { if(InvoiceXpress.DEBUG) { Log.d(DocumentsRestHandler.class.getCanonicalName(), "buildContactRequestHttpGet"); } StringBuffer request = new StringBuffer(InvoiceXpress.getInstance().getActiveAccount().getUrl()); request.append("/clients").append("/").append(contactId).append("/invoices.xml"); request.append("?api_key=" + InvoiceXpress.getInstance().getActiveAccount().getApiKey()); if(filterCode == DocumentFilterFragment.FILTER_CODE_ARCHIVED) { request.append("&filter[archived]=only_archived"); } else if(filterCode == DocumentFilterFragment.FILTER_CODE_OVER_DUE) { request.append("&filter[overdue]=1"); } if(page != null && !page.isEmpty()) { request.append("&page=" + page); } if(InvoiceXpress.DEBUG) { Log.d(DocumentsRestHandler.class.getCanonicalName(), request.toString()); } return request.toString(); } }