net.vexelon.mobileops.GLBClient.java Source code

Java tutorial

Introduction

Here is the source code for net.vexelon.mobileops.GLBClient.java

Source

/*
 * MyGlob Android Application
 * 
 * Copyright (C) 2010 Petar Petrov
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package net.vexelon.mobileops;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;

import net.vexelon.myglob.configuration.Defs;
import net.vexelon.myglob.utils.TrustAllSocketFactory;
import net.vexelon.myglob.utils.UserAgentHelper;
import net.vexelon.myglob.utils.Utils;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.SingleClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import android.util.Log;

public class GLBClient implements IClient {

    private static final String HTTP_MYTELENOR = "https://my.telenor.bg";

    private static final int DEFAULT_BUFFER_SIZE = 1024;
    private static final String RESPONSE_ENCODING = "windows-1251";
    private static final int MAX_REQUEST_RETRIES = 2;

    private String username;
    private String password;
    private DefaultHttpClient httpClient = null;
    private CookieStore httpCookieStore = null;
    private HttpContext httpContext = null;

    private HashMap<GLBRequestType, String> operationsHash;

    private long bytesDownloaded = 0;
    private long invoiceDateTime;

    public GLBClient(String username, String password) {
        this.username = username;
        this.password = password;

        initHttpClient();
    }

    public void close() {
        if (httpClient != null)
            httpClient.getConnectionManager().shutdown();
    }

    /**
     * Perform login into the web system using the specified user and password
     */
    public void login() throws HttpClientException, InvalidCredentialsException, SecureCodeRequiredException {

        try {
            List<NameValuePair> qparams = new ArrayList<NameValuePair>();
            //qparams.add(new BasicNameValuePair("refid", ""));
            //         qparams.add(new BasicNameValuePair("continuation",
            //               URLDecoder.decode("myglobul.portal%3Faction%3Duserhome%26pkey%3D0%26jkey%3D0",
            //                     "UTF-8")));

            //         qparams.add(new BasicNameValuePair("image.x", Integer.toString(Utils.getRandomInt(1024)) ));
            //         qparams.add(new BasicNameValuePair("image.y", Integer.toString(Utils.getRandomInt(768)) ));
            qparams.add(new BasicNameValuePair("password", password));
            qparams.add(new BasicNameValuePair("username", username));
            qparams.add(new BasicNameValuePair("_eventId", "submit"));
            qparams.addAll(findLoginParams());

            handleLogin(qparams);

        } catch (UnsupportedEncodingException e) {
            throw new HttpClientException("Failed to create url! " + e.getMessage(), e);
        }
    }

    public void logout() throws HttpClientException {

        StringBuilder fullUrl = new StringBuilder(100);
        //      fullUrl.append(HTTP_MYGLOBUL_SITE).append(GLBRequestType.LOGOUT.getPath()).append("?")
        //         .append(GLBRequestType.LOGOUT.getParams());
        fullUrl.append(GLBRequestType.LOGOUT.getPath()).append("?").append(GLBRequestType.LOGOUT.getParams());

        try {
            HttpGet httpGet = new HttpGet(fullUrl.toString());
            HttpResponse resp = httpClient.execute(httpGet, httpContext);
            StatusLine status = resp.getStatusLine();

            if (status.getStatusCode() != HttpStatus.SC_OK)
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());

            resp.getEntity().consumeContent();
        } catch (ClientProtocolException e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        } catch (IOException e) {
            throw new HttpClientException("Client error!" + e.getMessage(), e);
        }
    }

    public String getCurrentBalance() throws HttpClientException {

        StringBuilder builder = new StringBuilder(100);
        HttpResponse resp;
        long bytesCount = 0;
        try {
            String url = HTTP_MYTELENOR + GLBRequestType.GET_BALANCE.getPath();
            url += '?';
            url += new Date().getTime();

            HttpGet httpGet = new HttpGet(url);
            //         httpGet.setHeader("X-Requested-With", "XMLHttpRequest");
            resp = httpClient.execute(httpGet, httpContext);
        } catch (Exception e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        }

        StatusLine status = resp.getStatusLine();

        if (status.getStatusCode() != HttpStatus.SC_OK)
            throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());

        try {
            HttpEntity entity = resp.getEntity();
            // bytes downloaded
            bytesCount = entity.getContentLength() > 0 ? entity.getContentLength() : 0;

            Document doc = Jsoup.parse(entity.getContent(), RESPONSE_ENCODING, "");
            Elements elements;

            // period bill
            elements = doc.select("#outstanding-amount");
            if (elements.size() > 0) {
                Elements divs = elements.get(0).select("div");
                for (Element el : divs) {
                    String elClass = el.className();
                    if (elClass.contains("custme-select") || elClass.equalsIgnoreCase("history")) {
                        builder.insert(0, el.html());
                    }
                }
            }

            // current bill
            elements = doc.select("#bars-wrapper .p-price");
            if (elements.size() > 0) {
                Element el = elements.get(0);
                builder.insert(0, el.html());
            }

            return builder.toString();

        } catch (ClientProtocolException e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        } catch (IOException e) {
            throw new HttpClientException("Client error!" + e.getMessage(), e);
        } finally {
            addDownloadedBytesCount(bytesCount);
        }
    }

    @Override
    public byte[] getInvoiceData() throws HttpClientException, InvoiceException {

        return findInvoiceExportParams();
    }

    @Override
    public long getInvoiceDateTime() {
        return invoiceDateTime;
    }

    @Override
    public long getDownloadedBytesCount() {
        if (Defs.LOG_ENABLED)
            Log.d(Defs.LOG_TAG, "Get KBs: " + bytesDownloaded / 1024.0f);

        return bytesDownloaded;
    }

    private void addDownloadedBytesCount(long bytesCount) {
        if (Defs.LOG_ENABLED)
            Log.d(Defs.LOG_TAG, "Added bytes: " + bytesCount);

        bytesDownloaded += bytesCount;
    }

    // ---

    /**
     * Initialize Http Client
     */
    private void initHttpClient() {

        HttpParams params = new BasicHttpParams();
        params.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1);
        params.setParameter(CoreProtocolPNames.USER_AGENT, UserAgentHelper.getRandomUserAgent());
        //params.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8);
        // Bugfix #1: The target server failed to respond
        params.setParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, Boolean.FALSE);

        DefaultHttpClient client = new DefaultHttpClient(params);

        httpCookieStore = new BasicCookieStore();
        client.setCookieStore(httpCookieStore);

        httpContext = new BasicHttpContext();
        httpContext.setAttribute(ClientContext.COOKIE_STORE, httpCookieStore);

        // Bugfix #1: Adding retry handler to repeat failed requests
        HttpRequestRetryHandler retryHandler = new HttpRequestRetryHandler() {

            @Override
            public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {

                if (executionCount >= MAX_REQUEST_RETRIES) {
                    return false;
                }

                if (exception instanceof NoHttpResponseException || exception instanceof ClientProtocolException) {
                    return true;
                }

                return false;
            }
        };
        client.setHttpRequestRetryHandler(retryHandler);

        // SSL
        HostnameVerifier verifier = org.apache.http.conn.ssl.SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER;

        try {
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", new PlainSocketFactory(), 80));
            registry.register(new Scheme("https", new TrustAllSocketFactory(), 443));

            SingleClientConnManager connMgr = new SingleClientConnManager(client.getParams(), registry);

            httpClient = new DefaultHttpClient(connMgr, client.getParams());
        } catch (InvalidAlgorithmParameterException e) {
            //         Log.e(Defs.LOG_TAG, "", e);

            // init without connection manager
            httpClient = new DefaultHttpClient(client.getParams());
        }

        HttpsURLConnection.setDefaultHostnameVerifier(verifier);

    }

    /**
     * Login logic
     * @param qparams
     */
    private void handleLogin(List<NameValuePair> qparams) throws HttpClientException, InvalidCredentialsException,
            SecureCodeRequiredException, UnsupportedEncodingException {

        StringBuilder fullUrl = new StringBuilder(100);
        fullUrl.append(GLBRequestType.LOGIN.getPath())
                //      .append("?service=" + URLDecoder.decode(
                //            "https%3A%2F%2Fmy.globul.bg%2Fmg%2Fmyglobul.portal%3Faction%3Duserhome%26pkey%3D0%26jkey%3D0", "UTF-8"));
                .append("?asid=s01&service=https%3A%2F%2Fmy.telenor.bg%2Flogin");

        HttpPost httpPost = createPostRequest(fullUrl.toString(), qparams);
        HttpResponse resp;
        BufferedReader reader = null;
        long bytesCount = 0;

        try {
            resp = httpClient.execute(httpPost, httpContext);
        } catch (Exception e) {
            throw new HttpClientException("Client  protocol error!" + e.getMessage(), e);
        }

        StatusLine status = resp.getStatusLine();

        if (status.getStatusCode() == HttpStatus.SC_OK) {
            try {
                reader = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));

                boolean loggedIn = false;
                String line = null;

                while ((line = reader.readLine()) != null) {

                    bytesCount += line.length();

                    if (line.indexOf("action=pdetails") != -1 || line.indexOf("action=chpass") != -1
                            || line.indexOf("action=logoff") != -1) {
                        // if the username is not present in the content, then we're obviously not logged in
                        loggedIn = true;
                        break;
                    }

                    //check if secure code image is sent
                    if (line.indexOf("/mg/my/GetImage?refid=") != -1 || line.indexOf("j_captcha_response") != -1) {
                        //TODO: retrieve image url
                        //<img class="code" alt="? ? ? ?     , ? 
                        // ?   ? ?." src="/mg/my/GetImage?refid=b7b8fa558b461f1e2d400ae0f3348f2f">
                        throw new SecureCodeRequiredException("");
                    }
                }

                if (!loggedIn) {
                    // TODO: Seems that occasionally the login response would return
                    // something totally different than the welcome page.
                    // So we cannot count on the markers to be always correct. We just assume
                    // that we have logged in and we proceed with the actions further.
                    Log.w(Defs.LOG_TAG, "Could not locate login markers!");
                    //               throw new InvalidCredentialsException();
                }

            } catch (IOException e) {
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
            } finally {
                if (reader != null)
                    try {
                        reader.close();
                    } catch (IOException e) {
                    }
                ;
                // bytes downloaded
                addDownloadedBytesCount(bytesCount);
            }

        } else if (status.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY
                || status.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY
                || status.getStatusCode() == HttpStatus.SC_SEE_OTHER
                || status.getStatusCode() == HttpStatus.SC_TEMPORARY_REDIRECT) {

            // XXX Kind of a hack (sometimes we get 302 from the web serv),
            //     May not work if Globul changes impl.   
            //      What we should do is proper redirect and parsing.
            // See http://hc.apache.org/httpclient-legacy/redirects.html

            //         String redirectLocation;
            //           Header locationHeader = resp.getFirstHeader("location");
            //           if (locationHeader != null) {
            //               redirectLocation = locationHeader.getValue();
            //           } else {
            //               // The response is invalid and did not provide the new location for
            //               // the resource.  Report an error or possibly handle the response
            //               // like a 404 Not Found error.
            //           }

            try {
                resp.getEntity().consumeContent();

                // bytes downloaded
                // XXX This could be misleading!
                if (resp.getEntity().getContentLength() > 0) {
                    addDownloadedBytesCount(resp.getEntity().getContentLength());
                }

            } catch (IOException e) {
                throw new HttpClientException("Could not consume MOVED_TEMPORARILY content!", e);
            }

        } else {
            // Unhandled response
            throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
        }
    }

    private byte[] findInvoiceExportParams() throws HttpClientException, InvoiceException {

        BufferedReader reader = null;
        long bytesCount = 0;
        StringBuilder xmlUrl = new StringBuilder(100);
        String invoiceDate = Long.toString(new Date().getTime()); // today

        byte[] resultData = null;
        try {
            // Get invoice check page
            StringBuilder fullUrl = new StringBuilder(100);
            fullUrl.append(HTTP_MYTELENOR).append(GLBRequestType.PAGE_INVOICE.getPath());

            HttpGet httpGet = new HttpGet(fullUrl.toString());
            HttpResponse resp = httpClient.execute(httpGet, httpContext);
            StatusLine status = resp.getStatusLine();

            // Construct invoice id url
            fullUrl.setLength(0);
            fullUrl.append(HTTP_MYTELENOR);

            if (status.getStatusCode() == HttpStatus.SC_OK) {
                // bytes downloaded
                bytesCount += resp.getEntity().getContentLength() > 0 ? resp.getEntity().getContentLength() : 0;
                // Find invoice id
                Document doc = Jsoup.parse(resp.getEntity().getContent(), RESPONSE_ENCODING, "");
                Elements links = doc.select("a");
                for (Element el : links) {
                    String href = el.attributes().get("href");
                    if (href != null && href.contains("invId")) {
                        fullUrl.append("/").append(href);
                        break;
                    }
                }
            } else {
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
            }

            // Fetch invoice download parameters
            httpGet = new HttpGet(fullUrl.toString());
            resp = httpClient.execute(httpGet, httpContext);
            status = resp.getStatusLine();
            if (status.getStatusCode() == HttpStatus.SC_OK) {
                String line = null;
                reader = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
                while ((line = reader.readLine()) != null) {
                    // bytes downloaded
                    bytesCount += line.length();

                    if (line.contains("ei2_open_file") && line.contains("xml") && line.contains("summary")) {
                        if (Defs.LOG_ENABLED)
                            Log.d(Defs.LOG_TAG, line);

                        /*
                         * This is a g'damn hack. We don't need fancy stuff ;)
                         */
                        xmlUrl.append(HTTP_MYTELENOR).append(GLBRequestType.GET_INVOICE.getPath())
                                .append("?file_name=summary").append("&file_type=xml").append("&lower_bound=0")
                                .append("&upper_bound=0");

                        //                  ?file_name=summary
                        //                  &file_type=xml
                        //                  &lower_bound=0
                        //                  &upper_bound=0
                        //                  &invoiceNumber=1234567890
                        //                  &bill_acc_id=1231231
                        //                  &custnum=001234567
                        //                  &servicetype=1
                        //                  &period=1414792800000
                        //                  &cust_acc_id=1011111
                        //                  &custCode10=1.111111
                        //                  &prgCode=1

                        // extract keyword
                        String keys[] = { "file_type", "file_name", "lower_bound", "upper_bound", "invoiceNumber",
                                "bill_acc_id", "custnum", "servicetype", "period", "cust_acc_id", "custCode10",
                                "prgCode" };
                        String parts[] = line.split(",");
                        if (parts.length > 5) {
                            for (int i = 5; i < parts.length - 1; i++) {
                                String value = parts[i].replace("'", "").trim();

                                xmlUrl.append("&").append(keys[i]).append("=").append(value); // strip single quotes

                                // we need the invoice date
                                if (keys[i].equals("period")) {
                                    invoiceDate = value;
                                }
                            }
                            // the last param is a bit tricky, because we need to remove the <a> data
                            int lastidx = parts.length - 1;
                            parts = parts[lastidx].split("\\)");
                            xmlUrl.append("&").append(keys[lastidx]).append("=").append(parts[0].replace("'", ""));
                        } else {
                            Log.e(Defs.LOG_TAG, "Got line: " + line);
                            throw new IOException("Invalid invoice fingerprint!");
                        }
                        break;
                    }
                }
            } else {
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
            }

            // close current stream reader
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                }
            ;

            // Fetch Invoice XML
            if (Defs.LOG_ENABLED)
                Log.v(Defs.LOG_TAG, "Fetching invoice XML from: " + xmlUrl.toString());

            if (xmlUrl.length() == 0) {
                throw new InvoiceException("Invoice HTTP url not available!");
            }

            httpGet = new HttpGet(xmlUrl.toString());

            resp = httpClient.execute(httpGet, httpContext);
            status = resp.getStatusLine();
            if (status.getStatusCode() == HttpStatus.SC_OK) {
                resultData = Utils.read(resp.getEntity().getContent());
                // add loaded bytes
                bytesCount += resultData.length;
            } else {
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
            }

        } catch (ClientProtocolException e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        } catch (IOException e) {
            throw new HttpClientException("Client error!" + e.getMessage(), e);
        } finally {
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                }
            ;

            addDownloadedBytesCount(bytesCount);
        }
        // parse invoice datetime
        try {
            invoiceDateTime = Long.parseLong(invoiceDate); //invoiceDate.substring(0, invoiceDate.length() - 3));
        } catch (NumberFormatException e) {
            // default 
            invoiceDateTime = new Date().getTime();
        }

        return resultData;
    }

    private List<NameValuePair> findLoginParams() throws HttpClientException {

        List<NameValuePair> result = new ArrayList<NameValuePair>();
        BufferedReader reader = null;
        long bytesCount = 0;

        try {
            // Get invoice check page
            StringBuilder fullUrl = new StringBuilder(100);
            fullUrl.append(GLBRequestType.LOGIN.getPath()).append("?").append(GLBRequestType.LOGIN.getParams());

            HttpGet httpGet = new HttpGet(fullUrl.toString());
            HttpResponse resp = httpClient.execute(httpGet, httpContext);
            StatusLine status = resp.getStatusLine();
            if (status.getStatusCode() == HttpStatus.SC_OK) {

                Document doc = Jsoup.parse(resp.getEntity().getContent(), RESPONSE_ENCODING, "");
                Elements inputs = doc.select("input");
                for (Element el : inputs) {
                    //               if (Defs.LOG_ENABLED) {
                    //                  Log.v(Defs.LOG_TAG, "ELEMENT: " + el.tagName());
                    //               }
                    Attributes attrs = el.attributes();
                    //               for (Attribute attr : attrs) {
                    //                  if (Defs.LOG_ENABLED) {
                    //                     Log.v(Defs.LOG_TAG, " " + attr.getKey() + "=" + attr.getValue());
                    //                  }
                    //               }

                    String elName = attrs.get("name");
                    if (elName.equalsIgnoreCase("lt") || elName.equalsIgnoreCase("execution")) {
                        result.add(new BasicNameValuePair(elName, attrs.get("value")));
                    }
                }
            } else {
                throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());
            }

            // close current stream reader
            //         if (reader != null) try { reader.close(); } catch (IOException e) {};

        } catch (ClientProtocolException e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        } catch (IOException e) {
            throw new HttpClientException("Client error!" + e.getMessage(), e);
        } finally {
            if (reader != null)
                try {
                    reader.close();
                } catch (IOException e) {
                }
            ;

            addDownloadedBytesCount(bytesCount);
        }

        return result;
    }

    private HttpPost createPostRequest(String url, List<NameValuePair> qparams)
            throws UnsupportedEncodingException {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(new UrlEncodedFormEntity(qparams, HTTP.UTF_8));
        return httpPost;
    }

    private String doPostRequest(GLBRequestType requestType) throws HttpClientException {

        HttpResponse resp;
        try {
            List<NameValuePair> qparams = requestType.getParamsAsList();
            // Fix Issue #7 - Page does not exist
            qparams.add(new BasicNameValuePair("parameter", this.operationsHash.get(requestType)));

            //         for (NameValuePair nameValuePair : qparams) {
            //            Log.d(Defs.LOG_TAG, "Param: " + nameValuePair.getName() + " = " + nameValuePair.getValue());
            //         }

            HttpPost httpPost = createPostRequest(HTTP_MYTELENOR + requestType.getPath(), qparams);
            httpPost.setHeader("X-Requested-With", "XMLHttpRequest");
            httpPost.setHeader("X-Prototype-Version", "1.6.0.2");

            resp = httpClient.execute(httpPost, httpContext);
        } catch (Exception e) {
            throw new HttpClientException("Client protocol error!" + e.getMessage(), e);
        }

        StatusLine status = resp.getStatusLine();

        if (status.getStatusCode() != HttpStatus.SC_OK)
            throw new HttpClientException(status.getReasonPhrase(), status.getStatusCode());

        HttpEntity entity = resp.getEntity();
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream(DEFAULT_BUFFER_SIZE);
            entity.writeTo(baos);
        } catch (Exception e) {
            throw new HttpClientException("Failed to load response! " + e.getMessage(), e);
        } finally {
            if (baos != null)
                try {
                    baos.close();
                } catch (IOException e) {
                }
            ;
        }

        // bytes downloaded
        addDownloadedBytesCount(baos.size());

        try {
            return new String(baos.toByteArray(), RESPONSE_ENCODING);
        } catch (UnsupportedEncodingException e) {
            return new String(baos.toByteArray()); // XXX check this!
        }
    }
}