/**
* Copyright (C) 2014 by Johan von Forstner 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 org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
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.select.Elements;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import de.geeksfactory.opacclient.i18n.StringProvider;
import de.geeksfactory.opacclient.networking.HttpClientFactory;
import de.geeksfactory.opacclient.objects.Account;
import de.geeksfactory.opacclient.objects.AccountData;
import de.geeksfactory.opacclient.objects.Copy;
import de.geeksfactory.opacclient.objects.Detail;
import de.geeksfactory.opacclient.objects.DetailedItem;
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;
/**
* @author Johan von Forstner, 06.04.2014
*
* WebOPAC.net, Version 2.2.70 gestartet mit Gemeindebibliothek Nürensdorf (erstes
* Google-Suchergebnis)
*
* Unterstützt bisher nur Katalogsuche, Accountunterstüzung könnte (wenn keine Kontodaten
* verfügbar sind) über den Javascript-Code reverse-engineered werden:
* http://www.winmedio.net/nuerensdorf/de/mobile/GetScript.ashx?id=mobile.de.min.js&v=20140122
*/
/*
weitere kompatible Bibliotheken:
https://www.google.de/search?q=webOpac.net%202.1.30%20powered%20by%20winMedio.net&qscrl=1#q=%22webOpac.net+2.2.70+powered+by+winMedio.net%22+inurl%3Awinmedio&qscrl=1&start=0
*/
public class WebOpacNet extends BaseApi implements OpacApi {
protected static HashMap<String, MediaType> defaulttypes = new HashMap<>();
static {
defaulttypes.put("1", MediaType.BOOK);
defaulttypes.put("2", MediaType.CD_MUSIC);
defaulttypes.put("3", MediaType.AUDIOBOOK);
defaulttypes.put("4", MediaType.DVD);
defaulttypes.put("5", MediaType.CD_SOFTWARE);
defaulttypes.put("8", MediaType.MAGAZINE);
}
protected String opac_url = "";
protected JSONObject data;
protected List<SearchQuery> query;
@Override
public void init(Library lib, HttpClientFactory httpClientFactory) {
super.init(lib, httpClientFactory);
this.data = lib.getData();
try {
this.opac_url = data.getString("baseurl");
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
@Override
public SearchRequestResult search(List<SearchQuery> query)
throws IOException, OpacErrorException,
JSONException {
this.query = query;
List<NameValuePair> params = new ArrayList<>();
start();
int index = buildParams(query, params, 1);
if (index == 0) {
throw new OpacErrorException(
stringProvider.getString(StringProvider.NO_CRITERIA_INPUT));
}
String json = httpGet(opac_url + "/de/mobile/GetMedien.ashx"
+ buildHttpGetParams(params),
getDefaultEncoding());
return parse_search(json, 1);
}
protected int addParameters(SearchQuery query, StringBuilder params,
int index) {
if (query.getValue().equals("")) {
return index;
}
if (index > 0) {
params.append("$0");
}
params.append("|").append(query.getKey()).append("|").append(query.getValue());
return index + 1;
}
private SearchRequestResult parse_search(String text, int page)
throws OpacErrorException {
if (!text.equals("")) {
try {
List<SearchResult> results = new ArrayList<>();
JSONObject json = new JSONObject(text);
int total_result_count = Integer.parseInt(json
.getString("totalcount"));
JSONArray resultList = json.getJSONArray("mobmeds");
for (int i = 0; i < resultList.length(); i++) {
JSONObject resultJson = resultList.getJSONObject(i);
SearchResult result = new SearchResult();
result.setId(resultJson.getString("medid"));
String title = resultJson.getString("titel");
String publisher = resultJson.getString("verlag");
String series = resultJson.getString("reihe");
String html = "<b>" + title + "</b><br />" + publisher
+ ", " + series;
String type = resultJson.getString("iconurl").substring(12,
13);
result.setType(defaulttypes.get(type));
result.setInnerhtml(html);
if (resultJson.getString("imageurl").length() > 0) {
result.setCover(resultJson.getString("imageurl"));
}
results.add(result);
}
return new SearchRequestResult(results, total_result_count,
page);
} catch (JSONException e) {
e.printStackTrace();
throw new OpacErrorException(stringProvider.getFormattedString(
StringProvider.INTERNAL_ERROR_WITH_DESCRIPTION,
e.getMessage()));
}
} else {
return new SearchRequestResult(new ArrayList<SearchResult>(), 0,
page);
}
}
@Override
public SearchRequestResult filterResults(Filter filter, Option option)
throws IOException, OpacErrorException {
// TODO Auto-generated method stub
return null;
}
@Override
public SearchRequestResult searchGetPage(int page) throws IOException,
OpacErrorException, JSONException {
List<NameValuePair> params = new ArrayList<>();
start();
int index = buildParams(query, params, page);
if (index == 0) {
throw new OpacErrorException(
stringProvider.getString(StringProvider.NO_CRITERIA_INPUT));
}
String json = httpGet(opac_url + "/de/mobile/GetMedien.ashx"
+ buildHttpGetParams(params),
getDefaultEncoding());
return parse_search(json, page);
}
private int buildParams(List<SearchQuery> queryList,
List<NameValuePair> params, int page) throws JSONException {
int index = 0;
StringBuilder queries = new StringBuilder();
queries.append("erw:0");
for (SearchQuery query : queryList) {
if (!query.getSearchField().getData().getBoolean("filter")) {
index = addParameters(query, queries, index);
}
}
for (SearchQuery query : queryList) {
if (query.getSearchField().getData().getBoolean("filter")
&& !query.getValue().equals("")) {
queries.append("&").append(query.getKey()).append("=").append(query.getValue());
}
}
params.add(new BasicNameValuePair("q", queries.toString()));
params.add(new BasicNameValuePair("p", String.valueOf(page - 1)));
params.add(new BasicNameValuePair("t", "1"));
return index;
}
@Override
public DetailedItem getResultById(String id, String homebranch)
throws IOException, OpacErrorException {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("id", id));
params.add(new BasicNameValuePair("orientation", "1"));
String json = httpGet(opac_url + "/de/mobile/GetDetail.ashx"
+ buildHttpGetParams(params),
getDefaultEncoding());
return parse_detail(json);
}
private DetailedItem parse_detail(String text) throws OpacErrorException {
try {
DetailedItem result = new DetailedItem();
JSONObject json = new JSONObject(text);
result.setTitle(json.getString("titel"));
result.setCover(json.getString("imageurl"));
result.setId(json.getString("medid"));
// Details
JSONArray info = json.getJSONArray("medium");
for (int i = 0; i < info.length(); i++) {
JSONObject detailJson = info.getJSONObject(i);
String name = detailJson.getString("bez");
String value = "";
JSONArray values = detailJson.getJSONArray("values");
for (int j = 0; j < values.length(); j++) {
JSONObject valJson = values.getJSONObject(j);
if (j != 0) {
value += ", ";
}
String content = valJson.getString("dval");
content = content.replaceAll("<span[^>]*>", "");
content = content.replaceAll("</span>", "");
value += content;
}
Detail detail = new Detail(name, value);
result.addDetail(detail);
}
// Copies
JSONArray copies = json.getJSONArray("exemplare");
for (int i = 0; i < copies.length(); i++) {
JSONObject copyJson = copies.getJSONObject(i);
Copy copy = new Copy();
JSONArray values = copyJson.getJSONArray("rows");
for (int j = 0; j < values.length(); j++) {
JSONObject valJson = values.getJSONObject(j);
String name = valJson.getString("bez");
String value = valJson.getJSONArray("values")
.getJSONObject(0).getString("dval");
if (!value.equals("")) {
switch (name) {
case "Exemplarstatus":
copy.setStatus(value);
break;
case "Signatur":
copy.setShelfmark(value);
break;
case "Standort":
copy.setLocation(value);
break;
case "Themenabteilung":
if (copy.getDepartment() != null) {
value = copy.getDepartment() + value;
}
copy.setDepartment(value);
break;
case "Themenbereich":
if (copy.getDepartment() != null) {
value = copy.getDepartment() + value;
}
copy.setDepartment(value);
break;
}
}
}
result.addCopy(copy);
}
return result;
} catch (JSONException e) {
e.printStackTrace();
throw new OpacErrorException(stringProvider.getFormattedString(
StringProvider.INTERNAL_ERROR_WITH_DESCRIPTION,
e.getMessage()));
}
}
@Override
public DetailedItem getResult(int position) throws IOException,
OpacErrorException {
// TODO Auto-generated method stub
return null;
}
@Override
public ReservationResult reservation(DetailedItem item, Account account,
int useraction, String selection) throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public ProlongResult prolong(String media, Account account, int useraction,
String selection) throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public ProlongAllResult prolongAll(Account account, int useraction,
String selection) throws IOException {
// TODO Auto-generated method stub
return null;
}
@Override
public CancelResult cancel(String media, Account account, int useraction,
String selection) throws IOException, OpacErrorException {
// TODO Auto-generated method stub
return null;
}
@Override
public AccountData account(Account account) throws IOException,
JSONException, OpacErrorException {
// TODO Auto-generated method stub
return null;
}
@Override
public List<SearchField> parseSearchFields() throws IOException,
JSONException {
List<SearchField> fields = new ArrayList<>();
// Text fields
String html = httpGet(opac_url + "/de/mobile/default.aspx",
getDefaultEncoding());
Document doc = Jsoup.parse(html);
Elements options = doc.select("#drpOptSearchT option");
for (Element option : options) {
TextSearchField field = new TextSearchField();
field.setDisplayName(option.text());
field.setId(option.attr("value"));
field.setData(new JSONObject("{\"filter\":false}"));
field.setHint("");
fields.add(field);
}
// Dropdowns
String text = httpGet(opac_url + "/de/mobile/GetRestrictions.ashx",
getDefaultEncoding());
JSONArray filters = new JSONObject(text)
.getJSONArray("restrcontainers");
for (int i = 0; i < filters.length(); i++) {
JSONObject filter = filters.getJSONObject(i);
if (filter.getString("querytyp").equals("EJ")) {
// Querying by year also works for other years than the ones
// listed
// -> Make it a text field instead of a dropdown
TextSearchField field = new TextSearchField();
field.setDisplayName(filter.getString("kopf"));
field.setId(filter.getString("querytyp"));
field.setData(new JSONObject("{\"filter\":true}"));
field.setHint("");
fields.add(field);
} else {
DropdownSearchField field = new DropdownSearchField();
field.setId(filter.getString("querytyp"));
field.setDisplayName(filter.getString("kopf"));
JSONArray restrictions = filter.getJSONArray("restrictions");
field.addDropdownValue("", "Alle");
for (int j = 0; j < restrictions.length(); j++) {
JSONObject restriction = restrictions.getJSONObject(j);
field.addDropdownValue(restriction.getString("id"),
restriction.getString("bez"));
}
field.setData(new JSONObject("{\"filter\":true}"));
fields.add(field);
}
}
return fields;
}
@Override
public String getShareUrl(String id, String title) {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("id", id));
String url;
try {
url = opac_url + "/default.aspx"
+ buildHttpGetParams(params);
return url;
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
@Override
public int getSupportFlags() {
return SUPPORT_FLAG_ENDLESS_SCROLLING | SUPPORT_FLAG_CHANGE_ACCOUNT;
}
@Override
protected String getDefaultEncoding() {
return "UTF-8";
}
@Override
public void checkAccountData(Account account) throws IOException,
JSONException, OpacErrorException {
// TODO Auto-generated method stub
}
@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;
}
}