de.geeksfactory.opacclient.apis.Zones22.java Source code

Java tutorial

Introduction

Here is the source code for de.geeksfactory.opacclient.apis.Zones22.java

Source

/**
 * Copyright (C) 2013 by Raphael Michel under the MIT license:
 * 
 * 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.
 */
package de.geeksfactory.opacclient.apis;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.SocketException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.nodes.TextNode;
import org.jsoup.select.Elements;

import de.geeksfactory.opacclient.NotReachableException;
import de.geeksfactory.opacclient.i18n.StringProvider;
import de.geeksfactory.opacclient.objects.Account;
import de.geeksfactory.opacclient.objects.AccountData;
import de.geeksfactory.opacclient.objects.Detail;
import de.geeksfactory.opacclient.objects.DetailledItem;
import de.geeksfactory.opacclient.objects.Filter;
import de.geeksfactory.opacclient.objects.Filter.Option;
import de.geeksfactory.opacclient.objects.Library;
import de.geeksfactory.opacclient.objects.SearchRequestResult;
import de.geeksfactory.opacclient.objects.SearchResult;
import de.geeksfactory.opacclient.objects.SearchResult.MediaType;
import de.geeksfactory.opacclient.searchfields.DropdownSearchField;
import de.geeksfactory.opacclient.searchfields.SearchField;
import de.geeksfactory.opacclient.searchfields.SearchQuery;
import de.geeksfactory.opacclient.searchfields.TextSearchField;

/**
 * API fr Web-Opacs von Zones mit dem Hinweis "Zones.2.2.45.04" im Footer.
 * Einziger bekannter Einsatzort ist Hamburg.
 * 
 * TODO: Suche nach Medientypen, alles mit Konten + Vorbestellen
 * 
 */
public class Zones22 extends BaseApi {

    private String opac_url = "";
    private JSONObject data;
    private int page;
    private String searchobj;
    private String accountobj;

    private static HashMap<String, MediaType> defaulttypes = new HashMap<String, MediaType>();
    static {
        defaulttypes.put("Buch", MediaType.BOOK);
        defaulttypes.put("Buch/Druckschrift", MediaType.BOOK);
        defaulttypes.put("Buch Erwachsene", MediaType.BOOK);
        defaulttypes.put("Buch Kinder/Jugendliche", MediaType.BOOK);
        defaulttypes.put("Kinder-Buch", MediaType.BOOK);
        defaulttypes.put("DVD", MediaType.DVD);
        defaulttypes.put("Kinder-DVD", MediaType.DVD);
        defaulttypes.put("Konsolenspiele", MediaType.GAME_CONSOLE);
        defaulttypes.put("Blu-ray Disc", MediaType.BLURAY);
        defaulttypes.put("Compact Disc", MediaType.CD);
        defaulttypes.put("CD-ROM", MediaType.CD_SOFTWARE);
        defaulttypes.put("Kinder-CD", MediaType.CD_SOFTWARE);
        defaulttypes.put("Noten", MediaType.SCORE_MUSIC);
        defaulttypes.put("Zeitschrift, Heft", MediaType.MAGAZINE);
        defaulttypes.put("E-Book", MediaType.EBOOK);
        defaulttypes.put("CDROM", MediaType.CD_SOFTWARE);
        defaulttypes.put("E-Audio", MediaType.MP3);
        defaulttypes.put("CD", MediaType.CD);
    }

    @Override
    public List<SearchField> getSearchFields() throws ClientProtocolException, IOException {
        List<SearchField> fields = new ArrayList<SearchField>();
        String html = httpGet(
                opac_url + "/APS_ZONES?fn=AdvancedSearch&Style=Portal3&SubStyle=&Lang=GER&ResponseEncoding=utf-8",
                getDefaultEncoding());

        Document doc = Jsoup.parse(html);

        // Textfelder auslesen
        Elements txt_opts = doc.select("#formSelectTerm_1 option");
        for (Element opt : txt_opts) {
            TextSearchField field = new TextSearchField();
            field.setId(opt.attr("value"));
            field.setHint("");
            field.setDisplayName(opt.text());
            fields.add(field);
        }

        // Zweigstellen auslesen
        Elements zst_opts = doc.select(".TabRechAv .limitChoice label");
        if (zst_opts.size() > 0) {
            DropdownSearchField brDropdown = new DropdownSearchField();
            brDropdown.setId(zst_opts.get(0).parent().select("input").attr("name"));
            brDropdown.setDisplayName("Zweigstelle");

            List<Map<String, String>> brOptions = new ArrayList<Map<String, String>>();
            Map<String, String> all = new HashMap<String, String>();
            all.put("key", "");
            all.put("value", "Alle");
            brOptions.add(all);
            for (Element opt : zst_opts) {
                Map<String, String> value = new HashMap<String, String>();
                value.put("key", opt.attr("for"));
                value.put("value", opt.text().trim());
                brOptions.add(value);
            }
            brDropdown.setDropdownValues(brOptions);
            fields.add(brDropdown);
        }

        return fields;
    }

    @Override
    public void start() throws ClientProtocolException, SocketException, IOException, NotReachableException {
        String html = httpGet(
                opac_url + "/APS_ZONES?fn=AdvancedSearch&Style=Portal3&SubStyle=&Lang=GER&ResponseEncoding=utf-8",
                getDefaultEncoding());

        Document doc = Jsoup.parse(html);

        searchobj = doc.select("#ExpertSearch").attr("action");
        super.start();
    }

    @Override
    public void init(Library lib) {
        super.init(lib);

        this.library = lib;
        this.data = lib.getData();

        try {
            this.opac_url = data.getString("baseurl");
        } catch (JSONException e) {
            throw new RuntimeException(e);
        }
    }

    public static String getStringFromBundle(Map<String, String> bundle, String key) {
        // Workaround for Bundle.getString(key, default) being available not
        // before API 12
        String res = bundle.get(key);
        if (res == null)
            res = "";
        return res;
    }

    private int addParameters(SearchQuery query, List<NameValuePair> params, int index) {
        if (query.getValue().equals(""))
            return index;

        if (query.getSearchField() instanceof TextSearchField) {
            if (index != 1)
                params.add(new BasicNameValuePair(".form.t" + index + ".logic", "and"));
            params.add(new BasicNameValuePair("q.form.t" + index + ".term", query.getKey()));
            params.add(new BasicNameValuePair("q.form.t" + index + ".expr", query.getValue()));
            return index + 1;
        } else if (query.getSearchField() instanceof DropdownSearchField) {
            params.add(new BasicNameValuePair(query.getKey(), query.getValue()));
        }
        return index;
    }

    @Override
    public SearchRequestResult search(List<SearchQuery> queries)
            throws IOException, NotReachableException, OpacErrorException {
        start();

        List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("Style", "Portal3"));
        params.add(new BasicNameValuePair("SubStyle", ""));
        params.add(new BasicNameValuePair("Lang", "GER"));
        params.add(new BasicNameValuePair("ResponseEncoding", "utf-8"));
        params.add(new BasicNameValuePair("Method", "QueryWithLimits"));
        params.add(new BasicNameValuePair("SearchType", "AdvancedSearch"));
        params.add(new BasicNameValuePair("TargetSearchType", "AdvancedSearch"));
        params.add(new BasicNameValuePair("DB", "SearchServer"));
        params.add(new BasicNameValuePair("q.PageSize", "10"));

        int index = 1;
        for (SearchQuery query : queries) {
            index = addParameters(query, params, index);
        }

        if (index > 3) {
            throw new OpacErrorException(
                    stringProvider.getFormattedString(StringProvider.LIMITED_NUM_OF_CRITERIA, 3));
        } else if (index == 1) {
            throw new OpacErrorException(stringProvider.getString(StringProvider.NO_CRITERIA_INPUT));
        }

        String html = httpGet(opac_url + "/" + searchobj + "?" + URLEncodedUtils.format(params, "UTF-8"),
                getDefaultEncoding());

        page = 1;

        return parse_search(html, page);
    }

    @Override
    public SearchRequestResult searchGetPage(int page)
            throws IOException, NotReachableException, OpacErrorException {
        List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("Style", "Portal3"));
        params.add(new BasicNameValuePair("SubStyle", ""));
        params.add(new BasicNameValuePair("Lang", "GER"));
        params.add(new BasicNameValuePair("ResponseEncoding", "utf-8"));
        if (page > this.page) {
            params.add(new BasicNameValuePair("Method", "PageDown"));
        } else {
            params.add(new BasicNameValuePair("Method", "PageUp"));
        }
        params.add(new BasicNameValuePair("PageSize", "10"));

        String html = httpGet(opac_url + "/" + searchobj + "?" + URLEncodedUtils.format(params, "UTF-8"),
                getDefaultEncoding());
        this.page = page;

        return parse_search(html, page);
    }

    private SearchRequestResult parse_search(String html, int page) throws OpacErrorException {
        Document doc = Jsoup.parse(html);
        doc.setBaseUri(opac_url + "/APS_PRESENT_BIB");

        if (doc.select("#ErrorAdviceRow").size() > 0) {
            throw new OpacErrorException(doc.select("#ErrorAdviceRow").text().trim());
        }

        int results_total = -1;

        if (doc.select(".searchHits").size() > 0) {
            results_total = Integer.parseInt(
                    doc.select(".searchHits").first().text().trim().replaceAll(".*\\(([0-9]+)\\).*", "$1"));
        }

        if (doc.select(".pageNavLink").size() > 0) {
            searchobj = doc.select(".pageNavLink").first().attr("href").split("\\?")[0];
        }

        Elements table = doc.select("#BrowseList > tbody > tr");
        List<SearchResult> results = new ArrayList<SearchResult>();
        for (int i = 0; i < table.size(); i++) {
            Element tr = table.get(i);
            SearchResult sr = new SearchResult();

            String typetext = tr.select(".SummaryMaterialTypeField").text().replace("\n", " ").trim();
            if (data.has("mediatypes")) {
                try {
                    sr.setType(MediaType.valueOf(data.getJSONObject("mediatypes").getString(typetext)));
                } catch (JSONException e) {
                    sr.setType(defaulttypes.get(typetext));
                } catch (IllegalArgumentException e) {
                    sr.setType(defaulttypes.get(typetext));
                }
            } else {
                sr.setType(defaulttypes.get(typetext));
            }

            if (tr.select(".SummaryImageCell img[id^=Bookcover]").size() > 0) {
                String imgUrl = tr.select(".SummaryImageCell img[id^=Bookcover]").first().attr("src");
                sr.setCover(imgUrl);
            }

            String desc = "";
            Elements children = tr.select(".SummaryDataCell tr, .SummaryDataCellStripe tr");
            int childrennum = children.size();
            boolean haslink = false;

            for (int ch = 0; ch < childrennum; ch++) {
                Element node = children.get(ch);
                if (node.select(".SummaryFieldLegend").text().equals("Titel")) {
                    desc += "<b>" + node.select(".SummaryFieldData").text().trim() + "</b><br />";

                } else if (node.select(".SummaryFieldLegend").text().equals("Verfasser")
                        || node.select(".SummaryFieldLegend").text().equals("Jahr")) {
                    desc += node.select(".SummaryFieldData").text().trim() + "<br />";
                }

                if (node.select(".SummaryFieldData a.SummaryFieldLink").size() > 0 && haslink == false) {
                    String href = node.select(".SummaryFieldData a.SummaryFieldLink").attr("abs:href");
                    Map<String, String> hrefq = getQueryParamsFirst(href);
                    sr.setId(hrefq.get("no"));
                    haslink = true;
                }
            }
            if (desc.endsWith("<br />"))
                desc = desc.substring(0, desc.length() - 6);
            sr.setInnerhtml(desc);
            sr.setNr(i);

            results.add(sr);
        }

        return new SearchRequestResult(results, results_total, page);
    }

    @Override
    public DetailledItem getResultById(String id, String homebranch) throws IOException, NotReachableException {

        List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("Style", "Portal3"));
        params.add(new BasicNameValuePair("SubStyle", ""));
        params.add(new BasicNameValuePair("Lang", "GER"));
        params.add(new BasicNameValuePair("ResponseEncoding", "utf-8"));
        params.add(new BasicNameValuePair("no", id));

        String html = httpGet(opac_url + "/APS_PRESENT_BIB?" + URLEncodedUtils.format(params, "UTF-8"),
                getDefaultEncoding());

        return parse_result(id, html);
    }

    @Override
    public DetailledItem getResult(int nr) throws IOException {
        return null;
    }

    private DetailledItem parse_result(String id, String html) throws IOException {
        Document doc = Jsoup.parse(html);

        DetailledItem result = new DetailledItem();
        result.setTitle("");
        boolean title_is_set = false;

        result.setId(id);

        Elements detaildiv = doc.select("div.record-item-new");

        Elements detailtrs1 = doc.select(".DetailDataCell table table:not(.inRecordHeader) tr");
        for (int i = 0; i < detailtrs1.size(); i++) {
            Element tr = detailtrs1.get(i);
            int s = tr.children().size();
            if (tr.child(0).text().trim().equals("Titel") && !title_is_set) {
                result.setTitle(tr.child(s - 1).text().trim());
                title_is_set = true;
            } else if (s > 1) {
                Element valchild = tr.child(s - 1);
                if (valchild.select("table").isEmpty()) {
                    String val = valchild.text().trim();
                    if (val.length() > 0)
                        result.addDetail(new Detail(tr.child(0).text().trim(), val));
                }
            }
        }

        for (Element a : doc.select("a.SummaryActionLink")) {
            if (a.text().contains("Vormerken")) {
                result.setReservable(true);
                result.setReservation_info(a.attr("href"));
            }
        }

        if (!detaildiv.isEmpty()) {
            for (int i = 0; i < detaildiv.size(); i++) {
                Element dd = detaildiv.get(i);
                String text = "";
                for (Node node : dd.childNodes()) {
                    if (node instanceof TextNode) {
                        String snip = ((TextNode) node).text();
                        if (snip.length() > 0)
                            text += snip;
                    } else if (node instanceof Element) {
                        if (((Element) node).tagName().equals("br"))
                            text += "\n";
                        else {
                            String snip = ((Element) node).text().trim();
                            if (snip.length() > 0)
                                text += snip;
                        }
                    }
                }
                result.addDetail(new Detail("", text));
            }
        }

        if (doc.select("span.z3988").size() > 0) {
            // Sometimes there is a <span class="Z3988"> item which provides
            // data in a standardized format.
            String z3988data = doc.select("span.z3988").first().attr("title").trim();
            for (String pair : z3988data.split("\\&")) {
                String[] nv = pair.split("=", 2);
                if (nv.length == 2) {
                    if (!nv[1].trim().equals("")) {
                        if (nv[0].equals("rft.btitle") && result.getTitle().length() == 0) {
                            result.setTitle(nv[1]);
                        } else if (nv[0].equals("rft.atitle") && result.getTitle().length() == 0) {
                            result.setTitle(nv[1]);
                        } else if (nv[0].equals("rft.au")) {
                            result.addDetail(new Detail("Author", nv[1]));
                        }
                    }
                }
            }
        }

        Elements copydivs = doc.select(".DetailDataCell div[id^=stock_]");
        String pop = "";
        for (int i = 0; i < copydivs.size(); i++) {
            Element div = copydivs.get(i);

            if (div.attr("id").startsWith("stock_head")) {
                pop = div.text().trim();
                continue;
            }

            Map<String, String> copy = new HashMap<String, String>();

            // This is getting very ugly - check if it is valid for libraries
            // which are not
            // Hamburg.
            int j = 0;
            for (Node node : div.childNodes()) {
                try {
                    if (node instanceof Element) {
                        if (((Element) node).tag().getName().equals("br")) {
                            copy.put(DetailledItem.KEY_COPY_BRANCH, pop);
                            result.addCopy(copy);
                            j = -1;
                        } else if (((Element) node).tag().getName().equals("b") && j == 1) {
                            copy.put(DetailledItem.KEY_COPY_LOCATION, ((Element) node).text());
                        } else if (((Element) node).tag().getName().equals("b") && j > 1) {
                            copy.put(DetailledItem.KEY_COPY_STATUS, ((Element) node).text());
                        }
                        j++;
                    } else if (node instanceof TextNode) {
                        if (j == 0)
                            copy.put(DetailledItem.KEY_COPY_DEPARTMENT, ((TextNode) node).text());
                        if (j == 2)
                            copy.put(DetailledItem.KEY_COPY_BARCODE,
                                    ((TextNode) node).getWholeText().trim().split("\n")[0].trim());
                        if (j == 6) {
                            String text = ((TextNode) node).text().trim();
                            copy.put(DetailledItem.KEY_COPY_RETURN, text.substring(text.length() - 10));
                        }
                        j++;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        return result;
    }

    @Override
    public ReservationResult reservation(DetailledItem item, Account acc, int useraction, String selection)
            throws IOException {
        String reservation_info = item.getReservation_info();
        String html = httpGet(opac_url + "/" + reservation_info, getDefaultEncoding());
        Document doc = Jsoup.parse(html);
        if (html.contains("Geheimnummer")) {
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            for (Element input : doc.select("#MainForm input")) {
                if (!input.attr("name").equals("BRWR") && !input.attr("name").equals("PIN")) {
                    params.add(new BasicNameValuePair(input.attr("name"), input.attr("value")));
                }
            }
            params.add(new BasicNameValuePair("BRWR", acc.getName()));
            params.add(new BasicNameValuePair("PIN", acc.getPassword()));
            html = httpGet(opac_url + "/" + doc.select("#MainForm").attr("action") + "?"
                    + URLEncodedUtils.format(params, getDefaultEncoding()), getDefaultEncoding());
            doc = Jsoup.parse(html);
        }

        if (useraction == ReservationResult.ACTION_BRANCH) {
            List<NameValuePair> params = new ArrayList<NameValuePair>();
            for (Element input : doc.select("#MainForm input")) {
                if (!input.attr("name").equals("Confirm")) {
                    params.add(new BasicNameValuePair(input.attr("name"), input.attr("value")));
                }

            }
            params.add(new BasicNameValuePair("MakeResTypeDef.Reservation.RecipientLocn", selection));
            params.add(new BasicNameValuePair("Confirm", "1"));
            html = httpGet(opac_url + "/" + doc.select("#MainForm").attr("action") + "?"
                    + URLEncodedUtils.format(params, getDefaultEncoding()), getDefaultEncoding());
            return new ReservationResult(MultiStepResult.Status.OK);
        }

        if (useraction == 0) {
            ReservationResult res = null;
            for (Node n : doc.select("#MainForm").first().childNodes()) {
                if (n instanceof TextNode) {
                    if (((TextNode) n).text().contains("Entgelt")) {
                        res = new ReservationResult(ReservationResult.Status.CONFIRMATION_NEEDED);
                        List<String[]> details = new ArrayList<String[]>();
                        details.add(new String[] { ((TextNode) n).text().trim() });
                        res.setDetails(details);
                        res.setMessage(((TextNode) n).text().trim());
                        res.setActionIdentifier(MultiStepResult.ACTION_CONFIRMATION);
                    }
                }
            }
            if (res != null)
                return res;
        }
        if (doc.select("#MainForm select").size() > 0) {
            ReservationResult res = new ReservationResult(ReservationResult.Status.SELECTION_NEEDED);
            Map<String, String> sel = new HashMap<String, String>();
            for (Element opt : doc.select("#MainForm select option")) {
                sel.put(opt.attr("value"), opt.text().trim());
            }
            res.setSelection(sel);
            res.setMessage("Bitte Zweigstelle auswhlen");
            res.setActionIdentifier(ReservationResult.ACTION_BRANCH);
            return res;
        }

        return new ReservationResult(ReservationResult.Status.ERROR);
    }

    @Override
    public ProlongResult prolong(String media, Account account, int useraction, String Selection)
            throws IOException {
        if (accountobj == null) {
            try {
                login(account);
            } catch (OpacErrorException e) {
                return new ProlongResult(MultiStepResult.Status.ERROR, e.getMessage());
            }
        }
        String html = httpGet(opac_url + "/" + media, getDefaultEncoding());
        Document doc = Jsoup.parse(html);
        if ((html.contains("document.location.replace") || html.contains("Schnellsuche")) && useraction == 0) {
            try {
                login(account);
            } catch (OpacErrorException e) {
                return new ProlongResult(MultiStepResult.Status.ERROR, e.getMessage());
            }
            prolong(media, account, 1, null);
        }
        String dialog = doc.select("#SSRenewDlgContent").text();
        if (dialog.contains("erfolgreich"))
            return new ProlongResult(MultiStepResult.Status.OK, dialog);
        else
            return new ProlongResult(MultiStepResult.Status.ERROR, dialog);
    }

    @Override
    public CancelResult cancel(String media, Account account, int useraction, String selection)
            throws IOException, OpacErrorException {
        throw new UnsupportedOperationException();
    }

    private Document login(Account acc) throws IOException, OpacErrorException {
        String html = httpGet(
                opac_url + "/APS_ZONES?fn=MyZone&Style=Portal3&SubStyle=&Lang=GER&ResponseEncoding=utf-8",
                getDefaultEncoding());
        Document doc = Jsoup.parse(html);
        doc.setBaseUri(opac_url + "/APS_ZONES");
        if (doc.select(".AccountSummaryCounterLink").size() > 0) {
            return doc;
        }
        if (doc.select("#LoginForm").size() == 0) {
            throw new NotReachableException();
        }
        List<NameValuePair> params = new ArrayList<NameValuePair>();

        for (Element input : doc.select("#LoginForm input")) {
            if (!input.attr("name").equals("BRWR") && !input.attr("name").equals("PIN"))
                params.add(new BasicNameValuePair(input.attr("name"), input.attr("value")));
        }
        params.add(new BasicNameValuePair("BRWR", acc.getName()));
        params.add(new BasicNameValuePair("PIN", acc.getPassword()));

        String loginHtml;
        try {
            loginHtml = httpPost(doc.select("#LoginForm").get(0).absUrl("action"), new UrlEncodedFormEntity(params),
                    getDefaultEncoding());
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }

        if (!loginHtml.contains("Kontostand")) {
            throw new OpacErrorException(stringProvider.getString(StringProvider.LOGIN_FAILED));
        }

        Document doc2 = Jsoup.parse(loginHtml);
        Pattern objid_pat = Pattern.compile("Obj_([0-9]+)\\?.*");
        for (Element a : doc2.select("a")) {
            Matcher objid_matcher = objid_pat.matcher(a.attr("href"));
            if (objid_matcher.matches()) {
                accountobj = objid_matcher.group(1);
            }
        }

        return doc2;
    }

    @Override
    public AccountData account(Account acc)
            throws IOException, NotReachableException, JSONException, SocketException, OpacErrorException {
        Document login = login(acc);
        if (login == null)
            return null;

        AccountData res = new AccountData(acc.getId());

        String lent_link = null;
        String res_link = null;
        int lent_cnt = -1;
        int res_cnt = -1;
        for (Element td : login.select(
                ".AccountSummaryCounterNameCell, .AccountSummaryCounterNameCellStripe, .CAccountDetailFieldNameCellStripe, .CAccountDetailFieldNameCell")) {
            String section = td.text().trim();
            if (section.contains("Entliehene Medien")) {
                lent_link = td.select("a").attr("href");
                lent_cnt = Integer.parseInt(td.nextElementSibling().text().trim());
            } else if (section.contains("Vormerkungen")) {
                res_link = td.select("a").attr("href");
                res_cnt = Integer.parseInt(td.nextElementSibling().text().trim());
            } else if (section.contains("Kontostand")) {
                res.setPendingFees(td.nextElementSibling().text().trim());
            } else if (section.matches("Ausweis g.ltig bis")) {
                res.setValidUntil(td.nextElementSibling().text().trim());
            }
        }
        assert (lent_cnt >= 0);
        assert (res_cnt >= 0);
        if (lent_link == null)
            return null;

        String lent_html = httpGet(opac_url + "/" + lent_link.replace("utf-8?Method", "utf-8&Method"),
                getDefaultEncoding());
        Document lent_doc = Jsoup.parse(lent_html);
        List<Map<String, String>> lent = new ArrayList<Map<String, String>>();

        SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy", Locale.GERMAN);
        Pattern id_pat = Pattern.compile("javascript:renewItem\\('[0-9]+','(.*)'\\)");

        for (Element table : lent_doc
                .select(".LoansBrowseItemDetailsCellStripe table, .LoansBrowseItemDetailsCell table")) {
            Map<String, String> item = new HashMap<String, String>();

            for (Element tr : table.select("tr")) {
                String desc = tr.select(".LoanBrowseFieldNameCell").text().trim();
                String value = tr.select(".LoanBrowseFieldDataCell").text().trim();
                if (desc.equals("Titel"))
                    item.put(AccountData.KEY_LENT_TITLE, value);
                if (desc.equals("Verfasser"))
                    item.put(AccountData.KEY_LENT_AUTHOR, value);
                if (desc.equals("Mediennummer"))
                    item.put(AccountData.KEY_LENT_BARCODE, value);
                if (desc.equals("ausgeliehen in"))
                    item.put(AccountData.KEY_LENT_BRANCH, value);
                if (desc.matches("F.+lligkeits.*datum")) {
                    value = value.split(" ")[0];
                    item.put(AccountData.KEY_LENT_DEADLINE, value);
                    try {
                        item.put(AccountData.KEY_LENT_DEADLINE_TIMESTAMP,
                                String.valueOf(sdf.parse(value).getTime()));
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }
            if (table.select(".button[Title~=Zum]").size() == 1) {
                Matcher matcher1 = id_pat.matcher(table.select(".button[Title~=Zum]").attr("href"));
                if (matcher1.matches()) {
                    item.put(AccountData.KEY_LENT_LINK, matcher1.group(1));
                }
            }
            lent.add(item);
        }
        res.setLent(lent);
        assert (lent_cnt <= lent.size());

        List<Map<String, String>> reservations = new ArrayList<Map<String, String>>();
        String res_html = httpGet(opac_url + "/" + res_link, getDefaultEncoding());
        Document res_doc = Jsoup.parse(res_html);

        for (Element table : res_doc
                .select(".MessageBrowseItemDetailsCell table, .MessageBrowseItemDetailsCellStripe table")) {
            Map<String, String> item = new HashMap<String, String>();

            for (Element tr : table.select("tr")) {
                String desc = tr.select(".MessageBrowseFieldNameCell").text().trim();
                String value = tr.select(".MessageBrowseFieldDataCell").text().trim();
                if (desc.equals("Titel"))
                    item.put(AccountData.KEY_RESERVATION_TITLE, value);
                if (desc.equals("Publikationsform"))
                    item.put(AccountData.KEY_RESERVATION_FORMAT, value);
                if (desc.equals("Liefern an"))
                    item.put(AccountData.KEY_RESERVATION_BRANCH, value);
                if (desc.equals("Status"))
                    item.put(AccountData.KEY_RESERVATION_READY, value);
            }
            if ("Gelscht".equals(item.get(AccountData.KEY_RESERVATION_READY))) {
                continue;
            }
            reservations.add(item);
        }
        res.setReservations(reservations);
        assert (reservations.size() >= res_cnt);

        return res;
    }

    @Override
    public boolean isAccountSupported(Library library) {
        return true;
    }

    @Override
    public boolean isAccountExtendable() {
        return false;
    }

    @Override
    public String getAccountExtendableInfo(Account acc)
            throws ClientProtocolException, SocketException, IOException, NotReachableException {
        return null;
    }

    @Override
    public String getShareUrl(String id, String title) {
        List<NameValuePair> params = new ArrayList<NameValuePair>();

        params.add(new BasicNameValuePair("Style", "Portal3"));
        params.add(new BasicNameValuePair("SubStyle", ""));
        params.add(new BasicNameValuePair("Lang", "GER"));
        params.add(new BasicNameValuePair("ResponseEncoding", "utf-8"));
        params.add(new BasicNameValuePair("no", id));

        return opac_url + "/APS_PRESENT_BIB?" + URLEncodedUtils.format(params, "UTF-8");
    }

    @Override
    public int getSupportFlags() {
        return SUPPORT_FLAG_ENDLESS_SCROLLING | SUPPORT_FLAG_CHANGE_ACCOUNT;
    }

    @Override
    public ProlongAllResult prolongAll(Account account, int useraction, String selection) throws IOException {
        return null;
    }

    @Override
    public SearchRequestResult filterResults(Filter filter, Option option) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    protected String getDefaultEncoding() {
        return "UTF-8";
    }

    @Override
    public void checkAccountData(Account account) throws IOException, JSONException, OpacErrorException {
        Document login = login(account);
        if (login == null)
            throw new NotReachableException();
    }

    @Override
    public void setLanguage(String language) {
        // TODO Auto-generated method stub

    }

    @Override
    public Set<String> getSupportedLanguages() throws IOException {
        // TODO Auto-generated method stub
        return null;
    }

}