package de.geeksfactory.opacclient.apis;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.message.BasicNameValuePair;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.json.JSONArray;
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.TextNode;
import org.jsoup.select.Elements;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import de.geeksfactory.opacclient.i18n.StringProvider;
import de.geeksfactory.opacclient.objects.Account;
import de.geeksfactory.opacclient.objects.AccountData;
import de.geeksfactory.opacclient.objects.DetailedItem;
import de.geeksfactory.opacclient.objects.LentItem;
import de.geeksfactory.opacclient.objects.ReservedItem;
/**
* API for the PICA OPAC by OCLC with the old default PICA account functions
*
* @author Johan von Forstner, 30.08.2015
*/
public class PicaOld extends Pica {
@Override
public ReservationResult reservation(DetailedItem item, Account account,
int useraction, String selection) throws IOException {
try {
if (selection == null || !selection.startsWith("{")) {
JSONArray json = new JSONArray(item.getReservation_info());
int selectedPos;
if (json.length() == 1) {
selectedPos = 0;
} else if (selection != null) {
selectedPos = Integer.parseInt(selection);
} else {
// a location must be selected
ReservationResult res = new ReservationResult(
MultiStepResult.Status.SELECTION_NEEDED);
res.setActionIdentifier(ReservationResult.ACTION_BRANCH);
List<Map<String, String>> selections = new ArrayList<>();
for (int i = 0; i < json.length(); i++) {
Map<String, String> selopt = new HashMap<>();
selopt.put("key", String.valueOf(i));
selopt.put("value", json.getJSONObject(i).getString("desc"));
selections.add(selopt);
}
res.setSelection(selections);
return res;
}
try {
URL link = new URL(json.getJSONObject(selectedPos).getString("link"));
if (!opac_url.contains(link.getHost())) {
ReservationResult res = new ReservationResult(
MultiStepResult.Status.EXTERNAL);
res.setMessage(link.toString());
return res;
}
} catch (MalformedURLException e) {
// empty on purpose
}
if (json.getJSONObject(selectedPos).getBoolean("multi")) {
// A copy must be selected
String html1 = httpGet(json.getJSONObject(selectedPos)
.getString("link"), getDefaultEncoding());
Document doc1 = Jsoup.parse(html1);
Elements trs = doc1
.select("table[summary=list of volumes header] tr:has" +
"(input[type=radio])");
if (trs.size() > 0) {
List<Map<String, String>> selections = new ArrayList<>();
for (Element tr : trs) {
JSONObject values = new JSONObject();
for (Element input : doc1
.select("input[type=hidden]")) {
values.put(input.attr("name"),
input.attr("value"));
}
values.put(tr.select("input").attr("name"), tr
.select("input").attr("value"));
Map<String, String> selopt = new HashMap<>();
selopt.put("key", values.toString());
selopt.put("value", tr.text());
selections.add(selopt);
}
ReservationResult res = new ReservationResult(
MultiStepResult.Status.SELECTION_NEEDED);
res.setActionIdentifier(ReservationResult.ACTION_USER);
res.setMessage(stringProvider
.getString(StringProvider.PICA_WHICH_COPY));
res.setSelection(selections);
return res;
} else {
ReservationResult res = new ReservationResult(
MultiStepResult.Status.ERROR);
res.setMessage(stringProvider
.getString(StringProvider.NO_COPY_RESERVABLE));
return res;
}
} else {
String html1 = httpGet(json.getJSONObject(selectedPos)
.getString("link"), getDefaultEncoding());
Document doc1 = Jsoup.parse(html1);
Map<String, String> params = new HashMap<>();
if (doc1.select("input[type=radio][name=CTRID]").size() > 0 &&
selection == null) {
ReservationResult res = new ReservationResult(
MultiStepResult.Status.SELECTION_NEEDED);
res.setActionIdentifier(ReservationResult.ACTION_BRANCH);
List<Map<String, String>> selections = new ArrayList<>();
for (Element input : doc1.select("input[type=radio][name=CTRID]")) {
Map<String, String> selopt = new HashMap<>();
selopt.put("key", input.attr("value"));
selopt.put("value", input.parent().parent().text().trim());
selections.add(selopt);
}
res.setSelection(selections);
return res;
} else {
params.put("CTRID", selection);
}
for (Element input : doc1.select("input[type=hidden]")) {
if (!input.attr("name").equals("CTRID") || selection == null) {
params.put(input.attr("name"), input.attr("value"));
}
}
params.put("BOR_U", account.getName());
params.put("BOR_PW", account.getPassword());
List<NameValuePair> paramlist = new ArrayList<>();
for (Map.Entry<String, String> param : params.entrySet()) {
paramlist.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}
return reservation_result(paramlist,
doc1.select("form").attr("action").contains("REQCONT"));
}
} else {
// A copy has been selected
JSONObject values = new JSONObject(selection);
List<NameValuePair> params = new ArrayList<>();
//noinspection unchecked
Iterator<String> keys = values.keys();
while (keys.hasNext()) {
String key = keys.next();
String value = values.getString(key);
params.add(new BasicNameValuePair(key, value));
}
params.add(new BasicNameValuePair("BOR_U", account.getName()));
params.add(new BasicNameValuePair("BOR_PW", account
.getPassword()));
return reservation_result(params, true);
}
} catch (JSONException e) {
e.printStackTrace();
ReservationResult res = new ReservationResult(MultiStepResult.Status.ERROR);
res.setMessage(stringProvider.getString(StringProvider.INTERNAL_ERROR));
return res;
}
}
public ReservationResult reservation_result(List<NameValuePair> params,
boolean multi) throws IOException {
String html2 = httpPost(https_url + "/loan/DB=" + db + "/LNG="
+ getLang() + "/SET=" + searchSet + "/TTL=1/"
+ (multi ? "REQCONT" : "RESCONT"), new UrlEncodedFormEntity(
params, getDefaultEncoding()), getDefaultEncoding());
Document doc2 = Jsoup.parse(html2);
String alert = doc2.select(".alert").text().trim();
if (alert.contains("ist fuer Sie vorgemerkt")
|| alert.contains("has been reserved")) {
return new ReservationResult(MultiStepResult.Status.OK);
} else {
ReservationResult res = new ReservationResult(
MultiStepResult.Status.ERROR);
res.setMessage(doc2.select(".cnt .alert").text());
return res;
}
}
@Override
public ProlongResult prolong(String media, Account account, int useraction,
String Selection) throws IOException {
if (pwEncoded == null) {
try {
account(account);
} catch (JSONException e1) {
return new ProlongResult(MultiStepResult.Status.ERROR);
} catch (OpacErrorException e1) {
return new ProlongResult(MultiStepResult.Status.ERROR,
e1.getMessage());
}
}
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("ACT", "UI_RENEWLOAN"));
params.add(new BasicNameValuePair("BOR_U", account.getName()));
params.add(new BasicNameValuePair("BOR_PW_ENC", URLDecoder.decode(
pwEncoded, "UTF-8")));
params.add(new BasicNameValuePair("VB", media));
String html = httpPost(https_url + "/loan/DB=" + db + "/LNG="
+ getLang() + "/USERINFO", new UrlEncodedFormEntity(params,
getDefaultEncoding()), getDefaultEncoding());
Document doc = Jsoup.parse(html);
if (doc.select("td.regular-text")
.text()
.contains(
"Die Leihfrist Ihrer ausgeliehenen Publikationen ist ")
|| doc.select("td.regular-text").text()
.contains("Ihre ausgeliehenen Publikationen sind verl")) {
return new ProlongResult(MultiStepResult.Status.OK);
} else if (doc.select(".cnt").text().contains("identify")) {
try {
account(account);
return prolong(media, account, useraction, Selection);
} catch (JSONException e) {
return new ProlongResult(MultiStepResult.Status.ERROR);
} catch (OpacErrorException e) {
return new ProlongResult(MultiStepResult.Status.ERROR,
e.getMessage());
}
} else {
ProlongResult res = new ProlongResult(MultiStepResult.Status.ERROR);
res.setMessage(doc.select(".cnt").text());
return res;
}
}
@Override
public ProlongAllResult prolongAll(Account account, int useraction,
String selection) throws IOException {
return null;
}
@Override
public CancelResult cancel(String media, Account account, int useraction,
String selection) throws IOException, OpacErrorException {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("ACT", "UI_CANCELRES"));
params.add(new BasicNameValuePair("BOR_U", account.getName()));
params.add(new BasicNameValuePair("BOR_PW_ENC", URLDecoder.decode(
pwEncoded, "UTF-8")));
if (lor_reservations != null) {
params.add(new BasicNameValuePair("LOR_RESERVATIONS", lor_reservations));
}
params.add(new BasicNameValuePair("VB", media));
String html = httpPost(https_url + "/loan/DB=" + db + "/LNG="
+ getLang() + "/SET=" + searchSet + "/TTL=1/USERINFO",
new UrlEncodedFormEntity(params, getDefaultEncoding()),
getDefaultEncoding());
Document doc = Jsoup.parse(html);
if (doc.select("td.regular-text").text()
.contains("Ihre Vormerkungen sind ")) {
return new CancelResult(MultiStepResult.Status.OK);
} else if (doc.select(".cnt .alert").text()
.contains("identify yourself")) {
try {
account(account);
return cancel(media, account, useraction, selection);
} catch (JSONException e) {
throw new OpacErrorException(
stringProvider.getString(StringProvider.INTERNAL_ERROR));
}
} else {
CancelResult res = new CancelResult(MultiStepResult.Status.ERROR);
res.setMessage(doc.select(".cnt").text());
return res;
}
}
@Override
public AccountData account(Account account) throws IOException,
JSONException, OpacErrorException {
if (!initialised) {
start();
}
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("ACT", "UI_DATA"));
params.add(new BasicNameValuePair("HOST_NAME", ""));
params.add(new BasicNameValuePair("HOST_PORT", ""));
params.add(new BasicNameValuePair("HOST_SCRIPT", ""));
params.add(new BasicNameValuePair("LOGIN", "KNOWNUSER"));
params.add(new BasicNameValuePair("STATUS", "HML_OK"));
params.add(new BasicNameValuePair("BOR_U", account.getName()));
params.add(new BasicNameValuePair("BOR_PW", account.getPassword()));
String html = httpPost(https_url + "/loan/DB=" + db + "/LNG="
+ getLang() + "/USERINFO", new UrlEncodedFormEntity(params,
getDefaultEncoding()), getDefaultEncoding());
Document doc = Jsoup.parse(html);
AccountData res = new AccountData(account.getId());
if (doc.select(".cnt .alert, .cnt .error").size() > 0) {
String text = doc.select(".cnt .alert, .cnt .error").text();
if (doc.select("table[summary^=User data]").size() > 0) {
res.setWarning(text);
} else {
throw new OpacErrorException(text);
}
}
//noinspection StatementWithEmptyBody
if (doc.select("input[name=BOR_PW_ENC]").size() > 0) {
pwEncoded = URLEncoder.encode(doc.select("input[name=BOR_PW_ENC]")
.attr("value"), "UTF-8");
} else {
// TODO: do something here to help fix bug #229
}
html = httpGet(https_url + "/loan/DB=" + db + "/LNG=" + getLang()
+ "/USERINFO?ACT=UI_LOL&BOR_U=" + account.getName()
+ "&BOR_PW_ENC=" + pwEncoded, getDefaultEncoding());
doc = Jsoup.parse(html);
html = httpGet(https_url + "/loan/DB=" + db + "/LNG=" + getLang()
+ "/USERINFO?ACT=UI_LOR&BOR_U=" + account.getName()
+ "&BOR_PW_ENC=" + pwEncoded, getDefaultEncoding());
Document doc2 = Jsoup.parse(html);
List<LentItem> media = new ArrayList<>();
List<ReservedItem> reserved = new ArrayList<>();
if (doc.select("table[summary^=list]").size() > 0
&& !doc.select(".alert").text().contains("Keine Entleihungen")
&& !doc.select(".alert").text().contains("No outstanding loans")
&& !doc.select(".alert").text().contains("Geen uitlening")
&& !doc.select(".alert").text().contains("Aucun emprunt")) {
List<String> renewalCounts = loadRenewalCounts(doc);
parseMediaList(media, doc, stringProvider, renewalCounts);
}
if (doc2.select("table[summary^=list]").size() > 0
&& !doc2.select(".alert").text().contains("Keine Vormerkungen")
&& !doc2.select(".alert").text().contains("No outstanding reservations")
&& !doc2.select(".alert").text().contains("Geen reservering")
&& !doc2.select(".alert").text().contains("Aucune réservation")) {
updateLorReservations(doc);
parseResList(reserved, doc2, stringProvider);
}
res.setLent(media);
res.setReservations(reserved);
return res;
}
private List<String> loadRenewalCounts(Document doc) {
List<String> renewalCounts = new ArrayList<>();
for (Element iframe : doc.select("iframe[name=nr_renewals_in_a_box]")) {
try {
String html = httpGet(iframe.attr("src"), getDefaultEncoding());
renewalCounts.add(Jsoup.parse(html).text());
} catch (IOException e) {
renewalCounts.add(null);
}
}
return renewalCounts;
}
static void parseMediaList(List<LentItem> media, Document doc,
StringProvider stringProvider, List<String> renewalCounts) throws OpacErrorException {
Elements copytrs = doc.select("table[summary^=list] > tbody > tr[valign=top]");
DateTimeFormatter fmt = DateTimeFormat.forPattern("dd-MM-yyyy").withLocale(Locale.GERMAN);
int trs = copytrs.size();
if (trs < 1) {
throw new OpacErrorException(
stringProvider.getString(StringProvider.COULD_NOT_LOAD_ACCOUNT));
}
assert (trs > 0);
for (int i = 0; i < trs; i++) {
Element tr = copytrs.get(i);
if (tr.select("table[summary=title data]").size() > 0) {
// According to HTML code from Bug reports (Server TU Darmstadt,
// Berlin Ibero-Amerikanisches Institut)
LentItem item = new LentItem();
// Check if there is a checkbox to prolong this item
if (tr.select("input").size() > 0) {
item.setProlongData(tr.select("input").attr("value"));
} else {
item.setRenewable(false);
}
Elements datatrs = tr.select("table[summary=title data] tr");
item.setTitle(datatrs.get(0).text());
for (Element td : datatrs.get(1).select("td")) {
List<TextNode> textNodes = td.textNodes();
Elements titles = td.select("span.label-small");
List<String> values = new ArrayList<>();
if (td.select("span[name=xxxxx]").size() > 0) {
for (Element span : td.select("span[name=xxxxx]")) {
values.add(span.text());
}
} else {
for (TextNode node : textNodes) {
if (!node.text().equals(" ")) {
values.add(node.text());
}
}
}
assert (values.size() == titles.size());
for (int j = 0; j < values.size(); j++) {
String title = titles.get(j).text();
String value = values.get(j).trim().replace(";", "");
//noinspection StatementWithEmptyBody
if (title.contains("Signatur")
|| title.contains("shelf mark")
|| title.contains("signatuur")) {
// not supported
} else if (title.contains("Status")
|| title.contains("status")
|| title.contains("statut")) {
item.setStatus(value);
} else if (title.contains("Leihfristende")
|| title.contains("expiry date")
|| title.contains("vervaldatum")
|| title.contains("date d'expiration")) {
try {
item.setDeadline(fmt.parseLocalDate(value));
} catch (IllegalArgumentException e1) {
e1.printStackTrace();
}
} else //noinspection StatementWithEmptyBody
if (title.contains("Vormerkungen")
|| title.contains("reservations")
|| title.contains("reserveringen")
|| title.contains("réservations")) {
// not supported
}
}
}
media.add(item);
} else { // like in Kiel
String prolongCount = "";
if (renewalCounts.size() == trs && renewalCounts.get(i) != null) {
prolongCount = renewalCounts.get(i);
}
String reminderCount = tr.child(13).text().trim();
if (reminderCount.contains(" Mahn")
&& reminderCount.contains("(")
&& reminderCount.indexOf("(") < reminderCount
.indexOf(" Mahn")) {
reminderCount = reminderCount.substring(
reminderCount.indexOf("(") + 1,
reminderCount.indexOf(" Mahn"));
} else {
reminderCount = "";
}
LentItem item = new LentItem();
if (tr.child(4).text().trim().length() < 5
&& tr.child(5).text().trim().length() > 4) {
item.setTitle(tr.child(5).text().trim());
} else {
item.setTitle(tr.child(4).text().trim());
}
String status = tr.child(13).text().trim();
if (!reminderCount.equals("0") && !reminderCount.equals("")) {
if (!status.equals("")) status += ", ";
status += reminderCount + " " + stringProvider
.getString(StringProvider.REMINDERS) + ", ";
}
if (!status.equals("")) status += ", ";
status += prolongCount + "x " + stringProvider
.getString(StringProvider.PROLONGED_ABBR);
// + tr.child(25).text().trim() + " Vormerkungen");
item.setStatus(status);
try {
item.setDeadline(fmt.parseLocalDate(tr.child(21).text().trim()));
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
if (tr.child(1).select("input").size() > 0) {
// If there is no checkbox, the medium is not renewable
item.setProlongData(tr.child(1).select("input").attr("value"));
}
media.add(item);
}
}
assert (media.size() == trs);
}
static void parseResList(List<ReservedItem> media, Document doc,
StringProvider stringProvider) throws OpacErrorException {
Elements copytrs = doc.select("table[summary^=list] > tbody > tr[valign=top]");
int trs = copytrs.size();
if (trs < 1) {
throw new OpacErrorException(
stringProvider.getString(StringProvider.COULD_NOT_LOAD_ACCOUNT));
}
assert (trs > 0);
for (Element tr : copytrs) {
ReservedItem item = new ReservedItem();
if (tr.select("table[summary=title data]").size() > 0) {
// According to HTML code from Bug report (UB Frankfurt)
// Check if there is a checkbox to cancel this item
if (tr.select("input").size() > 0) {
item.setCancelData(tr.select("input").attr("value"));
}
Elements datatrs = tr.select("table[summary=title data] tr");
item.setTitle(datatrs.get(0).text());
List<TextNode> textNodes = datatrs.get(1).select("td").first()
.textNodes();
List<TextNode> nodes = new ArrayList<>();
Elements titles = datatrs.get(1).select("span.label-small");
for (TextNode node : textNodes) {
if (!node.text().equals(" ")) {
nodes.add(node);
}
}
assert (nodes.size() == titles.size());
for (int j = 0; j < nodes.size(); j++) {
String title = titles.get(j).text();
String value = nodes.get(j).text().trim().replace(";", "");
//noinspection StatementWithEmptyBody
if (title.contains("Signatur")
|| title.contains("shelf mark")
|| title.contains("signatuur")) {
// not supported
} else //noinspection StatementWithEmptyBody
if (title.contains("Vormerkdatum")) {
// not supported
}
}
} else {
// like in Kiel
item.setTitle(tr.child(5).text().trim());
item.setStatus(tr.child(17).text().trim());
item.setCancelData(tr.child(1).select("input").attr("value"));
}
media.add(item);
}
assert (media.size() == trs);
}
private void updateLorReservations(Document doc) {
if (doc.select("input[name=LOR_RESERVATIONS]").size() > 0) {
lor_reservations = doc.select("input[name=LOR_RESERVATIONS]").attr("value");
}
}
@Override
public void checkAccountData(Account account) throws IOException,
JSONException, OpacErrorException {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("ACT", "UI_DATA"));
params.add(new BasicNameValuePair("HOST_NAME", ""));
params.add(new BasicNameValuePair("HOST_PORT", ""));
params.add(new BasicNameValuePair("HOST_SCRIPT", ""));
params.add(new BasicNameValuePair("LOGIN", "KNOWNUSER"));
params.add(new BasicNameValuePair("STATUS", "HML_OK"));
params.add(new BasicNameValuePair("BOR_U", account.getName()));
params.add(new BasicNameValuePair("BOR_PW", account.getPassword()));
String html = httpPost(https_url + "/loan/DB=" + db + "/LNG="
+ getLang() + "/USERINFO", new UrlEncodedFormEntity(params,
getDefaultEncoding()), getDefaultEncoding());
Document doc = Jsoup.parse(html);
if (doc.select(".cnt .alert, .cnt .error").size() > 0) {
throw new OpacErrorException(doc.select(".cnt .alert, .cnt .error")
.text());
}
}
}